Compare commits
3 Commits
c14850a048
...
256e11d320
| Author | SHA1 | Date | |
|---|---|---|---|
| 256e11d320 | |||
| 467098a9e4 | |||
| 9532e0ec89 |
70
zenno/src/lib/Spark.svelte
Normal file
70
zenno/src/lib/Spark.svelte
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { sparks } from '$lib/data/spark';
|
||||
|
||||
interface Props {
|
||||
spark: number;
|
||||
region?: keyof typeof sparks;
|
||||
}
|
||||
|
||||
const { spark, region = 'global' }: Props = $props();
|
||||
|
||||
const cur = $derived(sparks[region].find((s) => s.spark_id === spark));
|
||||
const curClass = $derived.by(() => {
|
||||
if (cur == null) {
|
||||
return [];
|
||||
}
|
||||
switch (cur.type) {
|
||||
case 1:
|
||||
return ['stat'];
|
||||
case 2:
|
||||
return ['aptitude'];
|
||||
case 3:
|
||||
return ['unique'];
|
||||
case 5:
|
||||
case 4:
|
||||
case 6:
|
||||
case 10:
|
||||
case 8:
|
||||
case 11:
|
||||
case 9:
|
||||
return ['white-spark'];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
const stars = $derived('★'.repeat(cur?.rarity ?? 0));
|
||||
</script>
|
||||
|
||||
{#if cur != null}
|
||||
<div
|
||||
class={[
|
||||
curClass,
|
||||
'spark mx-1 flex items-center rounded-xl px-2 py-1 transition ease-in hover:shadow-md hover:ease-out motion-safe:duration-75 motion-safe:hover:-translate-y-0.5 dark:shadow-neutral-950',
|
||||
]}
|
||||
>
|
||||
<span>{cur.name}</span>
|
||||
<span class="ml-2 text-xl text-amber-800 text-shadow-md dark:text-amber-300">{stars}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.white-spark {
|
||||
--spark-color: light-dark(var(--color-mist-300), var(--color-mist-600));
|
||||
}
|
||||
|
||||
.stat {
|
||||
--spark-color: light-dark(var(--color-sky-300), var(--color-sky-600));
|
||||
}
|
||||
|
||||
.aptitude {
|
||||
--spark-color: light-dark(var(--color-rose-300), var(--color-rose-700));
|
||||
}
|
||||
|
||||
.unique {
|
||||
--spark-color: light-dark(var(--color-lime-400), var(--color-lime-700));
|
||||
}
|
||||
|
||||
.spark {
|
||||
background-color: var(--spark-color);
|
||||
}
|
||||
</style>
|
||||
35
zenno/src/lib/data/affinity.ts
Normal file
35
zenno/src/lib/data/affinity.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import globalJSON from '../../../../global/affinity.json';
|
||||
|
||||
/**
|
||||
* Precomputed character pair and trio affinity.
|
||||
*/
|
||||
export interface Affinity {
|
||||
/**
|
||||
* First character in the relation.
|
||||
*/
|
||||
chara_a: number;
|
||||
/**
|
||||
* Second character in the relation.
|
||||
* chara_a < chara_b is an invariant.
|
||||
*/
|
||||
chara_b: number;
|
||||
/**
|
||||
* Third character in the relation, if it is a trio relation.
|
||||
* If defined, chara_b < chara_c is an invariant.
|
||||
*/
|
||||
chara_c?: number;
|
||||
/**
|
||||
* Total base compatibility between characters in the relation.
|
||||
*/
|
||||
affinity: number;
|
||||
}
|
||||
|
||||
export const affinity = {
|
||||
global: globalJSON as Affinity[],
|
||||
} as const;
|
||||
|
||||
export function lookup(aff: Affinity[], chara_a: number, chara_b: number, chara_c?: number): number {
|
||||
const [a, b, c] = [chara_a, chara_b, chara_c ?? Infinity].sort((a, b) => a - b);
|
||||
const r = aff.find((v) => v.chara_a === a && v.chara_b === b && (v.chara_c ?? Infinity) === c);
|
||||
return r?.affinity ?? 0;
|
||||
}
|
||||
53
zenno/src/lib/data/spark.ts
Normal file
53
zenno/src/lib/data/spark.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import globalJSON from '../../../../global/spark.json';
|
||||
|
||||
/**
|
||||
* Sparks, or succession factors.
|
||||
*/
|
||||
export interface Spark {
|
||||
/**
|
||||
* Spark ID.
|
||||
*/
|
||||
spark_id: number;
|
||||
/**
|
||||
* Regional spark name.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Regional spark description.
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Spark group.
|
||||
* Different star levels of a given spark are different spark IDs but
|
||||
* share a spark group.
|
||||
*/
|
||||
spark_group: number;
|
||||
/**
|
||||
* Spark rarity, or star level.
|
||||
*/
|
||||
rarity: 1 | 2 | 3;
|
||||
/**
|
||||
* Spark type.
|
||||
* Roughly the spark color, with extra subdivisions for white sparks.
|
||||
*/
|
||||
type: 1 | 2 | 5 | 4 | 6 | 7 | 10 | 8 | 11 | 9 | 3;
|
||||
/**
|
||||
* Possible effects applied by the spark during inspiration.
|
||||
* A random element is selected from this list according to unknown
|
||||
* distributions, then all effects in that selection are applied.
|
||||
*/
|
||||
effects: SparkEffect[][];
|
||||
}
|
||||
|
||||
/**
|
||||
* Effects that a spark can apply.
|
||||
*/
|
||||
export interface SparkEffect {
|
||||
target: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 41 | 51 | 61 | 62 | 63 | 64 | 65;
|
||||
value1?: number;
|
||||
value2: number;
|
||||
}
|
||||
|
||||
export const sparks = {
|
||||
global: globalJSON as Spark[],
|
||||
} as const;
|
||||
75
zenno/src/lib/data/uma.ts
Normal file
75
zenno/src/lib/data/uma.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import globalJSON from '../../../../global/uma.json';
|
||||
|
||||
/**
|
||||
* Uma or character card definitions.
|
||||
*/
|
||||
export interface Uma {
|
||||
/**
|
||||
* Uma ID.
|
||||
*/
|
||||
chara_card_id: number;
|
||||
/**
|
||||
* Character ID that the Uma is a variant of.
|
||||
*/
|
||||
chara_id: number;
|
||||
/**
|
||||
* Regional name of the Uma, comprised of the variant name and the character name.
|
||||
* E.g. "[Special Dreamer] Special Week".
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Regional variant name.
|
||||
* E.g. "[Special Dreamer]".
|
||||
*/
|
||||
variant: string;
|
||||
|
||||
sprint: AptitudeLevel;
|
||||
mile: AptitudeLevel;
|
||||
medium: AptitudeLevel;
|
||||
long: AptitudeLevel;
|
||||
front: AptitudeLevel;
|
||||
pace: AptitudeLevel;
|
||||
late: AptitudeLevel;
|
||||
end: AptitudeLevel;
|
||||
turf: AptitudeLevel;
|
||||
dirt: AptitudeLevel;
|
||||
|
||||
/**
|
||||
* ID of the Uma's unique skill.
|
||||
*/
|
||||
unique: number;
|
||||
/**
|
||||
* ID of the Uma's first built-in skill.
|
||||
*/
|
||||
skill1: number;
|
||||
/**
|
||||
* ID of the Uma's second built-in skill.
|
||||
*/
|
||||
skill2: number;
|
||||
/**
|
||||
* ID of the Uma's third built-in skill.
|
||||
*/
|
||||
skill3: number;
|
||||
/**
|
||||
* ID of the skill unlocked at potential level 2.
|
||||
*/
|
||||
skill_pl2: number;
|
||||
/**
|
||||
* ID of the skill unlocked at potential level 3.
|
||||
*/
|
||||
skill_pl3: number;
|
||||
/**
|
||||
* ID of the skill unlocked at potential level 4.
|
||||
*/
|
||||
skill_pl4: number;
|
||||
/**
|
||||
* ID of the skill unlocked at potential level 5.
|
||||
*/
|
||||
skill_pl5: number;
|
||||
}
|
||||
|
||||
export type AptitudeLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
|
||||
export const uma = {
|
||||
global: globalJSON as Uma[],
|
||||
};
|
||||
72
zenno/src/lib/legacy.ts
Normal file
72
zenno/src/lib/legacy.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { affinity, lookup } from './data/affinity';
|
||||
import { uma, type Uma } from './data/uma';
|
||||
|
||||
/**
|
||||
* A legacy, or parents and grandparents.
|
||||
* Defined as an applicative over generic types to reduce the API surface.
|
||||
* @see mapLegacy
|
||||
*/
|
||||
export interface Legacy<T> {
|
||||
p1: T;
|
||||
s11: T;
|
||||
s12: T;
|
||||
p2: T;
|
||||
s21: T;
|
||||
s22: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applicative fmap for legacies.
|
||||
* @param l Input legacy
|
||||
* @param f Mapping function
|
||||
* @returns Transformed legacy
|
||||
*/
|
||||
export function mapLegacy<U, V>(l: Legacy<U>, f: (u: U) => V): Legacy<V> {
|
||||
return {
|
||||
p1: f(l.p1),
|
||||
s11: f(l.s11),
|
||||
s12: f(l.s12),
|
||||
p2: f(l.p2),
|
||||
s21: f(l.s21),
|
||||
s22: f(l.s22),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A veteran, or the result of a completed career.
|
||||
*/
|
||||
export interface Veteran {
|
||||
uma: number;
|
||||
sparks: number[];
|
||||
saddles: number[];
|
||||
}
|
||||
|
||||
function findUma(umas: Uma[], u: number): Uma | null {
|
||||
return umas.find((v) => v.chara_card_id === u) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute individual affinities for a legacy using the global region ruleset.
|
||||
* @param trainee Uma (not character) ID of the trainee Uma
|
||||
* @param legacy Legacy veterans
|
||||
* @returns Individual affinities
|
||||
*/
|
||||
export function globalAffinity(trainee: number, legacy: Legacy<Veteran>): Legacy<number> {
|
||||
const t = findUma(uma.global, trainee)?.chara_id ?? 0;
|
||||
const charas = mapLegacy(legacy, (u) => findUma(uma.global, u.uma)?.chara_id ?? 0);
|
||||
const saddles = mapLegacy(legacy, (u) => new Set(u.saddles));
|
||||
const aff = affinity.global;
|
||||
const pp = lookup(aff, charas.p1, charas.p2);
|
||||
const s11 = lookup(aff, t, charas.p1, charas.s11) + saddles.p1.intersection(saddles.s11).size;
|
||||
const s12 = lookup(aff, t, charas.p1, charas.s12) + saddles.p1.intersection(saddles.s12).size;
|
||||
const s21 = lookup(aff, t, charas.p2, charas.s21) + saddles.p2.intersection(saddles.s21).size;
|
||||
const s22 = lookup(aff, t, charas.p2, charas.s22) + saddles.p2.intersection(saddles.s22).size;
|
||||
return {
|
||||
p1: lookup(aff, t, charas.p1) + pp + s11 + s12,
|
||||
s11,
|
||||
s12,
|
||||
p2: lookup(aff, t, charas.p2) + pp + s21 + s22,
|
||||
s21,
|
||||
s22,
|
||||
};
|
||||
}
|
||||
103
zenno/src/lib/race.ts
Normal file
103
zenno/src/lib/race.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
// Umamusume race mechanics adapted from KuromiAK's doc:
|
||||
// https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?usp=sharing
|
||||
|
||||
export enum RunningStyle {
|
||||
FrontRunner,
|
||||
PaceChaser,
|
||||
LateSurger,
|
||||
EndCloser,
|
||||
GreatEscape,
|
||||
}
|
||||
|
||||
export enum AptitudeLevel {
|
||||
G,
|
||||
F,
|
||||
E,
|
||||
D,
|
||||
C,
|
||||
B,
|
||||
A,
|
||||
S,
|
||||
}
|
||||
|
||||
export enum Phase {
|
||||
EarlyRace,
|
||||
MidRace,
|
||||
LateRace,
|
||||
}
|
||||
|
||||
function baseSpeed(raceLen: number): number {
|
||||
return 20 - (raceLen - 2000) / 1000;
|
||||
}
|
||||
|
||||
const strategyPhaseCoeff = [
|
||||
[1.0, 0.98, 0.962],
|
||||
[0.978, 0.991, 0.975],
|
||||
[0.938, 0.998, 0.994],
|
||||
[0.931, 1.0, 1.0],
|
||||
[1.063, 0.962, 0.95],
|
||||
] as const;
|
||||
|
||||
const distanceProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0, 1.05] as const;
|
||||
|
||||
/**
|
||||
* Calculate an Uma's last spurt target speed.
|
||||
* @param rawSpeed Uma's speed stat. No accounting for mood lol.
|
||||
* @param gutsStat Uma's guts stat.
|
||||
* @param style Running style
|
||||
* @param distance Distance aptitude
|
||||
* @param raceLen Length of the race
|
||||
* @returns Target speed in the last spurt in m/s
|
||||
*/
|
||||
export function spurtSpeed(
|
||||
rawSpeed: number,
|
||||
gutsStat: number,
|
||||
style: RunningStyle,
|
||||
distance: AptitudeLevel,
|
||||
raceLen: number,
|
||||
): number {
|
||||
const speedStat = rawSpeed <= 1200 ? rawSpeed : 1200 + (rawSpeed - 1200) * 0.5;
|
||||
const spc = strategyPhaseCoeff[style][Phase.LateRace];
|
||||
const dpm = distanceProficiencyMod[distance];
|
||||
const base = baseSpeed(raceLen);
|
||||
// Expand and rearrange terms from the doccy to make solving for the inverse easier.
|
||||
// (lateBaseTarget + 0.01*base) * 1.05 + sqrt(500*speedStat) * dpm * 0.002
|
||||
// = (base * spc + sqrt(500*speedStat) * dpm * 0.002 + 0.01 * base) * 1.05 + sqrt(500*speedStat) * dpm * 0.002
|
||||
// = 1.05*base*spc + 1.05*sqrt(500*speedStat)*dpm*0.002 + 0.0105*base + sqrt(500*speedStat)*dpm*0.002
|
||||
// = base * (1.05*spc + 0.0105) + 0.0041 * sqrt(500*speedStat) * dpm
|
||||
// plus the guts component.
|
||||
// TODO(zephyr): numerical precision?
|
||||
return base * (1.05 * spc + 0.0105) + 0.0041 * Math.sqrt(500 * speedStat) * dpm + Math.pow(450 * gutsStat, 0.597) * 0.0001;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the speed stat which produces a given target speed in the last spurt.
|
||||
* Inverse of spurtSpeed.
|
||||
* @param spurtSpeed Target speed in the last spurt in m/s
|
||||
* @param gutsStat Uma's guts stat. No accounting for mood or stat thresholds.
|
||||
* @param style Running style
|
||||
* @param distance Distance aptitude
|
||||
* @param raceLen Length of the race in meters
|
||||
* @returns Speed stat which produces the target speed
|
||||
*/
|
||||
export function inverseSpurtSpeed(
|
||||
spurtSpeed: number,
|
||||
gutsStat: number,
|
||||
style: RunningStyle,
|
||||
distance: AptitudeLevel,
|
||||
raceLen: number,
|
||||
): number {
|
||||
// spurtSpeed = base * (1.05*spc + 0.0105) + 0.0041*sqrt(500*speedStat)*dpm + pow(450*gutsStat, 0.597)*0.0001
|
||||
// spurtSpeed - base * (1.05*spc + 0.0105) - pow(450*gutsStat, 0.597)*0.0001 = 0.0041*sqrt(500*speedStat)*dpm
|
||||
// (spurtSpeed - base * (1.05*spc + 0.0105) - pow(450*gutsStat, 0.597)*0.0001)²/(0.0041²*dpm²*500) = speedStat
|
||||
// (spurtSpeed - base * (1.05*spc + 0.0105) - pow(450*gutsStat, 0.597)*0.0001)²/(0.008405*dpm²) = speedStat
|
||||
const spc = strategyPhaseCoeff[style][Phase.LateRace];
|
||||
const dpm = distanceProficiencyMod[distance];
|
||||
const base = baseSpeed(raceLen);
|
||||
const nr = spurtSpeed - base * (1.05 * spc + 0.0105) - Math.pow(450 * gutsStat, 0.597) * 0.0001;
|
||||
const r = (nr * nr) / (0.008405 * dpm * dpm);
|
||||
if (r > 1200) {
|
||||
return Math.round(2 * r - 1200);
|
||||
}
|
||||
return Math.round(r);
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
<a href={resolve('/inherit')} class="mx-8 my-1 inline-block">Inheritance Chance</a>
|
||||
<a href={resolve('/spark')} class="mx-8 my-1 inline-block">Spark Chance</a>
|
||||
<a href={resolve('/vet')} class="mx-8 my-1 inline-block">My Veterans</a>
|
||||
<a href={resolve('/spurt')} class="mx-8 my-1 inline-block">Spurt Speed</a>
|
||||
<a href={resolve('/convo')} class="mx-8 my-1 inline-block">Lobby Conversations</a>
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
<a href={resolve('/vet')}>My Veterans</a> — <i>Not yet implemented</i> — Set up and track your veterans for Zenno Rob Roy's inspiration
|
||||
and spark calculators.
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/spurt')}>Spurt Speed</a> — Calculate a horse's target speed in the last spurt and compare to other distance aptitudes
|
||||
and running styles.
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/convo')}>Lobby Conversations</a> — Check participants in lobby conversations and get recommendations on unlocking
|
||||
them quickly.
|
||||
|
||||
@@ -38,5 +38,15 @@ a:hover {
|
||||
|
||||
select {
|
||||
background-color: light-dark(var(--color-mist-300), var(--color-mist-900));
|
||||
padding: 0.5rem 0.75rem;
|
||||
padding: 0 0.75rem;
|
||||
min-height: 1.5lh;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: light-dark(var(--color-mist-300), var(--color-mist-900));
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
min-height: 1.5lh;
|
||||
}
|
||||
|
||||
97
zenno/src/routes/spurt/+page.svelte
Normal file
97
zenno/src/routes/spurt/+page.svelte
Normal file
@@ -0,0 +1,97 @@
|
||||
<script lang="ts">
|
||||
import { AptitudeLevel, inverseSpurtSpeed, RunningStyle, spurtSpeed } from '$lib/race';
|
||||
|
||||
const aptsList = Object.entries(AptitudeLevel).filter(([_name, val]) => typeof val === 'number');
|
||||
const stylesList = [
|
||||
['Front Runner', RunningStyle.FrontRunner],
|
||||
['Pace Chaser', RunningStyle.PaceChaser],
|
||||
['Late Surger', RunningStyle.LateSurger],
|
||||
['End Closer', RunningStyle.EndCloser],
|
||||
['Great Escape', RunningStyle.GreatEscape],
|
||||
] as const;
|
||||
|
||||
let rawSpeed: number = $state(1200);
|
||||
let rawGuts: number = $state(1200);
|
||||
let style: RunningStyle = $state(RunningStyle.FrontRunner);
|
||||
let distanceApt: AptitudeLevel = $state(AptitudeLevel.S);
|
||||
let raceLen: number = $state(2000);
|
||||
let isCareer: boolean = $state(false);
|
||||
|
||||
const careerMod = $derived(isCareer ? 400 : 0);
|
||||
const speedStat = $derived(rawSpeed + careerMod);
|
||||
const gutsStat = $derived(rawGuts + careerMod);
|
||||
const speed = $derived(spurtSpeed(speedStat, gutsStat, style, distanceApt, raceLen));
|
||||
|
||||
const sProf = $derived([
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.FrontRunner, AptitudeLevel.S, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.PaceChaser, AptitudeLevel.S, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.LateSurger, AptitudeLevel.S, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.EndCloser, AptitudeLevel.S, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.GreatEscape, AptitudeLevel.S, raceLen) - careerMod,
|
||||
]);
|
||||
const aProf = $derived([
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.FrontRunner, AptitudeLevel.A, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.PaceChaser, AptitudeLevel.A, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.LateSurger, AptitudeLevel.A, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.EndCloser, AptitudeLevel.A, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.GreatEscape, AptitudeLevel.A, raceLen) - careerMod,
|
||||
]);
|
||||
</script>
|
||||
|
||||
<h1 class="text-4xl">Spurt Speed Calculator</h1>
|
||||
<div class="mx-auto mt-8 grid max-w-4xl grid-cols-1 rounded-md text-center shadow-md ring md:grid-cols-4">
|
||||
<div class="m-4">
|
||||
<label for="speedStat">Speed Stat</label>
|
||||
<input type="number" id="speedStat" required bind:value={rawSpeed} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4">
|
||||
<label for="gutsStat">Guts Stat</label>
|
||||
<input type="number" id="gutsStat" required bind:value={rawGuts} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4">
|
||||
<label for="style">Style</label>
|
||||
<select id="style" required bind:value={style} class="w-full">
|
||||
{#each stylesList as [name, style]}
|
||||
<option value={style}>{style === RunningStyle.GreatEscape ? 'Great Escape (Runaway)' : name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-4">
|
||||
<label for="distanceApt">Distance Aptitude</label>
|
||||
<select id="distanceApt" required bind:value={distanceApt} class="w-full">
|
||||
{#each aptsList.toReversed() as [name, val] (val)}
|
||||
<option value={val}>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-4 md:col-start-2">
|
||||
<label for="raceLen">Race Distance</label>
|
||||
<input type="number" id="raceLen" required bind:value={raceLen} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4 self-center">
|
||||
<label for="isCareer" class="mr-1 align-middle">In Career</label>
|
||||
<input type="checkbox" id="isCareer" role="switch" bind:checked={isCareer} class="min-h-6 min-w-6 align-middle" />
|
||||
</div>
|
||||
</div>
|
||||
<span class="mt-8 block w-full text-center text-lg">
|
||||
Target spurt speed: {speed.toFixed(3)} m/s
|
||||
</span>
|
||||
{#each [[AptitudeLevel.A, aProf] as const, [AptitudeLevel.S, sProf] as const] as [level, inv] (level)}
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<span class="mt-8 block w-full">
|
||||
With {AptitudeLevel[level]} proficiency, the equivalent speed is
|
||||
</span>
|
||||
<div class="flex flex-col md:flex-row">
|
||||
{#each stylesList as [styleName, s] (s)}
|
||||
<div
|
||||
class={['m-2 flex-1 rounded-md border shadow-sm transition-shadow hover:shadow-md', s === style ? 'border-2' : null]}
|
||||
>
|
||||
<div class="h-full w-full flex-col text-center">
|
||||
<span class="block text-lg">{styleName}</span>
|
||||
<span class="block text-2xl">{inv[s]}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
Reference in New Issue
Block a user