feat: roomier BO2 companion with +/- rank stepping
All checks were successful
Test and deploy staging / staging (push) Successful in 2m29s

- Scale the COD page to the screen: larger gun art, text and progress
  ribbon, sized from one viewport unit so it fills a MacBook Pro 16"
  and big monitors and still fits 1280x720 without scrolling.
- Five columns on wide screens and four below, picked by CSS, so the
  server-rendered page already has the final layout.
- Replace the tabs/filters/overview rows with one toolbar of matching
  controls.
- Player and weapon ranks step with + and -: level 55 + 1 is the next
  prestige at level 1, up to max prestige. Presses show at once, batch
  while a save is running, and stepRank does the arithmetic in SQL.
- Clicking a rank still opens exact entry for big jumps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 21:50:19 +00:00
parent 8eec207f2d
commit 0ee9ed0abb
10 changed files with 1128 additions and 588 deletions

View File

@@ -2,33 +2,107 @@
import { base } from '$app/paths';
import ChevronsUp from 'lucide-svelte/icons/chevrons-up';
import Award from 'lucide-svelte/icons/award';
let { game, level, prestige }: { game: string; level: number; prestige: number } = $props();
let {
game,
level,
prestige,
levelCap = null,
prestigeCap = null,
onedit
}: {
game: string;
level: number;
prestige: number;
levelCap?: number | null;
prestigeCap?: number | null;
/** Makes the badge a button for typing an exact rank. */
onedit?: () => void;
} = $props();
const master = $derived(game === 'black-ops-2' && prestige === 10 && level === 55);
</script>
<div class="flex items-center gap-4" aria-label="Player level and prestige">
<div class="flex items-center gap-2">
<ChevronsUp class="size-7 text-amber-500" aria-label="Level icon" role="img" />
<div>
<span class="block text-[9px] tracking-widest text-muted-foreground">LEVEL</span><strong
class="text-sm tabular-nums">{level}</strong
>
</div>
</div>
<div class="flex items-center gap-2">
<svelte:element
this={onedit ? 'button' : 'div'}
type={onedit ? 'button' : undefined}
class="badge"
class:editable={!!onedit}
aria-label="Player level and prestige"
title={onedit ? 'Type an exact level and prestige' : undefined}
onclick={onedit}
role={onedit ? undefined : 'group'}
>
<span class="stat">
{#if game === 'black-ops-2' && prestige > 0 && prestige <= 10}
<img
src={`${base}/game-art/call-of-duty/ranks/black-ops-2-prestige-${master ? 11 : prestige}.webp`}
alt={master ? 'Prestige Master emblem' : `Prestige ${prestige} emblem`}
width="36"
height="36"
class="size-9 object-contain"
width="48"
height="48"
class="emblem"
/>
{:else}<Award class="size-7 text-muted-foreground" aria-label="Prestige icon" role="img" />{/if}
<div>
<span class="block text-[9px] tracking-widest text-muted-foreground">PRESTIGE</span><strong
class="text-sm tabular-nums">{master ? 'Master' : prestige}</strong
>
</div>
</div>
</div>
{:else}<Award class="icon text-muted-foreground" aria-label="Prestige icon" role="img" />{/if}
<span
><span class="label">PRESTIGE</span><strong
>{master ? 'Master' : prestige}{#if prestigeCap !== null && !master}<small
>/{prestigeCap}</small
>{/if}</strong
></span
>
</span>
<span class="stat">
<ChevronsUp class="icon text-amber-500" aria-label="Level icon" role="img" />
<span
><span class="label">LEVEL</span><strong
>{level}{#if levelCap !== null}<small>/{levelCap}</small>{/if}</strong
></span
>
</span>
</svelte:element>
<style>
.badge {
display: flex;
align-items: center;
gap: calc(18 * var(--u, 1px));
border-radius: calc(10 * var(--u, 1px));
padding: calc(2 * var(--u, 1px)) calc(8 * var(--u, 1px));
text-align: left;
}
.editable {
cursor: pointer;
}
.editable:hover {
background: color-mix(in srgb, #f59e0b 10%, transparent);
}
.editable:focus-visible {
outline: 2px solid #f59e0b;
}
.stat {
display: flex;
align-items: center;
gap: calc(8 * var(--u, 1px));
}
.emblem,
.stat :global(.icon) {
width: calc(44 * var(--u, 1px));
height: calc(44 * var(--u, 1px));
object-fit: contain;
}
.label {
display: block;
font-size: calc(11 * var(--u, 1px));
letter-spacing: 0.14em;
color: var(--site-muted);
}
strong {
display: block;
font-size: calc(24 * var(--u, 1px));
line-height: 1.05;
font-variant-numeric: tabular-nums;
}
small {
font-size: 0.6em;
font-weight: 500;
color: var(--site-muted);
}
</style>