refactor: update app.vue and various components to improve routing paths, enhance UI consistency, and streamline layout for better user experience

This commit is contained in:
Jennie Robinson Faber 2025-09-11 11:51:48 +01:00
parent b6e8d3b7ec
commit 78af43770c
29 changed files with 1699 additions and 1990 deletions

View file

@ -2,275 +2,296 @@
<div class="mx-auto">
<div class="relative">
<div class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div class="relative bg-white dark:bg-neutral-950 border-1 border-black dark:border-neutral-400">
<!-- Controls -->
<div
class="p-6 border-b-1 border-black dark:border-neutral-400 bg-neutral-100 dark:bg-neutral-950">
<div class="flex flex-wrap gap-4 items-center">
<div class="flex items-center gap-2">
<UFormField label="Duration in months" class="">
<UInputNumber
id="duration"
v-model="durationMonths"
:min="3"
:max="24"
size="lg"
class="w-full" />
</UFormField>
class="relative bg-white dark:bg-neutral-950 border-1 border-black dark:border-neutral-400">
<!-- Controls -->
<div
class="p-6 border-b-1 border-black dark:border-neutral-400 bg-neutral-100 dark:bg-neutral-950">
<div class="flex flex-wrap gap-4 items-center">
<div class="flex items-center gap-2">
<UFormField label="Duration in months" class="">
<UInputNumber
id="duration"
v-model="durationMonths"
:min="3"
:max="24"
size="lg"
class="w-full" />
</UFormField>
</div>
</div>
</div>
</div>
<!-- Cost Summary -->
<div
class="p-6 border-b-1 border-black bg-neutral-100 dark:bg-neutral-950">
<ul class="space-y-2">
<!-- Two Column Layout -->
<li class="pb-2">
<div class="grid grid-cols-2 gap-6">
<!-- Left Column: Detailed Breakdown -->
<div
class="text-base text-neutral-600 dark:text-neutral-200 space-y-1">
<div class="font-bold font-display">
Monthly payroll breakdown:
</div>
<div>
Base hourly rate: {{ currency(theoreticalHourlyRate) }}/hour
</div>
<div class="pl-2 space-y-0.5">
<!-- Cost Summary -->
<div
class="p-6 border-b-1 border-black bg-neutral-100 dark:bg-neutral-950">
<ul class="space-y-2">
<!-- Two Column Layout -->
<li class="pb-2">
<div class="grid grid-cols-2 gap-6">
<!-- Left Column: Detailed Breakdown -->
<div
class="text-base text-neutral-600 dark:text-neutral-200 space-y-1">
<div class="font-bold font-display">
Monthly payroll breakdown:
</div>
<div>
Base hourly rate: {{ currency(theoreticalHourlyRate) }}/hour
</div>
<div class="pl-2 space-y-0.5">
<div
v-for="member in props.members"
:key="member.name"
class="flex justify-between">
<span
>{{ member.name }} @ {{ member.hoursPerMonth }}h:</span
>
<span class="font-mono">{{
currency(member.hoursPerMonth * theoreticalHourlyRate)
}}</span>
</div>
</div>
<div
v-for="member in props.members"
:key="member.name"
class="flex justify-between">
class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-medium">
<span>Total base pay:</span>
<span class="font-mono">{{
currency(baseMonthlyPayroll)
}}</span>
</div>
<div class="flex justify-between">
<span
>{{ member.name }} @ {{ member.hoursPerMonth }}h:</span
>Payroll taxes & benefits ({{
percent(props.oncostRate)
}}):</span
>
<span class="font-mono">{{
currency(member.hoursPerMonth * theoreticalHourlyRate)
currency(theoreticalOncosts)
}}</span>
</div>
<div
class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-bold">
<span>Total monthly payroll:</span>
<span class="font-mono">{{
currency(baseMonthlyPayroll + theoreticalOncosts)
}}</span>
</div>
</div>
<!-- Right Column: Complete Project Budget Estimate -->
<div
class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-medium">
<span>Total base pay:</span>
<span class="font-mono">{{
currency(baseMonthlyPayroll)
}}</span>
</div>
<div class="flex justify-between">
<span
>Payroll taxes & benefits ({{
percent(props.oncostRate)
}}):</span
>
<span class="font-mono">{{
currency(theoreticalOncosts)
}}</span>
</div>
<div
class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-bold">
<span>Total monthly payroll:</span>
<span class="font-mono">{{
currency(baseMonthlyPayroll + theoreticalOncosts)
}}</span>
class="text-base text-neutral-600 dark:text-neutral-200 space-y-1">
<div class="font-bold font-display">
Complete project budget estimate:
</div>
<p
class="text-sm mb-2 italic text-neutral-500 dark:text-neutral-400">
This uses a 1.8x multiplier, based on industry standards.
</p>
<!-- Team Payroll -->
<div class="flex justify-between">
<span class="font-bold">Team Payroll:</span>
<span class="font-mono font-bold">{{
currency(projectBudget)
}}</span>
</div>
<!-- External Resources -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>External resources:</span>
<span class="font-mono">{{
currency(externalResources)
}}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Freelancers, contractors, consultants, voice talent
</div>
</div>
<!-- Tools & Software -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Tools and software:</span>
<span class="font-mono">{{
currency(toolsSoftware)
}}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Licenses, subscriptions, cloud services, development
tools/kits
</div>
</div>
<!-- Testing & QA -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Testing and QA:</span>
<span class="font-mono">{{ currency(testingQA) }}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
User testing sessions, focus groups, QA contractors,
playtesting, bug fixing
</div>
</div>
<!-- Marketing & Community -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Marketing and community:</span>
<span class="font-mono">{{
currency(marketingCommunity)
}}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Community building, promotional materials, launch
preparation (minimum 10% for most funders)
</div>
</div>
<!-- Administration -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Administration:</span>
<span class="font-mono">{{
currency(administration)
}}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Legal, accounting, insurance, project-specific business
costs
</div>
</div>
<!-- Subtotal -->
<div
class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-bold">
<span>Subtotal:</span>
<span class="font-mono">{{
currency(budgetSubtotal)
}}</span>
</div>
<!-- Contingency -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Contingency (10%):</span>
<span class="font-mono">{{ currency(contingency) }}</span>
</div>
</div>
<!-- Total -->
<div
class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-bold text-lg">
<span>TOTAL PROJECT:</span>
<span class="font-mono">{{
currency(totalProjectBudget)
}}</span>
</div>
</div>
</div>
<!-- Right Column: Complete Project Budget Estimate -->
<div
class="text-base text-neutral-600 dark:text-neutral-200 space-y-1">
<div class="font-bold font-display">
Complete project budget estimate:
</div>
<p
class="text-sm mb-2 italic text-neutral-500 dark:text-neutral-400">
This uses a 1.8x multiplier, based on industry standards.
</p>
<!-- Team Payroll -->
<div class="flex justify-between">
<span class="font-bold">Team Payroll:</span>
<span class="font-mono font-bold">{{
currency(projectBudget)
}}</span>
</div>
<!-- External Resources -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>External resources:</span>
<span class="font-mono">{{
currency(externalResources)
}}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Freelancers, contractors, consultants, voice talent
</div>
</div>
<!-- Tools & Software -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Tools and software:</span>
<span class="font-mono">{{ currency(toolsSoftware) }}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Licenses, subscriptions, cloud services, development
tools/kits
</div>
</div>
<!-- Testing & QA -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Testing and QA:</span>
<span class="font-mono">{{ currency(testingQA) }}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
User testing sessions, focus groups, QA contractors,
playtesting, bug fixing
</div>
</div>
<!-- Marketing & Community -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Marketing and community:</span>
<span class="font-mono">{{
currency(marketingCommunity)
}}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Community building, promotional materials, launch
preparation (minimum 10% for most funders)
</div>
</div>
<!-- Administration -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Administration:</span>
<span class="font-mono">{{
currency(administration)
}}</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Legal, accounting, insurance, project-specific business
costs
</div>
</div>
<!-- Subtotal -->
<div
class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-bold">
<span>Subtotal:</span>
<span class="font-mono">{{ currency(budgetSubtotal) }}</span>
</div>
<!-- Contingency -->
<div class="space-y-0.5">
<div class="flex justify-between">
<span>Contingency (10%):</span>
<span class="font-mono">{{ currency(contingency) }}</span>
</div>
</div>
<!-- Total -->
<div
class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-bold text-lg">
<span>TOTAL PROJECT:</span>
<span class="font-mono">{{
currency(totalProjectBudget)
}}</span>
</div>
</div>
</div>
</li>
</ul>
</div>
<!-- Break-Even Sketch -->
<div
class="text-black dark:text-white border-t border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-950">
<div class="p-6 text-black dark:text-white">
<h3 class="font-bold mb-4">Break-Even Sketch</h3>
<!-- Inputs -->
<div class="flex flex-wrap space-x-4 mb-6">
<div>
<label for="price" class="block font-bold text-sm mb-1"
>Price per copy:</label
>
<UInput
id="price"
v-model="price"
type="number"
placeholder="20.00"
size="lg"
class="w-32"
:ui="{ leading: 'pointer-events-none' }">
<template #leading>
<span class="text-sm font-mono">{{
formatCurrency(0, {
showSymbol: true,
precision: 0,
}).replace("0", "")
}}</span>
</template>
</UInput>
</div>
<div>
<label for="storeCut" class="block font-bold text-sm mb-1"
>Store cut:</label
>
<UInput
id="storeCut"
v-model="storeCutInput"
type="number"
placeholder="30"
size="lg"
class="w-24"
:ui="{ trailing: 'pointer-events-none' }">
<template #trailing>
<span class="text-sm font-mono">%</span>
</template>
</UInput>
</div>
<div>
<label for="reviewToSales" class="block font-bold text-sm mb-1"
>Sales per review:</label
>
<UInputNumber
id="reviewToSales"
v-model="reviewToSales"
:min="5"
:max="100"
size="lg" />
</div>
</div>
<!-- Outputs -->
<ul class="space-y-2 mb-4">
<li>
At {{ currency(price) }} per copy after store fees, you'd need
about
<strong>{{ unitsToBreakEven.toLocaleString() }} sales</strong> to
cover this budget.
</li>
<li>
That's roughly
<strong
>{{ reviewsToBreakEven.toLocaleString() }} Steam reviews</strong
>
( {{ reviewToSales }} sales per review).
</li>
</ul>
<p class="text-base text-neutral-600 dark:text-neutral-400">
Assumes {{ percent(storeCutInput / 100) }} store fee. Taxes not
included.
</p>
</div>
</div>
<!-- Break-Even Sketch -->
<div
class="text-black dark:text-white border-t border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-950">
<div class="p-6 text-black dark:text-white">
<h3 class="font-bold mb-4">Break-Even Sketch</h3>
<p class="mb-4">
There are so many ways to guess at the sales figures of published
games. One is to use a review-to-sales ratio, formalized in the
Boxleiter method and later discussed and refined by many others.
<a
href="https://app.sensortower.com/vgi/insights/article/how-to-estimate-steam-video-game-sales"
>VGInsights</a
>
and <a href="https://gamediscover.co/">GameDiscoverCo</a> are good
sources for more information on this method. Your chosen ratio
will vary, typically between 30 and 70 reviews per sale, and is
dependent on factors like genre, pricing, and market conditions at
the time of release.
</p>
<!-- Inputs -->
<div class="flex flex-wrap space-x-4 mb-6">
<div>
<label for="price" class="block font-bold text-sm mb-1"
>Price per copy:</label
>
<UInput
id="price"
v-model="price"
type="number"
placeholder="20.00"
size="lg"
class="w-32"
:ui="{ leading: 'pointer-events-none' }">
<template #leading>
<span class="text-sm font-mono">{{
formatCurrency(0, {
showSymbol: true,
precision: 0,
}).replace("0", "")
}}</span>
</template>
</UInput>
</div>
<div>
<label for="storeCut" class="block font-bold text-sm mb-1"
>Store cut:</label
>
<UInput
id="storeCut"
v-model="storeCutInput"
type="number"
placeholder="30"
size="lg"
class="w-24"
:ui="{ trailing: 'pointer-events-none' }">
<template #trailing>
<span class="text-sm font-mono">%</span>
</template>
</UInput>
</div>
<div>
<label for="reviewToSales" class="block font-bold text-sm mb-1"
>Sales per review:</label
>
<UInputNumber
id="reviewToSales"
v-model="reviewToSales"
:min="5"
:max="100"
size="lg" />
</div>
</div>
<!-- Outputs -->
<ul class="space-y-2 mb-4">
<li>
At {{ currency(price) }} per copy after store fees, you'd need
about
<strong class="bg-yellow-200 dark:text-black"
>{{ unitsToBreakEven.toLocaleString() }} sales</strong
>
to cover this budget.
</li>
<li>
That's roughly
<strong class="bg-yellow-200 dark:text-black"
>{{ reviewsToBreakEven.toLocaleString() }} Steam
reviews</strong
>
( {{ reviewToSales }} sales per review).
</li>
</ul>
<p class="text-base text-neutral-600 dark:text-neutral-400">
Taxes not included.
</p>
</div>
</div>
</div>
</div>
</div>