@font-face {
  font-family: 'Berkeley Mono';
  font-style: normal;
  font-weight: 100 400;
  font-display: swap;
  src: url(assets/fonts/BerkeleyMonoVariable-Regular.woff2) format('woff2'),
       url(assets/fonts/BerkeleyMonoVariable-Regular.woff) format('woff');
}

:root {
  --bg:#020805; --panel:#04120a; --line:#0e3019; --faint:#0a2412;
  --dim:#2f7d4d; --txt:#4fd67d; --bright:#bcffd0; --accent:#39ff7a; --track:#0c2414;
  color-scheme: dark;
}
* { box-sizing:border-box; margin:0; padding:0; }
html, body { background:#000; font-family:'Berkeley Mono', ui-monospace, monospace; }
body {
  color:var(--txt); font-size:13px; line-height:1.5; padding:12px;
  position:relative; background:var(--bg);
}
body::after {
  content:""; position:fixed; inset:0; pointer-events:none; z-index:50;
  background:repeating-linear-gradient(to bottom, rgba(0,0,0,.16) 0, rgba(0,0,0,.16) 1px, transparent 1px, transparent 3px);
}
.glow { text-shadow:0 0 4px rgba(57,255,122,.45); }
.dashboard { max-width:1500px; margin-inline:auto; }

/* topbar */
.top { display:flex; align-items:center; gap:12px; flex-wrap:wrap; border-bottom:1px solid var(--line); padding-bottom:9px; margin-bottom:11px; }
.brand { color:var(--bright); font-size:16px; letter-spacing:.04em; }
.cur { color:var(--accent); }
.status { color:var(--dim); font-size:11.5px; } .status .on { color:var(--accent); }
.spacer { flex:1; }
.seg { display:inline-flex; border:1px solid var(--line); }
.seg button { background:transparent; border:0; color:var(--dim); font:inherit; font-size:11px; letter-spacing:.06em; padding:3px 9px; cursor:pointer; border-right:1px solid var(--line); }
.seg button:last-child { border-right:0; }
.seg button.on { background:var(--accent); color:#021006; }
.meta { color:var(--dim); font-size:11.5px; } .meta b { color:var(--txt); }

/* presets */
.presets { display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin-bottom:11px; }
.grp { color:var(--dim); font-size:11px; letter-spacing:.16em; margin:0 4px 0 2px; }
.chip { border:1px solid var(--line); color:var(--dim); padding:3px 10px; font-size:11.5px; letter-spacing:.08em; text-transform:uppercase; background:transparent; white-space:nowrap; cursor:pointer; }
.chip:hover { color:var(--txt); border-color:var(--dim); }
.chip.on { background:var(--accent); color:#021006; border-color:var(--accent); }

/* bands */
.band { border:1px solid var(--line); background:var(--panel); padding:9px 11px 11px; margin-bottom:11px; }
.ph { color:var(--bright); font-size:11px; letter-spacing:.2em; text-transform:uppercase; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--line); padding-bottom:6px; margin-bottom:10px; }
.ph .c { color:var(--dim); }
.ph .tog { display:none; color:var(--accent); margin-left:10px; cursor:pointer; }
.ph button.tog { background:transparent; border:0; font:inherit; padding:0; }
.ticker { display:none; }
.boards-backdrop { display:none; }

/* filters */
.fgrid { display:grid; grid-template-columns:repeat(4,1fr); gap:9px 26px; }
.slab .lh { display:flex; justify-content:space-between; font-size:11px; margin-bottom:5px; }
.slab .lh .lbl { color:var(--dim); } .slab .lh .val { color:var(--bright); } .slab .lh .val .u { color:var(--dim); }
.slab .lh .val.active { color:var(--accent); }
.track { position:relative; height:6px; background:var(--track); border:1px solid var(--line); touch-action:none; }
.range { position:absolute; top:0; bottom:0; background:var(--accent); opacity:.65; background-image:repeating-linear-gradient(90deg, rgba(0,0,0,.55) 0 1px, transparent 1px 4px); }
.knob { position:absolute; top:-3px; width:6px; height:10px; background:var(--bright); box-shadow:0 0 4px rgba(57,255,122,.6); cursor:ew-resize; }
.knob:focus { outline:1px solid var(--accent); outline-offset:1px; }
@media (pointer:coarse) { .knob { width:14px; height:18px; top:-7px; } .track { height:8px; } }

/* results table */
.tvp { position:relative; }
.tablewrap { overflow-x:auto; scrollbar-width:thin; }
/* separate, not collapse: collapsed borders belong to the table grid and
   scroll away when thead goes position:sticky; only per-cell bottom borders
   are used so the two modes render identically here */
table { width:100%; border-collapse:separate; border-spacing:0; font-size:12px; }
thead th { color:var(--dim); font-weight:400; text-align:right; font-size:10px; letter-spacing:.08em; text-transform:uppercase; border-bottom:1px solid var(--line); padding:4px 7px; white-space:nowrap; cursor:pointer; user-select:none; }
th.l, td.l { text-align:left; }
th.flex, td.flex { width:100%; }
thead th .ar { color:var(--accent); }
tbody td { padding:3px 7px; text-align:right; border-bottom:1px solid var(--faint); white-space:nowrap; }
tbody tr:hover td { background:#08200f; }
th.city, td.city { position:sticky; left:0; background:var(--panel); z-index:2; }
.tvp.scrollable th.city, .tvp.scrollable td.city { box-shadow:6px 0 8px -4px rgba(0,0,0,.7); }
.name { color:var(--bright); } .u { color:var(--dim); font-size:10px; } .dim { color:var(--dim); } .big { color:var(--bright); }
.g { display:inline-flex; align-items:center; gap:6px; justify-content:flex-end; } .g .n { min-width:20px; text-align:right; }
.meter { width:36px; height:7px; background:var(--track); border:1px solid var(--line); position:relative; display:inline-block; }
.meter i { position:absolute; left:0; top:0; bottom:0; background:var(--accent); opacity:.7; background-image:repeating-linear-gradient(90deg, rgba(0,0,0,.6) 0 1px, transparent 1px 3px); }
.dayrow { display:inline-flex; gap:1px; }
.dayrow b { width:13px; text-align:center; font-weight:400; font-size:10px; color:#1c4a2c; }
.dayrow b.on { color:#021006; background:var(--accent); }
.dayrow b.nd { color:#0d2415; }
.empty { color:var(--dim); padding:18px 7px; }

/* scroll affordance */
.fade-r { display:none; position:absolute; top:0; bottom:0; right:0; width:54px; pointer-events:none; z-index:3; background:linear-gradient(to right, rgba(4,18,10,0), var(--panel) 78%); }
.swipe { display:none; position:absolute; bottom:6px; right:8px; z-index:4; pointer-events:none; color:var(--accent); font-size:11px; letter-spacing:.1em; text-shadow:0 0 5px rgba(57,255,122,.6); animation:pulse 1.3s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity:.4; } 50% { opacity:1; } }
.tvp.scrollable .fade-r, .tvp.scrollable .swipe { display:block; }

/* boards */
.bgrid { display:grid; grid-template-columns:repeat(4,1fr); gap:10px 22px; }
.sub { color:var(--dim); font-size:10px; letter-spacing:.14em; text-transform:uppercase; margin-bottom:6px; }
.lb .r { display:flex; justify-content:space-between; padding:2px 0; }
.lb .r .nm { color:var(--txt); } .lb .r .nm .rk { color:var(--dim); display:inline-block; width:16px; } .lb .r .v { color:var(--bright); }
.summary .r { display:flex; justify-content:space-between; padding:2px 0; color:var(--txt); } .summary .r .v { color:var(--bright); }

.foot { color:var(--dim); font-size:11px; display:flex; gap:16px; flex-wrap:wrap; padding:2px; }
.foot .on { color:var(--accent); } .foot a { color:var(--dim); }

/* app shell (all sizes): the page itself never scrolls — results scroll
   inside their band, boards stay on screen. Falls back to page scroll on
   very short viewports via the .tablewrap min-height. */
/* the inner chain has min-height:0 so the table can compress to the
   available space (a flex container's intrinsic minimum is its content
   height, which would forbid shrinking); the explicit floor on the band
   itself (~header + 200px of table) is the one true shrink limit — flex
   stops there and the page scrolls instead of content overlapping. */
.band.results { display:flex; flex-direction:column; min-height:254px; }
.tvp { flex:1; min-height:0; display:flex; flex-direction:column; }
.tablewrap { overflow-y:auto; flex:1; min-height:0; }
thead th { position:sticky; top:0; background:var(--panel); z-index:3; }
th.city { z-index:4; } /* corner cell: above sticky header row and sticky city column */

@media (min-width:680px) {
  .dashboard { height:calc(100vh - 24px); } /* 24px = body 12px top+bottom padding */
}

/* medium (680-1079px): single column, results take the leftover height,
   boards sit below in their 2-column grid */
@media (min-width:680px) and (max-width:1079px) {
  .dashboard { display:flex; flex-direction:column; }
  .band.results { flex:1; }
}

/* wide (>=1080px): boards become a right sidebar next to the results */
@media (min-width:1080px) {
  .dashboard {
    display:grid;
    grid-template-columns:minmax(0,1fr) 300px;
    grid-template-rows:auto auto auto 1fr auto; /* 1fr min = content minimum: never crush the results row */
    grid-template-areas:
      "top top"
      "presets presets"
      "filters filters"
      "results boards"
      "foot foot";
    column-gap:11px;
  }
  .top { grid-area:top; }
  .presets { grid-area:presets; }
  .band.filters { grid-area:filters; }
  .band.results { grid-area:results; }
  .band.boards { grid-area:boards; min-height:0; overflow-y:auto; }
  .foot { grid-area:foot; }
  .band.boards .bgrid { grid-template-columns:1fr; } /* boards + summary stack vertically */
}

@media (max-width:1079px) { .fgrid { grid-template-columns:repeat(3,1fr); } .bgrid { grid-template-columns:repeat(2,1fr); gap:14px 22px; } }
/* upper-medium: enough width for 4-across filters and boards — halves their
   height so the boards stay inside the first viewport at ~800px tall
   (after the generic <=1079 block so it wins at 840-1079) */
@media (min-width:840px) and (max-width:1079px) {
  .fgrid { grid-template-columns:repeat(4,1fr); }
  .bgrid { grid-template-columns:repeat(4,1fr); gap:10px 16px; }
}
@media (max-width:679px) {
  body { padding:8px; }
  /* mobile app shell: results scroll internally; the collapsed ticker and
     the footer live in normal flow at the bottom of the viewport. dvh so
     the browser's dynamic toolbar doesn't push the footer off screen. */
  .dashboard { height:calc(100vh - 16px); height:calc(100dvh - 16px); display:flex; flex-direction:column; }
  .band.results { flex:1; }
  .meta { display:none; }
  .presets { flex-wrap:nowrap; overflow-x:auto; padding-bottom:4px; }
  .fgrid { grid-template-columns:1fr 1fr; }
  .bgrid { grid-template-columns:1fr; }
  .ph .tog { display:inline; }
  .hint-drag { display:none; }
  .band.filters.collapsed .ph { border-bottom:0; margin-bottom:0; padding-bottom:0; }
  .band.filters.collapsed .body { display:none; }

  /* boards: in-flow ticker strip between results and footer, expandable to
     a fixed bottom sheet. Collapsed is the class-less default so a fresh
     htmx swap of the band lands collapsed; JS re-adds .expanded if the
     sheet was open. */
  .band.boards {
    margin-bottom:8px; border-color:var(--accent);
    box-shadow:0 0 10px rgba(57,255,122,.25);
  }
  .band.boards:not(.expanded) { padding:0; }
  .band.boards.expanded {
    position:fixed; left:8px; right:8px; bottom:8px; z-index:40; /* under the scanline overlay (z 50) */
    margin:0; /* the in-flow margin-bottom would offset the fixed margin box */
  }
  .band.boards:not(.expanded) .ph,
  .band.boards:not(.expanded) .bgrid { display:none; }
  .band.boards:not(.expanded) .ticker {
    display:flex; align-items:center; gap:7px; width:100%;
    background:transparent; border:0; color:var(--bright); font:inherit;
    font-size:11.5px; letter-spacing:.06em; padding:9px 10px; cursor:pointer; text-align:left;
  }
  .ticker .tk-label { color:var(--dim); letter-spacing:.14em; }
  .ticker .tk-value { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .ticker .tk-pos { color:var(--dim); font-size:10px; }
  .ticker .tk-arrow { color:var(--accent); }
  .band.boards.expanded { max-height:72vh; overflow-y:auto; }
  .band.boards.expanded .ticker { display:none; }
  .boards-backdrop:not([hidden]) { position:fixed; inset:0; z-index:39; background:rgba(0,2,1,.7); display:block; }
}
