/* ==========================================================================
   TechSignal — style.css
   Direction: "instrument panel". Prose in a grotesque, every number in a
   monospace with tabular figures, because in this subject's world numbers are
   readouts. No web fonts, no external assets, no build step.
   ========================================================================== */

:root {
  /* Panel neutrals — cool anodised grey, deliberately not cream */
  --paper:      #e9edf1;
  --surface:    #ffffff;
  --surface-2:  #f3f6f9;
  --surface-3:  #e7ecf2;
  --rule:       #d6dee7;
  --rule-2:     #bcc8d4;
  --ink:        #101720;
  --ink-2:      #4c5a6b;
  /* 5.44:1 on --surface, 4.58:1 on the darkest panel fill. The previous #8494a6
     measured 3.10:1 on white and 2.61:1 on --surface-3, and it is the colour of
     nearly every 9.5-11.5px readout on the site, none of which qualify for the
     large-text exemption. */
  --ink-3:      #5b6b80;

  /* The boundary of an interactive control, as distinct from --rule-2, which is
     the decorative hairline. WCAG 1.4.11 asks for 3:1 on anything that identifies
     a control, and for a text input or an icon-only button the border IS the
     affordance. Kept as its own token so the ten decorative uses of --rule-2
     (dashed empty states, section rules, separators) keep their light weight. */
  --control-bd: #7a8899;

  /* Brand */
  --signal:     #0a6c8a;
  --signal-ink: #085570;
  --signal-bg:  #e2f0f5;

  /* Channels — a four-pen set, spaced like traces on a chart recorder */
  --ch-ai:         #5a34d6;
  --ch-ai-bg:      #eee9fd;
  /* #0b87a8 measured 3.61:1 as chip text on --ch-tech-bg. Darkened one step to
     4.60:1; the hue is unchanged so the four-pen set still reads as a set. */
  --ch-tech:       #0a7590;
  --ch-tech-bg:    #e0f2f7;
  --ch-mining:     #b0521a;
  --ch-mining-bg:  #fbeee3;
  --ch-eng:        #2f6e4a;
  --ch-eng-bg:     #e4f1ea;

  /* Proximity — a separate warm-to-cool urgency system.
     Each of these carries white text when its tier chip is pressed AND appears as
     text on its own -bg pair, so both readings have to clear 4.5:1. --t-month
     (was #b95d0b) failed the second at 4.01:1 and --t-past (was #78859a) failed
     both, at 3.74:1 and 3.30:1. */
  --t-week:     #c81e5a;
  --t-week-bg:  #fce8ef;
  --t-month:    #a24f07;
  --t-month-bg: #fcefe1;
  --t-later:    #2c5aa8;
  --t-later-bg: #e7eefa;
  --t-past:     #5d6b7e;
  --t-past-bg:  #eef1f5;

  --focus:      #1668c4;

  --shadow-1: 0 1px 1px rgba(16, 23, 32, .04);
  --shadow-2: 0 2px 4px rgba(16, 23, 32, .06), 0 8px 20px rgba(16, 23, 32, .06);

  --font-sans: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
               "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "SF Mono", Menlo,
               Consolas, "Liberation Mono", monospace;

  /* Aliases consumed by app.js inline styles. Defined once here so the token
     names in this file stay semantic and dark mode still flows through. */
  --cat-ai:                 var(--ch-ai);
  --cat-ai-bg:              var(--ch-ai-bg);
  --cat-technology:         var(--ch-tech);
  --cat-technology-bg:      var(--ch-tech-bg);
  --cat-mining-technology:  var(--ch-mining);
  --cat-mining-technology-bg: var(--ch-mining-bg);
  --cat-engineering:        var(--ch-eng);
  --cat-engineering-bg:     var(--ch-eng-bg);
  --tier-week:      var(--t-week);
  --tier-week-bg:   var(--t-week-bg);
  --tier-month:     var(--t-month);
  --tier-month-bg:  var(--t-month-bg);
  --tier-later:     var(--t-later);
  --tier-later-bg:  var(--t-later-bg);
  --tier-past:      var(--t-past);
  --tier-past-bg:   var(--t-past-bg);
  --accent:         var(--signal);

  --r-sm: 3px;
  --r:    6px;
  --r-lg: 10px;
  --sidebar-w: 244px;
  --topbar-h:  60px;
}

/* Dark palette. Driven by the data-theme attribute on <html>, which the inline
   boot script in each page's <head> sets to "light" or "dark" before first paint
   — it resolves the system preference itself. Deliberately NOT a
   prefers-color-scheme block: a media query cannot be overridden by a toggle,
   and duplicating the palette into two selectors is how token sets drift apart.
   One palette, one selector. */
:root[data-theme="light"] { color-scheme: light; }

:root[data-theme="dark"] {
    color-scheme: dark;
    --paper:      #0b1118;
    --surface:    #121a23;
    --surface-2:  #18222d;
    --surface-3:  #1e2937;
    --rule:       #253243;
    --rule-2:     #35455a;
    --ink:        #e6edf4;
    --ink-2:      #a0b0c2;
    /* Was #6d7f93 — 4.26:1 on --surface and 3.91:1 on --surface-2, both short. */
    --ink-3:      #8194a9;
    --control-bd: #687b94;

    --signal:     #3fb6d4;
    --signal-ink: #7fd2e8;
    --signal-bg:  #0d2c38;

    --ch-ai:         #a78bfa;
    --ch-ai-bg:      #241d3f;
    --ch-tech:       #45c2dd;
    --ch-tech-bg:    #0c2c36;
    --ch-mining:     #e0925a;
    --ch-mining-bg:  #331f10;
    --ch-eng:        #6ec292;
    --ch-eng-bg:     #122c1f;

    --t-week:     #f7699b;
    --t-week-bg:  #34131f;
    --t-month:    #f0913f;
    --t-month-bg: #331f0e;
    --t-later:    #7ba6f0;
    --t-later-bg: #16213a;
    --t-past:     #8b9aad;
    --t-past-bg:  #1a232e;

    --focus:      #6aa9ff;

    --shadow-1: 0 1px 1px rgba(0, 0, 0, .3);
    --shadow-2: 0 2px 4px rgba(0, 0, 0, .35), 0 8px 22px rgba(0, 0, 0, .35);
}

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scrollbar-gutter: stable;
  /* The UA's blue-grey flash is replaced by the explicit :active states below, so
     that press feedback is the same colour system as hover rather than a system
     tint layered over it. */
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p, ul, figure { margin: 0; }
ul { list-style: none; padding: 0; }
a { color: inherit; }
input, button, select { font: inherit; }

/* Removes the 300ms double-tap-zoom wait Safari applies to every tap target.
   Page zoom by pinch is untouched. */
a, button, label, summary, select, input, .tick { touch-action: manipulation; }

/* Widows on a two-line heading are the common case at these measures. */
h1, h2, h3, .card-title, .empty strong, .why-label { text-wrap: balance; }
p, .lede, .card-desc, .why-body, .faq-a { text-wrap: pretty; }

/* Anything a deep link, a :target or a focus scroll can land on has to clear the
   sticky topbar and the sticky controls bar underneath it. */
.card, .month-head, .feed-split, .story-block, .faq details {
  scroll-margin-top: calc(var(--topbar-h) + 84px);
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* First thing in the tab order on every page. The feed page puts roughly eighty
   stops — the facets, then one per trace tick — between the topbar and the first
   card, which is the whole reason this exists. */
.skip-link {
  position: absolute;
  left: 8px;
  top: 8px;
  z-index: 100;
  padding: 9px 14px;
  border-radius: var(--r-sm);
  background: var(--signal);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform .12s;
}
.skip-link:focus-visible { transform: none; }
:root[data-theme="dark"] .skip-link { color: var(--paper); }

/* The scroll targets the skip link and the trace point at. Neither should show a
   focus ring of its own — the ring belongs to the control that was activated. */
#feed:focus, #main:focus { outline: none; }

/* Numbers are readouts, never prose. */
.num, .card-date .dnum, .facet-count, .tier-chip .n, .stat-value, .month-n {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Available to assistive tech and to crawlers, absent from the visual design.
   Used for the feed page's h1: the page announces itself through layout and the
   topbar, so a visible heading would be redundant furniture, but a document with
   no h1 at all is a hole for screen readers and for indexing alike. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- Top bar ----------------------------------------------------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}

.topbar-inner {
  max-width: 1360px;
  margin: 0 auto;
  min-height: var(--topbar-h);
  /* The bar is full-bleed and sticky, so in landscape on a notched phone its
     content would otherwise sit under the cutout. */
  padding: 9px max(20px, env(safe-area-inset-right)) 9px max(20px, env(safe-area-inset-left));
  /* Grid, not flex + space-between: under space-between the nav's x position was
     a function of its neighbours' widths, so it slid sideways whenever they
     changed — between pages (index.html carries .topbar-meta, the prose pages do
     not; the brand string differs too) and on index.html itself the moment app.js
     swapped "Loading..." for the real timestamp. A fixed auto centre column pins
     the nav to the bar's midpoint regardless of what flanks it. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
}

/* The 1fr tracks are equal, so a side wider than its share overflows into the
   centre column instead of pushing it. Clipping is the safe failure here: the
   nav never moves, and the brand loses its tail rather than sitting on top of a
   link. */
.topbar-inner > .brand        { justify-self: start; min-width: 0; overflow: hidden; }
.topbar-inner > .topbar-right { justify-self: end;   min-width: 0; }

.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; min-width: 0; }

.brand-mark {
  display: grid;
  place-items: center;
  width: 34px; height: 26px;
  flex: none;
  color: var(--signal);
}

.brand-text { display: flex; flex-direction: column; min-width: 0; }

.brand-name {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.025em;
  line-height: 1.15;
}

.tagline {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .04em;
  color: var(--ink-3);
  line-height: 1.4;
}
.tagline b { font-weight: 600; color: var(--ink-2); }

/* Shrinkable, not `flex: none`. The right column is a 1fr grid track, and a flex
   child that refuses to shrink pushes its siblings straight off the page edge
   rather than out of the track — which is what put a horizontal scrollbar on
   every desktop width. The readout is the one thing here that can lose
   characters, so it is the one that gives. */
.topbar-meta {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-3);
  text-align: right;
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
}
.meta-updated {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.meta-updated .dot { margin: 0 6px; opacity: .5; }
.meta-updated strong { color: var(--ink); font-weight: 600; }

/* One shard failed while its siblings loaded. Rare, but the feed looks complete
   when it happens, so it has to be said out loud rather than merely logged. */
.meta-warn { color: var(--t-week); font-weight: 600; cursor: help; }

/* ---------- Region switcher ---------------------------------------------

   Not a filter. It picks which events/<region>.json the page downloads, which
   is why it sits with the site controls rather than in the sidebar. Built
   entirely from existing tokens, so it inherits light and dark for free.
   ------------------------------------------------------------------------ */

.region { position: relative; flex: none; }

.region-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 31px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--control-bd);
  border-radius: var(--r);
  padding: 6px 10px;
  white-space: nowrap;
}
.region-btn:hover { border-color: var(--signal); color: var(--signal); }
.region-btn:active { background: var(--surface-3); }
.region-btn[aria-expanded="true"] { border-color: var(--signal); }
.region-btn .pin { color: var(--signal); flex: none; }
.region-btn .caret { color: var(--ink-3); flex: none; transition: transform .15s ease; }
.region-btn[aria-expanded="true"] .caret { transform: rotate(180deg); }

/* `right: 0` is the PREFERRED alignment, not a guaranteed one. It is correct only
   while the trigger sits at the right end of a wide bar. Once .topbar-inner wraps,
   the region control becomes the leftmost thing on its row, and a 270px panel
   whose right edge is pinned to an 80px chip at x=14 starts at x=-176 — off the
   left of the screen. wireRegionSwitcher measures on open and slides the panel
   back inside the viewport; these rules are what it starts from, and what a
   no-JS render falls back to.

   Width is capped rather than floored (it was `min-width: 270px`, which wins over
   max-width in the cascade and so could not be reined in on a narrow phone). */
.region-menu {
  position: absolute;
  top: calc(100% + 7px);
  right: 0;
  z-index: 60;
  width: min(270px, calc(100vw - 24px));
  /* Landscape on a phone leaves ~390px of height for a ~400px list. Scroll it
     rather than let it run off the bottom; contain stops the page scrolling
     underneath once it hits the end. JS sets the exact figure from the trigger. */
  max-height: calc(100dvh - 120px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  padding: 5px;
}
.region-menu[hidden] { display: none; }

.region-group {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 9px 10px 5px;
}

/* An <a href="?loc=vic"> for a live region and a plain <span> for one with no
   routine yet. It was a <button> inside role="menu", which meant the control that
   most obviously navigates could not be middle-clicked, Cmd-clicked or opened in
   a new tab — and the ARIA menu pattern it declared (arrow keys, roving tabindex,
   focus moved into the menu) was never implemented. A link needs none of that. */
.region-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 34px;
  padding: 7px 10px;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: var(--r);
  font-family: inherit;
  font-size: 13.5px;
  color: var(--ink);
  text-align: left;
  text-decoration: none;
}
a.region-opt:hover { background: var(--surface-2); }
a.region-opt:active { background: var(--surface-3); }
.region-opt[aria-current="true"] {
  background: var(--signal-bg);
  color: var(--signal-ink);
  font-weight: 600;
}
span.region-opt { cursor: default; }
.region-opt .name { flex: 1; min-width: 0; }
.region-opt .n {
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  background: var(--surface-3);
  border-radius: 100px;
  padding: 1px 7px;
  flex: none;
}
.region-opt[aria-current="true"] .n { background: var(--surface); color: var(--signal-ink); }

/* A region with no routine yet. Listed and dimmed rather than hidden: an
   honest "soon" beats a menu implying Australia is only the states we sweep. */
.region-opt.is-empty .name { color: var(--ink-3); }
.region-opt.is-empty .n { color: var(--ink-3); opacity: .75; }

.region-sep { border: 0; border-top: 1px solid var(--rule); margin: 5px 0; }

.region-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px 6px;
  font-size: 12.5px;
}
.region-foot span {
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: 11px;
}
.region-foot a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: var(--signal);
  text-decoration: none;
  font-weight: 600;
}
.region-foot a:hover { text-decoration: underline; }

/* Full name on a roomy bar, state code once space is tight. Both are in the
   markup so the swap is a media query rather than a resize listener. */
.region-abbr { display: none; }

/* Shown above the feed when the chosen state contributed nothing and every card
   below it is national or interstate. Deliberately not styled as an error — the
   events are real and useful, they are just not from here. */
.feed-note {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--t-month);
  background: var(--surface);
  border-radius: var(--r);
  padding: 12px 15px;
  margin-bottom: 16px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-2);
}
.feed-note strong { color: var(--ink); font-weight: 650; }
.feed-note a { color: var(--signal); font-weight: 600; }

/* The zone abbreviation is not decoration. The site lists five states, WA never
   observes daylight saving and three of the others do, so a bare clock time is
   unreadable to anyone outside the event's own state. */
.card-time { white-space: nowrap; }
.card-time b {
  font-family: var(--font-mono);
  font-size: .92em;
  font-weight: 600;
  color: var(--ink-3);
  letter-spacing: .03em;
}
.card-time[title] { cursor: help; }

/* The venue's own local time, kept visible whenever the headline has been
   converted to the reader's zone. For an in-person event this is the number that
   governs when you walk in the door — a Perth reader who flew to a Brisbane
   conference on "5:00 pm your time" would arrive two hours early. */
.venue-time {
  display: block;
  width: 100%;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .02em;
  color: var(--ink-3);
  margin-top: 1px;
}

.cal-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  min-height: 26px;
  color: var(--signal);
  background: transparent;
  border: 1px solid var(--control-bd);
  border-radius: var(--r);
  padding: 3px 9px;
  cursor: pointer;
  white-space: nowrap;
}
.cal-btn:hover { border-color: var(--signal); background: var(--signal-bg); }
.cal-btn:active { border-color: var(--signal); background: var(--signal-bg); color: var(--signal-ink); }
.cal-btn svg { flex: none; opacity: .85; }

/* The divider between a region's own events and the national file every region
   loads. It has to read as a change of subject, not another month heading —
   that confusion is the whole reason the section exists. */
.feed-split {
  margin: 30px 0 4px;
  padding-top: 18px;
  border-top: 2px solid var(--rule-2);
}
.feed-split h2 {
  margin: 0 0 3px;
  font-size: 14px;
  font-weight: 650;
  letter-spacing: -.01em;
  color: var(--ink);
}
.feed-split p {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .03em;
  color: var(--ink-3);
}

/* ---------- Layout ------------------------------------------------------ */

.layout {
  max-width: 1360px;
  margin: 0 auto;
  padding: 18px 20px 0;
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}

/* ---------- Sidebar ----------------------------------------------------- */

/* max-height is insurance, not decoration: a sticky element taller than the space
   below where it pins has no way to scroll to its own bottom, which would put
   "Reset filters" permanently off screen on a short window. */
.sidebar {
  position: sticky;
  top: calc(var(--topbar-h) + 18px);
  max-height: calc(100dvh - var(--topbar-h) - 36px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sidebar-inner {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  overflow: hidden;
}

.facet { border-bottom: 1px solid var(--rule); padding: 12px 13px 13px; }
.facet:last-of-type { border-bottom: 0; }

.facet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}

.facet-head h2 {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.facet-actions { display: flex; align-items: center; gap: 5px; flex: none; }
.facet-actions .sep { color: var(--rule-2); font-size: 10px; }

/* Negative margin keeps the visual position of a bare text button while giving it
   a hit area that clears the 24px minimum. */
.linkbtn {
  background: none; border: 0;
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 0 6px;
  margin: -4px -6px;
  border-radius: var(--r-sm);
  font-size: 11px;
  color: var(--ink-3);
  cursor: pointer;
}
.linkbtn:hover { color: var(--signal); }
.linkbtn:active { color: var(--signal); background: var(--surface-2); }

.facet-list { display: flex; flex-direction: column; }

.facet-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 30px;
  padding: 5px 7px;
  margin: 0 -7px;
  border-radius: var(--r-sm);
  cursor: pointer;
  font-size: 13.5px;
  user-select: none;
}
.facet-row:hover { background: var(--surface-2); }
.facet-row:active { background: var(--surface-3); }
.facet-row input { margin: 0; flex: none; accent-color: var(--signal); width: 14px; height: 14px; }

.facet-swatch {
  width: 3px; height: 13px;
  border-radius: 2px;
  flex: none;
  background: var(--swatch, var(--ink-3));
}

.facet-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.facet-count {
  flex: none;
  font-size: 11px;
  color: var(--ink-3);
  min-width: 20px;
  text-align: right;
}
/* A zero-count facet is still a live checkbox, not a disabled one, so it cannot be
   dimmed to the point of illegibility — .38 put the label at 2.42:1. The count is
   what carries "nothing here", so that is what fades. */
.facet-row.is-zero { opacity: .72; }
.facet-row.is-zero .facet-count { opacity: .8; }

.sidebar-foot { padding: 11px 13px; border-top: 1px solid var(--rule); background: var(--surface-2); }

/* ---------- Buttons ----------------------------------------------------- */

.btn {
  font-size: 12.5px;
  font-weight: 550;
  min-height: 32px;
  padding: 7px 13px;
  border-radius: var(--r-sm);
  border: 1px solid var(--control-bd);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color .12s, color .12s, background .12s;
}
.btn:hover { border-color: var(--signal); color: var(--signal); }
.btn:active { background: var(--signal-bg); border-color: var(--signal); }
.btn-quiet { color: var(--ink-2); }
.btn-block { display: block; width: 100%; }

/* ---------- Controls ----------------------------------------------------- */

.controls {
  position: sticky;
  top: var(--topbar-h);
  z-index: 30;
  background: var(--paper);
  padding: 6px 0 10px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.controls-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

/* Search */
.search {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 220px;
  background: var(--surface);
  border: 1px solid var(--control-bd);
  border-radius: var(--r-sm);
  transition: border-color .12s, box-shadow .12s;
}
.search:focus-within {
  border-color: var(--signal);
  box-shadow: 0 0 0 3px var(--signal-bg);
}
.search-icon { position: absolute; left: 10px; color: var(--ink-3); pointer-events: none; }
.search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font-size: 13.5px;
  padding: 8px 30px 8px 32px;
  border-radius: inherit;
}
.search input:focus { outline: none; }
.search input::placeholder { color: var(--ink-3); }
.search input::-webkit-search-cancel-button { display: none; }
.search-clear {
  position: absolute; right: 4px;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border: 0; background: none;
  color: var(--ink-3);
  font-size: 17px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  border-radius: var(--r-sm);
}
.search-clear:hover { color: var(--ink); }
.search-clear:active { background: var(--surface-3); color: var(--ink); }

/* Proximity chips */
.tier-chips { display: flex; flex-wrap: wrap; gap: 5px; }

.tier-chip {
  font-size: 12px;
  font-weight: 550;
  min-height: 32px;
  padding: 6px 11px;
  border-radius: var(--r-sm);
  border: 1px solid var(--control-bd);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  transition: border-color .12s, color .12s, background .12s;
}
.tier-chip .n { font-size: 11px; color: var(--ink-3); }
.tier-chip:hover { border-color: var(--signal); color: var(--ink); background: var(--surface-2); }
.tier-chip:active { background: var(--surface-3); }

.tier-chip[aria-pressed="true"] { color: #fff; border-color: transparent; }
/* Was .75, which put the count at 2.84:1 on the Past chip and 3.27:1 on This
   Month. The number is the whole point of the chip, so it gets a legible tint. */
.tier-chip[aria-pressed="true"] .n { color: rgba(255, 255, 255, .9); }
.tier-chip[data-tier="this-week"][aria-pressed="true"]  { background: var(--t-week); }
.tier-chip[data-tier="this-month"][aria-pressed="true"] { background: var(--t-month); }
.tier-chip[data-tier="upcoming"][aria-pressed="true"]   { background: var(--t-later); }
.tier-chip[data-tier="past"][aria-pressed="true"]       { background: var(--t-past); }
.tier-chip[data-tier="all"][aria-pressed="true"]        { background: var(--ink); }
.tier-chip[data-tier="all"][aria-pressed="true"] .n     { color: rgba(255, 255, 255, .7); }

:root[data-theme="dark"] .tier-chip[aria-pressed="true"] { color: var(--paper); }
:root[data-theme="dark"] .tier-chip[aria-pressed="true"] .n { color: rgba(11, 17, 24, .85); }
:root[data-theme="dark"] .tier-chip[data-tier="all"][aria-pressed="true"] { background: var(--ink); }
:root[data-theme="dark"] .tier-chip[data-tier="all"][aria-pressed="true"] .n { color: rgba(11, 17, 24, .8); }

.control-selects { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }

.select-wrap {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  background: var(--surface);
  border: 1px solid var(--control-bd);
  border-radius: var(--r-sm);
  padding: 3px 7px 3px 9px;
}
.select-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--ink-3);
}
.select-wrap select {
  font-size: 12.5px;
  border: 0;
  /* Explicit rather than `none`: Windows renders an unstyled <select>'s option
     list from the UA's own palette, and only an explicit pair guarantees the
     popup follows the page's theme instead of the system's. */
  background-color: var(--surface);
  color: var(--ink);
  padding: 4px 0;
  cursor: pointer;
}
.select-wrap select:focus { outline: none; }
/* The outline above is removed on the control itself, so the replacement has to
   be as loud as the one .search gets — a 1px border tint was not. */
.select-wrap:focus-within {
  border-color: var(--signal);
  box-shadow: 0 0 0 3px var(--signal-bg);
}

/* ---------- Signature: the signal trace ---------------------------------- */

.trace {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  padding: 10px 13px 7px;
  margin-bottom: 12px;
}

.trace-cap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}
.trace-title {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.trace-hint {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--ink-3);
}

.trace-plot {
  position: relative;
  height: 42px;
  border-bottom: 1px solid var(--rule-2);
  /* Faint graticule, like a chart recorder */
  background-image: repeating-linear-gradient(
    to right, var(--rule) 0 1px, transparent 1px 25%);
  background-size: 100% 100%;
}

/* Pointer-only by design. One tick per event meant one tab stop per event — about
   fifty of them between the controls and the first card — for a control that only
   scrolls to a card already listed below. The plot is aria-hidden and the ticks
   are out of the tab order; the figcaption carries the same summary in words. */
.tick {
  position: absolute;
  bottom: 0;
  width: 3px;
  margin-left: -1.5px;
  border-radius: 2px 2px 0 0;
  background: var(--tick-c, var(--ink-3));
  cursor: pointer;
  border: 0;
  padding: 0;
  transition: transform .12s, opacity .12s;
  transform-origin: bottom center;
}
.tick:hover { transform: scaleX(2.2); opacity: 1; z-index: 2; }

.trace-today {
  position: absolute;
  top: 0; bottom: 0;
  width: 1px;
  background: var(--signal);
}
.trace-today::after {
  content: "";
  position: absolute;
  top: -3px; left: -2.5px;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--signal);
}

.trace-axis {
  position: relative;
  height: 14px;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.axis-mark { position: absolute; top: 0; transform: translateX(-2px); white-space: nowrap; }

/* Jumped-to card, after clicking a tick */
.card.is-flash {
  border-color: var(--signal);
  box-shadow: 0 0 0 3px var(--signal-bg), var(--shadow-2);
}

/* ---------- Stat readouts ------------------------------------------------ */

.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 16px;
}

.stat {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  padding: 9px 12px 10px;
  position: relative;
  overflow: hidden;
}
.stat::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--stat-accent, var(--rule-2));
}
.stat-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: .11em;
  color: var(--ink-3);
}
.stat-value {
  font-size: 23px;
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.2;
  margin-top: 1px;
}
.stat-sub { font-size: 11px; color: var(--ink-3); }

/* ---------- Feed --------------------------------------------------------- */

.month-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 20px 0 9px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.month-head:first-child { margin-top: 0; }
.month-head::after { content: ""; flex: 1; height: 1px; background: var(--rule); }
.month-head .month-n { font-size: 10px; letter-spacing: 0; color: var(--ink-3); }

.cards { display: flex; flex-direction: column; gap: 7px; }

.card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  padding: 13px 15px 12px 16px;
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 15px;
  transition: box-shadow .14s, border-color .14s, transform .14s;
}
.card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  border-radius: var(--r) 0 0 var(--r);
  background: var(--card-accent, var(--rule-2));
}
.card:hover {
  box-shadow: var(--shadow-2);
  border-color: var(--rule-2);
  transform: translateY(-1px);
}
.card:focus-within { border-color: var(--signal); }

/* A <time datetime> element, so the date is machine-readable and a screen reader
   reads one date rather than three loose fragments. */
.card-date { display: block; text-align: center; padding-right: 13px; border-right: 1px solid var(--rule); }
.card-date > span { display: block; }
.card-date .dnum { font-size: 21px; font-weight: 600; line-height: 1.05; letter-spacing: -.03em; }
.card-date .dmon {
  font-family: var(--font-mono);
  font-size: 9.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-2);
}
.card-date .dday {
  font-family: var(--font-mono);
  font-size: 9.5px; color: var(--ink-3); margin-top: 3px;
  text-transform: uppercase; letter-spacing: .08em;
}

.card-body { min-width: 0; }

/* Titles and venues are scraped from organiser pages, so they arrive at any
   length and occasionally as one unbroken token. */
.card-title {
  font-size: 15px; font-weight: 620; letter-spacing: -.012em; line-height: 1.35;
  overflow-wrap: anywhere;
}
.card-title a { text-decoration: none; }
.card-title a:hover { color: var(--signal); }
.card-title a:active { color: var(--signal-ink); }
.card-title .ext { font-size: 10px; color: var(--ink-3); margin-left: 4px; }

.card-chips { display: flex; flex-wrap: wrap; gap: 5px; margin: 7px 0 7px; align-items: center; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  font-size: 10.5px;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: var(--r-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: .01em;
}

.chip-tier {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: .08em;
  font-size: 9.5px;
  padding: 3px 7px;
  background: var(--tier-bg);
  color: var(--tier-c);
}

.chip-cat { background: var(--cat-bg); color: var(--cat-c); }
.chip-cat .cdot { width: 5px; height: 5px; border-radius: 1px; background: var(--cat-c); flex: none; }

/* The one chip carrying free text — access can arrive as
   "Paid · $1,500 single day / $2,500 both days (plus GST)". Clipping a price is
   worse than spending a second line on it, so this chip alone opts out of the
   set's nowrap. */
.chip-access {
  background: var(--surface-3);
  color: var(--ink-2);
  white-space: normal;
  overflow-wrap: anywhere;
}
.chip-access.is-gov  { color: var(--ch-mining); background: var(--ch-mining-bg); }
.chip-access.is-paid { color: var(--ch-tech);   background: var(--ch-tech-bg); }

.chip-scope  { background: var(--signal-bg); color: var(--signal-ink); }
.chip-format { background: transparent; color: var(--ink-3); border: 1px dashed var(--rule-2); }

/* Descriptions are scraped organiser copy with no length contract. Three lines is
   enough to judge relevance; the title links through for the rest. */
.card-desc {
  font-size: 13px; color: var(--ink-2); line-height: 1.5;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  overflow-wrap: anywhere;
}

.card-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px 9px;
  margin-top: 9px;
  padding-top: 8px;
  border-top: 1px solid var(--rule);
  font-size: 11.5px;
  color: var(--ink-3);
}
/* Deliberately only the figures: the meta line is prose with a date in it, not a
   readout, so it keeps the sans face it has always had. */
.card-meta time { font-variant-numeric: tabular-nums; }
.card-meta .venue { color: var(--ink-2); overflow-wrap: anywhere; }
.card-meta .org-label {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--surface-3);
  border-radius: var(--r-sm);
  padding: 1px 6px;
}
.card-meta .src-link {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  margin-left: auto;
  color: var(--ink-3);
  text-decoration: none;
  font-weight: 550;
}
.card-meta .src-link:hover { color: var(--signal); }
.card-meta .src-link:active { color: var(--signal-ink); }

/* Archived events: present, deliberately quieter */
.card.is-past { background: var(--surface-2); border-style: dashed; }
.card.is-past::before { opacity: .35; }
.card.is-past .card-title a { color: var(--ink-2); }
.card.is-past .card-title a:hover { color: var(--signal); }
.card.is-past .card-date .dnum { color: var(--ink-2); }
.card.is-past .chip-cat { opacity: .7; }

mark {
  background: var(--signal-bg);
  color: var(--signal-ink);
  border-radius: 2px;
  padding: 0 1px;
}

/* ---------- Empty / loading ---------------------------------------------- */

.empty {
  background: var(--surface);
  border: 1px dashed var(--rule-2);
  border-radius: var(--r);
  padding: 40px 24px;
  text-align: center;
  color: var(--ink-2);
  font-size: 13.5px;
}
.empty strong { display: block; color: var(--ink); font-size: 15px; margin-bottom: 5px; font-weight: 620; }
.empty .btn { margin-top: 14px; }

/* ---------- Footer ------------------------------------------------------- */

.sitefoot {
  max-width: 1360px;
  margin: 0 auto;
  padding: 26px max(20px, env(safe-area-inset-right))
           max(34px, env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
  font-size: 11.5px;
  color: var(--ink-3);
}

/* ---------- Mobile filter toggle ----------------------------------------- */

.filter-toggle {
  display: none;
  font-size: 12.5px;
  font-weight: 600;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: calc(100% - 28px);
  min-height: 44px;
  margin: 14px 14px -6px;
  padding: 9px 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--control-bd);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.filter-toggle:active { background: var(--surface-2); }

/* While the panel is open this is the only way out of it, so it has to stay
   reachable. Open, the facets are ~511px tall and push the feed off the bottom
   of the screen; without this you had to scroll back up to find the control you
   came in through. Sticky only while expanded, so nothing is pinned the rest of
   the time. Deliberately NOT dismissed by an outside tap: the panel sits in the
   flow rather than over it, so "outside" is a card, and closing the filters on
   the way to opening an event would be its own bug. */
.filter-toggle[aria-expanded="true"] {
  position: sticky;
  top: 0;
  z-index: 35;
  border-color: var(--signal);
  color: var(--signal);
}
.filter-toggle-count {
  font-family: var(--font-mono);
  font-size: 10.5px;
  background: var(--signal);
  color: #fff;
  border-radius: 20px;
  padding: 1px 6px;
}
.filter-toggle-count:empty { display: none; }

/* ---------- Top navigation ----------------------------------------------- */

/* The topbar is the instrument's channel selector: a row of labels where the
   live one is underlined flush with the panel edge, the way a selected input
   is marked on a front panel. */

.topbar-right {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
}

/* The nav spans the full bar height (the negative margin cancels .topbar-inner's
   vertical padding) so the active underline can be derived from --topbar-h rather
   than hand-tuned — a literal offset breaks the moment the bar height or the link
   padding changes. The links keep their own fixed height, so the rounded hover
   stays a pill instead of becoming a full-height slab. */
.nav {
  --nav-link-h: 29px;
  display: flex;
  align-items: center;
  align-self: stretch;
  margin: -9px 0;
  gap: 2px;
}

.nav a {
  position: relative;
  display: grid;
  place-items: center;
  height: var(--nav-link-h);
  padding: 0 11px;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink-2);
  text-decoration: none;
  border-radius: var(--r-sm);
  white-space: nowrap;
  transition: color .12s, background .12s;
}

.nav a:hover { color: var(--ink); background: var(--surface-2); }
.nav a:active { background: var(--surface-3); }

.nav a[aria-current="page"] { color: var(--signal); }

/* Sits on the topbar's own bottom rule: half the slack left over above and below
   a centred link, so it tracks --topbar-h instead of a magic number. */
.nav a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 11px;
  right: 11px;
  bottom: calc((var(--nav-link-h) - var(--topbar-h)) / 2);
  height: 2px;
  background: var(--signal);
}

.btn-cta {
  font-size: 12.5px;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--signal);
  background: var(--signal);
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.btn-cta:hover { background: var(--signal-ink); border-color: var(--signal-ink); }
.btn-cta:active { filter: brightness(.92); }

:root[data-theme="dark"] .btn-cta { color: var(--paper); }

.theme-toggle {
  display: grid;
  place-items: center;
  width: 31px;
  height: 31px;
  flex: none;
  padding: 0;
  border: 1px solid var(--control-bd);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
  transition: color .12s, border-color .12s, background .12s;
}
.theme-toggle:hover { color: var(--ink); border-color: var(--signal); }
.theme-toggle:active { background: var(--surface-3); }
.theme-toggle svg { display: block; }
.theme-toggle .icon-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun  { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: block; }

/* ---------- Prose pages (About / Contact / Subscribe) --------------------- */

.page {
  max-width: 720px;
  margin: 0 auto;
  padding: 40px max(20px, env(safe-area-inset-right)) 8px max(20px, env(safe-area-inset-left));
}

.page-head { margin-bottom: 30px; }

/* Mono eyebrow: in this design mono means "readout", so it labels the panel
   you are looking at rather than decorating the heading. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 13px;
}
.eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--rule);
}

.page h1 {
  margin: 0;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -.028em;
  line-height: 1.15;
}

.lede {
  margin: 14px 0 0;
  font-size: 16.5px;
  line-height: 1.55;
  color: var(--ink-2);
}
.lede a { color: var(--signal); text-underline-offset: 2px; }

.prose { font-size: 15px; line-height: 1.65; }
.prose h2 {
  margin: 34px 0 10px;
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -.015em;
}
.prose h2:first-child { margin-top: 0; }
.prose p { margin: 0 0 14px; color: var(--ink-2); }
.prose strong { color: var(--ink); font-weight: 600; }
.prose a { color: var(--signal); text-underline-offset: 2px; }
.prose ul { margin: 0 0 14px; padding-left: 19px; color: var(--ink-2); }
.prose li { margin-bottom: 7px; }
.prose li::marker { color: var(--ink-3); }

.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  padding: 18px 20px;
  margin: 0 0 14px;
}
.panel h2 { margin-top: 0; }
.panel > :last-child { margin-bottom: 0; }

/* Reasons-to-get-in-touch list: each row is a labelled input on the panel. */
.reasons { list-style: none; margin: 0; padding: 0; }
.reasons li {
  display: grid;
  grid-template-columns: 106px minmax(0, 1fr);
  gap: 14px;
  padding: 12px 0;
  border-top: 1px solid var(--rule);
  margin: 0;
}
.reasons li:first-child { border-top: 0; padding-top: 0; }
.reasons li:last-child { padding-bottom: 0; }
.reasons dt, .reasons .r-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--signal);
  padding-top: 3px;
}
.reasons .r-body { color: var(--ink-2); }

.mailto {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
  color: var(--signal);
  text-decoration: none;
  padding: 9px 14px;
  min-height: 44px;
  border: 1px solid var(--control-bd);
  border-radius: var(--r-sm);
  background: var(--surface);
  transition: border-color .12s, background .12s;
}
.mailto:hover { border-color: var(--signal); background: var(--signal-bg); }
.mailto:active { background: var(--signal-bg); border-color: var(--signal-ink); }
.mailto .arrow { transition: transform .12s; }
.mailto:hover .arrow { transform: translateX(2px); }

.note {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-3);
  border-left: 2px solid var(--rule-2);
  padding-left: 12px;
  margin: 0 0 14px;
}

/* Subscribe placeholder. The flatline is the honest readout: nothing shipping
   yet. It reuses the feed's trace vocabulary rather than inventing a new one. */
.soon {
  text-align: center;
  padding: 22px 20px 26px;
}
.soon .eyebrow { justify-content: center; }
.soon .eyebrow::after { display: none; }
.soon .prose { text-align: left; }
.soon .flatline {
  display: block;
  width: 100%;
  height: 40px;
  margin: 0 0 18px;
  color: var(--rule-2);
}
.soon h1 { font-size: 26px; }
.soon .lede { margin-left: auto; margin-right: auto; max-width: 46ch; }
.soon .status {
  display: inline-block;
  margin-top: 20px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
  border: 1px dashed var(--rule-2);
  border-radius: var(--r-sm);
  padding: 7px 13px;
}

/* ---------- Responsive ---------------------------------------------------- */

/* ---------- Story pages (About / Contact) --------------------------------- */

/* Entrance stagger. Each block declares its own step with --d and shares one
   keyframe. The end state is the readable state and the animation is `backwards`
   only for the delay, so if it never runs — reduced motion, an old engine, a
   failed paint — the page is simply there, fully legible. */
@keyframes ts-rise {
  from { opacity: 0; transform: translateY(11px); }
  to   { opacity: 1; transform: none; }
}
.reveal {
  animation: ts-rise .55s cubic-bezier(.22, .61, .36, 1) backwards;
  animation-delay: calc(var(--d, 0) * 80ms);
}

.story-hero { margin-bottom: 8px; }
.story-hero h1 { font-size: 34px; max-width: 19ch; }
/* The one coloured word in the sentence, carrying the brand rather than a
   decorative italic. */
.story-hero h1 em { font-style: normal; color: var(--signal); }
.story-hero .lede { max-width: 58ch; }

/* The brand mark drawn at page width: the trace resolves from flat noise into a
   single clean peak, which is the whole proposition in one line. */
.hero-trace {
  display: block;
  width: 100%;
  height: 46px;
  margin: 26px 0 30px;
  overflow: visible;
}
.hero-trace .t-base { stroke: var(--rule); }
.hero-trace .t-peak { stroke: var(--signal); }
.hero-trace .t-draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: ts-draw 1.1s cubic-bezier(.4, 0, .2, 1) .2s forwards;
}
/* The peak starts once the noise has been laid down, so it reads as a resolution
   of that noise rather than a second line racing it. */
.hero-trace .t-peak { animation-delay: 1.05s; animation-duration: .9s; }
@keyframes ts-draw { to { stroke-dashoffset: 0; } }

/* Section heads on the prose pages are quiet. Here they get the mono index the
   feed uses for readouts, so the page scans as a panel and not as an essay. */
.story-block { margin: 0 0 30px; }
.story-block > h2 {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 12px;
  font-size: 19px;
  font-weight: 650;
  letter-spacing: -.018em;
}
.story-block > h2::before {
  content: counter(story-step, decimal-leading-zero);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--signal);
  flex: none;
}
.page-story { counter-reset: story-step; }
.story-block { counter-increment: story-step; }

.story-block p { margin: 0 0 13px; color: var(--ink-2); font-size: 15px; line-height: 1.68; }
.story-block p:last-child { margin-bottom: 0; }
.story-block strong { color: var(--ink); font-weight: 600; }
.story-block a { color: var(--signal); text-underline-offset: 2px; }

/* Pull quote for the line the page turns on. */
.pullquote {
  margin: 18px 0;
  padding: 2px 0 2px 16px;
  border-left: 2px solid var(--signal);
  font-size: 17px;
  line-height: 1.5;
  letter-spacing: -.012em;
  color: var(--ink);
}

/* Kinds of event: a chip grid rather than a bulleted list, because the point is
   breadth at a glance, not sequence. */
.kinds {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(166px, 1fr));
  gap: 8px;
}
.kinds li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 13px;
  font-size: 13.5px;
  font-weight: 550;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  transition: transform .16s, border-color .16s, box-shadow .16s;
}
.kinds li:hover {
  transform: translateY(-2px);
  border-color: var(--rule-2);
  box-shadow: var(--shadow-2);
}
.kinds .k-dot {
  width: 7px; height: 7px;
  flex: none;
  border-radius: 50%;
  background: currentColor;
}
/* Cycles the feed's four-pen channel set so the chips read as the same
   instrument as the event cards. */
.kinds li:nth-child(4n+1) .k-dot { color: var(--ch-ai); }
.kinds li:nth-child(4n+2) .k-dot { color: var(--ch-tech); }
.kinds li:nth-child(4n+3) .k-dot { color: var(--ch-mining); }
.kinds li:nth-child(4n)   .k-dot { color: var(--ch-eng); }

/* Closing call to action. */
.story-end {
  margin: 34px 0 6px;
  padding: 24px 22px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
}
.story-end p {
  margin: 0 0 15px;
  font-size: 16px;
  letter-spacing: -.012em;
  color: var(--ink);
}
.story-end .btn-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  padding: 10px 18px;
  border-radius: var(--r);
}
.story-end .btn-cta .arrow { transition: transform .14s; }
.story-end .btn-cta:hover .arrow { transform: translateX(3px); }

.story-note {
  margin: 22px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-3);
  border-top: 1px solid var(--rule);
  padding-top: 14px;
}
.story-note a { color: var(--signal); }

/* Reasons to write in. Four icon tiles instead of prose: the contact page's whole
   job is to be understood at a glance and then acted on, so the reasons are
   scanned, not read. Icons carry the four-pen channel set the feed already uses. */
.why-grid {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(238px, 1fr));
  gap: 12px;
}
.why-grid li {
  padding: 18px 18px 17px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  transition: transform .16s, border-color .16s, box-shadow .16s;
}
.why-grid li:hover {
  transform: translateY(-2px);
  border-color: var(--rule-2);
  box-shadow: var(--shadow-2);
}
.why-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  margin-bottom: 13px;
  border-radius: var(--r-sm);
  color: var(--ch-tech);
  background: var(--ch-tech-bg);
}
.why-icon svg { display: block; }

/* Glyph and plate come from the same token pair, so both flip correctly in dark
   mode without a second set of rules. */
.why-grid li:nth-child(4n+1) .why-icon { color: var(--ch-ai);     background: var(--ch-ai-bg); }
.why-grid li:nth-child(4n+2) .why-icon { color: var(--ch-tech);   background: var(--ch-tech-bg); }
.why-grid li:nth-child(4n+3) .why-icon { color: var(--ch-mining); background: var(--ch-mining-bg); }
.why-grid li:nth-child(4n)   .why-icon { color: var(--ch-eng);    background: var(--ch-eng-bg); }
.why-label {
  display: block;
  margin-bottom: 5px;
  font-size: 14.5px;
  font-weight: 650;
  letter-spacing: -.012em;
  color: var(--ink);
}
.why-body {
  display: block;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-2);
}

.story-end .mailto {
  font-size: 15px;
  padding: 11px 18px;
  border-radius: var(--r);
}
.story-end .mailto .arrow { transition: transform .14s; }
.story-end .mailto:hover .arrow { transform: translateX(3px); }
.story-end .end-note {
  margin: 13px 0 0;
  font-size: 12.5px;
  color: var(--ink-3);
}

/* FAQ. Collapsed by default so the page stays short, but every answer is in the
   DOM at load — <details> hides visually, it does not withhold from a crawler or
   an answer engine. The schema on this page asserts these exact Q&As are
   present, so the markup and the text have to stay in step. */
.faq { margin: 4px 0 0; border-top: 1px solid var(--rule); }
.faq details { border-bottom: 1px solid var(--rule); }
.faq summary {
  position: relative;
  list-style: none;
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
  padding: 13px 30px 13px 0;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
  transition: color .12s;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--signal); }
.faq summary::after {
  content: "";
  position: absolute;
  right: 7px; top: 50%;
  width: 8px; height: 8px;
  margin-top: -7px;
  border-right: 1.8px solid var(--ink-3);
  border-bottom: 1.8px solid var(--ink-3);
  transform: rotate(45deg);
  transition: transform .16s;
}
.faq details[open] summary::after { transform: rotate(-135deg); margin-top: -3px; }
.faq details[open] summary { color: var(--signal); }
.faq .faq-a {
  margin: 0;
  padding: 0 26px 15px 0;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-2);
}

/* Fitting the topbar's right-hand group.
   ---------------------------------------------------------------------------
   The nav is centred by an equal-1fr grid, so the right group gets exactly
   ((min(vw, 1360) - 40 padding - nav - 32 gaps) / 2) — at most 549px, because
   .topbar-inner is capped at 1360. The group wants:

     readout 294 + region 184 + Subscribe 85 + toggle 31 + 3 gaps 42  =  636

   which does not fit at ANY viewport width. That was the bug: below 1360 it
   spilled off the right edge as a page-wide horizontal scrollbar, and once the
   readout was allowed to shrink it collided with the nav instead. 1040px, the
   old breakpoint, was never going to be the answer — no number is, while all
   four sit on one row at natural width.

   So two rules, each giving up the least useful thing at that width:
     - Below 1360 the readout goes. It is the least load-bearing item on the bar
       and it already had this treatment, just at the wrong threshold.
     - At 1360 and up, where the readout is shown, the region control drops to
       its state code: 636 - 94 = 542, inside the 549 available. The full name
       is still in the dropdown, and the tagline already carries the code. */
@media (max-width: 1359px) {
  .topbar-meta { display: none; }
}

@media (min-width: 1360px) {
  .region-full { display: none; }
  .region-abbr { display: inline; }
}

@media (max-width: 1040px) {
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The tagline stops wrapping to a second line, which was making the bar taller. */
  .tagline { display: none; }
  /* The region control keeps its place on the bar — it is the one control that
     changes what the page is showing — but drops to the state code to earn it. */
  .region-full { display: none; }
  .region-abbr { display: inline; }
}

@media (max-width: 767px) {
  .layout {
    grid-template-columns: minmax(0, 1fr);
    padding: 14px max(14px, env(safe-area-inset-right)) 0 max(14px, env(safe-area-inset-left));
    gap: 14px;
  }
  .filter-toggle { display: flex; }
  .sidebar { position: static; display: none; max-height: none; overflow: visible; }
  .sidebar.is-open { display: block; }
  /* The bar wraps to three rows at this width — brand, controls, nav — which is
     roughly 130px. Pinned, that is a fifth of a phone viewport gone permanently,
     and the controls bar already goes static here for the same reason. */
  .topbar { position: static; }
  /* The nav drops to its own full-width row here, which grid columns cannot
     express, so the bar reverts to a wrapping flex row. */
  .topbar-inner {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 12px;
    padding: 8px max(14px, env(safe-area-inset-right)) 8px max(14px, env(safe-area-inset-left));
  }
  .tagline { display: none; }
  /* Cramming brand + four controls onto one 360px row shrinks every tap target
     below usable size, so the nav drops to its own row instead. */
  .topbar-meta { display: none; }
  /* On its own row the nav no longer spans the bar, so it returns to ordinary
     flow and the underline hugs the link. */
  .nav { order: 3; width: 100%; align-self: auto; margin: 0 0 0 -9px; }
  .nav a { height: auto; padding: 7px 9px; font-size: 12.5px; }
  .nav a[aria-current="page"]::after { bottom: -2px; left: 9px; right: 9px; }
  .page {
    padding: 28px max(16px, env(safe-area-inset-right)) 8px max(16px, env(safe-area-inset-left));
  }
  .page h1 { font-size: 25px; }
  .story-hero h1 { font-size: 26px; max-width: none; }
  .hero-trace { height: 36px; margin: 20px 0 24px; }
  .story-block > h2 { font-size: 17.5px; }
  .pullquote { font-size: 15.5px; }
  .kinds { grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); }
  .why-grid { grid-template-columns: minmax(0, 1fr); }
  .lede { font-size: 15.5px; }
  .reasons li { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .reasons dt, .reasons .r-label { padding-top: 0; }
  .controls { position: static; }
  /* Search and Export share one unwrapped row here, so the field's 220px floor
     plus the button was wider than a 320px phone. The field is elastic; the
     button is not. */
  .controls-row-top { flex-wrap: nowrap; }
  .search { min-width: 0; }

  /* 16px is not a style choice. iOS Safari zooms the page whenever a focused
     text field or select computes below 16px, and it does NOT zoom back out on
     blur — so tapping search left the visitor on a zoomed page that pans
     horizontally, on a site with no horizontal scroll anywhere else. Suppressing
     it with maximum-scale/user-scalable would be the other way to stop it, and
     both of those break pinch-zoom for everyone. Desktop keeps its own sizes. */
  .search input { font-size: 16px; }
  .select-wrap select { font-size: 16px; }
  .control-selects { width: 100%; }
  .select-wrap { flex: 1; min-width: 0; }
  .select-wrap select { width: 100%; }
  /* Reclaiming the first screen.
     ------------------------------------------------------------------------
     Measured before this: header 86 + Filters 44 + controls 184 + trace 95 +
     stats 328 put the first event card at y=812 on an 812px viewport. The whole
     opening screen was chrome, on a site whose entire job is answering "what is
     on". The two biggest consumers were also the two least useful on a phone.

     The trace is the larger offence: since it became pointer-only it is
     aria-hidden with 3px ticks, so on a touch device it is 95px of decoration
     nobody can operate. It stays exactly as it is on desktop, where a pointer
     makes it work. */
  .trace { display: none; }

  /* Four stat tiles stacked one per row cost 328px. Two by two costs 164px and
     loses nothing — the tiles were already this size at 431-767px. */
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
  .stat-value { font-size: 20px; }
  .card { grid-template-columns: 44px minmax(0, 1fr); gap: 11px; padding: 12px 13px 11px 14px; }
  .card-date { padding-right: 10px; }
  .card-date .dnum { font-size: 18px; }
  .card-meta .src-link { margin-left: 0; }
  .trace-plot { height: 34px; }
  .sitefoot {
    padding: 20px max(14px, env(safe-area-inset-right))
             max(30px, env(safe-area-inset-bottom)) max(14px, env(safe-area-inset-left));
  }
  /* Nothing is pinned on a phone, so a target only has to clear its own gap. */
  .card, .month-head, .feed-split, .story-block, .faq details { scroll-margin-top: 16px; }
}

/* The stats used to drop to a single column here, which is what made the block
   328px tall on every phone and pushed the first event off the opening screen.
   Two columns hold at 320px — the tiles are ~140px wide, and the longest label
   ("Categories active") takes two lines, which the tile already has room for. */
@media (max-width: 430px) {
  .stat { padding: 8px 10px 9px; }
  .stat-label { letter-spacing: .08em; }
}

/* At 16px the two selects no longer share one 292px row: "Recently Added" needs
   104px of text plus the ~18px WebKit reserves inside the control for its own
   arrow, and each wrap only offers 92px once its eyebrow label is placed. One
   per row costs a single ~40px row at this width and keeps both labels, which
   is what gives the controls their accessible names. */
@media (max-width: 360px) {
  .control-selects { flex-direction: column; align-items: stretch; }
  .select-wrap { width: 100%; }
}

/* Short viewport, any width. Un-sticking the chrome was keyed to max-width: 767px,
   but a phone in landscape is 844px WIDE and 390px TALL, so it took the desktop
   treatment: measured, the topbar (61px) and the controls bar (147px) together
   held 53% of the viewport permanently, leaving 182px — about one and a half
   cards — to actually read the feed in. Height is the axis that matters for a
   decision about vertical space, so this asks about height. iPad landscape (768px
   tall, 27%) and ordinary desktop windows are deliberately above the threshold
   and keep their sticky bars. */
@media (max-height: 560px) {
  .topbar { position: static; }
  .controls { position: static; }
  .sidebar { position: static; max-height: none; overflow: visible; }
  .card, .month-head, .feed-split, .story-block, .faq details { scroll-margin-top: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
  }
  .card:hover { transform: none; }
}

@media print {
  .sidebar, .controls, .filter-toggle, .topbar-meta, .trace,
  .nav, .btn-cta, .theme-toggle, .region, .skip-link,
  .cal-btn, .src-link { display: none; }
  .layout { grid-template-columns: 1fr; }
  .card { break-inside: avoid; box-shadow: none; }
  /* On paper the description is the only body copy there is, so it prints whole. */
  .card-desc { display: block; overflow: visible; }
}
