/* ────────────────────────────────────────────────── */
/* AventusCMS CryoBlue Theme — Complete Stylesheet  */
/* ────────────────────────────────────────────────── */

/* Michroma, self-hosted rather than pulled from fonts.googleapis.com —
   see includes/fonts/michroma.css for the reasoning and the OFL licence. */
@import url('../../../includes/fonts/michroma.css');

:root {
    --av-bg: #050b10;
    --av-panel: rgba(8, 17, 24, 0.96);
    --av-accent: #56b8e8;
    --av-text: #e2edf3;
    --av-text-muted: #a7bac5;
    --av-text-dim: #728996;
    --good: #68cea1;
    --warn: #d8aa51;
    --bad: #eb6872;
    --bg-0: #050b10;
    --bg-1: #081118;
    --bg-2: #0c171f;
    --bg-3: #111f29;
    --bg-4: #182a35;
    --line-1: #1c303d;
    --line-2: #294352;
    --line-3: #3b5a6b;
    --shadow-md: 0 4px 24px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 14px 42px rgba(0, 0, 0, 0.55);
    --glow-accent: 0 0 10px rgba(86, 184, 232, 0.3);
    --transition-fast: 120ms ease;
    --transition-normal: 200ms ease;
    /* THE hover-lift knob for every button on the site — public pages
       and the admin panel both. Change this one value and .btn,
       .av-btn, the poll options, and every admin button (which reach
       it through --theme-hover-scale — see the contract block below)
       all move together; there is deliberately no second place where a
       button's hover scale is written as a literal. Was 1.02, which on
       a full-width button grew the box by ~2.5px and read as a jump
       rather than a response — the colour shift, glow and shadow
       already carry the hover, so this only has to add a hint of depth
       behind them. NOT used by non-button lifts (the hero play button,
       favourite stars, avatar tiles, drag ghosts): those are their own
       gestures at their own magnitudes and are not meant to track this. */
    --av-hover-scale: 1.01;

    /* Public contract for includes/css/admin.css: any theme may expose
       --theme-* custom properties so the admin panel's surfaces, text,
       and accent pick up the theme's own palette instead of the fixed
       default. Admin CSS references every one of these with a hard
       fallback (var(--theme-x, #hex)), so a theme that skips this stays
       fully functional — unlike reusing --av-accent/--bg-3/etc directly,
       a theme with no --theme-* block can never break admin, it just
       won't be color-matched. */
    --theme-accent: var(--av-accent);
    --theme-panel: var(--av-panel);
    --theme-bg-1: var(--bg-1);
    --theme-bg-2: var(--bg-2);
    --theme-bg-3: var(--bg-3);
    --theme-bg-4: var(--bg-4);
    --theme-line-1: var(--line-1);
    --theme-line-2: var(--line-2);
    --theme-line-3: var(--line-3);
    --theme-text: var(--av-text);
    --theme-text-muted: var(--av-text-muted);
    --theme-text-dim: var(--av-text-dim);
    --theme-input-bg: #09141b;
    --theme-input-focus-bg: #0b1820;
    --theme-dropdown-bg: #101d25;
    --theme-good: var(--good);
    --theme-warn: var(--warn);
    --theme-bad: var(--bad);
    --theme-hover-scale: var(--av-hover-scale);
}

* { box-sizing: border-box; }

/* Standard safeguard for every form control, site-wide (public pages,
   admin panel, any future block) — nothing with a fixed pixel width, a
   resize handle, or an unusually long value/placeholder can ever push
   wider than its own container and overflow the page horizontally. One
   rule here covers every input/select/textarea permanently, current and
   future, rather than needing a max-width added to each individual
   selector (.av-admin input[type=text], .av-dropdown-trigger, etc. —
   several of which already had it added directly as a first pass before
   this rule existed; harmless duplication, kept for clarity at each
   call site). */
input, select, textarea {
    max-width: 100%;
}

html {
    background-color: var(--av-bg);
    height: 100%;
}

/* Body is a 4-row grid: topbar -> navbar -> body-layout -> footer. The
   1fr on row 3 absorbs the slack, which pins the footer to the bottom of
   the viewport on a short page and lets it sit at the end of the document
   on a long one.

   THE FOOTER WAS position:fixed AND IS NOT ANY MORE, because it is a
   block REGION. A fixed element is out of flow, so the only thing keeping
   page content from scrolling underneath it was a padding-bottom here
   reserving "the footer's own height" — a constant, against an element
   whose height an admin sets by dragging blocks into it. Measured on this
   install: 60px reserved against a 695px footer, so ~635px of the account
   page was behind it. No constant can track that, which is why the
   reservation is deleted rather than retuned; two grid rows cannot
   overlap at any height. */
body {
    margin: 0;
    font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
    font-size: 13px;
    color: var(--av-text);
    background-color: var(--av-bg);
    background-image: radial-gradient(circle at 15% 15%, rgba(86, 184, 232, 0.09), transparent 45%), radial-gradient(circle at 88% 70%, rgba(126, 209, 255, 0.06), transparent 30%), radial-gradient(circle at 40% 95%, rgba(86, 184, 232, 0.07), transparent 40%), linear-gradient(180deg, #080a0f 0%, #0a0d13 100%);
    background-attachment: fixed;
    min-height: 100vh;
    display: grid;
    grid-template-rows: auto auto 1fr auto;
    grid-template-columns: minmax(0, 1fr);
}

a { color: var(--av-accent); text-decoration: none; }
a:hover { color: var(--av-accent); }

/* Every native heading tag, site-wide — admin page titles
   (.av-admin-topbar h1), .card-heading (an actual <h2>), standalone-page
   .av-card h1/h2 (login/register/installer), the static dashboard's
   .av-cryoblue-hero-copy h1, and .block-heading itself when the engine
   renders it as an <h3>. One rule here means a class-specific font-family
   never needs restating per heading type — only size/color/spacing do. */
h1, h2, h3, h4, h5, h6 {
    font-family: 'Michroma', sans-serif;
}

/* HEADER & TOPBAR */
.av-cryoblue-topbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 10px;
    background: #060810;
    border-bottom: 1px solid var(--line-2);
    font-size: 12px;
    /* position:relative so .av-cryoblue-topbar-announcement can be
       absolutely centered against THIS row specifically (see that
       rule's own comment) rather than the viewport or some other
       ancestor. min-height compensates for the announcement box now
       being position:absolute — pulled entirely out of normal flex
       flow, so it no longer contributes to this row's height the way
       it did before. Without a min-height here, the row's height would
       be determined solely by .av-cryoblue-topbar-social (the only
       flex-flow child left when there's an announcement to show),
       which could make the bar visibly shrink/jump shorter compared to
       before, or clip the absolutely-positioned announcement box if it
       ends up taller than that remaining content. 34px is roughly the
       announcement box's own rendered height (5px+5px padding, ~1px+1px
       border, ~22px line content) plus a little slack. */
    min-height: 34px;
    position: relative;
}

/* Admin panel: offset topbar/navbar for the collapsed rail only (84px).
   The expanded rail is position:fixed; z-index:5 and overlays as a fixed
   layer — nothing reflows when it opens. Full-width backgrounds intact. */
body.av-admin-context .av-cryoblue-topbar,
body.av-admin-context .av-cryoblue-navbar {
    padding-left: 84px; /* 68px collapsed rail + 16px breathing room */
}


/* Styled to look like an input box embedded directly in the topbar —
   same visual language as the theme's real text inputs (#09141b
   background, var(--line-2) border, 4px radius — see the shared
   input[type="text"]/etc. rule further down this file) but scaled down
   for a compact inline pill rather than a full-width form field: less
   padding, no bottom margin, and inline-flex sizing instead of
   width:100%. Purely decorative — nothing inside is actually an
   editable form control, the announcement text itself is still just
   plain text/links, this only borrows the LOOK of an input. */
.av-cryoblue-topbar-announcement {
    /* Absolutely centered against .av-cryoblue-topbar itself (that
       rule's own position:relative makes this possible) rather than
       flex-grow'd into whatever leftover space sits between the label
       and the social icons — flex-grow can only control how much of
       the ROW'S LEFTOVER space an item claims, it can't center an item
       independent of its siblings' own widths, which is what's
       actually being asked for here. left:50% + translateX(-50%) is
       the standard centering technique for an absolutely positioned
       element of unknown/variable width.

       No explicit width — an absolutely positioned element with no
       width set shrinks to fit its own content by default, so this box
       hugs the label + the 320px text window + the read-more link
       tightly, with no dead space between the marquee text and "Read
       More" the way a fixed percentage width previously left. max-
       width:1600px is kept purely as a safety ceiling matching .av-
       cryoblue-body-layout's own cap (this file, further down) — it
       won't normally be reached in practice since the inner .av-
       cryoblue-topbar-announcement-text window already caps visible
       text at 320px regardless, but it prevents this box from ever
       overflowing the viewport on an extreme edge case (e.g. a very
       long "Read More" translation string). z-index:1 keeps it above
       the topbar's own background. */
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    max-width: 1600px;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--av-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    background: #09141b;
    border: 1px solid var(--line-2);
    border-radius: 4px;
    padding: 5px 10px;
    transition: border-color var(--transition-fast);
    box-sizing: border-box;
}
.av-cryoblue-topbar-announcement:hover {
    border-color: var(--line-3);
}
.av-cryoblue-topbar-label { color: var(--warn); font-weight: 700; flex: none; }
/* Horizontal scroll, not ellipsis truncation — stays on one line
   (white-space:nowrap) and lets the user scroll/drag sideways to read
   the rest once the box has shrunk as far as it can (see
   .av-cryoblue-topbar-announcement's own min-width:0 comment for why
   it can shrink at all). overflow-x:auto shows a scrollbar only when
   content actually overflows; the thin custom scrollbar styling
   further down this file (::-webkit-scrollbar) already applies here
   automatically since it's a global rule, not scoped to a specific
   element. min-width:0 is still needed on this inner span too — flex
   children default to a min-width based on their content's natural
   size regardless of the PARENT's own min-width:0, so both have to be
   set for the announcement text to actually be allowed to overflow
   into a scrollable area instead of just re-expanding its container. */
/* Auto-scrolling marquee, not a user-dragged scrollbar — this outer
   element is purely a clipping viewport (overflow:hidden, no scrollbar
   of its own); the actual scrolling motion lives on
   .av-cryoblue-topbar-announcement-track just below, via a looping CSS
   animation that JS (header.php) only turns on when the text
   genuinely doesn't fit (adds .is-scrolling to the track — see that
   script's own comment for why detection needs real measurement, not
   a guess). Short text that already fits stays perfectly still, no
   class added, no animation running. flex:1 + min-width:0, NOT the
   default inline <span> box, is required for this to work at all:
   overflow:hidden is a no-op on inline elements (they have no CSS box
   model of their own to clip against), and element.clientWidth — what
   the detection script compares scrollWidth to — is ALWAYS 0 on an
   inline element in every browser, which silently broke both the
   clipping and the overflow check simultaneously. Making this a real
   flex item (sized by the row's own layout, shrinking via min-width:0
   the same way its parent .av-cryoblue-topbar-announcement already
   does) gives it an actual box with a real, measurable width. */
.av-cryoblue-topbar-announcement-text {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    /* max-width is UNCONDITIONAL, not gated behind .is-scrolling — an
       earlier version only applied this cap once JS added .is-scrolling,
       which meant a long announcement grew completely unbounded (full
       page width, forcing a horizontal scrollbar on the whole site)
       for however long it took the JS to run, or if it failed to run
       at all. This element can still be NARROWER than 320px for short
       text that fits comfortably (no min-width forcing it wide), but
       it can never be WIDER than 320px regardless of JS timing/success
       — the cap is pure CSS, always in effect from first paint. */
    max-width: 640px;
    flex: none;
}
/* Holds TWO copies of the announcement text back-to-back (see
   header.php's markup — .av-cryoblue-topbar-announcement-copy x2, the
   second marked aria-hidden since it's a purely visual duplicate for
   the loop, not real content a screen reader should announce twice).
   Only animates when .is-scrolling is present. The keyframe translates
   exactly -50% — since the track is two full copies wide, -50% moves
   past precisely ONE copy's width, so the moment the loop restarts at
   0%, copy #2 is sitting exactly where copy #1 started, and the seam
   is invisible. linear timing (not ease) keeps the scroll speed
   perfectly constant rather than speeding up/slowing down each loop,
   which reads as smoother for continuous ticker-style motion. */
.av-cryoblue-topbar-announcement-track {
    display: inline-flex;
    width: max-content;
}
.av-cryoblue-topbar-announcement-track.is-scrolling {
    /* Duration here is a FALLBACK only — the actual, correct duration
       is set per-instance via inline style by
       includes/js/announcement-marquee.js, calculated from the real
       measured text width so scroll SPEED stays constant regardless
       of message length (a fixed duration made short messages crawl
       and long ones blur past). This 40s only ever applies if that
       script somehow fails to run — inline animation-duration always
       wins over this shorthand's duration component when both are
       present, so this is not the value actually used in normal
       operation. */
    animation: av-announcement-marquee 40s linear infinite;
}
.av-cryoblue-topbar-announcement-copy {
    display: inline-flex;
    align-items: center;
    padding-right: 20px;
    flex: none;
}
/* The second copy (and both copies' separators) exist purely to make
   the marquee LOOP seamlessly once scrolling — they serve no purpose
   when the text already fits and isn't scrolling at all. Hidden by
   default; only shown once .is-scrolling is added to the track (see
   includes/js/announcement-marquee.js), matching exactly the same
   condition that turns the animation on. Without this, short text
   that never needed to scroll still rendered both copies side by side
   with visible seam artifacts (part of the second copy peeking out
   past the first), which is what was actually happening — not a CSS
   clipping bug, just unconditionally-rendered content that should have
   stayed hidden until actually needed. */
.av-cryoblue-topbar-announcement-copy[aria-hidden="true"] {
    display: none;
}
.av-cryoblue-topbar-announcement-track.is-scrolling .av-cryoblue-topbar-announcement-copy[aria-hidden="true"] {
    display: inline-flex;
}
.av-cryoblue-topbar-announcement-sep {
    display: none;
    margin-left: 20px;
    color: var(--line-3);
    font-size: 20px;
    line-height: 1;
}
.av-cryoblue-topbar-announcement-track.is-scrolling .av-cryoblue-topbar-announcement-sep {
    display: inline;
}
@keyframes av-announcement-marquee {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
    .av-cryoblue-topbar-announcement-track.is-scrolling {
        animation: none;
    }
}
/* margin-left:auto now lives on .av-cryoblue-topbar-evetime (just
   below), not here — EVE Time renders immediately before this block in
   the markup, so anchoring IT to the right edge pulls this element
   along with it as a normal flex-flow sibling right after (the
   topbar's own gap:8px already spaces the two apart). This element no
   longer needs its own auto margin; if EVE Time is ever removed, this
   would need margin-left:auto restored here to keep anchoring to the
   right on its own. */
.av-cryoblue-topbar-social { display: flex; align-items: center; gap: 12px; }
.av-cryoblue-topbar-social-label { color: var(--av-accent); font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; margin-right: 4px; }
.av-cryoblue-topbar-social a { color: var(--av-text-muted); text-decoration: none; font-size: 14px; transition: color var(--transition-fast); }
.av-cryoblue-topbar-social a:hover { color: var(--av-accent); }

/* EVE Time readout — a small, dim, always-UTC live clock sitting just
   left of the social icons (see header.php). tabular-nums keeps each
   digit the same width as it ticks every second, so the colon
   positions and overall width stay rock-steady instead of the text
   subtly shifting/jittering as digits change (e.g. "1" narrower than
   "8" in most fonts). Deliberately dim (--av-text-dim, the theme's
   quietest text tone) rather than the accent color — this is ambient
   reference info, not something meant to compete for attention with
   the real nav/social elements around it. */
.av-cryoblue-topbar-evetime {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-left: auto;
    color: var(--av-text-dim);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}
.av-cryoblue-topbar-evetime-label {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--av-text-dim);
}
/* Static — UTC never changes, so unlike the ticking value span this
   needs no JS at all. Slightly dimmer/smaller than the time itself so
   it reads as a secondary detail ("here's which zone that time is in")
   rather than competing with the actual clock value for attention. */
.av-cryoblue-topbar-evetime-zone {
    font-size: 10px;
    color: var(--av-text-dim);
    opacity: 0.75;
}
/* Thin vertical rule separating EVE Time from Socialize Now — flex:none
   so it keeps its own fixed 1px width regardless of how much space the
   row's other flex-flow items claim; height is eyeballed against this
   row's own text size (11-12px + a little line-height) rather than a
   shared token, since nothing else in the topbar needs this same
   measurement. */
.av-cryoblue-topbar-divider {
    width: 1px;
    height: 14px;
    background: var(--line-2);
    flex: none;
}

/* NAVBAR */
.av-cryoblue-navbar {
    background: linear-gradient(180deg, #10141d 0%, #0b0e15 100%);
    border-bottom: 1px solid var(--line-2);
    padding: 14px 10px;
}

.av-cryoblue-navbar-inner {
    display: flex;
    align-items: center;
    gap: 32px;
    flex-wrap: wrap;
    max-width: 1600px;
    margin: 0 auto;
}

.av-cryoblue-brand a {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    color: var(--av-text);
    transition: all var(--transition-fast);
}

.av-cryoblue-brand-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 2px solid var(--av-accent);
    color: var(--av-accent);
    font-family: 'Michroma', sans-serif;
    font-weight: 700;
    font-size: 14px;
    flex-shrink: 0;
}

.av-cryoblue-brand-name {
    font-size: 17px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.av-cryoblue-nav-links {
    list-style: none;
    display: flex;
    gap: 24px;
    margin: 0;
    padding: 0;
    flex: 1;
    flex-wrap: wrap;
}

.av-cryoblue-nav-links a {
    color: var(--av-text-muted);
    text-decoration: none;
    font-family: 'Michroma', sans-serif;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    transition: color var(--transition-fast);
}

.av-cryoblue-nav-links a:hover,
.av-cryoblue-nav-links a.active {
    color: var(--av-accent);
}

.av-cryoblue-nav-auth {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-right: 20px;
}

/* BODY LAYOUT — ADMIN PANEL ONLY */
.av-cryoblue-body-layout {
    display: flex;
    align-items: flex-start;
    gap: 20px;
    width: 100%;
    max-width: 1600px;
    margin: 0 auto;
    padding: 10px;
}

/* PUBLIC PAGE REGIONS */
.av-regions-row {
    display: flex;
    gap: 20px;
    margin-bottom: 8px;
}

.av-regions-row-triple {
    display: flex;
    gap: 10px;
    margin-bottom: 8px;
    align-items: flex-start;
}

.av-cryoblue-sidebar {
    width: 260px;
    flex-shrink: 0;
}

.av-cryoblue-main {
    flex: 1;
    min-width: 0;
}

/* ── PUBLIC SIDE RAIL ────────────────────────────────────────
   Mirrors the admin rail's visual design and collapse behaviour
   for the public site. position:fixed — same as .av-admin-rail
   in admin.css — so it spans full viewport height regardless of
   where it sits in the DOM. Rail width tokens match the admin
   rail exactly (220px expanded, 68px collapsed) so the offset
   numbers below stay in sync with no extra work.

   Body classes (set server-side from cookie in header.php):
     .has-public-rail         — always on public pages
     .public-rail-collapsed   — added when cookie is '1'
   These classes drive the topbar/navbar/main offsets so the
   correct layout is present on first paint, before any JS runs.
   Cookie: av_public_rail_collapsed (same pattern as the admin's
   av_admin_rail_collapsed). JS toggle is in header.php.
   ──────────────────────────────────────────────────────────── */

/* Topbar/navbar and main: offset for the collapsed rail only (84px / 68px).
   The expanded rail is position:fixed and overlays as a fixed layer —
   nothing reflows when it opens. Full-width backgrounds stay intact. */
body.has-public-rail .av-cryoblue-topbar,
body.has-public-rail .av-cryoblue-navbar {
    padding-left: 84px; /* 68px collapsed rail + 16px breathing room */
}

/* The margin clears the COLLAPSED rail (68px), which is position:fixed and
   would otherwise paint over the start of this column. It is mirrored on
   the right for one reason: anything centred inside main -- every .av-card
   page, login/register/forgot-password/reset-password -- centres against
   main's content box, so a one-sided offset put all of them half the
   margin (34px) right of true page centre. Matching the right side keeps
   main symmetric within the centred .av-cryoblue-body-layout, so
   `margin: 0 auto` on a card lands where the eye expects.

   Keep these two values equal. Changing one alone silently un-centres
   every card page on the site, and the drift is small enough to look like
   a design choice rather than a bug. */
body.has-public-rail .av-cryoblue-main {
    margin-left: 68px;
    margin-right: 68px;
}

/* The footer lands on main's content column, and the two numbers are
   derived from main's rather than guessed at.
       78px  = body-layout's own 10px padding + main's 68px margin
       1444px = 1600px body-layout cap - 20px its padding - 136px those margins
   Below a 1600px viewport the cap is inert and the 78px does the work;
   above it the padding is inert and the cap does. Both are needed, and
   MEASURING is the only way to get this right — the obvious version of
   this rule (78px padding plus a 1600px cap, matching body-layout's own
   width) lands the content at x=160 on a 1920px screen while main sits at
   x=238, which is still inside the 220px expanded rail. That is the bug
   this rule exists to fix, surviving the fix.
   Both selectors, because Theme::renderBlock() emits a footer block as a
   SIBLING of .av-cryoblue-footer-inner rather than inside it, so a block
   never inherited that wrapper's constraint. */
body.has-public-rail .av-cryoblue-footer {
    /* THE MARGIN IS THE POINT, not the padding. Padding would move the
       content and leave the BAR's background still painting the full
       width underneath the rail — the footer visibly running over the
       side menu. The margin stops the bar dead at the collapsed rail's
       edge instead, so the two abut and share no pixel at all.
       68 + 10 = the 78 that was here before, so the content box does not
       move: same left edge, same width, still aligned to main. */
    margin-left: 68px;
    padding-left: 10px;
    padding-right: 78px;
}
body.has-public-rail .av-cryoblue-footer-inner,
body.has-public-rail .av-cryoblue-footer > .block-wrapper {
    max-width: 1444px;
    margin-left: auto;
    margin-right: auto;
}

.av-public-rail {
    flex: none;
    width: 220px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    /* One step darker than --bg-1, same as the admin rail —
       a bespoke hex rather than a new token since nothing else
       needs this exact shade. */
    background: #071019;
    border-right: 1px solid var(--line-2);
    padding: 20px 16px;
    /* No separate bottom padding — the shorthand's 20px stands. It used to
       be 70px, reserving room so the rail ducked under a footer that
       painted over its bottom edge. Neither half of that is true now: the
       rail outranks the footer, and the footer bar stops at the rail's
       edge rather than crossing it, so there is nothing to duck. */
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    /* ONE ABOVE THE FOOTER (10), and no higher. The rail takes top place
       over the footer, full stop — it is a full-height fixed column and
       its collapse toggle lives at the bottom, so a footer painting over
       it made the toggle unclickable and the menu impossible to reopen.
       11 rather than a big number because everything that outranked the
       rail at 5 must still outrank it: toast 50, dropdowns 300 (a navbar
       dropdown opens inside the expanded rail's column), and the 1000
       overlays. Note the collapsed rail's tooltip ::after composites at
       THIS value, not its own 10200 — the rail is a stacking context —
       so the tooltip rises with the rail and still loses to all three. */
    z-index: 11;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: thin;
    scrollbar-color: var(--line-2) transparent;
}

/* Brand row — icon + site name + sub-label */
.av-public-rail-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 4px;
    flex-shrink: 0;
}
.av-public-rail-brand-icon {
    flex: none;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    border: 1px solid var(--av-accent);
    color: var(--av-accent);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 14px;
    font-family: 'Michroma', sans-serif;
}
.av-public-rail-brand-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.av-public-rail-brand-name {
    font-family: 'Michroma', sans-serif;
    font-size: 11px;
    font-weight: 400;
    letter-spacing: 0.02em;
    color: #fff;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.av-public-rail-brand-sub {
    font-size: 10px;
    color: var(--av-text-dim);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* Nav links */
.av-public-rail-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.av-public-rail-nav a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    border-radius: 7px;
    border: 1px solid transparent;
    color: var(--av-text-muted);
    font-size: 13px;
    text-decoration: none;
    transition: border-color var(--transition-fast), color var(--transition-fast),
                background var(--transition-fast), box-shadow var(--transition-fast);
}
.av-public-rail-nav a:hover {
    border-color: var(--line-2);
    color: var(--av-accent);
    box-shadow: var(--glow-accent);
}
.av-public-rail-nav a:hover i { color: var(--av-accent); }
.av-public-rail-nav a.active {
    background: var(--bg-3);
    border-color: var(--line-2);
    color: var(--av-accent);
}
.av-public-rail-nav a.active i { color: var(--av-accent); }
.av-public-rail-nav a i {
    font-size: 16px;
    flex: none;
    transition: color var(--transition-fast);
    color: var(--av-text-muted);
}
.av-public-rail-nav a span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Collapse/expand toggle — margin-top:auto pushes it to the
   bottom of the flex column, above the footer divider. Same
   pattern as .av-admin-rail-toggle in admin.css. */
.av-public-rail-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    margin-top: auto;
    padding: 6px 10px;
    border-radius: 7px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--av-text-muted);
    font-size: 13px;
    font-weight: 400;
    text-align: left;
    text-transform: none;
    letter-spacing: normal;
    cursor: pointer;
    flex-shrink: 0;
    transition: border-color var(--transition-fast), color var(--transition-fast),
                background var(--transition-fast), box-shadow var(--transition-fast);
}
.av-public-rail-toggle:hover {
    color: var(--av-accent);
    border-color: transparent;
    background: transparent;
    box-shadow: none;
    transform: none;
}
.av-public-rail-toggle:hover i { color: var(--av-accent); }
.av-public-rail-toggle i {
    font-size: 24px;
    flex: none;
    display: block;
    color: var(--av-text-dim);
    transition: color var(--transition-fast);
}
.av-public-rail-toggle span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.av-public-rail-divider {
    height: 1px;
    /* Accent, not --line-3: fading dividers are accent blue (2026-09-19); the amber ones are the shine rules, which set their own colour. */
    background: linear-gradient(90deg, transparent, var(--av-accent), transparent);
    opacity: 0.6;
    flex: none;
    flex-shrink: 0;
}

/* Footer strip — back-to-site link, always at the bottom */
.av-public-rail-footer {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding-top: 0;
    flex-shrink: 0;
}
.av-public-rail-backlink {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--av-text-dim);
    text-decoration: none;
    padding: 2px 4px;
    border-radius: 5px;
    transition: color var(--transition-fast);
}
.av-public-rail-backlink:hover { color: var(--av-accent); }
.av-public-rail-backlink i { font-size: 14px; flex: none; }
.av-public-rail-backlink span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Collapsed state (desktop only, min-width:901px) ─────────
   Hides text, centers icons — exact mirror of the admin rail's
   .is-rail-collapsed rules in admin.css. Class toggled on
   .av-public-rail by js/cryoblue-rail.js; header.php draws it
   from the cookie for first paint. */
@media (min-width: 901px) {
    .av-public-rail.is-rail-collapsed {
        width: 68px;
        padding-left: 14px;
        padding-right: 14px;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-brand {
        justify-content: center;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-brand-text {
        display: none;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-nav a {
        justify-content: center;
        padding: 9px 0;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-nav a span {
        display: none;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-toggle {
        justify-content: center;
        padding: 9px 0;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-toggle span {
        display: none;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-backlink {
        justify-content: center;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-backlink span {
        display: none;
    }
}

/* ── Mobile (≤900px): horizontal bar ────────────────────────
   Becomes position:static (back into flex flow), full-width,
   horizontal — same treatment as .av-admin-rail on mobile in
   admin.css. Offsets on topbar/navbar/main all reset to 0. */
@media (max-width: 900px) {
    body.has-public-rail .av-cryoblue-topbar,
    body.has-public-rail .av-cryoblue-navbar,
    body.has-public-rail.public-rail-collapsed .av-cryoblue-topbar,
    body.has-public-rail.public-rail-collapsed .av-cryoblue-navbar {
        padding-left: 10px;
    }
    body.has-public-rail .av-cryoblue-main,
    body.has-public-rail.public-rail-collapsed .av-cryoblue-main {
        /* Both sides, not just the left. The rail is position:static here
           and sits above the content as a horizontal bar, so neither
           offset is doing anything except stealing width. */
        margin-left: 0;
        margin-right: 0;
    }
    /* Same reasoning for the footer's inset — there is no fixed column to
       clear once the rail is a horizontal bar, so 78px each side is pure
       lost width on the narrowest screens. Back to the base 10px, and the
       margin goes with it or the bar carries a 68px notch beside nothing. */
    body.has-public-rail .av-cryoblue-footer {
        margin-left: 0;
        padding-left: 10px;
        padding-right: 10px;
    }
    body.has-public-rail .av-cryoblue-body-layout {
        flex-direction: column;
        gap: 0;
    }
    .av-public-rail {
        width: 100%;
        position: static;
        flex-direction: row;
        align-items: center;
        gap: 16px;
        overflow-x: auto;
        overflow-y: visible;
        border-right: none;
        border-bottom: 1px solid var(--line-2);
        padding: 8px 16px;
        padding-bottom: 8px;
    }
    .av-public-rail-toggle { display: none; }
    .av-public-rail-nav {
        flex-direction: row;
        flex: none;
    }
    .av-public-rail-nav a span { display: none; }
    .av-public-rail-footer {
        margin-top: 0;
        margin-left: auto;
        flex-direction: row;
        align-items: center;
        padding-top: 0;
        border-top: none;
        flex: none;
    }
}

/* FOOTER — body's last grid row, in normal flow. It is NOT fixed; see
   body's comment for why a block region cannot be.

   THE PUBLIC RAIL OUTRANKS THIS, deliberately — see .av-public-rail's
   z-index:11. An earlier version of this comment argued the opposite, and
   the reason it gave was real: a rail lifted above a FULL-BLEED footer
   painted its column over the left edge of footer blocks. The fix was not
   to keep the footer on top but to stop the two overlapping — the
   has-public-rail rule below now ends the bar at the collapsed rail's
   edge, so the rail can win without covering anything. What that rule
   protects is the rail's collapse toggle, which sits at the rail's bottom
   and was unreachable underneath a tall footer.

   z-index:10 still matters for the ADMIN rail, which is z-index:5 and has
   no such inset — admin.css's own comment depends on the footer painting
   above it, and body.has-public-rail never applies there.

   Full-bleed bar on admin and mobile; on the public desktop layout the
   bar starts at the rail's edge. Content is inset either way. */
.av-cryoblue-footer {
    background: #060810;
    border-top: 1px solid var(--line-2);
    padding: 10px;
    margin-top: 32px;
    position: relative;
    z-index: 10;
    box-sizing: border-box;
}

.av-cryoblue-footer-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 16px;
    max-width: 1600px;
    margin: 0 auto;
}

.av-cryoblue-footer-copyright {
    position: relative;
    display: inline-block;
    color: var(--av-text-dim);
    font-size: 12px;
    padding-bottom: 6px;
    margin-left: auto;
}

.av-cryoblue-footer-copyright::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 1px;
    background: linear-gradient(to left, var(--av-accent), transparent);
}

/* GENERIC BLOCK SYSTEM */
.block-wrapper {
    background: var(--av-panel);
    border: 1px solid var(--line-2);
    border-radius: 6px;
    padding: 22px;
    box-shadow: 0 0 12px rgba(74, 158, 255, 0.12), 0 0 24px rgba(74, 158, 255, 0.06), inset 0 0 16px rgba(74, 158, 255, 0.08), var(--shadow-md);
    transition: all var(--transition-normal);
    margin-bottom: 12px;
    position: relative;
    overflow: hidden;
}

/* Corner accent glow — same radial-gradient sunburst the admin section
   cards use (see includes/css/admin.css .settings-section::after).
   inset:0 fills the full card so the gradient scales with the block's
   dynamic height automatically; pointer-events:none keeps it decorative. */
.block-wrapper::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 6px;
    background: radial-gradient(circle at top left, rgba(86, 184, 232, 0.16), transparent 30%);
    pointer-events: none;
}

/* The percentage stop (30%) scales with block dimensions — on narrow
   sidebar blocks (~200px wide) 30% = only ~60px of actual reach, making
   the glow look underpowered vs. the wide top/center blocks. A fixed
   pixel radius gives sidebar blocks the same absolute spread regardless
   of their width. opacity also lifted slightly to compensate for nav-link
   backgrounds absorbing the gradient in content-dense blocks like sidenav. */
.block-wrapper[data-region="sidebar-left"]::after,
.block-wrapper[data-region="sidebar-right"]::after {
    background: radial-gradient(circle at top left, rgba(86, 184, 232, 0.18), transparent 140px);
}

.block-wrapper:hover {
    border-color: var(--line-2);
    box-shadow: 0 0 12px rgba(74, 158, 255, 0.12), 0 0 24px rgba(74, 158, 255, 0.06), inset 0 0 16px rgba(74, 158, 255, 0.08), var(--shadow-md);
}

.block-heading {
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: 'Michroma', sans-serif;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--av-accent);
    margin: -22px -22px 16px -22px;
    padding: 10px 16px;
    background: linear-gradient(to right, rgba(86, 184, 232, 0.14), transparent);
    border-radius: 5px 5px 0 0;
    border-bottom: 1px solid var(--line-1);
    overflow: hidden;
    overflow-wrap: anywhere;
    min-width: 0;
}

.block-heading-sub { margin-left: auto; font-size: 11px; color: var(--av-text-dim); text-transform: none; font-weight: 400; }
.block-heading-link { color: var(--av-accent); margin-left: auto; font-size: 11px; }
.block-heading-link:hover { color: var(--av-accent); }

.text-muted { color: var(--av-text-muted); }
.text-dim { color: var(--av-text-dim); }

/* Emitted by Theme::renderBlock() around every dispatched block's output.
   No box of its own — .block-wrapper already provides the card; this is
   just a hook themes can use to target block content specifically. */
.block-content { display: block; }

/* When the engine renders a block, .block-heading is an <h3>, so the
   browser's default h3 margin/size has to be neutralised — home.php's
   static cards use a <div> and never hit this. */
h3.block-heading { font-size: 12px; }

/* Menu block (blocks/menu.php) */
.block-menu { display: flex; flex-direction: column; gap: 18px; }
.block-menu__section { display: flex; flex-direction: column; }
.block-menu__heading { font-family: 'Michroma', sans-serif; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--av-accent); margin-bottom: 6px; padding-bottom: 4px; border-bottom: 1px solid var(--line-2); }
.block-menu__links { list-style: none; margin: 0; padding: 0; }
.block-menu__links li { margin: 0; }
.block-menu__links a { display: block; padding: 5px 0 5px 4px; color: var(--av-text-muted); font-size: 13px; transition: all var(--transition-fast); }
.block-menu__links a:hover { color: var(--av-accent); padding-left: 8px; }
.block-menu__empty { font-size: 13px; color: var(--av-text-dim); font-style: italic; margin: 0; }

/* In the nav region a menu lays out horizontally and drops its section
   headings — a single-row bar isn't the place for category labels. */
.av-region-nav .block-menu { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px 24px; }
.av-region-nav .block-menu__section { flex-direction: row; }
.av-region-nav .block-menu__heading { display: none; }
.av-region-nav .block-menu__links { display: flex; flex-wrap: wrap; gap: 4px 20px; }
.av-region-nav .block-menu__links a { padding: 4px 0; }
.av-region-nav .block-menu__links a:hover { padding-left: 0; }

/* Nav region blocks: hide headings, reduce width by half, and center */
.av-region-nav .block-heading { display: none; }
.av-region-nav .block-wrapper { max-width: 50%; margin: 0 auto; }

.block-actions { display: flex; align-items: center; gap: 16px; margin-top: 16px; flex-wrap: wrap; }

.btn {
    display: inline-block;
    background: var(--bg-3);
    border: 1px solid var(--line-2);
    color: var(--av-text);
    padding: 10px 18px;
    border-radius: 4px;
    font-size: 12px;
    text-transform: uppercase;
    font-weight: 600;
    cursor: pointer;
    transition: all var(--transition-normal);
}
.btn:hover {
    border-color: var(--av-accent);
    background: var(--bg-4);
    color: var(--av-accent);
    box-shadow: var(--glow-accent);
    transform: scale(var(--av-hover-scale));
}

/* The transition below is deliberately an EXPLICIT property list rather
   than `transition: all` — this is the one thing that makes textarea
   resizing feel sluggish, and it is fixed here once for every form
   control on the public side rather than per field.
   `all` means literally all animatable properties, and `height` is one
   of them. Dragging a textarea's resize handle changes height directly,
   so with `all` the browser animates every single drag frame over
   --transition-fast (120ms): the box trails the pointer instead of
   tracking it, and releasing the mouse leaves it still catching up.
   Nothing about the drag itself is slow — the transition is doing
   exactly what it was told to do, to a property that should never be
   animated. Naming the three properties that actually need to animate
   (the :focus state below changes border-color, box-shadow and
   background, and nothing else) keeps that feedback smooth while
   leaving height/width to follow the pointer instantly.
   Because this sits on the bare `textarea`/`input`/`select` selectors,
   every textarea on the public side — current and future, in any
   template or block, with no per-field CSS — gets the correct behaviour
   automatically. Do NOT reintroduce `transition: all` here or add one to
   an individual textarea. Mirrors .av-admin textarea in
   themes/av_cryoblue/style/admin.css, which uses the same explicit list
   for the same reason. */
input[type="text"], input[type="email"], input[type="password"], input[type="number"], textarea, select {
    width: 100%; max-width: 100%; padding: 10px; margin-bottom: 10px; border-radius: 4px; border: 1px solid var(--line-2);
    background: #09141b; color: var(--av-text); font-size: 13px;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
}

textarea { resize: vertical; }
/* The resize grip, where the browser lets it be drawn (Blink and
   WebKit; Gecko keeps its own): the stock grey strokes redrawn in the
   accent, dim at rest and full on focus like the border. The admin
   stylesheet draws the same grip in its own tokens. */
textarea::-webkit-resizer {
    width: 14px;
    height: 14px;
    border-bottom-right-radius: 3px;
    background: linear-gradient(
        135deg,
        transparent 0 calc(100% - 10px),
        rgba(86, 184, 232, 0.55) calc(100% - 10px) calc(100% - 8px),
        transparent calc(100% - 8px) calc(100% - 5px),
        rgba(86, 184, 232, 0.55) calc(100% - 5px) calc(100% - 3px),
        transparent calc(100% - 3px)
    );
}
textarea:focus::-webkit-resizer {
    background: linear-gradient(
        135deg,
        transparent 0 calc(100% - 10px),
        var(--av-accent) calc(100% - 10px) calc(100% - 8px),
        transparent calc(100% - 8px) calc(100% - 5px),
        var(--av-accent) calc(100% - 5px) calc(100% - 3px),
        transparent calc(100% - 3px)
    );
}

/* A field's refusal, drawn by includes/js/form-validation.js under the
   control it belongs to — the theme's answer to the browser's bubble,
   which cannot be styled. Sits on every page, admin and public; the
   admin's own input rules leave these two alone. aria-invalid is what
   the script sets, so the border follows the message. */
.av-field-error { color: var(--bad); font-size: 12px; line-height: 1.4; margin: -4px 0 10px; }
/* Unfocused, the border says invalid; focused, the theme's accent border
   and glow stay (they are the focus cue) and a red ring joins them, so the
   field the script just focused still reads as focused. */
[aria-invalid="true"]:not(:focus) { border-color: var(--bad) !important; }
[aria-invalid="true"]:focus { box-shadow: 0 0 0 2px rgba(235, 104, 114, 0.45) !important; }
/* Show/hide password — includes/js/password-reveal.js wraps every
   password input in .av-password-field and puts the eye inside the
   box. The wrapper takes over the input's 10px bottom margin so the
   field's footprint does not change and the eye centres on the input
   rather than on input-plus-margin; the admin's inputs carry no margin
   and its wrapper carries none either (admin.css). The right padding
   keeps typed text from running under the eye. The input's type flips
   to text while shown, so both types are styled. */
.av-password-field { position: relative; display: block; margin-bottom: 10px; }
.av-password-field > input[type="password"],
.av-password-field > input[type="text"] { margin-bottom: 0; padding-right: 40px; }
.av-password-toggle {
    position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
    width: 28px; height: 28px; padding: 0; margin: 0; border: 0; border-radius: 4px;
    background: transparent; color: var(--av-text-dim, #728996); cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    transition: color var(--transition-fast), background var(--transition-fast);
}
.av-password-toggle:hover, .av-password-toggle[aria-pressed="true"] { color: var(--av-accent, #56b8e8); }
.av-password-toggle:hover { background: rgba(86, 184, 232, 0.08); }
.av-password-toggle:focus-visible { outline: 2px solid var(--av-accent, #56b8e8); outline-offset: 1px; }
/* Present for assistive tech, invisible otherwise — the validation
   script's live region. */
.av-sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* Browser autofill (username/password fields especially) forces its own
   background color the moment a field is recognized/filled, ignoring a
   plain background-color rule entirely — this is a browser-level style
   with higher precedence, not something the theme's own CSS specificity
   can win against normally. The standard workaround: an enormous inset
   box-shadow the same color as the theme's input background, which
   visually paints over the autofill color from the inside without
   actually touching background-color (autofill styling doesn't override
   box-shadow). The absurd transition delay keeps Chrome's own "flash to
   yellow, animate back" from being visible at all. -webkit-text-fill-color
   is needed separately because autofill also forces its own text color,
   which -webkit-box-shadow does nothing to fix on its own. Firefox uses a
   different (saner) mechanism — filter: none on its own :autofill
   pseudo-class turns off its own tinting instead of needing this at all. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
    -webkit-box-shadow: 0 0 0 1000px #09141b inset !important;
    -webkit-text-fill-color: var(--av-text) !important;
    caret-color: var(--av-text);
    transition: background-color 600000s ease-in-out 0s;
}

input:autofill {
    filter: none;
}

input:focus, textarea:focus, select:focus {
    border-color: var(--av-accent); box-shadow: var(--glow-accent); background: #0b1820; outline: none;
}

input[type="radio"] { accent-color: var(--av-accent); }

/* CHECKBOX — the admin's (.av-admin input[type="checkbox"] in admin.css),
   drawn here for the public side in the public tokens: appearance:none
   drops the native widget, the box takes the theme's input background
   and border, a checked box the accent border and glow, and a ::before
   draws the tick with a clip-path so no markup or image is needed.
   Global, so the login block, login.php, the header's popup, the
   account page and the forum's search and split forms all agree with
   the admin rather than with the operating system. The focus handling
   is the admin's too — see its comment for why both outline and
   box-shadow are zeroed, and why the checked glow is re-declared. */
input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    width: 16px;
    height: 16px;
    padding: 0;
    margin: 0;
    border-radius: 4px;
    border: 1px solid var(--line-2);
    background: #09141b;
    cursor: pointer;
    vertical-align: middle;
    position: relative;
    flex: none;
    transition: border-color var(--transition-fast), background var(--transition-fast), box-shadow var(--transition-fast);
}
input[type="checkbox"]:hover { border-color: var(--line-3); background: #0b1820; }
input[type="checkbox"]:checked {
    background: rgba(86, 184, 232, 0.18);
    border-color: var(--av-accent);
    box-shadow: 0 0 6px rgba(86, 184, 232, 0.35);
}
input[type="checkbox"]::before {
    content: '';
    position: absolute;
    inset: 2px;
    background: var(--av-accent);
    clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 14%, 80% 0%, 43% 62%);
    opacity: 0;
    transition: opacity var(--transition-fast);
}
input[type="checkbox"]:checked::before { opacity: 1; }
input[type="checkbox"]:focus, input[type="checkbox"]:focus-visible { outline: none; box-shadow: none; background: #09141b; }
input[type="checkbox"]:checked:focus, input[type="checkbox"]:checked:focus-visible { box-shadow: 0 0 6px rgba(86, 184, 232, 0.35); background: rgba(86, 184, 232, 0.18); }
/* The admin shell's checkboxes keep the admin's own rule; this one is
   for the public side and the two agree, so the guard is belt and
   braces against the public focus rule above reaching in. */
.av-admin input[type="checkbox"]:focus { background: var(--aa-input-bg); }

/* DROPDOWN MENUS */
.av-dropdown { position: relative; width: 100%; display: inline-block; }

/* font-family: inherit on the trigger and the options, because both are
   <button>s and a button takes the browser's own face (Arial, 13.33px)
   rather than the page's unless told otherwise. admin.css covers its
   side with `.av-admin button`; nothing did on public pages, so the
   account page's folder picker and every forum dropdown drew in Arial
   beside Segoe text. The size is the admin copy's 13px; a consumer that
   sits the dropdown among smaller controls sets its own on the trigger. */
.av-dropdown-trigger {
    width: 100%;
    min-height: 30px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    border: 1px solid var(--line-2);
    border-radius: 3px;
    background: #09141b;
    color: var(--av-text);
    padding: 6px 8px;
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    font-size: 13px;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
}

.av-dropdown-trigger:hover:not(:disabled) {
    border-color: var(--line-3);
    transform: none;
    box-shadow: none;
}

.av-dropdown-trigger[aria-expanded="true"] {
    background: #0b1820;
}

.av-dropdown-trigger:disabled {
    cursor: not-allowed;
    opacity: 0.46;
}

.av-dropdown-trigger span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.av-dropdown-chevron {
    color: var(--av-text-dim);
    transition: transform var(--transition-fast), color var(--transition-fast);
    flex: none;
}

.av-dropdown-chevron.is-open {
    transform: rotate(180deg);
    color: var(--av-accent);
}

.av-dropdown-trigger:hover .av-dropdown-chevron {
    color: var(--av-accent);
}

.av-dropdown-menu {
    position: absolute;
    z-index: 300;
    left: 0;
    top: calc(100% + 4px);
    width: 100%;
    padding: 5px;
    background: #101d25;
    border: 1px solid var(--line-2);
    border-radius: 3px;
    box-shadow: 0 14px 42px rgb(0 0 0 / 55%);
    animation: av-dropdown-slide 0.2s ease-out;
    max-height: 260px;
    overflow-y: auto;
    overflow-x: hidden;
    box-sizing: border-box;
}

.av-dropdown-option {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    border: 0;
    margin: 0;
    border-radius: 3px;
    background: transparent;
    color: var(--av-text);
    padding: 7px 9px;
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    font-size: 13px;
    transition: all var(--transition-fast);
}

.av-dropdown-option span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1;
}

/* A tree in a menu (render_cryoblue_dropdown()'s option_meta): the
   indent is one 18px cell per level with a hairline down its left edge
   — a repeating gradient, so one element draws every guide line — and
   the icon says what the row is. Both are `flex: none`, out from under
   the label span's flex: 1 above. */
.av-dropdown-option .av-dropdown-indent {
    flex: none;
    align-self: stretch;
    margin: -7px 0;
    background: repeating-linear-gradient(to right, var(--line-2) 0 1px, transparent 1px 18px);
}
.av-dropdown-option .av-dropdown-option-icon {
    flex: none;
    font-size: 15px;
    color: var(--av-text-dim);
}
.av-dropdown-option:hover .av-dropdown-option-icon,
.av-dropdown-option.is-active .av-dropdown-option-icon { color: var(--av-accent); }

.av-dropdown-check {
    flex-shrink: 0;
    color: var(--av-accent);
}
/* Hidden by default, shown only for the currently .is-active option —
   see the matching rule's own comment in includes/css/admin.css for
   the full "why" (this is the same fix, duplicated here since this
   theme ships its own public-page copy of the .av-dropdown-* rules
   rather than depending on admin.css, which never loads outside
   admin.php). */
.av-dropdown-check {
    display: none;
}
.av-dropdown-option.is-active .av-dropdown-check {
    display: block;
}

.av-dropdown-option:hover:not(:disabled) {
    color: var(--av-accent);
    background: var(--bg-4);
    transform: none;
}

.av-dropdown-option.is-active {
    color: var(--av-accent);
    background: rgb(86 184 232 / 10%);
}

.av-dropdown-option:disabled {
    cursor: not-allowed;
    opacity: 0.46;
}

.av-dropdown-option,
.av-dropdown-option:hover,
.av-dropdown-option:focus,
.av-dropdown-option:focus-visible,
.av-dropdown-option:active,
.av-dropdown-option.is-active {
    border: none !important;
    outline: none !important;
    box-shadow: none !important;
}

@keyframes av-dropdown-slide {
    from { opacity: 0; transform: translateY(-12px); }
    to { opacity: 1; transform: translateY(0); }
}

/* DATE PICKER (render_cryoblue_datepicker()) — the public copy of the
   rules admin.css keeps under .av-admin in its own tokens, as the
   dropdown block above is to that file's .av-admin .av-dropdown-*.
   Same trigger box and panel as the dropdown, 1:1, so the two read as
   one family; the panel is a fixed 240px for the 7-column day grid.
   The trigger and every button in the panel are <button>s, so each
   says font-family: inherit for the reason the dropdown's do. */
.av-datepicker { position: relative; width: auto; min-width: 180px; max-width: 220px; display: inline-block; }
.av-datepicker-trigger {
    width: 100%;
    min-height: 30px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    background: #09141b;
    color: var(--av-text);
    border: 1px solid var(--line-2);
    border-radius: 3px;
    padding: 6px 8px;
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    font-size: 13px;
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
}
.av-datepicker-trigger:hover:not(:disabled) {
    background: #09141b;
    border-color: var(--line-3);
    color: var(--av-text);
    transform: none;
    box-shadow: none;
}
.av-datepicker-trigger.is-open { background: #0b1820; color: var(--av-text); }
.av-datepicker-trigger:disabled { background: #09141b; color: var(--av-text); cursor: not-allowed; opacity: 0.46; }
.av-datepicker-selected { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.av-datepicker-selected.is-placeholder { color: var(--av-text-dim); }
.av-datepicker-icon { color: var(--av-text-dim); transition: color var(--transition-fast); flex: none; font-size: 15px; }
.av-datepicker-trigger:hover .av-datepicker-icon { color: var(--av-accent); }
.av-datepicker-panel {
    position: absolute;
    z-index: 300;
    left: 0;
    top: calc(100% + 4px);
    width: 240px;
    padding: 12px;
    background: #101d25;
    border: 1px solid var(--line-2);
    border-radius: 6px;
    box-shadow: 0 14px 42px rgb(0 0 0 / 55%);
    animation: av-dropdown-slide 0.2s ease-out;
    box-sizing: border-box;
}
.av-datepicker-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.av-datepicker-month-label { font-size: 13px; font-weight: 600; color: var(--av-text); }
.av-datepicker-nav {
    background: transparent;
    border: 1px solid var(--line-2);
    border-radius: 4px;
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--av-text-muted);
    cursor: pointer;
    padding: 0;
    margin: 0;
    font-family: inherit;
    text-transform: none;
    transition: all var(--transition-fast);
}
.av-datepicker-nav:hover {
    border-color: var(--av-accent);
    color: var(--av-accent);
    background: var(--bg-4);
    transform: none;
    box-shadow: none;
}
.av-datepicker-weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    text-align: center;
    font-size: 10px;
    color: var(--av-text-dim);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 4px;
}
.av-datepicker-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.av-datepicker-day {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--av-text);
    font-family: inherit;
    font-size: 12px;
    padding: 6px 0;
    cursor: pointer;
    text-align: center;
    margin: 0;
    text-transform: none;
    font-weight: 400;
    letter-spacing: normal;
    transition: all var(--transition-fast);
}
.av-datepicker-day:hover {
    background: var(--bg-4);
    border-color: var(--line-2);
    color: var(--av-accent);
    transform: none;
    box-shadow: none;
}
.av-datepicker-day.is-outside { color: var(--av-text-dim); opacity: 0.5; }
.av-datepicker-day.is-today { border-color: var(--av-accent); color: var(--av-accent); }
.av-datepicker-day.is-selected {
    background: var(--av-accent);
    color: var(--bg-0);
    font-weight: 700;
    border-color: var(--av-accent);
}
.av-datepicker-day.is-selected:hover { background: var(--av-accent); color: var(--bg-0); }
.av-datepicker-footer {
    display: flex;
    justify-content: space-between;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--line-2);
}
.av-datepicker-footer button {
    background: transparent;
    border: none;
    padding: 0;
    margin: 0;
    font-family: inherit;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--av-accent);
    cursor: pointer;
    font-weight: 600;
    transition: color var(--transition-fast);
}
.av-datepicker-footer button:hover { color: #fff; transform: none; box-shadow: none; background: transparent; }
.av-datepicker-footer .av-datepicker-clear { color: var(--av-text-dim); }
.av-datepicker-footer .av-datepicker-clear:hover { color: var(--bad); }

.dashboard { display: flex; flex-direction: column; gap: 0; }
.row { display: grid; gap: 20px; margin-bottom: 20px; }
.row-hero { grid-template-columns: 2fr 1fr; }
.row-triple { grid-template-columns: 1fr 1.4fr 1fr; }

button:focus-visible { outline: 2px solid var(--av-accent); outline-offset: 1px; }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #263b47; border-radius: 8px; transition: all var(--transition-fast); }
::-webkit-scrollbar-thumb:hover { background-color: #385363; }

/* BLOCK-SPECIFIC STYLES */
.av-cryoblue-hero-block { display: grid; grid-template-columns: 1fr 1.2fr; gap: 24px; align-items: center; }
.av-cryoblue-hero-media { display: flex; flex-direction: column; gap: 12px; }
.av-cryoblue-hero-video { aspect-ratio: 16/9; background: linear-gradient(135deg, #141b28 0%, #0a0d13 100%); border: 1px solid var(--line-2); border-radius: 4px; display: flex; align-items: center; justify-content: center; box-shadow: inset 0 0 20px rgba(86, 184, 232, 0.08); transition: all var(--transition-normal); }
.av-cryoblue-hero-video:hover { border-color: var(--av-accent); box-shadow: inset 0 0 24px rgba(86, 184, 232, 0.12), 0 0 12px rgba(86, 184, 232, 0.2); }
.av-cryoblue-hero-play { width: 56px; height: 56px; border-radius: 50%; background: var(--av-accent); color: #0d1420; display: flex; align-items: center; justify-content: center; font-size: 18px; padding-left: 4px; transition: all var(--transition-normal); box-shadow: 0 0 16px rgba(86, 184, 232, 0.3); }
.av-cryoblue-hero-video:hover .av-cryoblue-hero-play { transform: scale(1.1); box-shadow: 0 0 24px rgba(86, 184, 232, 0.5); }
.av-cryoblue-hero-follow { text-align: center; font-size: 12px; color: var(--av-text-dim); margin: 0; text-transform: uppercase; letter-spacing: 0.03em; }
.av-cryoblue-hero-copy h1 { font-size: 24px; margin: 0 0 12px; text-transform: uppercase; letter-spacing: 0.02em; color: var(--av-text); }
.av-cryoblue-hero-copy p { color: var(--av-text-muted); margin: 0 0 12px; }

.av-cryoblue-status-players { font-size: 15px; font-weight: 700; margin: 0 0 10px; color: var(--av-accent); }
.av-cryoblue-status-online { margin-left: auto; color: var(--av-accent); font-size: 11px; text-transform: uppercase; font-weight: 700; }
.av-cryoblue-faction-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; margin-bottom: 14px; box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.3); }
.av-cryoblue-faction-alliance { background: linear-gradient(to right, #3b6fd6, #4b7fe6); flex: 1; }
.av-cryoblue-faction-horde { background: linear-gradient(to right, #b8433a, #c8534a); flex: 1; }
.av-cryoblue-status-body { color: var(--av-text-muted); margin: 0 0 8px; }
.av-cryoblue-status-realmlist { color: var(--av-text-muted); margin: 0; }

.av-cryoblue-diamond-list { list-style: none; margin: 0; padding: 0; font-size: 13px; color: var(--av-text-muted); }
.av-cryoblue-diamond-list li { position: relative; padding-left: 18px; margin-bottom: 10px; line-height: 1.5; }
.av-cryoblue-diamond-list li::before { content: ""; position: absolute; left: 2px; top: 6px; width: 6px; height: 6px; background: var(--av-accent); transform: rotate(45deg); transition: all var(--transition-fast); }
.av-cryoblue-diamond-list li:hover::before { background: var(--av-accent); left: 0px; }

.av-cryoblue-news-byline { color: var(--av-text-dim); font-size: 12px; margin: 0 0 8px; }
.av-cryoblue-news-more-heading { display: flex; justify-content: space-between; align-items: center; font-size: 13px; font-weight: 700; color: var(--av-text-dim); margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line-2); }
.av-cryoblue-article-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 12px; }
.av-cryoblue-article-tile { background: var(--bg-3); border: 1px solid var(--line-2); border-radius: 4px; padding: 10px; text-decoration: none; display: flex; flex-direction: column; gap: 4px; transition: all var(--transition-normal); }
.av-cryoblue-article-tile:hover { background: var(--bg-4); border-color: var(--av-accent); box-shadow: var(--glow-accent); transform: translateY(-2px); }
.av-cryoblue-article-title { color: var(--av-text); font-size: 12px; font-weight: 600; }
.av-cryoblue-article-date { color: var(--av-text-dim); font-size: 11px; }
.av-cryoblue-readmore { color: var(--av-accent); text-decoration: none; font-weight: 600; font-size: 11px; transition: all var(--transition-fast); }
.av-cryoblue-readmore:hover { text-decoration: underline; color: var(--av-accent); }

/* Login/user-status widget's CSS now lives inline in
   blocks/membership/block.php itself (self-contained, portable
   .block-membership__* classes) rather than here — see that block's doc
   comment. Kept the theme's other .av-cryoblue-* widget-specific rules
   in this file since those blocks (poll aside) are still static
   placeholder content, not real folder-based blocks yet. */

.av-cryoblue-promo-block { display: flex; align-items: center; gap: 14px; }
.av-cryoblue-promo-icon { width: 44px; height: 44px; flex-shrink: 0; border-radius: 6px; background: var(--bg-3); border: 1px solid var(--av-accent); display: flex; align-items: center; justify-content: center; font-size: 20px; box-shadow: inset 0 0 8px rgba(86, 184, 232, 0.08); }
.av-cryoblue-promo-body { display: flex; flex-direction: column; gap: 4px; }
.av-cryoblue-promo-title { color: var(--av-accent); font-weight: 600; font-size: 12px; }
.av-cryoblue-promo-timer { color: var(--av-text-dim); font-size: 11px; }
.av-cryoblue-promo-price { color: var(--av-accent); font-weight: 700; font-size: 12px; }

.av-cryoblue-event-list { list-style: none; margin: 0; padding: 0; }
.av-cryoblue-event-list li { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--bg-3); font-size: 13px; transition: all var(--transition-fast); }
.av-cryoblue-event-list li:hover { background: rgba(86, 184, 232, 0.05); padding-left: 4px; }
.av-cryoblue-event-icon { font-size: 16px; }
.av-cryoblue-event-info { display: flex; flex-direction: column; gap: 2px; flex: 1; }
.av-cryoblue-event-name { color: var(--av-text); font-weight: 600; }
.av-cryoblue-event-range { color: var(--av-text-dim); font-size: 12px; }
.av-cryoblue-event-time { color: var(--av-text-muted); font-size: 12px; }
.av-cryoblue-event-active { color: var(--av-accent); font-weight: 700; text-transform: uppercase; }

.av-cryoblue-leaderboard { width: 100%; border-collapse: collapse; font-size: 12px; }
.av-cryoblue-leaderboard th { text-align: left; padding: 6px 8px; color: var(--av-text-dim); text-transform: uppercase; font-size: 10px; border-bottom: 1px solid var(--line-2); background: var(--bg-2); font-weight: 700; }
.av-cryoblue-leaderboard td { padding: 8px; border-bottom: 1px solid var(--bg-3); color: var(--av-text-muted); transition: all var(--transition-fast); }
.av-cryoblue-leaderboard tr:hover td { background: rgba(86, 184, 232, 0.05); color: var(--av-text); }

.av-cryoblue-poll-bar { display: flex; height: 10px; border-radius: 4px; overflow: hidden; margin-bottom: 10px; box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.3); }
.av-cryoblue-poll-yes { background: linear-gradient(to right, var(--av-accent), #6bc8f5); flex: 1; }
.av-cryoblue-poll-no { background: linear-gradient(to right, #c8534a, #d8635a); flex: 1; }
.av-cryoblue-poll-tally { color: var(--av-text-muted); margin: 0 0 10px; font-size: 12px; }
.av-cryoblue-poll-no-text { color: #d8635a; }
.av-cryoblue-poll-options { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
.av-cryoblue-poll-option { background: var(--bg-3); border: 1px solid var(--line-2); color: var(--av-text); padding: 10px; border-radius: 4px; font-size: 12px; text-transform: uppercase; cursor: pointer; font-weight: 600; transition: all var(--transition-normal); }
.av-cryoblue-poll-option:hover { border-color: var(--av-accent); background: var(--bg-4); color: var(--av-accent); box-shadow: var(--glow-accent); transform: scale(var(--av-hover-scale)); }

.av-cryoblue-accent-light { color: var(--av-accent) !important; }
.av-cryoblue-accent-teal { color: var(--av-accent); }

.av-flash-success { background: rgba(104, 206, 161, 0.12); border: 1px solid var(--good); color: #a8e8c2; padding: 12px 16px; border-radius: 4px; margin-bottom: 18px; font-size: 13px; box-shadow: inset 0 0 8px rgba(104, 206, 161, 0.08); text-transform: capitalize; }
.av-flash-error { background: rgba(235, 104, 114, 0.12); border: 1px solid var(--bad); color: #ff9db0; padding: 12px 16px; border-radius: 4px; margin-bottom: 18px; font-size: 13px; box-shadow: inset 0 0 8px rgba(235, 104, 114, 0.08); text-transform: capitalize; }

@media (max-width: 1000px) {
    .row-hero, .row-triple { grid-template-columns: 1fr; }
    .av-cryoblue-hero-block { grid-template-columns: 1fr; }
    .av-cryoblue-article-tiles { grid-template-columns: 1fr; }
    .av-cryoblue-navbar-inner { flex-direction: column; align-items: flex-start; }
    .av-cryoblue-body-layout { flex-direction: column; }
    .av-cryoblue-sidebar { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    * { transition-duration: 0.01ms !important; }
}

/* ────────────────────────────────────────────────────────────
   SHARED PAGE COMPONENTS
   Used by the standalone pages that render outside the region
   grid — install.php, install-dev.php, login.php, register.php,
   forgot-password.php, forgot-username.php, reset-password.php,
   verify-email.php, resend-verification.php.

   These lived in themes/default/style/style.css until that theme
   was deleted in 0.8.19; every one of those pages went unstyled
   with it. Any future theme must provide this set as well as the
   .block-* set — see docs/BLOCK_ARCHITECTURE_STANDARD.md.
   ──────────────────────────────────────────────────────────── */
/* Used only by install.php/install-dev.php's <body> — the two pages
   that render raw HTML directly (no Theme::header()/footer(), no DB/
   theme system yet to call them with). Flex-centers the .av-card both
   axes within the full viewport, matching the centered-card look
   includes/installer-landing.php's own separate stylesheet already uses
   for the same kind of "single card on an otherwise empty page" layout.
   min-height:100vh is already inherited from the theme's own generic
   `body` rule further up this file, but restated here so this rule is
   self-contained/readable on its own without needing to trace back to
   that unrelated rule to understand the centering setup. */
.av-standalone-page {
    padding: 40px 20px;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
}

.av-card {
    max-width: 560px;
    margin: 0 auto;
    background: var(--av-panel);
    border: 1px solid var(--line-2);
    border-radius: 6px;
    padding: 32px;
    box-shadow: 0 0 12px rgba(86, 184, 232, 0.12), inset 0 0 16px rgba(86, 184, 232, 0.06), var(--shadow-md);
}
/* 600px — matches the live aventuscms.com landing page's own
   .installer-landing max-width exactly (includes/installer-landing.php,
   a separate standalone stylesheet used only when $db === null in
   index.php). install.php/install-dev.php are the only pages that use
   this modifier (plain .av-card alone, 560px, is what login/register/
   etc. use), so narrowing it here doesn't touch anything else. */
.av-card-wide { max-width: 600px; }
/* account.php only — and the pattern to reuse for any future page that
   renders the full region layout (see account.php's own comment on
   that). The homepage's own main column (.av-region-main in home.php)
   has no width cap at all — flex:1 on a flex parent with no max-width,
   so blocks there fill the entire space between the sidebars. This
   matches that exactly rather than picking another fixed pixel value:
   100% fills whatever room .av-region-main's own flex:1 gives it,
   growing or shrinking with the sidebars/viewport the same way the
   homepage's block content already does, instead of stopping short at
   an arbitrary width while the homepage's content keeps going. */
.av-account-card { max-width: 100%; }
/* Avatar header row (account.php) — sits at the top-left of the card,
   before the h1. Circle uses the exact same "colored ring + centered
   glyph" shape as .av-cryoblue-brand-icon in the navbar (this file,
   further up), just larger, so a user with no avatar_url set still gets
   a deliberate, on-brand placeholder instead of a blank box or a
   generic broken-image icon. Both the <img> and the initial <span>
   always exist in the DOM; account.php's inline script toggles which is
   hidden as the Avatar URL field changes (see that file's own comment),
   and the img's own onerror does the same on a broken/unreachable URL. */
.av-account-header {
    display: flex;
    align-items: center;
    gap: 18px;
    margin-bottom: 20px;
}
.av-account-avatar {
    width: 144px;
    height: 144px;
    flex: none;
    border-radius: 6px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-3);
    border: 2px solid var(--av-accent);
    box-shadow: 0 0 12px rgba(86, 184, 232, 0.25);
}
.av-account-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* Without this, the hidden ATTRIBUTE (toggled by account.php's own
   script — checking Remove, choosing a file, or the img's own onerror
   on a broken URL) has no visible effect: the browser's built-in
   "[hidden] { display: none; }" rule has LOWER specificity than the
   "display: block" rule directly above (a plain attribute selector vs.
   a class+type selector), so that rule was silently winning and the
   image stayed visible regardless of the hidden attribute. Both it and
   the initial-letter fallback would then render at once, and since the
   parent is display:flex, the two got squeezed side by side into the
   same box instead of one cleanly replacing the other. This selector's
   extra specificity (class + type + attribute) is what's needed to win
   back over the rule above. */
.av-account-avatar img[hidden] {
    display: none;
}
.av-account-avatar-initial {
    font-family: 'Michroma', sans-serif;
    font-size: 48px;
    font-weight: 700;
    color: var(--av-accent);
}
.av-account-header-text h1 {
    margin: 0 0 4px;
}
/* Bio, shown as a corner-bracket-framed quote directly under the
   username in the account header — moved here from a plain label/
   value row in the Overview panel (still fed by the same $currentBio
   the Profile tab's own edit form uses). Four small L-shaped corner
   marks (thin top/side pairs, no full border) rather than a solid box
   or a left-accent bar — the classic "framed quotation" look, built
   from four absolutely-positioned spans since a single element only
   gets two pseudo-elements (::before/::after), not the four needed for
   all four corners at once. position:relative on the blockquote itself
   is what anchors those four spans' position:absolute corners.
   white-space:pre-wrap preserves whatever line breaks the member typed
   into the (multi-line) Bio textarea, the same way a browser would
   render them there, rather than collapsing everything onto one line.
   No default margin/padding relied on — <blockquote> is otherwise
   unstyled by every browser's UA stylesheet inside this theme's CSS
   reset scope, so every value below is stated explicitly. */
.av-account-bio-quote {
    position: relative;
    display: inline-block;
    margin: 10px 0 0;
    padding: 10px 16px;
    font-size: 13px;
    color: var(--av-text-muted);
    line-height: 1.5;
    max-width: 480px;
    background: var(--bg-2);
    border-radius: 4px;
}
/* white-space:pre-wrap lives HERE, not on .av-account-bio-quote itself
   — scoped to only the actual bio text content, with nothing else
   inside this span. Putting it on the outer blockquote instead (the
   first version of this) preserved the PHP template's OWN indentation
   whitespace/newlines between the four corner spans and the text as
   real blank lines too, not just the member's own line breaks within
   their bio — that's what was inflating the box to ~140px tall and
   ~210px wide for a single short line: each template newline (plus its
   leading indentation spaces) became its own preserved, visible line.
   position:relative + z-index:1 lifts the actual text above the big
   decorative quote glyph below (.av-account-bio-quote::before), so the
   glyph reads as sitting BEHIND the words rather than the words
   rendering behind it. */
.av-account-bio-quote-text {
    position: relative;
    z-index: 1;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
/* Oversized decorative quote-mark icon, watermarked in behind the text
   — an actual SVG icon rather than a Unicode “ character, since a
   typographic curly-quote glyph (the first version of this) renders as
   just two small comma-shaped curls at most font sizes/faces, not the
   bold, immediately-recognizable "quote icon" shape this effect wants;
   an SVG shape sizes and positions predictably regardless of which
   fonts are actually installed. Two copies — opening mark top-left,
   closing mark bottom-right, the SAME icon rotated 180deg rather than a
   second distinct glyph, matching the classic "opening/closing quote"
   pull-quote convention. Low opacity (partial, not the full text color)
   so both still read as a background texture rather than competing
   focal points. Sits UNDER the actual text (z-index:0, versus the text
   span's own z-index:1) rather than beside it, so it overlaps the
   text's own corner words instead of pushing the layout wider.
   pointer-events:none so neither can ever intercept a click/selection
   meant for the text above them. */
.av-account-bio-quote-mark {
    position: absolute;
    width: 28px;
    height: 28px;
    fill: var(--av-text-dim);
    opacity: 0.65;
    z-index: 0;
    pointer-events: none;
}
.av-account-bio-quote-mark-start {
    left: 9px;
    top: 3px;
    transform: rotate(180deg);
}
.av-account-bio-quote-mark-end {
    right: 9px;
    bottom: 3px;
}
.av-account-bio-quote-corner {
    position: absolute;
    width: 10px;
    height: 10px;
    border: 2px solid var(--line-3);
}
.av-account-bio-quote-corner-tl {
    top: 0;
    left: 0;
    border-right: none;
    border-bottom: none;
}
.av-account-bio-quote-corner-tr {
    top: 0;
    right: 0;
    border-left: none;
    border-bottom: none;
}
.av-account-bio-quote-corner-bl {
    bottom: 0;
    left: 0;
    border-right: none;
    border-top: none;
}
.av-account-bio-quote-corner-br {
    bottom: 0;
    right: 0;
    border-left: none;
    border-top: none;
}
.av-account-header-text p {
    margin: 0;
}
/* Profile/Security pill tabs (account.php) — bare, unscoped mirror of
   Settings' own .settings-pills/.settings-pill (includes/css/
   admin.css, under .av-admin), rebuilt against this theme's own
   --av-, --bg-, and --line- tokens instead of admin's --aa- ones, same
   duplication approach this file already uses for .av-dropdown- rules
   and .av-dropdown-check (that admin component was never actually
   admin-only either — see includes/js/cryoblue-dropdown.js's own
   comment). .settings-panel[hidden] is the only other piece needed
   here — Settings' own version lives under .av-admin
   .settings-panel[hidden] in admin.css, so it doesn't reach this
   public page and has to be restated bare. */
/* Folder tabs (Overview / Profile / Forum Settings / Private Messages,
   account.php) — bare, unscoped mirror of Settings' own
   .settings-folder-tabs/.settings-folder-tab/.settings-folder-panel-wrap
   (includes/css/admin.css, under .av-admin), rebuilt against this
   theme's own --av-, --bg-, and --line- tokens instead of admin's
   --aa- ones — same duplication approach already used for
   .settings-pills/.settings-pill just below. Square top corners,
   sitting flush above the rounded .settings-folder-panel-wrap box it
   belongs to, with the active tab's own background matching that
   panel so it reads as physically attached to it — the classic
   folder-tab look, same as Settings' own version. */
.settings-folder-tabs {
    display: flex;
    gap: 6px;
    margin-bottom: 0;
}
.settings-folder-tab {
    display: flex;
    align-items: center;
    gap: 8px;
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.05) 0%,
        rgba(86, 184, 232, 0.06) 100%
    );
    border: 1px solid rgba(86, 184, 232, 0.16);
    border-bottom: none;
    border-radius: 10px 10px 0 0;
    padding: 11px 20px;
    font-size: 12px;
    font-weight: 600;
    text-transform: none;
    letter-spacing: normal;
    color: var(--av-text-dim);
    cursor: pointer;
    margin-top: 0;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
    transition: all var(--transition-fast);
}
.settings-folder-tab svg {
    flex: none;
}
.settings-folder-tab:hover {
    color: var(--av-accent);
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.08) 0%,
        rgba(86, 184, 232, 0.12) 100%
    );
    border-color: rgba(86, 184, 232, 0.28);
    transform: none;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10);
}
.settings-folder-tab:not(.active):hover {
    box-shadow:
        inset 0 -1px 0 var(--av-accent),
        inset 0 1px 0 rgba(255, 255, 255, 0.10);
}
.settings-folder-tab.active {
    background: var(--av-panel);
    border-color: rgba(86, 184, 232, 0.22);
    color: var(--av-accent);
    box-shadow:
        inset 0 2px 0 var(--av-accent),
        inset 0 -1px 0 var(--av-accent),
        inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}
/* The "folder" itself — sits flush under the tabs (no border-radius on
   the top-left corner, so the active tab's own square top-left corner
   continues seamlessly into this box's edge) with rounded corners
   everywhere else. */
.settings-folder-panel-wrap {
    border: 1px solid var(--line-2);
    border-radius: 0 12px 12px 12px;
    background: var(--av-panel);
    padding: 20px;
    margin-bottom: 25px;
}
.settings-folder-panel-wrap[hidden] {
    display: none;
}
/* Forum Settings / Private Messages placeholder body — same honest
   "nothing here yet" treatment Settings' own Forum folder placeholder
   uses (.settings-forum-placeholder in includes/css/admin.css), a
   generic name here rather than forum-specific since account.php
   reuses this exact wrapper for both placeholders it needs. */
.av-account-placeholder {
    text-align: center;
    padding: 60px 20px;
}
.av-account-placeholder svg {
    display: block;
    margin: 0 auto 16px;
    color: var(--av-text-dim);
    opacity: 0.6;
}
.av-account-placeholder h2 {
    font-family: 'Michroma', sans-serif;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--av-text-muted);
    margin: 0 0 8px;
}
.av-account-placeholder p {
    font-size: 13px;
    color: var(--av-text-dim);
    line-height: 1.6;
    max-width: 420px;
    margin: 0 auto;
}
/* Overview folder's two-column layout — account basics in a compact box
   on the left (email, registration date, last active, local time),
   activity plus any optional profile details in a matching box on the
   right (post counts, and location/website/bio once set), one row of two
   columns rather than one long stacked list. Neither column carries a
   heading: the key and the -col-heading rule written for one were both
   deleted, since the panel has shipped headless since birth and only one
   heading string ever existed for two columns. */
.av-account-overview-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}
.av-account-overview-col {
    background: var(--bg-2);
    border: 1px solid var(--line-1);
    border-radius: 6px;
    padding: 4px 16px;
}
.av-account-overview-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    padding: 8px 0;
    border-bottom: 1px solid var(--line-1);
}
.av-account-overview-row:last-child {
    border-bottom: none;
}
.av-account-overview-label {
    font-size: 13px;
    color: var(--av-text-dim);
    flex: none;
}
/* Colon suffix scoped to just these Overview summary labels — CSS
   rather than editing the underlying language strings directly, since
   several of them (location_label/website_url_label/bio_label) are
   shared with the actual Profile tab's edit-form <label> elements,
   which shouldn't pick up a trailing colon just because Overview
   wants one. */
.av-account-overview-label::after {
    content: ":";
}
.av-account-overview-value {
    font-size: 13px;
    color: var(--av-text);
    text-align: right;
    word-break: break-word;
}
.settings-pills-wrapper {
    margin-bottom: 25px;
}
.settings-pills {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    background: var(--bg-1);
    border: 1px solid var(--line-2);
    border-radius: 10px;
    padding: 5px;
}
.settings-pill {
    display: flex;
    align-items: center;
    gap: 7px;
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.03) 0%,
        rgba(86, 184, 232, 0.04) 100%
    );
    border: 1px solid rgba(86, 184, 232, 0.10);
    color: var(--av-text-muted);
    padding: 7px 12px;
    border-radius: 7px;
    font-size: 12px;
    font-weight: 500;
    text-transform: none;
    letter-spacing: normal;
    cursor: pointer;
    margin-top: 0;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
    transition: all var(--transition-fast);
}
.settings-pill svg {
    flex: none;
}
.settings-pill:hover {
    color: var(--av-accent);
    border-color: rgba(86, 184, 232, 0.28);
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.06) 0%,
        rgba(86, 184, 232, 0.10) 100%
    );
    transform: none;
    box-shadow:
        var(--glow-accent),
        inset 0 -1px 0 var(--av-accent),
        inset 0 1px 0 rgba(255, 255, 255, 0.09);
}
.settings-pill.active {
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.07) 0%,
        rgba(86, 184, 232, 0.14) 100%
    );
    border-color: rgba(86, 184, 232, 0.32);
    color: var(--av-accent);
    box-shadow:
        0 0 8px rgba(86, 184, 232, 0.16),
        inset 0 1px 0 rgba(255, 255, 255, 0.09);
}
.settings-panel[hidden] {
    display: none;
}
/* Avatar Gallery popup trigger + modal (account.php, Settings → CMS →
   Avatars → Avatar Gallery) — same themed-overlay pattern this theme's
   login popup already uses (.av-login-popup-overlay/-card/-close, just
   below this file's SHARED PAGE COMPONENTS section) rather than a new
   one-off mechanism: dark blurred backdrop, a centered .av-card-based
   dialog, a floating × close button, shown/hidden via .is-visible
   toggled by account.php's own inline script. Kept as its own class set
   (not literally reusing .av-login-popup-*) since this dialog needs a
   wider card for the thumbnail grid and lives inside a form rather than
   header.php — but every visual property mirrors that pattern on
   purpose, for the same reason .av-account-avatar itself already
   reuses this file's shared design language instead of inventing a new
   one. */
.av-avatar-gallery-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(3, 6, 10, 0.75);
    backdrop-filter: blur(3px);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-normal), visibility var(--transition-normal);
    padding: 20px;
}
.av-avatar-gallery-overlay.is-visible {
    opacity: 1;
    visibility: visible;
}
.av-avatar-gallery-card {
    position: relative;
    margin: 0;
    width: 100%;
    max-width: 640px;
    max-height: 90vh;
    overflow-y: auto;
}
.av-avatar-gallery-close {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: var(--av-text-dim);
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    transition: color var(--transition-fast);
}
/* Red on hover, not the theme's accent blue — matches the ×-to-close
   convention this app's OTHER themed popup (the admin Log Viewer's own
   .av-log-viewer-close, includes/css/admin.css) already uses, since a
   close/dismiss action reads as a "leave this" action rather than a
   "go deeper" one. */
.av-avatar-gallery-close:hover {
    color: var(--bad);
    background: transparent;
    box-shadow: none;
    transform: none;
}
.av-avatar-gallery-grid {
    margin-top: 16px;
}
/* [hidden] companion — this element carries no display property of
   its own anymore (that moved to .av-avatar-gallery-page below), so
   this one's actually redundant against the browser default... except
   the pattern this file uses everywhere a JS-toggled [hidden] sits
   behind a class-based display rule is kept explicit and consistent
   here anyway, so a future rule added to .av-avatar-gallery-grid can
   never silently reintroduce this exact bug without the companion
   already being right here to catch it. */
.av-avatar-gallery-grid[hidden] {
    display: none;
}
/* One page of up to 36 (6×6) thumbnails — a FIXED 6-column grid, not
   the auto-fill/minmax sizing an unpaginated grid would use, so a full
   page always lays out as exactly 6 rows regardless of viewport width
   (see account.php's own $avatarGalleryPageSize). Only one
   .av-avatar-gallery-page is ever visible per folder at a time (see
   this file's own script) — same reasoning .av-avatar-gallery-grid
   itself uses for switching between folders, one level up. */
.av-avatar-gallery-page {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 10px;
}
.av-avatar-gallery-page[hidden] {
    display: none;
}
/* Page-jump control (« 1 / N »), sitting below the grid — one shared
   bar re-scoped to whichever folder is active (see account.php's own
   script), not one per folder. Hidden entirely via [hidden] when the
   active folder fits on a single page. */
.av-avatar-gallery-pagination {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--line-2);
}
.av-avatar-gallery-pagination[hidden] {
    display: none;
}
/* A real 3-column grid, not a centered flex row — [left group] [/]
   [right group] with the OUTER two columns set to 1fr each so the
   MIDDLE column (the bare "/" character, sized to its own content via
   "auto") always lands at the exact horizontal midpoint of the bar's
   full width, matching the true center of the avatar grid above it
   (which spans the same card-content width) — i.e. right between the
   two inner thumbnail columns of the 6-wide grid, regardless of how
   much wider the left group (arrow + page-number input) is than the
   right group (total-pages number + arrow). Centering the CLUSTER as
   one unit (an earlier version of this rule, via inline-flex +
   text-align:center) can't guarantee this: an asymmetric cluster's own
   bounding-box center isn't the same point as where the "/" divider
   inside it actually sits once one side is visibly wider than the
   other. */
.av-avatar-gallery-pagination-controls {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 6px;
}
.av-avatar-gallery-pagination-left {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
}
.av-avatar-gallery-pagination-right {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
}
.av-avatar-gallery-page-sep {
    font-size: 12px;
    color: var(--av-text-dim);
}
.av-avatar-gallery-page-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    margin: 0;
    border-radius: 4px;
    border: 1px solid var(--line-2);
    background: var(--bg-3);
    color: var(--av-text-muted);
    font-size: 10px;
    cursor: pointer;
    text-transform: none;
    transition: all var(--transition-fast);
}
.av-avatar-gallery-page-btn:hover:not(:disabled) {
    border-color: var(--av-accent);
    background: var(--bg-4);
    color: var(--av-accent);
    box-shadow: var(--glow-accent);
}
.av-avatar-gallery-page-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}
/* Overrides this theme's own generic "every text input is 100% wide
   with a 10px bottom margin" rule (this file, near the top). Qualified
   as input[type="text"].av-avatar-gallery-page-input, NOT just the bare
   class alone — a bare single class selector (specificity 0,1,0) is
   actually LESS specific than that generic rule's own
   input[type="text"] (specificity 0,1,1, since a type+attribute pair
   outweighs one class), so it was silently losing and rendering at the
   generic rule's 100% width this whole time regardless of the value
   set here. This qualified form (0,2,1) wins unambiguously. */
.av-avatar-gallery-pagination input[type="text"].av-avatar-gallery-page-input {
    width: 30px;
    max-width: 30px;
    text-align: center;
    padding: 3px 2px;
    margin: 0;
    border-radius: 4px;
    border: 1px solid var(--line-2);
    background: #09141b;
    color: var(--av-text);
    font-size: 12px;
}
.av-avatar-gallery-page-total-value {
    font-size: 12px;
    color: var(--av-text-dim);
    white-space: nowrap;
}
.av-avatar-gallery-item {
    width: 100%;
    aspect-ratio: 1 / 1;
    padding: 0;
    margin: 0;
    border-radius: 6px;
    border: 2px solid var(--line-2);
    background: var(--bg-3);
    overflow: hidden;
    cursor: pointer;
    transition: all var(--transition-fast);
}
.av-avatar-gallery-item:hover {
    border-color: var(--line-3);
    transform: scale(1.04);
}
.av-avatar-gallery-item.is-selected {
    border-color: var(--av-accent);
    box-shadow: var(--glow-accent);
}
.av-avatar-gallery-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.av-card h1 { font-size: 22px; margin-top: 0; color: #fff; text-transform: uppercase; letter-spacing: 0.02em; }
.av-card h2 { font-size: 15px; color: var(--av-accent); text-transform: uppercase; letter-spacing: 0.05em; }
.av-card label { display: block; margin: 14px 0 4px; font-size: 13px; color: var(--av-text-muted); }

/* Simple fading rule separating a .av-card's title from whatever smaller
   text/content sits right below it (e.g. install.php's step navigator
   under its h1) — same gradient-to-transparent technique as
   .av-cryoblue-footer-copyright::after just further up this file, kept
   deliberately plain (no glow) since this sits inside ordinary page
   content rather than a modal/dialog. */
.av-card-divider {
    height: 1px;
    margin: 14px 0 20px;
    background: linear-gradient(to right, var(--av-accent), transparent);
    /* 0.9, not the 0.5 this used to be: at half strength over the
       card's own dark the accent read as grey, and fading dividers are accent blue (2026-09-19); the amber ones are the shine rules, which set their own colour. */
    opacity: 0.9;
}
/* The shine rules keep the strength they always had. */
.av-card-divider.av-shine-rule { opacity: 0.5; }
/* Reusable center-out fade modifier — same symmetric gradient the
   installer welcome hero uses below, but as a class any centered card
   can opt into (the module notice boxes in includes/modules.php do).
   A one-directional left-to-right fade reads wrong under a centered
   title, so every .av-center card that wants a divider should pair
   .av-card-divider with this. */
.av-card-divider-center {
    background: linear-gradient(to right, transparent, var(--av-shine-base, var(--av-accent)), transparent);
}

/* Center-out fade instead of the left-to-right default above, scoped to
   just the welcome hero's divider (sits directly under the centered
   "Welcome to AventusCMS" title) — a symmetric fade matches that
   centered layout better than a one-directional line would, whereas
   the default left-to-right fade still suits the OTHER .av-card-divider
   use (under the left-aligned "AventusCMS Installer" title on steps
   1-4), which this more specific selector leaves untouched. */
.av-installer-welcome .av-card-divider {
    background: linear-gradient(to right, transparent, var(--av-accent), transparent);
}

/* Install-detected status pill (install.php/install-dev.php's new step-0
   landing screen) — reuses the same --good/dim-neutral color language
   .av-pass/.av-fail already use elsewhere on this page for consistency,
   just as a small rounded badge rather than an inline checklist icon,
   since this is a single headline status rather than one item in a
   list. */
.av-install-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    border-radius: 20px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 18px;
}
.av-install-status.is-installed {
    background: rgba(104, 206, 161, 0.12);
    border: 1px solid var(--good);
    color: var(--good);
}
.av-install-status.is-not-installed {
    background: rgba(114, 137, 150, 0.12);
    border: 1px solid var(--line-2);
    color: var(--av-text-dim);
}

/* Step-0 landing hero (install.php/install-dev.php) — centered icon +
   large title + subtitle + status pill + description + stacked full-
   width action buttons + a plain flat-line footnote, replacing the
   standard small uppercase .av-card h1 every other step on this same
   page uses (that h1/.av-card-divider pair is skipped entirely for step
   0 — see install.php's own PHP). Scoped entirely under
   .av-installer-welcome so none of these overrides leak onto steps
   1-4's own h1/buttons elsewhere in this same .av-card. */
.av-installer-welcome {
    text-align: center;
    /* .av-card already carries 32px padding (shared with steps 1-4);
       this closes the gap to the reference's 40px card padding without
       touching that shared rule. */
    padding: 8px 0 0;
}
.av-installer-welcome-icon {
    /* Values below are matched directly against the live
       aventuscms.com landing page's own installer-landing.php styles
       (.icon/.installer-card h1/.subtitle/p/.installer-actions/.btn/
       .footer-note there) so this renders at the same font sizes,
       spacing, and button size as that page — not just a similar look. */
    font-size: 64px;
    line-height: 1;
    margin-bottom: 20px;
    opacity: 0.8;
}
.av-installer-welcome h1 {
    font-size: 32px;
    line-height: 1.2;
    margin: 0 0 12px;
    /* Lighter than the theme's base --av-accent (#56b8e8) on purpose —
       matches installer-landing.php's own --av-accent-light value
       exactly (same hex), used there for this identical heading. */
    color: #7ed7ff;
    text-transform: none;
    letter-spacing: 1px;
}
.av-installer-welcome-subtitle {
    font-size: 14px;
    color: var(--av-text-muted);
    margin: 0 0 30px;
    line-height: 1.6;
}
.av-installer-welcome .av-install-status {
    margin-bottom: 20px;
}
.av-installer-welcome-desc {
    font-size: 13px;
    color: var(--av-text-dim);
    line-height: 1.7;
    /* 32px, not the reference's own 24px on this paragraph — the
       reference actually renders a 32px gap here because
       .installer-actions' margin-top:32px WINS the margin-collapse
       against this paragraph's margin-bottom:24px (adjacent block
       margins collapse to the larger value, not the sum). Setting the
       gap directly here reaches the same 32px visual result without
       depending on that same collapsing behavior actually happening in
       this markup. */
    margin: 0 0 32px;
}
.av-installer-welcome-actions {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
/* Full-width + stacked, unlike this same .av-btn class's normal inline
   sizing elsewhere on the page (Continue/Create Account/etc buttons) —
   scoped to just this wrapper so those other buttons are unaffected.
   padding/font-size match installer-landing.php's own .btn exactly. */
.av-installer-welcome-actions .av-btn {
    width: 100%;
    margin: 0;
    padding: 10px 18px;
    font-size: 12px;
    box-sizing: border-box;
}
.av-installer-welcome-footnote {
    margin-top: 40px;
    padding-top: 20px;
    border-top: 1px solid var(--line-2);
    font-size: 11px;
    color: var(--av-text-dim);
    line-height: 1.6;
}

.av-btn {
    display: inline-block;
    margin: 4px 8px 4px 0;
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.05) 0%,
        rgba(86, 184, 232, 0.07) 100%
    );
    border: 1px solid rgba(86, 184, 232, 0.20);
    color: var(--av-text);
    padding: 10px 18px;
    border-radius: 6px;
    font-size: 12px;
    text-transform: uppercase;
    font-weight: 600;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
    transition: all var(--transition-normal);
}
.av-btn:hover {
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.09) 0%,
        rgba(86, 184, 232, 0.16) 100%
    );
    border-color: rgba(86, 184, 232, 0.45);
    color: var(--av-accent);
    box-shadow:
        0 4px 16px rgba(0, 0, 0, 0.30),
        0 0 12px rgba(86, 184, 232, 0.22),
        inset 0 1px 0 rgba(255, 255, 255, 0.12);
    transform: scale(var(--av-hover-scale));
}
.av-btn-secondary {
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.04) 0%,
        rgba(59, 90, 107, 0.07) 100%
    );
    border-color: rgba(59, 90, 107, 0.55);
    color: var(--av-text-muted);
}
.av-btn-secondary:hover {
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.08) 0%,
        rgba(86, 184, 232, 0.14) 100%
    );
    border-color: rgba(86, 184, 232, 0.45);
    color: var(--av-accent);
    box-shadow:
        0 4px 16px rgba(0, 0, 0, 0.30),
        0 0 12px rgba(86, 184, 232, 0.22),
        inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
/* Size variant — composes with the base and with either colour variant
   above (.av-btn-secondary / .av-btn-danger), which are purely colour and
   set no metrics of their own.

   The values match .av-admin button / .av-btn-sm in this theme's
   admin.css exactly, so a compact public button is the same box as an
   admin section button rather than a second, slightly-different small
   size. Only padding and line-height are restated: the base .av-btn
   already agrees with the admin base on border-radius, font-size,
   uppercase and weight, so those would be duplication. line-height is
   pinned rather than left at `normal` for the same reason admin.css
   pins it — `normal` resolves off the font's own metrics, so the
   rendered height would drift from the admin's on any font change.

   Unlike admin.css's .av-btn-sm this adds no margin-right: the public
   .av-btn base already carries `margin: 4px 8px 4px 0`, so the spacing
   the admin variant has to add by hand is present already. */
.av-btn-sm {
    padding: 6px 12px;
    line-height: 16px;
}
.av-btn-danger {
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.04) 0%,
        rgba(235, 104, 114, 0.09) 100%
    );
    border-color: var(--bad);
    color: var(--bad);
}
.av-btn-danger:hover {
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.07) 0%,
        rgba(235, 104, 114, 0.16) 100%
    );
    border-color: var(--bad);
    color: #ff9db0;
    box-shadow:
        0 4px 16px rgba(0, 0, 0, 0.30),
        0 0 12px rgba(235, 104, 114, 0.25),
        inset 0 1px 0 rgba(255, 255, 255, 0.10);
}
/* Full-bleed primary submit, scoped to .av-card — the standalone form
   pages this was written for (login/register/forgot-password/reset/resend/
   forgot-username, index.php's login block, both installers, and both
   themes' login popups) all wrap their form in that card, so every case
   that wants the treatment still gets it.
   Previously this was a bare `button.av-btn`, which also matched the admin
   panel: this stylesheet loads behind admin.php too, so every admin submit
   using .av-btn on a <button> rather than an <a> inherited the full width
   AND the 20px top margin. The module admin screens were the visible
   casualty; .av-card appears nowhere in admin markup, so scoping here ends
   the leak at the source instead of asking each admin rule to opt back out. */
.av-card button.av-btn { width: 100%; margin-top: 20px; }
/* account.php only — buttons stay their natural content-fit width and
   sit flush left, rather than the full-width block treatment
   button.av-btn uses everywhere else (login/register/forgot-password/
   etc., where a single full-width primary action per standalone form
   suits a simpler page). account.php has four buttons spread across
   three separate forms on the SAME page (Save Profile, Choose From
   Gallery, Update Email, Update Password) — all of them full-width
   read as needlessly heavy and stacked, and .av-account-card's own
   max-width:100% (this file, further down) already makes the card
   itself as wide as the region gives it, unlike the narrow fixed-width
   .av-card those other standalone pages use, so a full-width button
   here would be dramatically wider than on any of them anyway.
   Specificity (0,3,1) beats the .av-card button.av-btn rule's (0,2,1)
   regardless of source order, so this doesn't depend on appearing
   later in the file to win. The .av-card half of the selector is
   redundant for matching — account.php's wrapper is class="av-card
   av-account-card", so .av-account-card alone would match it — and is
   there purely to stay one class ahead of the base rule now that the
   base is itself scoped to .av-card. */
.av-card.av-account-card button.av-btn {
    width: auto;
    margin-top: 20px;
}

.av-steps { display: flex; align-items: center; gap: 0; margin-bottom: 24px; flex-wrap: wrap; }
/* Numbered-badge stepper, replacing the old plain "1. Requirements →"
   text-with-arrow row — each step is a small pill (circular number +
   label) linked by a thin connector line, with three visual states:
   untouched (dim), is-active (accent, glowing, current position), and
   is-done (good/green, steps already passed) so progress reads at a
   glance rather than just which single word happens to be bold. */
.av-step-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 10px 5px 6px;
    border-radius: 20px;
    font-size: 12px;
    color: var(--av-text-dim);
    transition: all var(--transition-fast);
}
.av-step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    flex: none;
    border-radius: 50%;
    border: 1px solid var(--line-2);
    background: var(--bg-2);
    font-size: 11px;
    font-weight: 700;
    color: var(--av-text-dim);
    transition: all var(--transition-fast);
}
.av-step-label { white-space: nowrap; }
.av-step-item.is-active {
    background: rgba(86, 184, 232, 0.1);
}
.av-step-item.is-active .av-step-num {
    border-color: var(--av-accent);
    background: var(--av-accent);
    color: #04141d;
    box-shadow: var(--glow-accent);
}
.av-step-item.is-active .av-step-label {
    color: var(--av-accent);
    font-weight: 700;
}
.av-step-item.is-done .av-step-num {
    border-color: var(--good);
    background: rgba(104, 206, 161, 0.14);
    color: var(--good);
}
.av-step-item.is-done .av-step-label {
    color: var(--av-text-muted);
}
.av-step-connector {
    width: 20px;
    height: 1px;
    background: var(--line-2);
    flex: none;
    margin: 0 2px;
}

.av-checklist { list-style: none; padding: 0; }
.av-checklist li { padding: 8px 12px; border-radius: 4px; margin-bottom: 6px; background: var(--bg-3); border: 1px solid var(--line-1); }
.av-pass { color: var(--good); font-weight: 700; }
.av-fail { color: var(--bad); font-weight: 700; }

.av-errors { background: rgba(235, 104, 114, 0.12); border: 1px solid var(--bad); color: #ff9db0; padding: 12px 16px; border-radius: 4px; margin-bottom: 18px; font-size: 13px; text-transform: capitalize; }
.av-errors ul { margin: 0; padding-left: 18px; }
.av-warning { background: rgba(216, 170, 81, 0.12); border: 1px solid var(--warn); color: #ffd98a; padding: 12px 16px; border-radius: 4px; margin-bottom: 18px; font-size: 13px; text-transform: capitalize; }
.av-notice { background: var(--bg-3); border: 1px solid var(--line-2); color: var(--av-text-muted); padding: 12px 16px; border-radius: 4px; margin-bottom: 18px; font-size: 13px; text-transform: capitalize; }
/* "Switch to the dev installer" callout (install.php/install-dev.php,
   $looksLocal) — previously a bare .av-hint paragraph floating above
   the stepper with no visual weight of its own. A light accent-tinted
   box (not the neutral .av-notice above) since this is specifically a
   helpful dev-environment tip, not a generic informational note —
   the tint gives it a distinct, slightly more inviting presence without
   being as loud as .av-warning. */
.av-dev-hint-box {
    background: rgba(86, 184, 232, 0.06);
    border: 1px solid var(--line-2);
    border-radius: 6px;
    padding: 12px 16px;
    margin-bottom: 20px;
    font-size: 13px;
    color: var(--av-text-muted);
    line-height: 1.5;
}
.av-dev-hint-box a {
    font-weight: 700;
    color: var(--av-accent);
}
.av-dev-hint-box a:hover {
    text-decoration: underline;
}
.av-dev-banner { background: rgba(216, 170, 81, 0.12); border: 1px solid var(--warn); color: #ffd98a; padding: 10px 16px; border-radius: 4px; margin-bottom: 20px; font-size: 13px; text-align: center; font-weight: 700; }

.av-status { font-size: 13px; color: var(--av-text-dim); margin-bottom: 20px; }
.av-hint { font-size: 12px; color: var(--av-text-dim); margin-top: 4px; }
/* Accents a live VALUE inside an otherwise dim .av-hint — e.g. the actual
   name the Display Name field says you currently render as. Applied to a
   span around the value, not to the hint itself: colouring the whole
   sentence makes ordinary guidance text compete with real content, when
   the only part worth the reader's eye is the value. Colour only; the
   size and spacing come from the .av-hint it sits inside. */
.av-hint-accent { color: var(--av-accent); }

/* Password strength indicator (register.php) — bar+label is a richer
   "how strong is this" signal, shown only once something's typed (see
   register.php's own script for why); the checklist below it always
   shows the exact 4 requirements validate_password_strength() in
   includes/security.php actually enforces, so a fully-checked list
   guarantees the submission will be accepted, not just "probably fine."
   data-strength on the wrapper (set by JS) drives both the fill color
   and the label color together via the two attribute selectors below,
   so they can never drift out of sync with each other. */
.av-password-strength { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.av-password-strength-track { flex: 1; height: 5px; border-radius: 3px; background: var(--bg-3); overflow: hidden; box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.3); }
.av-password-strength-fill { height: 100%; width: 0%; border-radius: 3px; background: var(--bad); transition: width var(--transition-normal), background-color var(--transition-normal); }
.av-password-strength-label { flex: none; min-width: 42px; text-align: right; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--av-text-dim); }
.av-password-strength[data-strength="weak"] .av-password-strength-fill { background: var(--bad); }
.av-password-strength[data-strength="fair"] .av-password-strength-fill { background: var(--warn); }
.av-password-strength[data-strength="good"] .av-password-strength-fill { background: var(--av-accent); }
.av-password-strength[data-strength="strong"] .av-password-strength-fill { background: var(--good); }
.av-password-strength[data-strength="weak"] .av-password-strength-label { color: var(--bad); }
.av-password-strength[data-strength="fair"] .av-password-strength-label { color: var(--warn); }
.av-password-strength[data-strength="good"] .av-password-strength-label { color: var(--av-accent); }
.av-password-strength[data-strength="strong"] .av-password-strength-label { color: var(--good); }

/* Requirement items start neutral/dim (not red) even before typing
   begins — an unmet requirement isn't a mistake yet, just not-yet-done,
   so this deliberately avoids the alarming all-red-X look a fresh,
   empty field would otherwise show immediately on page load. Only the
   strength bar above uses --bad for an actual warning signal. */
.av-password-requirements { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 5px 16px; }
.av-password-requirements li { position: relative; padding-left: 18px; font-size: 11px; color: var(--av-text-dim); transition: color var(--transition-fast); }
.av-password-requirements li::before { content: "\25CB"; position: absolute; left: 0; top: 0; font-size: 10px; color: var(--av-text-dim); }
.av-password-requirements li.is-met { color: var(--good); }
.av-password-requirements li.is-met::before { content: "\2713"; color: var(--good); }

.av-center { text-align: center; }
.av-checkbox-row { display: flex; align-items: center; gap: 8px; margin-top: 16px; font-size: 13px; color: var(--av-text-muted); }
/* No width rule: the theme's checkbox (above) sizes itself, and a
   `width: auto` here collapsed an appearance:none box to a 1px sliver. */
.av-login-links { margin-top: 18px; font-size: 13px; text-align: center; text-transform: capitalize; }
.av-login-links a { color: var(--av-accent); }
.av-login-links a:hover { text-decoration: underline; }
.av-card code { background: var(--bg-3); border: 1px solid var(--line-1); border-radius: 3px; padding: 1px 5px; font-size: 12px; color: var(--av-accent); }

/* Login popup (Settings → Registration → "Log In Button Behavior") —
   overlay wrapper only; everything inside it (.av-card, .btn, .av-errors,
   .av-login-links, form fields) is styled by the SHARED PAGE COMPONENTS
   rules above already, so it renders identically to the standalone
   login.php page. Hidden by default (opacity+visibility, not display:
   none, so the fade transition below actually has something to animate
   between); JS in header.php toggles .is-visible. */
.av-login-popup-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(3, 6, 10, 0.75);
    backdrop-filter: blur(3px);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-normal), visibility var(--transition-normal);
    padding: 20px;
}
.av-login-popup-overlay.is-visible {
    opacity: 1;
    visibility: visible;
}
.av-login-popup-card {
    position: relative;
    margin: 0;
    width: 100%;
    max-height: 90vh;
    overflow-y: auto;
}
.av-login-popup-close {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: var(--av-text-dim);
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    transition: color var(--transition-fast);
}
.av-login-popup-close:hover {
    color: var(--av-accent);
    background: transparent;
    box-shadow: none;
    transform: none;
}

/* Maintenance page (enforce_maintenance_mode() in includes/functions.php)
   — same fixed, centered-overlay technique as .av-login-popup-overlay
   just above, reused here because that's the exact "centered box on a
   dark backdrop" look being matched. Deliberately its own class rather
   than reusing .av-login-popup-overlay directly: that one is JS-toggled
   (starts hidden via opacity/visibility, a header.php script flips
   .is-visible on a button click) and ships its own close (X) button —
   neither applies here. A visitor can't dismiss maintenance mode, so
   this version is unconditionally visible with no interactive close
   affordance, sharing only the positioning/backdrop approach, not the
   show/hide mechanism. */
/* PUBLIC NOTICE PAGES ("module not found", "module inactive", etc —
   includes/modules.php's renderNotice()). Unlike the maintenance
   overlay below this is NOT a fixed overlay: it's ordinary page content
   that still shows the site's header/nav/footer, so it centers itself
   within whatever vertical space is left between them rather than
   against the viewport, with no header/footer height hardcoded
   anywhere.

   The :has() rule is what makes that possible. `body`'s grid
   (grid-template-rows: auto auto 1fr, further up this file) gives
   .av-cryoblue-body-layout the whole leftover row, but that wrapper is
   a flex row with align-items:flex-start, so <main> inside it sizes to
   its content and leaves the notice nothing to center within — a plain
   `min-height: 100%` here resolves against nothing and the card sits
   too high. Overriding <main> to align-self:stretch fills that row, and
   flex:1 passes the height down to .av-notice-page. Scoped via :has()
   so it applies only on pages that actually contain a notice and no
   other page's layout shifts. Browsers without :has() fall back to the
   60vh floor, which still centers the card, just a little higher up. */
/* AUTH CARD PAGES (login, register, forgot-password, forgot-username,
   resend-verification, reset-password) — a single card on a page that
   still shows the site's header/nav/footer, so it centers within whatever
   vertical space is left between them rather than against the viewport,
   with no header/footer height hardcoded anywhere.

   Same mechanism as the notice pages below, and for the same reason:
   `body`'s grid gives .av-cryoblue-body-layout the leftover row, but that
   wrapper is a flex row with align-items:flex-start, so <main> sizes to
   its content and the card has nothing to center within. align-self:
   stretch fills the row; the flex centering then has real height to work
   against.

   Scoped on .av-auth-card rather than on .av-card, because account.php
   ALSO renders a bare .av-card as a direct child of main — and it is a
   long page that must stay top-aligned. A marker class states which pages
   want this instead of inferring it from a shape that two unrelated
   layouts happen to share.

   Browsers without :has() fall back to the 60vh floor, which still
   centers the card, just a little higher up. */
.av-region-main:has(> .av-auth-card) {
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 60vh;
    padding: 40px 0;
    box-sizing: border-box;
}
/* width:100% so the card uses its full 560px on narrow screens rather
   than shrink-to-fitting as a flex item; the max-width still caps it.
   margin:0 is safe here — the flex parent does the horizontal centering
   that .av-card's own `margin: 0 auto` was doing. */
.av-region-main:has(> .av-auth-card) > .av-auth-card {
    width: 100%;
    margin: 0;
}
/* The heading of every auth card — Log In, Create Account, the two
   Forgot pages, Resend Verification, Reset Password — is centred: the
   card is a single centred block on an otherwise empty page, its
   button spans the full width and its footer links are centred, so a
   left-set title was the one thing on it not on the axis. On the
   marker class, not .av-card: the account page's card is a long,
   left-set page and keeps its heading where it is. */
.av-auth-card > h1 {
    text-align: center;
}

.av-region-main:has(> .av-notice-page) {
    align-self: stretch;
    display: flex;
}
.av-notice-page {
    flex: 1;
    min-height: 60vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px 20px;
    box-sizing: border-box;
}
/* The .av-card default 560px + `margin: 0 auto` centering is redundant
   inside the flex parent above, but harmless; width:100% just lets the
   card use the full 560px on narrow screens instead of shrink-to-fit. */
.av-notice-card {
    width: 100%;
    margin: 0;
}
/* Palette deliberately mirrors .settings-forum-placeholder in this
   theme's admin.css — the admin's own "nothing here" empty state, which
   is the same icon-over-title shape this notice is. Dim grey glyph,
   --warn title: a missing or inactive module is a caution state, not the
   neutral/informational one the theme's default cyan would signal, and
   the icon stays quiet so it frames the message instead of competing
   with it. Same --warn semantic used by .badge-unverified, the admin
   Danger Zone and the maintenance card further down, not a one-off
   colour.

   Icon sits above the divider (see renderNotice()'s markup order). */
.av-notice-icon {
    display: block;
    color: var(--av-text-dim);
    opacity: 0.6;
    line-height: 0;
    margin-bottom: 4px;
}
.av-notice-icon svg { display: inline-block; }
/* One property recolours the whole notice: the shared .av-shine-* component
   and .av-card-divider-center both read --av-shine-base, so the card does not
   override any paint directly. That matters for more than tidiness — a
   consumer-scoped background/colour rule would out-specify the shared
   component and silently suppress the shine. --warn because a missing or
   inactive module is a caution state, not the informational one the theme's
   default cyan signals; same semantic as .badge-unverified, the admin Danger
   Zone and the maintenance card. */
.av-notice-card { --av-shine-base: var(--warn); }
/* ────────────────────────────────────────────────────────────
   SHINE — SHARED PUBLIC + ADMIN COMPONENT
   One pass of light travelling across an icon, a rule and a
   heading.

   Lives in THIS file, not admin.css, because both sides use it:
   index.css is ungated (header.php loads it in public and admin
   context alike), so one definition serves the whole site.
   admin.css must not redeclare any of it — see the note at
   .settings-forum-placeholder there.

   Consumers opt in per element, in markup:
     .av-shine-icon   on the glyph
     .av-shine-rule   on an .av-card-divider
     .av-shine-text   on the heading
   Used by includes/modules.php's Module::renderNotice() (the public
   "Module Not Found" and its siblings) and by the admin's
   .settings-forum-placeholder empty state.

   Colour is a parameter, not a constant: --av-shine-base defaults to
   --warn and admin overrides it with its own --aa-warn, so each side
   keeps its palette discipline while sharing one mechanism.

   Selectors are doubled (.av-shine-text.av-shine-text) to raise
   specificity to 0,2,0. Without it the generic `.av-card h2` (0,1,1)
   out-specifies this rule's `color: transparent` and the heading
   paints solid over its own clipped gradient — no shine, no error.
   Consumers must therefore set --av-shine-base rather than declaring
   colour or background on these elements themselves.

   The two @supports guards are NOT equivalent:
   - background-clip:text is load-bearing. The rule sets
     color:transparent, so without it the heading renders INVISIBLE
     rather than merely unanimated. Outside the guard the plain
     --av-shine-base colour stands.
   - mask-image is a nicety. Without it .av-card-divider-center's
     centre-out fade simply remains and only the shine is missed.
   background-image and animation sit INSIDE the guards deliberately:
   applied unguarded they would paint a gradient rectangle behind the
   heading and slide the divider's faded ends around.

   background-position stays inside 0%–100%. With no-repeat and
   color:transparent, any part of the box the 250%-wide image does not
   cover has NO fill and renders invisible; that range is exactly where
   an oversized image is guaranteed to span the box. Widening it to
   throw the highlight further off-screen silently clips characters off
   the heading instead.

   Sync is structural, not tuned: the highlight is positioned as a
   percentage of each element's OWN box, so a consumer must give the
   heading and the rule the same width. Equal widths put the highlight
   at the same x at every instant, with no per-element delay to keep
   maintained.
   ──────────────────────────────────────────────────────────── */
.av-shine-text,
.av-shine-rule {
    --av-shine-image: linear-gradient(
        100deg,
        var(--av-shine-base, var(--warn)) 0%,
        var(--av-shine-base, var(--warn)) 42%,
        var(--av-shine-highlight, #fff4dc) 50%,
        var(--av-shine-base, var(--warn)) 58%,
        var(--av-shine-base, var(--warn)) 100%
    );
    background-size: 250% 100%;
    background-repeat: no-repeat;
}
.av-shine-text.av-shine-text {
    color: var(--av-shine-base, var(--warn));
}
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
    .av-shine-text.av-shine-text {
        background-image: var(--av-shine-image);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
        animation: av-shine var(--av-shine-duration, 4.5s) linear infinite;
    }
}
@supports ((mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000))) {
    .av-shine-rule.av-shine-rule {
        background-image: var(--av-shine-image);
        -webkit-mask-image: linear-gradient(to right, transparent, #000, transparent);
        mask-image: linear-gradient(to right, transparent, #000, transparent);
        animation: av-shine var(--av-shine-duration, 4.5s) linear infinite;
    }
}
/* Glyphs are small (40–48px), so a highlight travelling across one is
   imperceptible. The icon brightens in place instead, peaking at 50% to
   coincide with the shine reaching the heading's centre below it. */
.av-shine-icon {
    animation: av-shine-icon var(--av-shine-duration, 4.5s) linear infinite;
}
@keyframes av-shine {
    0%   { background-position: 100% 0; }
    100% { background-position: 0% 0; }
}
@keyframes av-shine-icon {
    0%, 30%   { filter: brightness(1); }
    50%       { filter: brightness(2); }
    70%, 100% { filter: brightness(1); }
}
/* Matches the topbar announcement marquee's own opt-out further up this
   file. The gradient fill stays, parked where no highlight sits over the
   text — halting mid-pass would freeze a bright band across it. */
@media (prefers-reduced-motion: reduce) {
    .av-shine-text,
    .av-shine-rule,
    .av-shine-icon { animation: none; }
    .av-shine-text,
    .av-shine-rule { background-position: 100% 0; }
}

/* Divider already carries margin: 14px 0 20px; the h2 directly under it
   would otherwise add its own default top margin on top of that. */
.av-notice-card h2 { margin-top: 0; }

.av-maintenance-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(3, 6, 10, 0.45);
    backdrop-filter: blur(3px);
    padding: 20px;
}
.av-maintenance-card {
    margin: 0;
    width: 100%;
}
/* Warn-colored accent instead of the theme's default cyan/blue —
   reuses the SAME --warn semantic used for badges/pills elsewhere
   (.badge-unverified, the admin Danger Zone panel, the persistent
   admin-maintenance-banner, etc: rgba(216,170,81,0.2) bg / #ffd98a
   text) rather than a one-off hardcoded orange, so this page reads as
   the same "warning/caution" state the rest of the app already uses,
   not an unrelated new color. Scoped to this page only so every other
   .av-card user (login, register, forgot-password, etc.) keeps the
   theme's usual blue look untouched. */
.av-maintenance-card {
    border-color: rgba(216, 170, 81, 0.4);
    box-shadow: 0 0 12px rgba(216, 170, 81, 0.15), inset 0 0 16px rgba(216, 170, 81, 0.07), var(--shadow-md);
}
.av-maintenance-card .av-status {
    color: #ffd98a;
}

/* Shared auto-dismiss toast — window.showToast(), defined in
   includes/js/toast.js, which header.php loads on every page, public
   and admin alike. Every caller today is in the admin (the settings
   saves, the blocks reorder, the side menu auto-save); the public side
   can call it and currently does not.
   Fixed bottom-right, above the site-wide fixed footer (z-index:10).
   Uses public theme tokens so this rule works in both contexts;
   admin pages load this stylesheet via header.php too. Enter:
   slide up + fade in. Exit: fade out. Transition on the base rule
   so both directions animate without a separate class.

   This comment named window.avShowToast() in includes/js/av-toast.js.
   Neither exists — no such function, no such file — and two callers
   had been written against that name, guarded, so they confirmed
   nothing silently for as long as they had existed. */
.av-toast {
    position: fixed;
    /* Clears the footer. It is in flow now rather than fixed, but that
       does not make this unnecessary: body is min-height:100vh with a 1fr
       row above the footer, so on any page shorter than the viewport the
       footer still occupies the bottom edge — and every showToast() caller
       is an admin page, where short pages are the norm. Dropping this to
       match `right` would put the toast on top of the copyright bar. */
    bottom: 64px;
    right: 24px;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--bg-3);
    border: 1px solid var(--good);
    border-left: 3px solid var(--good);
    border-radius: 6px;
    padding: 10px 16px;
    font-size: 13px;
    font-weight: 500;
    color: var(--good);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45), 0 0 12px rgba(104, 206, 161, 0.18);
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 180ms ease, transform 180ms ease;
    pointer-events: none;
    white-space: nowrap;
}
.av-toast.is-visible {
    opacity: 1;
    transform: translateY(0);
}
.av-toast i {
    font-size: 15px;
    flex: none;
}

/* ── Public rail — category groups ───────────────────────────────
   Categories render as <details>/<summary> — natively collapsible
   with no JS. Auto-opens (PHP adds [open]) when a sub-link is active.
   In collapsed-rail mode the category icon stays visible but items
   and chevron are hidden. */
.av-public-rail-cat {
    display: flex;
    flex-direction: column;
}
.av-public-rail-cat summary {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    border-radius: 7px;
    border: 1px solid transparent;
    color: var(--av-text-muted);
    font-size: 13px;
    list-style: none;
    cursor: pointer;
    user-select: none;
    transition: border-color var(--transition-fast), color var(--transition-fast),
                background var(--transition-fast), box-shadow var(--transition-fast);
}
.av-public-rail-cat summary::-webkit-details-marker { display: none; }
.av-public-rail-cat summary:hover {
    border-color: var(--line-2);
    color: var(--av-accent);
    box-shadow: var(--glow-accent);
}
.av-public-rail-cat summary i:first-child {
    font-size: 16px;
    flex: none;
    color: var(--av-text-muted);
    transition: color var(--transition-fast);
}
.av-public-rail-cat summary:hover i { color: var(--av-accent); }
/* No accent colour on the [open] label/icon — being expanded is not being
   current. The chevron below carries the whole open/closed cue (rotated
   and accented); the label text stays muted so accent *text* still means
   "this is the page you are on". */
.av-public-rail-cat summary > span {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.av-public-rail-cat-chevron {
    font-size: 12px;
    flex: none;
    margin-left: auto;
    transition: transform var(--transition-fast), color var(--transition-fast);
}
.av-public-rail-cat[open] .av-public-rail-cat-chevron {
    transform: rotate(90deg);
    color: var(--av-accent);
}
.av-public-rail-cat summary.av-public-rail-cat-bold { font-weight: 700; }
.av-public-rail-cat.av-public-rail-cat-center summary > span { text-align: center; }

.av-public-rail-cat-items {
    padding: 2px 0 4px 20px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.av-public-rail-cat-items a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 10px;
    border-radius: 7px;
    border: 1px solid transparent;
    color: var(--av-text-muted);
    font-size: 12px;
    text-decoration: none;
    transition: border-color var(--transition-fast), color var(--transition-fast),
                background var(--transition-fast);
}
.av-public-rail-cat-items a:hover { border-color: var(--line-2); color: var(--av-accent); box-shadow: var(--glow-accent); }
.av-public-rail-cat-items a.active { background: var(--bg-3); border-color: var(--line-2); color: var(--av-accent); }
.av-public-rail-cat-items a i { font-size: 14px; flex: none; color: var(--av-text-muted); transition: color var(--transition-fast); }
.av-public-rail-cat-items a:hover i,
.av-public-rail-cat-items a.active i { color: var(--av-accent); }

/* Collapsed rail: icon only, hide sub-items even if <details open> */
@media (min-width: 901px) {
    .av-public-rail.is-rail-collapsed .av-public-rail-cat summary {
        justify-content: center;
        padding: 9px 0;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-cat summary span,
    .av-public-rail.is-rail-collapsed .av-public-rail-cat-chevron { display: none; }
    .av-public-rail.is-rail-collapsed .av-public-rail-cat[open] .av-public-rail-cat-items { display: none; }
}

/* ── Collapsed rail hover tooltips ──────────────────────────
   Mirrors the admin rail's own tooltips (see .av-admin.is-rail-collapsed
   in admin.css). Pure CSS via attr(data-rail-label) — no JS. Only
   active when the rail is collapsed and the label span is hidden,
   and only on desktop, where the rail is a vertical fixed column. */
@media (min-width: 901px) {
    /* NO z-index LIFT HERE, deliberately — the collapsed rail keeps the
       base rail's 11 rather than getting a number of its own.

       This rule once raised the collapsed rail to 900 "so the tooltip can
       paint over the header and main content", and that premise was wrong
       twice over. .av-cryoblue-topbar and .av-cryoblue-navbar carry no
       z-index at all, and a block wrapper is position:relative with
       z-index:auto — so the rail, positioned with any POSITIVE z-index,
       already paints above every one of them by the normal painting order.
       The tooltip is trapped in the rail's stacking context, but that
       context was already high enough. Measured directly: the tooltip
       paints over a sidebar block at 5, 6, 9, 10, 11, 50 and 900 alike.

       What 900 actually did was put the rail over a then-full-bleed
       footer, covering footer blocks. The rail is above the footer again
       now, on purpose, but by the base rule's 11 and only after the footer
       bar was inset out of the rail's column — so there is nothing left
       under there to cover. A separate number here would just be a second
       place to get that wrong. */

    /* The rail's nav scrolls; a tooltip sticking out the side must not
       be clipped by that overflow while collapsed. */
    .av-public-rail.is-rail-collapsed,
    .av-public-rail.is-rail-collapsed .av-public-rail-nav {
        overflow: visible;
    }

    .av-public-rail.is-rail-collapsed .av-public-rail-nav a,
    .av-public-rail.is-rail-collapsed .av-public-rail-cat summary {
        position: relative;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-nav a::after,
    .av-public-rail.is-rail-collapsed .av-public-rail-cat summary::after {
        content: attr(data-rail-label);
        position: absolute;
        left: calc(100% + 10px);
        top: 50%;
        transform: translateY(-50%);
        background: var(--bg-4, #182a35);
        color: var(--av-accent, #56b8e8);
        font-size: 11px;
        font-weight: normal;
        text-transform: none;
        letter-spacing: normal;
        white-space: nowrap;
        padding: 4px 9px;
        border-radius: 4px;
        border: 1px solid var(--line-2, #294352);
        box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
        pointer-events: none;
        opacity: 0;
        transition: opacity .15s;
        z-index: 10200;
    }
    .av-public-rail.is-rail-collapsed .av-public-rail-nav a:hover::after,
    .av-public-rail.is-rail-collapsed .av-public-rail-cat summary:hover::after {
        opacity: 1;
    }
    /* Sub-items inside an open category never need a tooltip — they're
       hidden entirely when collapsed (see rule above). */
    .av-public-rail.is-rail-collapsed .av-public-rail-cat-items a::after {
        content: none;
    }
}

/* Admin panel styling now lives in includes/css/admin.css, loaded by
   admin.php independently of the active theme — see that file's header
   comment for why. This theme no longer needs to (and should not)
   duplicate it. */

body:not(.av-admin-context) input[type="file"] {
    color: var(--av-text-muted);
    font-size: 13px;
    cursor: pointer;
    background: #09141b;
    border: 1px solid var(--line-2);
    border-radius: 6px;
    padding: 4px 8px 4px 4px;
    transition: border-color var(--transition-fast);
}
body:not(.av-admin-context) input[type="file"]:hover {
    border-color: var(--line-3);
}
body:not(.av-admin-context) input[type="file"]:focus-within {
    border-color: var(--av-accent);
    outline: none;
}
body:not(.av-admin-context) input[type="file"]::file-selector-button {
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.05) 0%,
        rgba(86, 184, 232, 0.07) 100%
    );
    border: 1px solid rgba(86, 184, 232, 0.22);
    color: var(--av-text);
    padding: 6px 14px;
    border-radius: 5px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
    margin-right: 10px;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
    transition: all var(--transition-fast);
}
body:not(.av-admin-context) input[type="file"]::file-selector-button:hover {
    background: linear-gradient(
        145deg,
        rgba(255, 255, 255, 0.09) 0%,
        rgba(86, 184, 232, 0.16) 100%
    );
    border-color: rgba(86, 184, 232, 0.45);
    color: var(--av-accent);
    box-shadow:
        0 0 10px rgba(86, 184, 232, 0.18),
        inset 0 1px 0 rgba(255, 255, 255, 0.12);
}

/* ── Shared toast notification ───────────────────────────────
   Driven by includes/js/toast.js (window.showToast). Loaded on
   every page via header.php so it must live in index.css, not
   admin.css, to cover both public and admin contexts.

   NOTE: the real .av-toast ruleset lives further up this file
   (search "Shared auto-dismiss toast"). A second, conflicting copy
   used to sit here and has been removed — two rulesets for the same
   class silently fight over bottom/z-index. Adjust the original
   instead of re-adding one here. */
