/* ==========================================================================
   SkillzForge — Main Stylesheet
   8-bit retro pixel art style with rounded corners
   ========================================================================== */

/* --------------------------------------------------------------------------
   CSS Custom Properties (defaults — overridden by themes.css)
   -------------------------------------------------------------------------- */
:root {
    --sf-primary:      #0072B2;
    --sf-secondary:    #009E73;
    --sf-accent:       #CD5114;
    --sf-logo:         #CD5114;
    --sf-tagline:      #a0a0b0;
    --sf-bg:           #1a1a2e;
    --sf-surface:      #16213e;
    --sf-surface-2:    #0f3460;
    --sf-text:         #D5D5F0;
    --sf-text-muted:   #a0a0b0;
    --sf-border:       #2a2a4e;
    --sf-danger:       #D55E00;
    --sf-success:      #071d05;
    --sf-warning:      #E69F00;
    --sf-info:         #56B4E9;

    --sf-radius:       8px;
    --sf-radius-sm:    4px;
    --sf-radius-lg:    12px;

    --sf-shadow:       0 4px 12px rgba(0,0,0,0.4);
    --sf-shadow-sm:    0 2px 6px rgba(0,0,0,0.3);

    --sf-pixel-border: 3px solid;
    --sf-font-pixel:   'Press Start 2P', 'VT323', monospace;
    --sf-font-body:    'Press Start 2P', 'VT323', monospace;
    

    --sf-font-size:    12px;
    --sf-font-size-sm: 7px;
    --sf-font-size-md: 10px;
    --sf-font-size-lg: 14px;

    --sf-xp-bar-h:          28px;
    --sf-header-h:          220px;
    --sf-header-h-expanded: 330px;
    /* The strip left at the bottom of the viewport when the header is
       fullscreen. It has to hold the minimize/maximize buttons (18px tall,
       fixed at the header's own bottom edge) AND leave the site navigation
       reachable, per explicit user direction — so it is deliberately much
       wider than the buttons themselves rather than sized to them.

       THIS IS THE ONE NUMBER that sets the fullscreen header's height: the
       header is the viewport minus the xp bar minus this band, so raising it
       makes the header shorter by exactly the same amount. Raised from 60px
       to 90px on 2026-09-03 per explicit user direction, "reduce the full
       screen header height size 30 pixels" — which is this constant and
       nothing else, deliberately, so the next adjustment is one line.

       REVERSED 2026-10-02, per explicit user direction, after "large amounts
       of empty space between skill cards and the header when the header is
       full screen": 130 -> 60 ("shrink the band 130px to 60px"), then 60 -> 18
       ("remove the fullscreen empty band"). This undoes the 2026-09-05 "make
       the header fullscreen mode 40 px smaller" step and the 2026-09-03 one
       before it. 18px is the minimize button's own height, so the band is now
       just the strip the button hangs in — chosen over a band-free header
       because the button is anchored to the header's bottom edge and would
       otherwise fall off the screen. Site navigation is the hamburger
       dropdown, not a bar, so nothing else needs the room.

       Read by headerScroll.js's fullscreenHeaderHeight() with parseInt, so it
       must stay a single px value and never become a calc(). */
    --sf-header-fullscreen-band: 18px;
    /* `vh`, NOT `dvh`, is the fallback here on purpose — `dvh` is applied
       below, behind an @supports guard. A custom property accepts any token
       stream, so an unrecognized unit is stored happily and only fails when the
       calc() is USED, at which point `height` becomes invalid at computed-value
       time and the header collapses to `auto`. A browser too old for `dvh` gets
       a header 100vh tall instead of no header at all. */
    --sf-header-h-fullscreen: calc(100vh - var(--sf-xp-bar-h) - var(--sf-header-fullscreen-band));
    --sf-header-h-current:  var(--sf-header-h); /* -expanded when html.header--expanded, -fullscreen when html.header--fullscreen */
    /* 0 at every width now. Per explicit user direction (2026-08-21), "use the
       hamburger menu for site nav all the time, instead of just on a phone" —
       the links live in a dropdown panel rather than a bar, so the bar's row
       is given back to the page. Kept as a variable rather than deleted
       because four rules add it into their layout padding, and a name that
       reads 0 is clearer there than four edits that silently drop a term. */
    --sf-nav-h:             0px;

    --fab-power-ring:       #c4956a;
}

/* THE FULLSCREEN HEADER MEASURES THE VISIBLE VIEWPORT, NOT THE LARGE ONE.
   ------------------------------------------------------------------------
   `vh` is defined as the LARGE viewport — the spec says to assume any UA
   interface that dynamically expands and retracts is retracted — so on a phone
   showing its URL bar, `100vh` exceeds `window.innerHeight` by the height of
   that bar. Everything on the JS side of this header measures
   `window.innerHeight`: headerScroll.js's fullscreenHeaderHeight(), and through
   it the compositor's clip rectangle, the nav's top, the weather overlay and
   the body's padding.

   With `vh` those two disagreed on a phone and agreed on every desktop, which
   is why it survived: the header's BOX was drawn from the larger number and its
   clip from the smaller, leaving a band of visible header — as tall as the URL
   bar — that painted no fabs, no wildlife and no forest at all. Reported
   2026-09-06 as "fab objects being cropped at a point higher than the actual
   bottom of the visible header", on a phone, in fullscreen.

   `dvh` tracks the visual viewport, which is what `window.innerHeight` reports,
   so the two now agree by construction rather than by coincidence on one class
   of device. It also makes the band at the bottom — reserved for the minimize/
   maximize buttons and the nav — genuinely on screen, which is the whole reason
   the band exists.

   DO NOT "simplify" this back to a single `vh` declaration. The @supports guard
   is what keeps a browser without `dvh` on a working header rather than an
   `auto`-height one; see the fallback's own note above.
   ------------------------------------------------------------------------ */
@supports (height: 100dvh) {
    :root {
        --sf-header-h-fullscreen: calc(100dvh - var(--sf-xp-bar-h) - var(--sf-header-fullscreen-band));
    }
}

/* --------------------------------------------------------------------------
   Reset & Base
   -------------------------------------------------------------------------- */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    font-size: var(--sf-font-size);
    scroll-behavior: smooth;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

body {
    font-family: var(--sf-font-body);
    font-size: 16px;
    background-color: var(--sf-bg);
    color: var(--sf-text);
    line-height: 1.5;
    min-height: 100vh;
    padding-top: calc(var(--sf-xp-bar-h) + var(--sf-header-h) + var(--sf-nav-h));
    transition: padding-top 0.4s ease, background-color 0.6s ease, color 0.3s ease;
}

/* --------------------------------------------------------------------------
   XP Bar
   -------------------------------------------------------------------------- */
.xp-bar-wrapper {
    font-size: var(--sf-font-size);
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    height: var(--sf-xp-bar-h);
    background: var(--sf-surface);
    border-bottom: 2px solid var(--sf-primary);
    display: flex;
    align-items: center;
    transition: background-color 0.2s ease;
}

.xp-bar-profile-link {
    display: flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    min-width: 50%;
    margin-left: auto;
}

.xp-bar-profile-link:hover ~ .xp-bar-divider,
.xp-bar-profile-link:hover {
    opacity: 0.85;
}

.xp-bar-inner {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 0 12px;
}

.xp-label {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-accent);
    white-space: nowrap;
    min-width: 56px;
    padding: 0px;
    margin:0px;
}

.xp-track {
    flex: 1;
    min-width: 25%;
    max-width: 30%;
    height: 14px;
    background: var(--sf-border);
    border-radius: 2px;
    border: 2px solid var(--sf-primary);
    overflow: hidden;
    position: relative;
    margin-left:40px;
}

.xp-fill {
    height: 100%;
    background: linear-gradient(90deg, var(--sf-primary), var(--sf-accent));
    border-radius: 0;
    transition: width 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
    position: relative;
}

/* Animated shimmer on XP fill */
.xp-fill::after {
    content: '';
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: 30px;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
    animation: xpShimmer 1.5s infinite;
}

@keyframes xpShimmer {
    0%   { right: 100%; }
    100% { right: -30px; }
}

.xp-numbers, .xp-total {
    font-family: var(--sf-font-pixel);
    font-size: 7px;
    color: var(--sf-text-muted);
    white-space: nowrap;
    padding: 5px;
}

.xp-bar-divider {
    display: block;
    width: 1px;
    height: 16px;
    background: var(--sf-border);
    flex-shrink: 0;
    margin: 0 4px;
}

.grove-section {
    display: flex;
    align-items: center;
    flex: 1;
    min-width: 0;
    /* max-width: 50%; */
    overflow: hidden;
    margin-left:auto;
}

.forest-bar-label {
    min-width: 44px;
}

.forest-track {
    flex: 1;
    border-color: #3a7d44;
}

.forest-fill {
    height: 100%;
    background: linear-gradient(90deg, #2d7a3a, #6dbf52);
    border-radius: 0;
}

/* --------------------------------------------------------------------------
   Site Header
   -------------------------------------------------------------------------- */
.site-header {
    font-size: var(--sf-font-size);
    position: fixed;
    top: var(--sf-xp-bar-h);
    left: 0;
    right: 0;
    z-index: 900;
    height: var(--sf-header-h);
    overflow: hidden;
    background: var(--sf-bg);
    cursor: grab; /* moved here from .landscape-wrapper (now pointer-events:none) so the drag affordance still shows */
}

.landscape-wrapper {
    position: absolute;
    inset: 0;
    overflow: visible;
    /* Allow sun/moon glow to bleed ~120 px above the header top (XP bar at z-1000
       covers any overflow there); clip sides and bottom precisely. */
    clip-path: inset(-120px 0 0 0);
    isolation: isolate;
    /* Every individual canvas/image inside this wrapper (and the --fg
       sibling) already opts out with its own pointer-events: none —
       #celestialCanvas is the sole deliberate pointer-events: auto
       exception (so document.elementFromPoint resolves to it for
       ThemeCelestial's own hitTest — see PointerDispatcher.js's header
       comment: every real interaction is routed through it doing its own
       math against raw clientX/clientY, never through native DOM
       listeners on these elements). The WRAPPER DIV ITSELF was the one
       piece of this stack never given pointer-events: none, so its own
       full-header box (inset: 0) silently swallowed elementFromPoint at
       any point where none of its own children happened to have opaque
       content — e.g. clicking the sun, since .landscape-wrapper--fg (z-2,
       in front of the bg wrapper #celestialCanvas lives in) has no
       visible content in the sky region but was still the topmost
       element there. Confirmed via tests/playwright/celestial-sun-blink-
       real-page.spec.js, which reproduced this deterministically against
       the real app server (the isolated fixtures every earlier celestial-
       sun-blink-*.spec.js used never included this wrapper's own
       full-header CSS box, so none of them could catch it). */
    pointer-events: none;
}
/* Foreground wrapper sits in front of the bg wrapper (z-auto) in the header stacking context. */
.landscape-wrapper--fg {
    z-index: 2;
}
.site-header.header--dragging,
.site-header.header--dragging * { cursor: grabbing !important; user-select: none; }
/* Restore overflow during drag so the landscape can bleed horizontally for the parallax effect. */
.site-header.header--dragging { overflow: visible; }
.site-header.header--dragging .landscape-wrapper {
    /* Open all edges during drag so landscape bleed is revealed; bottom stays clipped. */
    clip-path: inset(-9999px -9999px 0 -9999px);
}

/* Shared styles for both landscape layers.
   left: -45px gives 45px horizontal bleed (30px base × 1.5) for drag reveals.
   top: 50% / translate centers the image vertically in the header.
   width: calc(100% + 90px) = header width + 2×45px bleed; height: auto scales
   proportionally so the image always fills the full header width. */
.landscape-layer {
    position: absolute;
    /* headerDrag.js drives its own custom drag gesture over these images;
       without this, the browser's native HTML5 image-drag can hijack the
       gesture mid-move (dragstart fires, mousemove/mouseup stop arriving),
       permanently stranding headerDrag.js's active/_hasMoved state as true.
       See tests/playwright/visual/header-drag-click-swallow.spec.js. */
    -webkit-user-drag: none;
    user-drag: none;
    /* Drag bleed: forest.js _resize() sets exact pixel width/left via JS so the
       image is 1.2× the header width (desktop) or 1.0× (mobile) + 90px bleed,
       keeping getBoundingClientRect() dimensions equal to the true layout/visual
       dimensions. -45px / calc(100%+90px) are CSS fallbacks active only before
       the first _resize() fires. CSS scale: 1 is a reset — zoom is applied by JS
       via explicit width so BoundingClientRect stays accurate for hit-testing. */
    left: -45px;
    width: calc(100% + 90px);
    scale: 1;
    /* Vertical centering + 12% downward bias so the landscape horizon falls in
       the lower third of the header at every viewport width. */
    top: 50%;
    translate: 0 calc(-50% + 12%);
    height: auto;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    display: block;
}
/*  Note: This has to be no greater than -300 or the opaque portion of the image will obscure the celesstial bleed canvas.  */
.landscape-bg { z-index: 1; scale: 1.56; transform-origin: center top; translate: 0 -400px; }

.landscape-fg {
    z-index: 3;
    pointer-events: none;
}

/* Sunrise/sunset sky color bands — additive overlay above landscape-bg, below sun/moon (z-2, DOM before celestial) */
#skyBandCanvas {
    position: absolute;
    top: 0;
    bottom: 0;
    left: -160px;
    right: -160px;
    height: 100%;
    pointer-events: none;
    z-index: 2;
    filter: blur(18px);
    will-change: transform, filter;
    transform: translateZ(0);
    backface-visibility: hidden;
}

/* Celestial canvas — sun / moon / stars.
   Sits inside .landscape-wrapper (bg) at z-3, above sky bands (z-2) and behind ocean (z-4).
   headerDrag.js moves it independently at CELESTIAL_RATIO via the celestialEl reference. */
#celestialCanvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: auto;
    cursor: default;
    z-index: 3;
}

/* Sky flyer canvas — objects fly through the sky, behind landscape-fg */
#skyFlyerCanvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 2;
}

/* Rocket canvas — launches from terrain, arcs into sky; z-1 places it behind fg-wrapper (z-2) */
#rocketCanvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 1;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

/* Wildlife canvas was removed from the DOM; wildlife.js renders via EntityCompositor. */

/* Weather overlay */
.weather-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 5;
}

.weather-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    image-rendering: pixelated;
    opacity: 0.4;
}

.weather-tint {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: calc(var(--sf-xp-bar-h) + var(--sf-header-h-current));
    pointer-events: none;
    background: transparent;
    transition: background 4s ease, height 0.4s ease;
    z-index: 3002;
}
.weather-tint--clouds { background: rgba(180, 190, 215, 0.25); }
.weather-tint--storm  { background: rgba(25,  30,  55,  0.5); }

#smokeCanvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 7;
}

/* Fixed overlay canvas for weather effects.
   top: 0 / height includes the XP bar so (0,0) aligns with the viewport origin.
   --sf-header-h-current resolves to --sf-header-h normally and to --sf-header-h-expanded
   when html.header--expanded is set.
   IMPORTANT: weather-canvas must be a direct child of <body> (root stacking context).
   Placing it inside .site-header would scope its z-index to z-900 and allow
   fabCanvas/mascot/worldFrontCanvas to escape the weather overlays. */
.weather-canvas {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: calc(var(--sf-xp-bar-h) + var(--sf-header-h-current));
    pointer-events: none;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    z-index: 3002;
    transition: height 0.4s ease;
}

/* Entity compositor canvases — full-viewport fixed; z-indexes are in the root stacking context.
   bodyEntityCompositorCanvas (z-918): effects layer (smoke, explosions, lasers, faeries, wind,
     blood pools). Always clipped at headerBottom so effects stay inside the header.
   entityCompositorCanvas (z-919): depth-sorted scene layer. Applies header-offset translate for
     header-constrained entities (trees, wildlife, etc.) but renders the FULL VIEWPORT in normal
     mode so roaming entities (fabs, pets) remain visible and above page content when they walk
     below the header. When _clipBottom is active (moonBase scroll / header collapse) both
     canvases clip to that boundary instead.
   z-919 sits above page content (.main-content and descendants, which carry no explicit z-index)
   and above site-header (z-900) / site-nav (z-910); below fabOverlayContainer (z-920). */
#bodyEntityCompositorCanvas,
#entityCompositorCanvas {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}
#bodyEntityCompositorCanvas { z-index: 918; }
#entityCompositorCanvas     { z-index: 919; }

/* faerieCanvas was removed from the DOM; faeries.js renders via EntityCompositor body layer. */

#lightningCanvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 10;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

#fireworkCanvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 11;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}


.header-content {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    padding: 8px 16px;
    /*background: linear-gradient(to bottom, rgba(0,0,0,0.7) 0%, transparent 100%);*/
    z-index: 9;
    pointer-events: none;
}

/* position:fixed at z-3010 (above weather canvas z-3002, worldFrontCanvas z-3001, and entity
   canvases z-918/919) so the logo is always visible on top of all header elements.
   Lives outside site-header in the DOM so moonBase clip-path never clips it. */
.header-logo-stack {
    position: fixed;
    top: calc(var(--sf-xp-bar-h) + 8px); /* xp-bar height + header-content padding-top */
    left: 16px;                           /* header-content padding-left */
    z-index: 3010;
    pointer-events: auto;
}

.site-logo {
    text-decoration: none;
    color: inherit;
    pointer-events: auto;
}

.site-title {
    font-family: var(--sf-font-pixel);
    font-size: clamp(12px, 2.5vw, 20px);
    color: var(--sf-logo);
    text-shadow: 1px 2px 0 var(--sf-primary), -1px -1px 0 rgba(0,0,0,0.8);
    letter-spacing: 2px;
}

.site-tagline {
    font-family: var(--sf-font-body);
    font-size: var(--sf-font-size-sm);
    color: var(--sf-tagline);
}

.header-controls {
    display: flex;
    align-items: center;
    gap: 12px;
}

.header-user {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
}

.header-username {
    font-family: var(--sf-font-pixel);
    font-size: 7px;
    color: var(--sf-text);
    translate: -100px;
}

.header-level {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-accent);
    translate: -100px;
}

/* --------------------------------------------------------------------------
   Navigation
   -------------------------------------------------------------------------- */
.site-nav {
    position: fixed;
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h));
    left: 0;
    right: 0;
    z-index: 910;
    background: var(--sf-surface);
    /*border-bottom: 2px solid var(--sf-primary);*/
    height: var(--sf-nav-h);
    transition: top 0.4s ease;
}

/* --------------------------------------------------------------------------
   Header expand toggle
   -------------------------------------------------------------------------- */
html.header--expanded {
    --sf-header-h-current: var(--sf-header-h-expanded);
}
html.header--expanded .site-header {
    height: var(--sf-header-h-expanded);
}
html.header--expanded .site-nav {
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-expanded));
}
html.header--expanded body {
    padding-top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-expanded) + var(--sf-nav-h));
}

/* --------------------------------------------------------------------------
   Header fullscreen — the third state (2026-09-03)
   --------------------------------------------------------------------------
   Per explicit user direction, the header has three states rather than two:
   minimized (--sf-header-h), normal 100% (--sf-header-h-expanded) and
   fullscreen. The classes are MUTUALLY EXCLUSIVE — main.php's setHeaderState()
   never sets both — so these rules and the html.header--expanded rules above
   can never both apply and do not have to be ordered against each other.

   Fullscreen is a taller BOX and nothing else. There is no transform, no
   scale, and no zoom anywhere in this block, per explicit user direction:
   "when the header is maximized to fullscreen, don't zoom content. just
   display more of the header." Every rule below is the html.header--expanded
   rule above with a different height variable substituted, which is what
   keeps that promise structurally rather than by inspection.
   -------------------------------------------------------------------------- */
html.header--fullscreen {
    --sf-header-h-current: var(--sf-header-h-fullscreen);
}
html.header--fullscreen .site-header {
    height: var(--sf-header-h-fullscreen);
}
html.header--fullscreen .site-nav {
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-fullscreen));
}
html.header--fullscreen body {
    padding-top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-fullscreen) + var(--sf-nav-h));
}
/* The first group box sits 24px below the fullscreen header, per explicit user
   direction (2026-10-02: "in fullscreen header mode, the header should sit 24px
   above the skill card"). Higher specificity than the responsive .main-content
   padding, so it wins at every width. */
html.header--fullscreen .main-content {
    padding-top: 24px;
}

.header-expand-btn {
    position: fixed;
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h));
    left: 50%;
    transform: translateX(-50%);
    /* Above #fabOverlayContainer (920) and #fabContextMenu (921, forest.css) so a
       ground fab dragged near the header's bottom-center can never visually cover
       or (via native elementFromPoint hit-testing) swallow clicks meant for this
       button — see fabs.js's fab hitTest() .header-expand-btn exclusion, which
       relies on this button winning elementFromPoint(). That exclusion matches
       on the CLASS rather than on #headerExpandBtn's id, so the maximize button
       — which carries the same class and sits alongside it in the normal state
       — is covered by the same rule rather than needing its own. */
    z-index: 922;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 18px;
    padding: 0;
    background: var(--sf-surface);
    border: 1px solid color-mix(in srgb, var(--sf-primary) 40%, transparent);
    border-top: none;
    border-radius: 0 0 8px 8px;
    cursor: pointer;
    color: var(--sf-primary);
    transition: top 0.4s ease, background-color 0.15s, color 0.15s, opacity 0.15s;
    opacity: 0.7;
}
.header-expand-btn:hover {
    opacity: 1;
    background: color-mix(in srgb, var(--sf-primary) 15%, var(--sf-surface));
}

/* The maximize button — shown ONLY in the normal (100%) state, per explicit
   user direction: "When the header is at 100% of height, instead of only
   displaying the minimize button, also display the maximize button... When
   the header is fullscreen, don't display the maximize button." The minimized
   state is unchanged and still shows one button.

   It carries .header-expand-btn as well as its own modifier so it inherits
   every base style AND is already covered by both fab exclusions (fabs.js's
   hitTest, FabBase.js's :not(.header-expand-btn) avoidance filter) without a
   second rule in either place. */
.header-expand-btn--maximize {
    display: none;
}
html.header--expanded:not(.header--fullscreen) .header-expand-btn--maximize {
    display: flex;
}

/* Side by side, still centered as a pair. The base rule centers a single
   button with left:50% + translateX(-50%); margin-left then slides each half
   of the pair off that shared center, which leaves the centering mechanism
   (and its transform, which nothing else may claim) untouched. Only the
   normal state has two buttons, so only the normal state shifts them.

   :has() IS LOAD-BEARING, not a flourish. The maximize button is rendered only
   on the dashboard now (fullscreen is a dashboard-only state, explicit user
   direction 2026-09-06), and every other page still reaches the normal state —
   so without this condition the SOLE remaining button would be shifted 25px
   left of center on every page in the site, hanging off nothing. Keying on the
   button's presence rather than on a second "is this the dashboard" class means
   the two can never disagree: the shift exists exactly when the pair does. */
html.header--expanded:not(.header--fullscreen):has(.header-expand-btn--maximize) #headerExpandBtn {
    margin-left: -25px;
}
html.header--expanded:not(.header--fullscreen) .header-expand-btn--maximize {
    margin-left: 25px;
}

/* Drew's Journal (planning-drew-dungeon-snow-swamp-additions.md) — button
   + panel. Only ever shown while Drew's Dungeon is the active MoonBase
   Interior scene (toggled by moonInteriorDrew.js's own activate()/
   deactivate() via the .drew-journal-btn--visible class), matching the
   scene-scoped "only relevant while this interior is on screen" shape
   every other Drew's Dungeon UI element already has. Positioned at the
   bottom-left of the header's own canvas area — the creature legend
   (drawn directly on moonInteriorCanvasA/B, not a separate DOM element)
   renders along the bottom of that same canvas, so "below the legend"
   reads as "just under the bottom edge of the header itself." */
#drewJournalButtonContainer {
    position: fixed;
    left: 12px;
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h) + 6px);
    z-index: 1120; /* above every moonInteriorCanvas layer (max 1110) */
    display: none;
}
#drewJournalButtonContainer.drew-journal-btn--visible {
    display: block;
}
html.header--expanded #drewJournalButtonContainer {
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-expanded) + 6px);
}
html.header--fullscreen #drewJournalButtonContainer {
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-fullscreen) + 6px);
}
#drewJournalOpenBtn {
    font: inherit;
    font-size: 10px;
    padding: 4px 10px;
    background: var(--sf-surface);
    border: 1px solid color-mix(in srgb, var(--sf-primary) 40%, transparent);
    border-radius: 6px;
    color: var(--sf-primary);
    cursor: pointer;
}
#drewJournalOpenBtn:hover {
    background: color-mix(in srgb, var(--sf-primary) 15%, var(--sf-surface));
}

#drewJournalPanel {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 3050; /* above the gnome-office dialog canvas (3011) — a modal, meant to sit above every scene */
    display: none;
    flex-direction: column;
    width: min(520px, 92vw);
    max-height: 80vh;
    background: var(--sf-surface);
    border: 1px solid color-mix(in srgb, var(--sf-primary) 40%, transparent);
    border-radius: 10px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
    padding: 16px;
}
#drewJournalPanel.drew-journal-panel--open {
    display: flex;
}
#drewJournalPanelHeader {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
}
#drewJournalPanelHeader h2 {
    margin: 0;
    font-size: 18px;
}
#drewJournalCloseBtn {
    font: inherit;
    font-size: 19px;
    line-height: 1;
    background: none;
    border: none;
    color: var(--sf-primary);
    cursor: pointer;
    padding: 4px;
}
#drewJournalEntries {
    overflow-y: auto;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.drew-journal-entry {
    padding: 8px 10px;
    background: color-mix(in srgb, var(--sf-primary) 6%, transparent);
    border-radius: 6px;
    font-size: 11px;
    line-height: 1.4;
    /* The caret below is positioned against this box rather than against
       the summary button, so the button keeps its natural block flow and
       the caret still counts as part of the button for hit-testing. */
    position: relative;
}
.drew-journal-entry__date {
    display: block;
    font-size: 9px;
    opacity: 0.65;
    margin-bottom: 4px;
}
/* WHERE it happened, by name (documentation/planning-drew-board-names.md):
   its own short line under the date, in the place's own accent. */
.drew-journal-entry__place {
    display: block;
    font-size: 10px;
    font-weight: 600;
    color: var(--sf-accent, #8fc7d9);
    margin-bottom: 4px;
}

/* Per explicit user direction (2026-08-20) — "let the user click each entry
   to see more detail about what happened."

   A real <button> rather than a div with a handler, so the row is reachable
   with Tab, opens on Enter/Space with no extra key handling, and carries
   aria-expanded/aria-controls for free. Only rows whose entry actually
   carries a structured memory (migration 176 onward) get one — an older row
   stays a plain, non-interactive div, so no affordance is ever offered for a
   reveal that would be empty. See _buildJournalEntryRow(). */
.drew-journal-entry__summary {
    display: block;
    width: 100%;
    /* The row's own font/color, not the UA's button defaults — the summary
       IS the entry, and must not look like a control pasted over it. */
    font: inherit;
    color: inherit;
    text-align: left;
    background: none;
    border: none;
    padding: 0 16px 0 0; /* right pad clears the caret */
    cursor: pointer;
}
.drew-journal-entry--expandable:hover {
    background: color-mix(in srgb, var(--sf-primary) 12%, transparent);
}
.drew-journal-entry__summary:focus-visible {
    outline: 2px solid var(--sf-primary);
    outline-offset: 2px;
}
.drew-journal-entry__caret {
    position: absolute;
    top: 8px;
    right: 10px;
    font-size: 8px;
    opacity: 0.55;
    transition: transform 0.15s ease;
}
.drew-journal-entry--expanded .drew-journal-entry__caret {
    transform: rotate(180deg);
}
/* A margin note against the entry, not a table under it — one rule, indented,
   quieter than the prose it belongs to. */
.drew-journal-entry__detail {
    margin-top: 8px;
    padding-left: 10px;
    border-left: 2px solid color-mix(in srgb, var(--sf-primary) 35%, transparent);
    font-size: 10px;
    line-height: 1.5;
}
.drew-journal-entry__detail[hidden] {
    display: none;
}
.drew-journal-entry__detail-line {
    display: block;
}
.drew-journal-entry__detail-line + .drew-journal-entry__detail-line {
    margin-top: 2px;
    opacity: 0.75;
}
/* A WAR entry's "go there" button (2026-09-16) — per explicit user direction,
   a stored war toast is clickable to teleport Drew to the board it happened
   on. Its own small control rather than folded into
   .drew-journal-entry__summary: that button is shared with the Nexis
   Library's journal reader, a page with no Drew to move. */
.drew-journal-entry__teleport {
    display: inline-block;
    margin-top: 6px;
    font: inherit;
    font-size: 10px;
    color: var(--sf-primary);
    background: color-mix(in srgb, var(--sf-primary) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--sf-primary) 40%, transparent);
    border-radius: 4px;
    padding: 2px 8px;
    cursor: pointer;
}
.drew-journal-entry__teleport:hover {
    background: color-mix(in srgb, var(--sf-primary) 20%, transparent);
}
#drewJournalPagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 12px;
    font-size: 10px;
}
#drewJournalPrevBtn, #drewJournalNextBtn {
    font: inherit;
    padding: 4px 10px;
    background: var(--sf-surface);
    border: 1px solid color-mix(in srgb, var(--sf-primary) 40%, transparent);
    border-radius: 6px;
    color: var(--sf-primary);
    cursor: pointer;
}
#drewJournalPrevBtn:disabled, #drewJournalNextBtn:disabled {
    opacity: 0.4;
    cursor: default;
}
.header-expand-icon {
    display: block;
    transition: transform 0.4s ease;
}
html.header--expanded .header-expand-btn {
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-expanded));
}
html.header--expanded .header-expand-icon {
    transform: rotate(180deg);
}
/* Fullscreen: the ONE remaining button points up, because it takes the header
   back down to normal. The maximize icon has its own class and so is never
   caught by this rotation. */
html.header--fullscreen .header-expand-btn {
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-fullscreen));
}
html.header--fullscreen .header-expand-icon {
    transform: rotate(180deg);
}
.header-maximize-icon {
    display: block;
}

.nav-list {
    list-style: none;
    display: flex;
    align-items: center;
    height: 100%;
    padding: 0 16px;
    gap: 4px;
 
}

.nav-link {
    display: inline-flex;
    align-items: center;
    padding: 6px 12px;
    font-family: var(--sf-font-pixel);
    font-size: 10px;
    color: var(--sf-text-muted);
    text-decoration: none;
    border-radius: var(--sf-radius-sm);
    border: 2px solid transparent;
    white-space: nowrap;
    transition: all 0.15s;
    /* Pixel rounded edges. 
    source: https://pixelcorners.lukeb.co.uk/?radius=4&multiplier=4
    credit: Luke Bonaccorsi */ 
    clip-path: polygon(
    0px calc(100% - 4px),
    2px calc(100% - 4px),
    2px calc(100% - 2px),
    4px calc(100% - 2px),
    4px 100%,
    calc(100% - 4px) 100%,
    calc(100% - 4px) calc(100% - 2px),
    calc(100% - 2px) calc(100% - 2px),
    calc(100% - 2px) calc(100% - 4px),
    100% calc(100% - 4px),
    100% 4px,
    calc(100% - 2px) 4px,
    calc(100% - 2px) 2px,
    calc(100% - 4px) 2px,
    calc(100% - 4px) 0px,
    4px 0px,
    4px 2px,
    2px 2px,
    2px 4px,
    0px 4px
  );

}

.nav-link:hover,
.nav-link--active {
    color: var(--sf-accent);
    border-color: var(--sf-accent);
    background: rgba(230, 159, 0, 0.1);
}

.nav-link--logout {
    margin-left: auto;
    color: var(--sf-danger);
}

.nav-link--logout:hover {
    border-color: var(--sf-danger);
    background: rgba(213, 94, 0, 0.1);
    color: var(--sf-danger);
}

/* --------------------------------------------------------------------------
   Hamburger — the site navigation at EVERY width (2026-08-21)
   --------------------------------------------------------------------------
   Per explicit user direction: "use the hamburger menu for site nav all the
   time, instead of just on a phone."

   These rules used to live inside the `max-width: 639px` block. They are
   written entirely in the layout variables rather than in the mobile
   viewport's values, which is what lets them lift out unchanged: the button
   sits in the xp bar's own row at whatever height that row happens to be, and
   the panel hangs below the header at whatever height THAT is. The mobile
   block keeps only what is genuinely narrower there.
   -------------------------------------------------------------------------- */
.nav-hamburger {
    display: flex;
    flex-direction: column;
    gap: 4px;
    cursor: pointer;
    /* Inline with the xp bar at the top of the viewport, left edge. */
    position: fixed;
    top: 0;
    left: 0;
    right: auto;
    width: 41px;
    height: var(--sf-xp-bar-h);
    padding: 0;
    background: transparent;
    backdrop-filter: none;
    border: none;
    border-right: 1px solid var(--sf-border);
    border-radius: 0;
    /* Above the header canvases, which reach z-3011 in the moon-base scene. */
    z-index: 3100;
    justify-content: center;
    align-items: center;
}

/* THE BAR MUST BE RAISED TOO, and this is not redundancy — I removed it once
   as belt-and-braces and the button vanished.

   `.site-nav` is `position: fixed` with a z-index, so it creates a STACKING
   CONTEXT. Its children's z-index values are resolved inside that context, not
   against the page: the button's 3100 was being capped at the bar's own 910
   and painting underneath the header canvases, which reach 3011. The bar has
   to carry the raise for the button to keep it.

   Height stays driven by --sf-nav-h (0), so the bar is a zero-height anchor
   whose only job now is to be a stacking context high enough for what it
   holds. */
.site-nav {
    top: 0;
    height: 0;
    width: 0;
    z-index: 3100;
}

.nav-list {
    position: fixed;
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h));
    left: 0;
    right: 0;
    background: var(--sf-surface);
    border-bottom: 2px solid var(--sf-primary);
    flex-direction: column;
    align-items: stretch;
    height: auto;
    padding: 8px;
    display: none;
    z-index: 3100;
}

.nav-list.nav--open {
    display: flex;
}

.nav-list .nav-link {
    padding: 10px 12px;
    border-radius: var(--sf-radius);
    width: 200px;
}

.nav-list .nav-link--logout {
    margin-left: 0;
}

.hamburger-line {
    display: block;
    width: 22px;
    height: 2px;
    background: var(--sf-text);
    transition: all 0.2s;
}

.site-footer {
    text-align: center;
    padding: 20px;
    font-family: var(--sf-font-body);
    font-size: 16px;
    color: var(--sf-text-muted);
}

/* --------------------------------------------------------------------------
   Main Content
   -------------------------------------------------------------------------- */
.main-content {
    max-width: 1400px;
    margin: 0 auto;
    padding: 24px 16px 48px;
}

/* --------------------------------------------------------------------------
   Flash Messages
   -------------------------------------------------------------------------- */
.flash-container {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
    pointer-events: auto;
    /* .header-content starts flush at the xp-bar/header top, same as
       .header-logo-stack (top: xp-bar-h + 8px) — without this, a toast
       renders directly under the logo's own space and, since the logo sits
       at z-index 3010 vs this container's inherited z-index 9, the logo
       draws OVER it. Push toasts below the logo block's own height (site-
       title up to 20px + site-tagline 7px, generous line-height) instead of
       fighting it on z-index. */
    margin-top: 64px;
}

.flash-container:empty {
    display: none;
}

/* The main-app header's own instance ONLY (app/views/partials/header.php) —
   auth.php's flash-container stays plain/in-flow, unchanged, for its simple
   card layout. #siteHeader clips its DOM children with overflow:hidden and
   shrinks its own height when headerScroll.js contracts it on scroll, so a
   toast still living inside that box (as this one used to) gets clipped the
   moment the header is shorter than the toast's own offset from the top —
   reported as "a new tree was planted" toast disappearing on a minimized
   header. Fixed by moving the markup itself outside #siteHeader (same
   pattern .header-logo-stack already uses, for the same underlying reason)
   and switching its positioning from flow (margin-top under .header-content)
   to fixed, with the equivalent viewport offset:
   siteHeader.top (xp-bar-h) + header-content padding-top (8px) + the old
   margin-top (64px) = xp-bar-h + 72px. */
.flash-container--floating {
    position: fixed;
    top: calc(var(--sf-xp-bar-h) + 72px);
    left: 16px;
    right: 16px;
    width: auto;
    margin-top: 0;
    z-index: 3005; /* above the header (900)/xp-bar (1000)/toy crate (1500); just under the logo (3010), which it must never cover */
}

.flash {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-radius: var(--sf-radius);
    border-left: 4px solid;
    font-family: var(--sf-font-body);
    font-size: 16px;
    line-height: 1.5;
    background-color: var(--sf-secondary);
    cursor: pointer;
    animation: flashEnter 0.2s ease forwards;
}

@keyframes flashEnter {
    from { opacity: 0; transform: translateY(-8px); }
    to   { opacity: 1; transform: translateY(0); }
}

.flash--dismissing {
    transition: opacity 0.2s ease, transform 0.2s ease;
    opacity: 0;
    transform: translateY(-8px);
    animation: none;
}

.flash--success {
    background: rgba(193, 230, 188, 0.623);
    border-color: var(--sf-success);
    color: var(--sf-success);
}

.flash--error {
    background: rgba(213, 94, 0, 0.5);
    border-color: var(--sf-danger);
    color: var(--sf-danger);
}

.flash__close {
    margin-left: auto;
    background: none;
    border: none;
    cursor: pointer;
    font-size: 16px;
    color: inherit;
    opacity: 0.7;
    line-height: 1;
}

.flash--warning{
    background: rgba(213, 94, 0, 0.5);
    border-color: var(--sf-danger);
    color: var(--sf-danger);
}

/* --------------------------------------------------------------------------
   Pixel Buttons
   -------------------------------------------------------------------------- */
.pixel-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 14px;
    margin: 5px;
    font-family: var(--sf-font-pixel);
    font-size: 9px;
    border-radius: var(--sf-radius);
    border: 3px solid;
    cursor: pointer;
    text-decoration: none;
    transition: transform 0.1s, box-shadow 0.1s;
    white-space: nowrap;
    background: var(--sf-surface);
    color: var(--sf-text);
    border-color: var(--sf-border);
    user-select: none;
    /* Pixel rounded edges. 
    source: https://pixelcorners.lukeb.co.uk/?radius=4&multiplier=4
    credit: Luke Bonaccorsi */ 
    clip-path: polygon(
    0px calc(100% - 8px),
    4px calc(100% - 8px),
    4px calc(100% - 4px),
    8px calc(100% - 4px),
    8px 100%,
    calc(100% - 8px) 100%,
    calc(100% - 8px) calc(100% - 4px),
    calc(100% - 4px) calc(100% - 4px),
    calc(100% - 4px) calc(100% - 8px),
    100% calc(100% - 8px),
    100% 8px,
    calc(100% - 4px) 8px,
    calc(100% - 4px) 4px,
    calc(100% - 8px) 4px,
    calc(100% - 8px) 0px,
    8px 0px,
    8px 4px,
    4px 4px,
    4px 8px,
    0px 8px
  );
}

.pixel-btn:active {
    transform: translateY(2px);
    box-shadow: none !important;
}

.pixel-btn:focus-visible {
    outline: 3px solid var(--sf-accent);
    outline-offset: 2px;
}

.pixel-btn--primary {
    background: var(--sf-primary);
    border-color: color-mix(in srgb, var(--sf-primary) 50%, black);
    color: #fff !important;
    box-shadow: 0 4px 0 color-mix(in srgb, var(--sf-primary) 50%, black);
    
}

.pixel-btn--green {
    background: var(--sf-success);
    border-color: color-mix(in srgb, var(--sf-success) 50%, black);
    color: #111;
    box-shadow: 0 4px 0 color-mix(in srgb, var(--sf-success) 50%, black);
    /* Pixel rounded edges. 
    source: https://pixelcorners.lukeb.co.uk/?radius=4&multiplier=4
    credit: Luke Bonaccorsi */ 
    clip-path: polygon(
    0px calc(100% - 8px),
    4px calc(100% - 8px),
    4px calc(100% - 4px),
    8px calc(100% - 4px),
    8px 100%,
    calc(100% - 8px) 100%,
    calc(100% - 8px) calc(100% - 4px),
    calc(100% - 4px) calc(100% - 4px),
    calc(100% - 4px) calc(100% - 8px),
    100% calc(100% - 8px),
    100% 8px,
    calc(100% - 4px) 8px,
    calc(100% - 4px) 4px,
    calc(100% - 8px) 4px,
    calc(100% - 8px) 0px,
    8px 0px,
    8px 4px,
    4px 4px,
    4px 8px,
    0px 8px
  );
    
}

.skill-card__start{
    font-size: 16px;
    width:100%;
}

.skill-card__stop{
    font-size: 16px;
    width:100%;
}

.pixel-btn--red, .pixel-btn--danger {
    background: var(--sf-danger);
    border-color: color-mix(in srgb, var(--sf-danger) 50%, black);
    color: #fff;
    box-shadow: 0 4px 0 color-mix(in srgb, var(--sf-danger) 50%, black);
}

.pixel-btn--blue {
    background: var(--sf-info);
    border-color: color-mix(in srgb, var(--sf-info) 50%, black);
    color: #111;
    box-shadow: 0 4px 0 color-mix(in srgb, var(--sf-info) 50%, black);
}

.pixel-btn--orange {
    background: var(--sf-warning);
    border-color: color-mix(in srgb, var(--sf-warning) 50%, black);
    color: #fff;
    box-shadow: 0 4px 0 color-mix(in srgb, var(--sf-warning) 50%, black);
}

.pixel-btn--muted {
    background: #444;
    border-color: #333;
    color: #bbb;
    box-shadow: 0 4px 0 #222;
}

.pixel-btn--md  { font-size: 16px; padding: 5px 10px; }
.pixel-btn--sm  { font-size: 9px; padding: 5px 10px; }
.pixel-btn--xs  { font-size: 8px; padding: 4px 7px; }
.pixel-btn--full { width: 100%; }

/* --------------------------------------------------------------------------
   Pixel Cards
   -------------------------------------------------------------------------- */
.pixel-card {
    background: var(--sf-surface);
    border: 3px solid var(--sf-primary);
    border-radius: var(--sf-radius-lg);
    padding: 16px;
    box-shadow: var(--sf-shadow), 4px 4px 0 var(--sf-primary);
    transition: transform 0.15s, box-shadow 0.15s;
    margin-bottom:24px;
}

.pixel-card--sm {
    padding: 10px 12px;
    border-width: 2px;
    box-shadow: var(--sf-shadow-sm), 3px 3px 0 var(--sf-primary);
}

/* --------------------------------------------------------------------------
   Dashboard
   -------------------------------------------------------------------------- */
.dashboard-controls {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 5px;
    flex-wrap: wrap;
    gap: 10px;
    margin-left: 5%;
}

.dashboard-title {
    font-family: var(--sf-font-pixel);
    font-size: 16px;
    color: var(--sf-accent);
}

.dashboard-controls__actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

/* Achievements strip */
.dashboard-achievements {
    margin-bottom: 20px;
    margin-top: 20px;
    padding-top : 20px;
}

.achievements-strip {
    display: flex;
    gap: 10px;
    overflow-x: auto;
    padding-bottom: 8px;
    align-items: center;
}

.achievement-mini {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    background: var(--sf-surface);
    border: 2px solid var(--sf-accent);
    border-radius: var(--sf-radius);
    padding: 8px;
    cursor: default;
    flex-shrink: 0;
    width: 128px;
    text-align: center;
    min-height: 60px;
}

.achievement-mini img {
    width: 15px;
    height: 15px;
    image-rendering: pixelated;
}

.achievement-mini span {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-accent);
    word-break: break-word;
    line-height: 1.4;
}

.achievements-strip__more {
    flex-shrink: 0;
    font-family: var(--sf-font-pixel);
    font-size: 7px;
    color: var(--sf-primary);
    text-decoration: none;
    padding: 8px;
}

.dashboard-reminders {
    margin-top: 20px;
    margin-bottom: 20px;
    padding-top: 20px;
    margin-left: 25px;
}

/* --------------------------------------------------------------------------
   Inline news feed (dashboard)
   -------------------------------------------------------------------------- */
.dashboard-news {
    margin-top: 20px;
    margin-bottom: 20px;
    padding-top: 20px;
    max-width: 820px;
}

.news-feed {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.news-feed__item {
    background: var(--sf-surface);
    border: 2px solid var(--sf-info);
    border-radius: var(--sf-radius);
    padding: 12px 14px;
}

.news-feed__title {
    font-family: var(--sf-font-body);
    font-size: 18px;
    color: var(--sf-info);
    text-decoration: none;
    display: block;
}

.news-feed__title:hover { text-decoration: underline; }

.news-feed__snippet {
    font-size: 16px;
    color: var(--sf-text-muted);
    margin-top: 4px;
    line-height: 1.4;
}

/* --------------------------------------------------------------------------
   Groups Container
   -------------------------------------------------------------------------- */
.groups-container {
    display: grid;
    /* minmax(0, 1fr), never a bare 1fr: a bare 1fr is minmax(auto, 1fr), so a
       track can never be narrower than its widest item's min-content. On a
       320-360px phone the group header (title + Edit/Del buttons) measured
       315px against 268px available, which widened the track, the group box and
       finally the whole document — and a phone then re-fits the page to that
       wider document, which is the "auto-zoom" that left fixed UI like #toyCrate
       (anchored to the layout viewport's right edge) sitting past the cards. */
    grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
    gap: 20px;
}

/* --------------------------------------------------------------------------
   Group Box
   -------------------------------------------------------------------------- */
.group-box {
    background: var(--sf-surface);
    border: 1px solid var(--group-color, var(--sf-primary));
    border-radius: var(--sf-radius-lg);
    box-shadow: 8px 3px 0 color-mix(in srgb, var(--group-color, var(--sf-primary)) 50%, black);
    overflow: visible;
    transition: box-shadow 0.15s;
    width: 95%;
    margin-left:5%;
    min-width: 0; /* a grid item defaults to min-width:auto; see .groups-container */
    border-left:3px solid var(--group-color);
    
    
}

.group-box__header {
    display: flex;
    flex-wrap: wrap; /* title and Edit/Del buttons stack on a narrow phone instead of widening the page */
    gap: 6px 8px;
    justify-content: space-between;
    align-items: center;
    padding: 10px 14px;
    border-bottom: 2px solid var(--group-color, var(--sf-border));
    border-left: 5px solid var(--group-color, var(--sf-primary));
    background: rgba(0,0,0,0.2);
    border-radius: var(--sf-radius-lg) var(--sf-radius-lg) 0 0;
    translate: -2px;
    margin-left: -1px;
}

.group-box__title-wrap {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.group-box__color-dot {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    flex-shrink: 0;
}

.group-box__title {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-text);
    overflow-wrap: anywhere; /* a long unbroken group name must wrap, not set the box's min width */
}

.group-box__controls {
    display: flex;
    gap: 4px;
}

.group-box__cards {
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-height: 60px;
    /* Pixel rounded edges. 
    source: https://pixelcorners.lukeb.co.uk/?radius=4&multiplier=4
    credit: Luke Bonaccorsi */ 
    clip-path: polygon(
    0px calc(100% - 8px),
    4px calc(100% - 8px),
    4px calc(100% - 4px),
    8px calc(100% - 4px),
    8px 100%,
    calc(100% - 8px) 100%,
    calc(100% - 8px) calc(100% - 4px),
    calc(100% - 4px) calc(100% - 4px),
    calc(100% - 4px) calc(100% - 8px),
    100% calc(100% - 8px),
    100% 8px,
    calc(100% - 4px) 8px,
    calc(100% - 4px) 4px,
    calc(100% - 8px) 4px,
    calc(100% - 8px) 0px,
    8px 0px,
    8px 4px,
    4px 4px,
    4px 8px,
    0px 8px
  );
    
}

.group-box__empty {
    font-family: var(--sf-font-body);
    font-size: 16px;
    color: var(--sf-text-muted);
    text-align: center;
    padding: 20px;
    border: 2px dashed var(--sf-border);
    border-radius: var(--sf-radius);
}

/* --------------------------------------------------------------------------
   Skill Cards
   -------------------------------------------------------------------------- */
.skill-card {
    background: var(--sf-surface-2);
    border: 2px solid var(--sf-border);
    border-radius: var(--sf-radius);
    padding: 10px;
    position: relative;
    cursor: default;
    transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
    user-select: none;
    cursor: help;
    box-shadow:3px 3px 0 color-mix(in srgb, var(--group-color, var(--sf-primary)) 50%, black)
}

.skill-card:hover {
    border-color: var(--sf-primary);
    box-shadow: 2px 2px 0 var(--sf-primary);
}

/* Drag state */
.skill-card--dragging {
    transform: rotate(2deg) scale(1.04);
    box-shadow: 8px 8px 20px rgba(0,0,0,0.5);
    border-color: var(--sf-accent);
    z-index: 9999;
    cursor: grabbing;
}

.skill-card--ghost {
    border: 2px dashed var(--sf-accent);
    background: rgba(230,159,0,0.07);
    border-radius: var(--sf-radius);
    opacity: 1 !important;
}

.skill-card__drag-handle {
    position: absolute;
    top: 6px;
    left: 6px;
    padding: 20px;
    color: var(--sf-text-muted);
    cursor: grab;
    line-height: 1;
    opacity: 0.35;
    transition: opacity 0.15s;
    /* Ensure it doesn't overlay action buttons */
    z-index: 1;
    pointer-events: all;
    user-select: none;
    -webkit-user-select: none;
}

.skill-card__drag-handle:hover,
.skill-card:hover .skill-card__drag-handle {
    opacity: 1;
}

.skill-card__drag-handle:active {
    cursor: grabbing;
}

.skill-card__header {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: 18px;
    margin-bottom: 8px;
}

.skill-card__icon img {
    width: 32px;
    height: 32px;
    image-rendering: pixelated;
    border-radius: var(--sf-radius-sm);
}

.skill-card__name {
    font-family: var(--sf-font-pixel);
    font-size: 11px;
    color: var(--sf-text);
    word-break: break-word;
}

.skill-card__schedule {
    font-family: var(--sf-font-body);
    font-size: 16px;
    color: var(--sf-accent);
}

.skill-card__timer {
    text-align: center;
    padding: 4px 0;
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-text-muted);
}

.skill-card__timer--active {
    color: var(--sf-success);
    animation: timerPulse 1s ease-in-out infinite alternate;
}

@keyframes timerPulse {
    from { opacity: 0.7; }
    to   { opacity: 1.0; }
}

.skill-card__actions {
    display: flex;
    gap: 5px;
    flex-wrap: wrap;
    margin-top: 6px;
}

.skill-card__streak{
    padding-top:12px;
}

/* --------------------------------------------------------------------------
   Reminder Popups
   -------------------------------------------------------------------------- */
.reminder-container {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 2000;
    display: flex;
    flex-direction: column-reverse;
    gap: 10px;
    max-width: 320px;
    pointer-events: none;
}

.reminder-popup {
    background: var(--sf-surface);
    border: 3px solid var(--sf-warning);
    border-radius: var(--sf-radius-lg);
    padding: 14px;
    box-shadow: var(--sf-shadow);
    display: flex;
    gap: 10px;
    align-items: flex-start;
    pointer-events: all;
    animation: reminderSlideIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
    position: relative;
    overflow: hidden;
}

@keyframes reminderSlideIn {
    from { transform: translateX(120%); opacity: 0; }
    to   { transform: translateX(0);    opacity: 1; }
}

.reminder-popup__icon { font-size: 24px; flex-shrink: 0; }

.reminder-popup__skill {
    font-family: var(--sf-font-pixel);
    font-size: 7px;
    color: var(--sf-warning);
    margin-bottom: 4px;
}

.reminder-popup__message {
    font-family: var(--sf-font-body);
    font-size: 16px;
    color: var(--sf-text);
}

.reminder-popup__body {
    flex: 1;
    min-width: 0;
}

.reminder-dismiss {
    float: right;
    margin-left: 10px;
    margin-bottom: 4px;
}

.reminder-popup__progress {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: rgba(255,255,255,0.1);
}

.reminder-popup__progress-bar {
    height: 100%;
    background: var(--sf-warning);
    width: 100%;
    animation: reminderCountdown 60s linear forwards;
}

@keyframes reminderCountdown {
    from { width: 100%; }
    to   { width: 0%; }
}

.reminder-popup--dismissing {
    transition: transform 0.3s ease, opacity 0.3s ease;
    transform: translateX(140%);
    opacity: 0;
}

/* --------------------------------------------------------------------------
   News Dialog
   -------------------------------------------------------------------------- */
.news-dialog {
    position: fixed;
    inset: 0;
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0,0,0,0.7);
    backdrop-filter: blur(4px);
    padding: 20px;
}

.news-dialog[hidden] { display: none; }

.news-dialog__inner {
    background: var(--sf-surface);
    border: 3px solid var(--sf-info);
    border-radius: var(--sf-radius-lg);
    max-width: 540px;
    width: 100%;
    box-shadow: 6px 6px 0 color-mix(in srgb, var(--sf-info) 40%, black);
    overflow: hidden;
    animation: modalSlideIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.news-dialog__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 14px 16px;
    background: rgba(86, 180, 233, 0.1);
    border-bottom: 2px solid var(--sf-info);
}

.news-dialog__title {
    font-family: var(--sf-font-pixel);
    font-size: 9px;
    color: var(--sf-info);
}

.news-dialog__list {
    list-style: none;
    padding: 12px 16px;
    max-height: 320px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.news-dialog__list li a {
    font-family: var(--sf-font-body);
    font-size: 18px;
    color: var(--sf-info);
    text-decoration: none;
}

.news-dialog__list li a:hover { text-decoration: underline; }

.news-dialog__list li p {
    font-size: 16px;
    color: var(--sf-text-muted);
    margin-top: 2px;
}

.news-dialog__footer {
    padding: 10px 16px;
    border-top: 2px solid var(--sf-border);
    display: flex;
    justify-content: flex-end;
}

/* --------------------------------------------------------------------------
   Generic Modal
   -------------------------------------------------------------------------- */
.sf-modal {
    position: fixed;
    inset: 0;
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}

.sf-modal[hidden] { display: none; }

.sf-modal__overlay {
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,0.75);
    cursor: pointer;
}

.sf-modal__box {
    position: relative;
    z-index: 1;
    background: var(--sf-surface);
    border: 3px solid var(--sf-primary);
    border-radius: var(--sf-radius-lg);
    max-width: 520px;
    width: 100%;
    box-shadow: 6px 6px 0 color-mix(in srgb, var(--sf-primary) 40%, black);
    animation: modalSlideIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes modalSlideIn {
    from { transform: translateY(-40px) scale(0.95); opacity: 0; }
    to   { transform: translateY(0)     scale(1);    opacity: 1; }
}

.sf-modal__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 14px 16px;
    border-bottom: 2px solid var(--sf-border);
}

.sf-modal__title {
    font-family: var(--sf-font-pixel);
    font-size: 9px;
    color: var(--sf-accent);
}

.sf-modal__body  { padding: 16px; }
.sf-modal__footer {
    padding: 12px 16px;
    border-top: 2px solid var(--sf-border);
    display: flex;
    gap: 8px;
    justify-content: flex-end;
}

/* --------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */
.form-group {
    margin-bottom: 16px;
}

.form-group--inline {
    display: flex;
    align-items: center;
    gap: 10px;
}

.form-label {
    display: block;
    font-family: var(--sf-font-pixel);
    font-size: 16px;
    color: var(--sf-text-muted);
    margin-bottom: 6px;
}

.form-control {
    width: 100%;
    padding: 8px 12px;
    background: var(--sf-bg);
    border: 2px solid var(--sf-border);
    border-radius: var(--sf-radius);
    color: var(--sf-text);
    font-family: var(--sf-font-body);
    font-size: 16px;              /* ≥16px prevents iOS auto-zoom on focus */
    transition: border-color 0.15s;
    -webkit-appearance: none;     /* remove iOS inner shadow / rounded corners */
    appearance: none;
}

.form-control:focus {
    outline: none;
    border-color: var(--sf-primary);
    box-shadow: 0 0 0 3px rgba(0, 114, 178, 0.2);
}

.form-control--inline {
    display: inline-block;
    width: auto;
    padding: 4px 8px;
    font-size: 16px;
}

.form-control--color {
    padding: 2px;
    height: 36px;
    width: 60px;
    cursor: pointer;
}

.form-hint {
    font-size: 16px;
    color: var(--sf-text-muted);
    margin-top: 4px;
    display: block;
}

.form-fieldset {
    border: 2px solid var(--sf-border);
    border-radius: var(--sf-radius);
    padding: 14px;
    margin-bottom: 16px;
}

.form-legend {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-accent);
    padding: 0 6px;
}

.radio-group, .checkbox-group {
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
    margin-top: 6px;
}

.radio-label, .checkbox-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--sf-font-body);
    font-size: 16px;
    cursor: pointer;
}

.form-divider {
    border: none;
    border-top: 2px solid var(--sf-border);
    margin: 16px 0;
}

/* Day-of-week picker */
.day-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 6px;
}

.day-picker__btn {
    position: relative;
    cursor: pointer;
}

.day-picker__btn input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.day-picker__btn span {
    display: inline-block;
    padding: 5px 10px;
    border: 2px solid var(--sf-border);
    border-radius: var(--sf-radius);
    font-family: var(--sf-font-pixel);
    font-size: 16px;
    color: var(--sf-text-muted);
    background: var(--sf-surface);
    transition: border-color 0.12s, background 0.12s, color 0.12s;
    user-select: none;
}

.day-picker__btn input[type="checkbox"]:checked + span {
    border-color: var(--sf-accent);
    background: color-mix(in srgb, var(--sf-accent) 18%, var(--sf-surface));
    color: var(--sf-accent);
}

.day-picker__btn:hover span {
    border-color: var(--sf-accent);
}

.skill-stat__value--days {
    font-family: var(--sf-font-body);
    font-size: 16px;
    letter-spacing: 0.02em;
}

.form-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 20px;
}

.required { color: var(--sf-danger); }

/* Group select row */
.group-select-row {
    display: flex;
    gap: 8px;
    align-items: center;
}

.group-select-row select { flex: 1; }

/* Inline group form */
.inline-group-form {
    margin-top: 8px;
}

/* Icon picker */
.icon-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.icon-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    cursor: pointer;
    border: 2px solid transparent;
    border-radius: var(--sf-radius);
    padding: 6px;
    transition: border-color 0.15s, background 0.15s;
    width: 65px;
    height: 65px;
}

.icon-option input[type="radio"] { display: none; }

.icon-option:hover,
.icon-option:has(input:checked) {
    border-color: var(--sf-primary);
    background: rgba(0, 114, 178, 0.1);
}

.icon-option img {
    width: 36px;
    height: 36px;
    image-rendering: pixelated;
}

.icon-option span {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-text-muted);
}

/* Color picker */
.color-picker-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.color-swatches { display: flex; gap: 6px; flex-wrap: wrap; }

.color-swatch {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 3px solid transparent;
    cursor: pointer;
    transition: transform 0.15s, border-color 0.15s;
}

.color-swatch:hover {
    transform: scale(1.2);
    border-color: #fff;
}

/* --------------------------------------------------------------------------
   Skill Detail
   -------------------------------------------------------------------------- */
.skill-detail__header {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

.skill-detail__title {
    font-family: var(--sf-font-pixel);
    font-size: 11px;
    color: var(--sf-accent);
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
}

.skill-detail__icon {
    width: 36px;
    height: 36px;
    image-rendering: pixelated;
}

.skill-detail__desc {
    font-size: 18px;
    color: var(--sf-text-muted);
    margin-bottom: 16px;
}

.skill-stats {
    display: flex;
    gap: 12px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}

.skill-stat {
    flex: 1;
    min-width: 120px;
    text-align: center;
}

.skill-stat__value {
    display: block;
    font-family: var(--sf-font-pixel);
    font-size: 11px;
    color: var(--sf-accent);
    margin-bottom: 4px;
}

.skill-stat__label {
    font-family: var(--sf-font-body);
    font-size: 16px;
    color: var(--sf-text-muted);
}

.skill-detail__practice {
    margin-bottom: 20px;
}

.practice-timer {
    text-align: center;
    padding: 10px;
    font-family: var(--sf-font-pixel);
    font-size: 10px;
    color: var(--sf-text-muted);
    margin-bottom: 10px;
}

.timer-display--lg { font-size: 16px; }

.practice-controls {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

.upload-section {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.upload-section[hidden] { display: none; }

.photo-upload-input {
    display: none;
}

.session-notes { margin-top: 10px; }

.uploaded-photos {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 10px;
}

.session-photo-thumb {
    width: 64px;
    height: 64px;
    object-fit: cover;
    border-radius: var(--sf-radius-sm);
    border: 2px solid var(--sf-primary);
    cursor: pointer;
    transition: transform 0.15s;
    /* Ensure the thumb itself is a tap target on touch screens */
    touch-action: manipulation;
}

.session-photo-thumb:hover { transform: scale(1.1); }

/* Inline "add photo" control inside the history table */
.session-photos-add {
    display: inline-flex;
    align-items: center;
    margin-top: 4px;
}

.session-photos { display: flex; flex-wrap: wrap; gap: 6px; }

/* Chart */
.skill-chart { margin-bottom: 20px; }

.skill-chart__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 8px;
}

.skill-chart__header h3 { margin: 0; }

.skill-chart__heading { min-width: 0; }

.skill-chart__subtitle {
    margin: 2px 0 0;
    font-size: 9px;
    color: var(--sf-text-muted);
}

.pixel-select {
    font: inherit;
    font-size: 10px;
    padding: 4px 8px;
    background: var(--sf-bg);
    color: var(--sf-text);
    border: 2px solid var(--sf-primary);
    border-radius: var(--sf-radius-sm);
}

.chart-wrapper {
    position: relative;
    height: 200px;
}

.chart-status {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    color: var(--sf-text-muted);
    font-size: 11px;
    pointer-events: none;
}

.skill-chart-hint { margin-bottom: 20px; }
.skill-chart-hint p { color: var(--sf-text-muted); margin: 0; }

/* Share buttons */
.skill-share { margin-bottom: 20px; }

.share-buttons {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 12px;
}

.share-btn--bluesky  { background: #0085ff; border-color: #0060bb; color: #fff; }
.share-btn--linkedin { background: #0077b5; border-color: #005580; color: #fff; }
.share-btn--facebook { background: #1877f2; border-color: #0d5bb0; color: #fff; }
.share-btn--instagram {
    background: linear-gradient(135deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888);
    border-color: #9b1369;
    color: #fff;
}

/* --------------------------------------------------------------------------
   Practice History Table
   -------------------------------------------------------------------------- */
.history-table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--sf-font-body);
    font-size: 16px;
}

.history-table th {
    font-family: var(--sf-font-pixel);
    font-size: 6px;
    color: var(--sf-text-muted);
    padding: 8px;
    border-bottom: 2px solid var(--sf-border);
    text-align: left;
}

.history-table th.history-col-action {
    width: 1%;
    white-space: nowrap;
}

.history-table td {
    padding: 8px;
    border-bottom: 1px solid var(--sf-border);
    vertical-align: top;
}

.history-table tr:hover td { background: rgba(255,255,255,0.03); }

/* Date cell — distinct color to separate timestamp from content */
.history-table td.session-date {
    color: var(--sf-accent);
    font-family: var(--sf-font-pixel);
    font-size: 9px;
    white-space: nowrap;
    vertical-align: middle;
}

/* Action cells — shrink-wrap the buttons */
.history-table td.session-edit-cell,
.history-table td.session-delete-cell {
    width: 1%;
    white-space: nowrap;
    vertical-align: middle;
    padding: 6px 4px;
}

/* Edit / delete modal */
.practice-edit-modal {
    border: none;
    background: transparent;
    padding: 0;
    max-width: 420px;
    width: 92vw;
}

.practice-edit-modal::backdrop {
    background: rgba(0, 0, 0, 0.6);
}

.practice-edit-modal__inner {
    padding: 24px;
}

.practice-edit-modal__title {
    margin: 0 0 16px;
    font-family: var(--sf-font-pixel);
    font-size: 10px;
}

.practice-edit-modal__actions {
    display: flex;
    gap: 10px;
    margin-top: 16px;
}

/* --------------------------------------------------------------------------
   Achievements
   -------------------------------------------------------------------------- */
.achievements-page { max-width: 1100px; margin: 0 auto; }

.achievements-summary {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 24px;
    font-family: var(--sf-font-pixel);
    font-size: 8px;
}

.achievement-category { margin-bottom: 28px; }

.achievement-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 12px;
    margin-top: 12px;
    margin-bottom: 12px;
}

.achievement-badge {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 10px;
    border-radius: var(--sf-radius);
    border: 2px solid var(--sf-border);
    text-align: center;
    transition: transform 0.15s, border-color 0.15s;
    cursor: default;
}

.achievement-badge--earned {
    border-color: var(--sf-accent);
    background: rgba(230,159,0,0.06);
}

.achievement-badge--locked {
    filter: grayscale(0.8);
    opacity: 0.5;
}

.achievement-badge:hover { transform: translateY(-3px); }

.achievement-badge__img-wrap {
    position: relative;
    width: 56px;
    height: 56px;
}

.achievement-badge__img {
    width: 56px;
    height: 56px;
    image-rendering: pixelated;
}

.achievement-badge__lock {
    position: absolute;
    bottom: 0;
    right: 0;
    font-size: 16px;
}

.achievement-badge__name {
    font-family: var(--sf-font-pixel);
    font-size: 5px;
    color: var(--sf-text);
    word-break: break-word;
}

.achievement-badge__date {
    font-size: 16px;
    color: var(--sf-text-muted);
}

.achievement-badge__xp {
    font-family: var(--sf-font-pixel);
    font-size: 6px;
    color: var(--sf-success);
}

/* --------------------------------------------------------------------------
   Drew Inventory (Stage 16h, read-only — Drew is self-playing, never
   player-controlled, so this page has no forms/equip-swap interactivity)
   -------------------------------------------------------------------------- */
.drew-inventory-page { max-width: 1100px; margin: 0 auto; }

.drew-inventory-summary {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    align-items: center;
    margin-bottom: 24px;
    font-family: var(--sf-font-pixel);
    font-size: 8px;
}

.drew-inventory-empty {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-text-muted);
}

.drew-inventory-category { margin-bottom: 28px; }

.drew-inventory-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 12px;
    margin-top: 12px;
    margin-bottom: 12px;
}

.drew-inventory-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 10px;
    border-radius: var(--sf-radius);
    border: 2px solid var(--sf-border);
    text-align: center;
    cursor: default;
}

.drew-inventory-item--equipped {
    border-color: var(--sf-accent);
    background: rgba(230,159,0,0.06);
}

.drew-inventory-item__img {
    width: 32px;
    height: 32px;
    image-rendering: pixelated;
}

.drew-inventory-item__name {
    font-family: var(--sf-font-pixel);
    font-size: 5px;
    color: var(--sf-text);
    word-break: break-word;
}

.drew-inventory-item__equipped-tag {
    font-size: 16px;
    color: var(--sf-accent);
}

.drew-inventory-item__stats {
    font-size: 16px;
    color: var(--sf-text-muted);
}

/* --------------------------------------------------------------------------
   Profile Page
   -------------------------------------------------------------------------- */
.profile-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 20px;
}

.card-title {
    font-family: var(--sf-font-pixel);
    font-size: 9px;
    color: var(--sf-accent);
    margin-bottom: 16px;
}

.card-subtitle {
    font-family: var(--sf-font-pixel);
    font-size: 7px;
    color: var(--sf-text-muted);
    margin-bottom: 12px;
}

.profile-xp { margin-top: 16px; }

.profile-xp-banner {
    margin-bottom: 20px;
}

.profile-bounce-score {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid rgba(255,255,255,0.08);
    font-size: 10px;
}
.profile-bounce-score__label { color: var(--text-muted, #888); }
.profile-bounce-score__value {
    font-family: var(--sf-font-pixel, monospace);
    color: #f1c40f;
    font-size: 11px;
}

.profile-xp-header {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

.profile-level-badge {
    font-family: var(--sf-font-pixel);
    font-size: 16px;
    color: var(--sf-accent);
}

.profile-tier-label {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-primary);
}

.profile-tier-name {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-text-muted);
}

.profile-xp-track {
    height: 16px;
    margin-bottom: 8px;
}

.profile-xp-numbers {
    font-family: var(--sf-font-pixel);
    font-size: 7px;
    color: var(--sf-text-muted);
    display: flex;
    justify-content: space-between;
}

/* --------------------------------------------------------------------------
   Auth Pages
   -------------------------------------------------------------------------- */
.auth-body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100svh;           /* shrinks with iOS address bar */
    justify-content: center;
    align-items: center;
    overflow-y: auto;             /* scroll when the keyboard pushes content up */
    padding: 20px;
    /* respect notch / home-indicator on iPhone */
    padding-top:    max(20px, env(safe-area-inset-top));
    padding-bottom: max(20px, env(safe-area-inset-bottom));
    padding-left:   max(20px, env(safe-area-inset-left));
    padding-right:  max(20px, env(safe-area-inset-right));
}

.auth-landscape {
    position: fixed;
    inset: 0;
    z-index: 0;
    overflow: hidden;
}

.auth-landscape .landscape-layer {
    width: 100%;
    height: 100%;
    object-fit: cover;
    image-rendering: pixelated;
    opacity: 0.4;
}

.auth-container {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 420px;
}

.auth-logo {
    text-align: center;
    margin-bottom: 20px;
}

.auth-logo .site-title { font-size: 16px; }
.auth-logo .site-tagline { font-size: 18px; }

.auth-card {
    animation: modalSlideIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.auth-card__title {
    font-family: var(--sf-font-pixel);
    font-size: 9px;
    color: var(--sf-accent);
    margin-bottom: 16px;
    text-align: center;
}

.auth-card__switch {
    text-align: center;
    margin-top: 12px;
    font-size: 16px;
    color: var(--sf-text-muted);
}

.auth-card__switch a {
    color: var(--sf-primary);
    text-decoration: none;
    display: inline-block;
    padding: 6px 4px;             /* expand touch target to ~44px tall */
}

/* --------------------------------------------------------------------------
   Miscellaneous
   -------------------------------------------------------------------------- */
.page-title {
    font-family: var(--sf-font-pixel);
    font-size: 16px;
    color: var(--sf-accent);
    margin-bottom: 20px;
}

.section-title {
    font-family: var(--sf-font-pixel);
    font-size: 8px;
    color: var(--sf-primary);
    margin-bottom: 12px;
    padding: 16px 16px 16px 6px;
}

.empty-state {
    font-family: var(--sf-font-body);
    font-size: 18px;
    color: var(--sf-text-muted);
    text-align: center;
    padding: 20px;
}

.empty-dashboard {
    text-align: center;
    padding: 40px;
    max-width: 400px;
    margin: 0 auto;
}

.empty-dashboard__icon { font-size: 60px; margin-bottom: 12px; }
.empty-dashboard h3 {
    font-family: var(--sf-font-pixel);
    font-size: 10px;
    color: var(--sf-accent);
    margin-bottom: 8px;
}
.empty-dashboard p {
    font-size: 18px;
    color: var(--sf-text-muted);
    margin-bottom: 16px;
}

/* Page form */
.page-form {
    max-width: 680px;
    margin: 0 auto;
}

.page-form__title {
    font-family: var(--sf-font-pixel);
    font-size: 10px;
    color: var(--sf-accent);
    margin-bottom: 20px;
}

/* Skills index */
.skills-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
}
/* Ad box — shown at the bottom of each page when ads are enabled */
.ad-box {
    margin: 40px auto 0;
    max-width: 960px;
    padding: 0 16px 40px;
    text-align: center;
}

.ad-box__label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--sf-text-muted, #888);
    margin-bottom: 6px;
    opacity: 0.6;
}




/* ---------------------------------------------------------------------------
   Drew's GOAP priority sliders (2026-08-26)

   Mirrors the journal's own positioning rules deliberately -- same fixed
   anchoring under the header, same z-index band above every moonInterior
   canvas layer, same expanded-header offset. Two panels that sit in the same
   place should get there the same way, or one of them drifts the next time the
   header height changes.
   --------------------------------------------------------------------------- */
/* THE OPENER IS ON THE CANVAS (2026-08-26), painted by DrewHUD beside the Art
   button, so it has no rules here at all -- see PRIORITIES_BUTTON_LABEL in
   public/js/utils/DrewHUD.js. This is deliberately NOT a hidden DOM button
   kept for old markup: there is no markup left to hide.

   THE PANEL FLOATS OVER DREW'S DUNGEON, not under it. Per explicit user
   direction, "display the sliders on top of drew's dungeon, instead of below
   it" -- so its `top` is measured from the xp bar rather than from the bottom
   of the header, which is what put it below the scene before. It deliberately
   does NOT track --sf-header-h: the panel opens against the TOP of the canvas
   and the header growing taller underneath it should not move it.

   `max-height` plus scrolling matters here in a way it did not below the
   header: a short header leaves less room than the sliders want, and a panel
   that overflowed would put its close button off-screen with the game paused
   behind it. */
#drewGoapPanel {
    position: fixed;
    left: 12px;
    top: calc(var(--sf-xp-bar-h) + 26px);
    /* ABOVE THE SPEECH-BUBBLE CANVAS (2026-09-01), per explicit user direction
       "ensure the drew: options popup displays on top of speech bubbles."

       This was 1121, which is above the HUD's own canvases
       (#moonInteriorCanvasA/B, 1090..1110) but BELOW
       #moonInteriorDialogCanvas (3011) -- the separate canvas every interior
       scene routes its speech bubbles to so they composite over the logo. So a
       bubble painted over this panel no matter which drew first, and no
       reordering inside the frame could have fixed it: the two are different
       stacking contexts, and only the z-index decides.

       3050 rather than an arbitrary larger number, because #drewJournalPanel
       above already made exactly this fix for exactly this reason and its
       comment says so. Both are modal-ish panels over the scene -- opening
       this one sets timeScale = 0 -- and both must still sit UNDER .site-nav
       at 3100, so the page's own navigation is never buried by a game panel. */
    z-index: 3050;
    display: none;
    width: 320px;
    max-width: calc(100vw - 24px);
    /* TALLER, per explicit user direction 2026-08-31: "Let the popup be taller
       than it is now." It gained a story row at the top, and the old ceiling
       left the drives and the prompt toggles below it needing a scroll on a
       short viewport -- which put the thing the panel is named for out of
       sight the moment anything was added above it.

       Still a calc against the viewport rather than a fixed height: the panel
       is anchored under the xp bar, so a hard number would run off the bottom
       of a small screen. This spends the margin (40px -> 16px) rather than the
       relationship to the viewport. */
    max-height: calc(100vh - var(--sf-xp-bar-h) - 16px);
    overflow-y: auto;
    padding: 10px 12px 12px;
    background: var(--sf-surface);
    border: 1px solid color-mix(in srgb, var(--sf-primary) 40%, transparent);
    border-radius: 8px;
    color: var(--sf-text);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
}
#drewGoapPanel.drew-goap-panel--open {
    display: block;
}
/* The manual story advance, at the top of the panel. An ACTION rather than a
   setting, which is why it sits above the preset picker and is separated from
   it by a rule. */
#drewGoapStoryRow {
    margin-bottom: 10px;
    padding-bottom: 10px;
    border-bottom: 1px solid color-mix(in srgb, var(--sf-primary) 24%, transparent);
}
#drewGoapAdvanceBeatBtn {
    width: 100%;
    padding: 7px 10px;
    font: inherit;
    color: var(--sf-text);
    background: color-mix(in srgb, var(--sf-primary) 22%, transparent);
    border: 1px solid color-mix(in srgb, var(--sf-primary) 50%, transparent);
    border-radius: 6px;
    cursor: pointer;
}
#drewGoapAdvanceBeatBtn:hover:not(.is-locked) {
    background: color-mix(in srgb, var(--sf-primary) 34%, transparent);
}
/* LOCKED IS A CLASS, NOT `:disabled`. The button must stay clickable while it
   is unavailable, because clicking it is how a player asks how long is left --
   a disabled button fires no click event at all and would simply do nothing,
   which reads as broken rather than as "not yet". */
#drewGoapAdvanceBeatBtn.is-locked {
    opacity: 0.5;
    cursor: default;
}
/* While the countdown is showing, the button IS the countdown -- so it must not
   also look pressable. */
#drewGoapAdvanceBeatBtn.is-counting {
    opacity: 0.75;
    cursor: default;
    font-variant-numeric: tabular-nums;
}
#drewGoapPanelHeader {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}
#drewGoapPanelHeader h2 {
    font-size: 11px;
    margin: 0;
}
#drewGoapCloseBtn {
    font: inherit;
    font-size: 18px;
    line-height: 1;
    background: none;
    border: none;
    color: var(--sf-text-muted);
    cursor: pointer;
}
#drewGoapPresetRow {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
    font-size: 10px;
}
#drewGoapPresetRow select {
    font: inherit;
    font-size: 10px;
    flex: 1;
    padding: 3px 6px;
    background: var(--sf-bg);
    color: var(--sf-text);
    border: 1px solid color-mix(in srgb, var(--sf-primary) 30%, transparent);
    border-radius: 4px;
}
/* VERTICAL sliders, per the direction. `writing-mode: vertical-lr` with a
   direction flip is the modern way and the appearance/orient pair is the
   fallback that still carries Firefox and older WebKit -- all three are set
   because a range input that silently renders horizontally would make the
   panel look like an ordinary settings list rather than a mixing desk. */
#drewGoapSliders {
    display: flex;
    justify-content: space-around;
    align-items: flex-end;
    gap: 6px;
}
.drew-goap-slider {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    flex: 1;
}
.drew-goap-slider input[type="range"] {
    writing-mode: vertical-lr;
    direction: rtl;
    -webkit-appearance: slider-vertical;
    appearance: slider-vertical;
    width: 22px;
    height: 130px;
    accent-color: var(--sf-primary);
    cursor: pointer;
}
/* SMALLER, SO THE LABEL FITS ITS COLUMN (2026-09-11), per explicit user
   direction: "make the fonts in drew's options slightly smaller so the slider
   text fits in the popup."

   The panel is 320px wide and the drives went from four to five when
   `curiosity` was added, so each column is roughly 54px — and `Curiosity` is
   the longest label there has ever been. At 0.72rem it wrapped onto a second
   line, which pushed the whole row taller and read as a broken layout rather
   than as a long word.

   `nowrap` is the other half and matters more than the size: a future sixth
   drive, or a longer label, would wrap again at any font size, and the failure
   would look identical. With it, a label that genuinely cannot fit is clipped
   at the column edge — visibly wrong in a way somebody will fix, rather than
   silently re-flowing the panel. */
.drew-goap-slider__label {
    font-size: 7px;
    color: var(--sf-text-muted);
    text-align: center;
    white-space: nowrap;
}
.drew-goap-slider__value {
    font-size: 7px;
    color: var(--sf-primary);
    font-variant-numeric: tabular-nums;
}
/* The "ask me about" toggles, built from DrewChoicePrompt.PROMPT_KINDS. This
   is the only way to switch a question back on after "Drew always decides". */
#drewPromptChoices {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-top: 0.6rem;
}
.drew-prompt-choices__heading {
    font-size: 8px;
    color: var(--sf-text-muted);
    margin: 0;
}
.drew-prompt-choices__row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 8px;
    cursor: pointer;
}
#drewGoapHint {
    margin: 10px 0 0;
    font-size: 8px;
    color: var(--sf-text-muted);
}

/* Site-wide "Loading" scanner (public/js/utils/LoadingScanner.js). Visible
   by default from first paint — no JS required — so it covers the window
   before the page's own deferred script chain has run at all. z-index above
   every other layer in the file (max elsewhere is 9999) since it must sit
   over any modal or scene while the site is still loading. */
#siteLoadingScanner {
    position: fixed;
    /* Centered in the header box, not pinned to the viewport's top edge —
       var(--sf-header-h-current) tracks the header's minimized/expanded/
       fullscreen state (see the html.header--expanded/--fullscreen rules
       above), so this stays centered across all three without JS. */
    top: calc(var(--sf-xp-bar-h) + var(--sf-header-h-current) / 2);
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 10000;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    pointer-events: none;
}

#siteLoadingScanner.site-loading-scanner-hidden {
    display: none;
}

#siteLoadingScanner__label {
    font-family: "Press Start 2P", monospace;
    font-size: 14px;
    color: #ff2a2a;
    text-shadow: 0 0 4px #000;
}

/* Auto-linked URLs and user free-text: long unbroken strings wrap instead of
   widening the page. See linkify() in config/config.php / SkillzForge.linkify. */
.auto-link,
.skill-detail__desc,
.inbox-detail__body,
.session-notes-cell,
.skill-topics-item-snippet,
.skill-topics-item-blurb,
.skill-topics-item-meta {
    overflow-wrap: anywhere;
    word-break: break-word;
}

.auto-link {
    color: var(--sf-accent);
    text-decoration: underline;
}
