/* Calendar page styles. Served as site/app.css; agent/build_site.py copies it
   there unchanged. */
:root{
  --bg:#FFFFFF; --surface:#FFFFFF; --surface-2:#F6F6F6; --surface-3:#ECECEC; --inkblock:#0A0A0A;
  --ink:#0A0A0A; --muted:#565656; --faint:#8C8C8C; --border:#E6E6E6; --border-strong:#111;
  --accent:#E4384A; --accent-strong:#C22B3C; --accent-ink:#FFFFFF; --on-block:#FFFFFF;
  --shadow:0 1px 0 rgba(10,10,10,.04);
  --s-ironman:#D62828; --s-challenge:#2A9D5C; --s-world:#2D6CDF; --s-t100:#7A3FF2; --s-indep:#8C8C8C; --s-fed:#C77D00;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --bg:#0A0A0A; --surface:#141414; --surface-2:#1B1B1B; --surface-3:#242424; --inkblock:#000000;
    --ink:#F4F4F4; --muted:#A6A6A6; --faint:#6E6E6E; --border:#2A2A2A; --border-strong:#3A3A3A;
    --accent:#F0435A; --accent-strong:#FF5A6E; --accent-ink:#0A0A0A; --on-block:#FFFFFF;
    --shadow:none;
    --s-ironman:#F0533F; --s-challenge:#43B274; --s-world:#5A93DB; --s-t100:#9E75E0; --s-indep:#9A9A9A; --s-fed:#E0A22E;
  }
}
:root[data-theme="dark"]{
  --bg:#0A0A0A; --surface:#141414; --surface-2:#1B1B1B; --surface-3:#242424; --inkblock:#000000;
  --ink:#F4F4F4; --muted:#A6A6A6; --faint:#6E6E6E; --border:#2A2A2A; --border-strong:#3A3A3A;
  --accent:#F0435A; --accent-strong:#FF5A6E; --accent-ink:#0A0A0A; --on-block:#FFFFFF;
  --shadow:none;
  --s-ironman:#F0533F; --s-challenge:#43B274; --s-world:#5A93DB; --s-t100:#9E75E0; --s-indep:#9A9A9A; --s-fed:#E0A22E;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);
  font-family:"Poppins",system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
  line-height:1.55;-webkit-font-smoothing:antialiased;}
.wrap{max-width:980px;margin:0 auto;padding:0 22px}
a{color:var(--ink)}
.oswald{font-family:"Oswald",Impact,sans-serif}

/* Masthead */
header.masthead{padding:40px 0 24px}
header.masthead .wrap{position:relative}
header.masthead .themetoggle{position:absolute;top:0;right:22px}
.eyebrow{font-family:"Oswald",sans-serif;font-weight:600;font-size:13px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--accent);margin:0 0 12px}
/* The mark is a red chevron on transparency, so it reads on both the light
   and the dark ground without a second file or a filter. */
/* Vertical padding so the tap target clears 44px. The mark is 26px tall and
   the row was 22px, which is a fiddly thing to hit on a phone for the one
   link on the page that goes to the business. The negative margin keeps the
   masthead spacing exactly as it was. */
a.brand{display:inline-flex;align-items:center;gap:11px;text-decoration:none;color:var(--accent);
  padding:11px 0;margin:-11px 0 3px}
a.brand:hover span{color:var(--accent-strong)}
a.brand:hover .logo{opacity:.82}
.logo{height:26px;width:auto;display:block;transition:opacity .15s}
h1{font-family:"Archivo",sans-serif;font-weight:900;letter-spacing:-.02em;text-transform:uppercase;
  font-size:clamp(32px,6.5vw,60px);line-height:.95;margin:0;text-wrap:balance}
.lede{color:var(--muted);max-width:62ch;margin:16px 0 0;font-size:15.5px}
.brandline{display:flex;align-items:center;gap:10px;margin-top:18px;font-size:13px;color:var(--muted);flex-wrap:wrap}
.brandline strong{color:var(--ink);font-weight:600}
.tick{width:6px;height:6px;background:var(--accent);display:inline-block}

.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin:26px 0 0;border:1px solid var(--border)}
.stat{padding:16px 18px;border-right:1px solid var(--border)}
.stat:last-child{border-right:0}
.stat .n{font-family:"Oswald",sans-serif;font-weight:700;font-size:30px;line-height:1;font-variant-numeric:tabular-nums}
.stat .l{font-family:"Oswald",sans-serif;font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);margin-top:8px;font-weight:500}


/* ── Controls ─────────────────────────────────────────────────────────────
   One bar, one row of controls, no second filter surface. The previous build
   had three ways to narrow the same list — a tabbed chip panel, this bar, and
   a month rail that also filtered — so the same cut could be made in three
   places and the state was never legible in one glance. Four selects say the
   whole thing: what, where, which series, how far. */
.controls{position:sticky;top:0;z-index:20;background:var(--bg);border-top:2px solid var(--border-strong);
  border-bottom:1px solid var(--border);padding:12px 0}
.controls .wrap{display:flex;flex-wrap:wrap;gap:9px;align-items:center}
.search{flex:1 1 200px;min-width:170px;max-width:340px;display:flex;align-items:center;gap:8px;background:var(--surface-2);
  border:1px solid var(--border);padding:9px 12px}
.search input{border:0;background:transparent;color:var(--ink);font:inherit;width:100%;outline:none}
.search svg{flex:none;color:var(--faint)}

/* Native <select>: the whole option list, its counts and its keyboard
   behaviour come free, and on a phone it opens as the OS picker instead of
   pushing a 70-chip grid down the page. */
.sel{position:relative;display:inline-flex;align-items:center}
.sel select{font-family:"Oswald",sans-serif;font-size:12.5px;letter-spacing:.05em;text-transform:uppercase;
  font-weight:600;color:var(--ink);background:var(--surface-2);border:1px solid var(--border);
  padding:10px 28px 10px 11px;cursor:pointer;appearance:none;max-width:200px;
  text-overflow:ellipsis;border-radius:0}
.sel::after{content:"";position:absolute;right:12px;pointer-events:none;
  border:4px solid transparent;border-top-color:var(--faint);margin-top:4px}
.sel select:hover{border-color:var(--border-strong)}
.sel.on select{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.sel.on::after{border-top-color:var(--bg)}
.sel select option{background:var(--surface);color:var(--ink);text-transform:none;letter-spacing:0}
.sel select option:disabled{color:var(--faint)}

/* The filter panel is a real element only on a phone. On desktop it is
   display:contents, so the selects it wraps stay direct flex children of
   .controls .wrap and the desktop bar keeps the layout it always had. */
.filterpanel{display:contents}
.filtertoggle{display:none}

.btn{font-family:"Oswald",sans-serif;font-size:13px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink);background:var(--surface-2);border:1px solid var(--border);padding:10px 12px;cursor:pointer}
.btn:focus-visible,.search:focus-within,a:focus-visible,button:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.btn.clear{border-color:var(--border-strong);color:var(--muted)}
.btn.clear:hover{color:var(--accent);border-color:var(--accent)}
.btn.clear[hidden]{display:none}
.count{font-family:"Oswald",sans-serif;font-size:13px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted);margin-left:auto;padding-left:6px;white-space:nowrap}
.count b{color:var(--ink)}

/* Pro-only races have no open entry, so they are off by default and the
   switch stays a switch rather than becoming a fifth dropdown. */
.protoggle{font-family:"Oswald",sans-serif;font-size:12px;letter-spacing:.04em;text-transform:uppercase;
  display:inline-flex;align-items:center;gap:8px;cursor:pointer;user-select:none;color:var(--muted);
  border:1px solid var(--border);padding:9px 12px;white-space:nowrap;background:var(--surface-2)}
.protoggle input{position:absolute;opacity:0;width:0;height:0}
.protoggle .sw{width:30px;height:16px;background:var(--border-strong);position:relative;flex:none;transition:background .15s}
.protoggle .sw::after{content:"";position:absolute;top:2px;left:2px;width:12px;height:12px;background:var(--bg);transition:transform .15s}
.protoggle input:checked + .sw{background:var(--accent)}
.protoggle input:checked + .sw::after{transform:translateX(14px)}
.protoggle input:focus-visible + .sw{outline:2px solid var(--accent);outline-offset:2px}
.chip.pro{color:var(--accent);border-color:var(--accent);font-weight:600}

/* ── Month rail ───────────────────────────────────────────────────────────
   Purely a jump: clicking a month reveals everything up to it and scrolls
   there. It used to be a filter, which is why the page could show "September"
   while the reader believed they were looking at the season. */
.rail{position:sticky;top:var(--railtop,55px);z-index:18;background:var(--bg);
  border-bottom:1px solid var(--border)}
.rail .wrap{padding:0}
.railscroll{display:flex;overflow-x:auto;scrollbar-width:thin;padding:0 22px;
  scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch}
.railscroll::-webkit-scrollbar{height:3px}
.railscroll::-webkit-scrollbar-thumb{background:var(--border-strong)}
.mchip{font-family:"Oswald",sans-serif;flex:none;scroll-snap-align:start;background:transparent;
  border:0;border-bottom:3px solid transparent;color:var(--muted);cursor:pointer;
  padding:11px 14px 9px;display:flex;align-items:baseline;gap:7px;white-space:nowrap;
  font-size:13px;letter-spacing:.07em;text-transform:uppercase;font-weight:600}
.mchip:hover{color:var(--ink);background:var(--surface-2)}
.mchip .n{font-size:11.5px;font-weight:500;color:var(--faint);font-variant-numeric:tabular-nums}
.mchip[aria-current="true"]{color:var(--ink);border-bottom-color:var(--accent)}
.mchip[aria-current="true"] .n{color:var(--accent)}
.mchip.noraces{opacity:.38;cursor:default}
.mchip.yearmark::before{content:attr(data-year);position:absolute;transform:translateY(-9px);
  font-size:9px;letter-spacing:.14em;color:var(--faint)}
.mchip.yearmark{position:relative;padding-top:15px}

main{padding:6px 0 44px}
.month{font-family:"Archivo",sans-serif;font-weight:800;font-size:15px;letter-spacing:.02em;
  text-transform:uppercase;margin:26px 0 0;display:flex;align-items:center;gap:12px;
  width:100%;background:var(--bg);border:0;color:var(--ink);cursor:pointer;
  padding:10px 0;text-align:left;position:sticky;top:var(--stickytop,96px);z-index:8}
.month .rule{flex:1;height:2px;background:var(--border-strong)}
.month .mc{font-family:"Oswald",sans-serif;font-size:12px;color:var(--faint);font-weight:500;white-space:nowrap}
.month .caret{font-size:11px;color:var(--faint);transition:transform .15s}
/* The build-time copy of the list uses an <h2> where the app uses a <button>,
   because a month heading a crawler reads should be a heading. It carries no
   caret and nothing collapses, so the interactive affordances are dropped. */
h2.month-static{font-size:15px;cursor:default}
.month[aria-expanded="false"] .caret{transform:rotate(-90deg)}
.month[aria-expanded="false"]+ul.races{display:none}
.month .now{font-family:"Oswald",sans-serif;font-size:10px;letter-spacing:.14em;color:var(--accent);font-weight:600}

ul.races{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;border:1px solid var(--border)}
li.race{display:grid;grid-template-columns:70px 1fr auto;gap:18px;align-items:center;background:var(--surface);
  padding:16px 18px;border-bottom:1px solid var(--border);transition:background .12s}
li.race:last-child{border-bottom:0}
li.race:hover{background:var(--surface-2)}
.datebox{text-align:center;border-right:2px solid var(--border-strong);padding-right:16px}
.datebox .d{font-family:"Oswald",sans-serif;font-weight:700;font-size:26px;line-height:.9;font-variant-numeric:tabular-nums}
.datebox .m{font-family:"Oswald",sans-serif;font-size:11px;text-transform:uppercase;letter-spacing:.12em;color:var(--accent);margin-top:4px;font-weight:600}
.datebox .wd{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--faint);margin-top:3px;font-weight:600;font-family:"Oswald",sans-serif}
.datebox .yr{font-family:"Oswald",sans-serif;font-size:11px;font-weight:600;letter-spacing:.08em;color:var(--muted);margin-top:5px;font-variant-numeric:tabular-nums}
.rmain{min-width:0}
.rname{font-family:"Archivo",sans-serif;font-weight:700;font-size:16.5px;letter-spacing:-.01em;margin:0 0 6px;display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.rname a{color:inherit;text-decoration:none}
.rname a:hover{color:var(--accent)}
.rmeta{display:flex;flex-wrap:wrap;gap:6px 14px;align-items:center;font-size:13px;color:var(--muted)}
.loc{display:inline-flex;align-items:center;gap:6px}
.chip{font-family:"Oswald",sans-serif;font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  padding:3px 9px;white-space:nowrap;border:1px solid var(--border-strong);display:inline-flex;align-items:center;gap:6px}
.chip .sd{width:7px;height:7px;flex:none}
.chip.dist{color:var(--muted);border-color:var(--border);font-weight:500}
.chip.tbc{color:var(--faint);border-style:dashed;border-color:var(--border);font-weight:500}
.chip.stale{color:var(--faint);border-style:dashed;border-color:var(--border);font-weight:500}
/* Actual swim/bike/run, shown only when they differ from what the category
   name implies. Same weight as a distance chip but unboxed, so it reads as
   detail about the race rather than another thing to filter on. */
.region-tag{font-family:"Oswald",sans-serif;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.ractions{display:flex;align-items:center}
.addcal{font-family:"Oswald",sans-serif;font-size:12px;letter-spacing:.05em;text-transform:uppercase;font-weight:600;
  color:var(--ink);text-decoration:none;border:1px solid var(--border-strong);padding:8px 12px;white-space:nowrap;display:inline-flex;align-items:center;gap:6px}
.addcal:hover{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
.flag{font-size:15px;line-height:1}

/* The reveal control. One month lands at a time; the sentinel above it pulls
   the next one in before the reader reaches the bottom, so scrolling feels
   continuous while the DOM never holds more than the months actually read. */
.more{padding:24px 0 4px;text-align:center;font-family:"Oswald",sans-serif;font-size:12.5px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.more[hidden]{display:none}
.more button{font-family:"Oswald",sans-serif;background:transparent;border:1px solid var(--border-strong);
  color:var(--ink);padding:11px 20px;cursor:pointer;text-transform:uppercase;letter-spacing:.07em;font-size:12.5px}
.more button:hover{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
.more .rest{display:block;margin-top:9px;font-size:11px;letter-spacing:.1em}
.allin{padding:26px 0 4px;text-align:center;font-family:"Oswald",sans-serif;font-size:11.5px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}

.empty{text-align:center;color:var(--muted);padding:64px 20px;border:1px dashed var(--border-strong);margin-top:24px}
.empty a{color:var(--accent)}
.empty .hint{display:block;margin-top:8px;font-size:13px;color:var(--faint)}

/* One band, pinned under the filters so it is on screen the whole time the
   reader is working the list, rather than scrolling away with the first
   month. That is the whole point of it living in the sticky stack, so its
   HEIGHT is now a permanent tax on the list: keep it to one row, and drop the
   sentence on a phone (below) where the control bar already wraps to three.
   Sticky-TOP is safe in the iframe embed in a way sticky-bottom was not: the
   frame's top edge is in the host viewport, its bottom edge is below it. */
.cta{position:sticky;top:var(--ctatop,55px);z-index:19;background:var(--inkblock);
  color:var(--on-block);padding:11px 0}
/* The row is the INNER .wrap, so the band bleeds full width like the controls
   bar while its content lines up with the filters and the races. Putting the
   flex on .cta itself instead left the copy hard against the viewport edge. */
.cta .wrap{display:flex;flex-wrap:wrap;align-items:center;gap:10px 24px;justify-content:space-between}
.cta .ctatext{min-width:0;flex:1 1 380px}
.cta .k{font-family:"Oswald",sans-serif;font-weight:700;font-size:11px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--accent);margin:0 0 5px}
.cta .k a{color:inherit;text-decoration:none}
.cta .k a:hover{color:var(--accent-strong);text-decoration:underline}
/* No measure cap here. 72ch is a reading width for a paragraph, and it was
   what wrapped this one sentence onto a second line inside a band that has
   room for it — costing the race list ~20px of screen, permanently, to
   protect the line length of a single line. */
.cta .ch{margin:0;color:#e2e2e2;font-size:14px;line-height:1.45}
.cta .ch span{color:#fff;font-weight:600}
.cta a.ctabtn{background:var(--accent);color:#fff;font-family:"Oswald",sans-serif;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;text-decoration:none;padding:12px 20px;white-space:nowrap;flex:none}
.cta a.ctabtn:hover{background:var(--accent-strong)}
.brandlink{color:inherit;text-decoration:none;border-bottom:1px solid var(--border-strong)}
.brandlink:hover{color:var(--accent);border-color:var(--accent)}

footer{border-top:2px solid var(--border-strong);padding:22px 0 48px;color:var(--faint);font-size:12.5px}
footer .wrap{display:flex;flex-wrap:wrap;gap:8px 18px;justify-content:space-between;align-items:center}
footer a{color:var(--muted)}
.themetoggle{font-family:"Oswald",sans-serif;background:transparent;border:1px solid var(--border-strong);color:var(--muted);
  padding:8px 12px;cursor:pointer;font-size:12.5px;text-transform:uppercase;letter-spacing:.06em}

/* ── Phone ────────────────────────────────────────────────────────────────
   Measured on a 390x844 viewport before this block was rewritten: the pinned
   chrome (controls 187 + band 56 + rail 56 + month heading 37) came to 336px,
   40% of the screen, and a race row ran 207-369px. Exactly ONE race was ever
   fully visible, on a calendar of 1539. Everything here buys that back. */
@media (max-width:640px){
  /* 22px of gutter each side is a desktop measure. On a 390px screen it is
     11% of the width, and the race name is the thing paying for it. */
  .wrap{padding:0 14px}

  /* 597px of masthead stood between the page and the first race. */
  header.masthead{padding:22px 0 14px}
  header.masthead .themetoggle{top:0;right:14px;padding:6px 9px;font-size:11px}
  .logo{height:22px}
  a.brand{gap:9px}
  .lede{font-size:14px;margin-top:10px}
  .brandline{margin-top:12px;font-size:12px;gap:7px}
  .stats{grid-template-columns:repeat(4,1fr);margin-top:16px}
  .stat{padding:9px 7px;border-right:1px solid var(--border)}
  .stat:last-child{border-right:0}
  .stat .n{font-size:18px}
  .stat .l{font-size:8.5px;letter-spacing:.07em;margin-top:4px}

  /* Controls: ONE row. Search takes the space; everything else that narrows
     the list goes behind the Filters button, badged with how many are on so
     collapsing them never hides state the reader can't see. */
  .controls{padding:8px 0}
  .controls .wrap{gap:7px;padding:0 14px}
  /* flex-basis 0 and no max-width, or the search keeps its 340px desktop cap,
     fills the row on its own and pushes the Filters button onto a second one
     — which is the whole 52px this block exists to save. */
  .search{flex:1 1 0;min-width:0;max-width:none;padding:7px 10px;order:1}
  .filtertoggle{display:inline-flex;align-items:center;gap:6px;order:2;flex:none;padding:9px 11px}
  .filtertoggle[aria-expanded="true"]{background:var(--ink);color:var(--bg);border-color:var(--ink)}
  .fcount{font-family:"Oswald",sans-serif;background:var(--accent);color:var(--accent-ink);
    font-size:10px;min-width:15px;height:15px;padding:0 4px;display:inline-flex;
    align-items:center;justify-content:center;font-weight:700}
  .fcount[hidden]{display:none}
  .btn.clear{order:3;flex:none;padding:9px 10px}
  .count{order:4;flex-basis:100%;margin-left:0;text-align:left;font-size:11.5px;padding:0}

  /* Out of flow until asked for, so the panel's height is a cost only on the
     screen where the reader opened it, not on every screen of the scroll. */
  .filterpanel{display:none;order:5;flex-basis:100%;flex-wrap:wrap;gap:7px;padding-top:2px}
  .filterpanel.open{display:flex}
  .sel{flex:1 1 calc(50% - 4px)}
  .sel select{width:100%;max-width:none;padding:10px 26px 10px 10px;font-size:12px}
  .protoggle{flex:1 1 calc(50% - 4px);justify-content:center}

  /* The rail stays pinned — it is the only way to cross 16 months — but slim. */
  .railscroll{padding:0 14px}
  .mchip{padding:8px 10px 6px;font-size:12px}
  .mchip.yearmark{padding-top:12px}

  /* The coaching band is NOT sticky here. Pinned, it charged 56px of every
     screen for the entire scroll; in flow it is still met on load, directly
     under the masthead, and the race list gets the 56px back. */
  .cta{position:static;padding:10px 0}
  .cta .wrap{gap:10px;flex-wrap:nowrap;padding:0 14px}
  .cta .ch{display:none}
  .cta .k{margin:0}
  .cta a.ctabtn{padding:9px 14px;font-size:13px}

  /* The row. Its action used to drop to a full-width strip under the race,
     behind a dashed rule — a third row of height for one button. It is now a
     compact square in the row's own right column, and the region tag goes:
     the country is already on the line above it. */
  /* Two columns, not three. The action is taken OUT of the grid and parked in
     the row's own right padding, which hands its 48px column back to the race
     name — the widest single cause of a tall row, since a name that wraps to
     three lines costs more than everything else here put together. */
  li.race{position:relative;grid-template-columns:44px 1fr;gap:11px;
    padding:11px 50px 11px 14px;align-items:start}
  .datebox{padding-right:9px}
  .datebox .d{font-size:20px}
  .datebox .m{font-size:9.5px;margin-top:2px}
  .datebox .wd{font-size:9px;margin-top:2px}
  .datebox .yr{font-size:9.5px;margin-top:3px}
  .rname{font-size:15px;margin-bottom:5px;gap:6px}
  .rmeta{font-size:12px;gap:4px 9px}
  .region-tag{display:none}
  .ractions{position:absolute;top:9px;right:10px;grid-column:auto;padding-top:0;border-top:0}
  .addcal{padding:7px 9px;font-size:0;gap:0}
  .addcal::before{content:"\FF0B";font-size:15px;line-height:1}
  .month{font-size:14px;padding:8px 0}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
