app/pages/tools/templates/decision-framework.vue

1141 lines
42 KiB
Vue

<template>
<div>
<!-- Export Options at Top -->
<ExportOptions
:export-data="exportData"
filename="decision-framework"
title="Decision Framework Helper" />
<div
class="template-wrapper bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100">
<!-- Document Container -->
<div class="document-page">
<div class="template-content">
<!-- Document Header -->
<div class="text-center mb-8">
<h1
class="text-3xl md:text-5xl font-bold uppercase text-neutral-900 dark:text-white m-0 py-4 border-t-2 border-b-2 border-neutral-900 dark:border-neutral-100">
Decision Framework
</h1>
</div>
<!-- All Steps Visible -->
<div v-if="!showResult">
<!-- Step 1: Urgency -->
<div class="section-card">
<div class="flex justify-between items-center mb-4">
<h2 class="section-title">1. How urgent is this decision?</h2>
<div class="flex items-center gap-2">
<span
v-if="state.urgency"
class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 font-bold border border-green-300 dark:border-green-700"
> COMPLETE</span
>
<span
v-else
class="px-3 py-1 text-sm bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 border border-neutral-300 dark:border-neutral-600"
>PENDING</span
>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
v-for="option in urgencyOptions"
:key="option.value"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="state.urgency === option.value"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<UCard
:class="[
'cursor-pointer transition-all',
state.urgency === option.value
? 'item-selected border border-neutral-900 dark:border-neutral-100 bg-white dark:bg-neutral-950'
: 'border border-neutral-700 dark:border-neutral-700 hover:border-black dark:hover:border-white hover:bg-black hover:text-white dark:hover:bg-white dark:hover:text-black cursor-pointer ',
]"
:ui="{
body: { padding: 'p-4' },
ring: '',
shadow: '',
divide: '',
}"
@click="selectOption('urgency', option.value)">
<div class="text-xl font-bold mb-1">
{{ option.title }}
</div>
<div class="text-sm">
{{ option.description }}
</div>
</UCard>
</div>
</div>
</div>
<!-- Step 2: Reversibility -->
<div class="section-card">
<div class="flex justify-between items-center mb-4">
<h2 class="section-title">2. Can we change our minds later?</h2>
<div class="flex items-center gap-2">
<span
v-if="state.reversible"
class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 font-bold border border-green-300 dark:border-green-700"
> COMPLETE</span
>
<span
v-else
class="px-3 py-1 text-sm bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 border border-neutral-300 dark:border-neutral-600"
>PENDING</span
>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
v-for="option in reversibilityOptions"
:key="option.value"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="state.reversible === option.value"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<UCard
:class="[
'cursor-pointer transition-all',
state.reversible === option.value
? 'item-selected border border-neutral-900 dark:border-neutral-100 bg-white dark:bg-neutral-950'
: 'border border-neutral-700 dark:border-neutral-700 hover:border-black dark:hover:border-white hover:bg-black hover:text-white dark:hover:bg-white dark:hover:text-black cursor-pointer',
]"
:ui="{
body: { padding: 'p-4' },
ring: '',
shadow: '',
divide: '',
}"
@click="selectOption('reversible', option.value)">
<div class="text-xl font-bold mb-1">
{{ option.title }}
</div>
<div class="text-sm text-neutral-600 dark:text-neutral-400">
{{ option.description }}
</div>
</UCard>
</div>
</div>
</div>
<!-- Step 3: Expertise -->
<div class="section-card">
<div class="flex justify-between items-center mb-4">
<h2 class="section-title">
3. Who has the most relevant expertise?
</h2>
<div class="flex items-center gap-2">
<span
v-if="state.expertise"
class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 font-bold border border-green-300 dark:border-green-700"
> COMPLETE</span
>
<span
v-else
class="px-3 py-1 text-sm bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 border border-neutral-300 dark:border-neutral-600"
>PENDING</span
>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
v-for="option in expertiseOptions"
:key="option.value"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="state.expertise === option.value"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<UCard
:class="[
'cursor-pointer transition-all',
state.expertise === option.value
? 'item-selected border border-neutral-900 dark:border-neutral-100 bg-white dark:bg-neutral-950'
: 'border border-neutral-700 dark:border-neutral-700 hover:border-black dark:hover:border-white hover:bg-black hover:text-white dark:hover:bg-white dark:hover:text-black cursor-pointer',
]"
:ui="{
body: { padding: 'p-4' },
ring: '',
shadow: '',
divide: '',
}"
@click="selectOption('expertise', option.value)">
<div class="text-xl font-bold mb-1">
{{ option.title }}
</div>
<div class="text-sm text-neutral-600 dark:text-neutral-400">
{{ option.description }}
</div>
</UCard>
</div>
</div>
</div>
<!-- Step 4: Impact -->
<div class="section-card">
<div class="flex justify-between items-center mb-4">
<h2 class="section-title">4. Who will this impact?</h2>
<div class="flex items-center gap-2">
<span
v-if="state.impact"
class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 font-bold border border-green-300 dark:border-green-700"
> COMPLETE</span
>
<span
v-else
class="px-3 py-1 text-sm bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 border border-neutral-300 dark:border-neutral-600"
>PENDING</span
>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
v-for="option in impactOptions"
:key="option.value"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="state.impact === option.value"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<UCard
:class="[
'cursor-pointer transition-all',
state.impact === option.value
? 'item-selected border border-neutral-900 dark:border-neutral-100 bg-white dark:bg-neutral-950'
: 'border border-neutral-700 dark:border-neutral-700 hover:border-black dark:hover:border-white hover:bg-black hover:text-white dark:hover:bg-white dark:hover:text-black cursor-pointer',
]"
:ui="{
body: { padding: 'p-4' },
ring: '',
shadow: '',
divide: '',
}"
@click="selectOption('impact', option.value)">
<div class="text-xl font-bold mb-1">
{{ option.title }}
</div>
<div class="text-sm text-neutral-600 dark:text-neutral-400">
{{ option.description }}
</div>
</UCard>
</div>
</div>
</div>
<!-- Step 5: Options clarity -->
<div class="section-card">
<div class="flex justify-between items-center mb-4">
<h2 class="section-title">
5. How well-defined are the options?
</h2>
<div class="flex items-center gap-2">
<span
v-if="state.options"
class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 font-bold border border-green-300 dark:border-green-700"
> COMPLETE</span
>
<span
v-else
class="px-3 py-1 text-sm bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 border border-neutral-300 dark:border-neutral-600"
>PENDING</span
>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
v-for="option in optionsOptions"
:key="option.value"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="state.options === option.value"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<UCard
:class="[
'cursor-pointer transition-all',
state.options === option.value
? 'item-selected border border-neutral-900 dark:border-neutral-100 bg-white dark:bg-neutral-950'
: 'border border-neutral-700 dark:border-neutral-700 hover:border-black dark:hover:border-white hover:bg-black hover:text-white dark:hover:bg-white dark:hover:text-black cursor-pointer',
]"
:ui="{
body: { padding: 'p-4' },
ring: '',
shadow: '',
divide: '',
}"
@click="selectOption('options', option.value)">
<div class="text-xl font-bold mb-1">
{{ option.title }}
</div>
<div class="text-sm text-neutral-600 dark:text-neutral-400">
{{ option.description }}
</div>
</UCard>
</div>
</div>
</div>
<!-- Step 6: Investment -->
<div class="section-card">
<div class="flex justify-between items-center mb-4">
<h2 class="section-title">6. How invested is everyone?</h2>
<div class="flex items-center gap-2">
<span
v-if="state.investment"
class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 font-bold border border-green-300 dark:border-green-700"
> COMPLETE</span
>
<span
v-else
class="px-3 py-1 text-sm bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 border border-neutral-300 dark:border-neutral-600"
>PENDING</span
>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
v-for="option in investmentOptions"
:key="option.value"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="state.investment === option.value"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<UCard
:class="[
'cursor-pointer transition-all',
state.investment === option.value
? 'item-selected border border-neutral-900 dark:border-neutral-100 bg-white dark:bg-neutral-950'
: 'border border-neutral-700 dark:border-neutral-700 hover:border-black dark:hover:border-white hover:bg-black hover:text-white dark:hover:bg-white dark:hover:text-black cursor-pointer',
]"
:ui="{
body: { padding: 'p-4' },
ring: '',
shadow: '',
divide: '',
}"
@click="selectOption('investment', option.value)">
<div class="text-xl font-bold mb-1">
{{ option.title }}
</div>
<div class="text-sm text-neutral-600 dark:text-neutral-400">
{{ option.description }}
</div>
</UCard>
</div>
</div>
</div>
<!-- Step 7: Team size -->
<div class="section-card">
<div class="flex justify-between items-center mb-4">
<h2 class="section-title">
7. How many people need to participate?
</h2>
<div class="flex items-center gap-2">
<span
v-if="state.teamSize"
class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 font-bold border border-green-300 dark:border-green-700"
> COMPLETE</span
>
<span
v-else
class="px-3 py-1 text-sm bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 border border-neutral-300 dark:border-neutral-600"
>PENDING</span
>
</div>
</div>
<div class="grid grid-cols-3 sm:grid-cols-5 gap-4">
<div v-for="size in teamSizes" :key="size" class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="state.teamSize === size"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<button
:class="[
'px-4 py-3 font-semibold text-sm border w-full transition-all',
state.teamSize === size
? 'item-selected bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100 border-neutral-900 dark:border-neutral-100'
: 'bg-white dark:bg-neutral-950 text-neutral-700 dark:text-neutral-300 border-neutral-700 dark:border-neutral-700 hover:border-black dark:hover:border-white cursor-pointer',
]"
@click="selectOption('teamSize', size)">
{{ size }}
</button>
</div>
</div>
</div>
<!-- Get Recommendation Button -->
<div class="section-card text-center">
<UButton
v-if="allStepsCompleted"
@click="showRecommendation"
size="xl"
color="primary"
class="px-8 py-4 text-lg">
Get Your Decision Framework Recommendation
</UButton>
<div v-else class="text-neutral-600 dark:text-neutral-400">
<p class="mb-4">
Complete all steps above to get your personalized
recommendation.
</p>
<p class="text-sm">
{{ completedSteps }} of {{ totalSteps }} steps completed
</p>
</div>
</div>
</div>
<!-- Results -->
<div v-if="showResult" data-results>
<div class="section-card">
<h2 class="section-title text-center mb-8">
Your Recommended Framework
</h2>
<div
class="text-center mb-8 p-6 bg-neutral-100 dark:bg-neutral-900 border-2 border-neutral-900 dark:border-neutral-100">
<h3
class="text-3xl font-bold text-neutral-900 dark:text-neutral-100 mb-2">
{{ result.method }}
</h3>
<p class="text-lg text-neutral-600 dark:text-neutral-400">
{{ result.tagline }}
</p>
</div>
<div class="space-y-8">
<div class="section-card">
<h3
class="text-xl font-semibold text-neutral-900 dark:text-neutral-100 mb-4">
Why this framework?
</h3>
<p
class="text-neutral-700 dark:text-neutral-300 leading-relaxed">
{{ result.reasoning }}
</p>
</div>
<div class="section-card">
<h3
class="text-xl font-semibold text-neutral-900 dark:text-neutral-100 mb-4">
How to implement:
</h3>
<ul class="space-y-3">
<li
v-for="step in result.steps"
:key="step"
class="flex items-start">
<span
class="text-neutral-900 dark:text-neutral-100 font-bold mr-3"
></span
>
<span class="text-neutral-700 dark:text-neutral-300">{{
step
}}</span>
</li>
</ul>
</div>
<div v-if="result.tips" class="section-card">
<h3
class="text-xl font-semibold text-neutral-900 dark:text-neutral-100 mb-4">
Hot tips:
</h3>
<ul class="space-y-3">
<li
v-for="tip in result.tips"
:key="tip"
class="flex items-start">
<span
class="text-neutral-900 dark:text-neutral-100 font-bold mr-3"
></span
>
<span class="text-neutral-700 dark:text-neutral-300">{{
tip
}}</span>
</li>
</ul>
</div>
<UAlert
v-if="result.warning"
color="red"
variant="soft"
:title="'Watch out for:'"
:description="result.warning"
class="mb-6" />
<UAlert
v-if="result.success"
color="emerald"
variant="soft"
:title="'Success looks like:'"
:description="result.success"
class="mb-6" />
<div v-if="result.alternatives" class="section-card">
<h3
class="text-xl font-semibold text-neutral-900 dark:text-neutral-100 mb-4">
Also consider:
</h3>
<div class="space-y-3">
<div
v-for="alt in result.alternatives"
:key="alt.method"
class="p-4 border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-950">
<span class="font-semibold">{{ alt.method }}:</span>
{{ alt.when }}
</div>
</div>
</div>
<div class="section-card text-center">
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<UButton @click="resetForm" size="lg" color="primary">
Try Another Decision
</UButton>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Credits Section -->
<div class="mt-12 py-8 border-t border-neutral-200 dark:border-neutral-700">
<div class="text-center text-sm text-neutral-600 dark:text-neutral-400">
<p class="mb-2 font-medium">With inspiration from:</p>
<div class="space-y-1">
<p>Rocket Adrift</p>
<p>Baby Ghosts Peer Accelerator curriculum</p>
<p><a href="https://thedecider.app/" target="_blank" rel="noopener noreferrer" class="hover:text-neutral-900 dark:hover:text-neutral-100 underline">The Decider App</a></p>
<p><a href="https://patterns.sociocracy30.org/index.html" target="_blank" rel="noopener noreferrer" class="hover:text-neutral-900 dark:hover:text-neutral-100 underline">Sociocracy 3.0</a></p>
</div>
</div>
</div>
</div>
<!-- Export Options at Bottom -->
<ExportOptions
:export-data="exportData"
filename="decision-framework"
title="Decision Framework Helper" />
</div>
</template>
<script setup>
import ExportOptions from "~/components/ExportOptions.vue";
const state = reactive({
urgency: null,
reversible: null,
expertise: null,
impact: null,
options: null,
investment: null,
teamSize: null,
});
const totalSteps = 7;
const urgencyOptions = [
{
value: 1,
title: "No rush",
description: "We have plenty of time to think this through",
},
{
value: 2,
title: "Some time pressure",
description: "We should decide soon but not immediately",
},
{
value: 3,
title: "Moderate urgency",
description: "We need to decide within days or weeks",
},
{
value: 4,
title: "High urgency",
description: "We need to decide very soon",
},
{
value: 5,
title: "Crisis mode",
description: "We should have decided yesterday!!",
},
];
const reversibilityOptions = [
{
value: "high",
title: "Easily reversible",
description: "We can pivot anytime with minimal cost",
},
{
value: "medium",
title: "Some commitment",
description: "Changes possible but with effort/cost",
},
{
value: "low",
title: "One-way door",
description: "This decision is permanent or very hard to undo",
},
];
const expertiseOptions = [
{
value: "concentrated",
title: "One clear expert",
description: "One person has deep knowledge here",
},
{
value: "multiple",
title: "Multiple experts",
description: "Several people have expertise",
},
{
value: "distributed",
title: "Distributed knowledge",
description: "Everyone has valuable input",
},
{
value: "lacking",
title: "Unknown territory",
description: "We're all learning together",
},
];
const impactOptions = [
{
value: "narrow",
title: "One person or small team",
description: "Affects specific individuals or area",
},
{
value: "wide",
title: "Whole organization",
description: "Everyone feels the effects",
},
];
const optionsOptions = [
{
value: "clear",
title: "Clear choices",
description: "We know our options and their tradeoffs",
},
{
value: "emerging",
title: "Still exploring",
description: "Options are emerging through discussion",
},
{
value: "undefined",
title: "Wide open",
description: "We don't even know what's possible yet",
},
];
const investmentOptions = [
{
value: "high",
title: "Everyone cares deeply",
description: "Strong opinions all around",
},
{
value: "mixed",
title: "Mixed investment",
description: "Some care more than others",
},
{
value: "low",
title: "Low stakes for most",
description: "People are flexible",
},
];
const teamSizes = ["2", "3", "4-5", "6-8", "9+"];
const showResult = ref(false);
const result = computed(() => {
if (!showResult.value) return null;
return determineFramework();
});
const completedSteps = computed(() => {
let completed = 0;
if (state.urgency) completed++;
if (state.reversible) completed++;
if (state.expertise) completed++;
if (state.impact) completed++;
if (state.options) completed++;
if (state.investment) completed++;
if (state.teamSize) completed++;
return completed;
});
const allStepsCompleted = computed(() => {
return completedSteps.value === totalSteps;
});
function selectOption(category, value) {
state[category] = value;
}
function showRecommendation() {
showResult.value = true;
nextTick(() => {
const resultsElement = document.querySelector("[data-results]");
if (resultsElement) {
resultsElement.scrollIntoView({ behavior: "smooth" });
}
});
}
function determineFramework() {
// AUTOCRATIC - urgent + concentrated expertise + predictable
if (
state.urgency >= 5 &&
state.expertise === "concentrated" &&
state.options === "clear"
) {
return {
method: "Autocratic",
tagline: "Quick decision by designated leader",
reasoning:
"Extreme urgency with clear options and concentrated expertise. Speed is critical.",
steps: [
"Leader makes immediate decision",
"Communicate decision and rationale quickly",
"Execute without delay",
"Debrief when crisis passes",
],
warning:
"Only use in true emergencies. Follow up with team discussion afterward.",
success:
"Crisis averted through quick action. Team understands why autocratic mode was necessary.",
};
}
// DEFER TO EXPERT - clear expertise + urgency
if (
state.expertise === "concentrated" &&
(state.urgency >= 4 || state.impact === "narrow")
) {
return {
method: "Defer to Expert",
tagline: "Trust the person who knows this best",
reasoning:
"You have someone with clear expertise, and either time is short or the impact is contained. Let them lead while keeping everyone informed.",
steps: [
"Expert proposes solution with reasoning",
"Quick clarifying questions (set time limit)",
"Expert makes final call",
"Document decision and rationale",
"Schedule check-in if reversible",
],
tips: [
"Expert should explain their thinking, not just the outcome",
"Create space for concerns to be raised",
"If expert is unsure, that's valuable info—maybe try another method",
],
warning:
"The expert should still seek input. Expertise + diverse perspectives = better decisions.",
success:
"Decision made quickly with buy-in because people trust the expert's judgment and understand the reasoning.",
};
}
// AVOIDANT - non-urgent + undefined + low investment
if (
state.urgency <= 2 &&
state.options === "undefined" &&
state.investment === "low"
) {
return {
method: "Strategic Delay",
tagline: "Wait for clarity to emerge",
reasoning:
"It's not urgent, options aren't clear, and people aren't strongly invested. Sometimes the best decision is to hold off on deciding!",
steps: [
"Acknowledge the decision exists",
"Set a future check-in date",
"Gather information passively",
"Revisit when conditions change",
"Document why you're waiting",
],
warning:
"Don't let avoidance become paralysis. Set a deadline for revisiting.",
success:
"By waiting, better options emerged or the decision became unnecessary.",
alternatives: [
{
method: "Time-boxed exploration",
when: "Give it 2 weeks to see if clarity emerges",
},
],
};
}
// CONSENSUS - low urgency + high stakes + everyone affected
if (
state.urgency <= 2 &&
state.reversible === "low" &&
state.impact === "wide" &&
state.investment === "high"
) {
return {
method: "Full Consensus",
tagline: "Everyone agrees to support the decision",
reasoning:
"This is a high-stakes, permanent decision affecting everyone who cares deeply. Take the time to get real alignment.",
steps: [
"Share context and constraints with everyone",
"Gather all perspectives (async or live discussion)",
"Identify shared values and concerns",
"Iterate on proposals until everyone can support it",
"Document the decision and everyone's commitment",
],
tips: [
"Consensus ≠ everyone's favorite. It means everyone can live with it",
"Use 'I can live with this' as your bar, not 'I love this'",
"Timebox discussion rounds to maintain energy",
],
warning:
"If consensus is taking too long, check: Is everyone operating with the same info? Are we solving the right problem?",
success:
"Everyone understands the decision and commits to supporting it, even if it wasn't their first choice.",
};
}
// CONSENT - medium stakes, mixed investment
if (state.investment === "mixed" && state.reversible !== "low") {
return {
method: "Consent-Based Decision",
tagline: "No one objects strongly enough to block",
reasoning:
"Not everyone is equally invested, and the decision is reversible. Focus on addressing objections rather than optimizing preferences.",
steps: [
"Proposer presents solution",
"Ask: 'Can you live with this?'",
"Address only strong objections",
"Modify proposal if needed",
"Move forward when no blocking objections remain",
],
tips: [
"Objections must be based on harm to the co-op, not personal preference",
"Set a clear bar for what counts as a blocking objection",
"This is faster than consensus but still inclusive",
],
success:
"Decision made efficiently with key concerns addressed, without getting stuck in preference debates.",
};
}
// DELEGATION - narrow impact + concentrated expertise
if (
state.impact === "narrow" &&
state.expertise === "concentrated" &&
state.urgency >= 3
) {
return {
method: "Delegation",
tagline: "Empower the responsible party to decide",
reasoning:
"This primarily affects specific people who have the expertise. Trust them to handle it.",
steps: [
"Clarify scope and constraints",
"Delegate to affected party/expert",
"Set check-in points if needed",
"Trust them to execute",
"Report back on outcome",
],
tips: [
"Be clear about what's delegated and what's not",
"Delegation means trusting their judgment, not micromanaging",
],
success:
"Decision made efficiently by those closest to the work, building trust and autonomy.",
};
}
// CONSULTATIVE - lacking expertise but need input
if (state.expertise === "lacking" && state.options === "emerging") {
return {
method: "Consultative Process",
tagline: "Gather input, then designated person decides",
reasoning:
"No one has clear expertise but we need various perspectives to understand the options.",
steps: [
"Designate decision owner",
"Owner seeks input from all stakeholders",
"Owner researches and synthesizes options",
"Owner makes decision and explains reasoning",
"Share decision with clear rationale",
],
tips: [
"Be transparent about who decides and when",
"Document all input received",
"Explain how input influenced the decision",
],
success:
"Decision informed by diverse perspectives with clear accountability.",
};
}
// STOCHASTIC - truly stuck, low stakes
if (
state.options === "clear" &&
state.investment === "low" &&
state.reversible === "high"
) {
return {
method: "Roll the Dice",
tagline: "Let chance break the tie",
reasoning:
"Options are equally good, stakes are low, and people aren't strongly invested. Save time and energy.",
steps: [
"Confirm all options are acceptable",
"Choose random method (coin, dice, draw straws)",
"Do it publicly for transparency",
"Commit to the outcome",
"Move on without second-guessing",
],
warning:
"Only works if everyone truly accepts all options. Don't use for important decisions.",
success: "Quick resolution that feels fair because chance is impartial.",
alternatives: [
{
method: "Take turns choosing",
when: "Rotate who picks when these situations arise",
},
],
};
}
// 3-PERSON TRAP
if (state.teamSize === "3" && state.investment === "high") {
return {
method: "Modified Consensus (Not Voting!)",
tagline: "Voting creates problems in groups of three",
reasoning:
"With 3 people, one person always becomes the tie-breaker, which creates unhealthy dynamics. Use rotating facilitation instead.",
steps: [
"Rotate who facilitates the decision",
"Facilitator synthesizes others' views first",
"Look for creative third options",
"If stuck, defer to whoever is most affected",
"Or use external input (advisor, user feedback)",
],
warning:
"Never use simple majority voting with 3 people—it turns one person into a perpetual kingmaker.",
success:
"All three members feel heard and the decision reflects collective wisdom, not just the middle person's preference.",
alternatives: [
{
method: "Time-boxed experiment",
when: "Try one option for 2 weeks, then reassess",
},
],
};
}
// DEMOCRATIC VOTE - larger group, time pressure
if (
(state.teamSize === "6-8" || state.teamSize === "9+") &&
state.urgency >= 4
) {
return {
method: "Democratic Vote",
tagline: "Majority decides, move forward together",
reasoning:
"Large group + time pressure = need for efficiency. Voting provides clear resolution while respecting everyone's input.",
steps: [
"Present options clearly with pros/cons",
"Discussion round (time-boxed)",
"Anonymous or open vote (decide beforehand)",
"Announce result and thank minority view",
"Document dissenting concerns for future review",
],
tips: [
"Consider ranked choice for more than 2 options",
"Anonymous voting reduces peer pressure",
"Always acknowledge the minority position respectfully",
],
warning:
"Don't vote on everything! Reserve it for when other methods are too slow.",
success:
"Clear decision made efficiently with everyone having equal say.",
};
}
// EXPERIMENTAL - unknown territory
if (state.expertise === "lacking" && state.reversible === "high") {
return {
method: "Run an Experiment",
tagline: "Try something small and learn",
reasoning:
"Nobody knows the right answer and it's easy to change course. Perfect for learning by doing.",
steps: [
"Define what you're testing",
"Set clear success metrics",
"Choose shortest meaningful trial period",
"Pick simplest version to test",
"Schedule review before committing further",
],
tips: [
"Make it clear this is an experiment, not a decision",
"Shorter trials = faster learning",
"Document what you learn, not just what happened",
],
success:
"You learn what works through experience rather than speculation, building confidence for bigger decisions.",
};
}
// ADVICE PROCESS - multiple expertise, mixed investment
if (state.expertise === "multiple" && state.investment === "mixed") {
return {
method: "Advice Process",
tagline: "Decision-maker seeks input, then decides",
reasoning:
"Multiple people have valuable input, but not everyone needs to be involved in the final call. This balances inclusion with efficiency.",
steps: [
"Assign decision owner (most affected or willing)",
"Owner seeks advice from those with expertise",
"Owner seeks input from those affected",
"Owner makes decision and explains reasoning",
"Share decision and thank advisors",
],
tips: [
"Be clear who the decision owner is upfront",
"Seeking advice ≠ design by committee",
"Owner genuinely considers input but isn't bound by it",
],
success:
"Decision made efficiently with relevant input incorporated, and everyone understands the reasoning.",
};
}
// DEFAULT
return {
method: "Facilitated Discussion",
tagline: "Talk it through with structure",
reasoning:
"Your situation has mixed signals. Use a structured discussion to find clarity before choosing a decision method.",
steps: [
"Clarify what we're actually deciding",
"Share all relevant information",
"Each person shares their perspective (timed)",
"Identify where we align and where we differ",
"Choose appropriate method based on what emerges",
],
tips: [
"Sometimes the discussion reveals you're solving the wrong problem",
"Visual tools (sticky notes, diagrams) help with complex decisions",
"If stuck, ask: 'What would happen if we did nothing?'",
],
warning:
"Don't let discussion become delay. Set a deadline for moving to a decision method.",
success:
"The real question becomes clear and the right decision method becomes obvious.",
};
}
function resetForm() {
state.urgency = null;
state.reversible = null;
state.expertise = null;
state.impact = null;
state.options = null;
state.investment = null;
state.teamSize = null;
showResult.value = false;
window.scrollTo({ top: 0, behavior: "smooth" });
}
function printResult() {
window.print();
}
// Export data for standardized export component
const exportData = computed(() => ({
formData: {
state: state,
showResult: showResult.value,
result: result.value,
},
surveyResponses: {
urgency: state.urgency,
reversible: state.reversible,
expertise: state.expertise,
impact: state.impact,
options: state.options,
investment: state.investment,
teamSize: state.teamSize,
},
recommendedFramework: result.value || null,
metadata: {
completedAt: showResult.value ? new Date().toISOString() : null,
totalSteps: totalSteps,
completedSteps: completedSteps.value,
progressPercentage: Math.round((completedSteps.value / totalSteps) * 100),
},
exportedAt: new Date().toISOString(),
section: "decision-framework",
title: "Decision Framework Helper",
description: "Interactive wizard to find the right way to decide together",
}));
// Keyboard shortcuts
onMounted(() => {
const handleKeydown = (event) => {
if (showResult.value) return;
// Enter to get recommendation when all steps completed
if (event.key === "Enter" && allStepsCompleted.value) {
showRecommendation();
}
// Escape to reset form
else if (event.key === "Escape") {
resetForm();
}
};
document.addEventListener("keydown", handleKeydown);
onUnmounted(() => {
document.removeEventListener("keydown", handleKeydown);
});
});
useHead({
title: "Decision Framework Helper",
meta: [
{
name: "description",
content:
"Find the right way to decide together with this interactive decision-making framework helper.",
},
],
});
</script>