html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: 2px solid var(--signal);
    outline-offset: .2rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
  text-align: start;
}

:root {
    --ink: #17231f;
    --muted: #68736e;
    --paper: #f7f4ee;
    --paper-deep: #ebe5d9;
    --signal: #e86d45;
    --line: rgba(23, 35, 31, .14);
    --line-strong: rgba(23, 35, 31, .26);
    --accent: #a96f3d;
    --bottom-nav-space: 6.5rem;
}

html, body {
    background: var(--paper);
    color: var(--ink);
    min-height: 100%;
}

body {
    margin: 0;
    font-family: Inter, ui-sans-serif, system-ui, sans-serif;
}

a {
    color: inherit;
}

.app-frame {
    min-height: 100vh;
    display: grid;
    grid-template-rows: auto 1fr;
}

.app-header {
    align-items: center;
    background: rgba(247, 244, 238, .92);
    border-bottom: 1px solid var(--line);
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    overflow: hidden;
    padding: 1rem clamp(1.25rem, 5vw, 5rem);
    position: relative;
}

.brand {
    font-size: 1.1rem;
    font-weight: 800;
    letter-spacing: .08em;
    position: relative;
    text-decoration: none;
    text-transform: uppercase;
    z-index: 1;
}

.brand span {
    color: var(--signal);
    margin-left: .25rem;
}

.nav-rail-shell {
    bottom: .85rem;
    left: 50%;
    max-width: min(54rem, calc(100vw - 1.25rem));
    overflow: hidden;
    position: fixed;
    transform: translateX(-50%);
    width: max-content;
    z-index: 1000;
    border-radius: 1.2rem;
}

@media (min-width: 900px) {
    .nav-rail-shell {
        max-width: min(72rem, calc(100vw - 2rem));
        width: 100%;
    }
}

.nav-rail-shell::before,
.nav-rail-shell::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2rem;
    pointer-events: none;
    opacity: 0;
    z-index: 2;
    transition: opacity .15s;
}

.nav-rail-shell::before {
    left: 0;
    background: linear-gradient(to right, rgba(23, 35, 31, .95), transparent);
}

.nav-rail-shell::after {
    right: 0;
    background: linear-gradient(to left, rgba(23, 35, 31, .95), transparent);
}

.nav-rail-shell.scroll-left::before {
    opacity: 1;
}

.nav-rail-shell.scroll-right::after {
    opacity: 1;
}

.bottom-nav-rail {
    align-items: center;
    backdrop-filter: blur(14px);
    background: rgba(23, 35, 31, .9);
    border: 1px solid rgba(247, 244, 238, .16);
    border-radius: 1.2rem;
    box-shadow: 0 .8rem 2.2rem rgba(0, 0, 0, .28);
    display: flex;
    font-size: .86rem;
    overflow-x: auto;
    padding: .45rem;
    scrollbar-width: none;
    width: 100%;
}

.pet-promenade {
    bottom: 0;
    height: 2.4rem;
    left: 0;
    overflow: hidden;
    pointer-events: none;
    position: fixed;
    right: 0;
    z-index: 900;
}

/* The nav rail is the horizontal scroll container (see .bottom-nav-rail's `overflow-x: auto`
   above), and an absolutely positioned child of a scroll container is sized against the
   container's *padding box* - the visible width - not the scrollable content width. Without this
   wrapper, the pet lane (and therefore `pet-shuffle`'s `left` percentages) would be capped at
   whatever's currently visible, and because the lane is part of the scrollable content it would
   also scroll away with everything else - leaving the scrolled-into region with no lane over it
   at all. This track carries the full `max-content` width of the nav sections and is the pet
   lane's containing block instead of the rail, so pets can roam the entire scrollable rail,
   on-screen or not - see `.pet-promenade.pet-promenade-nav` below.

   `flex: 0 0 auto` is load-bearing: as a flex item, the default `flex-shrink: 1` collapses
   `width: max-content` straight back down to the visible width, silently reintroducing the bug.
   `min-width: 100%` keeps the lane spanning the full rail when the rail doesn't scroll (few nav
   items - narrower content than the rail's own max-width clamp). */
.nav-rail-track {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    min-width: 100%;
    position: relative;
    width: max-content;
}

/* The header lane is confined to the header bar itself rather than spanning the full viewport
   height, and sits behind the brand title (see .brand's position/z-index above) and behind the
   fixed Sign out button (which always wins on z-index regardless of DOM nesting). */
.pet-promenade.pet-promenade-header {
    bottom: 0;
    height: auto;
    left: 0;
    position: absolute;
    right: 0;
    top: 0;
    z-index: 0;
}

/* The nav lane fills its containing block - `.nav-rail-track` above, not the rail itself - so
   pets walk the full scrollable width of the rail via `left` percentages, not just whatever's
   currently visible. Percentages on `left` resolve against the containing block's width, unlike
   `transform: translateX` percentages, which resolve against the sprite's own size and would not
   span the lane at all. Sits behind the icon buttons (see .nav-section's position/z-index below). */
.pet-promenade.pet-promenade-nav {
    bottom: 0;
    height: 100%;
    left: 0;
    position: absolute;
    right: 0;
    top: 0;
    z-index: 0;
}

/* Anchored near the top of the rail, behind the icon row, instead of the bottom where the
   pill's label text sits - keeps quest labels readable while pets are still visibly "in" the
   nav bar. */
.pet-promenade-nav .pet-sprite {
    animation-name: pet-shuffle, pet-wander;
    bottom: auto;
    top: .35rem;
}

/* Facing direction for the nav lane is a JS-toggled class rather than nth-child parity, since
   promenade.js can hop a sprite into or out of this lane at any position, not just alternating
   ones. Free to set directly (not baked into a keyframe, unlike .pet-walk/.pet-hop/.pet-dawdle
   below): the nav lane's travel animation (pet-shuffle) animates `left`, not `transform`, so a
   static `transform: scaleX(...)` here never fights it for the property. */
.pet-promenade-nav .pet-sprite.pet-facing-left {
    transform: scaleX(-1);
}

.pet-promenade-nav .pet-sprite.pet-facing-right {
    transform: scaleX(1);
}

/* The outer element owns travel (whichever lane's transform/left keyframes) plus a slow Y
   wander via the independent `translate` property - `translate` and `transform` are separate
   CSS properties that compose rather than compete, so the wander never fights the travel
   animation for control of `transform`. The inner <img> (see .pet-sprite img below) owns the
   fast walking gait, on its own element entirely so it never needs to share a property with
   whichever travel animation happens to be running.

   --pet-from/--pet-to/--pet-face are computed server-side by CompanionPromenade.razor's
   PetStyle from the pet's currently-tracked facing (PromenadeState flips it in
   OnEdgeArrivalAsync), re-emitted on every render - see the .pet-walk/.pet-hop/.pet-dawdle
   keyframes below, which are one-way traversals parametrized by these three custom properties
   rather than baked-in round trips. This is what lets a single, shared keyframe serve a pet
   travelling in either direction: the header/ground lanes animate `transform`, which
   pet-shuffle's nav lane above does not, so facing (a scaleX, also on `transform`) has to be
   baked into the SAME keyframe as the translateX travel here, unlike nav's free-standing class
   above. --pet-delay is always 0s (see PetStyle) - unlike --gait-delay/--wander-delay, a
   negative offset here would make a freshly re-created sprite (every lane hop recreates the
   node) enter mid-track instead of at the edge, since the offset re-applies from scratch on
   every fresh animation start. */
.pet-sprite {
    animation-delay: var(--pet-delay), var(--wander-delay);
    animation-duration: var(--pet-duration), var(--wander-duration);
    animation-iteration-count: infinite, infinite;
    animation-name: pet-walk, pet-wander;
    animation-timing-function: linear, ease-in-out;
    bottom: var(--pet-bottom);
    height: var(--pet-size);
    position: absolute;
    transform-origin: bottom center;
    width: var(--pet-size);
}

.pet-sprite img {
    animation: pet-gait var(--gait-duration) ease-in-out var(--gait-delay) infinite;
    filter: drop-shadow(0 .12rem .16rem rgba(23, 35, 31, .22));
    height: 100%;
    transform-origin: bottom center;
    width: 100%;
}

.pet-style-2 {
    animation-name: pet-hop, pet-wander;
}

.pet-style-3 {
    animation-name: pet-dawdle, pet-wander;
}

/* One-way traversal (0% = lane entry edge, 100% = the opposite edge) rather than a round trip -
   this is what makes the `animationiteration` event fire exactly once per edge-to-edge crossing,
   which is promenade.js's hop trigger. The turnaround itself already happens off-screen either
   way (-3rem / 100vw+3rem are both outside the visible viewport), so swapping --pet-from/--pet-
   to/--pet-face and restarting the animation between iterations is invisible to the player - it
   reads as the same continuous walk, now free to continue in a freshly-chosen lane. */
@keyframes pet-walk {
    0% { transform: translateX(var(--pet-from, -3rem)) scaleX(var(--pet-face, 1)); }
    100% { transform: translateX(var(--pet-to, calc(100vw + 3rem))) scaleX(var(--pet-face, 1)); }
}

@keyframes pet-hop {
    0% { transform: translateX(var(--pet-from, -3rem)) translateY(0) scaleX(var(--pet-face, 1)); }
    50% { transform: translateX(calc((var(--pet-from, -3rem) + var(--pet-to, calc(100vw + 3rem))) / 2)) translateY(-.28rem) scaleX(var(--pet-face, 1)); }
    100% { transform: translateX(var(--pet-to, calc(100vw + 3rem))) translateY(0) scaleX(var(--pet-face, 1)); }
}

@keyframes pet-dawdle {
    0% { transform: translateX(var(--pet-from, -3rem)) rotate(-2deg) scaleX(var(--pet-face, 1)); }
    100% { transform: translateX(var(--pet-to, calc(100vw + 3rem))) rotate(3deg) scaleX(var(--pet-face, 1)); }
}

/* The nav rail is a narrow, variable-width pill, so pets walk it via `left` percentages
   (resolved against the rail's own width) rather than the viewport-width `pet-walk` keyframes
   above - this is what lets them pass behind every pill, not just whichever one they happen to
   start closest to. One-way (see the .pet-walk comment above for why); --pet-from-pct/--pet-to-
   pct are the nav-lane equivalent of --pet-from/--pet-to, set by promenade.js. Facing is the
   free-standing .pet-facing-left/.pet-facing-right class above, not a var here, since this
   keyframe never touches `transform`. */
@keyframes pet-shuffle {
    0% { left: var(--pet-from-pct, -10%); }
    100% { left: var(--pet-to-pct, 105%); }
}

/* Fast secondary motion for the sprite's own <img> - a short bob plus a slight sway, on a period
   far shorter than any travel animation's, is what reads as an actual walking gait rather than a
   sprite sliding smoothly along a track. Shared across every lane and the sanctuary. */
@keyframes pet-gait {
    0%, 100% { transform: translateY(0) rotate(0); }
    25% { transform: translateY(-6%) rotate(-4deg); }
    50% { transform: translateY(0) rotate(0); }
    75% { transform: translateY(-6%) rotate(4deg); }
}

/* Slow vertical drift on the outer wrapper via the independent `translate` property (see the
   comment on .pet-sprite above for why that property and not `transform`). Deliberately not a
   round number of cycles relative to the travel duration, so the vertical drift doesn't line up
   the same way on every pass and start to look like a mechanical sine wave. */
@keyframes pet-wander {
    0% { translate: 0 0; }
    33% { translate: 0 -.35rem; }
    66% { translate: 0 .2rem; }
    100% { translate: 0 0; }
}

/* The Pets page sanctuary - the payoff view for the collection. Unlike the ambient promenade
   lanes, these sprites are real interactive buttons (tappable for a detail card), so they use
   `left` percentages rather than `transform: translateX` - percentages on `left` resolve against
   the container's width, while `transform` percentages resolve against the sprite's own size,
   which would not scale correctly across the sanctuary's responsive width. */
.pet-sanctuary {
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .9rem;
    /* Half of the old 13rem (6.5rem) clips the tallest/highest-riding sprite combination - the
       wander/roam animations plus the largest size band (up to 4.375rem) landing on the highest
       bottom band (2.9rem) need more headroom than a flat half gives. 9rem keeps the sanctuary
       much shorter than before (matching the "pets only use the bottom half" report) while still
       leaving every sprite fully inside the frame. */
    height: 8rem;
    overflow: hidden;
    padding: .9rem;
    position: relative;
}

.sanctuary-empty {
    align-items: center;
    display: flex;
    height: 100%;
    justify-content: center;
    text-align: center;
}

.sanctuary-pet {
    animation-delay: var(--pet-delay), var(--wander-delay);
    animation-duration: var(--pet-duration), var(--wander-duration);
    animation-iteration-count: infinite, infinite;
    animation-name: sanctuary-roam, pet-wander;
    animation-timing-function: linear, ease-in-out;
    background: none;
    border: 0;
    bottom: var(--pet-bottom);
    cursor: pointer;
    height: var(--pet-size);
    padding: 0;
    position: absolute;
    width: var(--pet-size);
}

.sanctuary-pet img {
    animation: pet-gait var(--gait-duration) ease-in-out var(--gait-delay) infinite;
    filter: drop-shadow(0 .12rem .16rem rgba(23, 35, 31, .22));
    height: 100%;
    transform-origin: bottom center;
    width: 100%;
}

.catalog-pet-img {
    height: 3rem;
    width: 3rem;
    object-fit: contain;
    filter: drop-shadow(0 .1rem .12rem rgba(23, 35, 31, .18));
}

.sanctuary-pet:focus-visible {
    outline: 2px solid var(--signal);
    outline-offset: .2rem;
}

@keyframes sanctuary-roam {
    0% { left: -8%; }
    49% { left: 104%; }
    50% { left: 104%; }
    99% { left: -8%; }
    100% { left: -8%; }
}

/* Facing lives on the button (outer), not the <img>, the same way the nav lane's static facing
   does - sanctuary-roam only ever animates `left` on the button, leaving its `transform` free for
   a static per-sprite value, while the <img>'s own `transform` is now claimed by the pet-gait
   animation and can't also carry a competing static scaleX. */
.sanctuary-pet:nth-child(odd) {
    transform: scaleX(1);
}

.sanctuary-pet:nth-child(even) {
    transform: scaleX(-1);
}

.pet-detail-card {
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .9rem;
    margin-top: .9rem;
    padding: .9rem;
}

@media (prefers-reduced-motion: reduce) {
    .sanctuary-pet,
    .sanctuary-pet img {
        animation: none;
    }

    .sanctuary-pet:nth-child(1) { left: 6%; }
    .sanctuary-pet:nth-child(2) { left: 20%; }
    .sanctuary-pet:nth-child(3) { left: 34%; }
    .sanctuary-pet:nth-child(4) { left: 48%; }
    .sanctuary-pet:nth-child(5) { left: 62%; }
    .sanctuary-pet:nth-child(6) { left: 76%; }
    .sanctuary-pet:nth-child(n+7) { left: 88%; }
}

@media (prefers-reduced-motion: reduce) {
    .pet-sprite,
    .pet-sprite img {
        animation: none;
    }

    .pet-sprite {
        opacity: .72;
    }

    .pet-sprite:nth-child(1) { left: 8%; }
    .pet-sprite:nth-child(2) { left: 24%; }
    .pet-sprite:nth-child(3) { left: 42%; }
    .pet-sprite:nth-child(4) { left: 60%; }
    .pet-sprite:nth-child(5) { left: 76%; }
    .pet-sprite:nth-child(6) { left: 88%; }
}

.bottom-nav-rail::-webkit-scrollbar {
    display: none;
}

.nav-section {
    display: flex;
    flex-wrap: nowrap;
    gap: .35rem;
    min-width: max-content;
    position: relative;
    z-index: 1;
}

.nav-divider {
    width: 1px;
    align-self: stretch;
    margin: 0 .15rem;
    background: rgba(23, 35, 31, .14);
}

.nav-pill {
    align-items: center;
    background: transparent;
    border: 0;
    border-radius: .95rem;
    color: rgba(247, 244, 238, .72);
    display: inline-flex;
    flex-direction: column;
    gap: .4rem;
    min-width: 4.25rem;
    padding: .35rem .48rem;
    text-decoration: none;
}

.nav-pill.active {
    background: rgba(247, 244, 238, .12);
    color: var(--paper);
}

.nav-icon {
    align-items: center;
    background: rgba(247, 244, 238, .1);
    border-radius: 999px;
    color: currentColor;
    display: inline-flex;
    height: 1.8rem;
    justify-content: center;
    position: relative;
    width: 1.8rem;
}

.nav-badge {
    background: var(--signal);
    border: 2px solid rgba(23, 35, 31, .9);
    border-radius: 999px;
    height: .55rem;
    position: absolute;
    right: -.1rem;
    top: -.1rem;
    width: .55rem;
}

.nav-pill.active .nav-icon {
    background: var(--signal);
    color: var(--paper);
}

.nav-icon svg {
    fill: none;
    height: 1.12rem;
    stroke: currentColor;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.8;
    width: 1.12rem;
}

.nav-label {
    font-size: .66rem;
    font-weight: 800;
    letter-spacing: .03em;
}

/* Flex row (2026-08-19, was a single-child box) - now holds the header purse chip to the left of
   Sign out/Sign in, both positioned together as one fixed unit rather than two separately
   positioned floats that would need to stay in sync with each other's width. */
.top-session-actions {
    align-items: center;
    display: flex;
    gap: .5rem;
    position: fixed;
    right: clamp(.75rem, 3vw, 1.25rem);
    top: .75rem;
    z-index: 1100;
}

.session-button {
    background: rgba(23, 35, 31, .86);
    border: 1px solid rgba(247, 244, 238, .18);
    border-radius: 999px;
    color: var(--paper);
    cursor: pointer;
    font: inherit;
    font-size: .78rem;
    font-weight: 800;
    padding: .55rem .8rem;
    text-decoration: none;
}

.app-main {
    align-items: start;
    display: grid;
    justify-items: center;
    padding: clamp(1.25rem, 4vw, 2.5rem) 1.25rem var(--bottom-nav-space);
}

.welcome-panel, .auth-card {
    max-width: 39rem;
    width: 100%;
}

/* The admin shell is a desktop data workspace, not a reading column. It used to share the
   39rem cap with .welcome-panel/.auth-card, which put every admin grid inside a container
   barely wider than .admin-table's own 38rem min-width - so any real column set fell
   straight into horizontal scroll. The GM works from a Windows desktop; give the tables
   the room the player pages already get (.today-page 64rem, .shop-page 72rem) and then
   some, since these are the densest screens in the app. (Two rules used to exist for this
   selector, added in separate rounds - merged, nothing changed.) */
.admin-shell {
    border-top: 4px solid var(--signal);
    max-width: 88rem;
    min-width: 0;
    padding-top: 1.5rem;
    width: 100%;
}

/* Admin heading scale, scoped to .admin-shell only - the player-facing h1 (clamp 2.4rem-5.5rem,
   an editorial magazine headline) is correct on Today/Shop/Collection and must stay untouched.
   On an 88rem-wide desktop data workspace that same clamp resolves near its 5.5rem ceiling,
   which is what made every admin page's title dwarf the table beneath it. h2/h3 had NO rule
   anywhere in this file before this - every admin section heading ("Existing Lines", "Add Line
   Entry", "Repair") fell back to the browser/Bootstrap default of em-based sizing and margins,
   which is why vertical rhythm across admin pages looked inconsistent page to page: it was never
   actually being set.
   The h2/h3 element selectors are wrapped in :where(...), which matches identically but
   contributes zero specificity - so this rule's real specificity is just .admin-shell's (0,1,0).
   Written as plain ".admin-shell h2" this regressed Admin.razor's "Quick Author" heading: that
   h2 carries a more specific class, .admin-hub-heading ((0,1,0) for the class, but "h2" added
   (0,1,1) beat it regardless of which rule came later in the file. The same trap exists for h3
   nested under .admin-hub-group - it only kept working because .admin-hub-group h3 happens to be
   declared after this block, a tie broken by source order that the next reordering would have
   silently broken. :where() makes any future page-specific heading override win on its own
   merits (narrower selector, or simply appearing later at equal specificity) instead of by luck. */
.admin-shell :where(h1) {
    font-size: clamp(1.7rem, 2.4vw, 2.2rem);
    letter-spacing: -.02em;
}

.admin-shell :where(h2) {
    font-size: 1.05rem;
    font-weight: 800;
    margin: 1.75rem 0 .75rem;
}

.admin-shell :where(h3) {
    font-size: .92rem;
    font-weight: 700;
    margin: 1rem 0 .5rem;
}

.welcome-panel {
    border-left: 4px solid var(--signal);
    padding: 1rem 0 1rem clamp(1.25rem, 4vw, 3rem);
}

.auth-card {
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: 1rem;
    padding: clamp(1.5rem, 5vw, 3rem);
}

.admin-hub-heading {
    font-size: clamp(1.35rem, 3vw, 2rem);
    margin: 2rem 0 .5rem;
}

.admin-hub-grid {
    display: grid;
    gap: 2rem;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    margin-top: 2.5rem;
}

.admin-hub-group h3 {
    color: var(--signal);
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .12em;
    margin: 0 0 .65rem;
    text-transform: uppercase;
}

.admin-hub-card {
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .75rem;
    color: inherit;
    display: block;
    padding: .75rem .85rem;
    text-decoration: none;
    transition: border-color .15s, box-shadow .15s;
}

.admin-hub-card:hover {
    border-color: var(--accent);
    box-shadow: 0 .2rem .6rem rgba(23, 35, 31, .08);
}

.admin-hub-card strong {
    display: block;
    font-size: .95rem;
    margin-bottom: .2rem;
}

.admin-hub-card .muted {
    font-size: .78rem;
    line-height: 1.4;
    margin: 0;
}

.eyebrow {
    color: var(--signal);
    font-size: .75rem;
    font-weight: 800;
    letter-spacing: .16em;
    margin: 0 0 1rem;
    text-transform: uppercase;
}

h1 {
    font-size: clamp(2.4rem, 7vw, 5.5rem);
    letter-spacing: -.06em;
    line-height: .95;
    margin: 0 0 1rem;
}

.muted {
    color: var(--muted);
    line-height: 1.6;
    max-width: 35rem;
}

.primary-button {
    background: var(--ink);
    border: 0;
    border-radius: 999px;
    color: var(--paper);
    cursor: pointer;
    display: inline-block;
    font: inherit;
    font-weight: 700;
    margin-top: 1.25rem;
    padding: .8rem 1.25rem;
    text-decoration: none;
}

.primary-button:disabled {
    background: rgba(23, 35, 31, .24);
    color: rgba(247, 244, 238, .76);
    cursor: not-allowed;
}

.secondary-button {
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--ink);
    cursor: pointer;
    font: inherit;
    margin-top: .6rem;
    padding: .65rem 1rem;
    width: 100%;
}

.danger-button {
    background: #8f2f20;
    border: 0;
    border-radius: 999px;
    color: var(--paper);
    cursor: pointer;
    font: inherit;
    font-size: .8rem;
    font-weight: 800;
    margin-left: .4rem;
    padding: .55rem .85rem;
}

.danger-button:disabled {
    cursor: not-allowed;
    opacity: .45;
}

/* Link-styled button for inline actions (sortable table headers, inline edit links). This had
   no rule at all - it rendered as a native gray browser button wherever it was used, most
   visibly as the whole sortable header row of the Task Library table. */
.link-button {
    background: transparent;
    border: 0;
    color: inherit;
    cursor: pointer;
    font: inherit;
    padding: 0;
}

.link-button:hover {
    color: var(--signal);
    text-decoration: underline;
}

.admin-inline-input {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: .45rem;
    color: var(--ink);
    font: inherit;
    max-width: 8rem;
    padding: .5rem .6rem;
}

.stacked-form {
    display: grid;
    gap: 1rem;
    margin-top: 2rem;
}

.stacked-form label {
    display: grid;
    font-size: .8rem;
    font-weight: 700;
    gap: .4rem;
    /* Zero-min single column so the control below the caption can never exceed the label.
       Without this, the grid's implicit auto column sizes to the CONTROL's intrinsic width -
       a select is sized to its longest option ("Name (Skill · Size)") - and the label, as a
       grid item of the form's own zero-min track, overflows that track and paints over the
       neighboring column's field. minmax(0, 1fr) on the FORM track (see .admin-shell
       .stacked-form) only stops the grid growing; it neither constrains nor clips the item.
       This is the actual "Task extends under Quest name" fix; the track rule alone was not
       enough (confirmed re-reported in the browser). */
    grid-template-columns: minmax(0, 1fr);
}

/* The selector is input:not([type=checkbox]) plus .stacked-form select. Blazor's InputSelect
   renders a <select>, not an <input>, so the original input-only selector left every form
   dropdown (Task Library's Skill/Size, the schedule modal's Recurrence, and the equivalent
   fields on every other admin form) with native browser chrome next to the styled text inputs.
   Textareas (the Interventions message box) had the same gap - no rule matched them at all,
   so they rendered as raw browser chrome too. */
.stacked-form input:not([type=checkbox]),
.stacked-form select,
.stacked-form textarea {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: .45rem;
    color: var(--ink);
    font: inherit;
    padding: .75rem;
    /* The width constraint is load-bearing, not decorative: without it, a select renders at
       its intrinsic width (the longest option) and overflows its grid cell into the neighbor
       column - see the .stacked-form label rule above for the full mechanism. width 100% with
       a zero-min track makes the control fill exactly its cell and nothing more; min-width: 0
       lets it shrink below its intrinsic (longest-option) size. */
    min-width: 0;
    width: 100%;
}

.stacked-form textarea {
    min-height: 6rem;
    resize: vertical;
}

.checkbox-label {
    align-items: center;
    display: flex !important;
    flex-direction: row;
    font-weight: 500 !important;
}

/* Checkbox-in-label rows inside .stacked-form (e.g. Task Library's "Appears every day").
   .stacked-form label is display:grid, which would stack the checkbox on its own row with the
   caption text below it instead of beside it - same layout the .checkbox-label class exists to
   produce for the schedule modal's weekday list, applied here for the form's own checkboxes. */
.stacked-form label:has(input[type=checkbox]) {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: .4rem .5rem;
}

.stacked-form input[type=checkbox] {
    appearance: none;
    -webkit-appearance: none;
    width: 1.15rem;
    height: 1.15rem;
    border: 1.5px solid var(--line-strong, rgba(23, 35, 31, .26));
    border-radius: .25rem;
    background: var(--paper);
    cursor: pointer;
    flex-shrink: 0;
    position: relative;
    vertical-align: middle;
}

.stacked-form input[type=checkbox]:checked {
    background: var(--ink);
    border-color: var(--ink);
}

.stacked-form input[type=checkbox]:checked::after {
    content: '';
    position: absolute;
    left: .3rem;
    top: .05rem;
    width: .35rem;
    height: .65rem;
    border: solid var(--paper);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.stacked-form input[type=checkbox]:focus-visible {
    outline: 2px solid var(--signal);
    outline-offset: 1px;
}

/* Every form inside .admin-shell gets the width cap and wide column layout automatically -
   .stacked-form stays single-column and full-bleed everywhere else (Login's .auth-card, the
   player-facing screens), since neither selector below matches outside .admin-shell.
   This used to be opt-in: admin edit forms had to add a second .admin-form class to get columns
   at all, and only 5 of 12 admin pages ever did. The other 7 (ProgressionManager, PetsManager,
   ProjectAuthor, Interventions, SkillsCurve, Admin, plus Task Library's schedule modal) rendered
   every field - including things like a bare "Step number" input - stretched to the form's full
   width, which on the 88rem .admin-shell could mean a single number box spanning 1300+px.
   .admin-form may still appear in markup on the pages that already had it; it no longer does
   anything there and is harmless to leave or remove. */
.admin-shell .stacked-form {
    max-width: 50rem;
}

@media (min-width: 900px) {
    .admin-shell .stacked-form {
        /* Explicit two-column grid with zero-min tracks. auto-fit/minmax(16rem, 1fr) let a
           track's min-content (e.g. a select sized to its longest option, a long label) force
           tracks wider than their fr share, shoving the neighbor column's field out from under
           it - the Interventions message form's offer fields overlapped into the next column
           this way. minmax(0, 1fr) makes that overflow geometrically impossible; fields clip
           or wrap inside their column instead. */
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 1rem 1.5rem;
    }

    /* Fields that need the full row: long text, notes, JSON. */
    .admin-shell .stacked-form .form-span-all {
        grid-column: 1 / -1;
    }

    /* Buttons pinned to their own full-width row so Save is never mid-grid. */
    .admin-shell .stacked-form .form-actions {
        display: flex;
        gap: .75rem;
        grid-column: 1 / -1;
        justify-content: flex-start;
    }
}

/* The Task Library schedule modal is a 30rem dialog; its .stacked-form must stay single-column
   even though the shared rule above now forces two - two ~13rem columns would crush the
   recurrence select and weekday checkboxes. */
.modal .stacked-form {
    grid-template-columns: 1fr;
}

/* Related-field group inside an admin form (e.g. the message form's attached bonus-quest
   offer): spans the full form width and nests its own column grid, so the message fields read
   as one unit instead of spilling into the surrounding form's grid. */
.admin-form-group {
    background: color-mix(in srgb, var(--paper-deep) 45%, transparent);
    border: 1px solid var(--line);
    border-radius: .6rem;
    display: grid;
    gap: 1rem 1.5rem;
    grid-column: 1 / -1;
    padding: .85rem;
}

@media (min-width: 900px) {
    /* Same zero-min discipline as the parent form grid - a select sized to its longest option
       must not shove the neighboring column's field. */
    .admin-form-group {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* .admin-form also appears bare, without .stacked-form, wrapping a handful of related
   fields/buttons inline (PlayerResources.razor's purse/hatch-dust/incubation/skill-repair rows).
   That combination never matched any rule above (both require .stacked-form) and rendered as
   raw browser-chrome inputs and unstyled labels stacked by plain block flow. The :not(.stacked-
   form) guard keeps this from also matching the 5 pages that already combine both classes on the
   same element - those get the grid layout above instead, not this flex row. */
.admin-form:not(.stacked-form) {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: .75rem 1.25rem;
    margin-top: 1rem;
}

.admin-form:not(.stacked-form) label {
    display: grid;
    font-size: .8rem;
    font-weight: 700;
    gap: .3rem;
}

.admin-form:not(.stacked-form) input:not([type=checkbox]),
.admin-form:not(.stacked-form) select,
.admin-form:not(.stacked-form) textarea {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: .45rem;
    color: var(--ink);
    font: inherit;
    padding: .6rem .75rem;
}

/* Compact select used inside table rows (assign-to-player, quick status change). */
.inline-select {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: .45rem;
    color: var(--ink);
    font: inherit;
    font-size: .78rem;
    max-width: 12rem;
    padding: .3rem .5rem;
}

/* Filter/search bar above a grid. */
.admin-filter-bar {
    align-items: end;
    display: flex;
    flex-wrap: wrap;
    gap: .75rem;
    margin-top: 1.5rem;
}

.admin-filter-bar label {
    display: grid;
    font-size: .72rem;
    font-weight: 700;
    gap: .3rem;
}

.admin-filter-bar input,
.admin-filter-bar select {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: .45rem;
    color: var(--ink);
    font: inherit;
    padding: .4rem .6rem;
}

/* Progression Manager's "Existing Lines" cards - previously 13 inline style="" attributes on
   this one page (every other admin page has 0-4), the worst offender for the admin CSS pass. */
.progression-line-card {
    margin-bottom: 1rem;
    padding: 1rem;
}

.progression-line-head {
    align-items: center;
    display: flex;
    justify-content: space-between;
}

.progression-line-steps {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: .5rem;
}

.progression-step {
    text-align: center;
    width: 5rem;
}

.progression-step img {
    height: 3rem;
    object-fit: contain;
    width: 3rem;
}

.progression-step-label {
    font-size: .65rem;
    margin: .25rem 0 0;
}

.progression-step-claimed {
    color: var(--accent);
    font-size: .55rem;
    margin: .125rem 0 0;
}

/* Line-key select + "new line" text input sitting side by side inside one label. */
.form-inline-pair {
    align-items: center;
    display: flex;
    gap: .5rem;
}

.form-inline-pair > select,
.form-inline-pair > input {
    flex: 1;
}

/* Step-number conflict/non-contiguous warnings on the line-entry form. */
.step-warning-box {
    background: rgba(232, 109, 69, .08);
    border: 1px solid var(--signal);
    border-radius: .5rem;
    margin-top: .5rem;
    padding: .5rem .75rem;
}

.step-warning-box p {
    font-size: .8rem;
    margin: .125rem 0;
}

.page-message {
    color: var(--muted);
    text-align: center;
}

.today-page {
    max-width: 64rem;
    min-width: 0;
    overflow-x: hidden;
    width: 100%;
}

.today-heading {
    align-items: end;
    display: flex;
    justify-content: space-between;
    margin-bottom: 2rem;
}

.today-heading h1 {
    margin-bottom: 0;
}

/* Chips row beside the Today heading: purse + score + weekly streak. Wrapped so a narrow phone
   stacks them under the title instead of squashing it - but as of 2026-08-19 all three are short
   enough (icon + a number or two, no label words) that they fit on one line at phone widths
   without actually needing to wrap; flex-wrap stays as the safety net for an even narrower case
   (a long player name pushing the heading wider, a browser zoom, ...). Padding/gap tightened from
   the original .6rem/.9rem/.5rem - three pills read as cramped-but-legible far more readily than
   they read as "didn't fit," and legibility was never the complaint. */
.today-chips {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    justify-content: flex-end;
}

/* Both are real <button>s (2026-08-19, tap-to-expand) - the properties below EXIST to override
   native button chrome (Chrome/Firefox default a button to a gray filled background, a platform
   UI font-family, and an inline-block box), not just to style a fresh element the way they would
   on the <div>s these used to be. Every one of background/font-family/cursor is load-bearing for
   that reason, not decorative.
   No flex `gap` (2026-08-19 correction, when the purse chip moved out and these two grew a label
   suffix) - a uniform gap across icon/number/separator/label would space the icon away from its
   own number the same amount as everything else, when the actual shape wanted is icon+number
   glued as one tight unit ("⭐21") and the breathing room concentrated around the center-dot
   separator instead. .chip-sep's own margins carry that spacing now; align-items:center is kept
   for vertical centering only. */
.score-chip,
.streak-chip {
    align-items: center;
    background: none;
    border: 1px solid var(--line);
    border-radius: 999px;
    cursor: pointer;
    display: inline-flex;
    font-family: inherit;
    font-size: .8rem;
    padding: .45rem .7rem;
}

.score-chip:hover,
.streak-chip:hover {
    background: color-mix(in srgb, var(--ink) 5%, transparent);
}

.score-chip:focus-visible,
.streak-chip:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.streak-chip {
    color: var(--signal);
    font-weight: 700;
}

/* --accent instead of --streak-chip's --signal so the two read as distinct pills at a glance,
   not one repeated chip. */
.score-chip {
    color: var(--accent);
    font-weight: 700;
}

/* The center-dot separator between a chip's icon+number and its label word ("⭐21 · Today's
   Score"). opacity, not a muted color token - it needs to read as de-emphasized punctuation
   against BOTH .score-chip's --accent text and .streak-chip's --signal text, and a single fixed
   color would clash with one of the two. */
.chip-sep {
    margin: 0 .35rem;
    opacity: .55;
}

/* The purse chip's coin icon is GameIcon Type="coin" - .game-icon's base size is a 2rem circular
   ink-badge meant for a title-bar/button context, which would blow out a compact inline pill.
   Stripped down to a bare 1em glyph that sizes with the chip's own font-size and inherits its
   text color, instead of the badge's fixed white-on-ink look.
   Scoped to .top-session-actions only (2026-08-19) - the purse chip lives in the global header
   now, to the left of Sign out, not on Today's own chip row (.today-chips no longer renders a
   GameIcon at all: Score/Streak use emoji, and the purse chip that used to sit beside them moved
   out - see NavMenu.razor and ARCHITECTURE-CURRENT.md's "Coin purse" section for why). */
.top-session-actions .game-icon {
    background: none;
    border: none;
    box-shadow: none;
    color: inherit;
    height: 1em;
    width: 1em;
}

.top-session-actions .game-icon svg {
    height: 1em;
    stroke-width: 2.2;
    width: 1em;
}

/* GM message inbox: floating mail button below the Sign out pill, and the popup it opens
   (modeled on the reward ceremony). While a bonus-quest offer is pending the popup cannot be
   dismissed - Accept or Decline is forced, so an undecided offer is never lost by accident. */
.mail-button {
    align-items: center;
    background: rgba(23, 35, 31, .86);
    border: 1px solid rgba(247, 244, 238, .18);
    border-radius: 999px;
    color: var(--paper);
    cursor: pointer;
    display: flex;
    font: inherit;
    height: 2.2rem;
    justify-content: center;
    padding: 0 .8rem;
    position: fixed;
    right: clamp(.75rem, 3vw, 1.25rem);
    top: 4.35rem;
    width: 2.2rem;
    z-index: 1100;
}

.mail-badge {
    align-items: center;
    background: var(--signal);
    border-radius: 999px;
    color: var(--paper);
    display: flex;
    font-size: .62rem;
    font-weight: 800;
    height: 1.1rem;
    justify-content: center;
    min-width: 1.1rem;
    padding: 0 .3rem;
    position: absolute;
    right: -.35rem;
    top: -.35rem;
}

/* Generic fixed-overlay popup shell (2026-08-19) - originally mail-only (.mail-backdrop/
   .mail-popup/.mail-popup-head/.mail-close), renamed and generalized so Today.razor's chip-info
   popover can reuse the exact same shape instead of growing a second one (CLAUDE.md rule 6).
   MessageInbox.razor's mail-specific pieces (.mail-message, .mail-badge, .mail-button, ...) keep
   their own names - only the shell (backdrop, panel sizing/shadow, header row, close button) was
   generic in the first place. */
.popover-backdrop {
    background: rgba(23, 35, 31, .5);
    inset: 0;
    position: fixed;
    z-index: 1190;
}

.popover-panel {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: .8rem;
    box-shadow: 0 1.25rem 3rem rgba(35, 27, 20, .28);
    display: grid;
    gap: .6rem;
    /* fit-content is load-bearing: position:fixed with inset:0 and an auto height stretches
       the box to fill the viewport (capped only by max-height), so a two-line message made a
       full-screen popup with the text stranded in the middle. fit-content sizes it to its
       content instead; max-height + overflow-y still bound and scroll long message lists. */
    height: fit-content;
    inset: 0;
    margin: auto;
    max-height: min(36rem, calc(100vh - 2rem));
    max-width: 30rem;
    overflow-y: auto;
    padding: clamp(1rem, 3vw, 1.35rem);
    position: fixed;
    width: min(100%, 30rem);
    z-index: 1200;
}

.popover-head {
    align-items: flex-start;
    display: flex;
    gap: .75rem;
    justify-content: space-between;
}

.popover-head h2 {
    margin: 0;
}

.popover-close {
    margin-top: 0;
    padding: .45rem .9rem;
    width: auto;
}

.mail-message {
    border-bottom: 1px solid var(--line);
    display: grid;
    gap: .4rem;
    padding: .85rem 0;
}

.mail-message:last-of-type {
    border-bottom: 0;
    padding-bottom: 0;
}

.mail-message-text {
    font-size: .95rem;
    line-height: 1.6;
    margin: 0;
}

.mail-force-note {
    margin: .25rem 0 0;
}

.gm-offer {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: .6rem;
    display: grid;
    gap: .3rem;
    margin-top: .4rem;
    padding: .75rem;
}

.gm-offer-name {
    font-weight: 800;
    margin: 0;
}

.gm-offer p {
    margin: 0;
}

.gm-offer-actions {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: .35rem;
}

.gm-offer-actions .primary-button,
.gm-offer-actions .secondary-button {
    margin-top: 0;
    width: auto;
}

/* Started-state marker on quest cards (optional Start action, §2.4). */
.quest-started {
    align-self: center;
    color: var(--signal);
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .04em;
    text-transform: uppercase;
}

.quest-grid {
    display: grid;
    gap: .75rem;
    grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr));
}

.shop-page, .garden-page, .chests-page, .collection-page, .pets-page {
    max-width: 72rem;
    min-width: 0;
    overflow-x: hidden;
    width: 100%;
}

.shop-hero {
    align-items: end;
    display: flex;
    flex-wrap: wrap;
    gap: .5rem 1rem;
    justify-content: space-between;
    margin-bottom: 1.25rem;
    min-width: 0;
}

.shop-hero h1 {
    margin-bottom: 0;
}

.shop-wallet {
    align-items: center;
    background: var(--ink);
    border-radius: 999px;
    color: var(--paper);
    display: inline-flex;
    gap: .45rem;
    padding: .55rem .8rem;
    white-space: nowrap;
}

/* Renamed from .sticky-currency (2026-08-19) - the per-page purse displays this styled
   (Chests.razor, Shop.razor) are gone; the shape now lives once, in the global header, to the
   left of Sign out (see .top-session-actions and NavMenu.razor). Kept `position` OUT of this rule
   now - the old .sticky-currency scrolled with page content, but .top-session-actions already
   fixes the whole row's position, so this only needs to be a plain inline pill inside it.
   button-chrome resets (background/border/cursor/font-family) are load-bearing here, same
   reasoning as .purse-chip/.score-chip/.streak-chip below: this is a real <button> now (tap
   opens the same info popover the old Today purse chip had), not the <div> .sticky-currency was. */
.header-purse-chip {
    align-items: center;
    background: var(--ink);
    border: 1px solid rgba(247, 244, 238, .18);
    border-radius: 999px;
    color: var(--paper);
    cursor: pointer;
    display: inline-flex;
    font-family: inherit;
    font-size: .78rem;
    font-weight: 800;
    gap: .4rem;
    padding: .55rem .8rem;
    white-space: nowrap;
}

.header-purse-chip:hover {
    background: rgba(23, 35, 31, .72);
}

.header-purse-chip:focus-visible {
    outline: 2px solid var(--signal);
    outline-offset: 2px;
}

.shop-grid {
    display: grid;
    gap: .75rem;
    grid-template-columns: repeat(auto-fit, minmax(10.75rem, 1fr));
}

.shop-grid-boxes {
    grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
}

.shop-section-title {
    font-size: clamp(1.35rem, 3vw, 2rem);
    margin: 1.7rem 0 .85rem;
}

.shop-card, .compact-card {
    min-height: 0;
}

.shop-card {
    background:
        radial-gradient(circle at top right, rgba(232, 109, 69, .11), transparent 7rem),
        var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .9rem;
    display: flex;
    flex-direction: column;
    gap: .55rem;
    padding: .85rem;
}

.shop-card h2 {
    font-size: 1rem;
    letter-spacing: -.02em;
    line-height: 1.1;
    margin: 0;
}

.shop-card .muted {
    display: -webkit-box;
    font-size: .8rem;
    line-height: 1.35;
    margin: 0;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.shop-card .primary-button {
    margin-top: auto;
    padding: .62rem .75rem;
    width: 100%;
}

.shop-card-footer {
    align-items: center;
    display: flex;
    font-size: .8rem;
    justify-content: space-between;
    gap: .5rem;
}

/* Tabbed shop layout */
.shop-tabs {
    border-bottom: 1px solid var(--line);
    display: flex;
    margin-bottom: 1rem;
    overflow-x: auto;
    scrollbar-width: none;
}

.shop-tabs::-webkit-scrollbar {
    display: none;
}

.shop-tab {
    align-items: center;
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    color: var(--muted);
    cursor: pointer;
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    font-size: .65rem;
    gap: .1rem;
    min-width: 0;
    padding: .4rem .5rem;
    text-align: center;
    white-space: nowrap;
}

.shop-tab + .shop-tab {
    border-left: 1px solid var(--line);
}

.shop-tab.active {
    border-bottom-color: var(--signal);
    color: var(--ink);
}

.shop-tab .game-icon {
    border: none;
    box-shadow: none;
    height: 1.3rem;
    width: 1.3rem;
}

.shop-tab .game-icon svg {
    height: .75rem;
    width: .75rem;
}

/* Today page tabs */
.today-tabs {
    border-bottom: 1px solid var(--line);
    display: flex;
    margin-bottom: 1rem;
    overflow-x: auto;
    scrollbar-width: none;
}

.today-tabs::-webkit-scrollbar {
    display: none;
}

.today-tab {
    align-items: center;
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    color: var(--muted);
    cursor: pointer;
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    font-size: .65rem;
    gap: .1rem;
    min-width: 0;
    padding: .4rem .5rem;
    position: relative;
    text-align: center;
    white-space: nowrap;
}

.today-tab + .today-tab {
    border-left: 1px solid var(--line);
}

.today-tab.active {
    border-bottom-color: var(--signal);
    color: var(--ink);
}

.today-tab .game-icon {
    border: none;
    box-shadow: none;
    height: 1.3rem;
    width: 1.3rem;
}

.today-tab .game-icon svg {
    height: .75rem;
    width: .75rem;
}

.today-tab-badge {
    border-radius: 999px;
    font-size: .55rem;
    font-weight: 700;
    line-height: 1;
    min-width: 1rem;
    padding: .15rem .3rem;
    position: absolute;
    right: .2rem;
    top: .2rem;
}

.today-tab-badge.badge-new {
    background: var(--signal);
    color: var(--paper);
}

.today-tab-badge.badge-viewed {
    background: rgba(23, 35, 31, .15);
    color: var(--muted);
}

/* Daily routine tasks */
.daily-grid {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.daily-row {
    align-items: center;
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .75rem;
    display: flex;
    gap: .65rem;
    padding: .55rem .75rem;
}

.daily-row .game-icon {
    flex-shrink: 0;
}

.daily-row-text {
    flex: 1 1 0;
    min-width: 0;
}

.daily-row h3 {
    font-size: .85rem;
    line-height: 1.15;
    margin: 0;
}

.daily-row .muted {
    font-size: .7rem;
    margin: 0;
}

/* Buttons inside a daily row sit in a centered flex row - the buttons' own margin-top (meant
   for stacked forms) would push them off the row's vertical center. */
.daily-row .primary-button,
.daily-row .secondary-button {
    flex-shrink: 0;
    margin-top: 0;
    padding: .45rem .8rem;
    width: auto;
}

.daily-check {
    flex-shrink: 0;
    height: 1.1rem;
    width: 1.1rem;
}

.shop-list {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.shop-row {
    align-items: start;
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .75rem;
    display: flex;
    gap: .5rem;
    min-width: 0;
    padding: .65rem .85rem;
}

.shop-row-text {
    flex: 1 1 0;
    min-width: 0;
}

.shop-row h3 {
    font-size: .9rem;
    line-height: 1.15;
    margin: 0;
}

.shop-row .muted {
    font-size: .75rem;
    margin: 0;
}

.shop-buy-btn {
    align-items: center;
    display: inline-flex;
    flex-shrink: 0;
    gap: .35rem;
    padding: .45rem .7rem;
    white-space: nowrap;
}

.shop-buy-btn .game-icon {
    border: none;
    box-shadow: none;
    height: 1.2rem;
    width: 1.2rem;
}

.shop-buy-btn .game-icon svg {
    height: .7rem;
    width: .7rem;
}

.card-topline {
    align-items: center;
    display: flex;
    gap: .45rem;
}

.card-topline .quest-skill,
.card-topline .eyebrow {
    margin: 0;
}

.game-icon {
    align-items: center;
    background: var(--ink);
    border: 2px solid rgba(247, 244, 238, .9);
    border-radius: 999px;
    box-shadow: 0 .25rem .7rem rgba(23, 35, 31, .15);
    color: var(--paper);
    display: inline-flex;
    flex: 0 0 auto;
    font-size: .62rem;
    font-weight: 900;
    height: 2rem;
    justify-content: center;
    letter-spacing: .04em;
    text-transform: uppercase;
    width: 2rem;
}

.game-icon svg {
    fill: none;
    height: 1.1rem;
    stroke: currentColor;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.8;
    width: 1.1rem;
}

.resource-chip {
    align-items: center;
    display: inline-flex;
    gap: .45rem;
}

.resource-chip .game-icon,
.primary-button .game-icon,
.secondary-button .game-icon {
    height: 1.45rem;
    width: 1.45rem;
}

.primary-button:has(.game-icon),
.secondary-button:has(.game-icon) {
    align-items: center;
    display: inline-flex;
    gap: .45rem;
    justify-content: center;
}

.skill-card {
    display: grid;
    gap: .55rem;
}

.skill-card .card-topline span:not(.game-icon) {
    color: var(--muted);
    font-size: .78rem;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
}

.research-grid {
    display: grid;
    gap: .75rem;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    margin-top: 1.25rem;
}

.research-card {
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .9rem;
    display: grid;
    gap: .55rem;
    padding: .9rem;
}

.research-card strong {
    font-size: 1.05rem;
}

.research-step-list {
    display: grid;
    gap: .45rem;
    margin-top: .25rem;
}

.research-step-list .secondary-button {
    margin-top: 0;
    overflow: hidden;
    padding: .5rem .7rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.step-completed {
    opacity: .55;
    text-decoration: line-through;
}

.project-step-list {
    display: grid;
    gap: .2rem;
}

.project-step-row {
    align-items: center;
    display: flex;
    gap: .4rem;
    font-size: .82rem;
    line-height: 1.25;
    padding: .3rem .45rem;
    border-radius: .4rem;
    background: var(--paper);
}

.project-step-row .step-check {
    flex-shrink: 0;
    width: 1rem;
    text-align: center;
    font-size: .8rem;
}

.project-step-row .step-name {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

.project-step-row .secondary-button {
    flex-shrink: 0;
    margin-top: 0;
    padding: .25rem .5rem;
    font-size: .72rem;
    width: auto;
}

.project-step-row.step-done {
    opacity: .5;
}

.project-step-row.step-done .step-name {
    text-decoration: line-through;
}

.project-collapsed {
    padding: .5rem .7rem;
}

.project-collapsed .quest-card-head {
    gap: .5rem;
}

.project-collapsed h2 {
    font-size: .95rem;
    margin: 0;
}

.project-collapsed .step-check {
    flex-shrink: 0;
    color: var(--signal);
    font-weight: 800;
}

.project-collapsed .muted {
    margin-left: auto;
}

.quest-card {
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .9rem;
    display: flex;
    flex-direction: column;
    gap: .65rem;
    min-height: 0;
    padding: .9rem;
}

.quest-card h2 {
    font-size: 1.05rem;
    letter-spacing: -.03em;
    line-height: 1.08;
    margin: 0;
}

.quest-card-head {
    align-items: center;
    display: flex;
    gap: .65rem;
}

.quest-card-body {
    display: grid;
    gap: .35rem;
}

.quest-card-body .muted {
    display: -webkit-box;
    font-size: .8rem;
    line-height: 1.35;
    margin: 0;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.quest-number {
    color: var(--signal);
    font-variant-numeric: tabular-nums;
    font-weight: 800;
}

.quest-skill {
    color: var(--muted);
    font-size: .72rem;
    letter-spacing: .08em;
    margin-top: 2.2rem;
    text-transform: uppercase;
}

.quest-card-head .quest-skill {
    margin: .12rem 0 0;
}

.quest-card .primary-button {
    margin-top: auto;
    width: 100%;
}

.adventure-board {
    align-items: center;
    background:
        radial-gradient(circle at top right, rgba(232, 109, 69, .14), transparent 8rem),
        var(--paper-deep);
    border: 1px dashed var(--line);
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    justify-content: space-between;
    margin-top: 1rem;
    max-width: 100%;
    min-width: 0;
    padding: 1rem;
}

.adventure-board > div {
    min-width: 0;
}

.adventure-board h2,
.adventure-board p {
    margin: 0;
}

.adventure-board .muted {
    max-width: 100%;
}

.adventure-board .secondary-button {
    flex: 0 0 auto;
    margin-top: 0;
    width: auto;
}

.field-report {
    background: var(--paper-deep);
    border: 1px solid var(--line);
    display: flex;
    flex-wrap: wrap;
    gap: .75rem;
    justify-content: space-between;
    margin-top: .75rem;
    max-width: 100%;
    min-width: 0;
    padding: 1rem;
}

.field-report > div {
    display: grid;
    gap: .18rem;
    min-width: 0;
}

.field-report h2,
.field-report p {
    margin: 0;
}

.field-report .primary-button {
    align-self: center;
    flex: 0 0 auto;
    margin-top: 0;
    width: auto;
}

.field-input {
    background: rgba(247, 244, 238, .72);
    border: 1px solid var(--line);
    border-radius: .7rem;
    color: var(--ink);
    font: inherit;
    min-width: 0;
    padding: .65rem .75rem;
    width: 100%;
}

.field-report-modal {
    align-items: center;
    background: rgba(42, 33, 24, .48);
    display: flex;
    height: min(var(--visible-height, 100vh), calc(100vh - var(--bottom-nav-space)));
    justify-content: center;
    left: 0;
    padding: 1rem;
    position: fixed;
    right: 0;
    top: var(--visible-top, 0);
    z-index: 1200;
}

.field-report-panel {
    background: var(--paper);
    border: 1px solid var(--line-strong);
    box-shadow: 0 1.25rem 3rem rgba(35, 27, 20, .28);
    display: grid;
    gap: .85rem;
    max-height: min(36rem, calc(min(var(--visible-height, 100vh), calc(100vh - var(--bottom-nav-space))) - 2rem));
    max-width: 34rem;
    overflow-y: auto;
    padding: clamp(1rem, 3vw, 1.35rem);
    width: min(100%, 34rem);
}

.field-report-panel-head {
    align-items: flex-start;
    display: flex;
    gap: .75rem;
    justify-content: space-between;
}

.field-report-panel-head > div {
    min-width: 0;
}

.field-report-panel-head .secondary-button {
    flex: 0 0 auto;
    margin-top: 0;
    width: auto;
}

.field-report-panel h2,
.field-report-panel p {
    margin: 0;
}

.field-report-matches {
    display: grid;
    gap: .55rem;
}

.field-report-actions {
    display: flex;
    justify-content: flex-end;
}

.field-report-actions .secondary-button {
    margin-top: 0;
    width: auto;
}

.field-report-actions .primary-button {
    margin-top: 0;
    width: auto;
}

.field-report-tell {
    display: grid;
    gap: .7rem;
}

.field-report-tell-label {
    color: var(--muted);
    display: grid;
    font-size: .9rem;
    gap: .35rem;
}

.field-report-match {
    background: rgba(247, 244, 238, .8);
    border: 1px solid var(--line);
    border-radius: .85rem;
    color: var(--ink);
    cursor: pointer;
    display: grid;
    font: inherit;
    gap: .15rem;
    padding: .7rem .8rem;
    text-align: left;
}

.field-report-match:hover,
.field-report-match:focus-visible {
    border-color: var(--accent);
}

.field-report-match span,
.field-report-match small {
    color: var(--muted);
}

.field-report-none {
    background: none;
    border: none;
    color: var(--muted);
    cursor: pointer;
    font: inherit;
    font-size: .9rem;
    margin-top: .25rem;
    padding: .25rem 0;
    text-align: left;
}

.field-report-none:hover,
.field-report-none:focus-visible {
    color: var(--accent);
    text-decoration: underline;
}

.quest-actions {
    display: grid;
    gap: .45rem;
    grid-template-columns: minmax(0, 1fr) auto;
    margin-top: auto;
}

.quest-actions .primary-button,
.quest-actions .secondary-button {
    margin-top: 0;
    padding: .58rem .72rem;
}

.quest-actions .secondary-button {
    min-width: 4.7rem;
    width: auto;
}

/* Cap-reached copy on the Odd Jobs tab - a design affordance ("That's five odd jobs today..."),
   not an error state. Spans the action row so the note reads as a full-width message. */
.odd-jobs-cap {
    grid-column: 1 / -1;
    margin: 0;
}

.quest-status {
    color: var(--signal);
    font-size: .8rem;
    font-weight: 800;
    margin-top: auto;
    text-transform: uppercase;
}

.quest-complete {
    opacity: .65;
}

.stat-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    margin-top: 2rem;
}

/* Rebalances the vertical rhythm around a heading that sits directly against a stat-grid on
   either side (2026-08-19) - both directions of the SAME underlying problem: a heading's own
   small default top margin reads as cramped right under a stat-card, while .stat-grid's 2rem
   margin-top reads as an oversized gap directly under a heading with nothing else between them.
   General rules, not scoped to one page - Stats.razor's back-to-back "Pace"/"Score" sections is
   where this was first noticed, but PlayerResources.razor's admin dossier has the identical
   heading-then-grid shape throughout and gets the same, purely positive tightening/spacing for
   free. Neither rule fires when something else (a <p>, an <EditForm>) sits between the heading
   and the grid - only the true back-to-back case this was written for. */
h2 + .stat-grid {
    margin-top: 1rem;
}

.stat-grid + h2 {
    margin-top: 2.5rem;
}

.stat-card, .collection-card {
    background: var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .8rem;
    padding: 1.1rem;
}

.stat-card strong {
    display: block;
    font-size: 1.5rem;
}

.stat-card span {
    color: var(--muted);
    font-size: .75rem;
}

/* Tiny header/footer caption text (2026-08-18/19) - the Stats page's high-score date ("September
   16th 2025", a <span> inside a .stat-card) and the score chart's date-range line (a <p> that is
   NOT inside a .stat-card, in ScoreHistoryChart.razor). `display: block` is load-bearing:
   .stat-card span is inline by default, so a second <span> here would sit on the SAME line as the
   first instead of its own row without this. */
.stat-caption {
    color: var(--muted);
    display: block;
    font-size: .55rem;
    margin-top: .15rem;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Specificity override, not a duplicate-by-mistake (2026-08-19 correction #2, found by
   inspecting computed styles via javascript_tool after the screenshot tool became unavailable
   mid-verification - not something a visual pass alone catches, since the wrong size still LOOKS
   like a plausible small caption). The high-score caption above is a <span> inside a .stat-card,
   so it is ALSO matched by `.stat-card span` (specificity 0,1,1), which beats the bare
   `.stat-caption` rule (0,1,0) - every font-size that rule has ever declared (the original
   `clamp(...)`, then `.58rem`, then `.55rem`) was silently losing the cascade to `.stat-card
   span`'s `.75rem` the ENTIRE time this feature has existed. That - not the vw term the comment
   above `.stat-caption` used to blame - is the real reason "September 16th 2025" truncated: the
   rendered size was always 12px, never whatever `.stat-caption` said. This rule exists ONLY to
   win that specificity fight for the property that actually collides (font-size); every other
   property stays on the general rule above, since the chart's date-range <p> - never inside a
   .stat-card, so never touched by `.stat-card span` - needs no such override and would be
   pointlessly duplicated by scoping the whole rule to `.stat-card` instead. */
.stat-card .stat-caption {
    font-size: .55rem;
}

.collection-section {
    margin-top: 1.25rem;
}

.collection-summary {
    display: grid;
    gap: .75rem;
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
    margin-top: 1.25rem;
}

.collection-stat {
    align-items: center;
    background:
        radial-gradient(circle at top right, rgba(232, 109, 69, .14), transparent 7rem),
        var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .9rem;
    display: flex;
    gap: .75rem;
    padding: .8rem;
}

.collection-stat strong {
    display: block;
    font-size: 1.2rem;
    line-height: 1;
}

.collection-stat-link {
    color: inherit;
    text-decoration: none;
}

.collection-stat span,
.collection-section-head span {
    color: var(--muted);
    font-size: .78rem;
    font-weight: 800;
    text-transform: uppercase;
}

.collection-section-head {
    align-items: end;
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    margin-bottom: .75rem;
}

.collection-section h2,
.collection-section-head h2 {
    font-size: clamp(1.35rem, 3vw, 2rem);
    margin: 0;
}

.collection-grid {
    display: grid;
    gap: .75rem;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}

.collection-shelf {
    display: grid;
    gap: .5rem;
    grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
}

/* Consecutive shelves in the same section (Pets catalog renders one shelf per group inside a
   single section) had ZERO vertical spacing: the grid gap only spaces rows WITHIN a shelf, and
   nothing separated the shelf divs - a single-card group (rare groups are small, which is why
   the bug looked rarity-correlated) landed flush under the previous shelf's last row. Other
   pages (Collection, Progression/Reward sections) use one shelf per SECTION, whose margin-top
   already provides the gap, so this only affects stacked shelves. .5rem matches the shelf's
   own row gap. */
.collection-shelf + .collection-shelf {
    margin-top: .5rem;
}

.collection-shelf > * {
    max-width: 10rem;
}

.collection-card {
    box-shadow: inset 0 0 0 .2rem rgba(247, 244, 238, .38);
    display: flex;
    flex-direction: column;
    min-width: 0;
    padding: .7rem .8rem;
}

/* Used for the Pets catalog, which never carries a description or a "Found <date>" line - the
   base .collection-card padding still fits the Collection page's richer cards, which do. */
.collection-card-dense {
    padding: .55rem .65rem;
}

.collection-card h3 {
    margin: 0 0 .35rem;
    overflow-wrap: anywhere;
}

.collection-card span {
    color: var(--signal);
    display: inline-block;
    font-size: .78rem;
    font-weight: 800;
    margin-top: auto;
    padding-top: .5rem;
    text-transform: uppercase;
}

.collection-earned {
    background:
        radial-gradient(circle at top right, rgba(232, 109, 69, .16), transparent 9rem),
        var(--paper-deep);
}

.collection-locked {
    border-style: dashed;
    opacity: .72;
}

.collection-mystery {
    background:
        linear-gradient(135deg, rgba(46, 69, 62, .13), transparent 45%),
        repeating-linear-gradient(-12deg, rgba(31, 45, 39, .035), rgba(31, 45, 39, .035) .45rem, transparent .45rem, transparent .9rem),
        var(--paper-deep);
}

.incubator-card {
    margin-top: 1.25rem;
}

.egg-row {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem .9rem;
    margin: .75rem 0;
    position: relative;
    align-items: start;
}

.egg-info {
    flex: 1 1 0;
    min-width: 0;
}

.hatch-btn {
    flex-shrink: 0;
    padding: .4rem .7rem;
}

.egg-sprite {
    height: 3rem;
    image-rendering: auto;
    width: 3rem;
}

.egg-progress {
    background: rgba(31, 45, 39, .12);
    border-radius: 999px;
    height: .55rem;
    margin-top: .45rem;
    overflow: hidden;
    width: min(16rem, 70vw);
}

.egg-progress span {
    background: linear-gradient(90deg, var(--signal), var(--leaf));
    display: block;
    height: 100%;
}

.hatch-dust-pill {
    background: rgba(232, 109, 69, .12);
    border: 1px solid rgba(232, 109, 69, .24);
    border-radius: 999px;
    padding: .35rem .65rem;
    /* Each pill holds exactly one fact - never wrap mid-phrase; the row wraps as a whole. */
    white-space: nowrap;
}

/* The incubator card's fact row: Hatch Dust / Slots / Stored as separate pills that wrap as
   whole units instead of splitting inside a single pill. */
.incubator-pills {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
}

.egg-inventory {
    display: flex;
    flex-wrap: wrap;
    gap: .6rem;
}

/* Collapsible stored-egg group on the incubator card: a full-width header row with a caret,
   the egg rows below it. Without the group the stored eggs floated directly under the
   "Spend 10 Hatch Dust" button with no header or structure. */
.egg-storage {
    border-top: 1px solid var(--line);
    margin-top: .75rem;
    padding-top: .6rem;
}

.egg-storage-toggle {
    align-items: center;
    background: none;
    border: 0;
    color: var(--ink);
    cursor: pointer;
    display: flex;
    font: inherit;
    font-weight: 700;
    gap: .45rem;
    padding: .25rem 0;
    width: 100%;
}

.egg-storage-caret {
    color: var(--muted);
    font-size: .8rem;
    width: .8rem;
}

.egg-choice {
    align-items: center;
    display: inline-flex;
    gap: .5rem;
}

/* Read-only stored-egg row (incubator full): same shape as the Start button so the storage
   list reads consistently, but muted and non-interactive. */
.egg-stored {
    align-items: center;
    color: var(--muted);
    display: inline-flex;
    gap: .5rem;
    padding: .4rem .55rem;
}

.chest-grid {
    display: grid;
    gap: .6rem;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    margin-top: 1rem;
}

.chest-card {
    background:
        linear-gradient(145deg, rgba(23, 35, 31, .06), transparent 34%),
        var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .75rem;
    padding: .65rem;
}

.chest-card h2 {
    font-size: .95rem;
    margin: .3rem 0 .5rem;
}

.chest-card-footer {
    align-items: center;
    display: flex;
    justify-content: space-between;
    gap: .5rem;
    margin-top: .35rem;
}

.chest-open-btn {
    flex-shrink: 0;
    padding: .4rem .7rem;
    white-space: nowrap;
}

.chest-meter {
    background: rgba(23, 35, 31, .12);
    border-radius: 999px;
    height: .6rem;
    overflow: hidden;
}

.chest-fill {
    background: linear-gradient(90deg, #b78345, var(--signal));
    display: block;
    height: 100%;
}

.chest-unlocked {
    border-color: rgba(232, 109, 69, .55);
    box-shadow: 0 .5rem 1.5rem rgba(232, 109, 69, .12);
}

.garden-grid {
    display: grid;
    gap: .75rem;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    margin-top: 1.25rem;
}

.garden-plot {
    background:
        radial-gradient(circle at 85% 10%, rgba(78, 126, 83, .18), transparent 8rem),
        var(--paper-deep);
    border: 1px solid var(--line);
    border-radius: .9rem;
    overflow: hidden;
    padding: .9rem;
    position: relative;
}

.garden-plot h2 {
    font-size: 1.05rem;
    margin: .45rem 0 .45rem;
}

.garden-stage {
    color: var(--signal);
    display: inline-block;
    font-size: .78rem;
    font-weight: 800;
    margin-bottom: .85rem;
    text-transform: uppercase;
}

.garden-meter {
    background: rgba(23, 35, 31, .12);
    border-radius: 999px;
    height: .75rem;
    overflow: hidden;
}

.garden-meter span {
    background: linear-gradient(90deg, #78976c, var(--signal));
    display: block;
    height: 100%;
}

.garden-ready {
    border-color: rgba(78, 126, 83, .55);
    box-shadow: 0 .8rem 2rem rgba(78, 126, 83, .14);
}

.admin-table-wrap {
    border: 1px solid var(--line);
    border-radius: .8rem;
    margin-top: 2rem;
    max-width: 100%;
    overflow-x: auto;
    max-height: min(70vh, 44rem);
    overflow-y: auto;
}

.stats-page,
.history-page,
.research-page {
    max-width: 72rem;
    min-width: 0;
    width: 100%;
}

.history-table-wrap {
    width: 100%;
}

.admin-table {
    border-collapse: collapse;
    min-width: 38rem;
    width: 100%;
}

.admin-table th, .admin-table td {
    border-bottom: 1px solid var(--line);
    padding: .85rem 1rem;
    text-align: left;
}

.admin-table th {
    color: var(--muted);
    font-size: .72rem;
    letter-spacing: .08em;
    text-transform: uppercase;
}

/* Tags column on the task table: the tag is the machine-readable grouping (e.g.
   "Cleaning,Deep Clean") and is the only place a task's kind is spelled out - it was invisible
   before this column existed, which is how the deep-clean group lost its distinction. */
.task-tags-cell {
    color: var(--muted);
    font-size: .75rem;
    max-width: 14rem;
    white-space: normal;
}

/* Desktop density. The base padding stays thumb-friendly for the phone case; this tightens
   rows only where there's a real pointer, so a GM can scan 30 quests without scrolling. */
@media (min-width: 900px) {
    .admin-table th,
    .admin-table td {
        padding: .5rem .75rem;
    }
}

.admin-table tbody tr:nth-child(even) {
    background: color-mix(in srgb, var(--paper-deep) 45%, transparent);
}

.admin-table tbody tr:hover {
    background: color-mix(in srgb, var(--signal) 8%, transparent);
}

/* Sticky header inside the scrolling wrapper - long catalogs lose their column labels
   after ~20 rows otherwise. */
.admin-table thead th {
    background: var(--paper);
    position: sticky;
    top: 0;
    z-index: 1;
}

.admin-table .numeric {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* The Interventions page's three tables (active boosts, sent messages, resistance scores) use
   .boost-table; without a rule they rendered as raw default tables. Same visual language as
   .admin-table minus the sticky header - these are short, page-local tables, not scroll
   containers. */
.boost-table {
    border-collapse: collapse;
    margin-top: 1rem;
    width: 100%;
}

.boost-table th,
.boost-table td {
    border-bottom: 1px solid var(--line);
    padding: .7rem .85rem;
    text-align: left;
}

.boost-table th {
    color: var(--muted);
    font-size: .72rem;
    letter-spacing: .08em;
    text-transform: uppercase;
}

.boost-table tbody tr:nth-child(even) {
    background: color-mix(in srgb, var(--paper-deep) 45%, transparent);
}

.boost-table tbody tr:hover {
    background: color-mix(in srgb, var(--signal) 8%, transparent);
}

.boost-table .secondary-button {
    font-size: .72rem;
    margin-top: 0;
    padding: .3rem .6rem;
    white-space: nowrap;
    width: auto;
}

/* Row action clusters: keep buttons on one line and stop them stretching the row. */
.admin-row-actions {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
}

.admin-row-actions .secondary-button,
.admin-row-actions .danger-button {
    font-size: .72rem;
    padding: .3rem .6rem;
    white-space: nowrap;
    width: auto;
    margin-top: 0;
}

.completion-message {
    color: var(--signal);
    font-weight: 700;
    margin-bottom: 1rem;
}

.completion-message.error {
    color: #8f2f20;
}

/* Pending Insight prompt on the GM dashboard (§12.10) - the one sentence that makes the
   pending-release incentive legible: neglect yields no spendable resource, a meaningful Stock
   action releases it. */
.pending-prompt {
    background: color-mix(in srgb, var(--signal) 12%, transparent);
    border: 1px solid var(--line);
    border-radius: .6rem;
    font-weight: 700;
    margin: 1rem 0 0;
    padding: .75rem 1rem;
}

/* §12.4 rhythm heatmap: 7x24 completion-density grid, shared by Stats and the GM Dashboard. */
.rhythm-heatmap {
    display: grid;
    gap: .18rem;
    margin-top: 1.25rem;
    max-width: 100%;
    overflow-x: auto;
}

.heatmap-hours,
.heatmap-day {
    display: grid;
    grid-template-columns: 3rem repeat(24, minmax(.55rem, 1fr));
}

.heatmap-hours {
    font-size: .58rem;
    font-weight: 700;
    gap: 1px;
    color: var(--muted);
    text-align: center;
}

.heatmap-day {
    align-items: center;
    gap: 1px;
}

.heatmap-day-label {
    color: var(--muted);
    font-size: .62rem;
    padding-right: .25rem;
    text-align: right;
}

.heatmap-cells {
    display: contents;
}

.heatmap-cell {
    background: color-mix(in srgb, var(--signal) 8%, transparent);
    border-radius: 2px;
    height: .85rem;
}

.heatmap-cell-1 { background: color-mix(in srgb, var(--signal) 22%, transparent); }
.heatmap-cell-2 { background: color-mix(in srgb, var(--signal) 45%, transparent); }
.heatmap-cell-3 { background: color-mix(in srgb, var(--signal) 70%, transparent); }
.heatmap-cell-4 { background: var(--signal); }

.heatmap-legend {
    align-items: center;
    display: flex;
    gap: .3rem;
    justify-content: flex-end;
    color: var(--muted);
    font-size: .62rem;
}

.heatmap-legend .heatmap-cell {
    height: .7rem;
    width: .9rem;
}

/* Score-over-time bar chart (2026-08-18), ScoreHistoryChart.razor. Responsive SVG: viewBox +
   width:100% scales without JS, same overflow-x:auto safety net as .rhythm-heatmap above. */
.score-chart {
    margin-top: 1.25rem;
    max-width: 100%;
    overflow-x: auto;
}

.score-chart svg {
    display: block;
    height: auto;
    width: 100%;
}

/* vector-effect="non-scaling-stroke" on the element keeps this a true 1px hairline regardless of
   how far the viewBox is scaled up or down by its container width. */
.score-chart-baseline {
    stroke: var(--line);
    stroke-width: 1;
}

/* Past days sit at reduced intensity; today's bar is full-strength --signal. One hue throughout
   (magnitude is already carried by bar height) - "today" is emphasis via intensity, not a second
   color, matching the heatmap's own color-mix idiom above rather than introducing a new hue. */
.score-bar {
    fill: color-mix(in srgb, var(--signal) 55%, transparent);
}

.score-bar-today {
    fill: var(--signal);
}

.chart-caption {
    margin-top: .4rem;
    text-align: right;
}

/* The Task Library schedule dialog uses Bootstrap's bare .modal/.modal-backdrop class names.
   Bootstrap's own .modal is display:none until its JS adds .show (or inline display), and this
   app loads no Bootstrap JS for it - so the dialog itself never rendered; only the black
   backdrop showed. app.css owns both rules here (nothing else in the app uses either class
   name) so the dialog is visible, layered above the app's own fixed chrome, and matches the
   other dialogs (see .field-report-modal). */
.modal-backdrop {
    background: rgba(23, 35, 31, .5);
    z-index: 1190;
}

.modal {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: .8rem;
    box-shadow: 0 1.25rem 3rem rgba(35, 27, 20, .28);
    display: block;
    height: fit-content;
    inset: 0;
    margin: auto;
    max-height: min(36rem, calc(100vh - 2rem));
    max-width: 30rem;
    overflow-y: auto;
    padding: clamp(1rem, 3vw, 1.35rem);
    position: fixed;
    width: min(100%, 30rem);
    z-index: 1200;
}

.modal h2 {
    margin: 0;
}

.reward-overlay {
    align-items: center;
    animation: reward-overlay-in .18s ease-out;
    background:
        radial-gradient(circle at 20% 18%, rgba(247, 244, 238, .16), transparent 18rem),
        radial-gradient(circle at 80% 22%, rgba(232, 109, 69, .22), transparent 16rem),
        radial-gradient(circle at 50% 35%, rgba(232, 109, 69, .24), rgba(23, 35, 31, .72) 42%, rgba(23, 35, 31, .9));
    display: flex;
    inset: 0;
    justify-content: center;
    padding: 1.25rem;
    position: fixed;
    z-index: 2000;
}

.reward-event-chestOpened,
.reward-event-chestUnlocked {
    background:
        radial-gradient(circle at 50% 18%, rgba(183, 131, 69, .34), transparent 18rem),
        radial-gradient(circle at 80% 22%, rgba(232, 109, 69, .22), transparent 16rem),
        rgba(23, 35, 31, .88);
}

.reward-event-gardenHarvested {
    background:
        radial-gradient(circle at 50% 18%, rgba(78, 126, 83, .34), transparent 18rem),
        radial-gradient(circle at 25% 24%, rgba(247, 244, 238, .16), transparent 14rem),
        rgba(23, 35, 31, .88);
}

.reward-event-achievementUnlocked,
.reward-event-companionUnlocked {
    background:
        radial-gradient(circle at 50% 14%, rgba(232, 109, 69, .34), transparent 19rem),
        radial-gradient(circle at 20% 70%, rgba(247, 244, 238, .12), transparent 13rem),
        rgba(23, 35, 31, .9);
}

.reward-ceremony {
    animation: reward-card-in .32s cubic-bezier(.18, .95, .25, 1.18);
    background:
        repeating-linear-gradient(90deg, rgba(23, 35, 31, .025) 0 1px, transparent 1px 12px),
        linear-gradient(135deg, rgba(255, 255, 255, .42), rgba(255, 255, 255, 0) 28%),
        var(--paper);
    border: 1px solid rgba(23, 35, 31, .2);
    border-radius: 1.4rem;
    box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, .32), inset 0 0 0 .45rem rgba(235, 229, 217, .7);
    color: var(--ink);
    max-width: min(34rem, 100%);
    min-height: 18rem;
    outline: 0;
    overflow: hidden;
    padding: clamp(1.75rem, 6vw, 3.25rem);
    position: relative;
    text-align: center;
    width: 100%;
}

.reward-ceremony::marker { content: ""; }

.reward-ceremony::before,
.reward-ceremony::after {
    border: 1px solid rgba(232, 109, 69, .34);
    border-radius: 999px;
    content: "";
    height: 13rem;
    position: absolute;
    width: 13rem;
}

.reward-ceremony::before {
    left: -6.5rem;
    top: -6.5rem;
}

.reward-ceremony::after {
    bottom: -7rem;
    right: -6rem;
}

.reward-ceremony-large,
.reward-ceremony-epic {
    background:
        radial-gradient(circle at 50% 15%, rgba(232, 109, 69, .26), transparent 34%),
        linear-gradient(135deg, rgba(255, 255, 255, .46), rgba(255, 255, 255, 0) 30%),
        var(--paper);
}

.reward-ceremony-epic {
    border-color: rgba(232, 109, 69, .72);
    max-width: min(40rem, 100%);
    min-height: 22rem;
}

/* Critical reward ceremony (Rare/Jackpot deck tiers) - the design's "strong visual
   presentation" for critical rewards (§15.3): a warm gold glow that reads as special without
   the epic tier's full Milestone treatment. Driven by RewardEventFactory.QuestComplete, which
   maps the Rare/Jackpot tiers onto the "critical" ceremony tier. */
.reward-ceremony-critical {
    background:
        radial-gradient(circle at 50% 15%, rgba(218, 165, 32, .3), transparent 34%),
        linear-gradient(135deg, rgba(255, 255, 255, .46), rgba(255, 255, 255, 0) 30%),
        var(--paper);
    border-color: rgba(218, 165, 32, .72);
    max-width: min(40rem, 100%);
}

.reward-ceremony-critical .reward-seal {
    color: rgba(218, 165, 32, .95);
}

/* ── Rarity effects: rotating light rays behind the card ── */

.reward-rarity-rare .reward-ceremony,
.reward-rarity-legendary .reward-ceremony,
.reward-rarity-mystic .reward-ceremony {
    overflow: hidden;
}

/* Shared ray base — primary layer */
.reward-rarity-rare .reward-ceremony::before,
.reward-rarity-legendary .reward-ceremony::before,
.reward-rarity-mystic .reward-ceremony::before {
    border-radius: 50%;
    content: "";
    height: 32rem;
    left: 50%;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 32rem;
    z-index: 0;
    mask-image: radial-gradient(circle, rgba(0,0,0,1) 15%, rgba(0,0,0,.5) 45%, transparent 70%);
    -webkit-mask-image: radial-gradient(circle, rgba(0,0,0,1) 15%, rgba(0,0,0,.5) 45%, transparent 70%);
}

/* Secondary counter-rotating layer via ::after on the ceremony itself */
.reward-rarity-rare .reward-ceremony::after,
.reward-rarity-legendary .reward-ceremony::after,
.reward-rarity-mystic .reward-ceremony::after {
    border-radius: 50%;
    content: "";
    height: 26rem;
    left: 50%;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 26rem;
    z-index: 0;
    pointer-events: none;
    mask-image: radial-gradient(circle, rgba(0,0,0,.7) 10%, rgba(0,0,0,.3) 40%, transparent 65%);
    -webkit-mask-image: radial-gradient(circle, rgba(0,0,0,.7) 10%, rgba(0,0,0,.3) 40%, transparent 65%);
}

/* ── Rare — teal ── */
.reward-rarity-rare .reward-ceremony::before {
    background: repeating-conic-gradient(
        from 0deg,
        rgba(56, 189, 189, .22) 0deg 5deg,
        transparent 5deg 15deg,
        rgba(40, 170, 170, .14) 15deg 19deg,
        transparent 19deg 32deg,
        rgba(56, 189, 189, .10) 32deg 35deg,
        transparent 35deg 50deg
    );
    animation: reward-rays-spin 11s linear infinite;
}
.reward-rarity-rare .reward-ceremony::after {
    background: repeating-conic-gradient(
        from 22deg,
        rgba(56, 189, 189, .12) 0deg 8deg,
        transparent 8deg 22deg,
        rgba(80, 200, 200, .08) 22deg 27deg,
        transparent 27deg 45deg
    );
    animation: reward-rays-spin-reverse 16s linear infinite;
}
.reward-rarity-rare .reward-ceremony {
    border-color: rgba(56, 189, 189, .45);
    box-shadow:
        0 0 50px rgba(56, 189, 189, .15),
        0 0 100px rgba(56, 189, 189, .06),
        0 1.5rem 4rem rgba(0, 0, 0, .32),
        inset 0 0 0 .45rem rgba(56, 189, 189, .15);
}

/* ── Legendary — gold ── */
.reward-rarity-legendary .reward-ceremony::before {
    background: repeating-conic-gradient(
        from 5deg,
        rgba(218, 165, 32, .28) 0deg 4deg,
        rgba(255, 220, 50, .10) 4deg 8deg,
        transparent 8deg 16deg,
        rgba(218, 165, 32, .18) 16deg 20deg,
        transparent 20deg 30deg,
        rgba(255, 200, 0, .14) 30deg 33deg,
        transparent 33deg 48deg
    );
    animation: reward-rays-spin 5s linear infinite;
}
.reward-rarity-legendary .reward-ceremony::after {
    background: repeating-conic-gradient(
        from -10deg,
        rgba(255, 215, 0, .16) 0deg 6deg,
        transparent 6deg 18deg,
        rgba(218, 165, 32, .10) 18deg 22deg,
        transparent 22deg 40deg
    );
    animation: reward-rays-spin-reverse 8s linear infinite;
}
.reward-rarity-legendary .reward-ceremony {
    border-color: rgba(218, 165, 32, .65);
    box-shadow:
        0 0 60px rgba(218, 165, 32, .22),
        0 0 120px rgba(218, 165, 32, .10),
        0 1.5rem 4rem rgba(0, 0, 0, .32),
        inset 0 0 0 .45rem rgba(218, 165, 32, .2);
}

/* ── Mystic — violet ── */
.reward-rarity-mystic .reward-ceremony::before {
    background: repeating-conic-gradient(
        from -3deg,
        rgba(147, 112, 219, .24) 0deg 6deg,
        transparent 6deg 17deg,
        rgba(160, 120, 230, .12) 17deg 21deg,
        transparent 21deg 33deg,
        rgba(147, 112, 219, .16) 33deg 37deg,
        transparent 37deg 50deg
    );
    animation: reward-rays-spin 8s linear infinite;
}
.reward-rarity-mystic .reward-ceremony::after {
    background: repeating-conic-gradient(
        from 15deg,
        rgba(160, 120, 230, .14) 0deg 7deg,
        transparent 7deg 20deg,
        rgba(147, 112, 219, .08) 20deg 25deg,
        transparent 25deg 42deg
    );
    animation: reward-rays-spin-reverse 13s linear infinite;
}
.reward-rarity-mystic .reward-ceremony {
    border-color: rgba(147, 112, 219, .5);
    box-shadow:
        0 0 55px rgba(147, 112, 219, .18),
        0 0 100px rgba(147, 112, 219, .08),
        0 1.5rem 4rem rgba(0, 0, 0, .32),
        inset 0 0 0 .45rem rgba(147, 112, 219, .15);
}

/* Glow pulse on the overlay backdrop for rare+ */
.reward-rarity-rare .reward-overlay,
.reward-rarity-legendary .reward-overlay,
.reward-rarity-mystic .reward-overlay {
    animation: reward-overlay-in .18s ease-out, reward-glow-pulse 3s ease-in-out infinite alternate;
}
.reward-rarity-legendary .reward-overlay {
    animation-duration: .18s, 2s;
}

/* Rarity-tinted sparkle */
.reward-rarity-rare .reward-sparkle { color: rgba(56, 189, 189, .9); }
.reward-rarity-legendary .reward-sparkle { color: rgba(218, 165, 32, .9); }
.reward-rarity-mystic .reward-sparkle { color: rgba(147, 112, 219, .9); }
.reward-rarity-rare .reward-sparkle::before,
.reward-rarity-rare .reward-sparkle::after { color: rgba(56, 189, 189, .6); }
.reward-rarity-legendary .reward-sparkle::before,
.reward-rarity-legendary .reward-sparkle::after { color: rgba(218, 165, 32, .6); }
.reward-rarity-mystic .reward-sparkle::before,
.reward-rarity-mystic .reward-sparkle::after { color: rgba(147, 112, 219, .6); }

/* Floating particles via box-shadow on sparkle pseudo-elements */
.reward-rarity-rare .reward-sparkle::before { box-shadow: 12vw -8vh 0 1px rgba(56, 189, 189, .5), -8vw -12vh 0 0 rgba(56, 189, 189, .35), 6vw 4vh 0 1px rgba(80, 200, 200, .4); }
.reward-rarity-legendary .reward-sparkle::before { box-shadow: 10vw -10vh 0 1px rgba(218, 165, 32, .6), -12vw -6vh 0 0 rgba(255, 215, 0, .45), 8vw 6vh 0 1px rgba(218, 165, 32, .35), -4vw -14vh 0 0 rgba(255, 220, 50, .4); }
.reward-rarity-mystic .reward-sparkle::before { box-shadow: 11vw -9vh 0 1px rgba(147, 112, 219, .55), -9vw -11vh 0 0 rgba(160, 120, 230, .4), 5vw 5vh 0 1px rgba(147, 112, 219, .3); }

@keyframes reward-rays-spin {
    to { transform: translate(-50%, -50%) rotate(360deg); }
}

@keyframes reward-rays-spin-reverse {
    to { transform: translate(-50%, -50%) rotate(-360deg); }
}

@keyframes reward-glow-pulse {
    from { opacity: .92; }
    to { opacity: 1; }
}

.reward-sparkle {
    animation: reward-sparkle-drift 1.6s ease-in-out infinite alternate;
    color: var(--signal);
    font-size: 2.1rem;
    font-weight: 900;
    left: 18%;
    position: absolute;
    top: 16%;
}

.reward-sparkle::before,
.reward-sparkle::after {
    color: rgba(232, 109, 69, .72);
    content: "*";
    font-size: 1.2rem;
    position: absolute;
}

.reward-sparkle::before {
    left: 16rem;
    top: 2rem;
}

.reward-sparkle::after {
    left: 9rem;
    top: 11rem;
}

.reward-seal {
    align-items: center;
    background: var(--ink);
    border: .25rem solid var(--paper-deep);
    border-radius: 999px;
    box-shadow: 0 .4rem 1rem rgba(0, 0, 0, .2);
    color: var(--paper);
    display: inline-flex;
    font-size: .72rem;
    font-weight: 900;
    height: 4.5rem;
    justify-content: center;
    letter-spacing: .08em;
    margin-bottom: 1rem;
    position: relative;
    text-transform: uppercase;
    width: 4.5rem;
    z-index: 1;
}

.reward-seal::after {
    border: 1px dashed rgba(247, 244, 238, .46);
    border-radius: 999px;
    content: "";
    inset: .35rem;
    position: absolute;
}

.reward-ceremony .eyebrow {
    margin-bottom: .6rem;
    position: relative;
    z-index: 1;
}

.reward-ceremony h2 {
    font-size: clamp(2.2rem, 8vw, 5rem);
    letter-spacing: -.06em;
    line-height: .95;
    margin: 0;
    position: relative;
    z-index: 1;
}

.reward-pet-image {
    display: block;
    filter: drop-shadow(0 1rem 1.4rem rgba(23, 35, 31, .24));
    height: clamp(7rem, 28vw, 12rem);
    margin: 1rem auto 0;
    max-width: min(15rem, 70vw);
    object-fit: contain;
    position: relative;
    width: auto;
    z-index: 1;
}

.reward-body {
    color: var(--muted);
    font-size: 1.05rem;
    line-height: 1.5;
    margin: 1rem auto 0;
    max-width: 26rem;
    position: relative;
    z-index: 1;
}

.reward-loot {
    background: rgba(235, 229, 217, .64);
    border: 1px solid rgba(23, 35, 31, .12);
    border-radius: 1rem;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .48);
    display: grid;
    gap: .55rem;
    margin: 1.35rem auto 0;
    max-width: 24rem;
    padding: .9rem;
    position: relative;
    z-index: 1;
}

.reward-loot-row {
    align-items: center;
    background: rgba(247, 244, 238, .72);
    border: 1px solid rgba(23, 35, 31, .08);
    border-radius: .8rem;
    display: flex;
    justify-content: space-between;
    padding: .7rem .85rem;
}

.reward-loot dt {
    color: var(--muted);
    font-size: .72rem;
    font-weight: 900;
    letter-spacing: .1em;
    margin: 0;
    text-transform: uppercase;
}

.reward-loot dd {
    color: var(--signal);
    font-size: 1.15rem;
    font-weight: 900;
    margin: 0;
}

.reward-amount {
    color: var(--signal);
    display: block;
    font-size: clamp(2rem, 9vw, 4.5rem);
    letter-spacing: -.06em;
    margin-top: 1rem;
    position: relative;
    z-index: 1;
}

.reward-continue {
    box-shadow: 0 .6rem 1.4rem rgba(23, 35, 31, .18);
    margin-top: 1.5rem;
    min-width: 9rem;
    position: relative;
    z-index: 1;
}

@keyframes reward-overlay-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes reward-card-in {
    from { opacity: 0; transform: translateY(1rem) scale(.96) rotate(-.4deg); }
    to { opacity: 1; transform: translateY(0) scale(1) rotate(0); }
}

@keyframes reward-sparkle-drift {
    from { opacity: .55; transform: translateY(0) rotate(0); }
    to { opacity: 1; transform: translateY(-.65rem) rotate(12deg); }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        scroll-behavior: auto !important;
        transition-duration: .01ms !important;
    }

    .reward-overlay, .reward-ceremony, .reward-sparkle {
        animation: none !important;
        transition: none !important;
    }
    .reward-overlay::before, .reward-overlay::after,
    .reward-ceremony::before, .reward-ceremony::after {
        animation: none !important;
    }
}

@media (max-width: 600px) {
    .app-header {
        padding: 1rem 1.25rem;
        align-items: flex-start;
        flex-wrap: wrap;
        gap: .75rem;
    }

    .today-heading {
        align-items: start;
        flex-direction: column;
        gap: 1rem;
    }

    .quest-grid {
        grid-template-columns: 1fr;
    }

    .field-report-modal {
        align-items: end;
        padding: .75rem;
    }

    .field-report-panel-head {
        flex-direction: column;
    }

    .field-report-panel-head .secondary-button {
        width: 100%;
    }

    .field-report,
    .adventure-board {
        align-items: stretch;
        flex-direction: column;
    }

    .field-report .primary-button,
    .adventure-board .secondary-button,
    .field-report-actions .secondary-button {
        width: 100%;
    }

    .collection-grid,
    .garden-grid,
    .chest-grid {
        grid-template-columns: 1fr;
    }

    .nav-links {
        flex-wrap: wrap;
        gap: .6rem .9rem;
        max-width: 100%;
    }
}

/* Garden plot pets - inside plot cards */
.garden-plot {
    position: relative;
    overflow: hidden;
}

.garden-plot-pets {
    position: absolute;
    top: 0;
    right: 0;
    width: 50%;
    height: 50%;
    pointer-events: none;
    z-index: 2;
}

.garden-plot-pet {
    position: absolute;
    pointer-events: auto;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    transition: filter 0.2s;
    z-index: 2;
}

.garden-plot-pet:hover:not(:disabled) {
    filter: brightness(1.15);
}

.garden-plot-pet:disabled {
    cursor: default;
}

.garden-plot-pet-sprite {
    width: 2rem;
    height: 2rem;
    object-fit: contain;
}

.garden-plot-pet:not(:disabled) {
    animation: garden-pet-roam var(--pet-roam-duration, 20s) ease-in-out infinite var(--pet-roam-delay, 0s);
}

@keyframes garden-pet-roam {
    0% {
        transform: translate(0, 0);
    }
    20% {
        transform: translate(20px, -10px);
    }
    40% {
        transform: translate(-15px, 15px);
    }
    60% {
        transform: translate(10px, 5px);
    }
    80% {
        transform: translate(-8px, -8px);
    }
    100% {
        transform: translate(0, 0);
    }
}

.garden-plot-pet:not(:disabled) img {
    animation: garden-pet-gait var(--gait-duration, 0.9s) ease-in-out infinite var(--gait-delay, 0s),
               garden-pet-flip var(--pet-roam-duration, 20s) steps(1) infinite var(--pet-roam-delay, 0s);
}

@keyframes garden-pet-gait {
    0%, 100% {
        transform: translateY(0) rotate(0deg);
    }
    50% {
        transform: translateY(-2px) rotate(2deg);
    }
}

@keyframes garden-pet-flip {
    0%, 49.9% {
        transform: scaleX(1);
    }
    50%, 100% {
        transform: scaleX(-1);
    }
}

.garden-plot-pet.garden-plot-pet-glow {
    animation: garden-pet-roam var(--pet-roam-duration, 20s) ease-in-out infinite var(--pet-roam-delay, 0s),
               garden-pet-glow-pulse 2.5s ease-in-out infinite;
}

.garden-plot-pet.garden-plot-pet-glow img {
    animation: garden-pet-gait var(--gait-duration, 0.9s) ease-in-out infinite var(--gait-delay, 0s),
               garden-pet-flip var(--pet-roam-duration, 20s) steps(1) infinite var(--pet-roam-delay, 0s),
               garden-pet-glow-glow-pulse 2.5s ease-in-out infinite;
}

@keyframes garden-pet-glow-pulse {
    0%, 100% {
        filter: drop-shadow(0 0 4px rgba(255, 210, 60, 0.4));
    }
    50% {
        filter: drop-shadow(0 0 14px rgba(255, 210, 60, 0.9));
    }
}

@keyframes garden-pet-glow-glow-pulse {
    0%, 100% {
        filter: drop-shadow(0 0 3px rgba(255, 210, 60, 0.3));
    }
    50% {
        filter: drop-shadow(0 0 10px rgba(255, 210, 60, 0.8));
    }
}

.garden-plot-pet-claimed {
    opacity: 1;
}

.garden-plot-pet-claimed {
    animation: garden-pet-roam var(--pet-roam-duration, 20s) ease-in-out infinite var(--pet-roam-delay, 0s);
}

.garden-plot-pet-claimed img {
    animation: garden-pet-gait var(--gait-duration, 0.9s) ease-in-out infinite var(--gait-delay, 0s),
               garden-pet-flip var(--pet-roam-duration, 20s) steps(1) infinite var(--pet-roam-delay, 0s);
}

.garden-claim-all-btn {
    display: block;
    margin: 0 0 1rem 0;
    font-size: 0.9rem;
    padding: 0.5rem 1rem;
}

@media (prefers-reduced-motion: reduce) {
    .garden-plot-pet:not(:disabled),
    .garden-plot-pet:not(:disabled) img,
    .garden-plot-pet.garden-plot-pet-glow,
    .garden-plot-pet.garden-plot-pet-glow img,
    .garden-plot-pet-claimed,
    .garden-plot-pet-claimed img {
        animation: none;
    }
    .garden-plot-pet.garden-plot-pet-glow {
        filter: drop-shadow(0 0 6px rgba(255, 210, 60, 0.6));
    }
}

.skill-reward-choice {
    margin-top: .75rem;
    padding-top: .6rem;
    border-top: 1px dashed var(--border, rgba(255,255,255,.15));
}

.choice-row {
    display: flex;
    gap: .5rem;
    flex-wrap: wrap;
}

.choice-button {
    flex: 1 1 auto;
    min-width: 5.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    padding: .45rem .6rem;
    border-radius: .6rem;
    border: 1px solid currentColor;
    background: transparent;
    color: inherit;
    font: inherit;
    font-size: .85rem;
    cursor: pointer;
}

.choice-button:disabled {
    opacity: .6;
    cursor: default;
}

.choice-button img {
    width: 1.75rem;
    height: 1.75rem;
    object-fit: contain;
}
