/* ConLogs — Dark Theme */

/* Claude (v0.89.7): Inter as the site body font — self-hosted variable font
   served from /public/fonts/InterVariable.woff2 (352 KB, all weights 100-900 in
   one file). v0.89.4 used Google Fonts via @import but the prod CSP only allows
   style-src from 'self' + zamimg + wowhead, so the external @import was blocked.
   Self-hosting is the right answer anyway: no external request, no CSP
   weakening, no extra DNS lookup, and the 1-year immutable cache header from
   express.static (server.js line 432) makes the first-load cost a one-time
   payment per visitor.
   font-display:swap → system fallback renders immediately, Inter swaps in when
   loaded. unicode-range left implicit (full Inter coverage). The variable axis
   is implicit on `font-weight: 100 900` (single-axis weight variable font). */
@font-face {
    font-family: 'Inter';
    src: url('/public/fonts/InterVariable.woff2') format('woff2-variations'),
         url('/public/fonts/InterVariable.woff2') format('woff2');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
    --bg:        #0e1117;
    --surface:   #161b22;
    --surface2:  #1c2230;
    /* Claude (v0.88.3): site-wide modernization — --border switched from the original
       hard slate-blue #2d3748 to a luminous edge (rgba(255,255,255,0.14)). Every consumer
       of `border: 1px solid var(--border)` (75+ usages across modals, tables, cards,
       form inputs, page headers, raid blocks, admin panels) picks up the new look in one
       shot — no need to enumerate every selector. Spot-check first: .meter-row and
       .rankings-table use their own rgba(.03)/(.04) row separators (not --border), so
       table density is unaffected. The luminance is close to the old #2d3748 (~rgb(45)
       vs ~rgb(33) on bg) so visibility holds; the slight loss of blue saturation reads
       as "designed" instead of "default". If a specific surface needs the hard slate
       back, alias --border-hard locally. */
    --border:    rgba(255, 255, 255, 0.14);
    --accent:    #c89b3c;
    --text:      #e2e8f0;
    --text-muted:#7d8a9c; /* Claude: lightened from #718096 → clears WCAG AA 4.5:1 on --surface/--surface2 (was ~3.95–4.28:1 for the 11–12px muted text used site-wide) */
    --bar-spell: #805ad5;
    --green:     #68d391; /* Claude: HPS / healing accent */
    --red:       #fc8181; /* Claude: wipes / errors / deaths */
    --text-secondary: #a0aec0; /* Claude: secondary text accent (footer etc.) */
    /* Claude (v0.56.23): boss / NPC / trash name color — light coral/salmon
       that complements the gold accent without reading as "error" (which is
       what --red is reserved for). Used everywhere a hostile-NPC name renders
       (progression tables, hover tooltips, ranking sidebar, log-viewer fight
       title + NPC filter). */
    --boss:      #ff9f7f;
}

body {
    background: var(--bg);
    color: var(--text);
    /* Claude (v0.89.4): Inter primary, system fallback while it loads.
       Inter has a real tabular-nums character set so numeric columns
       align even cleaner than with Segoe UI. */
    font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
    font-feature-settings: 'cv11', 'ss03'; /* Claude: stylistic alternates — open '6'/'9', single-storey 'a' */
    font-size: 14px;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* ── Header ─────────────────────────────────────────────────── */
/* Claude: header has a fixed height so it is pixel-identical on every page */
header {
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 100;
    height: 56px; /* Claude: static height — never grows or shrinks with content */
}
/* Claude: inner wrapper constrains header content to same max-width as <main>
   so buttons / h1 align with page content on all viewport sizes */
/* Claude (v0.35.5): final value 1300px after iterating 1100 → 1600 → 1800 → 1600
   → 1300. 1600+ felt too wide for our table/card density; 1300 is the settled
   middle ground — modest bump from the original 1100 so 27" monitors don't
   feel half-empty, but compact enough that ranking tables and compare cards
   stay scannable without horizontal eye-travel. Sidebar ads on big monitors
   are anchored to this content edge via calc(50vw ± 650px ± 24px). */
.header-inner {
    max-width: 1300px;
    margin: 0 auto;
    padding: 0 24px;
    height: 100%;
    display: flex;
    align-items: center;
    gap: 12px;
}
header h1 { font-size: 20px; font-weight: 700; color: var(--accent); letter-spacing: 0.5px; }
/* Claude (v0.55.3): push the nav cluster to the right on every page. Pages with a
   .player-search-wrap (which carries its own margin-left:auto) split the slack with the
   h1 — visually identical end-result. Pages without a search bar (player.html, guild.html)
   relied on the search-wrap's auto-margin for nav alignment and were rendering all nav
   buttons crammed to the left. h1 margin-right:auto fixes both cases. */
header h1 { margin-right: auto; }
header .subtitle { color: var(--text-muted); font-size: 12px; flex: 1; }

/* Claude: dedicated log meta bar below the main header — shows guild/raid/date/duration,
   merged badge, Logs dropdown, notes, and the Copy Link button. Constrained to the same
   max-width + padding as <main> so the left edge lines up with the meter table below. */
/* Claude (v0.35.0): kept in sync with main + .header-inner — see note there. */
#log-meta-bar {
    max-width: 1300px;
    margin: 0 auto;
    padding: 10px 16px;
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    color: var(--text-muted);
    flex-wrap: wrap;
    border-bottom: 1px solid var(--border);
}
#log-meta-bar .subtitle { display: block; flex: 1; min-width: 0; }
#log-meta-bar .header-copy-btn { flex-shrink: 0; }
.header-admin-btn {
    color: rgba(212,175,55,0.6);
    text-decoration: none;
    font-size: 16px;
    padding: 3px 8px;
    border-radius: 5px;
    transition: color 0.15s;
    line-height: 1;
}
.header-admin-btn:hover { color: var(--accent); }
.header-admin-btn.active { color: var(--accent); }

/* Claude: rankings button — gold tinted, visible label */
.header-rankings-btn {
    color: var(--accent);
    text-decoration: none;
    font-size: 12px;
    font-weight: 700;
    white-space: nowrap;
    padding: 4px 10px;
    border: 1px solid rgba(212,175,55,0.4);
    border-radius: 5px;
    background: rgba(212,175,55,0.08);
    transition: all 0.12s;
    margin-right: 4px;
}
.header-rankings-btn:hover { background: rgba(212,175,55,0.18); border-color: var(--accent); }
/* Claude: active-state — nav button for the current page gets a filled accent look so the
   user always knows where they are. Auto-applied by utils.js on DOMContentLoaded. */
.header-rankings-btn.active {
    background: var(--accent);
    color: #0e1117;
    border-color: var(--accent);
    box-shadow: 0 1px 4px rgba(212,175,55,0.35);
}
.header-rankings-btn.active:hover { background: var(--accent); color: #0e1117; }

/* Claude (v0.50.0): Tools dropdown — collapses Talents / Armory / Softres into a single
   header button with a downwards arrow. Wrap is the click target's parent (relative pos).
   Menu is absolutely positioned below; .open class on the wrap reveals it. */
.header-tools-wrap { position: relative; display: inline-block; margin-right: 4px; }
.header-tools-wrap .header-tools-btn {
    /* Claude: type=button on a <button> sharing .header-rankings-btn — strip native button look */
    cursor: pointer;
    margin-right: 0;       /* wrap already provides the right margin */
    font-family: inherit;
}
/* Claude (v0.83.16): bigger + bolder + full opacity so the dropdown affordance is obvious — was 9px / 0.85 opacity which read as decorative noise rather than "this opens a menu" */
.header-tools-arrow { margin-left: 4px; font-size: 14px; opacity: 1; display: inline-block; transition: transform 0.12s; font-weight: 700; }
.header-tools-wrap.open .header-tools-arrow { transform: rotate(180deg); }
.header-tools-menu {
    position: absolute;
    top: calc(100% + 4px);
    /* Claude (v0.56.19): align the dropdown's LEFT edge with the Tools button
       (was right:0 which anchored to the right edge instead). The wrap is
       position:relative around the button, so left:0 + width:max-content gives
       a menu that starts where the button starts. */
    left: 0;
    /* Claude (v0.56.18): width auto-fits to the widest item instead of the
       hard-coded 160px min-width. width:max-content sizes to the natural width
       of the largest <a> child (item text + padding); min-width nudges to a
       sensible floor so single-letter labels still get a usable click target. */
    width: max-content;
    min-width: 120px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 4px 14px rgba(0,0,0,0.4);
    padding: 4px;
    z-index: 200;
    display: none;
}
.header-tools-wrap.open .header-tools-menu { display: block; }

/* Claude (v0.87.4): mobile hamburger button + slide-out drawer. Hidden on
   desktop; @media (max-width: 700px) flips display:inline-flex. js/utils.js
   injects the button into .header-inner and builds the drawer panel lazily on
   first open by cloning the existing .header-rankings-btn + .header-tools-menu
   markup so we don't duplicate the nav list. */
.header-mobile-menu-btn {
    display: none;
    background: rgba(212,175,55,0.08);
    border: 1px solid rgba(212,175,55,0.4);
    color: var(--accent);
    font-size: 20px;
    line-height: 1;
    padding: 0;
    min-width: 44px;
    min-height: 44px;
    border-radius: 6px;
    cursor: pointer;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.header-mobile-menu-btn:hover { background: rgba(212,175,55,0.18); }
.header-mobile-drawer {
    position: fixed;
    inset: 0;
    z-index: 9999;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s;
    /* Claude (v0.100.18): clip the off-canvas panel. The panel sits at
       transform:translateX(100%) (off-screen RIGHT) when closed; without this,
       iOS Safari counts that off-screen width toward the document and lets the
       WHOLE PAGE scroll sideways (html{overflow-x:hidden} does NOT contain a
       transformed position:fixed child on iOS). This drawer is injected on every
       page by utils.js, so the leak hit every page — the real cause behind the
       recurring "goes outside the mobile window" reports. When open, the panel
       is on-screen within the viewport, so this clip is a no-op there. */
    overflow: hidden;
}
.header-mobile-drawer.open { opacity: 1; pointer-events: auto; }
.header-mobile-drawer-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,0.6);
}
.header-mobile-drawer-panel {
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: min(85vw, 320px);
    background: var(--surface);
    border-left: 1px solid var(--border);
    padding: 56px 14px 14px;
    overflow-y: auto;
    transform: translateX(100%);
    transition: transform 0.22s ease-out;
    box-shadow: -4px 0 16px rgba(0,0,0,0.4);
}
.header-mobile-drawer.open .header-mobile-drawer-panel { transform: translateX(0); }
.header-mobile-drawer-close {
    position: absolute;
    top: 10px;
    right: 10px;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    width: 36px;
    height: 36px;
    border-radius: 6px;
    font-size: 18px;
    cursor: pointer;
    line-height: 1;
}
.header-mobile-drawer-close:hover { color: var(--accent); border-color: var(--accent); }
.header-mobile-drawer-links {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.header-mobile-link {
    display: block;
    padding: 12px 14px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    text-decoration: none;
    font-size: 14px;
    font-weight: 600;
    min-height: 44px;
    box-sizing: border-box;
    cursor: pointer;
    width: 100%;
    text-align: left;
}
.header-mobile-link:hover { background: rgba(212,175,55,0.10); border-color: var(--accent); color: var(--accent); }
.header-mobile-link.active {
    background: var(--accent);
    color: #0e1117;
    border-color: var(--accent);
}
/* Claude (v0.100.9): grouped drawer — section headers + 2-line rich links.
   Mirrors the desktop Rankings/Tools dropdown grouping and restores the
   one-line descriptions on the Rankings items (user feedback: the flat,
   description-less mobile list was hard to parse). */
.header-mobile-section {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--accent);
    padding: 6px 4px 4px;
    margin-top: 10px;
    border-bottom: 1px solid var(--border);
}
.header-mobile-drawer-links > .header-mobile-section:first-child { margin-top: 0; }
.header-mobile-link .hml-name { display: block; font-weight: 600; }
.header-mobile-link .hml-sub {
    display: block;
    font-size: 11px;
    font-weight: 500;
    color: var(--text-muted);
    margin-top: 2px;
    line-height: 1.3;
    white-space: normal;
}
.header-mobile-link:hover .hml-sub,
.header-mobile-link.active .hml-sub { color: var(--text); }
.header-mobile-link.active .hml-sub { color: #0e1117; }

.header-tools-menu a {
    display: block;
    /* Claude (v0.56.19): items use the same accent (orange) as the header
       buttons themselves — was var(--text) which read as off-white. */
    color: var(--accent);
    text-decoration: none;
    font-size: 12px;
    font-weight: 600;
    padding: 7px 12px;
    border-radius: 4px;
    white-space: nowrap;
}
.header-tools-menu a:hover { background: rgba(212,175,55,0.18); color: var(--accent); }

/* Claude (v0.92.21): .rich modifier turns the Rankings dropdown into a 2-column
   layout per item (icon | name + sub-description) so users get a one-liner
   explaining what each page is about. Tools dropdown keeps the slim layout —
   the .rich class only opts INTO the wider format. */
.header-tools-menu.rich {
    min-width: 280px;
    padding: 6px;
}
.header-tools-menu.rich a {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 11px;
    white-space: normal;
    border-radius: 5px;
    transition: background 0.12s;
}
.header-tools-menu.rich a:hover { background: rgba(212,175,55,0.14); }
.header-tools-menu.rich .rdi-icon {
    font-size: 18px;
    line-height: 1;
    flex-shrink: 0;
    width: 24px;
    text-align: center;
}
.header-tools-menu.rich .rdi-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1 1 auto;
}
.header-tools-menu.rich .rdi-name {
    font-size: 13px;
    font-weight: 700;
    color: var(--accent);
    line-height: 1.2;
}
.header-tools-menu.rich .rdi-sub {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-muted);
    line-height: 1.3;
    letter-spacing: 0.1px;
}
.header-tools-menu.rich a:hover .rdi-sub { color: var(--text); }

/* Claude: 3.3.5 client badge — Kezan / Gurubashi. Same orange style as the header nav
   buttons so it sits naturally alongside them. Used in the log meta bar, speedrun widgets,
   and uploaded-logs listings. */
.server-pill {
    display: inline-block;
    color: var(--accent);
    font-size: 10px;
    font-weight: 700;
    /* Claude (v0.56.12): tighter padding for the new 2-char abbreviated text
       (Kezan → KZ, Gurubashi → GB). Was 1px 8px when the badge held the full
       name; now sits at the same visual weight as a small inline tag. */
    padding: 1px 5px;
    border: 1px solid rgba(212,175,55,0.4);
    border-radius: 5px;
    background: rgba(212,175,55,0.08);
    white-space: nowrap;
    letter-spacing: 0.4px;
    vertical-align: middle;
}
/* Claude: shown when a group's logs span multiple servers — should never happen in practice
   (upload enforces a single server per log, group matching is per-guild per-night), so this
   is a loud red warning rather than a neutral pill. */
.server-pill.multiple {
    color: #fc8181;
    border-color: rgba(252,129,129,0.5);
    background: rgba(252,129,129,0.1);
}
/* Claude: .header-back removed — unused dead selector */

/* Claude: header search input — gold border/placeholder to match header button family */
.header-search-input {
    background: rgba(212,175,55,0.05);
    border: 1px solid rgba(212,175,55,0.3);
    color: var(--text);
    padding: 5px 10px;
    border-radius: 5px;
    font-size: 13px;
    outline: none;
    width: 180px;
    transition: all 0.12s;
}
.header-search-input:focus {
    border-color: var(--accent);
    background: rgba(212,175,55,0.08);
}
.header-search-input::placeholder { color: rgba(212,175,55,0.45); }

/* Claude: copy-link button — same gold style as .header-rankings-btn */
.header-copy-btn {
    color: var(--accent);
    background: rgba(212,175,55,0.08);
    border: 1px solid rgba(212,175,55,0.4);
    font-size: 12px;
    font-weight: 700;
    padding: 4px 10px;
    border-radius: 5px;
    cursor: pointer;
    transition: all 0.12s;
    white-space: nowrap;
    margin-right: 4px;
}
.header-copy-btn:hover { background: rgba(212,175,55,0.18); border-color: var(--accent); }

/* Claude: version badge — small monospace button appended to header by changelog.js */
.version-badge {
    background: none;
    border: 1px solid rgba(212,175,55,0.35);
    color: rgba(212,175,55,0.7);
    font-size: 11px;
    padding: 3px 8px;
    border-radius: 4px;
    cursor: pointer;
    margin-left: 8px;
    font-family: monospace;
    transition: all 0.12s;
    white-space: nowrap;
}
.version-badge:hover { border-color: var(--accent); color: var(--accent); }

/* Claude (v0.41.0): footer-mounted version badge — initVersionBadge now appends to
   <footer> instead of the header. Make it look like an inline footer link rather than
   the small monospace pill it was. The .version-badge above still applies its base
   styling; .footer-mounted overrides margin and adds a separator dot. */
.version-badge.footer-mounted {
    margin-left: 0;
    background: none;
    border: none;
    padding: 0 4px;
    color: var(--text-muted);
    text-decoration: underline dotted transparent;
    transition: color 0.12s, text-decoration-color 0.12s;
}
.version-badge.footer-mounted:hover {
    color: var(--accent);
    text-decoration-color: var(--accent);
}

/* Claude (v0.41.1): the v0.41.0 dropdown approach (.auth-widget-pill / .auth-menu)
   was reverted because the caret + menu crowded the navbar. The widget is back to a
   simple avatar+name+logout layout (styled inline in js/auth.js); admin shortcut
   lives on the /profile page itself. */

.pet-toggle-btn {
    background: none;
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-size: 12px;
    padding: 3px 10px;
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.12s;
    white-space: nowrap;
    margin-left: auto;
    margin-right: 8px;
}
.pet-toggle-btn:hover  { color: var(--text); border-color: var(--text-muted); }
.pet-toggle-btn.active { color: var(--green); border-color: var(--green); background: #1a2e1a; }

/* Wipe fight entries in the fight dropdown */
option.fight-wipe { color: var(--red); }

/* Wipe label shown in the fight duration bar */
#fight-duration { color: var(--text-muted); }
#fight-duration.is-wipe { color: var(--red); }

/* ── Footer ──────────────────────────────────────────────────── */
footer {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Claude (v0.75.1 mobile): added flex-wrap so footer doesn't overflow horizontally
       on phones. With 8 items at 11px + 12px gaps the natural width is ~570px,
       so anything narrower (every phone) was being clipped or causing the whole
       page to side-scroll. row-gap kept tight (6px) so wrapped lines hug each
       other; column-gap stays at 12px between sibling items. */
    flex-wrap: wrap;
    gap: 6px 12px;
    padding: 24px 16px 20px;
    color: var(--text-muted);
    font-size: 11px;
    border-top: 1px solid var(--border);
    margin-top: 32px;
}
footer strong { color: var(--text-secondary); }
footer a {
    color: var(--text-muted);
    text-decoration: none;
    transition: color 0.12s;
}
footer a:hover { color: var(--accent); }

/* ── Homepage intro copy ── */
/* Collapsed behind a <details> toggle so it doesn't dominate above-the-fold space. */
.home-intro-wrap {
    max-width: 900px;
    margin: 12px auto 16px;
}
.home-intro-wrap summary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 12px;
    color: var(--text-muted);
    cursor: pointer;
    list-style: none;
    user-select: none;
    transition: all 0.12s;
}
.home-intro-wrap summary::-webkit-details-marker { display: none; }
.home-intro-wrap summary::before {
    content: '▸';
    font-size: 10px;
    transition: transform 0.15s;
}
.home-intro-wrap[open] summary::before { transform: rotate(90deg); }
.home-intro-wrap summary:hover,
.home-intro-wrap[open] summary { border-color: var(--accent); color: var(--text); }

.home-intro {
    margin: 10px 0 0;
    padding: 18px 22px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
}
.home-intro h2 {
    font-size: 16px;
    font-weight: 700;
    color: var(--accent);
    margin: 0 0 10px 0;
}
.home-intro p {
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-muted);
    margin: 0 0 10px 0;
}
.home-intro p:last-child { margin-bottom: 0; }
.home-intro strong { color: var(--text); }

/* ── Legal / About pages (scoped so dense rules don't bleed in) ── */
.legal {
    max-width: 720px;
    margin: 32px auto;
    padding: 0 20px;
    line-height: 1.65;
    color: var(--text);
}
.legal h1 {
    margin: 0 0 10px;
    font-size: 24px;
    color: var(--accent);
}
.legal h2 {
    margin: 28px 0 10px;
    font-size: 15px;
    color: var(--text);
    border-bottom: 1px solid var(--border);
    padding-bottom: 6px;
}
.legal p, .legal li {
    margin-bottom: 10px;
    font-size: 13.5px;
    color: var(--text);
}
.legal ul { padding-left: 22px; margin-bottom: 14px; }
.legal em { color: var(--text-muted); font-size: 12px; font-style: italic; }
.legal a { color: var(--accent); text-decoration: none; }
.legal a:hover { text-decoration: underline; }

/* Cookie consent banner CSS removed — superseded by Google's CMP, which renders its own
   UI for EU/UK/CH visitors. Non-EU visitors don't see a banner by policy. */

/* ── Main layout ─────────────────────────────────────────────── */
/* Claude (v0.35.5): final max-width 1300px (iterated 1100 → 1600 → 1800 →
   1600 → 1300). The data dashboard pages — Rankings tables, Compare cards,
   Meta box-plots, Timeline lanes — read better at modestly compact widths
   than at fully-stretched ones. 1300px is the sweet spot: meaningfully
   wider than the original 1100 so 27" monitors don't feel half-empty, but
   compact enough that tables/cards stay scannable. Sidebar ads on big
   monitors hug the 1300px edge via calc(50vw ± 650px ± 24px). */
main {
    max-width: 1300px;
    margin: 0 auto;
    padding: 24px 16px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Claude: .header-upload-btn removed — element uses .header-rankings-btn class instead */

/* ── AdSense placements (Claude v0.35.6) ────────────────────────────────────
   Placeholder containers are now FULLY TRANSPARENT — no background, no
   border, no min-height, no "Advertisement" label. Slot stays in DOM so
   AdSense can still inject the <ins class="adsbygoogle"> and fill it; when
   filled, the ad's own content shows. When unfilled, AdSense sets
   data-ad-status="unfilled" and applies display:none to its <ins>, so the
   wrapper collapses invisibly. Result: visitors only see real ads, never
   empty placeholder boxes during ad-load or fill failure. */
.ad-in-content {
    width: 100%;
    margin: 16px 0;
    overflow: hidden;
    text-align: center;
}

/* Sidebar rails — content-edge anchored via calc(50vw ± 800px ± 24px).
   Math: content is 1600px wide, centered. Half-width = 800px. Content edges
   live at 50vw ± 800px. The 24px adds breathing space between content and ad.
   Result: ad's inner edge sits 24px outside the content edge regardless of
   viewport size, instead of stuck to the viewport edge in the periphery.
   Claude (v0.35.3): 300×600 half-page → 160×600 wide skyscraper. The 300×600
   felt visually too heavy (42% of vertical viewport on 1440p) — the smaller
   skyscraper sits in the gutter without dominating. */
.ad-sidebar-left,
.ad-sidebar-right {
    position: fixed;
    top: 80px;               /* below 56px header + breathing room */
    width: 160px;            /* wide skyscraper — keeps explicit size so AdSense knows the slot dimensions */
    height: 600px;
    z-index: 5;              /* below modals (100+) and dropdowns (50+) */
    overflow: hidden;
    display: none;           /* hidden until viewport is wide enough; chrome removed in v0.35.6 so invisible until filled */
}
.ad-sidebar-left  { right: calc(50vw + 650px + 24px); } /* right edge = content's left edge − 24px */
.ad-sidebar-right { left:  calc(50vw + 650px + 24px); } /* left edge  = content's right edge + 24px */

/* Claude (v0.35.5): content shrunk from 1600 → 1300, so the calc anchor is
   now 650px (was 800). Threshold lowered slightly: 1300 + 2×(24 + 160 + 16) =
   1700px minimum. Bumped to 2000px so 1080p users (1920px) still don't see
   sidebars — their 310px gutter would technically fit a 160px ad with
   breathing space, but ads on top of an already-narrow viewport feels
   cramped. Only 1440p+ users (2560px) get sidebars, with ~446px outer
   breathing. */
@media (min-width: 2000px) {
    .ad-sidebar-left,
    .ad-sidebar-right { display: block; }
}

/* Claude (v0.35.6): "Advertisement" ::before pseudo-elements removed.
   They were always visible regardless of fill state, defeating the purpose
   of hiding the placeholder until an ad actually loads. Real AdSense ads
   already render with their own visible "Ad" / "Sponsored" labelling per
   Google's serving rules, so visitors aren't misled. */

/* ── Fight row ───────────────────────────────────────────────── */
#fight-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

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

/* Claude (v0.92.22): filter + tab buttons share the boss-card surface treatment.
   Claude (v0.92.23): bumped base from --surface to --surface2.
   Claude (v0.92.25): user said v0.92.23 still looked dark. Made the change much
   more aggressive: stronger top highlight (8% white instead of 5%), nearly-zero
   bottom darkening (2% white instead of 8% black), AND an inset top-edge
   highlight via box-shadow. The small button area now reads as clearly
   "elevated" like the boss-shortcut cards — bigger boss cards inherently show
   more of any overlay because they have more surface area, so the smaller
   buttons need stronger highlights to land the same perceptual brightness. */
.filter-btn {
    background: linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%), var(--surface2);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
    border: 1px solid var(--border);
    color: var(--text-muted);
    padding: 5px 14px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 12px;
    font-weight: 600;
    transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.12s, box-shadow 0.15s;
    white-space: nowrap;
}
.filter-btn:hover {
    color: var(--text);
    border-color: rgba(212,175,55,0.5);
    transform: translateY(-1px);
    box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.filter-btn.active {
    background: linear-gradient(180deg, #3a2d08 0%, #221805 100%);
    border-color: var(--accent);
    color: var(--accent);
    box-shadow: inset 0 1px 0 rgba(212,175,55,0.15), 0 1px 4px rgba(212,175,55,0.18);
}

/* Claude (v0.92.22): same neutral-grey surface as the filter / tab buttons.
   Claude (v0.92.23): bumped base to --surface2 along with the tab + filter buttons.
   The <option> rule below applies a dark background to the dropdown list itself
   (browsers render <option> with default OS chrome unless `option {…}` is set;
   user reported the dropdown was rendering as white-on-white). */
#fight-select {
    /* Claude (v0.92.25): match the brighter filter/tab button treatment. */
    background: linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%), var(--surface2);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 6px 12px;
    border-radius: 6px;
    flex: 1;
    min-width: 200px;
    font-size: 13px;
    transition: border-color 0.15s, box-shadow 0.15s;
}
#fight-select:hover  { border-color: rgba(212,175,55,0.5); }
#fight-select:focus  { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(212,175,55,0.18); }
/* Claude (v0.92.22): dropdown options now respect the dark theme. Most browsers
   (Chrome, Edge, Firefox) render <option> via OS chrome by default — Chrome 91+
   honours background-color + color on option elements when set explicitly. The
   selected/hover row in the dropdown is still browser-controlled; the rest now
   uses --surface so the per-fight list isn't legibility-killing white-on-white. */
#fight-select option {
    background-color: var(--surface);
    color: var(--text);
}
#fight-select option:checked,
#fight-select option:hover {
    background-color: var(--surface2);
    color: var(--accent);
}

/* ── Tabs row ────────────────────────────────────────────────── */
#tabs-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
.tabs { display: flex; gap: 6px; }
/* Claude (v0.92.25): see .filter-btn comment — same brightness treatment so the
   inactive tabs read as "elevated" like the boss-shortcut cards. */
.tab-btn {
    background: linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%), var(--surface2);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
    border: 1px solid var(--border);
    color: var(--text-muted);
    padding: 6px 16px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
    transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.12s, box-shadow 0.15s;
}
.tab-btn:hover {
    color: var(--text);
    border-color: rgba(212,175,55,0.5);
    transform: translateY(-1px);
    box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
/* Active tabs (per-tab background/border below) get a subtle inset top highlight
   + a soft outer glow matching their accent — sits behind the per-tab rules so
   the colors stay distinct but every active tab gets the same lift treatment. */
.tab-btn.active {
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 1px 6px rgba(0,0,0,0.35);
    transform: translateY(0);
}
.tab-btn.active[data-tab="damage"]  { background: #7f1d1d; border-color: #e53e3e; color: #fff; }
.tab-btn.active[data-tab="healing"] { background: #1a4731; border-color: #38a169; color: #fff; }
.tab-btn.active[data-tab="taken"]   { background: #7b341e; border-color: #dd6b20; color: #fff; }
.tab-btn.active[data-tab="deaths"]  { background: #2d1515; border-color: var(--red); color: #fff; }
.tab-btn.active[data-tab="casts"]      { background: #1a2744; border-color: #63b3ed; color: #fff; }
.tab-btn.active[data-tab="interrupts"] { background: #2d1a44; border-color: #a78bfa; color: #fff; }
.tab-btn.active[data-tab="dispels"]    { background: #134e4a; border-color: #2dd4bf; color: #fff; }
.tab-btn.active[data-tab="buffs"]      { background: #1a2e3a; border-color: #4fd1c5; color: #fff; } /* Claude: buffs tab active state */
.tab-btn.active[data-tab="friendlyfire"] { background: #3b1a1a; border-color: #f56565; color: #fff; } /* Claude: friendly fire tab */
.tab-btn.active[data-tab="overhealing"] { background: #3a2a00; border-color: #ecc94b; color: #fff; } /* Claude: overhealing tab */
.tab-btn.active[data-tab="resources"]   { background: #1e3a5f; border-color: #60a5fa; color: #fff; } /* Claude (v0.37.0): resources tab — sub-tabs for Mana/Rage/Focus/Energy/Happiness/Runic/Extra Attacks */
/* Claude (v0.43.24): summary + timeline active rules — both got missed when each tab's
   active styling was cherry-picked above, so the buttons looked unselected even on the
   active tab. Summary uses the accent gold; Timeline uses a violet to match the cast
   timeline's purple aesthetic. */
.tab-btn.active[data-tab="summary"]     { background: #2a1f00; border-color: var(--accent); color: var(--accent); }
.tab-btn.active[data-tab="timeline"]    { background: #2d1a44; border-color: #a78bfa; color: #fff; }
.tab-btn.active[data-tab="loot"]        { background: #2e2410; border-color: #e6cc80; color: #e6cc80; } /* Claude (v0.100.x): loot tab active — treasure gold (was missing → looked unselected) */

/* Claude (v0.92.22): tab-group dropdown wrappers. Parent button styles same as
   .tab-btn (shared class). When a sub-tab is the active tab, JS marks the parent
   .tab-btn-group with .tab-btn-group-active so it picks up an accent border +
   subtle accent fill so the user can see the dropdown is "current" without
   needing the menu open. */
.tab-group { position: relative; display: inline-block; }
.tab-group-arrow {
    margin-left: 4px;
    font-size: 11px;
    opacity: 0.85;
    display: inline-block;
    transition: transform 0.15s;
    font-weight: 700;
}
.tab-group.open .tab-group-arrow { transform: rotate(180deg); opacity: 1; }
.tab-btn-group-active {
    background: #2a1f00 !important;
    border-color: var(--accent) !important;
    color: var(--accent) !important;
    box-shadow: inset 0 1px 0 rgba(212,175,55,0.15), 0 1px 4px rgba(212,175,55,0.18);
}
.tab-group-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    min-width: 160px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(0,0,0,0.5);
    padding: 4px;
    z-index: 200;
    display: none;
}
.tab-group.open .tab-group-menu { display: block; }
.tab-group-item {
    display: block;
    width: 100%;
    background: none;
    border: 1px solid transparent;
    color: var(--text);
    font-size: 12px;
    font-weight: 600;
    padding: 7px 12px;
    border-radius: 4px;
    cursor: pointer;
    text-align: left;
    white-space: nowrap;
    font-family: inherit;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.tab-group-item:hover {
    background: rgba(212,175,55,0.14);
    color: var(--accent);
}
.tab-group-item.active {
    background: rgba(212,175,55,0.22);
    color: var(--accent);
    border-color: rgba(212,175,55,0.4);
}

/* Claude (v0.39.2): on the Resources tab, the parse-badge column is always empty
   (no percentile concept for resource gains) so we shift .meter-bar left to fill
   that 72px of dead space. Bar now starts at left:14px (just past row padding) and
   extends across both parse + rank columns; the rank "#N" text still renders on top
   because it has no z-index conflict with the bar (bar is opacity:0.2 + behind in
   DOM order). Without this rule the bar started at left:86px and the row read as
   "wasted empty space + then a small bar in the middle". */
/* Claude (v0.92.23): also hide the class stripe on the Resources tab since the
   bar fills the full row width there — leaving a stripe at left:95px would float
   in the middle of the row. */
#meter-panel.tab-resources .meter-bar { left: 14px; }
#meter-panel.tab-resources .meter-row::before { display: none; }

/* Claude (v0.92.20): polish on the fight-stats line — pill border around the row.
   Claude (v0.92.25): segmented chip layout per user — was a bullet-separated text
   string, now each metric (Duration, Players, Guardians, Total DMG, Total DPS,
   Physical/Magical %) is its own labeled chip with subtle vertical separators.
   Claude (v0.92.27): now its own full-width row below #tabs-row.
   Claude (v0.92.28): chips now flex equally to fill the full width per user — was
   left-bunched with all the empty space on the right. Each chip is centered, gets
   `flex: 1` so they share the row width evenly, and the value text bumped up so
   the strip reads as a proper stat band instead of a thin caption. */
#fight-stats {
    color: var(--text-muted);
    margin: 10px 0 0;
    padding: 8px 0;
    background: linear-gradient(180deg, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0.01) 100%), var(--surface2);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
    border: 1px solid var(--border);
    border-radius: 6px;
    line-height: 1.3;
    font-variant-numeric: tabular-nums;
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 0;
    width: 100%;
    box-sizing: border-box;
}
#fight-stats:empty { display: none; }
.fs-chip {
    flex: 1 1 0;
    min-width: 0;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 2px 14px;
    border-right: 1px solid rgba(255,255,255,0.08);
    gap: 3px;
    line-height: 1.15;
}
.fs-chip:last-child { border-right: none; }
.fs-chip-row {
    /* horizontal variant for the physical/magical school chips so the two
       colored percentages sit side-by-side in one column instead of stacking. */
    flex-direction: column;
    align-items: center;
    gap: 3px;
}
.fs-chip-row .fs-value {
    display: inline-flex;
    gap: 10px;
    align-items: baseline;
}
.fs-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.7px;
    color: var(--text-muted);
    opacity: 0.7;
    text-transform: uppercase;
}
.fs-value {
    font-size: 15px;
    font-weight: 700;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    line-height: 1.15;
}
.fs-school-phys { color: #ffd989; font-weight: 700; }
.fs-school-mag  { color: #c4b5fd; font-weight: 700; }
@media (max-width: 700px) {
    /* On narrow viewports the equal-flex layout would make every chip too cramped
       to read. Drop to wrap mode + min-width so chips wrap naturally instead. */
    #fight-stats { flex-wrap: wrap; padding: 4px 0; }
    .fs-chip { flex: 0 0 auto; min-width: 96px; border-bottom: 1px solid rgba(255,255,255,0.04); padding: 6px 14px }
    .fs-value { font-size: 13px; }
}

/* ── Icons ───────────────────────────────────────────────────── */
.class-icon {
    width: 20px; height: 20px;
    border-radius: 3px;
    object-fit: cover;
    flex-shrink: 0;
    display: block;
}
.class-icon-placeholder {
    display: inline-block;
    width: 20px; height: 20px;
}
.spell-icon {
    width: 20px; height: 20px;
    border-radius: 3px;
    object-fit: cover;
    display: block;
    background: var(--surface2);
}

/* ── Meter ───────────────────────────────────────────────────── */
#meter-panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    /* Claude (v0.92.19): removed `overflow:hidden` so .meter-header can position:sticky
       against the window scroll. Each .meter-row still clips its own bar via its own
       overflow:hidden, and the border-radius alone gives the panel rounded corners. */
}
.meter-header {
    display: grid;
    /* Claude (v0.99.x): iLvl is the LAST column — a separated "side info" column on the far
       right (see .meter-ilvl divider). Player-meter tabs only; buffs/debuffs/resources get a
       7-col override below. */
    grid-template-columns: 44px 40px 28px 1fr 90px 80px 60px 56px;
    padding: 8px 14px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border);
    background: var(--surface2);
    /* Claude (v0.92.19): sticky to the viewport top so the column headers stay readable
       when scrolling a 25-player raid meter. z-index:5 sits above the per-row .meter-bar
       (which has no z-index → default 0). */
    position: sticky;
    top: 0;
    z-index: 5;
    /* Claude (v0.92.19): borders on a sticky element don't always paint over the rows
       below, so reinforce with a subtle drop shadow on scroll. */
    box-shadow: 0 1px 0 var(--border);
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
}
.meter-row {
    display: grid;
    /* Claude (v0.99.x): iLvl is the LAST column (far-right "side info"); 7-col override for
       buffs/debuffs/resources below. */
    grid-template-columns: 44px 40px 28px 1fr 90px 80px 60px 56px;
    align-items: center;
    /* Claude (v0.56.9): tighter rows — matches the v0.56.6 homepage trim. Was
       7px / 14px / min-height:34px (≈48px row); now 4px / 14px / 28px (≈36px row). */
    padding: 4px 14px;
    position: relative;
    cursor: pointer;
    border-bottom: 1px solid rgba(255,255,255,0.03);
    /* Claude (v0.92.19): added transform to the transition so the hover translate is smooth. */
    transition: background 0.12s, transform 0.12s;
    overflow: hidden;
    min-height: 28px;
}
.meter-row:last-child { border-bottom: none; }
/* Claude (v0.92.19): hover lifts the row 2px to the right and adds a slightly stronger
   tint. Gives row hover real feedback instead of the previous near-invisible bg shift. */
.meter-row:hover    { background: rgba(255,255,255,0.055); transform: translateX(2px); }
.meter-row.selected { background: rgba(255,255,255,0.07); }
/* Claude (v0.92.20): the v0.92.19 .compact density-toggle block was removed per user. */

.meter-parse { display:flex; align-items:center; justify-content:center; position:relative; z-index:1; width:100%; }

.meter-bar {
    position: absolute;
    /* Claude (v0.92.23): bar-left bumped 86 → 98 to match the widened parse + rank
       columns (44 + 40 + 14 padding). The 5 bar-width calculations in app.js
       reference this same offset via a JS constant `METER_BAR_LEFT_OFFSET`. */
    left: 98px; top: 0; bottom: 0;
    /* Claude (v0.92.19): lighter (0.20 → 0.14) so the row text reads as the primary
       content instead of competing with the colored band. Combined with the new
       class-color left stripe below, the row identity still feels strong. */
    opacity: 0.14;
    pointer-events: none;
    transition: width 0.3s ease;
}
/* Claude (v0.92.19): class-colored vertical stripe on the row's left edge. Reuses the
   per-row CSS variable --row-accent (set inline by js/app.js renderMeterRow off
   playerColor()). Falls back to a neutral slate if the var isn't set (sub-rows that
   reuse the meter-row class but don't carry a class color).
   Claude (v0.92.23): moved from left:0 (which sat next to the parse-badge column —
   user said it should align with the bar, not the parses) to left:95px width:3px
   so the stripe sits in the small gap just BEFORE the spec icon column. The icon
   column starts at 98px (14 + 44 + 40), so the stripe ends right at the icon's
   left edge — aligned with the bar's start without clashing with the icon. */
.meter-row::before {
    content: '';
    position: absolute;
    left: 95px; top: 0; bottom: 0;
    width: 3px;
    background: var(--row-accent, transparent);
    pointer-events: none;
    z-index: 1;
}
.meter-rank  { color: var(--text-muted); font-size: 14px; font-variant-numeric: tabular-nums; }
.meter-name  { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 8px; display: flex; align-items: center; gap: 6px; }
/* Claude (v0.92.19): visually-distinct guardian-section header inside #meter-body.
   Replaces the inline-styled divider that JS used to emit; styles centralized here. */
.meter-guardians-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px 6px;
    margin-top: 6px;
    font-size: 11px;
    font-weight: 700;
    color: var(--accent);
    text-transform: uppercase;
    letter-spacing: 0.7px;
    border-top: 1px solid var(--border);
    background: linear-gradient(180deg, rgba(212,175,55,0.05) 0%, transparent 100%);
}
.meter-guardians-header .mgh-count {
    color: var(--text-muted);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* WarcraftLogs-style parse percentile badge */
.parse-badge {
    display: inline-block;
    font-size: 13px;
    font-weight: 700;
    font-family: monospace;
    padding: 1px 5px;
    border-radius: 3px;
    border: 1px solid currentColor;
    /* Claude: dropped opacity:0.9 — it dimmed the grey/low tiers into borderline
       contrast non-deterministically. Full-strength currentColor is AA-safe. */
    flex-shrink: 0;
    letter-spacing: 0.3px;
    /* Claude (v0.92.19): tabular-nums so two-digit + three-digit badges line up. */
    font-variant-numeric: tabular-nums;
}
/* Claude: Tank shield emoji in meter rows — marks players detected as tank for the active fight.
   Shows before the player name in every tab (Damage Done / Taken / Healing / Casts / …).
   Uses the same 🛡 shield as the rankings page Tank button. */
.tank-icon {
    font-size: 16px;
    flex-shrink: 0;
    line-height: 1;
}
/* Claude (v0.56.6): <GM> tag rendered next to verified Guildmaster character names. */
.gm-tag {
    display: inline-block;
    font-size: 9px;
    font-weight: 700;
    color: #e5cc80;
    background: rgba(229,204,128,0.10);
    border: 1px solid rgba(229,204,128,0.5);
    border-radius: 3px;
    padding: 0 4px;
    margin-right: 4px;
    line-height: 1.4;
    vertical-align: middle;
    flex-shrink: 0;
    letter-spacing: 0.04em;
}
/* Claude (v0.56.6): verified guild icon — sits in the .tbl-guild / .saved-log-guild
   pill before the name. Single rule on .guild-icon, applied wherever the helper renders. */
.guild-icon {
    width: 12px; height: 12px;
    border-radius: 2px;
    margin-right: 4px;
    vertical-align: -2px;
    object-fit: cover;
    display: inline-block;
}
/* ── Top Parsers section (homepage) ──────────────────── */
.parsers-section {
    margin-bottom: 24px;
}
.parsers-section-header {
    font-size: 13px;
    font-weight: 700;
    color: var(--accent);
    margin-bottom: 10px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
}
.parsers-widgets {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}
/* Claude (v0.54.7): on narrow screens the desktop's inline grid-template-columns
   (set in overview.js to balance widgets across two rows) would overflow horizontally.
   Revert to flex-wrap so widgets stack naturally on mobile. */
@media (max-width: 700px) {
    .parsers-widgets {
        display: flex !important;
        flex-wrap: wrap;
        grid-template-columns: none !important;
    }
}
.parse-widget {
    flex: 1;
    min-width: 155px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
    font-size: 12px;
}
.pwgt-body {
    /* Claude (v0.56.6): 5 rows × 28px row height = 140px (was 155px = 5 × 31px).
       Claude (v0.94.19): shifted bar/header proportions per user — rows shrunk
       30 → 26px so the body cap is now 5 × 26 = 130px (header gets the visual
       slack via the .parse-widget-title bump below). */
    max-height: 130px;
    overflow-y: auto;
    /* Claude (v0.95.1): explicit overflow-x:hidden per user — without it the
       browser promotes the implicit `visible` x-axis to `auto` (CSS spec: can't
       mix visible + scroll on the two axes) and any sub-pixel horizontal
       overflow surfaces an unwanted scrollbar under the widget. .pwgt-name
       already ellipsis-truncates so x-scroll is never wanted here. */
    overflow-x: hidden;
    scroll-snap-type: y mandatory;
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
}
.parse-widget-title {
    background: var(--surface2);
    /* Claude (v0.94.19): chunkier header (4px 10px → 9px 12px / 11px → 13px font)
       so the boss name + icon read as the widget's anchor rather than a thin caption.
       Pairs with the .pwgt-row height shrink below — header gains visual weight,
       rows lose a touch.
       Claude (v0.94.21): padding 9px → 5px top/bottom and font 13 → 16 per user;
       boss icon also bumped 14 → 20 in overview.js renderWidget(). The 4px each
       side of padding reclaimed pays for the bigger icon + font so the outer
       header height stays ~32px — same anchor weight, just no more wasted slack
       above/below a tiny 14px icon.
       Claude (v0.94.22): font dialed back 16 → 14 per user — the 20px icon stays
       as the visual anchor, the text supports it instead of competing. Icon→text
       ratio is now ~1.4× which reads as "title + glyph" rather than "two equally
       loud elements". Slight letter-spacing (0.15px) gives the text a title feel
       at the smaller size. Padding unchanged: icon (20) is taller than text
       (14 + line-height), so the icon still sets the header height.
       Claude (v0.96.1): scaled back to harmonize with the parent .parsers-section-header
       (13px / 700 / accent) — having a 14px child title under a 13px parent label
       was the "off" feeling per user. Now: font 14 → 13 (matches parent), icon
       20 → 18 in overview.js renderWidget() (keeps ~1.4× ratio, still anchored),
       padding 5/5 → 6/6 (small icon = more vertical slack to balance). Outer
       header height: 6+18+6 = 30 (was 5+20+5 = 30) — visually unchanged, just
       no longer competing with its own parent. */
    padding: 6px 10px 6px 12px;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.15px;
    /* Claude (v0.56.23): boss-name title uses the boss color (--boss). The
       --overall variant below overrides for the gold ⚡ Overall pill.
       Claude (v0.90.1): per-raid thematic colour via --raid-color (set inline
       on the .raid-block ancestor based on GAME_RAID_COLORS). Each raid's parse
       widgets now read in that raid's tint instead of all being the same salmon.
       Falls back to --boss for raids without a colour. */
    color: var(--raid-color, var(--boss));
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
}
.pwgt-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wgt-maximize-btn {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 13px;
    padding: 1px 3px;
    border-radius: 3px;
    line-height: 1;
    opacity: 0.6;
    transition: opacity 0.15s, color 0.15s;
}
.wgt-maximize-btn:hover { opacity: 1; color: var(--accent); }
/* Claude: collapse/minimize button inside parse widget title bar — same style as maximize */
.wgt-collapse-btn {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 11px;
    padding: 1px 4px;
    border-radius: 3px;
    line-height: 1;
    opacity: 0.6;
    transition: opacity 0.15s, color 0.15s;
}
.wgt-collapse-btn:hover { opacity: 1; color: var(--accent); }
/* Hide body when a parse widget is collapsed */
.parse-widget.pwgt-collapsed .pwgt-body { display: none; }
/* Claude: each row IS the bar — rank+dps on left, icon+name on right, all overlaid.
   Claude (v0.92.30): polished to match the .meter-row treatment family per user.
   Bar opacity 0.35 → 0.18 (closer to meter's 0.14 but slightly more visible since
   the widget bars are shorter than meter bars). Hover swapped from harsh
   `filter: brightness(1.25)` to a soft translateX + bg tint matching .meter-row.
   tabular-nums on the value column so 1K/s / 972/s / 939/s line up across rows.
   Row background was flat var(--surface2); now uses a subtle gradient so rows feel
   "elevated" like the new card surface above. Text-shadow trimmed since the
   lighter bar no longer screams over the labels. */
.pwgt-row {
    position: relative;
    /* Claude (v0.94.19): 30 → 26px per user — slimmer bars + chunkier header
       (.parse-widget-title above) shifts visual weight toward the boss-name
       anchor. The .pwgt-row--lg variant (36px) used by the wider full-width
       widgets keeps its existing height. */
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid rgba(255,255,255,0.04);
    overflow: hidden;
    background: linear-gradient(180deg, rgba(255,255,255,0.025) 0%, rgba(255,255,255,0) 100%), var(--surface2);
    scroll-snap-align: start;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.12s, transform 0.12s;
}
.pwgt-row:last-child { border-bottom: none; }
.pwgt-row:hover {
    background: linear-gradient(180deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.01) 100%), var(--surface2);
    transform: translateX(2px);
}
.pwgt-bar {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    opacity: 0.18;
    pointer-events: none;
    z-index: 0;
    border-radius: 0 2px 2px 0;
}
.pwgt-left, .pwgt-right {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 9px 0 11px;
    line-height: 1;
}
.pwgt-right { padding: 0 11px 0 9px }
/* Rank #N — muted accent + tabular nums so the numbers line up. */
.pwgt-rank {
    font-size: 11px;
    font-weight: 700;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    min-width: 22px;
    opacity: 0.85;
}
/* Value column (per-sec / total) — tabular nums + accent gold to match
   .meter-value's accent treatment site-wide. */
.pwgt-dps  {
    font-size: 12px;
    font-weight: 700;
    font-family: monospace;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
}
/* Player name — picks up the class colour via --pwgt-class-color (same custom
   prop the ::before stripe already reads) instead of being hardcoded white. */
.pwgt-name {
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 110px;
    color: var(--pwgt-class-color, var(--text));
}
.pwgt-empty { padding: 10px; color: var(--text-muted); font-size: 11px; text-align: center; }

/* ── Player parse modal ──────────────────────────────────────────────────── */
/* Claude (v0.18.2): widened from 420px — role-split sub-sections + Peak column were
   cramming the parse tables. 580px gives boss names + spec icon + values breathing room
   on desktop, still wraps cleanly on phones via width:100% + max-width. */
#player-modal .modal-box { max-width: 580px; width: 100%; }
.player-modal-header {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 14px;
}
.player-modal-name { font-size: 16px; font-weight: 700; color: var(--text); }
.player-modal-spec { font-size: 11px; color: var(--text-muted); }
.player-parse-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.player-parse-table th {
    text-align: left;
    font-size: 10px;
    font-weight: 700;
    color: var(--text-muted);
    letter-spacing: 0.5px;
    padding: 0 0 6px;
    border-bottom: 1px solid var(--border);
}
.player-parse-table th:not(:first-child) { text-align: right; }
.player-parse-table td {
    padding: 6px 0;
    border-bottom: 1px solid var(--border);
    color: var(--text);
}
.player-parse-table td:not(:first-child) { text-align: right; }
.player-parse-table tr:last-child td { border-bottom: none; }
.ppt-boss { font-weight: 600; }
.ppt-dps  { font-family: monospace; color: var(--accent); }
.ppt-badge {
    display: inline-block;
    min-width: 32px;
    text-align: center;
    font-weight: 700;
    font-size: 11px;
    padding: 1px 5px;
    border-radius: 4px;
    border: 1px solid currentColor;
}
.wgt-icon    { width: 22px; height: 22px; border-radius: 3px; display: block; flex-shrink: 0; }
.wgt-icon-ph { display: inline-block; width: 22px; height: 22px; flex-shrink: 0; }
/* Claude: Overall raid widget gets a gold accent header to stand out from per-boss widgets */
.parse-widget--overall { border-color: rgba(255, 215, 0, 0.25); }
.parse-widget-title--overall { background: rgba(255, 215, 0, 0.10); color: #ffd700; }
/* Claude: HPS section header uses green accent instead of gold */
.parsers-section-header--hps { color: var(--green); margin-top: 14px; }
/* Claude: larger rows in widget maximize modal — no scroll-snap, taller for readability */
.pwgt-row--lg { height: 36px; scroll-snap-align: none; }
.pwgt-name--lg { max-width: 200px; font-size: 12px; }
/* Widget maximize modal — wider box */
.widget-modal-box { max-width: 480px; width: 100%; }

/* Claude (v0.92.19): tabular-nums so column values line up perfectly across rows
   (191,630 / 186,744 sit directly above each other instead of drifting by digit width). */
.meter-value { text-align: right; font-family: monospace; font-size: 13px; color: var(--accent); font-variant-numeric: tabular-nums; }
.meter-ps    { text-align: right; font-family: monospace; font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.meter-pct   { text-align: right; font-family: monospace; font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* Claude (v0.99.x): meter iLvl column — the far-right "side info" column. A left divider +
   padding visually separate it from the damage data. Gold = avg item level worn THIS raid;
   muted ~N = the player's latest-known scan (not captured here); dim — = unknown (pets/
   guardians or no scan). position+z-index so it paints above the .meter-bar.
   .meter-ilvl-h is the matching header label (same divider). */
.meter-ilvl,
.meter-header > span.meter-ilvl-h {
    text-align: center;
    border-left: 1px solid var(--border);
    padding-left: 8px;
    margin-left: 4px;
}
.meter-ilvl  { font-family: monospace; font-size: 12px; font-weight: 700; color: #e6cc80; font-variant-numeric: tabular-nums; position: relative; z-index: 1; }
.meter-ilvl.cc-approx { opacity: 0.65; }
.meter-ilvl.cc-na     { color: var(--text-muted); opacity: 0.4; font-weight: 400; }
/* Claude (v0.99.x): iLvl is a clickable badge → opens the player's Armory gear/talents.
   Tint (color / bg / border) is set inline from the dynamic tier colour. */
.meter-ilvl-badge {
    display: inline-block; font-family: monospace; font-weight: 700; font-size: 11px;
    border: 1px solid transparent; border-radius: 5px; padding: 1px 6px;
    text-decoration: none; cursor: pointer; line-height: 1.45; white-space: nowrap;
    transition: filter 0.12s, transform 0.12s;
}
.meter-ilvl-badge:hover { filter: brightness(1.35); transform: translateY(-1px); }
/* Claude (v0.99.x): buffs/debuffs/resources keep the original 7-col layout (no iLvl) —
   they render their own headers/rows (renderBuffsTab / renderResourcesTab). */
#meter-panel.tab-buffs     .meter-header, #meter-panel.tab-buffs     .meter-row,
#meter-panel.tab-debuffs   .meter-header, #meter-panel.tab-debuffs   .meter-row,
#meter-panel.tab-resources .meter-header, #meter-panel.tab-resources .meter-row {
    grid-template-columns: 44px 40px 28px 1fr 90px 80px 60px;
}

/* Claude (replay): the Replay tab is a full-panel canvas — the meter column
   headers (%ile/#/Player/Total/Per Sec/…) don't apply, so hide them. */
#meter-panel.tab-replay .meter-header { display: none; }

.empty-state { padding: 40px; text-align: center; color: var(--text-muted); }

/* Claude (v0.43.4): global .hidden rule. Was previously only defined for
   #detail-panel / .modal-overlay / .player-search-dropdown — every other
   element with class="hidden" (notably the #detail-important-btn toggle in
   log.html, plus #progression-section / #raid-sections / #overview-section
   on the homepage during initial load) was actually visible. The Important
   toggle showed up on the Damage / Healing / Casts player detail panels
   even though openDetail tried to hide it via classList.add('hidden') —
   that classList call was a no-op without this rule.
   Claude (v0.96.18): added !important. The v0.43.4 fix was incomplete —
   `.detail-action-btn { display: inline-flex }` at line ~1430 has equal
   specificity (1 class) to `.hidden` but is defined LATER in the
   stylesheet, so it WINS the cascade and the Important toggle was STILL
   visible on Damage/Healing/Casts player detail panels even with
   class='hidden'. Promoting `.hidden` to !important is the canonical
   utility-class pattern and beats every non-!important display rule. */
.hidden { display: none !important; }

/* ── Detail panel ────────────────────────────────────────────── */
#detail-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
#detail-panel.hidden { display: none; }

.detail-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    background: var(--surface2);
    border-bottom: 1px solid var(--border);
}
#detail-title { font-weight: 700; font-size: 15px; display: flex; align-items: center; gap: 8px; }
#detail-close { background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 18px; padding: 0 4px; line-height: 1; }
#detail-close:hover { color: var(--text); }

/* Claude (v0.92.20): dedicated styling for the Armory + Compare buttons in the spell
   detail header. Previously these inherited .header-rankings-btn (gold pill nav style)
   with inline padding overrides — looked identical to header navigation links rather
   than action buttons. Each button now gets its own accent color (Armory = warm steel,
   Compare = cool blue) so they read as two distinct actions, plus a soft gradient,
   inner icon weight, and a subtle lift on hover. */
.detail-action-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: linear-gradient(180deg, rgba(255,255,255,0.04) 0%, rgba(0,0,0,0.05) 100%);
    border: 1px solid var(--detail-action-border, rgba(255,255,255,0.12));
    color: var(--detail-action-color, var(--text));
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.3px;
    padding: 4px 11px;
    border-radius: 5px;
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
    line-height: 1.4;
    transition: background 0.12s, border-color 0.12s, transform 0.12s, box-shadow 0.12s;
}
.detail-action-btn:hover {
    background: var(--detail-action-bg-hover, rgba(255,255,255,0.06));
    border-color: var(--detail-action-color, var(--accent));
    transform: translateY(-1px);
    box-shadow: 0 2px 6px rgba(0,0,0,0.35);
}
.detail-action-btn:active { transform: translateY(0); box-shadow: none; }
.detail-action-btn:disabled,
.detail-action-btn[disabled] {
    opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none;
}
/* Armory — warm steel/gold theme matching the shield/paperdoll vibe. */
.detail-action-btn-armory {
    --detail-action-color: #ffd989;
    --detail-action-border: rgba(255,217,137,0.4);
    --detail-action-bg-hover: rgba(255,217,137,0.12);
}
/* Compare — cool blue, distinct from Armory + matches the v0.32 compare-list accent. */
.detail-action-btn-compare {
    --detail-action-color: #7fc3e8;
    --detail-action-border: rgba(127,195,232,0.4);
    --detail-action-bg-hover: rgba(127,195,232,0.12);
}
/* Important (debuff filter) — accent-violet so it's clearly a different mode toggle. */
.detail-action-btn-important {
    --detail-action-color: #c4b5fd;
    --detail-action-border: rgba(196,181,253,0.4);
    --detail-action-bg-hover: rgba(196,181,253,0.12);
}

/* Claude (sim-perfect-play): "Simulate Perfect Play" action — artifact-gold accent. */
.detail-action-btn-sim {
    --detail-action-color: #f3dd92;
    --detail-action-border: rgba(229,204,128,0.7);
    --detail-action-bg-hover: rgba(229,204,128,0.20);
    /* Claude (v0.106.7): make the feature easy to spot among the header actions —
       a persistent gold tint plus a gentle glow pulse (disabled state opts out;
       reduced-motion users get the static highlight only). */
    background: linear-gradient(180deg, rgba(229,204,128,0.22) 0%, rgba(229,204,128,0.10) 100%);
    animation: spp-pulse 2.4s ease-in-out infinite;
}
.detail-action-btn-sim:hover {
    background: linear-gradient(180deg, rgba(229,204,128,0.34) 0%, rgba(229,204,128,0.18) 100%);
}
.detail-action-btn-sim[disabled] { opacity: 0.45; cursor: not-allowed; animation: none; box-shadow: none; }
@keyframes spp-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(229,204,128,0.0); }
    50%      { box-shadow: 0 0 10px 1px rgba(229,204,128,0.45); }
}
@media (prefers-reduced-motion: reduce) {
    .detail-action-btn-sim { animation: none; box-shadow: 0 0 8px 0 rgba(229,204,128,0.35); }
}

.detail-col-headers {
    display: grid;
    grid-template-columns: 24px 1fr 130px 80px 60px;
    padding: 6px 14px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border);
}
.detail-row {
    display: grid;
    grid-template-columns: 24px 1fr 130px 80px 60px;
    align-items: center;
    padding: 6px 14px;
    position: relative;
    overflow: hidden;
    border-bottom: 1px solid rgba(255,255,255,0.03);
}
/* Claude: expanded miss-column layout for the damage tab */
.detail-col-headers.expanded,
.detail-row.expanded {
    /* Claude: the 4 miss columns (Dodge/Parry/Miss/Other) are minmax(0,52px) so they SHRINK
       first when horizontal space is tight; the Spell/Ability column is minmax(150px,1fr) so it
       holds a readable floor and grows when there's room — only the miss columns give way on
       narrow viewports, the ability name no longer truncates first. */
    grid-template-columns: 24px minmax(150px, 1fr) 50px 80px minmax(0, 52px) minmax(0, 52px) minmax(0, 52px) minmax(0, 52px) 72px 50px;
}
/* Claude: shrink header text in expanded mode so labels fit the narrow miss columns */
.detail-col-headers.expanded span {
    font-size: 9px;
    letter-spacing: 0.2px;
}
/* Claude: Damage Taken tab — extra School column between Spell and Hits */
/* Claude: damage-school tabs (damage / friendly fire / damage taken) get an extra School
   column between Spell and Hits — compact and expanded variants. */
.detail-col-headers.school-col:not(.expanded),
.detail-row.school-col:not(.expanded) {
    grid-template-columns: 24px 1fr 72px 130px 80px 60px;
}
.detail-col-headers.school-col.expanded,
.detail-row.school-col.expanded {
    /* Claude: same shrink-first miss columns + protected Spell/Ability floor, with School col. */
    grid-template-columns: 24px minmax(150px, 1fr) 72px 50px 80px minmax(0, 52px) minmax(0, 52px) minmax(0, 52px) minmax(0, 52px) 72px 50px;
}
.detail-school {
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
/* Claude (v0.92.19): tabular-nums + tighter dash centering on the miss columns so
   the dashes ("—") in empty cells sit visually under the numbers above. */
.detail-miss { font-size: 12px; color: var(--text-muted); text-align: right; font-variant-numeric: tabular-nums; overflow: hidden; } /* Claude: clip (don't overlap Crits) when a miss column shrinks on narrow viewports */
.detail-row:last-child { border-bottom: none; }
.detail-bar   { position: absolute; left: 0; top: 0; bottom: 0; opacity: 0.13; pointer-events: none; }
.detail-spell { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 8px; }
.detail-hits  { font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.detail-total { text-align: right; font-family: monospace; font-size: 13px; color: var(--accent); font-variant-numeric: tabular-nums; }
.detail-pct   { text-align: right; font-family: monospace; font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* Claude (v0.92.19): subtle row hover on the per-spell drilldown — same treatment as
   the meter rows. Selected/active not relevant here so just the hover. */
.detail-row { transition: background 0.12s; }
.detail-row:hover { background: rgba(255,255,255,0.04); }

/* Claude (v0.36.5): Buffs detail panel layout — adds an "Uptime timeline" column
   that shows actual time windows instead of just a fill-bar proportional to %.
   Claude (v0.36.7): widened columns + standardized gap. UPTIME % header was
   wrapping because the column was only 56px; bumped to 70px and reduced count
   column to 56px (1× / 99× fits in less). Added column-gap so the timeline
   track doesn't touch the % cell. */
.detail-row-buff,
.detail-col-headers.detail-col-headers-buff {
    grid-template-columns: 24px minmax(0, 1fr) 56px 70px minmax(160px, 2.5fr);
    column-gap: 12px;
}
/* Claude (v0.97.4): destination/target filter dropdown in the buff/debuff detail
   header (Applied mode only). Compact dark select that matches the action buttons. */
.detail-target-select {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
    border-radius: 5px;
    padding: 4px 8px;
    font-size: 11px;
    font-weight: 600;
    max-width: 180px;
    cursor: pointer;
    transition: border-color 0.12s;
}
.detail-target-select:hover { border-color: var(--accent); }

/* Claude (v0.97.3): Applied-mode buff/debuff detail adds a 6th column so it can
   show Count (applications) AND Targets (distinct targets) side by side. The
   `applied-cols` modifier is added to the header, the axis row, and every data
   row together so the columns stay aligned. Layout: icon | name | Count | Targets
   | Uptime% | timeline. */
.detail-row-buff.applied-cols,
.detail-col-headers.detail-col-headers-buff.applied-cols {
    grid-template-columns: 24px minmax(0, 1fr) 48px 56px 64px minmax(150px, 2.4fr);
}
.detail-row-buff .detail-spell { padding-right: 0; }
.detail-row-buff .detail-total,
.detail-row-buff .detail-pct,
.detail-col-headers.detail-col-headers-buff > span:nth-child(3),
.detail-col-headers.detail-col-headers-buff > span:nth-child(4) {
    text-align: right;
    white-space: nowrap;
}
.detail-col-headers.detail-col-headers-buff > span:nth-child(5) {
    text-align: left;
    white-space: nowrap;
}
/* Applied-cols header alignment: cols 3,4,5 right (Count, Targets, Uptime%),
   col 6 left (timeline). Overrides the 5-col nth-child(5) left-align above. */
.detail-col-headers.detail-col-headers-buff.applied-cols > span:nth-child(5) {
    text-align: right;
}
.detail-col-headers.detail-col-headers-buff.applied-cols > span:nth-child(6) {
    text-align: left;
    white-space: nowrap;
}
.aura-tl-track {
    position: relative;
    height: 16px;
    background: rgba(255,255,255,0.04);
    border-radius: 3px;
    overflow: hidden;
    border: 1px solid rgba(255,255,255,0.05);
    cursor: crosshair;
}
.aura-tl-segment {
    position: absolute;
    top: 0;
    bottom: 0;
    border-radius: 1px;
    opacity: 0.85;
    pointer-events: none;
}
.aura-tl-empty {
    color: var(--text-muted);
    font-size: 10px;
    font-style: italic;
    padding: 2px 8px;
}

/* Claude (v0.36.8): time axis row above the aura rows + shared hover cursor
   that spans across all tracks so the user can see what aura was up at any
   given fight time at a glance. Axis row uses the same grid as detail-row-buff
   so the timeline column aligns with the rows below. */
.detail-row-buff.aura-tl-axis-row {
    padding: 4px 14px 6px;
    border-bottom: 1px solid var(--border);
    font-size: 10px;
    color: var(--text-muted);
    cursor: default;
}
.aura-tl-axis {
    position: relative;
    height: 14px;
}
.aura-tl-tick {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.aura-tl-tick::before {
    content: '';
    display: block;
    height: 4px;
    width: 1px;
    background: rgba(255,255,255,0.18);
    margin: 0 auto -2px;
}
.aura-tl-cursor {
    position: absolute;
    top: -2px;
    bottom: -2px;
    width: 1px;
    background: rgba(255,255,255,0.7);
    opacity: 0;
    pointer-events: none;
    z-index: 2;
}
.aura-tl-cursor.visible { opacity: 1; }
.aura-tl-time-label {
    display: none;
    position: absolute;
    top: 14px;
    transform: translateX(-50%);
    font-size: 10px;
    color: var(--text);
    background: var(--surface2);
    padding: 1px 6px;
    border-radius: 3px;
    border: 1px solid var(--border);
    pointer-events: none;
    z-index: 3;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.aura-tl-time-label.visible { display: block; }

/* Claude (v0.36.10): always-visible time readout that lives in the axis row.
   Shows the cursor's current fight time anchored at the top of the panel so
   users see the time regardless of which row their mouse is over. The
   per-row .aura-tl-time-label still shows a redundant label near the cursor
   for convenience, but THIS is the one that always works. */
.aura-tl-axis-time {
    position: absolute;
    top: -2px;
    transform: translateX(-50%);
    font-size: 10px;
    font-weight: 700;
    color: var(--accent);
    background: var(--surface);
    padding: 1px 6px;
    border-radius: 3px;
    border: 1px solid var(--accent);
    pointer-events: none;
    z-index: 4;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ── Spell hover tooltip ─────────────────────────────────────── */
#spell-tooltip {
    display: none;
    position: fixed;
    z-index: 1000;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 0 6px;
    min-width: 320px;
    max-width: 440px;
    max-height: 85vh;
    overflow-y: auto;
    box-shadow: 0 6px 24px rgba(0,0,0,0.6);
    pointer-events: none;
}
#spell-tooltip.visible { display: block; }

.tt-title {
    display: flex;
    align-items: center;
    gap: 7px;
    font-weight: 700;
    font-size: 13px;
    padding: 0 14px 8px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 4px;
}
.tt-header {
    display: grid;
    grid-template-columns: 24px 1fr 90px 50px 44px;
    padding: 3px 14px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.7px;
    color: var(--text-muted);
}
.tt-row {
    display: grid;
    grid-template-columns: 24px 1fr 90px 50px 44px;
    align-items: center;
    padding: 3px 14px;
    font-size: 12px;
    position: relative;
    overflow: hidden;
}
.tt-bar {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    opacity: 0.12;
    pointer-events: none;
}
.tt-spell { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 6px; }
.tt-hits  { font-size: 11px; color: var(--text-muted); }
.tt-val   { text-align: right; font-family: monospace; color: var(--accent); }
.tt-pct   { text-align: right; font-family: monospace; color: var(--text-muted); }

/* ── Deaths detail panel ─────────────────────────────────────── */

.detail-section-header {
    padding: 7px 14px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-muted);
    background: var(--surface2);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}
.detail-section-header:first-child { border-top: none; }

.death-block-header {
    padding: 6px 14px 4px;
    font-size: 13px;
    font-weight: 700;
    color: var(--red);
    border-bottom: 1px solid rgba(255,255,255,0.05);
}

.death-hit-row {
    display: grid;
    grid-template-columns: 24px 1fr 110px 56px 44px 64px;
    align-items: center;
    padding: 3px 14px;
    font-size: 12px;
    border-bottom: 1px solid rgba(255,255,255,0.02);
    position: relative;
    overflow: hidden;
}
/* Claude: school/class-colored magnitude bar behind each death-recap event (like .detail-bar) */
.death-hit-bar    { position: absolute; left: 0; top: 0; bottom: 0; opacity: 0.13; pointer-events: none; }
.death-hit-spell  { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 6px; }
.death-hit-source { color: var(--text-muted); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; }
.death-hit-ts     { font-family: monospace; font-size: 11px; color: var(--text-muted); text-align: right; padding-right: 4px; }
.death-hit-delta  { font-family: monospace; font-size: 10px; color: var(--text-muted); text-align: right; padding-right: 4px; }
.death-hit-amount { text-align: right; font-family: monospace; }

/* ── Saved Logs panel ────────────────────────────────────────── */
#overview-section { /* Claude: removed dead #saved-logs-section from combined selector */
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
}
#saved-logs-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 16px;
    background: var(--surface2);
    border-bottom: 1px solid var(--border);
    cursor: default;
}
#saved-logs-title {
    font-size: 13px;
    font-weight: 700;
    color: var(--accent);
    letter-spacing: 0.3px;
}
/* Claude: #saved-logs-toggle, #saved-logs-list, .raid-category-* removed — dead selectors, never referenced in JS/HTML */
/* Claude: show exactly 3 full rows (measured 164px from live DOM), snap scroll per row */
/* Claude (v0.86.22→v0.86.24): widget height history is the saga of
   row-height-misestimation. v0.86.22 set 432px (claimed 8 rows, actually
   ~10.5). v0.86.23 set 615px (claimed 15 rows, also rough). User wants
   exactly 10 rows visible with no partial row at the bottom. Row height
   measured at ~41.14px in the live DOM (432px / 10.5 visible rows). For
   10 rows: 10 × 41.14 = 411.4px. Setting 410px to guarantee no overflow
   produces a sliver of partial-row peek — go slightly UNDER the exact
   multiple so any rendering rounding still falls inside the boundary. */
#overview-list {
    max-height: 410px;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
    scroll-snap-type: y mandatory;
}
.saved-log-row { scroll-snap-align: start; }
.raid-category-body { padding: 4px 0; }

.merged-badge {
    display: inline-block;
    color: #68d391;
    font-size: 11px;
    font-weight: 600;
    background: #1a2e1a;
    border: 1px solid #2d5a3a;
    border-radius: 3px;
    padding: 1px 6px;
    margin-left: 6px;
    vertical-align: middle;
}

.saved-log-row {
    display: grid;
    /* Claude: columns — guild | name (meta, 1fr) | server pill | info | view button */
    grid-template-columns: auto 1fr auto auto auto;
    align-items: center;
    gap: 10px;
    padding: 7px 16px;
    border-bottom: 1px solid rgba(255,255,255,0.03);
}
/* Claude: admin variant — extra columns for Raw dropdown, Relevance badge, Actions dropdown */
.saved-log-row.admin-row {
    /* Claude: guild column is 170px — fits the 20-char maxlength enforced on upload
       (index.html #pub-upload-guild) including pill padding + border. Any legal guild
       name renders without overflowing into the next column, and the fixed width keeps
       every row's columns aligned.
       Server column: 90px — fits 'Gurubashi' + padding. Sortable via admin sort bar.
       Relevance column: 56px — fits .rel-badge (min-width 36 + 16px padding + 2px border)
       plus a little breathing room before the Actions button. Was 30px and overflowed.
       Columns: id | raw | guild | server | meta | raid | info | relevance | actions
       (Claude v0.99.x: added the 150px Raid/Dungeon column between meta and info). */
    grid-template-columns: auto auto 170px 90px 1fr 150px 200px 56px 80px;
    gap: 0 6px; /* Claude: no row gap, tight 6px column gap (overrides parent 10px) */
    transition: background 0.12s;
    cursor: default;
}
.saved-log-row:last-child { border-bottom: none; }
.saved-log-row:hover { background: var(--surface2); }
.saved-log-row.hidden-row { opacity: 0.45; } /* Claude: admin hidden logs */

.saved-log-guild {
    width: fit-content;   /* Claude: pill wraps text only — not full column width */
    /* Claude: left-align inside the 170px grid column (was centered via margin:0 auto).
       Matches the left-aligned Guild sort button in the admin sort bar. */
    margin: 0;
    padding: 2px 9px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.3px;
    white-space: nowrap;
    text-align: center;
    background: #2a1f00;
    color: #f0c050;
    border: 1px solid #5a4520;
}
.saved-log-guild.pug {
    background: #1a1f2e;
    color: #8a95a8;
    border-color: #3a4558;
}

/* Claude: guild profile roster — class-colored player pills with appearance count */
.guild-roster-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.guild-roster-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 4px 3px 10px;
    border-radius: 12px;
    border: 1px solid;
    background: rgba(255, 255, 255, 0.02);
    font-size: 12px;
    font-weight: 600;
    text-decoration: none;
    transition: background 0.12s, border-color 0.12s;
    white-space: nowrap;
}
.guild-roster-pill:hover {
    background: rgba(255, 255, 255, 0.06);
    filter: brightness(1.2);
}
.guild-roster-count {
    display: inline-block;
    min-width: 18px;
    padding: 1px 6px;
    border-radius: 10px;
    background: rgba(0, 0, 0, 0.4);
    color: var(--text-muted);
    font-size: 10px;
    font-weight: 700;
    text-align: center;
}

/* Claude: rankings table guild link — transparent so it blends with the row's parse bar gradient */
/* Claude (v0.56.25): inline-flex + gap so the verified guild icon sits cleanly before the name */
.rk-guild {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 1px 8px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.3px;
    white-space: nowrap;
    background: transparent;
    color: rgba(240, 192, 80, 0.75);
    border: 1px solid rgba(240, 192, 80, 0.35);
    text-decoration: none;
    transition: color 0.12s, border-color 0.12s;
}
.rk-guild:hover {
    color: #f0c050;
    border-color: rgba(240, 192, 80, 0.7);
}

/* Claude (v0.33.2): restyled to match the "Log ↗" gold/orange pill. Was using the
   muted-grey palette + cyan marked-state which clashed with the rest of the site's
   dark + orange aesthetic. Now: transparent bg with semi-gold border by default,
   solid gold on hover, soft gold-tinted fill when marked. */
.rk-compare-btn {
    display: inline-block;
    padding: 2px 7px;
    border: 1px solid rgba(212, 175, 55, 0.35);
    border-radius: 4px;
    background: transparent;
    color: rgba(212, 175, 55, 0.8);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    line-height: 1;
    transition: all 0.12s;
}
.rk-compare-btn:hover {
    border-color: var(--accent);
    color: var(--accent);
}
.rk-compare-btn.marked {
    background: rgba(212, 175, 55, 0.15);
    border-color: var(--accent);
    color: var(--accent);
}
.rk-compare-btn.marked:hover { background: rgba(212, 175, 55, 0.25); }

.saved-log-meta {
    font-size: 12px;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.saved-log-meta strong { color: var(--text); font-weight: 600; }

.saved-log-info {
    font-size: 11px;
    color: var(--text-muted);
    white-space: nowrap;
}
.saved-log-info .boss-count { color: var(--accent); font-weight: 600; }

.saved-log-load {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 4px 12px;
    border-radius: 5px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: all 0.12s;
}
.saved-log-load:hover { border-color: var(--accent); color: var(--accent); }

/* Claude: .saved-log-delete removed — dead selector, never referenced */

/* ── Modal ────────────────────────────────────────────────────── */
.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.65);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    backdrop-filter: blur(2px);
}
.modal-overlay.hidden { display: none; }

.modal-box {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    width: 380px;
    box-shadow: 0 20px 60px rgba(0,0,0,0.5);
    animation: modal-in 0.18s ease;
}
@keyframes modal-in {
    from { transform: translateY(-16px); opacity: 0; }
    to   { transform: translateY(0);     opacity: 1; }
}
.modal-title {
    padding: 16px 20px 12px;
    font-size: 15px;
    font-weight: 700;
    color: var(--accent);
    border-bottom: 1px solid var(--border);
}
.modal-body {
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
/* Claude: removed unused .modal-label, .modal-input, .modal-pug-label, .modal-footer (no references in HTML/JS) */
.modal-btn {
    padding: 7px 20px;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    border: 1px solid transparent;
    transition: all 0.12s;
}
/* Claude: removed unused .modal-btn-primary (no references in HTML/JS) */
.modal-btn-secondary {
    background: var(--surface2);
    border-color: var(--border);
    color: var(--text-muted);
}
.modal-btn-secondary:hover { border-color: var(--text-muted); color: var(--text); }

/* Claude (sim-perfect-play): modal buttons need an explicit theme bg — .modal-btn
   alone is transparent/unstyled and renders as a white default button. */
.spp-btn {
    background: var(--surface2);
    color: var(--text);
    border: 1px solid var(--border);
}
.spp-btn:hover { border-color: var(--accent, #e5cc80); background: rgba(229,204,128,0.10); }
/* The "Use Armory gear" action — readable gold, not the dark header-button tint. */
.spp-btn-gold {
    background: linear-gradient(180deg, rgba(229,204,128,0.20) 0%, rgba(229,204,128,0.10) 100%);
    color: #f3dd92;
    border: 1px solid rgba(229,204,128,0.65);
}
.spp-btn-gold:hover { background: linear-gradient(180deg, rgba(229,204,128,0.32) 0%, rgba(229,204,128,0.16) 100%); }

/* ── Scrollbar ───────────────────────────────────────────────── */
/* Claude: removed the 6px ::-webkit-scrollbar block here — it was fully shadowed
   by the later universal *::-webkit-scrollbar rule (8px, --border-strong) in the
   v0.89.1 polish bundle, so it never rendered. Single source of truth now. */

/* ── Progression page rows ──────────────────────────────────────────────── */
/* Claude: shared layout for both "First Full Clear" and "Per-boss First Kills" tables.
   Grid columns: rank/boss · team · server · date. Rows are clickable; hover brightens. */
.prog-row {
    display: grid;
    /* Claude: rank/boss · team · server · date.
       Claude (v0.54.6): date column shrunk 170px → 95px now that fmtDate emits the compact
       "3 May · 23:18" format (was "17 Apr 2026 · 20:34:11"). The reclaimed 75px goes to the
       team (1fr) column so guild names stop truncating.
       Claude (v0.54.8): server-pill column changed from auto (sized to content) to fixed
       90px so it matches the Guild Progression rows' inline 90px pill column. With matching
       column widths AND server-pill centered (see rule below), all three progression
       sub-cards now have visually identical pill placement.
       Claude (v0.56.12): server column shrunk 90px → 46px now that the badge holds a
       2-char abbreviation ("KZ" / "GB"). The reclaimed 44px flows into the 1fr team column
       so longer guild names don't truncate. */
    grid-template-columns: 70px 1fr 46px 95px;
    gap: 10px;
    align-items: center;
    padding: 8px 14px;
    border-bottom: 1px solid rgba(255,255,255,0.03);
    cursor: pointer;
    font-size: 12px;
    transition: background 0.12s;
}
/* Claude (v0.54.8): center the server-pill within its 90px grid column on every progression
   row variant (.prog-row covers First Full Clear + Per-boss First Kills; .prog-guild-row,
   which also carries .prog-row, covers Guild Progression). justify-self is the per-grid-item
   alignment property; default is `start` (left), `center` puts the pill mid-column with
   equal padding on each side. */
.prog-row .server-pill {
    justify-self: center;
}
/* Claude (v0.56.13/20): Per-boss First Kills variant. v0.56.13 set the boss column
   to 140px to fit full names ("Sulfuron Harbinger", "Golemagg the Incinerator",
   "Majordomo Executus"). v0.56.20 truncates the boss name to its first word in
   progression.js so the column shrinks to 90px — fits longest single-word names
   ("Majordomo" / "Sulfuron" / "Golemagg") with icon + gap. The 50px reclaimed
   flows into the 1fr team column. */
.prog-row.prog-firstkill-row {
    grid-template-columns: 90px 1fr 46px 95px;
}
/* Claude: mobile — stack the two panel columns (progression.js uses inline
   grid-template-columns:1fr 1fr, so we target the parent via its child selector and
   override with !important since inline styles otherwise win). Each row wraps the date
   under the team name so the timestamp stays visible without overflow. */
@media (max-width: 700px) {
    #progression-body > div > div[style*="grid-template-columns:1fr 1fr"],
    .prog-home-block [style*="grid-template-columns:1fr 1fr"] {
        grid-template-columns: 1fr !important;
    }
    .prog-row {
        grid-template-columns: 44px 1fr auto !important;
        grid-template-rows: auto auto;
        padding: 10px 14px;
        row-gap: 2px;
    }
    .prog-date {
        grid-column: 2 / -1 !important;
        grid-row: 2 !important;
        text-align: left !important;
        font-size: 10px;
    }
}
.prog-row:last-child  { border-bottom: none; }
.prog-row:hover       { background: var(--surface2); }
.prog-rank            { font-size: 14px; font-weight: 700; text-align: center; }
/* Claude (v0.56.20/23): guild names use the orange accent treatment; boss names
   use the boss/NPC color (--boss, light coral) so the two are visually distinct
   in the same row. Matches the convention applied site-wide in v0.56.23. */
.prog-boss            { font-size: 12px; font-weight: 700; color: var(--boss);   overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prog-team            { color: var(--accent); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prog-date            { color: var(--text-muted); font-size: 11px; text-align: right; }
/* Pre-release raids (no kills yet) render at reduced contrast */
.prog-raid-block.pre-release h3 { color: var(--text-muted); }

/* ── Mobile baseline rules (apply across all pages) ──────────────────────── */
@media (max-width: 700px) {
    /* Claude: iOS Safari zooms the viewport when the user focuses a form field whose
       computed font-size is below 16px. Bump every form control to 16px to stop the
       zoom-in — accessible alternative to blocking pinch-zoom via maximum-scale. */
    input, select, textarea,
    .header-search-input, .meta-select { font-size: 16px !important; }

    /* Claude: 44×44 is Apple's accessibility guideline for tap targets. Apply to the
       header nav + admin button. Per-page smaller buttons still exist; we enforce the
       floor on the critical nav and the admin widget action buttons. */
    .header-rankings-btn, .header-admin-btn, .filter-btn,
    .tab-btn, .tf-btn, .action-btn {
        min-height: 44px;
        min-width:  44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    /* Upload modal: full-screen on phones so form + submit stay reachable. */
    #upload-modal-overlay > div {
        max-width: 100vw !important;
        width: 100% !important;
        max-height: 100vh !important;
        margin: 0 !important;
        border-radius: 0 !important;
        overflow-y: auto;
    }
    /* Meta filter bar: drop the inline labels on narrow screens so the row actually fits. */
    #meta-filters label { display: none; }
    #meta-filters { gap: 6px !important; padding: 10px !important; }

    /* Claude (v0.87.4): mobile hamburger nav — hide the secondary header buttons +
       Tools dropdown + Upload button. js/utils.js injects a hamburger button and a
       slide-out drawer that contains the same links + the expanded Tools menu. The
       H1 / search / auth widget stay visible in the slim mobile header. Saves
       ~80-100px of vertical viewport per page (was wrapping to 3 rows × 44px). */
    .header-inner > .header-rankings-btn,
    .header-inner > .header-tools-wrap { display: none !important; }
    .header-mobile-menu-btn { display: inline-flex !important; }

    /* Claude (v0.87.4): mobile typography floor. Many JS-generated widgets use
       inline font-size:10px/11px which is squint-only on phones. Bump the floor to
       12px in dense widgets. Surgical — we target the worst offenders rather than
       overriding * { font-size } which would break carefully sized tables. */
    .saved-log-info, .saved-log-row .saved-log-info,
    .pwgt-row, .pwgt-row *,
    .rel-badge, .server-pill,
    #overview-list .saved-log-info,
    .detail-row-extra, .detail-row-extra * {
        font-size: 12px !important;
    }

    /* Claude (v0.87.4): tap-target floor for inline-styled buttons inside the
       homepage widgets + log viewer detail panel. Existing 44×44 rule (line 1536)
       only covers class-tagged buttons; inline-styled <button>/<a> elements with
       padding:3px 9px;font-size:11px slip through. Extend the floor to the dense
       widget regions. min-height:36 is tighter than the 44 a11y guideline to
       preserve the meter-row density expected by power users, but well above the
       sub-30px sliver that's currently unhittable on a touch screen. */
    #overview-list button, #overview-list a.action-link,
    .pwgt-list button, .pwgt-list a,
    /* Claude (v0.87.7): added .rules-attention-btn — the homepage "Rules" pulse
       button next to Speedruns / Top Guilds. v0.87.5 selector required
       [role="button"] which wasn't set on this element, so the floor didn't
       bite. Now matches by class directly. */
    .rules-attention-btn, [class*="rules"][role="button"], .dsr-rules-btn,
    #detail-panel button, #detail-panel a.action-link {
        min-height: 36px;
        display: inline-flex;
        align-items: center;
    }

    /* Claude (v0.87.4): table overflow safety net. Wide JS-generated tables that
       don't have their own breakpoint rules (player.html .pp-table notably) get a
       horizontal-scroll affordance so content stays reachable instead of cropping
       off-screen. Pattern already in use for guild-instance tables at ≤600px and
       conrank — extending to the long tail. */
    .pp-table-wrap, .home-row-table-wrap {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
}

/* ── Meta page filter selects ───────────────────────────────────────────── */
/* Claude: native <select> defaults to white in dark-mode browsers — theme to match surface2. */
.meta-select {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 5px 10px;
    border-radius: 5px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    outline: none;
    transition: border-color 0.12s;
}
.meta-select:hover  { border-color: var(--accent); }
.meta-select:focus  { border-color: var(--accent); }
.meta-select option { background: var(--surface); color: var(--text); }

/* ── Timeframe filter buttons (index.html) ────────────────────────────── */
.tf-btn {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text-muted);
    padding: 4px 12px;
    border-radius: 5px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.12s;
}
.tf-btn:hover  { border-color: var(--accent); color: var(--text); }
.tf-btn.active { background: #2a1f00; border-color: var(--accent); color: var(--accent); }

/* ── Player search dropdown ────────────────────────────────────────────── */
/* Claude (v0.92.45): polished — wider dropdown for two-line log rows, gradient
   surface to match the v0.92.26 elevated button family, larger box-shadow + 1px
   inset top highlight, min-width so log meta line doesn't crowd against the badge. */
.player-search-dropdown {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    min-width: 320px;
    max-width: 420px;
    background: linear-gradient(180deg, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0.01) 100%), var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 12px 32px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.05);
    z-index: 500;
    overflow: hidden;
    padding: 4px 0;
}
.player-search-dropdown.hidden { display: none; }
.ps-result {
    padding: 8px 14px;
    font-size: 13px;
    cursor: pointer;
    color: var(--text);
    transition: background 0.1s, color 0.1s;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}
.ps-result:hover { background: rgba(212,175,55,0.07); color: var(--accent); }
.ps-result-main {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Claude (v0.92.45): two-line log row layout — main filename on top, meta line
   (guild · raid · date) below in muted small text. Wraps the text in .ps-result-text
   so it can grow while the #log-id badge stays right-aligned at fixed width. */
.ps-result-log .ps-result-text {
    flex: 1 1 auto;
    min-width: 0;
}
.ps-result-log .ps-result-sub {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 400;
}
.ps-result-log:hover .ps-result-sub { color: rgba(212,175,55,0.65); }
.ps-result-badge {
    font-family: monospace;
    font-size: 11px;
    color: var(--text-muted);
    background: rgba(255,255,255,0.04);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 6px;
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
}
.ps-result-log:hover .ps-result-badge {
    color: var(--accent);
    border-color: rgba(212,175,55,0.3);
}
.ps-result-arrow {
    color: var(--text-muted);
    font-size: 16px;
    line-height: 1;
    flex-shrink: 0;
    transition: transform 0.12s, color 0.12s;
}
.ps-result-guild:hover .ps-result-arrow {
    color: var(--accent);
    transform: translateX(2px);
}
/* Claude: section header in unified search dropdown — groups players / guilds / logs.
   v0.92.45: accent left-dot + icon + label, gradient strip matching .pp-section-title. */
.ps-header {
    padding: 8px 14px 6px;
    font-size: 10px;
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.6px;
    background: linear-gradient(90deg, rgba(212,175,55,0.05) 0%, transparent 70%);
    border-top: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 8px;
}
.ps-header:first-child { border-top: none; }
.ps-header-dot {
    width: 5px; height: 5px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 4px rgba(212,175,55,0.55);
    flex-shrink: 0;
}
.ps-header-icon { font-size: 11px; line-height: 1; }
.ps-header-label { color: var(--text); font-size: 10px; }
.ps-profile-btn {
    font-size: 11px;
    color: #e5cc80;
    background: linear-gradient(180deg, rgba(212,175,55,0.18) 0%, rgba(212,175,55,0.06) 100%), #2a1f00;
    border: 1px solid var(--accent);
    border-radius: 4px;
    padding: 2px 9px;
    text-decoration: none;
    font-weight: 600;
    white-space: nowrap;
    transition: background 0.12s, transform 0.12s, box-shadow 0.12s;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
    flex-shrink: 0;
}
.ps-profile-btn:hover {
    background: linear-gradient(180deg, rgba(212,175,55,0.32) 0%, rgba(212,175,55,0.14) 100%), #3a2a00;
    transform: translateY(-1px);
    box-shadow: 0 2px 6px rgba(212,175,55,0.25), inset 0 1px 0 rgba(255,255,255,0.08);
}

/* ── Rankings page ─────────────────────────────────────────────────────── */
.rankings-layout {
    display: flex;
    gap: 20px;
    align-items: flex-start;
}
.rankings-sidebar {
    width: 200px;
    flex-shrink: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
    position: sticky;
    top: 12px;
}
.rankings-sidebar-raid {
    padding: 8px 12px 4px;
    font-size: 11px;
    font-weight: 700;
    color: var(--accent);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    border-top: 1px solid var(--border);
}
.rankings-sidebar-raid:first-child { border-top: none; }

/* Claude (v0.91.3): meta-section grouping for the rankings sidebar. Each category
   (Raids / Training Dummies / Dungeons) wraps its raids in a <details> element so
   the user can collapse a whole category. Dungeons default collapsed; the others
   default open. Header is the <summary> — slightly larger than individual raid
   labels with a ▸/▾ arrow on the left. The summary itself replaces the marker. */
.rankings-sidebar-category {
    border-top: 1px solid var(--border);
}
.rankings-sidebar-category:first-child {
    border-top: none;
}
.rankings-sidebar-category-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    font-size: 12px;
    font-weight: 800;
    color: var(--text);
    text-transform: uppercase;
    letter-spacing: 0.10em;
    background: rgba(255, 255, 255, 0.025);
    cursor: pointer;
    user-select: none;
    list-style: none;
}
.rankings-sidebar-category-header::-webkit-details-marker { display: none; }
.rankings-sidebar-category-header::marker { display: none; }
.rankings-sidebar-category-header:hover {
    background: rgba(255, 255, 255, 0.05);
    color: var(--accent);
}
.rankings-sidebar-category-arrow {
    display: inline-block;
    width: 10px;
    font-size: 9px;
    color: var(--text-muted);
    text-align: center;
}
.rankings-sidebar-category-label {
    flex: 1;
}
/* When a category is collapsed, drop the top border on the first raid inside the
   NEXT category so the boundary reads cleanly. The details-state-rule handles it. */
.rankings-sidebar-category[open] .rankings-sidebar-raid:first-child {
    border-top: none;
}
/* Claude (v0.56.23): default boss-link color is --boss; muted on rest, brighter
   on hover, accent on the active page so the user always knows where they are. */
.rankings-sidebar-boss {
    display: block;
    padding: 5px 16px;
    font-size: 13px;
    /* Claude (v0.90.1): per-raid thematic colour. --raid-color is set on the parent
       .rankings-sidebar-raid-group from rankings.js based on GAME_RAID_COLORS.
       Falls back to --boss (the original salmon) for raids without a colour. */
    color: var(--raid-color, var(--boss));
    cursor: pointer;
    text-decoration: none;
    transition: background 0.1s;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.rankings-sidebar-boss:hover  { background: var(--surface2); color: var(--text); }
.rankings-sidebar-boss.active { background: #2a1f00; color: var(--accent); font-weight: 600; }
.rankings-main { flex: 1; min-width: 0; }
.rankings-filters {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 14px;
    flex-wrap: wrap;
}
.rk-metric-btn {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text-muted);
    padding: 5px 14px;
    border-radius: 5px;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
    transition: all 0.12s;
}
.rk-metric-btn:hover  { border-color: var(--accent); color: var(--text); }
.rk-metric-btn.active { background: #2a1f00; border-color: var(--accent); color: var(--accent); }
.rk-class-select {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text-muted);
    padding: 5px 10px;
    border-radius: 5px;
    font-size: 12px;
    outline: none;
    cursor: pointer;
}
.rk-class-select:focus { border-color: var(--accent); }
.rankings-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.rankings-table th {
    padding: 8px 10px;
    text-align: left;
    background: var(--surface2);
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    position: sticky;
    top: 56px; /* Claude: offset below fixed-height page header so they don't overlap */
    z-index: 10; /* Claude: above row backgrounds so sticky header doesn't get covered by bar gradients */
}
/* Claude (v0.56.9): tighter row padding (was 7px) — matches the homepage bar trim. */
.rankings-table td { padding: 4px 10px; border-top: 1px solid rgba(255,255,255,0.04); }
.rankings-table tr:hover td { background: var(--surface2); }
.rk-pagination {
    display: flex;
    gap: 8px;
    align-items: center;
    justify-content: center;
    margin-top: 16px;
    font-size: 13px;
    color: var(--text-muted);
}
.rk-page-btn {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text-muted);
    padding: 4px 12px;
    border-radius: 5px;
    font-size: 12px;
    cursor: pointer;
    transition: all 0.12s;
}
.rk-page-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.rk-page-btn:disabled { opacity: 0.35; cursor: default; }

/* Claude: mobile boss select — hidden by default, shown at ≤700px */
#rk-mobile-boss-select { display: none; }

/* Claude: header-only breakpoint at ≤1000px. Covers iPhone 14 Pro Max landscape (932px),
   iPad portrait (768-1024px), small laptops under the site's natural 1100px header width.
   Forces the nav onto a second row instead of cramming everything horizontally — also
   prevents the h1 from wrapping to 3 lines when buttons squeeze it. The narrower ≤700
   breakpoint below stays the primary "phone" rules block. */
@media (max-width: 1000px) {
    header { height: auto; }
    .header-inner {
        flex-wrap: wrap;
        padding: 8px 16px;
        row-gap: 6px;
    }
    /* Claude: h1 stays single-line (was wrapping into 3 rows when the flex row overflowed).
       flex: 0 0 auto + white-space: nowrap keeps "Epog Logs" intact on row 1. */
    header h1 {
        flex: 0 0 auto;
        white-space: nowrap;
        font-size: 18px;
    }
    /* Give the search input room to breathe once the row wraps */
    .player-search-wrap { flex: 1 1 220px; }
    .header-search-input { width: 100%; }
    /* Claude: version badge sits at the end of header-inner and would otherwise overflow
       off-screen when the row wraps. Let it drop onto its own row cleanly. */
    .version-badge { margin-left: auto; }
}

/* ── Mobile responsive ───────────────────────────────────────────────────── */
@media (max-width: 700px) {
    /* Claude: mobile — let header grow if content wraps; inner wrapper uses smaller padding */
    header { height: auto; }
    .header-inner { padding: 10px 12px; gap: 6px; flex-wrap: wrap; }
    header h1 { font-size: 17px; }
    /* Hide non-essential header items on small screens */
    .header-copy-btn { display: none; }
    .version-badge   { display: none; }

    main { padding: 12px 8px; gap: 10px; }

    /* Fight row — stack filters above select */
    #fight-row { gap: 6px; }
    #fight-select { min-width: 0; }

    /* Tabs — allow wrapping */
    #tabs-row { gap: 6px; }
    .tabs { flex-wrap: wrap; gap: 4px; }
    .tab-btn { font-size: 11px; padding: 5px 9px; }
    .pet-toggle-btn { font-size: 11px; padding: 3px 7px; }

    /* Meter — drop Per Sec and % columns, keep parse + rank + icon + name + total + iLvl */
    .meter-header,
    .meter-row {
        /* Claude (v0.99.x): iLvl is the last (6th visible) column on tablet. */
        grid-template-columns: 34px 30px 22px 1fr 80px 50px;
    }
    /* Hide Per Sec (6th) and % (7th); iLvl (8th) is kept */
    .meter-header > span:nth-child(6),
    .meter-header > span:nth-child(7),
    .meter-row > .meter-ps,
    .meter-row > .meter-pct { display: none; }

    /* Detail panel */
    .detail-col-headers,
    .detail-row,
    .detail-col-headers.expanded,
    .detail-row.expanded,
    .detail-col-headers.school-col,
    .detail-row.school-col {
        grid-template-columns: 24px 1fr 80px 60px;
    }
    /* Hide % column, all miss columns, and the School column on mobile */
    .detail-col-headers > span:last-child,
    .detail-row > .detail-pct,
    .detail-school,
    .detail-miss { display: none; }

    /* Claude (v0.87.12): log viewer mobile polish.
       (1) Activity chart hero shrinks from 240px → 160px so it doesn't dominate
       the top half of the viewport. Inline height:240px on log.html:133 wins
       without !important here because the inline style targets the wrap element
       — we override via the !important child rule. */
    #activity-chart-svg-wrap { height: 160px !important; }
    /* Tighten chart padding so chart edge sits closer to the page edge. */
    #activity-chart { padding: 10px 8px 6px !important; }
    #activity-chart-widgets {
        /* Widget grid stays 2-col on mobile but with tighter gap. */
        gap: 6px !important;
    }
    /* (2) Hide the timeline hotkeys panel entirely on mobile — keyboard
       shortcuts are irrelevant on touch. The panel is opened by `?` keypress
       which doesn't exist on phones; hiding it also covers the unlikely case
       of it showing up via some other path. */
    .tl-hotkeys-panel { display: none !important; }

    /* Footer */
    footer { padding: 16px 8px; }

    /* Rankings — hide sidebar, show mobile dropdown */
    .rankings-sidebar { display: none; }
    #rk-mobile-boss-select {
        display: block;
        width: 100%;
        background: var(--surface2);
        border: 1px solid var(--border);
        color: var(--text);
        padding: 8px 10px;
        border-radius: 6px;
        font-size: 13px;
        margin-bottom: 12px;
    }
    #rk-mobile-boss-select option { background: var(--surface2); color: var(--text); }
    #rk-mobile-boss-select optgroup { background: var(--surface); color: var(--accent); }
    /* Claude: hide placeholder texts on mobile — dropdown replaces sidebar */
    .rk-placeholder { display: none; }
    #rk-title { display: none; }
    .rankings-layout { flex-direction: column; gap: 0; }

    /* Rankings table — smaller text, tighter padding */
    .rankings-table { font-size: 12px; }
    .rankings-table th, .rankings-table td { padding: 5px 6px; }

    /* Touch targets — minimum 36px height for interactive elements */
    .filter-btn, .rk-metric-btn, .tf-btn, .tab-btn, .rk-page-btn,
    .saved-log-load, .header-rankings-btn {
        min-height: 36px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* Claude: mobile boss select hidden by default — shown in 700px media query above */

/* ── Medium phones (≤600px) ─────────────────────────────────────────────── */
@media (max-width: 600px) {
    /* Rankings filters — compress + wrap timeframe to new row */
    .rankings-filters { gap: 6px; flex-wrap: wrap; }
    .rk-metric-btn { padding: 4px 10px; font-size: 11px; }
    .rk-class-select { font-size: 11px; padding: 4px 8px; min-height: 36px; }
    .tf-btn { padding: 3px 8px; font-size: 11px; }
    /* Claude: Timeframe label — remove left margin on mobile so wrapping looks clean */
    .tf-label { margin-left: 0 !important; }

    /* Claude: stale 7-col-table column-hides removed — they hit value(6)/Parse%(7)
       on the live 10-col table (the data you want). The consolidated phone layer
       (section H, ~line 5648) is now the single source of rankings column hiding. */

    /* Speedrun tables — smaller font + tighter padding */
    [data-speedrun-body] table { font-size: 11px; }
    [data-speedrun-body] th,
    [data-speedrun-body] td { padding: 6px 8px !important; }

    /* Parse widgets — max 2 columns */
    .parse-widget { min-width: 140px; }

    /* Guild page tables — horizontal scroll */
    [data-instance] > div { overflow-x: auto; -webkit-overflow-scrolling: touch; }

    /* Admin tables — horizontal scroll */
    .admin-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* ── Small phones (≤480px) ──────────────────────────────────────────────── */
@media (max-width: 480px) {
    /* Header — compress */
    .header-search-input { width: 100px; font-size: 12px; }
    .header-rankings-btn { padding: 3px 6px; font-size: 11px; margin-right: 0; }
    .header-admin-btn { font-size: 14px; padding: 2px 4px; }
    header h1 { font-size: 14px; }
    header .subtitle { display: none; }

    /* Rankings filters — stack vertically */
    .rankings-filters { flex-direction: row; flex-wrap: wrap; gap: 4px; }

    /* Claude: stale 7-col Parse%-hide removed — superseded by section H (~5648). */

    /* Tabs — horizontal scroll strip instead of wrapping */
    .tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    .tabs::-webkit-scrollbar { display: none; }
    .tab-btn { font-size: 10px; padding: 4px 6px; white-space: nowrap; }

    /* Meter — 4 columns: badge, icon, name, total */
    .meter-header, .meter-row {
        grid-template-columns: 30px 22px 1fr 70px;
        padding: 5px 8px;
    }
    /* Hide rank (#, 2nd) and iLvl (last, 8th) columns on the narrowest phones — Claude (v0.99.x) */
    .meter-header > span:nth-child(2),
    .meter-header > span:nth-child(8),
    .meter-row > .meter-rank,
    .meter-row > .meter-ilvl { display: none; }

    /* Detail panel — 3 columns */
    .detail-col-headers, .detail-row,
    .detail-col-headers.expanded, .detail-row.expanded,
    .detail-col-headers.school-col, .detail-row.school-col {
        grid-template-columns: 20px 1fr 60px;
        font-size: 11px;
        padding: 4px 8px;
    }
    .detail-col-headers > span:nth-child(3),
    .detail-row > .detail-hits,
    .detail-school { display: none; }
    /* Claude: school-col shifts columns by one (School at child 3) — also hide the Hits header
       at child 4 so the compact header stays aligned with its rows on the narrowest screens. */
    .detail-col-headers.school-col > span:nth-child(4) { display: none; }

    /* Spell tooltip — constrain to viewport */
    #spell-tooltip {
        min-width: 0 !important;
        max-width: calc(100vw - 16px) !important;
        left: 8px !important;
        right: 8px !important;
    }

    /* Fight selector */
    #fight-select { min-width: 0; flex: 1; font-size: 12px; }
    .filter-btn { padding: 4px 8px; font-size: 11px; }

    /* Parse widgets — single column */
    .parse-widget { min-width: 100%; flex-basis: 100%; }

    /* Saved logs — stack into a 2-line card on phones so the 5-col grid (guild, name,
       server, info, view) doesn't cram everything horizontally. Row 1: guild + name +
       view button. Row 2: server pill + metadata (boss count + fight count + date). */
    .saved-log-row {
        grid-template-columns: auto 1fr auto !important;
        grid-template-rows: auto auto;
        gap: 4px 8px;
        padding: 8px 12px;
    }
    .saved-log-guild { font-size: 10px; padding: 1px 6px; }
    .saved-log-meta { font-size: 11px; grid-column: 2 / 3; grid-row: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .saved-log-load { grid-column: 3 / 4; grid-row: 1; }
    .saved-log-row > .server-pill {
        grid-column: 1 / 2; grid-row: 2;
        justify-self: start;
    }
    .saved-log-info { font-size: 10px; grid-column: 2 / -1; grid-row: 2; }

    /* Claude (v0.87.9): footer was stacked one-line-per-item via flex-direction:column
       which produced an absurdly tall ~8-row footer on phones. User-reported. The
       default rule (footer { flex-wrap: wrap; gap: 6px 12px }) already wraps compactly.
       At ≤480px we just tighten the column-gap (12px → 8px) so 8+ items fit in 2-3
       wrapped rows on a 360px-wide viewport. Keep text centered for the wrapped look. */
    footer { gap: 6px 8px; text-align: center; justify-content: center; }

    /* Guild page — hide Wipes + Duration columns */
    [data-instance] table th:nth-child(4),
    [data-instance] table td:nth-child(4),
    [data-instance] table th:nth-child(5),
    [data-instance] table td:nth-child(5) { display: none; }

    /* Admin login — tighter margin */
    .admin-login { margin: 40px auto; padding: 20px 16px; }
}

/* ── Player Profile Page ─────────────────────────────────────────────── */

/* Claude (v0.28.3): flex layout so the armory CTA sits top-right while the player
   info (name + guild) stays on the left. Wraps on narrow screens so mobile stays sane. */
.pp-header {
    padding: 20px 0 8px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 16px;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
    flex-wrap: wrap;
}
.pp-header-info { flex: 1; min-width: 0; }
.pp-name {
    font-size: 22px;
    font-weight: 700;
    display: flex;
    align-items: center;
    gap: 4px;
}
.pp-guild {
    display: inline-block;
    margin-right: 10px;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 13px;
    margin-top: 4px;
}
.pp-guild:hover { color: var(--accent); }

/* Claude (v0.28.3): prominent armory shortcut.
   Claude (v0.92.37): elevated treatment matching v0.92.26 button family — gradient
   surface on a darker accent base + inset top-edge highlight + lift on hover. Same
   accent identity but reads as the elevated card surface instead of a flat fill. */
.pp-armory-btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    background: linear-gradient(180deg, #3a2d08 0%, #221805 100%);
    border: 1px solid var(--accent);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 1px 4px rgba(212,175,55,0.18);
    border-radius: 8px;
    padding: 10px 18px;
    color: var(--accent);
    text-decoration: none;
    font-size: 13px;
    font-weight: 700;
    line-height: 1.3;
    transition: background 0.12s, color 0.12s, transform 0.12s, box-shadow 0.12s;
    white-space: nowrap;
    flex-shrink: 0;
}
.pp-armory-btn:hover {
    background: linear-gradient(180deg, var(--accent) 0%, #b8941d 100%);
    color: #0e1117;
    transform: translateY(-1px);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.15), 0 4px 12px rgba(212,175,55,0.35);
}
.pp-armory-btn .pp-armory-icon { font-size: 22px; }
.pp-armory-btn .pp-armory-sub  { font-size: 10px; font-weight: 400; opacity: 0.85; display:block; margin-top:1px; }

.pp-specs {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 16px;
}
/* Claude (v0.92.37): elevated surface treatment to match the rest of the v0.92.26
   button family. Border-color stays inline (class-color per spec); we only add the
   gradient + inset highlight + hover lift on top. */
.pp-spec-pill {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: 12px;
    font-weight: 600;
    padding: 3px 10px;
    border: 1px solid;
    border-radius: 12px;
    background: linear-gradient(180deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0) 100%), var(--surface);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
    transition: transform 0.12s, box-shadow 0.12s;
}
.pp-spec-pill:hover {
    transform: translateY(-1px);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.07), 0 2px 6px rgba(0,0,0,0.3);
}

.pp-section {
    margin-bottom: 24px;
}
/* Claude (v0.92.37): added a small accent-tinted bar on the left edge of each
   section title so "BEST PARSES" / "RECENT RAIDS" / "PARSE HISTORY" read as proper
   section labels instead of just muted text. */
.pp-section-title {
    font-size: 13px;
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-bottom: 10px;
    padding: 4px 0 6px 10px;
    border-bottom: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    background: linear-gradient(90deg, rgba(212,175,55,0.06) 0%, transparent 60%);
}
.pp-raid-label {
    font-size: 11px;
    font-weight: 700;
    color: var(--accent);
    letter-spacing: 0.5px;
    margin: 14px 0 6px;
    text-transform: uppercase;
}
/* Claude: collapsible raid section on the player profile — same visual as .pp-raid-label
   but wraps a <details>/<summary> so each raid can be minimised/maximised independently.
   Claude (v0.92.37): styled as accent-tinted strip with hover background tint so the
   raid header reads as a proper clickable section instead of just naked accent text.
   Default <details> marker hidden so we control the affordance via padding/hover. */
.pp-raid-details {
    margin: 12px 0 6px;
    border: 1px solid transparent;
    border-radius: 6px;
}
.pp-raid-details[open] {
    border-color: rgba(212,175,55,0.18);
    background: linear-gradient(180deg, rgba(212,175,55,0.04) 0%, transparent 40%);
}
.pp-raid-summary {
    font-size: 11px;
    font-weight: 700;
    color: var(--accent);
    letter-spacing: 0.6px;
    text-transform: uppercase;
    cursor: pointer;
    user-select: none;
    padding: 8px 12px;
    outline: none;
    border-radius: 6px;
    transition: background 0.12s, color 0.12s;
}
.pp-raid-summary:hover { color: #ffd989; background: rgba(212,175,55,0.10); }
.pp-raid-details[open] .pp-raid-summary { margin-bottom: 0; }
/* Claude (v0.92.37): replaced the old `padding-left: 4px` rule below with the new
   horizontal+bottom padding above so the body content has breathing room against
   the new accent-bordered card. */
.pp-raid-body { padding: 4px 12px 10px; }
.pp-boss-history-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--text);
    margin: 10px 0 4px 4px;
}

.pp-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
    margin-bottom: 8px;
    table-layout: fixed;
}
.pp-table th {
    text-align: left;
    font-size: 10px;
    font-weight: 700;
    color: var(--text-muted);
    letter-spacing: 0.5px;
    padding: 6px 8px 8px;
    border-bottom: 1px solid var(--border);
    /* Claude (v0.92.37): subtle accent-tinted gradient on the table header row so
       it visually separates from the rows below. */
    background: linear-gradient(180deg, rgba(255,255,255,0.025) 0%, rgba(0,0,0,0.08) 100%);
    text-transform: uppercase;
    overflow: hidden;
    text-overflow: ellipsis;
}
.pp-table th:not(:first-child) { text-align: right; }
.pp-table td {
    padding: 5px 8px;
    border-top: 1px solid rgba(255,255,255,0.04);
    overflow: hidden;
    text-overflow: ellipsis;
}
.pp-table td:not(:first-child) { text-align: right; }
.pp-table.pp-table-compact td { padding: 3px 8px; }
.pp-table.pp-table-compact th { padding: 3px 8px 4px; }

/* Claude (v0.92.40): shared right-side columns aligned between Best Parses + Recent
   Raids per user. The 5 right-most columns (Spec / DPS-HPS / Rank / Parse% / Peak%)
   use IDENTICAL widths in both tables so they line up vertically regardless of
   which table is rendered above the other. Bumped DPS/HPS to 160px in both (was
   110 in Best Parses, 160 in Recent) so the "Overall DPS / HPS" header in Recent
   Raids has room for the PB badge without wrapping. Boss column in Best Parses
   loses 50px of fluid width; still has plenty since boss names are short.
   Anchored from right edge: 80+80+90+160+100 = 510px of fixed columns.
   Also added the missing .pp-recent-table nth-child(5) Rank width that v0.92.38
   forgot when adding the Rank column to the markup — that was making every column
   from Rank onwards drift in Recent Raids. */

/* Best Parses: Boss | Spec | DPS/HPS | Rank | Parse % | Peak % */
.pp-best-table th:nth-child(1) { width: auto; }     /* Boss — fills remaining */
.pp-best-table th:nth-child(2) { width: 100px; }    /* Spec */
.pp-best-table th:nth-child(3) { width: 160px; }    /* DPS / HPS — bumped 110 → 160 to align with Recent Raids */
.pp-best-table th:nth-child(4) { width: 90px; }     /* Rank */
.pp-best-table th:nth-child(5) { width: 80px; }     /* Parse % */
.pp-best-table th:nth-child(6) { width: 80px; }     /* Peak % */
/* Spec column left-aligned */
.pp-best-table th:nth-child(2),
.pp-best-table td:nth-child(2) { text-align: left; }

/* Recent Raids: Date | Raid | Spec | Overall DPS/HPS | Rank | Parse % | Peak % */
.pp-recent-table th:nth-child(1) { width: 70px; }   /* Date — Claude (v0.92.43): bumped 55 → 70 so "20 May" doesn't ellipsis-truncate */
.pp-recent-table th:nth-child(2) { width: auto; }   /* Raid — fills remaining */
.pp-recent-table th:nth-child(3) { width: 100px; }  /* Spec */
.pp-recent-table th:nth-child(4) { width: 160px; }  /* Overall DPS/HPS + PB badge */
.pp-recent-table th:nth-child(5) { width: 90px; }   /* Rank — Claude (v0.92.40): added to match Best Parses */
.pp-recent-table th:nth-child(6) { width: 80px; }   /* Parse % — shifted from nth-child(5) */
.pp-recent-table th:nth-child(7) { width: 80px; }   /* Peak % — shifted from nth-child(6) */
/* Raid + Spec columns left-aligned */
.pp-recent-table th:nth-child(2),
.pp-recent-table td:nth-child(2),
.pp-recent-table th:nth-child(3),
.pp-recent-table td:nth-child(3) { text-align: left; }

/* Claude (v0.87.11): mobile card-per-row layout for all .pp-table tables (best /
   recent / per-boss history). At ≤700px the JS-rendered <td> cells carry
   data-label attrs (player.js v0.87.11); we convert each <tr> into a vertical
   card with each <td> as a labelled key/value row inside it. Replaces the
   v0.87.6 overflow-x:auto fallback that worked but cropped trailing columns
   off-screen by default. Cards are inherently single-column so they fit any
   viewport without horizontal scroll. Empty data-label cells (e.g. the "Log"
   action button column) render without a label so they still look clean.
   thead is hidden because the labels are now per-cell pseudo-elements.
   pp-history-scroll's max-height + overflow-y still applies — cards just
   stack inside it. The overflow-x:auto wrappers from v0.87.6 stay as a
   safety net for any future <td> that lacks data-label. */
@media (max-width: 700px) {
    .pp-table thead { display: none; }
    .pp-table, .pp-table tbody, .pp-table tr, .pp-table td { display: block; width: 100%; }
    .pp-table tr {
        border: 1px solid var(--border);
        border-radius: 6px;
        background: var(--surface);
        margin-bottom: 8px;
        padding: 8px 10px;
        box-sizing: border-box;
    }
    .pp-table tr.pp-row:hover { background: var(--surface2); }
    .pp-table td {
        padding: 4px 0 !important;
        text-align: left !important;
        border: none !important;
        white-space: normal !important;
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 12px;
        min-height: 22px;
    }
    .pp-table td:before {
        content: attr(data-label);
        color: var(--text-muted);
        font-size: 11px;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.4px;
        flex-shrink: 0;
    }
    /* Empty-label cells (action buttons): hide the label spacer + right-align. */
    .pp-table td[data-label=""]:before { display: none; }
    .pp-table td[data-label=""] { justify-content: flex-end; }
    /* Boss / Raid / Spec cells: prominent left label, value flows right. The
       boss / raid is the card "title" — make it bigger + accent color. */
    .pp-table td.ppt-boss { font-weight: 700; font-size: 14px; color: var(--text); padding-bottom: 6px !important; border-bottom: 1px solid var(--border) !important; margin-bottom: 4px; }
    .pp-table td.ppt-boss:before { display: none; }
    /* DPS / HPS values get a slightly larger font to stand out. */
    .pp-table td.ppt-dps { font-size: 14px; font-weight: 700; }
    /* Override the column-width rules that don't make sense in card layout. */
    .pp-table th, .pp-table td { width: 100% !important; }
    /* pp-history-scroll wraps the compact history table; keep its scroll cap
       but the cards now stack vertically inside it. Drop the border since
       cards have their own. */
    .pp-history-scroll { border: none; max-height: none; overflow-x: visible; }
}

/* Parse History: Date | Spec | DPS | HPS | Parse % | Historic % | Duration | Log */
.pp-table-fixed th:nth-child(1) { width: 55px; }    /* Date */
.pp-table-fixed th:nth-child(2) { width: 100px; }   /* Spec */
.pp-table-fixed th:nth-child(3) { width: 70px; }    /* DPS */
.pp-table-fixed th:nth-child(4) { width: 70px; }    /* HPS */
.pp-table-fixed th:nth-child(5) { width: 70px; }    /* Parse % */
.pp-table-fixed th:nth-child(6) { width: 75px; }    /* Historic % */
.pp-table-fixed th:nth-child(7) { width: 70px; }    /* Duration */
.pp-table-fixed th:nth-child(8) { width: 45px; }    /* Log btn */
/* Spec column left-aligned */
.pp-table-fixed th:nth-child(2),
.pp-table-fixed td:nth-child(2) { text-align: left; }

/* Claude: scrollable container for parse history — shows ~5 rows then scrolls */
.pp-history-scroll {
    max-height: 185px;
    overflow-y: auto;
    /* Claude (v0.87.6): also allow horizontal scroll. The 8-column compact table
       (Date / Spec / DPS / HPS / Parse% / Historic% / Duration / action) is ~520px
       wide minimum; on a 360px phone it cropped the trailing columns invisibly.
       Pair with the @media(max-width:700px) .pp-table-wrap rule for fixed tables. */
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    margin-bottom: 8px;
    border: 1px solid var(--border);
    border-radius: 4px;
}
.pp-history-scroll .pp-table { margin-bottom: 0; }
.pp-history-scroll thead { position: sticky; top: 0; z-index: 1; background: var(--surface); }

.pp-row { cursor: pointer; transition: background 0.12s, transform 0.12s; }
/* Claude (v0.92.37): row hover bumped to a slightly stronger tint + tiny lift via
   translateX so the row you're inspecting is clearly highlighted. Matches the
   .meter-row treatment family. */
.pp-row:hover td { background: rgba(255,255,255,0.04); }
.pp-row:hover .ppt-boss,
.pp-row:hover td[data-label="Raid"] { color: var(--accent); }

.pp-log-btn {
    font-size: 11px;
    color: var(--text-muted);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 8px;
    text-decoration: none;
    font-weight: 600;
    white-space: nowrap;
    transition: all 0.1s;
}
.pp-log-btn:hover { color: var(--accent); border-color: var(--accent); }

/* Claude (v0.92.37): PB badge gets the elevated gradient + soft glow so the
   "🏆 PB" marker feels celebratory instead of flat. */
.pp-pb-badge {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 10px;
    background: linear-gradient(180deg, #3a2d08 0%, #221805 100%);
    border: 1px solid var(--accent);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 1px 4px rgba(212,175,55,0.25);
    border-radius: 4px;
    padding: 1px 7px;
    color: #ffd989;
    font-weight: 700;
    letter-spacing: 0.04em;
    margin-left: 6px;
}

@media (max-width: 700px) {
    .pp-name { font-size: 18px; }
    .pp-table { font-size: 11px; }
    /* Claude (v0.92.40): mobile-hide Spec + Peak columns. The rules below short-
       circuit on .pp-table mobile-card layout (cells become display:block) anyway,
       but the nth-child rules are still useful for any future fixed-layout pages.
       Per-table because Best Parses has Spec at nth-child(2) and Peak at nth-child(6)
       (6-col table); Recent Raids has Spec at nth-child(3) and Peak at nth-child(7)
       (7-col table after v0.92.38 added the Rank column). */
    .pp-best-table th:nth-child(2),
    .pp-best-table td:nth-child(2),
    .pp-best-table th:nth-child(6),
    .pp-best-table td:nth-child(6) { display: none; }
    .pp-recent-table th:nth-child(3),
    .pp-recent-table td:nth-child(3),
    .pp-recent-table th:nth-child(7),
    .pp-recent-table td:nth-child(7) { display: none; }
}

/* Claude: Talent calculator styles (talents.html) */
.talent-class-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 18px;
    max-width: 900px;
}
@media (min-width: 640px) { .talent-class-grid { grid-template-columns: repeat(5, 1fr); } }
@media (min-width: 900px) { .talent-class-grid { grid-template-columns: repeat(9, 1fr); } }
.talent-class-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 16px;
    border: 1px solid var(--border);
    background: var(--surface);
    border-radius: 6px;
    text-decoration: none;
    color: var(--text-muted);
    font-size: 12px;
    font-weight: 500;
    transition: all 0.15s;
}
.talent-class-card:hover {
    border-color: rgba(212, 175, 55, 0.4);
    background: rgba(212, 175, 55, 0.05);
    color: var(--accent);
}
.talent-control-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    max-width: 1400px;
    margin-bottom: 18px;
    padding: 10px 18px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    flex-wrap: wrap;
    gap: 12px;
}
.talent-link-btn {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 13px;
    padding: 0;
    font-family: inherit;
    transition: opacity 0.15s;
}
.talent-link-btn:hover { opacity: 0.75; }
.talent-trees-wrap {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    justify-content: center;
}
.talent-tree-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 1px solid rgba(212, 175, 55, 0.3);
    border-radius: 6px;
    background: var(--surface);
}
.talent-tree-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    background: var(--surface2, #161b22);
    border-bottom: 1px solid var(--border);
}
.talent-tree-grid {
    position: relative;
}
.talent-tree-overlay {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
}
.talent-arrow-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 1;
}
.talent-icon-el {
    user-select: none;
    -webkit-user-select: none;
    transition: transform 0.1s;
}
.talent-icon-el:hover {
    transform: scale(1.1);
}
.talent-img-dimmed {
    opacity: 0.4;
    filter: grayscale(100%);
}
.talent-glow-maxed     { box-shadow: inset 0 0 6px 2px rgba(245, 197, 24, 0.7); }
.talent-glow-has-points { box-shadow: inset 0 0 6px 2px rgba(30, 255, 30, 0.6); }
.talent-glow-can-add    { box-shadow: inset 0 0 5px 2px rgba(30, 255, 30, 0.4); }
.talent-rank-badge {
    position: absolute;
    bottom: -6px;
    right: -6px;
    min-width: 18px;
    font-size: 11px;
    font-weight: 700;
    padding: 1px 4px;
    border-radius: 4px;
    background: #111;
    text-align: center;
    z-index: 3;
}
.talent-tooltip {
    position: fixed;
    z-index: 9999;
    pointer-events: none;
    max-width: 280px;
    padding: 10px 12px;
    background: var(--surface, #1c2230);
    border: 1px solid rgba(212, 175, 55, 0.3);
    border-radius: 6px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
}

/* ── Timeline tab (v0.19.x) ───────────────────────────────────── */
/* Claude: timeline lives inside #meter-body which becomes the scroll container — both horizontal
   (for zoom-induced lane overflow) and vertical (for long player lists). The meter-header row
   is hidden because the timeline has its own axis ruler below the sticky controls. */
#meter-panel.tab-timeline .meter-header { display: none; }
/* Claude (v0.99.x): Loot tab renders its own header (rarity chips + column row) in the body. */
#meter-panel.tab-loot .meter-header { display: none; }
.loot-row:hover { background: rgba(255,255,255,0.04); }
#meter-panel.tab-timeline #meter-body   { padding: 0; overflow: auto; position: relative; }

/* Claude (v0.92.34): timeline toolbar + filter buttons now share the v0.92.26
   elevated treatment so the whole top control band reads as the same UI family
   as the meter tabs / filter row above. Same 8%/2% gradient on --surface2 +
   inset top-edge highlight + accent-tinted active state. */
.tl-view-btn,
.tl-zoom-btn {
    background: linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%), var(--surface2);
    border: 1px solid var(--border);
    color: var(--text-muted);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
    border-radius: 4px;
    padding: 4px 10px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.12s, box-shadow 0.12s;
}
.tl-view-btn:hover,
.tl-zoom-btn:hover {
    border-color: rgba(212,175,55,0.5);
    color: var(--text);
    transform: translateY(-1px);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 2px 6px rgba(0,0,0,0.3);
}
.tl-view-btn.active {
    background: linear-gradient(180deg, #3a2d08 0%, #221805 100%);
    border-color: var(--accent);
    color: var(--accent);
    box-shadow: inset 0 1px 0 rgba(212,175,55,0.15), 0 1px 4px rgba(212,175,55,0.18);
}
.tl-zoom-btn { padding: 4px 8px; min-width: 28px; text-align: center; }

/* Class-filter buttons — one per class present in the fight */
.tl-class-btn {
    background: linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%), var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
    border-radius: 4px;
    padding: 3px 8px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.12s, box-shadow 0.12s;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.tl-class-btn:hover  {
    border-color: rgba(212,175,55,0.5);
    transform: translateY(-1px);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 2px 6px rgba(0,0,0,0.3);
}
.tl-class-btn.active {
    background: linear-gradient(180deg, #3a2d08 0%, #221805 100%);
    border-color: var(--accent);
    color: var(--accent);
    box-shadow: inset 0 1px 0 rgba(212,175,55,0.15), 0 1px 4px rgba(212,175,55,0.18);
}
.tl-class-count {
    font-size: 10px;
    opacity: 0.7;
    font-weight: 500;
}
.tl-player-select {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
    border-radius: 4px;
    padding: 4px 8px;
    font-size: 11px;
    outline: none;
    max-width: 180px;
}
.tl-player-select:hover { border-color: var(--accent); }
/* Cursor hint: clicking the player left column toggles the single-player filter */
.tl-left:hover { background: var(--surface2); cursor: pointer; }

/* Time axis row — lane is the wider (scrollable) piece; left cell is sticky to stay visible. */
.tl-axis {
    display: flex;
    position: sticky;
    top: 40px; /* sits just under the view-toggle controls */
    background: var(--surface);
    z-index: 3;
    border-bottom: 1px solid var(--border);
    height: 24px;
    width: max-content;
    min-width: 100%;
}
.tl-axis-left {
    flex: 0 0 var(--tl-left-col-w, 130px);
    border-right: 1px solid var(--border);
    position: sticky;
    left: 0;
    background: var(--surface);
    z-index: 4; /* Claude: above axis ticks so they don't visually overlap during horizontal scroll */
}
.tl-axis-lane {
    flex: 0 0 var(--tl-lane-w, 100%);
    position: relative;
}
.tl-tick {
    position: absolute;
    top: 14px;
    width: 1px;
    height: 6px;
    background: var(--text-muted);
    opacity: 0.35;
}
.tl-tick.major {
    top: 10px;
    height: 10px;
    opacity: 0.7;
}
.tl-tick-label {
    position: absolute;
    top: -12px;
    left: 3px;
    font-size: 10px;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Per-player row — width matches axis so horizontal scroll stays in sync */
.tl-row {
    display: flex;
    border-bottom: 1px solid rgba(255,255,255,0.04);
    min-height: 32px;
    position: relative;
    width: max-content;
    min-width: 100%;
}
/* Claude (v0.92.34): bumped from 0.02 → 0.045 so hovering a row in a 25-player
   dense timeline gives clear visual feedback for which row you're tracking. */
.tl-row:hover { background: rgba(255,255,255,0.045); }

/* Boss row — always first, highlighted with a subtle red tint so it reads as distinct from
   player rows. The 👹 emoji + light-red name in the sticky left column do most of the work. */
.tl-row-boss {
    background: rgba(252, 129, 129, 0.05);
    border-bottom: 2px solid rgba(252, 129, 129, 0.3);
}
.tl-row-boss:hover { background: rgba(252, 129, 129, 0.08); }
.tl-row-boss .tl-left { background: var(--surface); }
.tl-left {
    flex: 0 0 var(--tl-left-col-w, 130px);
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 10px;
    border-right: 1px solid var(--border);
    background: var(--surface);
    position: sticky;
    left: 0;
    /* Claude: z-index 7 so cast icons (3, hover 6), channel bars (2), buff bars (2), grid (0),
       and death markers (4) are all visually covered by the sticky left column as the lane
       scrolls horizontally. Without this, icons at small lane-x bled through when scrolled. */
    z-index: 7;
}
.tl-left-text {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-width: 0;
}
.tl-name {
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tl-gcd-sum {
    font-size: 10px;
    font-weight: 600;
    color: var(--text-muted);
    opacity: 0.9;
    font-variant-numeric: tabular-nums;
    margin-top: 2px;
    /* Claude (v0.92.35): nowrap so "GCD: -34.4s" stays on a single line. Was
       wrapping to two lines on narrow viewports / when the Compare button ate
       into the column width. */
    white-space: nowrap;
}
/* Claude (v0.92.34): color-code the GCD loss summary by severity per user. JS
   adds one of these modifier classes based on gcdLossMs — was always red which
   read as "everyone played poorly" even for healthy GCD usage.
     gcd-good: <10s loss — green
     gcd-warn: 10-30s   — amber
     gcd-bad:  >=30s    — red */
.tl-gcd-sum.gcd-good { color: #68d391; }
.tl-gcd-sum.gcd-warn { color: #f6c149; }
.tl-gcd-sum.gcd-bad  { color: #fc8181; }
.tl-left-selected {
    background: rgba(246, 201, 14, 0.08);
    box-shadow: inset 3px 0 0 var(--accent);
}
.tl-left-selected .tl-name { color: var(--accent) !important; }

/* Claude (v0.92.34): Compare-list button per player row.
   Claude (v0.92.35): position:absolute (was flex with margin-left:auto) so the
   button doesn't reserve column width when hidden — was eating ~30-40px of the
   130px column even at opacity:0, squeezing names and forcing the GCD line to
   wrap to two lines. Now it overlays in the top-right corner of the left column
   on hover; column layout reverts to text-only when not hovered. */
.tl-compare-btn {
    position: absolute;
    top: 4px;
    right: 4px;
    z-index: 8;
    background: rgba(127,195,232,0.08);
    border: 1px solid rgba(127,195,232,0.35);
    color: #7fc3e8;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 700;
    padding: 1px 6px;
    line-height: 1.4;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s, background 0.12s, border-color 0.12s, transform 0.12s;
    font-family: inherit;
}
.tl-left:hover .tl-compare-btn,
.tl-compare-btn:focus-visible {
    opacity: 1;
    pointer-events: auto;
}
.tl-compare-btn:hover {
    background: rgba(127,195,232,0.18);
    border-color: #7fc3e8;
    transform: translateY(-1px);
}
/* Marked state stays visible always — pointer-events restored so clicking
   to remove from compare list works without hovering the row first. */
.tl-compare-btn.marked {
    background: rgba(127,195,232,0.22);
    border-color: #7fc3e8;
    color: #7fc3e8;
    opacity: 1;
    pointer-events: auto;
}
.tl-lane {
    flex: 0 0 var(--tl-lane-w, 100%);
    position: relative;
    overflow: hidden;
}

/* Gridlines — mirror the axis ticks to guide the eye vertically.
   Claude (v0.92.34): minor gridlines bumped from 0.04 → 0.05 alpha, major
   gridlines from 0.07 → 0.10 so the every-minute markers are easier to track
   across a 25-row vertical span. */
.tl-grid {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 1px;
    background: rgba(255,255,255,0.05);
    pointer-events: none;
}
.tl-grid.major { background: rgba(255,255,255,0.10); }

/* Cast icons — image + timestamp label stacked vertically, both centered on their ts.
   Icon size + label size scale with --tl-row-scale so focused views (2-3 players) read bigger. */
.tl-cast {
    position: absolute;
    top: 4px;
    transform: translateX(-50%); /* centre container (any width) on the timestamp */
    z-index: 3;
    cursor: help;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.tl-cast img {
    width: var(--tl-icon-size, 18px);
    height: var(--tl-icon-size, 18px);
    border-radius: 3px;
    border: 1px solid rgba(0,0,0,0.5);
    display: block;
    /* Claude (v0.92.34): smooth the existing v0.19.x hover scale+outline
       (was a jarring 80ms snap). */
    transition: transform 0.12s ease, outline 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}
.tl-cast-ts {
    margin-top: 2px;
    /* Claude: cap font scaling at 1.25× so labels stay readable at scale=2 (icon 36px) — user
       reported 18px fonts felt "way too big" when filtering to one player. 9px → max 11.25px. */
    font-size: calc(9px * min(1.25, var(--tl-row-scale, 1)));
    line-height: 1;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    opacity: 0.8;
    pointer-events: none;
}
.tl-cast:hover { z-index: 6; }
/* Claude (v0.92.34): polished cast-icon hover — slightly bigger scale + soft
   accent glow so the icon visually "lifts" instead of just outline-flashing. */
.tl-cast:hover img {
    outline: 1.5px solid var(--accent);
    transform: scale(1.4);
    box-shadow: 0 0 10px rgba(212,175,55,0.55);
    border-color: rgba(0,0,0,0.7);
}
.tl-cast:hover .tl-cast-ts { color: var(--text); opacity: 1; }

/* GCD-loss marker — "-0.3s" between two cast icons, centered vertically on the icon row so
   it reads inline with the spell icons rather than on its own line below the timestamps. */
.tl-gcd-loss {
    position: absolute;
    top: calc(4px + var(--tl-icon-size, 18px) / 2);
    transform: translate(-50%, -50%);
    font-size: calc(9px * min(1.25, var(--tl-row-scale, 1)));
    line-height: 1;
    font-weight: 700;
    color: #fc8181;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    opacity: 0.85;
    pointer-events: none;
    z-index: 2;
}

/* Channel bars — drawn behind cast icons, coloured per player. Stay centered on the icon as
   the icon scales with --tl-icon-size. top = iconSize/2 places the 8px bar on the icon's vertical midline. */
.tl-channel {
    position: absolute;
    top: calc(var(--tl-icon-size, 18px) / 2);
    height: 8px;
    opacity: 0.45;
    border-radius: 2px;
    z-index: 2;
    cursor: help;
}
.tl-channel:hover { opacity: 0.8; }

/* Buff bars — 10px-tall horizontal strips stacked below the cast row. Background color comes
   from the spell school (Envenom green, Slice and Dice amber, fire/frost/etc.) via inline style.
   Small 10×10 spell icon sits at the bar's left edge for quick visual identification. */
.tl-buff {
    position: absolute;
    height: 10px;
    border-radius: 2px;
    z-index: 2;
    cursor: help;
    opacity: 0.88;
    overflow: hidden; /* clip the icon when bar is narrower than 10px */
}
.tl-buff img {
    position: absolute;
    left: 0;
    top: 0;
    width: 10px;
    height: 10px;
    border-radius: 2px 0 0 2px;
    border: none;
    display: block;
    pointer-events: none;
}
.tl-buff.debuff { background: #2dd4bf !important; } /* debuffs: teal (overrides inline bg) */
.tl-buff:hover  { opacity: 1; z-index: 5; box-shadow: 0 0 4px rgba(255,255,255,0.3); }

/* Death markers — red vertical line across the full row height */
.tl-death {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    margin-left: -1px;
    background: #e53e3e;
    box-shadow: 0 0 4px rgba(229, 62, 62, 0.6);
    z-index: 4;
    pointer-events: auto;
    cursor: help;
}

/* Rows wrapper — containing block for the sync marker. Spans lane width like the rows
   inside it, so the marker can be positioned at any lane-x and be bounded vertically
   only by the player rows (not by the sticky controls/axis above). */
.tl-rows {
    position: relative;
    width: max-content;
    min-width: 100%;
}

/* Sync marker — draggable vertical line spanning all player rows. The element is 14px wide
   (invisible flanking zones) so it's easy to grab, while the visible line is a thin 2px
   stripe drawn via ::before centered inside. Label is sticky-top within the scroll container
   so it stays visible while vertically scrolling. */
.tl-marker {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 14px;
    margin-left: -7px;   /* centre the click target on the timestamp */
    background: transparent;
    z-index: 6;
    cursor: ew-resize;
    pointer-events: auto;
}
.tl-marker::before {
    content: '';
    position: absolute;
    left: 6px;           /* centre the 2px line within the 14px click zone */
    top: 0;
    bottom: 0;
    width: 2px;
    background: #60a5fa;
    opacity: 0.65;
    transition: opacity 0.12s;
    pointer-events: none;
}
.tl-marker:hover::before { opacity: 1; box-shadow: 0 0 6px rgba(96, 165, 250, 0.5); }
/* Hotkeys popover — positioned via fixed + inline top/left (relative to viewport) just below
   the Hotkeys button. Lives on document.body so it isn't clipped by the scroll container. */
.tl-hotkeys-panel {
    position: fixed;
    z-index: 1000;
    background: var(--surface);
    border: 1px solid var(--accent);
    border-radius: 6px;
    padding: 14px 18px;
    min-width: 280px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.7);
    font-size: 12px;
    color: var(--text);
}
.tl-hotkeys-panel h4 {
    margin: 0 0 10px 0;
    font-size: 11px;
    font-weight: 700;
    color: var(--accent);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.tl-hotkeys-panel kbd {
    display: inline-block;
    min-width: 18px;
    padding: 1px 6px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-bottom-width: 2px;
    border-radius: 3px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10.5px;
    color: var(--text);
    text-align: center;
    line-height: 1.4;
}
.tl-hotkeys-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 18px;
    padding: 4px 0;
}
.tl-hotkeys-desc { color: var(--text-muted); }

.tl-marker-label {
    position: sticky;
    top: 8px;
    left: 10px;
    display: inline-block;
    background: rgba(17, 24, 39, 0.95);
    color: #60a5fa;
    padding: 3px 8px;
    border-radius: 3px;
    font-size: 11px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    pointer-events: none;
    border: 1px solid #60a5fa;
    margin-left: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}

/* Claude: mobile tweaks for Timeline tab. Phones are ≤700px wide — the default 130px left
   column eats over a third of the viewport, and the controls row wraps into a wall of text.
   Shrink left column, compact controls, and hide purely-decorative elements. Matching JS
   reads _tlLeftColW() → 90 on mobile → the --tl-left-col-w CSS var = 90px. */
@media (max-width: 700px) {
    #meter-panel.tab-timeline .meter-header { font-size: 10px; }
    .tl-view-btn, .tl-zoom-btn, .tl-class-btn {
        padding: 4px 7px;
        font-size: 10px;
        letter-spacing: 0.03em;
    }
    .tl-class-count { display: none; } /* save horizontal space */
    .tl-player-search { width: 90px; font-size: 10px; padding: 2px 6px; }
    .tl-name { font-size: 11px; }
    .tl-gcd-sum { font-size: 9px; }
    .tl-axis-left, .tl-left { padding: 0 6px; }
    /* Expand tap target on touch devices — the marker's 14px click zone is borderline on
       small phones. 24px tap area with the visible 2px line still centered inside. */
    .tl-marker {
        width: 24px;
        margin-left: -12px;
    }
    .tl-marker::before { left: 11px; }
}

/* Claude (v0.31.1): combined sticky strip — site stats on the left, timeframe
   pills on the right. Pinned at top:56px to clear the page header (header height
   = 56px). Opaque var(--bg) prevents raid blocks from bleeding through during
   scroll; subtle box-shadow gives a slight lift so the user notices it stuck. */
.tf-sticky {
    position: sticky;
    top: 56px;
    z-index: 50;
    background: var(--bg);
    padding: 10px 0;
    margin: -10px 0 12px;
    box-shadow: 0 4px 8px -8px rgba(0,0,0,0.6);
}
.tf-sticky-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 8px 14px;
}
.tf-controls {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
.tf-label {
    font-size: 12px;
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
/* Claude (v0.75.2 mobile): hide the DEV BUILD badge on phones — the position:fixed
   badge at top:60px sits on top of the wrapped (≥100 px tall) mobile header instead
   of below it like on desktop. Dev developers know they're on dev.epoglogs.com
   without the visual reminder. env-banner.js tags the badge with this class. */
@media (max-width: 700px) {
    .dev-build-badge { display: none !important; }
}

/* Claude (v0.75.4 mobile): /conrank tables — defense in depth. The HTML
   inline-style fix from v0.75.3 (overflow:auto on widget bodies) only works
   for users who got fresh conrank.html. Aggressively cached browsers may
   still serve the old overflow-y:auto. Force overflow-x:auto here so the
   fix lands even on stale HTML. ALSO drop table-layout:fixed + explicit
   column widths on mobile so the tables auto-size to viewport — we lose
   the Leaderboard/All-Stars Guild-column alignment but gain readability
   (and no per-widget horizontal scroll bar). The Guild column also gets
   hidden at ≤480px to avoid crushing the Name column. */
@media (max-width: 700px) {
    #conrank-leaderboard,
    #all-stars-body {
        overflow-x: auto !important;
    }
    #conrank-leaderboard table.admin-table,
    #all-stars-body     table.admin-table {
        table-layout: auto !important;
        min-width: 0 !important;
    }
    #conrank-leaderboard table.admin-table th,
    #all-stars-body     table.admin-table th,
    #conrank-leaderboard table.admin-table td,
    #all-stars-body     table.admin-table td {
        width: auto !important;
        padding: 6px 8px !important;
        font-size: 12px;
    }
}
@media (max-width: 480px) {
    /* Hide Guild column on small phones — Name + Points/Avg Parse is enough. */
    #conrank-leaderboard table.admin-table th:nth-child(3),
    #conrank-leaderboard table.admin-table td:nth-child(3),
    #all-stars-body     table.admin-table th:nth-child(3),
    #all-stars-body     table.admin-table td:nth-child(3) {
        display: none;
    }
}

@media (max-width: 700px) {
    .tf-sticky { top: 50px; } /* slightly tighter on small screens */
    /* Claude (v0.75.2 mobile): stack the stats banner above the timeframe pills
       so they don't end up side-by-side. With the default flex row, the stats
       banner has flex:1 and wraps its 4 stats vertically on the LEFT while the
       3 timeframe pills sit on the RIGHT — visually broken (TIMEFRAME inline
       with "25 guilds"). flex-direction:column drops them onto separate rows. */
    .tf-sticky-row { flex-direction: column; align-items: stretch; gap: 10px; }
    .tf-controls   { justify-content: center; }
    .site-stats-banner { justify-content: center; }
}

/* Claude (v0.87.15): hero motivation banner. Below the sticky stats/timeframe
   bar, above the per-raid widgets. CSS-only — gradient base + inline SVG
   pattern overlay (rendered separately in markup as .hero-banner-bg) + bold
   gold typography. No external image dependency. */
.hero-banner {
    position: relative;
    margin: 4px 0 20px;
    padding: 28px 32px;
    border: 1px solid rgba(212,175,55,0.45);
    border-radius: 10px;
    background:
        radial-gradient(ellipse at 80% 100%, rgba(212,175,55,0.10), transparent 60%),
        linear-gradient(135deg, #1a1410 0%, #0e0a06 100%);
    overflow: hidden;
    box-shadow: 0 4px 18px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(212,175,55,0.05);
}
.hero-banner-bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    /* Claude (v0.87.18): SEAL the SVG content to its own bounds. preserveAspectRatio="slice"
       lets the viewBox extend content beyond the SVG element's visible area (it's the
       opposite of "meet" — content fills the container and overflows are cropped at the
       SVG layer). Some browsers (Chrome at certain container aspect ratios) leak that
       overflow downward into siblings as visually orphaned shape fragments — the user
       reported a row of empty rectangles inside the Onyxia raid block at narrower-than-
       full-monitor widths. The parent .hero-banner has overflow:hidden but the SVG was
       happily painting past its element bounds, escaping that clip. overflow:hidden on
       the SVG element itself (via CSS — the attribute form is unreliable across browsers)
       forces the SVG to clip to its own bounds before the parent's clip even kicks in.
       Also display:block to drop the inline-element baseline gap that adds a few px under
       the SVG (a long-standing browser quirk that's worth eliminating preemptively). */
    overflow: hidden;
    display: block;
}
.hero-banner-inner {
    position: relative; /* over the SVG bg */
    display: flex;
    align-items: center;
}
.hero-banner-text { flex: 1; min-width: 0; }
.hero-banner-title {
    /* Claude (v0.87.17): icon dropped per user — title carries all the visual weight
       now. Bumped 24 → 34px, weight 800 → 900, brighter gold gradient (added a top
       highlight stop), added a soft outer text-shadow glow on top of the engraved
       inner shadow. Tightened letter-spacing on a larger size for a more decisive
       feel. */
    font-size: 34px;
    font-weight: 900;
    color: var(--accent);
    margin: 0 0 10px;
    letter-spacing: -0.4px;
    line-height: 1.05;
    text-shadow:
        0 1px 8px rgba(0,0,0,0.7),
        0 0 24px rgba(212,175,55,0.18);
    background: linear-gradient(180deg, #ffe289 0%, #f4d56f 35%, #c69528 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}
.hero-banner-subtitle {
    font-size: 15px;
    color: var(--text);
    margin: 0;
    opacity: 0.78;
    line-height: 1.55;
    letter-spacing: 0.15px;
}

@media (max-width: 700px) {
    .hero-banner { padding: 22px 18px; margin: 4px 0 14px; }
    .hero-banner-title { font-size: 22px; margin-bottom: 6px; letter-spacing: -0.2px; }
    .hero-banner-subtitle { font-size: 13px; }
}
@media (max-width: 480px) {
    .hero-banner { padding: 18px 14px; }
    .hero-banner-title { font-size: 19px; }
    .hero-banner-subtitle { font-size: 12px; }
}

/* Claude (v0.31.0): site-wide stats banner — totals strip (logs/guilds/players/raids).
   Now lives inside .tf-sticky-row as the left-side content, so its own background +
   border are dropped (parent provides the chrome). */
/* Claude (v0.31.2): bumped base font 12 → 13px and the strong numbers 13 → 15.5px
   so the totals read as the meaningful content of the strip. Row height is still
   governed by the tf-btn buttons (12px + padding) so the widget chrome doesn't grow. */
.site-stats-banner {
    font-size: 13px;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    flex: 1;
    min-width: 0;
}
.site-stats-banner .ssb-stat {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    white-space: nowrap;
}
.site-stats-banner .ssb-stat strong {
    color: var(--text);
    font-weight: 700;
    font-size: 15.5px;
}
.site-stats-banner .ssb-sep {
    color: var(--text-muted);
    opacity: 0.45;
}

/* ── Summary tab raid overview (v0.92.4) ───────────────────────────────────── */
/* Claude (v0.92.4): visual overhaul of the log-viewer Summary tab. New strip above
   the activity chart with raid-level stats + a grid of boss-encounter cards. Click
   a card to jump to that fight; click "Overall" to reset. Inspired by competitor
   pages — this is the introduction to the raid, so the layout should give immediate
   orientation (how many bosses, how many kills, how many wipes) and let the user
   drill into any encounter in one click. */
.summary-raid-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
    padding: 16px 20px;
    background: var(--glass-overlay), var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: var(--shadow-elev1);
    margin-bottom: 12px;
}
.summary-raid-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    text-align: center;
}
.summary-raid-stat-value {
    font-size: 28px;
    font-weight: 800;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    line-height: 1.05;
    letter-spacing: -0.01em;
}
.summary-raid-stat-value.is-kills  { color: #68d391; }
.summary-raid-stat-value.is-wipes  { color: #e88a8a; }
.summary-raid-stat-value.is-accent { color: var(--accent); }
.summary-raid-stat-label {
    font-size: 10px;
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

/* Boss-shortcut card grid. auto-fit + minmax(180px, 1fr) lays out as many cards as
   the row can hold; cards stretch to fill the row. Each card is a button-shaped
   surface with the boss icon at the top, name + duration below, kill/wipe badge
   at the bottom. Active fight (matches activeFight.index) gets a gold border so
   the user knows what they're currently looking at. */
.summary-boss-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 10px;
}
.summary-boss-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 14px 12px 12px;
    background: var(--glass-overlay), var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    cursor: pointer;
    transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
    user-select: none;
    text-align: center;
    overflow: hidden;
}
.summary-boss-card:hover {
    transform: translateY(-2px);
    border-color: var(--border-strong);
    box-shadow: var(--shadow-elev2);
}
.summary-boss-card.is-active {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent), var(--shadow-elev1);
}
.summary-boss-card-icon {
    width: 44px;
    height: 44px;
    border-radius: 8px;
    border: 1px solid var(--border);
    object-fit: cover;
    flex-shrink: 0;
}
.summary-boss-card-icon-emoji {
    width: 44px;
    height: 44px;
    border-radius: 8px;
    background: var(--surface2);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
}
.summary-boss-card-name {
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    max-width: 100%;
}
.summary-boss-card-meta {
    font-size: 11px;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.summary-boss-card-badge {
    display: inline-block;
    margin-top: 2px;
    font-size: 9.5px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.10em;
    padding: 2px 8px;
    border-radius: 4px;
}
.summary-boss-card-badge.is-kill  { background: rgba(104, 211, 145, 0.15); color: #68d391; border: 1px solid rgba(104, 211, 145, 0.30); }
.summary-boss-card-badge.is-wipe  { background: rgba(232, 138, 138, 0.15); color: #e88a8a; border: 1px solid rgba(232, 138, 138, 0.30); }
.summary-boss-card-badge.is-overall { background: rgba(212, 175, 55, 0.12); color: var(--accent); border: 1px solid rgba(212, 175, 55, 0.30); }
.summary-boss-card-badge.is-trash { background: rgba(255, 255, 255, 0.06); color: var(--text-muted); border: 1px solid var(--border); }
/* Section label above the boss grid (e.g. "Encounters" / "Trash"). */
.summary-section-label {
    font-size: 11px;
    font-weight: 800;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    margin: 14px 4px 8px;
}
.summary-section-label:first-child { margin-top: 0; }

/* ── Modernization pass (v0.88.1) ──────────────────────────────────────────── */
/* Claude (v0.88.1): homepage modernization. Five small, additive changes:
   (1) new --border-soft / --shadow-elev* / --surface3 vars (no existing var values changed);
   (2) softened borders + subtle elevation on the sticky bar, raid-block-header, raid-block-body
       inner wrapper, and parse-widget cards;
   (3) promoted stat strip — vertical stat chips (big number on top, small uppercase label below);
   (4) class-coloured 3px left edge on every meter row (paints over the bar via a z-index:2
       ::before pseudo, using --pwgt-class-color set inline in overview.js);
   (5) parse-widget hover lift (translateY -1px + shadow upgrade).
   Existing :root var VALUES are untouched so pages I'm not testing don't shift. */
:root {
    /* Claude (v0.88.2): luminous borders, not dark dividers. On the near-black canvas
       (#0e1117) my v0.88.1 rgba(.06) border painted DARKER than the panel surface it
       was bordering — actively hiding the boundary instead of refining it. Bumped to
       rgba(.12) (visible silver line on top of #1c2230 ≈ #3a4150) so the chrome is
       perceptible without going back to the hard slate-blue --border. */
    --border-soft:   rgba(255, 255, 255, 0.12);
    --border-strong: rgba(255, 255, 255, 0.18);
    /* Claude (v0.88.2): elevation via highlights + meaningful shadow. The v0.88.1
       rgba(.03) inset top highlight was invisible; bumped to rgba(.10) so the panel
       catches the eye like a real "lit from above" surface. Shadow alpha pushed up
       too (35% → 50%) — on near-black canvas the shadow needs to be DARK to register. */
    --shadow-elev1: 0 1px 3px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.10);
    --shadow-elev2: 0 6px 18px rgba(0, 0, 0, 0.65), inset 0 1px 0 rgba(255, 255, 255, 0.14);
    /* Hover-state surface tier — perceptibly brighter than --surface2 (#1c2230 → #2a3349)
       so the user feels the affordance when mousing over a collapsible section. */
    --surface3: #2a3349;
    /* Claude (v0.88.2): translucent overlays used to paint a "glass card" gradient on top of
       any existing background. White on top + black on bottom gives lit-from-above depth
       without changing the underlying var(--surface2) hue. */
    --glass-overlay:        linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(0, 0, 0, 0.08) 100%);
    --glass-overlay-strong: linear-gradient(180deg, rgba(255, 255, 255, 0.07) 0%, rgba(0, 0, 0, 0.12) 100%);
}

/* Sticky filter bar (stats + timeframe pills) — visible silver border + real elevation. */
.tf-sticky-row {
    /* Claude (v0.88.2): gradient overlay on top of existing var(--surface2) so the bar
       reads as a glass card. The gradient sits in the background-image slot; the base
       colour stays as the inline-set var(--surface2). */
    background: var(--glass-overlay), var(--surface2) !important;
    border-color: var(--border-soft) !important;
    box-shadow: var(--shadow-elev1);
}

/* Raid block header (Onyxia's Lair, Training Dummy, ...) — gradient overlay + visible
   border + real shadow. The header's chrome is inline-styled in overview.js so we use
   !important to override both background AND border-color. */
.raid-block-header {
    /* Claude (v0.88.2): glass gradient layered over the existing surface2 base. The
       top-to-bottom highlight is what your eye actually picks up as "depth" on dark
       canvas — far more visible than the previous dark-shadow approach. */
    background: var(--glass-overlay), var(--surface2) !important;
    border-color: var(--border-soft) !important;
    box-shadow: var(--shadow-elev1);
    transition: background-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.raid-block-header:hover {
    /* Brighter glass + lifted shadow + 1px translate. The hover should feel like the
       header tilts toward you, not just changes colour. */
    background: var(--glass-overlay-strong), var(--surface3) !important;
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.50), inset 0 1px 0 rgba(255, 255, 255, 0.14);
    transform: translateY(-1px);
}

/* Inner card inside .raid-block-body (the wrapper around speedruns + parsers + guild prog)
   is also inline-styled (border + bg). Match the same glass treatment. */
.raid-block-body > div {
    /* Claude (v0.88.2): the inline bg is rgba(255,255,255,0.01) — basically transparent.
       Layer the glass overlay on a very dark base so the inner card reads as a separate
       surface from the header above it, not a continuation of the page canvas. */
    background: var(--glass-overlay), rgba(255, 255, 255, 0.015) !important;
    border-color: var(--border-soft) !important;
    box-shadow: var(--shadow-elev1);
}

/* Top DPS / Top HPS widget cards — gradient overlay + visible border + hover lift.
   Claude (v0.92.27): bumped to match the v0.92.26 elevated tab-button treatment
   per user — same brighter 8%/2% linear-gradient on --surface2 base + inset
   top-edge highlight. The Summary tab widgets (Top DPS / Top HPS / Top Damage
   Taken / Top Deaths) now read as the same "elevated" card surface as the
   inactive tab buttons + the boss-shortcut encounter cards. */
.parse-widget {
    background: linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%), var(--surface2);
    border: 1px solid var(--border);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), var(--shadow-elev1);
    transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.parse-widget:hover {
    transform: translateY(-2px);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), var(--shadow-elev2);
    border-color: rgba(212,175,55,0.4);
}
/* Claude (v0.92.27): the inner widget title bar gets a subtle accent-tinted
   surface so it sits visually as a "header" inside the brighter card. The
   v0.88.x rule above used var(--surface2) flat — now slightly tinted on top. */
.parse-widget .parse-widget-title {
    background: linear-gradient(180deg, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0) 100%), var(--surface2);
}

/* Class-coloured 3px left edge on every meter row. The row already has
   position:relative + overflow:hidden, so a ::before pseudo absolutely positioned
   at left:0 paints inside. z-index:2 keeps it above the bar (z-index:0) AND above
   the text columns (z-index:1) — but the leftmost 3px is empty padding anyway,
   so nothing important is masked. The class color is set inline via the
   --pwgt-class-color custom prop (see overview.js renderWidget). */
.pwgt-row::before {
    content: '';
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    background: var(--pwgt-class-color, transparent);
    z-index: 2;
    pointer-events: none;
}

/* Stat strip promotion — switch from inline ticker ("📋 192 logs · ...") to a row of
   vertical stat chips (big tabular number above, small uppercase label below). Sits in
   the same sticky row as the timeframe pills; row height grows ~12px which keeps the
   sticky bar reasonable while making the totals feel like the meaningful content. */
.site-stats-banner {
    gap: 20px;
}
.site-stats-banner .ssb-stat {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: 2px 2px;
}
.site-stats-banner .ssb-stat .ssb-num {
    font-size: 20px;
    font-weight: 800;
    color: var(--text);
    /* tabular-nums keeps column widths consistent if the value updates as
       timeframe changes (so the chip doesn't jump). */
    font-variant-numeric: tabular-nums;
    line-height: 1.05;
    letter-spacing: -0.01em;
}
.site-stats-banner .ssb-stat .ssb-lbl {
    font-size: 9.5px;
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.10em;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.site-stats-banner .ssb-stat .ssb-icon {
    font-size: 11px;
    opacity: 0.85;
}
/* Dot separators no longer needed — chips have built-in gap. */
.site-stats-banner .ssb-sep { display: none; }
/* Tighten the chips on phones so the strip doesn't dominate the viewport. */
@media (max-width: 700px) {
    .site-stats-banner { gap: 14px; }
    .site-stats-banner .ssb-stat .ssb-num { font-size: 17px; }
    .site-stats-banner .ssb-stat .ssb-lbl { font-size: 9px; letter-spacing: 0.08em; }
}

/* ── Site-wide modernization (v0.88.3) ─────────────────────────────────────── */
/* Claude (v0.88.3): extend the v0.88.1/.2 chrome treatment beyond the homepage to every
   page (rankings, log viewer, player profile, guild, admin, compare, simulators hub, info
   pages, guide pages, etc.). Four additive things, all in CSS — no per-page JS edits.
   The biggest lever is the global --border swap above in :root; this block adds the
   complementary elevation, glass overlay, table refinement, and section header bump. */

/* (a) Elevation + glass overlay on common panel containers across the site.
   Class list assembled from a JS-rendered class-name audit: covers admin panel widgets,
   modals, compare cards, rankings sidebar groups, simulators hub cards, guide cards.
   Uses !important on background because some pages (admin.html, compare.html) define
   these classes in a per-page <style> block loaded AFTER styles.css that would otherwise
   win the cascade. box-shadow doesn't need !important — those pages don't set one. */
.admin-widget,
.modal-box,
.cmp-card,
.cmp-player-card,
.cmp-section,
.rankings-sidebar-raid,
.sim-card,
.guide-card,
.legal {
    background: var(--glass-overlay), var(--surface) !important;
    box-shadow: var(--shadow-elev1);
    /* Claude (v0.90.4): rounded corners + a soft border + clip overflow so the
       luminous edge actually reads on pages that DON'T load per-page inline
       styles for these classes. Previously /conrank and /guides showed square
       widgets because admin.html's <style> block defines .admin-widget locally
       with border-radius: 10px + overflow: hidden, but those declarations live
       in admin.html itself — every other page that uses .admin-widget (conrank
       in particular) got only the glass overlay + shadow with no clipping.
       Now the canonical rounded chrome lives globally here. */
    border-radius: 10px;
    border: 1px solid var(--border);
    overflow: hidden;
}

/* (b) Tabular numerals on numeric data cells. Claude (v0.88.4): originally included
   [class*="-num"]/[class*="-dps"]/[class*="-hps"] substring attribute selectors, which
   forced the browser to substring-match every element's class against three patterns on
   every style recalc. On log.html with thousands of meter rows + nested children that
   produced a visible page-load slowdown across the site. Dropped the substring matchers
   entirely; kept only explicit named selectors. Coverage is slightly narrower (a few
   one-off numeric cells will fall back to proportional digits) but the perf is restored
   to v0.88.2 levels. The named list still covers every high-traffic data column. */
.meter-parse, .meter-name,
.rankings-table td, .admin-table td,
.pp-table td, .ppt-dps,
.pwgt-dps, .pwgt-rank,
.cmp-cell-num, .cmp-col-num, .cmp-summary-num,
.sr-dps-tip {
    font-variant-numeric: tabular-nums;
}

/* Claude (v0.88.4): class-coloured 3px left edge on rankings table rows — matches the
   homepage .pwgt-row::before treatment. The class colour is set inline on each <tr> as
   --rk-class-color (see rankings.js). Paints via inset box-shadow on the FIRST td (not
   a ::before pseudo on the row) because position:relative + ::before on <tr> is
   unreliable across browsers — table-row layout doesn't honour positioning consistently. */
.rankings-table tbody td:first-child {
    box-shadow: inset 3px 0 0 var(--rk-class-color, transparent);
}

/* Claude (v0.88.4): page header + footer get the same luminous-edge + subtle elevation
   treatment so the chrome around every page is consistent with the panels inside it.
   Header already uses border-bottom: 1px solid var(--border) which now luminates via
   the v0.88.3 --border swap; the new box-shadow adds a downward elevation cue so the
   sticky header reads as floating above the content rather than glued onto it. */
header {
    box-shadow: var(--shadow-elev1);
}
/* Footer is a plain <footer> element across all 35 HTMLs (no .site-footer class) —
   use the element selector so it lands without per-page edits. The shadow is inverted
   (negative offset) so it casts UPWARD onto the content above instead of downward
   into nothing. */
footer {
    box-shadow: 0 -1px 0 var(--border-soft), 0 -2px 6px rgba(0, 0, 0, 0.25);
}

/* (c) Data table refinement — drop vertical inter-column dividers (if any) and add a
   subtle hover-row tint on the major data tables. The hover tint matches the .meter-row
   pattern already in use on log.html so the whole site uses one hover language. */
.admin-table th, .admin-table td,
.pp-table th,    .pp-table td,
.rankings-table th, .rankings-table td {
    border-right: none;
}
.admin-table tbody tr:hover {
    background: rgba(255, 255, 255, 0.025);
}

/* (d) Section header typography bump. Common section header classes site-wide get a
   modest size + tracking bump so they anchor sections instead of fading into the
   surrounding content. Keeps existing colour/weight — pure rhythm change. */
.parsers-section-header {
    font-size: 15px;
    letter-spacing: 0.04em;
    padding-bottom: 8px;
    margin-bottom: 12px;
}
.pp-section-title,
.cmp-section-title {
    font-size: 14px;
    letter-spacing: 0.10em;
    padding-bottom: 8px;
    margin-bottom: 12px;
}
/* Generic <h2> inside <main> on info pages (about, terms, privacy, guides, contribute)
   — slightly larger + tighter tracking so chapter heads anchor the page. */
main h2 {
    font-size: 22px;
    letter-spacing: -0.005em;
    line-height: 1.25;
}

/* ── Polish bundle (v0.89.1) ───────────────────────────────────────────────── */
/* Claude (v0.89.1): five quick-win polish items on top of v0.89.0 baseline:
   (1) hero banner refinement — soft radial glow behind the title + reduced chart
       opacity so the gold headline dominates instead of competing with the bg art;
   (2) empty-cell glyph treatment — .empty-cell utility class for muted em-dashes
       in tables (Verified column, missing DPS, etc.) — replaces the cheap raw "—";
   (3) tab bar inactive state polish — softer background, kept the per-tab loud
       active colours intact (those are intentional design);
   (4) custom dark scrollbars site-wide — thin, theme-tinted, no Webkit chrome;
   (5) keyboard focus rings site-wide via :focus-visible — gold outline only on
       keyboard nav (mouse clicks skip the ring per spec). */

/* (1) Hero banner — soft radial glow behind the title so it reads as the focal
   point. The existing bottom-right glow stays; this adds a top-left highlight
   centred where the text lives. background-image stacks left-to-right (top of
   stack paints first), so the new highlight goes ON TOP of the existing layers
   to brighten the text region. */
.hero-banner {
    background:
        radial-gradient(ellipse at 18% 35%, rgba(212,175,55,0.18), transparent 55%),
        radial-gradient(ellipse at 80% 100%, rgba(212,175,55,0.10), transparent 60%),
        linear-gradient(135deg, #1a1410 0%, #0e0a06 100%) !important;
}
/* Slightly desaturate the SVG chart so the title carries the visual weight. The
   chart is still readable as decoration but stops competing for attention. */
.hero-banner-bg { opacity: 0.78; }

/* (2) Empty-cell glyph — apply via class="empty-cell" on the rendered span.
   Letter-spacing widens the en-dash so it reads as "empty state" rather than
   a stray punctuation mark. Centered, muted, smaller. */
.empty-cell {
    color: rgba(255, 255, 255, 0.22);
    font-weight: 400;
    font-size: 11px;
    letter-spacing: 0.25em;
    display: inline-block;
    min-width: 18px;
    text-align: center;
}

/* Claude (v0.92.26): the v0.89.x "soften the inactive state" pass below was
   silently overriding every v0.92.20-onward attempt to brighten the tab buttons
   because `:not(.active)` ranks above plain `.tab-btn` in CSS specificity. So
   every v0.92.20 → v0.92.25 tab-btn background change landed on the wrong side
   of the cascade and the user kept seeing the same transparent-on-page dark
   look. Now: the inactive override matches the elevated treatment the v0.92.25
   .tab-btn rule was supposed to produce all along — a clear lighter surface,
   full opacity, normal border. The per-tab .active rules above still supply
   their own intense per-tab colours. */
.tab-btn:not(.active) {
    background: linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%), var(--surface2);
    border-color: var(--border);
    opacity: 1;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
}
.tab-btn:not(.active):hover {
    background: linear-gradient(180deg, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0.03) 100%), var(--surface2);
    border-color: rgba(212,175,55,0.5);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 2px 6px rgba(0,0,0,0.3);
}

/* (4) Custom scrollbars — thin dark theme-tinted, no garish browser default.
   Firefox uses scrollbar-color + scrollbar-width; Chromium/Webkit uses the
   ::-webkit-scrollbar pseudo. Both covered.
   Universal selector is fine here — scrollbar styles only apply to scrollable
   elements and don't trigger expensive substring matching. */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
}
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
    background: var(--border-strong);
    border-radius: 4px;
}
*::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.28);
}
*::-webkit-scrollbar-corner { background: transparent; }

/* (5) Keyboard focus rings — :focus-visible only fires on keyboard navigation
   per spec (not on mouse click). Provides accessibility without the "ring
   appears after every click" annoyance. Gold accent matches the brand. */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
    transition: outline-offset 0.1s ease;
}
/* Suppress the default outline on mouse focus (e.g. clicked buttons) to keep
   the visual clean for sighted/mouse users. :focus-visible covers keyboard. */
:focus:not(:focus-visible) {
    outline: none;
}

/* ── Accessibility utilities (Claude — ui-polish audit) ───────────────────────
   (a) prefers-reduced-motion: the site has 9+ @keyframes (modal-in, skeleton
       shimmer, page-fade, rulesAttentionPulse, …) + dozens of hover transforms.
       Honour the OS "reduce motion" setting — collapse animations/transitions to
       a near-instant snap to the END state (so layouts/visibility still settle).
   (b) .sr-only: visually-hidden but screen-reader-readable text (for icon-only
       controls / labels). .sr-only-focusable reveals on keyboard focus.
   (c) .skip-link: "skip to content" link injected by js/utils.js as the first
       body child; off-screen until focused. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

.sr-only {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.sr-only-focusable:focus,
.sr-only-focusable:focus-within {
    position: static !important;
    width: auto; height: auto;
    margin: 0; overflow: visible;
    clip: auto; white-space: normal;
}

.skip-link {
    position: fixed;
    top: 8px;
    left: 8px;
    z-index: 2000;
    transform: translateY(-150%);
    background: var(--accent);
    color: #0e1117;
    font-weight: 700;
    font-size: 13px;
    padding: 8px 14px;
    border-radius: 6px;
    text-decoration: none;
    box-shadow: 0 2px 10px rgba(0,0,0,0.5);
    transition: transform 0.15s ease;
}
.skip-link:focus {
    transform: translateY(0);
    outline: 2px solid #0e1117;
    outline-offset: 2px;
}

/* ── Polish bundle 2 (v0.89.2) ─────────────────────────────────────────────── */
/* Claude (v0.89.2): four mid-effort polish items.
   (1) footer cleanup — hide the literal "·" separator spans and push the "About
       this site" button to the right; tighten the link spacing;
   (2) backdrop blur on sticky chrome — header, .tf-sticky-row, .rankings-table th
       all become semi-transparent so the page content scrolling underneath shows
       through as a soft blur (the classic glass nav-bar effect);
   (3) loading skeleton shimmer — .skeleton-row class with animated gradient
       sweep replaces spinner labels site-wide;
   (4) modal entry polish — overlay backdrop blur bumped 2px→8px, modal-box
       animation becomes scale+fade (was translateY only). */

/* (1) Footer cleanup. The current markup is duplicated across 30+ HTMLs as
   <span>©…</span><span>·</span><span>Made by…</span><span>·</span><a>…</a>... .
   Killing the literal "·" separators via :nth-child + pushing the trailing
   button right is CSS-only — no per-HTML edits needed. The 2nd + 4th children
   are the two separator spans (positions known + stable). */
footer > span:nth-child(2),
footer > span:nth-child(4) {
    display: none;
}
/* Push the "About this site" button to the right edge; preceding links cluster
   to the left with the copyright/credit spans. The footer's flex-wrap means on
   narrow viewports this still wraps to a new line — auto-margin is ignored when
   wrap kicks in (flexbox spec). */
footer > button:last-child {
    margin-left: auto;
}
/* A touch more breathing room around the link group + brighter on hover so the
   nav reads as a proper site-nav cluster instead of a comma-list. */
footer > a {
    padding: 2px 4px;
    border-radius: 3px;
    transition: color 0.12s, background-color 0.12s;
}
footer > a:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.04);
}

/* (2) Backdrop blur on sticky chrome. For backdrop-filter to actually paint
   visible blur we need the element's own background to be partially
   transparent — an opaque bg hides whatever the filter blurred behind it.
   Hardcoded rgba values are the existing --surface (#161b22) and --surface2
   (#1c2230) at 78% alpha. The -webkit- prefix is still needed for Safari
   even though Chrome/Firefox use the unprefixed form. */
header {
    background: rgba(22, 27, 34, 0.78);
    backdrop-filter: blur(12px) saturate(140%);
    -webkit-backdrop-filter: blur(12px) saturate(140%);
}
.tf-sticky-row {
    background: var(--glass-overlay), rgba(28, 34, 48, 0.78) !important;
    backdrop-filter: blur(10px) saturate(140%);
    -webkit-backdrop-filter: blur(10px) saturate(140%);
}
.rankings-table th {
    background: rgba(28, 34, 48, 0.82);
    backdrop-filter: blur(10px) saturate(140%);
    -webkit-backdrop-filter: blur(10px) saturate(140%);
}

/* (3) Loading skeleton — gradient sweep that suggests "data on the way" instead
   of the current bare "Loading…" text. Used in _ovLoadingHtml() in overview.js
   (homepage raid sections); other call sites can adopt it incrementally. The
   shimmer animation uses background-position translation, which is GPU-cheap
   and doesn't trigger layout. */
.skeleton-row {
    height: 28px;
    margin: 6px 0;
    border-radius: 4px;
    background: linear-gradient(
        90deg,
        rgba(255, 255, 255, 0.03) 0%,
        rgba(255, 255, 255, 0.10) 50%,
        rgba(255, 255, 255, 0.03) 100%
    );
    background-size: 200% 100%;
    animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
.skeleton-row.skeleton-short { width: 60%; }
.skeleton-row.skeleton-tall  { height: 40px; }
@keyframes skeleton-shimmer {
    0%   { background-position: 100% 0; }
    100% { background-position: -100% 0; }
}
/* Claude (v0.89.3): wrapper drops max-width so the skeleton fills the same width
   as the surrounding panels (Uploaded Logs below, hero banner above) — previously
   capped at 800px which left a visible "narrow column" disconnect between the
   skeleton and its neighbours. */
.skeleton-wrap {
    padding: 4px 0;
}

/* Claude (v0.89.3): per-raid skeleton block — mimics the actual .raid-block layout
   (40px header bar with icon + title + dropdown arrow, then a body card with rows).
   Renders inside .skeleton-wrap; each block costs ~120px and we stack a couple so
   the loading state visually reserves the same vertical space the real content will. */
.skeleton-block {
    margin-bottom: 18px;
}
.skeleton-block-header {
    height: 44px;
    border-radius: 8px;
    margin-bottom: 6px;
    background: linear-gradient(
        90deg,
        rgba(255, 255, 255, 0.04) 0%,
        rgba(255, 255, 255, 0.10) 50%,
        rgba(255, 255, 255, 0.04) 100%
    );
    background-size: 200% 100%;
    animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
.skeleton-block-body {
    border: 1px solid var(--border-soft);
    border-radius: 8px;
    padding: 14px 16px;
    background: rgba(255, 255, 255, 0.015);
}

/* Claude (v0.89.4): sticky header scroll cue. The header has a glass-blur baseline
   from v0.89.2; this adds a stronger drop shadow when the user has scrolled past
   the top so the header reads as "lifted" off the content. The .scrolled class
   is toggled by a tiny IIFE inside partials/header.html (runs on every page). */
header.scrolled {
    box-shadow:
        0 1px 0 var(--border-soft),
        0 4px 18px rgba(0, 0, 0, 0.55),
        inset 0 1px 0 rgba(255, 255, 255, 0.10);
    transition: box-shadow 0.18s ease;
}

/* Claude (v0.89.4): tab content fade transition on the log viewer. When the user
   clicks a tab, app.js toggles a transient .tab-fade-in class on #meter-panel,
   which fires this animation once. The void-offsetWidth dance in app.js is what
   re-triggers it on each click (CSS animations only fire on initial mount or
   when the animation property changes). */
@keyframes meter-tab-fade {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: none; }
}
#meter-panel.tab-fade-in,
#detail-panel.tab-fade-in {
    animation: meter-tab-fade 140ms ease-out;
}

/* Claude (v0.89.5): section dividers between adjacent raid blocks on the homepage.
   60% width centred + tapered ends so the divider reads as a chapter break rather
   than a hard <hr>. The mid-stop bump from --border-soft (rgba .12) up to .20 makes
   the centre clearly visible while the tapered ends fade to nothing — gives the
   page a "chaptered" rhythm without screaming "I'M A SEPARATOR". */
.raid-block + .raid-block::before {
    content: '';
    display: block;
    height: 1px;
    width: 60%;
    margin: 0 auto 18px;
    background: linear-gradient(
        90deg,
        transparent 0%,
        var(--border-soft) 25%,
        rgba(255, 255, 255, 0.20) 50%,
        var(--border-soft) 75%,
        transparent 100%
    );
}

/* Claude (v0.89.5): activity chart (log viewer Summary tab) picks up the same
   glass overlay + elevation treatment as the homepage parse widgets and modals
   so the log viewer doesn't feel inconsistent. !important on background because
   the chart's bg is inline-styled in log.html as var(--surface). */
#activity-chart {
    background: var(--glass-overlay), var(--surface) !important;
    box-shadow: var(--shadow-elev1);
}

/* Claude (v0.89.6): faint raid silhouette watermark. The header content has
   position:relative + z-index:1 (set inline in overview.js) so it sits above the
   watermark. The watermark is positioned at the right edge of the header, scaled
   up to 80px, and faded to 6% opacity — visible flavour cue without obstructing
   text. filter: blur(0.5px) softens the small icon's pixel grid when scaled. */
.raid-watermark {
    position: absolute;
    right: -8px;
    top: 50%;
    transform: translateY(-50%);
    width: 80px;
    height: 80px;
    opacity: 0.06;
    pointer-events: none;
    filter: blur(0.5px);
    z-index: 0;
    border-radius: 4px;
}
/* On the raid-block hover state (already styled in v0.88.x) the watermark
   brightens slightly so the section feels alive when the user mouses over. */
.raid-block-header:hover .raid-watermark {
    opacity: 0.10;
    transition: opacity 0.18s ease;
}

/* Claude (v0.89.6): page transition fade — soft 200ms fade-in on body load. Makes
   page navigation feel SPA-like without actually being one. Pure CSS, no JS,
   triggers once on every initial page mount (per HTML's "first" frame). The
   `animation-fill-mode: backwards` means the from-state (opacity:0) is applied
   even before the animation kicks in, preventing a flash of opaque-then-fade. */
body {
    animation: page-fade-in 200ms ease-out backwards;
}
@keyframes page-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
/* Modal overlays + tooltips shouldn't inherit the body fade timing — they have
   their own entry animations. The fade-in is a one-shot at page load; modals
   open later and use modal-in-scale (v0.89.2). */

/* (4) Modal entry polish. The existing animation was a small translateY drop;
   the new one combines scale-up with fade so the modal feels like it "lands"
   instead of slides in. Backdrop blur bumped 2px → 8px so the page behind
   actually reads as out-of-focus, not just darkened. */
.modal-overlay {
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
.modal-box {
    animation: modal-in-scale 0.22s cubic-bezier(0.2, 0.8, 0.3, 1.05);
}
@keyframes modal-in-scale {
    from {
        transform: scale(0.94) translateY(-8px);
        opacity: 0;
    }
    to {
        transform: scale(1) translateY(0);
        opacity: 1;
    }
}

/* Claude (v0.66.0): homepage two-column layout — main content left, polls sidebar right.
   Stacks vertically on viewports <1100px so mobile gets polls below the main content. */
.home-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 16px;
    align-items: start;
}
.home-grid-main {
    min-width: 0;
}
.home-grid-sidebar {
    position: sticky;
    top: 60px;
    max-height: calc(100vh - 80px);
    overflow-y: auto;
}
@media (max-width: 1100px) {
    .home-grid {
        grid-template-columns: 1fr;
    }
    .home-grid-sidebar {
        position: static;
        max-height: none;
    }
}

.polls-widget {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    margin-bottom: 16px;
}
.polls-widget-header {
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--surface2);
    font-size: 13px;
    font-weight: 700;
    color: var(--accent);
}
.polls-widget-body {
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 720px;
    overflow-y: auto;
}
/* Claude (v0.66.2): footer note at the bottom of each polls widget. */
.polls-widget-footer {
    padding: 8px 14px;
    border-top: 1px solid var(--border);
    background: var(--surface2);
    color: var(--text-muted);
    font-size: 11px;
    font-style: italic;
    text-align: center;
}
.poll-card {
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 12px;
    font-size: 12px;
}
.poll-header {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 8px;
}
.poll-question {
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    line-height: 1.3;
}
.poll-meta {
    font-size: 10px;
    color: var(--text-muted);
}
.poll-multi-hint {
    display: inline-block;
    background: rgba(212, 175, 55, 0.15);
    color: var(--accent);
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.poll-options {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.poll-option {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 5px;
    cursor: pointer;
    overflow: hidden;
    transition: background 0.12s, border-color 0.12s;
}
.poll-option:hover {
    border-color: var(--accent);
}
.poll-option-mine {
    border-color: var(--accent);
    background: rgba(212, 175, 55, 0.08);
}
.poll-option input[type="radio"],
.poll-option input[type="checkbox"] {
    margin: 0;
    accent-color: var(--accent);
    flex-shrink: 0;
    z-index: 1;
}
.poll-bar {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    background: rgba(212, 175, 55, 0.18);
    z-index: 0;
    transition: width 0.4s cubic-bezier(.4,0,.2,1);
}
.poll-option-mine .poll-bar {
    background: rgba(212, 175, 55, 0.32);
}
.poll-label-text {
    flex: 1;
    z-index: 1;
    color: var(--text);
}
.poll-pct {
    z-index: 1;
    font-size: 11px;
    font-weight: 700;
    color: var(--accent);
    font-family: monospace;
}
.poll-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--border);
}
.poll-total {
    font-size: 10px;
    color: var(--text-muted);
}
.poll-submit-btn {
    background: var(--accent);
    color: #0e1117;
    border: none;
    padding: 4px 12px;
    border-radius: 4px;
    font-size: 11px;
    font-weight: 700;
    cursor: pointer;
    transition: opacity 0.12s, filter 0.12s;
}
.poll-submit-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.poll-submit-btn:not(:disabled):hover {
    filter: brightness(1.1);
}

/* News popup */
.news-popup-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.7);
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    animation: news-popup-fade 0.18s ease-out;
}
@keyframes news-popup-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
.news-popup-box {
    background: var(--surface);
    border: 1px solid var(--accent);
    border-radius: 12px;
    width: 100%;
    max-width: 520px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
    overflow: hidden;
}
.news-popup-header {
    padding: 14px 18px;
    border-bottom: 1px solid var(--border);
    background: var(--surface2);
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.news-popup-title-wrap {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
}
.news-popup-title {
    font-size: 15px;
    font-weight: 700;
    color: var(--accent);
}
.news-popup-date {
    font-size: 11px;
    color: var(--text-muted);
}
.news-popup-close {
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 18px;
    cursor: pointer;
    line-height: 1;
}
.news-popup-close:hover {
    color: var(--accent);
}
.news-popup-body {
    padding: 18px;
    color: var(--text);
    font-size: 13px;
    line-height: 1.6;
    max-height: 50vh;
    overflow-y: auto;
    /* Claude: News bodies are stored as HTML but admins often type plain-text
     * announcements with literal \n line breaks (Discord webhook renders those
     * fine, the website was collapsing them). pre-line preserves \n as line
     * breaks while still collapsing repeated spaces — HTML tags (links,
     * <strong>, lists) keep rendering normally. */
    white-space: pre-line;
}
.news-popup-body p { margin: 0 0 10px; }
.news-popup-body p:last-child { margin-bottom: 0; }
.news-popup-body a { color: var(--accent); }
/* Claude (news-images): screenshots attached via the admin 📷 upload — scale to the popup
 * width (the source can be up to 1600px wide), block layout so the pre-line \n padding
 * around the <img> tag gives clean breaks. Same rules apply inside the admin preview
 * overlay since it reuses .news-popup-* markup. */
.news-popup-body img {
    display: block;
    max-width: 100%;
    height: auto;
    margin: 10px auto;
    border: 1px solid var(--border);
    border-radius: 6px;
}
.news-popup-footer {
    padding: 12px 18px;
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: flex-end;
}
.news-popup-ok {
    background: var(--accent);
    color: #0e1117;
    border: none;
    padding: 7px 24px;
    border-radius: 5px;
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
}
.news-popup-ok:hover {
    filter: brightness(1.1);
}

/* Claude (v0.67.1): "📋 Parse rules" button next to the boss title on the rankings page. */
.rk-rules-btn {
    margin-left: 12px;
    background: rgba(212, 175, 55, 0.08);
    border: 1px solid rgba(212, 175, 55, 0.4);
    color: var(--accent);
    font-size: 11px;
    font-weight: 600;
    padding: 3px 10px;
    border-radius: 5px;
    cursor: pointer;
    vertical-align: middle;
    transition: background 0.12s, border-color 0.12s;
}
.rk-rules-btn:hover {
    background: rgba(212, 175, 55, 0.18);
    border-color: var(--accent);
}

/* Claude (v0.67.4): Discord brand icon for footer link. CSS mask so the icon
   picks up currentColor (matches the link's text color and hover state). */
.discord-icon {
    display: inline-block;
    width: 13px;
    height: 13px;
    vertical-align: -2px;
    margin-right: 5px;
    background-color: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z'/%3E%3C/svg%3E") center / contain no-repeat;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* Claude (v0.86.20): subtle attention pulse on Rules buttons site-wide.
   Per user — wants a small bump/flash on the Rules button when entering
   any page that has one, so users notice they should read the rules before
   trying to rank. Runs 3 short pulses (~2.4 sec total) then settles —
   non-distracting + naturally fades. Applied via the .rules-attention-btn
   class which is added to every Rules button in:
     • dummy-stats.html  #dummy-rules-btn  (single)
     • js/dungeon-speedruns.js  .dsr-rules-btn  (one per dungeon card)
     • js/rankings.js  .rk-rules-btn  (one per boss view)
     • js/overview.js  inline rules button  (one per raid section)
   Per-session de-dup uses sessionStorage so the pulse only fires the FIRST
   time the user lands on a page with a Rules button in a given session;
   subsequent navigations within the same session don't re-pulse (avoids
   the "look at me" effect for returning users). Logic in js/utils.js. */
@keyframes rulesAttentionPulse {
    0%, 100% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(212, 175, 55, 0);
    }
    50% {
        transform: scale(1.08);
        box-shadow: 0 0 0 6px rgba(212, 175, 55, 0.28);
    }
}
.rules-attention-btn.rules-attention-pulse {
    animation: rulesAttentionPulse 0.8s ease-in-out 3;
}

/* Claude (v0.96.7): hunter melee-weave efficiency card (damage detail panel) */
.weave-card {
    margin: 4px 0 12px;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface2);
}
.weave-head {
    display: flex;
    align-items: center;
    gap: 8px;
}
.weave-head img { width: 20px; height: 20px; border-radius: 3px; }
.weave-title {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--text);
}
/* Claude (v0.96.x): headline widgets — Efficiency + Weave Quality + Melee Uptime */
.weave-headline {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 10px 0 10px;
}
.weave-metric {
    min-width: 110px;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 10px 8px;
    border-radius: 6px;
    background: var(--surface);
    border: 1px solid var(--border);
    cursor: help; /* Claude (v0.96.x): signals the detailed hover tooltip */
}
/* Efficiency is the glance metric — give it a touch more emphasis */
.weave-metric-primary {
    border-color: rgba(104, 211, 145, 0.35);
    background: rgba(104, 211, 145, 0.06);
}
.weave-metric-primary .weave-metric-val { font-size: 26px; }
.weave-metric-val {
    font-size: 22px;
    font-weight: 800;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}
.weave-metric-sub {
    display: block;
    font-size: 9px;
    font-weight: 500;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    text-transform: none;
    margin-top: 1px;
}
/* Claude (v0.96.x): "ⓘ" info badge — signals a detailed hover tooltip is available on the
   parent metric. The parent element carries the title; hovering the badge shows it too
   (the browser walks up to the nearest titled ancestor). */
.weave-info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 12px;
    height: 12px;
    margin-left: 4px;
    border: 1px solid var(--text-muted);
    border-radius: 50%;
    color: var(--text-muted);
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 9px;
    font-style: italic;
    font-weight: 700;
    line-height: 1;
    text-transform: none;
    letter-spacing: 0;
    cursor: help;
    vertical-align: middle;
    transition: color .12s, border-color .12s;
}
.weave-metric:hover .weave-info,
.weave-bar-label:hover .weave-info {
    color: var(--text);
    border-color: var(--text);
}
/* Auto Shot Uptime label row sitting above the segmented bar */
.weave-bar-label {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 12px;
    font-weight: 700;
    color: var(--text);
    cursor: help; /* Claude (v0.96.x): detailed tooltip on the uptime row */
    font-variant-numeric: tabular-nums;
}
.weave-bar-sub {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-muted);
}
.weave-metric-label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    margin-top: 2px;
}
.weave-bar {
    display: flex;
    height: 14px;
    margin: 4px 0 6px;
    border-radius: 4px;
    overflow: hidden;
    background: #4a5568;
}
.weave-seg { height: 100%; }
.weave-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    font-size: 11px;
    color: var(--text-muted);
    margin-bottom: 10px;
}
.weave-legend i {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 2px;
    margin-right: 4px;
    vertical-align: -1px;
}
.weave-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}
.weave-stat {
    display: flex;
    flex-direction: column;
    padding: 6px 8px;
    border-radius: 5px;
    background: var(--surface);
    border: 1px solid var(--border);
}
.weave-stat-val {
    font-size: 14px;
    font-weight: 700;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.weave-stat-label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}
@media (max-width: 600px) {
    .weave-stats { grid-template-columns: repeat(2, 1fr); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE OVERHAUL — consolidated phone-tier system (Claude v0.100.7)
   ---------------------------------------------------------------------------
   Every prior mobile pass patched one page inside a scattered @media block
   above, with no shared contract — so real phones (~390px, iPhone 12/13/14)
   kept falling into the gaps. This is the single authoritative phone layer. It
   loads LAST so it wins same-specificity ties and can correct the rules above
   without hunting each one down. Two tiers, reused consistently:
       ≤700px  "compact"  — tablets + large/landscape phones (existing primary)
       ≤480px  "phone"    — portrait phones
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── (A) PAGE-LEVEL HORIZONTAL-OVERFLOW BACKSTOP ─────────────────────────────
   THE #1 complaint: no page should ever scroll sideways. overflow-x:clip clips
   the blow-out WITHOUT establishing a scroll container (unlike overflow:hidden),
   so the sticky <header> keeps sticking to the viewport. Containers get
   min-width:0 so a wide child (table/grid) can shrink-to-fit or scroll inside
   its OWN wrapper rather than stretching the page. Wide data tables below are
   given contained horizontal scroll so their columns stay reachable (the clip
   is only a safety net for anything we haven't wrapped). */
@media (max-width: 700px) {
    /* Page-level backstop. overflow-x:hidden on the ROOT (html) is universally
       supported (unlike overflow:clip, which needs iOS 16+) and — being the
       scroll root — does NOT break position:sticky. We deliberately do NOT put
       it on body/main, since those are sticky ancestors (site header, log-viewer
       meter header) and a scroll container there would break their stickiness. */
    html { overflow-x: hidden; }
    body { max-width: 100%; }
    main, footer, header,
    .home-grid, .parsers-section, .parse-widget,
    .raid-block, section, article { min-width: 0; }
    main { max-width: 100%; }
    img { max-width: 100%; height: auto; }

    /* Admin-style tables (conrank / dummy-stats / speedkills / dungeon) FIT the
       viewport by letting their cells wrap. Do NOT use display:block here — a
       <table> with display:block IGNORES max-width and side-scrolls the WHOLE
       PAGE on iOS Safari (the recurring "goes outside the window" bug). As a
       normal table, the inline width:100% + table-layout:auto (section G) keeps
       it within the viewport and wraps long cells instead of overflowing. */
    .pp-table-wrap, .admin-table-wrap,
    [data-instance] > div, .m-tscroll {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        max-width: 100%;
    }
}

/* ── (B) HEADER — deterministic 2-row phone layout ───────────────────────────
   The wrap-based header shuffled logo / realm-switcher / search / auth /
   hamburger into 3 ragged rows (and the ≤480 width:100px truncated search to
   "Sear"). Pin it to:
       Row 1:  [logo ▾] ……………………… [auth] [☰]
       Row 2:  [ search — full width        ]
   NOTE: js/utils.js wraps the <h1> inside .realm-switcher-wrap, so the
   header-inner direct child is the wrap on pages where the switcher injected,
   and the bare <h1> elsewhere — target both. */
@media (max-width: 700px) {
    .header-inner {
        flex-wrap: wrap;
        align-items: center;
        gap: 8px;
        row-gap: 8px;
        padding: 8px 12px;
    }
    .header-inner > h1,
    .header-inner > .realm-switcher-wrap { order: 0; flex: 0 0 auto; margin-right: 0; }
    .header-inner > #auth-widget         { order: 1; flex: 0 1 auto; margin-left: auto; min-width: 0; }
    .header-mobile-menu-btn              { order: 2; flex: 0 0 auto; margin-left: 4px; }
    /* Search always drops to its own full-width row. */
    .header-inner > .player-search-wrap  { order: 10; flex: 1 0 100%; margin-left: 0; min-width: 0; }
    .header-search-input { width: 100% !important; }
}
@media (max-width: 480px) {
    /* Override the legacy ≤480 width:100px that squeezed the search to "Sear". */
    .header-search-input { width: 100% !important; font-size: 16px; }
    header h1 { font-size: 16px; }
}

/* ── (C) MODALS — fit the viewport, never overflow ───────────────────────────
   align-items:center cut tall modals off at BOTH ends (no way to scroll to
   them); the boxes had no phone width cap so inner tables spilled past the
   screen (the player modal in particular). Top-align + scroll the overlay, and
   cap every box to the viewport. The upload modal keeps its full-screen
   treatment (#upload-modal-overlay rule above out-specifies these). */
@media (max-width: 700px) {
    .modal-overlay {
        align-items: flex-start;
        justify-content: center;
        overflow-y: auto;
        padding: 12px 8px;
    }
    .modal-box,
    #player-modal .modal-box,
    .widget-modal-box {
        width: calc(100vw - 16px) !important;
        max-width: calc(100vw - 16px) !important;
        max-height: calc(100vh - 24px);
        max-height: calc(100dvh - 24px);
        overflow-y: auto;
        overflow-x: hidden;
    }
    /* Inner tables fit the box; tighten the parse table so 5 cols fit 390px. */
    .modal-box table, #player-modal table { max-width: 100%; }
    .player-parse-table { font-size: 11px; }
    .player-parse-table th, .player-parse-table td { padding-left: 0; padding-right: 0; }
}

/* ── (D) RANKINGS — filters wrap, table scrolls (no page overflow) ───────────
   The filter cluster (DPS/HPS/Tank + class select + timeframe + phase) ran off
   the right edge, and the leaderboard's stale nth-child column-hiding no longer
   matched the live column set (iLvl was inserted), leaving the page to scroll.
   Let the filters wrap and the table scroll inside itself (handled in A). */
@media (max-width: 700px) {
    .rankings-filters { flex-wrap: wrap; gap: 6px; }
    .rankings-filters > * { min-width: 0; max-width: 100%; }
    .rk-class-select, .meta-select { max-width: 46vw; }
}

/* ── (E) HOMEPAGE SPEEDRUN WIDGET — stop the 11-col squish/overlap ───────────
   The widget table is table-layout:fixed + width:100%, so 11 fixed columns
   (~780px of colgroup) got crushed into ~360px → text overlapped. Switch to
   auto layout with a real min-width inside the existing overflow-x:auto wrapper
   so the columns render at their natural width and the WIDGET (not the page)
   scrolls horizontally. */
@media (max-width: 700px) {
    [data-sr-key] table {
        table-layout: auto !important;
        min-width: 720px;
        font-size: 12px;
    }
}

/* ── (F) LOG VIEWER — fight row, tab strip, summary tiles ────────────────────
   Phone screenshots showed: the fight-scope buttons + Characters wrapping
   awkwardly; the sub-tab labels spilling OUTSIDE their buttons; and the big
   overview numbers dominating the screen. Root cause of the tab spill: the
   baseline mobile rule pins .filter-btn/.tab-btn to min-width:44px, and in the
   nowrap .tabs strip the default flex-shrink crushed each tab to 44px so its
   text overflowed the border instead of the strip scrolling. */
@media (max-width: 700px) {
    /* Drop the 44px MIN-WIDTH (it oversized the fight buttons and squished the
       tabs); keep a comfortable tap HEIGHT and size to content. */
    .filter-btn, .tab-btn, .tab-btn-group {
        min-width: 0 !important;
        min-height: 38px;
        padding: 6px 10px;
    }

    /* Fight-scope row: the buttons (All Fights / ★ Bosses / Trash / 👥 Characters)
       share one row; the fight <select> drops to its own full-width row below. */
    #fight-row { gap: 6px; row-gap: 6px; align-items: center; }
    #fight-row .filter-group { display: flex; flex-wrap: wrap; gap: 6px; order: 0; }
    #cc-open-btn   { order: 1; margin-left: auto !important; }
    #fight-select  { order: 10; flex: 1 0 100%; min-width: 0; max-width: 100%; }
    #fight-duration{ order: 11; }

    /* Sub-tabs: ONE horizontal scroll strip — buttons keep their natural width
       (flex:0 0 auto) so the strip scrolls on swipe rather than crushing labels
       past the border. Overrides the earlier ≤700 flex-wrap:wrap. */
    .tabs {
        flex-wrap: nowrap !important;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        gap: 5px;
        scrollbar-width: thin;
    }
    .tabs > .tab-btn,
    .tabs > .tab-group { flex: 0 0 auto; }
    .tab-btn { white-space: nowrap; font-size: 12px; }
}
@media (max-width: 480px) {
    /* Overview numbers (Encounters / Kills / Wipes / Raid Duration) were 28px in
       a 150px-min grid — too big on a phone. Tidy 2×2 at a calmer size. */
    .summary-raid-stats {
        grid-template-columns: repeat(2, 1fr);
        gap: 8px;
        padding: 12px 14px;
        margin-bottom: 10px;
    }
    .summary-raid-stat-value { font-size: 19px; }
    .summary-raid-stat-label { font-size: 9px; letter-spacing: 0.08em; }
    /* Fight-stats chip strip: trim the per-chip min so 2 fit per row cleanly. */
    .fs-chip { min-width: 84px; padding: 5px 10px; }
}

/* ── (G) REMAINING PAGES — table scroll + form-control safety ────────────────
   The .admin-table leaderboards (conrank, dummy-stats, raid-speedkills, dungeon-
   speedruns) have 6-8 columns whose content (guild / player / spec names) has a
   min-content width WIDER than a phone, so they can't shrink to fit — earlier
   attempts (display:block, table-layout:auto, fit-and-wrap) either side-scrolled
   the page or got CLIPPED at the card edge (.admin-widget is overflow:hidden) with
   columns unreachable. Real fix (v0.100.20): each table is now wrapped in a
   .admin-table-wrap scroll container by its JS builder (dummy-stats already had
   .admin-widget-body). The wrapper is max-width:100% so the PAGE never moves; the
   table takes its content width (min-width:max-content) and SCROLLS horizontally
   inside the card — every column reachable by swipe. Form controls: max-width so
   fixed-width inputs (guilds search, roster filter) can't overflow. */
@media (max-width: 700px) {
    /* Claude (v0.100.25): TABLE-ONLY horizontal scroll, natural column widths.
       The wide table no longer cramps (v0.100.24 overflow-wrap broke even "DPS"
       and "802"). The trick that finally contains it: contain:inline-size on the
       scroll wrapper makes the wrapper's width INDEPENDENT of its content, so the
       table's intrinsic width can't leak up and expand the page — it just scrolls
       inside the wrapper. The table takes its content width (width:max-content)
       and cells stay single-line (white-space:nowrap). */
    .admin-table-wrap, .admin-widget-body {
        display: block;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        max-width: 100%;
        contain: inline-size;
    }
    .admin-table-wrap > .admin-table,
    .admin-widget-body > .admin-table {
        table-layout: auto !important;
        min-width: 100%;
    }
    .admin-table { table-layout: auto !important; }
    /* Claude (v0.100.26): don't FORCE every cell onto one line (v0.100.25's
       width:max-content + white-space:nowrap did, which made the dungeon Players
       cell — a multi-player list meant to wrap, note line-height:1.9 — render as
       one over-wide line that overlapped Date/View). Cells wrap naturally at
       spaces / between players now; overflow-wrap:normal keeps single words (names)
       whole so a column still widens + the table scrolls (contain:inline-size)
       rather than breaking mid-word. Cells with their OWN inline white-space:nowrap
       (Guild ellipsis, Time, Date) keep it — inline wins. */
    .admin-table th, .admin-table td { overflow-wrap: normal; word-break: normal; }
    /* Card header ("N clears · required bosses · Rules") wraps instead of cramming.
       Base rule lives only in admin.html; the dungeon/dummy inline header has no wrap. */
    .admin-widget-header { flex-wrap: wrap; row-gap: 6px; }
    input, select, textarea { max-width: 100%; box-sizing: border-box; }
}

/* ── (H) ROUND-2 PHONE FIXES (second screenshot pass) ───────────────────────── */

/* Rankings parse table — FIT (no horizontal scroll), keep the sticky <thead>.
   The table now has 9 cols (#, icon, Player, Guild, iLvl, value, Parse%, Date,
   Log); the nth-child hides in the legacy blocks above were written for the old
   7-col layout and now hit the WRONG columns (they were hiding value/Parse%/iLvl,
   the data you actually want). Reset value + Parse% to visible, then hide the
   genuinely-optional columns. Loads last so it wins. */
@media (max-width: 700px) {
    .rankings-table th:nth-child(6), .rankings-table td:nth-child(6),   /* value (DPS/HPS) */
    .rankings-table th:nth-child(7), .rankings-table td:nth-child(7) {  /* Parse %          */
        display: table-cell;
    }
    .rankings-table th:nth-child(5), .rankings-table td:nth-child(5),    /* iLvl    */
    .rankings-table th:nth-child(8), .rankings-table td:nth-child(8),    /* Date    */
    .rankings-table th:nth-child(9), .rankings-table td:nth-child(9),    /* Log     */
    /* Claude: column 10 (Compare) was leaking onto phones — the old hides stopped
       at 9. Compare isn't useful on touch; hide it so the table fits ~390px. */
    .rankings-table th:nth-child(10), .rankings-table td:nth-child(10) { /* Compare */
        display: none;
    }
}
@media (max-width: 480px) {
    /* Phones: also drop Guild → #, icon, Player, value, Parse% fit ~390px. */
    .rankings-table th:nth-child(4), .rankings-table td:nth-child(4) { display: none; }
}

/* Player modal — pin the ✕ to the top-right (absolute) and flow the name + the
   Full Profile / Armory pills (on their own row) beneath it. Replaces round-2's
   clear:both, which detached the ✕ and threw the header alignment off. The ✕ is
   now a .pm-close-x class (was an inline float:right in the 3 modal HTMLs). */
.pm-close-x { float: right; margin-top: -4px; }   /* desktop default (== the old inline style) */
@media (max-width: 700px) {
    .modal-title { position: relative; padding-right: 42px; }
    .pm-close-x  { position: absolute; top: 10px; right: 10px; float: none; margin: 0; }
    .pm-actions  { display: block; margin-top: 8px; }
    .pm-actions > a { margin-left: 0 !important; margin-right: 6px; }
    #player-modal .modal-title { line-height: 1.4; }
}

/* Meter bar — start from the very LEFT on phones (the parse/rank columns are
   hidden at ≤480, so the desktop 98px offset left a big empty gutter). Pairs
   with meterBarLeftOffset()→0 in app.js so the bar width stays proportional. */
@media (max-width: 480px) {
    .meter-bar { left: 0; }
    /* Claude (v0.102.5): the class-color stripe (.meter-row::before) is hardcoded
       left:95px (desktop, where the bar area starts after the parse/rank columns).
       Those columns are hidden on phones + the bar now starts at left:0, so the
       stripe was landing OVER the player name. Pin it to the very left to match. */
    .meter-row::before { left: 0; }
}
/* Claude (v0.102.5): per-spell drilldown panel (click a meter row) on phones. The
   desktop grid (24px 1fr 130px 80px 60px = ~294px of fixed columns) is far wider than
   a ~360px screen, so the columns squished unevenly and the CRITS/TOTAL headers stopped
   sitting above their numbers. Compact the fixed columns so the header + rows realign;
   the SAME grid is applied to the header row (.detail-col-headers) so they stay locked.
   Also: the Armory + Compare action buttons sized to their text (unequal) — make them
   share the row equally. */
@media (max-width: 480px) {
    .detail-row,
    .detail-col-headers {
        grid-template-columns: 20px minmax(0, 1fr) 60px 58px 38px;
        padding-left: 9px; padding-right: 9px;
    }
    .detail-row.school-col:not(.expanded),
    .detail-col-headers.school-col:not(.expanded) {
        grid-template-columns: 20px minmax(0, 1fr) 42px 60px 58px 38px;
    }
    .detail-action-btn { flex: 1 1 0; justify-content: center; }
}
/* (Characters-modal mobile stacking lives in log.html's own <style> — it loads
   AFTER styles.css and would otherwise override these, so it's co-located with
   the base .cc-c-table rules there.) */

/* Summary boss-shortcut cards — far more compact on phones (they were eating a
   lot of vertical space): 2-up, smaller icon / padding / text. */
@media (max-width: 480px) {
    .summary-boss-grid { grid-template-columns: 1fr 1fr; gap: 6px; }
    .summary-boss-card { padding: 8px 8px 7px; gap: 3px; }
    .summary-boss-card-icon,
    .summary-boss-card-icon-emoji { width: 30px; height: 30px; }
    .summary-boss-card-icon-emoji { font-size: 16px; }
    .summary-boss-card-name { font-size: 11px; }
    .summary-boss-card-meta { font-size: 9.5px; }
    .summary-boss-card-badge { font-size: 8px; padding: 1px 6px; margin-top: 1px; }
}

/* Activity chart — its SVG height is a hardcoded 240px in app.js, so the part-3
   rule that shrank the WRAP to 160px left the 240px SVG spilling 80px onto the
   Top-DPS/HPS widgets below it. Match the wrap to the SVG and clip any spill so
   the chart stays inside its own box. */
@media (max-width: 700px) {
    #activity-chart-svg-wrap { height: 240px !important; overflow: hidden; }
}
/* Series-toggle legend — denser on phones so the 12 togglable series don't push
   the chart far down the page (tighter gaps + smaller text; still tappable). */
@media (max-width: 480px) {
    #activity-chart-legend { gap: 5px 10px !important; font-size: 11px !important; }
    #activity-chart-filters { gap: 8px !important; }
    #activity-chart { padding: 12px 10px 8px !important; }
}

/* ── (I) LEADERBOARD FLEX-COLUMN OVERFLOW (the real one) ─────────────────────
   #raid-speedkills-list and #dungeon-list are display:flex;flex-direction:column.
   A flex item defaults to min-width:auto, which REFUSES to shrink below its
   content's min size — so the wide leaderboard table forced the whole flex
   column (and the page) wider than the viewport, and the inner .admin-table-wrap
   never got to scroll. (This is why the page was fine until the table's data
   rendered.) Force every non-table descendant of those lists to min-width:0 so
   they shrink and the wrapper scrolls. The table itself is EXCLUDED so it keeps
   min-width:max-content (its content width) and scrolls inside the wrapper.
   Conrank/dummy use overflow:auto containers (not flex) so they're unaffected. */
@media (max-width: 700px) {
    #raid-speedkills-list, #dungeon-list,
    #raid-speedkills-list *:not(table):not(thead):not(tbody):not(tr):not(th):not(td):not(col):not(colgroup),
    #dungeon-list *:not(table):not(thead):not(tbody):not(tr):not(th):not(td):not(col):not(colgroup) {
        min-width: 0;
    }
    /* "Reduce the table" — smaller font + tighter cells so the leaderboards need
       less width (and the horizontal scroll is shorter). */
    .admin-table-wrap > .admin-table,
    .admin-widget-body > .admin-table { font-size: 11px !important; }
    .admin-table-wrap > .admin-table th, .admin-table-wrap > .admin-table td,
    .admin-widget-body > .admin-table th, .admin-widget-body > .admin-table td { padding: 5px 7px !important; }
}

/* ── (J) RANKINGS FILTER CLUSTER — tidy rows on phones (Claude v0.100.27) ─────
   The flat flex cluster (metric buttons + class select + "Timeframe:" label + 3
   buttons + "Phase:" label + select) wrapped into a ragged block with the labels
   orphaned at line ends. Organise it into clean, full-width rows:
       [ DPS | HPS | Tank ]
       [ All Classes ▾ ]
       [ All Time | This Month | This Week ]
       [ Phase ▾ ]
   Each <select> gets a 100% flex-basis so it lands on its own row (which also
   breaks the two button groups apart), and the buttons in each group share their
   row equally (flex:1). The redundant Timeframe:/Phase: labels are dropped — the
   controls are self-evident on a phone. NOTE: DPS/HPS are .rk-metric-btn but
   Tank/Speedkill are .filter-btn, so the metric group is targeted by id. */
@media (max-width: 700px) {
    .rankings-filters { gap: 6px; row-gap: 8px; }
    .rankings-filters .tf-label { display: none; }
    .rankings-filters #rk-dps-btn,
    .rankings-filters #rk-hps-btn,
    .rankings-filters #rk-tank-btn,
    .rankings-filters .tf-btn { flex: 1 1 0; min-width: 0; margin: 0; }
    .rankings-filters > .rk-class-select { flex: 1 1 100%; max-width: 100%; }
}

/* ── (K) TALENTS + GUILDS phone fixes (Claude — ui-polish audit) ──────────────
   talents: each tree grid is a fixed-px-width child of an overflow:hidden card.
   With only html{overflow-x:hidden} as the backstop the rightmost talent column
   was CLIPPED (unreachable) on ~390px. js/talents.js now wraps the grid in
   .talent-tree-scroll; cap the card to the viewport and let that wrapper scroll
   horizontally so every column stays reachable while the header stays put.
   guilds: the 8-col table now carries .admin-table-wrap (phone-layer scroll
   containment); additionally drop the two lowest-value columns on small phones
   so less horizontal scrolling is needed. */
@media (max-width: 700px) {
    .talent-tree-card { max-width: 100%; }
    .talent-tree-scroll {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        max-width: 100%;
    }
}
@media (max-width: 480px) {
    #guilds-table th:nth-child(5), #guilds-table td:nth-child(5),   /* Boss Kills       */
    #guilds-table th:nth-child(7), #guilds-table td:nth-child(7) {  /* Progression Rank */
        display: none;
    }
}

/* ── (L) PAIRED ACTION BUTTONS — equal width on phones (Claude — ui-polish) ───
   The log-viewer Armory/Compare buttons were equalized at ≤480 (v0.102.5). The
   same "buttons size to their text → unequal/ragged row" pattern lives on several
   sibling pages; give each paired row flex:1 1 0 so the buttons share it evenly. */
@media (max-width: 700px) {
    /* Log-viewer detail panel: also equalize across the 481–700 tablet band
       (matches the existing ≤480 rule; identical value, so no cascade conflict). */
    .detail-action-btn { flex: 1 1 0; justify-content: center; }
}
@media (max-width: 480px) {
    /* Profile per-card actions (🛡 Armory / ⭐ Set as main / ✕ Unlink). */
    .profile-action-btn { flex: 1 1 0; justify-content: center; text-align: center; }
    /* Player page: the lone Armory CTA fills the row instead of left-aligning. */
    .pp-armory-btn { width: 100%; justify-content: center; }
    /* compare-logs A/B actor pickers share the row equally (drop the 150px floor). */
    .cl-ap-btn { min-width: 0; flex: 1 1 0; max-width: none; }
    /* Armory paperdoll: name to its own row; the two ←/→ nav buttons split evenly. */
    .pd-name { flex: 1 1 100%; margin-right: 0; }
    .pd-back { flex: 1 1 0; justify-content: center; text-align: center; }
}

/* ── (M) CONTENT-PAGE TABLES + LONG CODE (Claude — ui-polish audit) ───────────
   Prose tables (simulators tier table, sim-license-guide flag tables) and long
   inline code (Windows file paths) overflowed narrow phones — and because the
   only backstop is html{overflow-x:hidden} they were CLIPPED (unreachable), not
   scrolled. table-layout:fixed + width:100% makes the tables fit and wrap;
   overflow-wrap:anywhere lets long unbreakable tokens break instead of pushing
   past the right edge. */
.sim-tier-table, .sl-guide table { width: 100%; table-layout: fixed; }
.sim-tier-table th, .sim-tier-table td,
.sl-guide table th, .sl-guide table td { overflow-wrap: anywhere; }
.legal code, .legal pre, .sl-guide code, .sl-guide pre { overflow-wrap: anywhere; }
.legal p[style*="Consolas"], .legal p[style*="monospace"] { overflow-wrap: anywhere; }
@media (max-width: 480px) {
    .sim-tier-table th:first-child { width: 84px; } /* drop the 120px Tier col so the perk col gets room */
}

/* Patreon / Buy-Me-a-Coffee CTAs (contribute page) had no hover/focus feedback —
   the inline brand colors need !important to win over the element style attr. */
#patreon-button:hover { background: #e23b45 !important; }
#bmac-button:hover    { background: #e6c700 !important; }

/* ── (N) FILTER-CHIP TAP TARGETS (Claude — ui-polish audit) ───────────────────
   The dummy-stats class/variant chips + armory class chips were ~24px tall —
   below the comfortable touch target. Give them a 36px min height on phones. */
@media (max-width: 700px) {
    .dummy-class-chip, .dummy-variant-btn, .arm-class-chip {
        min-height: 36px;
        display: inline-flex;
        align-items: center;
    }
}
