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:
parent
b6e8d3b7ec
commit
78af43770c
29 changed files with 1699 additions and 1990 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue