app/pages/templates/decision-framework.vue

1046 lines
35 KiB
Vue

<template>
<div>
<!-- Wizard Subnav -->
<WizardSubnav />
<!-- 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"
style="font-family: 'Ubuntu', monospace"
>
<!-- Spacer for consistency -->
<div class="py-4"></div>
<div class="max-w-4xl mx-auto relative px-4">
<div
class="bg-white dark:bg-neutral-950 border-2 border-neutral-900 dark:border-neutral-100 decision-framework-container"
>
<!-- Header -->
<div
class="bg-black dark:bg-white text-white dark:text-black px-8 py-12 text-center header-section"
>
<!-- Dithered shadow background -->
<div
class="absolute top-4 left-4 right-0 bottom-0 dither-shadow-header"
></div>
<div
class="relative bg-black dark:bg-white text-white dark:text-black px-4 py-4 border-2 border-neutral-100 dark:border-neutral-900"
>
<h1
class="text-3xl font-bold mb-2 uppercase"
style="font-family: 'Ubuntu', monospace"
>
Decision Framework Helper
</h1>
<p class="text-lg" style="font-family: 'Ubuntu', monospace">
Find the right way to decide together
</p>
<!-- Progress Bar -->
<div v-if="!showResult" class="mt-8">
<div class="flex justify-between items-center mb-2">
<span class="text-sm" style="font-family: 'Ubuntu Mono', monospace"
>Step {{ currentStep }} of {{ totalSteps }}</span
>
<span class="text-sm" style="font-family: 'Ubuntu Mono', monospace"
>{{ Math.round((currentStep / totalSteps) * 100) }}%</span
>
</div>
<div
class="w-full bg-white dark:bg-black h-2 border-2 border-neutral-100 dark:border-neutral-900"
>
<div
class="bg-black dark:bg-white h-full transition-all duration-300 progress-dither"
:style="{
width: (currentStep / totalSteps) * 100 + '%',
}"
></div>
</div>
</div>
</div>
</div>
<!-- Content -->
<div class="px-8 py-12">
<!-- Step Content -->
<div v-if="!showResult" class="min-h-[400px]">
<!-- Question 1: Urgency -->
<div v-if="currentStep === 1">
<div
class="font-semibold text-black dark:text-white mb-6 text-2xl"
style="font-family: 'Ubuntu', monospace"
>
How urgent is this decision?
</div>
<div
class="bg-white dark:bg-neutral-950 p-8 border-2 border-neutral-900 dark:border-neutral-100 relative"
>
<!-- Dithered shadow background -->
<div class="absolute top-2 left-2 right-0 bottom-0 dither-shadow"></div>
<div class="relative">
<div class="flex justify-between mb-6 text-sm">
<span
class="text-black dark:text-white font-bold"
style="font-family: 'Ubuntu Mono', monospace"
>WE HAVE PLENTY OF TIME</span
>
<span
class="text-black dark:text-white font-bold"
style="font-family: 'Ubuntu Mono', monospace"
>NEEDED YESTERDAY</span
>
</div>
<div class="relative">
<input
type="range"
v-model="state.urgency"
min="1"
max="5"
step="1"
class="w-full h-2 bg-white dark:bg-black appearance-none cursor-pointer slider"
/>
<div
class="flex justify-between mt-4 text-sm text-black dark:text-white"
style="font-family: 'Ubuntu Mono', monospace"
>
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
</div>
</div>
</div>
</div>
</div>
<!-- Question 2: Reversibility -->
<div v-if="currentStep === 2">
<div
class="font-semibold text-black mb-6 text-2xl"
style="font-family: 'Ubuntu', monospace"
>
Can we change our minds later?
</div>
<div class="grid gap-4">
<UCard
v-for="option in reversibilityOptions"
:key="option.value"
:class="[
'cursor-pointer transition-all duration-200 border-2',
state.reversible === option.value
? 'border-violet-700 bg-violet-700 text-white'
: 'border-neutral-200 hover:border-violet-700 hover:bg-violet-50',
]"
@click="selectOption('reversible', option.value)"
>
<div class="font-semibold mb-1">{{ option.title }}</div>
<div class="text-sm opacity-85">
{{ option.description }}
</div>
</UCard>
</div>
</div>
<!-- Question 3: Expertise -->
<div v-if="currentStep === 3">
<div class="font-semibold text-neutral-900 mb-6 text-2xl">
Who has the most relevant expertise?
</div>
<div class="grid gap-4">
<UCard
v-for="option in expertiseOptions"
:key="option.value"
:class="[
'cursor-pointer transition-all duration-200 border-2',
state.expertise === option.value
? 'border-violet-700 bg-violet-700 text-white'
: 'border-neutral-200 hover:border-violet-700 hover:bg-violet-50',
]"
@click="selectOption('expertise', option.value)"
>
<div class="font-semibold mb-1">{{ option.title }}</div>
<div class="text-sm opacity-85">
{{ option.description }}
</div>
</UCard>
</div>
</div>
<!-- Question 4: Impact -->
<div v-if="currentStep === 4">
<div class="font-semibold text-neutral-900 mb-6 text-2xl">
Who will this impact?
</div>
<div class="grid gap-4">
<UCard
v-for="option in impactOptions"
:key="option.value"
:class="[
'cursor-pointer transition-all duration-200 border-2',
state.impact === option.value
? 'border-violet-700 bg-violet-700 text-white'
: 'border-neutral-200 hover:border-violet-700 hover:bg-violet-50',
]"
@click="selectOption('impact', option.value)"
>
<div class="font-semibold mb-1">{{ option.title }}</div>
<div class="text-sm opacity-85">
{{ option.description }}
</div>
</UCard>
</div>
</div>
<!-- Question 5: Options clarity -->
<div v-if="currentStep === 5">
<div class="font-semibold text-neutral-900 mb-6 text-2xl">
How well-defined are the options?
</div>
<div class="grid gap-4">
<UCard
v-for="option in optionsOptions"
:key="option.value"
:class="[
'cursor-pointer transition-all duration-200 border-2',
state.options === option.value
? 'border-violet-700 bg-violet-700 text-white'
: 'border-neutral-200 hover:border-violet-700 hover:bg-violet-50',
]"
@click="selectOption('options', option.value)"
>
<div class="font-semibold mb-1">{{ option.title }}</div>
<div class="text-sm opacity-85">
{{ option.description }}
</div>
</UCard>
</div>
</div>
<!-- Question 6: Investment -->
<div v-if="currentStep === 6">
<div class="font-semibold text-neutral-900 mb-6 text-2xl">
How invested is everyone?
</div>
<div class="grid gap-4">
<UCard
v-for="option in investmentOptions"
:key="option.value"
:class="[
'cursor-pointer transition-all duration-200 border-2',
state.investment === option.value
? 'border-violet-700 bg-violet-700 text-white'
: 'border-neutral-200 hover:border-violet-700 hover:bg-violet-50',
]"
@click="selectOption('investment', option.value)"
>
<div class="font-semibold mb-1">{{ option.title }}</div>
<div class="text-sm opacity-85">
{{ option.description }}
</div>
</UCard>
</div>
</div>
<!-- Question 7: Team size -->
<div v-if="currentStep === 7">
<div class="font-semibold text-neutral-900 mb-6 text-2xl">
How many people need to participate?
</div>
<div class="grid grid-cols-3 sm:grid-cols-5 gap-4">
<button
v-for="size in teamSizes"
:key="size"
:class="[
'px-4 py-3 font-semibold text-sm rounded-md border-2 transition-all duration-200',
state.teamSize === size
? 'bg-violet-700 text-white border-violet-700'
: 'bg-white text-neutral-700 border-neutral-200 hover:border-violet-700 hover:bg-violet-50',
]"
@click="selectOption('teamSize', size)"
>
{{ size }}
</button>
</div>
</div>
<!-- Navigation -->
<div
class="flex justify-between items-center mt-12 pt-8 border-t-2 border-neutral-200"
>
<button
v-if="currentStep > 1"
@click="previousStep"
class="px-6 py-3 text-violet-700 border-2 border-violet-700 rounded-md hover:bg-violet-50 transition-all duration-200"
>
Previous
</button>
<div v-else></div>
<button
v-if="canProceed && currentStep < totalSteps"
@click="nextStep"
class="px-6 py-3 bg-violet-700 text-white rounded-md hover:bg-violet-800 transition-all duration-200"
>
Next
</button>
<button
v-else-if="canProceed && currentStep === totalSteps"
@click="showRecommendation"
class="px-6 py-3 bg-violet-700 text-white rounded-md hover:bg-violet-800 transition-all duration-200"
>
Get Recommendation
</button>
</div>
</div>
<!-- Results -->
<div
v-if="showResult"
data-results
class="border-t-2 border-neutral-200 pt-12"
>
<UCard class="bg-neutral-50">
<div class="mb-8">
<h2 class="text-2xl font-semibold text-violet-700 mb-2">
{{ result.method }}
</h2>
<p class="text-lg text-neutral-600">{{ result.tagline }}</p>
</div>
<UCard class="bg-white mb-8">
<div class="space-y-8">
<div>
<h3 class="font-semibold text-neutral-900 mb-4 text-lg">
Why this framework?
</h3>
<p class="text-neutral-700 leading-relaxed">
{{ result.reasoning }}
</p>
</div>
<div>
<h3 class="font-semibold text-neutral-900 mb-4 text-lg">
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-violet-700 font-bold mr-3 mt-1"></span>
<span class="text-neutral-700">{{ step }}</span>
</li>
</ul>
</div>
<div v-if="result.tips">
<h3 class="font-semibold text-neutral-900 mb-4 text-lg">
Pro tips:
</h3>
<ul class="space-y-3">
<li
v-for="tip in result.tips"
:key="tip"
class="flex items-start"
>
<span class="text-violet-700 font-bold mr-3 mt-1"></span>
<span class="text-neutral-700">{{ tip }}</span>
</li>
</ul>
</div>
</div>
</UCard>
<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"
/>
<UCard v-if="result.alternatives" class="bg-neutral-50">
<h3 class="font-semibold text-neutral-900 mb-4 text-lg">
Also consider:
</h3>
<div class="space-y-3">
<UCard
v-for="alt in result.alternatives"
:key="alt.method"
class="bg-white"
>
<span class="font-semibold">{{ alt.method }}:</span>
{{ alt.when }}
</UCard>
</div>
</UCard>
<div class="flex gap-4 mt-8">
<UButton @click="resetForm" color="violet">
Try Another Decision
</UButton>
<UButton @click="printResult" variant="outline" color="violet">
Print Recommendation
</UButton>
</div>
</UCard>
</div>
</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: 3,
reversible: null,
expertise: null,
impact: null,
options: null,
investment: null,
teamSize: null,
});
const currentStep = ref(1);
const totalSteps = 7;
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 relevant 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 department",
},
{
value: "wide",
title: "Whole organization",
description: "Everyone feels the effects",
},
];
const optionsOptions = [
{
value: "clear",
title: "Clear choices",
description: "We know our options and their trade-offs",
},
{
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 canProceed = computed(() => {
switch (currentStep.value) {
case 1:
return true; // urgency always has a value
case 2:
return state.reversible !== null;
case 3:
return state.expertise !== null;
case 4:
return state.impact !== null;
case 5:
return state.options !== null;
case 6:
return state.investment !== null;
case 7:
return state.teamSize !== null;
default:
return false;
}
});
function selectOption(category, value) {
state[category] = value;
}
function nextStep() {
if (currentStep.value < totalSteps) {
currentStep.value++;
}
}
function previousStep() {
if (currentStep.value > 1) {
currentStep.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 not decide yet.",
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 sync)",
"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: "Controlled Randomness",
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 = 3;
state.reversible = null;
state.expertise = null;
state.impact = null;
state.options = null;
state.investment = null;
state.teamSize = null;
currentStep.value = 1;
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,
currentStep: currentStep.value,
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,
progressPercentage: Math.round((currentStep.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 navigation
onMounted(() => {
const handleKeydown = (event) => {
if (showResult.value) return;
if (
event.key === "ArrowRight" &&
canProceed.value &&
currentStep.value < totalSteps
) {
nextStep();
} else if (event.key === "ArrowLeft" && currentStep.value > 1) {
previousStep();
} else if (
event.key === "Enter" &&
canProceed.value &&
currentStep.value === totalSteps
) {
showRecommendation();
}
};
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>
<style scoped>
/* Dark mode utility overrides for better contrast */
html.dark :deep(.text-neutral-900),
html.dark :deep(.text-neutral-800),
html.dark :deep(.text-neutral-700),
html.dark :deep(.text-neutral-600),
html.dark :deep(.text-neutral-500) {
color: #e5e7eb !important;
}
html.dark :deep(.bg-neutral-50),
html.dark :deep(.bg-neutral-100),
html.dark :deep(.bg-neutral-200) {
background-color: #0a0a0a !important;
}
html.dark :deep(.border-neutral-200),
html.dark :deep(.border-neutral-300) {
border-color: #374151 !important;
}
/* Header progress bar frame inversion */
html.dark :deep(.header-section .w-full.h-2) {
background-color: #0a0a0a !important;
border-color: #000 !important;
}
/* Buttons in results area */
html.dark :deep(.u-card),
html.dark :deep(.bg-white) {
background-color: #0a0a0a !important;
}
html.dark :deep(.bg-neutral-50) {
background-color: #0f172a !important;
}
</style>