/* Raleway — the EMF brand typeface (SIL Open Font License 1.1; see
   static/fonts/OFL.txt). Self-hosted variable font (weight axis 100-900), so the
   strict CSP (font-src falls back to 'self') is satisfied without an external CDN.
   Two subsets: latin covers the UI; latin-ext catches accented names. font-display
   swap paints immediately in the fallback, then swaps in Raleway when it loads. */
@font-face {
    font-family: "Raleway";
    font-style: normal;
    font-weight: 100 900;   /* variable range: 400 body, 600 headings */
    font-display: swap;
    src: url("/static/fonts/raleway-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
        U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "Raleway";
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("/static/fonts/raleway-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
        U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
        U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Auto follows the OS via color-scheme + light-dark(); the toggles set
   data-theme / data-timezone. Approach from esi-sso-quart. */
:root {
    color-scheme: light dark;
    --background-color: light-dark(#ffffff, #121212);
    --foreground-color: light-dark(#111111, #eaeaea);
    --muted-color:      light-dark(#666666, #999999);
    --border-color:     light-dark(#eeeeee, #2a2a2a);
    --mid-background-color: light-dark(#c0c0c0, #333333);

    /* EMF 2026 brand palette (emfcamp.org/about/branding). Fixed hues, so these
       don't vary by theme. */
    --emf-orange:     #F77F02;   /* primary */
    --emf-coral:      #F5515E;   /* secondary */
    --emf-yellow:     #F9E200;   /* accent */
    --emf-green:      #2AE28C;   /* secondary */
    --emf-light-blue: #2EADD9;   /* accent */
    --emf-mid-blue:   #005D96;   /* accent */
    --emf-dark-blue:  #000730;   /* accent */

    /* Interactive accent: brand blue picked per theme for contrast — Mid Blue on
       light (~6.5:1), Light Blue on dark (~7:1). Orange (the primary) fails 4.5:1 on
       white, so it's reserved for non-text/fills, not links. light-dark() tracks the
       color-scheme the theme toggle sets below. */
    --accent: light-dark(var(--emf-mid-blue), var(--emf-light-blue));
}
html[data-theme="light"] {
    color-scheme: light;
    --background-color: #ffffff;
    --foreground-color: #111111;
    --muted-color: #666666;
    --border-color: #eeeeee;
    --mid-background-color: #c0c0c0;
}
html[data-theme="dark"] {
    color-scheme: dark;
    --background-color: #121212;
    --foreground-color: #eaeaea;
    --muted-color: #999999;
    --border-color: #2a2a2a;
    --mid-background-color: #333333;
}

body {
    /* Raleway (brand typeface) at 400; system-ui paints first / covers any glyph
       outside the vendored subsets. */
    font: 0.875rem/1.5 "Raleway", system-ui, sans-serif;
    margin: 2rem;
    background-color: var(--background-color);
    color: var(--foreground-color);
}
/* Headings in Raleway Semi-Bold (600), as the brand guide specifies. */
h1, h2, h3 { font-weight: 600; }
h1 { font-size: 1.2rem; }
#status { color: var(--muted-color); }
header nav { margin: 0.3rem 0 0.6rem; color: var(--muted-color); }
/* Larger/bolder than body links, but same brand-accent colour so link rendering is
   consistent site-wide; the muted separators around them still set them apart. */
header nav a { font-weight: 600; font-size: 1.15rem; }

footer {
    margin-top: 2rem;
    padding-top: 1rem;
    border-top: 0.0625rem solid var(--border-color);
    color: var(--muted-color);
    font-size: 0.9em;
    /* Internal links left, official-site link pushed to the lower-right. The auto
       margin (not justify-content) keeps it right-aligned even when it wraps onto
       its own line on a narrow portrait screen. */
    display: flex;
    align-items: baseline;
    gap: 0.5rem 1rem;
    flex-wrap: wrap;
}
footer p { margin: 0; }
/* The "stack" link is the leftmost of the lower-right cluster; its auto margin pushes
   the whole cluster (stack + external links) to the right, and the flex `gap` spaces
   the members. */
footer .footer-stack { margin-left: auto; }

/* Every link renders in the brand accent (blue) for one consistent treatment,
   footer included. */
a { color: var(--accent); }

/* Visible keyboard focus in the brand accent (meets 3:1 non-text contrast in both
   themes). The toggles manage their own focus via opacity below. */
:focus-visible { outline: 0.125rem solid var(--accent); outline-offset: 0.125rem; }

/* Visually hidden, but available to screen readers (captions, map entity list,
   the /hackers live-line announcer). */
.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
code {
    font-family: ui-monospace, monospace;
    background-color: var(--mid-background-color);
    padding: 0 0.3em;
    border-radius: 0.1875rem;
}
.about { max-width: 60rem; color: var(--muted-color); }
.about p { margin: 0.4rem 0; }

/* Two-column, border-separated rows that mirror the data table's styling. */
.pipeline {
    margin: 0.4rem 0 0;
    display: grid;
    grid-template-columns: max-content 1fr;
}
.pipeline dt, .pipeline dd {
    padding: 0.25rem 0.625rem;               /* match the table's cell padding */
    border-top: 0.0625rem solid var(--border-color);
}
.pipeline dt { font-weight: 600; color: var(--foreground-color); }
.pipeline dd { margin: 0; }

table {
    border-collapse: collapse;
    margin-top: 1rem;
    /* Span the full window width; the body's 2rem margin is the right-side gap.
       Topic/Time are fixed-width, so the Data column absorbs the rest. */
    width: 100%;
    /* Column widths come from the layout, not the content, so cells don't
       resize/jump as values change. */
    table-layout: fixed;
}
th, td { text-align: left; vertical-align: top; padding: 0.25rem 0.625rem; border-bottom: 0.0625rem solid var(--border-color); }
td.data, td.time { font-family: ui-monospace, monospace; }

/* Sortable headers + active-sort arrow (also used by the mobile sort buttons).
   The interactive control is a real <button> inside the <th> (keyboard-operable);
   it's styled to read as a plain header rather than a form button. */
th.sortable { user-select: none; }
th.sortable button {
    font: inherit; font-weight: inherit; color: inherit;
    background: none; border: 0; padding: 0; margin: 0;
    cursor: pointer; text-align: left;
}
th.sortable button:hover { color: var(--muted-color); }
[data-key][data-dir="asc"]::after { content: " \25B2"; font-size: 0.8em; }
[data-key][data-dir="desc"]::after { content: " \25BC"; font-size: 0.8em; }

/* Vehicle-map layer toggles (Vehicles / Buses). */
.map-toggles {
    display: flex; gap: 1rem; margin: 0.3rem 0 0.6rem;
    color: var(--muted-color); font-size: 0.9rem;
}
.map-toggles label { display: inline-flex; align-items: center; gap: 0.3rem; cursor: pointer; }

/* Client-side topic filter: shown on every viewport (unlike the sort bar). */
.filter-bar { margin-top: 1rem; display: flex; gap: 0.75rem; align-items: baseline; }
.filter-input {
    flex: 1 1 auto; max-width: 24rem; padding: 0.4rem 0.6rem; font: inherit;
    color: var(--foreground-color); background-color: var(--background-color);
    border: 0.0625rem solid var(--border-color); border-radius: 0.25rem;
}
.filter-count { color: var(--muted-color); font-size: 0.875rem; white-space: nowrap; }
/* Rows the filter excludes. The attribute selector outranks the mobile
   `tr { display: block }` rule, so hiding works in both the table and card views. */
tr[hidden] { display: none; }

/* Sort toolbar: hidden on desktop (use the headers), shown in the mobile card view. */
.sort-bar { display: none; margin-top: 1rem; color: var(--muted-color); }
.sort-btn {
    background-color: var(--mid-background-color);
    color: var(--foreground-color);
    border: 0.0625rem solid var(--foreground-color);
    border-radius: 0.25rem;
    padding: 0.2em 0.6em;
    margin: 0 0.15rem;
    font: inherit;
    cursor: pointer;
}

/* Constant-width Topic + Time columns. */
th:nth-child(1), td:nth-child(1) {   /* Topic */
    width: 16rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
th:nth-child(2), td:nth-child(2) {   /* Time */
    width: 16rem;
    white-space: nowrap;
}
td.time { color: var(--muted-color); }

/* Data column takes the remaining width and wraps instead of widening. */
td.data { white-space: normal; overflow-wrap: anywhere; }

/* Fixed upper-right cluster holding the official-site link + the toggles. Flex row
   so it lays out cleanly whether a page has one toggle or two — no per-element
   right offsets to keep in sync. */
.controls {
    position: fixed;
    top: 0.75rem;
    right: 1vw;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.theme-toggle, .timezone-toggle {
    background-color: var(--mid-background-color);
    color: var(--foreground-color);
    border: 0.0625rem solid var(--foreground-color);
    border-radius: 0.25rem;
    padding: 0.3em 0.6em;
    font-size: 80%;
    cursor: pointer;
    opacity: 0.5;
    text-align: center;
}
/* Keyboard focus must restore full opacity too, not just hover — otherwise a
   tabbed-to toggle stays faint (contrast + focus-visibility failure). */
.theme-toggle:hover, .timezone-toggle:hover,
.theme-toggle:focus-visible, .timezone-toggle:focus-visible { opacity: 1; }

/* Official-site link, used in both corners (upper-right cluster + footer). Matches
   the toggles' scale in the cluster; the external-arrow marks it as off-site. */
.site-link {
    color: var(--accent);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}
.site-link::after { content: " \2197"; }   /* ↗ leaves the site */
.site-link:hover, .site-link:focus-visible { text-decoration: underline; }

/* Fixed upper-left official-site link, mirroring the footer's lower-right one. */
.site-link-corner {
    position: fixed;
    top: 0.75rem;   /* same fixed top margin as .controls, so both corners align */
    left: 2rem;     /* align with the header text (body's left margin) */
    z-index: 2;
    font-size: 80%;
    /* Row of external links (emfcamp.org + bar). No padding/border and a tight
       line-height so the links' TEXT top sits at the same level as the toggle
       buttons' top edge (their box top, at top: 0.75rem) — not below it. The buttons'
       own padding insets their label lower; we align to the button box top. */
    display: flex;
    gap: 0.5rem 1rem;
    line-height: 1;
}

html[data-timezone="local"] .timezone-toggle {
    background-color: var(--emf-yellow);
    color: black;
}

/* --- Overbrewed scoreboard --- */
.overbrewed-intro { color: var(--muted-color); max-width: 60rem; }
.board-updated { color: var(--muted-color); font-size: 0.9em; margin-top: 0.75rem; }
.scoreboard-scroll { overflow-x: auto; }   /* wide board scrolls, never overflows the page */
/* Content-sized columns + right-aligned tabular numbers. Overrides the live-feed
   table's fixed Topic/Time column widths (which apply to any table). */
.scoreboard { table-layout: auto; }
.scoreboard th:nth-child(1), .scoreboard td:nth-child(1),
.scoreboard th:nth-child(2), .scoreboard td:nth-child(2) {
    width: auto; white-space: normal; overflow: visible; text-overflow: clip;
}
.scoreboard th.num, .scoreboard td.num {
    text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.scoreboard tbody tr:first-child td { font-weight: 600; }   /* the leader stands out */

/* --- Mobile (portrait & small landscape): stack rows into cards --- */
/* 32rem of fixed columns can't fit a phone, so drop the table grid and stack
   Topic / Time / Data per row. Applies in both orientations below 43.75rem. */
@media (max-width: 43.75rem) {
    body {
        margin: 1rem;
        padding-top: 2.75rem;   /* clear the fixed toggle buttons */
    }

    .controls { top: 0.5rem; right: 0.5rem; }
    .site-link-corner { top: 0.5rem; left: 1rem; }   /* match mobile body margin */

    .sort-bar { display: block; }            /* headers are hidden below, so sort via buttons */

    table, tbody, tr, td { display: block; width: auto; }
    thead { display: none; }                 /* header labels are redundant when stacked */
    table { table-layout: auto; margin-top: 0.5rem; }

    tr { border-bottom: 0.0625rem solid var(--border-color); padding: 0.5rem 0; }
    td { border: 0; padding: 0.0625rem 0; }

    /* Undo the desktop fixed-width Topic/Time columns. */
    td:nth-child(1), td:nth-child(2) {
        width: auto;
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }
    td:nth-child(1) { font-weight: 600; }     /* Topic = card heading */
    td.time { font-size: 0.9em; }
    td.data { overflow-wrap: anywhere; }

    /* The scoreboard is a numeric grid — keep it a real table (it scrolls sideways
       inside .scoreboard-scroll) rather than stacking into label-less cards. */
    .scoreboard { display: table; table-layout: auto; }
    .scoreboard thead { display: table-header-group; }
    .scoreboard tbody { display: table-row-group; }
    .scoreboard tr { display: table-row; padding: 0; }
    .scoreboard th, .scoreboard td {
        display: table-cell;
        width: auto;
        border-bottom: 0.0625rem solid var(--border-color);
    }
}

/* Touch devices have no hover, so keep the toggles readable rather than faint. */
@media (hover: none) {
    .theme-toggle, .timezone-toggle { opacity: 0.9; }
}

/* --- /topic charts page --- */
.range-bar { margin-top: 1rem; }
.range-btn {
    background-color: var(--mid-background-color);
    color: var(--foreground-color);
    border: 0.0625rem solid var(--foreground-color);
    border-radius: 0.25rem;
    padding: 0.2em 0.7em;
    margin-right: 0.3rem;
    font: inherit;
    cursor: pointer;
}
.range-btn.active { background-color: var(--emf-yellow); color: black; }

/* One chart per row, stacked vertically, each filling the full content width
   (the body margin provides the side gutters). */
#charts {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.2rem;
    margin-top: 1rem;
}
.chart-card { min-width: 0; }  /* let the cell shrink below the canvas size */
.chart-card h2 { font-size: 0.95rem; font-weight: 600; margin: 0 0 0.25rem; }
.chart-wrap { position: relative; height: 14rem; }

/* Vehicle map (/vehicles) */
emf-map {
    display: block;
    width: 100%;
    height: 75vh;
    margin-top: 1rem;
    border: 0.0625rem solid var(--border-color);
    border-radius: 0.25rem;
}
/* Shown by map.js when the external base map can't load (e.g. an EMF tile outage). */
.map-fallback {
    margin-top: 1rem;
    padding: 0.75rem 1rem;
    border: 0.0625rem solid var(--border-color);
    border-radius: 0.25rem;
    background-color: var(--mid-background-color);
}
.map-fallback p { margin: 0; }
/* The tracked-entities list, normally sr-only, laid out readably when the fallback
   reveals it (map.js removes .sr-only). Columns keep a long list compact. */
#entity-section:not(.sr-only) { margin-top: 1rem; }
#entity-section:not(.sr-only) ul {
    margin: 0.5rem 0 0;
    padding-left: 1.25rem;
    columns: 18rem;
    color: var(--muted-color);
}
#entity-section:not(.sr-only) li { break-inside: avoid; }
/* Vehicle markers live in the component's shadow DOM and are styled inline in
   vehicles.js (this stylesheet can't reach across the shadow boundary). */

/* --- /hackers screenplay reading --- */
.hackers-intro { max-width: 60rem; color: var(--muted-color); margin: 0.4rem 0 0; }

.screenplay {
    max-width: 44rem;
    margin: 1.5rem 0;
    /* A little wider line-height than the default for readability of prose. */
    line-height: 1.5;
}

/* Scene heading = the subheading that groups the lines beneath it. */
.scene-heading {
    font-family: ui-monospace, monospace;
    font-size: 0.9rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--foreground-color);
    margin: 1.8rem 0 0.8rem;
    padding-bottom: 0.3rem;
    border-bottom: 0.0625rem solid var(--border-color);
}
.scene-heading:first-child { margin-top: 0; }

/* Stage directions: the action, set apart from spoken dialogue. */
.stage-direction {
    color: var(--muted-color);
    font-style: italic;
    margin: 0.6rem 0;
}

/* A block of dialogue: the speaker called out above their words. */
.dialogue { margin: 0.9rem 0; }
.speaker {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    margin: 0 0 0.15rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    font-size: 0.85rem;
}
.speaker .line-time {
    font-family: ui-monospace, monospace;
    font-weight: 400;
    font-size: 0.75rem;
    text-transform: none;
    letter-spacing: 0;
    color: var(--muted-color);
}
.spoken { margin: 0 0 0 1.5rem; max-width: 34rem; }

/* --- /bar bar inventory --- */
.bar-intro { color: var(--muted-color); max-width: 60rem; }
.bar-totals { margin-top: 0.5rem; color: var(--muted-color); font-size: 0.9em; }
.bar-venue-nav { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1rem 0; }
.venue-chip {
    padding: 0.2em 0.7em; border: 0.0625rem solid var(--border-color);
    border-radius: 1rem; text-decoration: none; font-size: 0.9rem; white-space: nowrap;
}
.venue { margin: 1.75rem 0; }
.venue h2 { font-size: 1.1rem; margin: 0 0 0.3rem; }
.venue-map { font-size: 0.8rem; font-weight: 400; }
.dept {
    font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em;
    font-weight: 600; color: var(--muted-color); margin: 0.9rem 0 0.2rem;
}
.products { list-style: none; margin: 0; padding: 0; }
/* Each product is a single line: name (truncates if long), meta, then a
   right-aligned cluster of availability badge + price. */
.product {
    display: flex; flex-wrap: nowrap; align-items: center; gap: 0.5rem;
    padding: 0.35rem 0; border-bottom: 0.0625rem solid var(--border-color);
}
.p-name {
    font-weight: 600; flex: 0 1 auto; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.p-meta {
    color: var(--muted-color); font-size: 0.85em; flex: 0 1 auto; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.p-right {
    margin-left: auto; flex: none; display: flex; align-items: center; gap: 0.5rem;
}
.p-price { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Draught availability badge. */
.p-avail {
    font-size: 0.72em; font-weight: 600; padding: 0.1em 0.5em;
    border-radius: 0.75rem; white-space: nowrap;
}
.avail-plenty { background-color: var(--emf-green); color: #06301c; }
.avail-some { background-color: var(--emf-yellow); color: #3a3400; }
.avail-low { background-color: var(--emf-orange); color: #3a1e00; }
.avail-empty { background-color: var(--mid-background-color); color: var(--muted-color); }

/* --- /arcade lighting --- */
.arcade-intro { color: var(--muted-color); max-width: 60rem; }
.arcade-section { margin: 1.5rem 0; }
.arcade-section h2 { font-size: 1.1rem; margin: 0 0 0.6rem; }
/* The strips are a physical 4x4 matrix: render as a 4x4 grid of square tiles at
   every width — fills the viewport on mobile, capped on desktop. */
.arcade-strips {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.4rem;
    max-width: 36rem;
}
.arcade-swatch {
    display: flex; flex-direction: column; justify-content: space-between;
    aspect-ratio: 1; min-width: 0; overflow: hidden;
    padding: 0.4rem; border-radius: 0.4rem;
    border: 0.0625rem solid var(--border-color);
    transition: background-color 0.4s ease;
}
.sw-label { font-weight: 600; font-size: clamp(0.65rem, 2.4vw, 0.95rem); line-height: 1.1; }
.sw-hex {
    font-family: var(--mono-font, monospace); opacity: 0.85;
    font-size: clamp(0.55rem, 2.1vw, 0.85rem);
}
.arcade-matrix { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.arcade-chip {
    display: inline-flex; align-items: center; gap: 0.45rem;
    padding: 0.3em 0.6em; border-radius: 1rem;
    border: 0.0625rem solid var(--border-color);
}
.chip-dot {
    width: 1rem; height: 1rem; border-radius: 50%; flex: none;
    border: 0.0625rem solid rgba(128, 128, 128, 0.4);
}
.chip-hex { font-family: var(--mono-font, monospace); font-size: 0.8rem; }
.chip-count { color: var(--muted-color); font-size: 0.85em; font-variant-numeric: tabular-nums; }
.arcade-count { font-size: 0.8rem; font-weight: 400; color: var(--muted-color); }
/* Cabinets are physical machines laid out as a 4x4 grid, matching the strips. */
.arcade-cabinets {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.4rem;
    max-width: 36rem;
}
.arcade-cab {
    display: flex; flex-direction: column; justify-content: space-between;
    aspect-ratio: 1; gap: 0.25rem; min-width: 0; overflow: hidden;
    padding: 0.5rem 0.6rem; border-radius: 0.4rem;
    border: 0.0625rem solid var(--border-color);
}
.cab-top { display: flex; align-items: center; gap: 0.4rem; min-width: 0; }
.cab-dot {
    width: 0.6rem; height: 0.6rem; border-radius: 50%; flex: none;
    background: var(--muted-color);
}
.arcade-cab.cab-online .cab-dot { background: var(--emf-green); }
.arcade-cab.cab-offline .cab-dot { background: var(--emf-coral); }
.arcade-cab.cab-offline { opacity: 0.6; }
.cab-label {
    font-weight: 600; font-size: clamp(0.7rem, 2.2vw, 0.9rem);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cab-status {
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.03em;
    color: var(--muted-color);
}
