feat: build compact BO2 grind companion with cumulative ranks
Some checks failed
Test and deploy staging / staging (push) Failing after 2m50s

This commit is contained in:
2026-10-08 21:06:19 +00:00
parent c2ba8febb4
commit c2bc9050c2
39 changed files with 1499 additions and 447 deletions

View File

@@ -12,6 +12,7 @@
prestigeCap = 1000,
prestigeRequiresMaxLevel = false,
compact = false,
dense = false,
disabled,
onupdate,
onedit
@@ -24,6 +25,7 @@
prestigeCap?: number;
prestigeRequiresMaxLevel?: boolean;
compact?: boolean;
dense?: boolean;
disabled: boolean;
onupdate: (rank: { itemId: number; level: number; prestige: number }) => void;
onedit: () => void;
@@ -61,78 +63,117 @@
}
</script>
<div class="space-y-1">
<div class="flex flex-wrap items-center gap-3">
<div
class="flex items-center gap-1 rounded-lg border bg-background p-1"
aria-label={`${name} level controls`}
{#if dense}
<div class="relative flex shrink-0 items-center gap-0.5">
<button
type="button"
class="rounded px-1 py-1 text-[11px] tabular-nums hover:bg-accent"
aria-label={`Edit ${name} level and prestige`}
disabled={disabled || saving}
onclick={onedit}
title="Edit level and prestige"
>
<Button
variant="ghost"
size="icon"
class={compact ? 'size-7' : 'size-10'}
aria-label={`Level down ${name}`}
disabled={disabled || saving || level <= 1}
onclick={() => adjust('level', -1)}
><Minus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
>
<Button
variant="ghost"
class={compact
? 'h-8 min-w-11 flex-col gap-0 px-1 tabular-nums'
: 'h-10 min-w-16 flex-col gap-0 px-2 tabular-nums'}
aria-label={`Edit ${name} level and prestige`}
disabled={disabled || saving}
onclick={onedit}
><span
class={compact ? 'text-[8px] text-muted-foreground' : 'text-[10px] text-muted-foreground'}
>LEVEL</span
><span class={compact ? 'text-sm font-semibold' : 'text-lg font-semibold'}>{level}{#if levelCap}<span class="text-muted-foreground">/{levelCap}</span>{/if}</span
></Button
>
<Button
variant="secondary"
size="icon"
class={compact ? 'size-7' : 'size-10'}
aria-label={`Level up ${name}`}
disabled={disabled || saving || level >= (levelCap ?? 10000)}
onclick={() => adjust('level', 1)}
><Plus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
>
</div>
<div
class="flex items-center gap-1 rounded-lg border bg-background p-1"
aria-label={`${name} prestige controls`}
<span class="sr-only">LEVEL</span>{level}{#if levelCap}<span class="text-muted-foreground"
>/{levelCap}</span
>{/if}
</button>
<Button
variant="secondary"
size="icon"
class="size-6"
aria-label={`Level up ${name}`}
disabled={disabled || saving || level >= (levelCap ?? 10000)}
onclick={() => adjust('level', 1)}><Plus class="size-3" /></Button
>
<span
role="status"
class={error
? 'absolute right-0 top-full z-20 w-52 rounded border bg-destructive p-2 text-xs text-white'
: 'sr-only'}>{saving ? 'Saving…' : error || (saved ? 'Saved' : '')}</span
>
<Button
variant="ghost"
size="icon"
class={compact ? 'size-7' : 'size-10'}
aria-label={`Prestige down ${name}`}
disabled={disabled || saving || prestige <= 0}
onclick={() => adjust('prestige', -1)}
><Minus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
>
<span class="min-w-12 text-center tabular-nums"
><span
class={compact
? 'block text-[8px] text-muted-foreground'
: 'block text-[10px] text-muted-foreground'}>PRESTIGE</span
><span class={compact ? 'text-sm font-semibold' : 'text-lg font-semibold'}>{prestige}{#if prestigeCap < 1000}<span class="text-muted-foreground">/{prestigeCap}</span>{/if}</span
></span
>
<Button
variant="secondary"
size="icon"
class={compact ? 'size-7' : 'size-10'}
aria-label={`Prestige up ${name}`}
disabled={disabled || saving || prestige >= prestigeCap || (prestigeRequiresMaxLevel && level < (levelCap ?? 10000))}
onclick={() => adjust('prestige', 1)}
><Plus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
>
</div>
</div>
<p class="min-h-3 text-[9px] text-muted-foreground" role="status">
{saving ? 'Saving…' : error || (saved ? 'Saved' : 'Tap the level to edit both values')}
</p>
</div>
{:else}
<div class="space-y-1">
<div class="flex flex-wrap items-center gap-3">
<div
class="flex items-center gap-1 rounded-lg border bg-background p-1"
aria-label={`${name} level controls`}
>
<Button
variant="ghost"
size="icon"
class={compact ? 'size-7' : 'size-10'}
aria-label={`Level down ${name}`}
disabled={disabled || saving || level <= 1}
onclick={() => adjust('level', -1)}
><Minus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
>
<Button
variant="ghost"
class={compact
? 'h-8 min-w-11 flex-col gap-0 px-1 tabular-nums'
: 'h-10 min-w-16 flex-col gap-0 px-2 tabular-nums'}
aria-label={`Edit ${name} level and prestige`}
disabled={disabled || saving}
onclick={onedit}
><span
class={compact
? 'text-[8px] text-muted-foreground'
: 'text-[10px] text-muted-foreground'}>LEVEL</span
><span class={compact ? 'text-sm font-semibold' : 'text-lg font-semibold'}
>{level}{#if levelCap}<span class="text-muted-foreground">/{levelCap}</span>{/if}</span
></Button
>
<Button
variant="secondary"
size="icon"
class={compact ? 'size-7' : 'size-10'}
aria-label={`Level up ${name}`}
disabled={disabled || saving || level >= (levelCap ?? 10000)}
onclick={() => adjust('level', 1)}
><Plus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
>
</div>
<div
class="flex items-center gap-1 rounded-lg border bg-background p-1"
aria-label={`${name} prestige controls`}
>
<Button
variant="ghost"
size="icon"
class={compact ? 'size-7' : 'size-10'}
aria-label={`Prestige down ${name}`}
disabled={disabled || saving || prestige <= 0}
onclick={() => adjust('prestige', -1)}
><Minus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
>
<span class="min-w-12 text-center tabular-nums"
><span
class={compact
? 'block text-[8px] text-muted-foreground'
: 'block text-[10px] text-muted-foreground'}>PRESTIGE</span
><span class={compact ? 'text-sm font-semibold' : 'text-lg font-semibold'}
>{prestige}{#if prestigeCap < 1000}<span class="text-muted-foreground"
>/{prestigeCap}</span
>{/if}</span
></span
>
<Button
variant="secondary"
size="icon"
class={compact ? 'size-7' : 'size-10'}
aria-label={`Prestige up ${name}`}
disabled={disabled ||
saving ||
prestige >= prestigeCap ||
(prestigeRequiresMaxLevel && level < (levelCap ?? 10000))}
onclick={() => adjust('prestige', 1)}
><Plus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
>
</div>
</div>
<p class="min-h-3 text-[9px] text-muted-foreground" role="status">
{saving ? 'Saving…' : error || (saved ? 'Saved' : 'Tap the level to edit both values')}
</p>
</div>
{/if}