/* NoVA Food Guide. Structure and typography only; brand tokens are injected
   per region by the generator, so this file stays region neutral.

   Built to docs/DESIGN-SPEC.md, which was measured off 13 live food
   publications. The load bearing decisions:
     Display is SANS, body is SERIF. Serif display over sans body is the
     signature of the two weakest sites in the reference set.
     Display leading is below 1.0. Eater 0.95, Infatuation 0.94, Time Out 0.89.
     ONE metadata style carries every small uppercase label on the site.
     No image is ever displayed larger than its source can honestly fill.
   No em dashes, ever. */

:root {
  /* fallbacks; the per region theme block overrides these */
  --paper: #f7f6f3;
  --paper-2: #efece5;
  --band: #e7e3d9;
  --ink: #12110f;
  --ink-2: #66635c;          /* metadata, captions */
  --ink-3: #918d85;          /* furthest back */
  --accent: #8e1d13;         /* lacquer red, hairlines and live state only */
  --serif: 'Literata', Georgia, serif;
  --sans: 'Archivo', 'Helvetica Neue', Arial, sans-serif;

  --line: color-mix(in srgb, var(--ink) 13%, transparent);
  --line-mid: color-mix(in srgb, var(--ink) 24%, transparent);

  /* grid: 1280 container, 40 gutter, 1200 content */
  --maxw: 1280px;
  --gutter: 40px;
  --gap: 24px;

  /* type scale. integers only, per spec 1.2 */
  --d1: 48px; --d1-lh: 44px;
  --d2: 34px; --d2-lh: 38px;
  --d3: 24px; --d3-lh: 28px;
  --d4: 20px; --d4-lh: 22px;
  --b1: 20px; --b1-lh: 30px;
  --b2: 18px; --b2-lh: 30px;
  --b3: 15px; --b3-lh: 24px;
  --b4: 14px; --b4-lh: 22px;
}

@media (max-width: 720px) {
  :root {
    --gutter: 20px;
    --d1: 34px; --d1-lh: 32px;
    --d2: 26px; --d2-lh: 29px;
    --d3: 20px; --d3-lh: 23px;
    --d4: 18px; --d4-lh: 20px;
    --b1: 18px; --b1-lh: 28px;
    --b2: 17px; --b2-lh: 28px;
  }
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-padding-top: 96px; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: var(--b2);
  line-height: var(--b2-lh);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; display: block; }
a { color: inherit; }
::selection { background: var(--accent); color: var(--paper); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ---------- THE metadata style. Spec 1.3, the highest leverage rule.
   Day, window, neighborhood, cuisine, credit, verified date, byline,
   breadcrumb, CTA label, filter label. All of them. Any other small
   uppercase tracked style in this file is a bug. ---------- */
.meta {
  font-family: var(--sans);
  font-size: 11px;
  line-height: 16px;
  font-weight: 600;
  font-stretch: 100%;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.meta--accent { color: var(--accent); }
.meta--invert { color: color-mix(in srgb, var(--paper) 72%, transparent); }

/* numbers that must align in a column */
.num {
  font-family: var(--sans);
  font-size: var(--b3);
  line-height: var(--b3-lh);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ---------- display type: sans, sub 1.0 leading ---------- */
h1, h2, h3, h4 {
  font-family: var(--sans);
  font-weight: 700;
  margin: 0;
  text-wrap: balance;
  font-stretch: 100%;
}
h1 { font-size: var(--d1); line-height: var(--d1-lh); letter-spacing: 0.01em; }
h2 { font-size: var(--d2); line-height: var(--d2-lh); letter-spacing: 0.02em; font-stretch: 88%; }
h3 { font-size: var(--d3); line-height: var(--d3-lh); }
p { margin: 0 0 1em; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

.lede { font-size: var(--b1); line-height: var(--b1-lh); letter-spacing: -0.01em; max-width: 52ch; color: var(--ink); }
.dek  { font-size: var(--b3); line-height: var(--b3-lh); color: var(--ink-2); }

/* ---------- layout ---------- */
.wrap { max-width: var(--maxw); margin: 0 auto; padding-inline: var(--gutter); }
.rule { border: 0; border-top: 1px solid var(--line); margin: 0; }
.vh { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden;
      clip-path: inset(50%); white-space: nowrap; }
.skip { position: absolute; left: -9999px; background: var(--ink); color: var(--paper);
        padding: 10px 16px; z-index: 100; }
.skip:focus { left: 0; }

/* ---------- masthead ---------- */
/* One masthead across the whole site. The front page sets the pattern: the
   lockup owns the top left, the nav sits hard right, and the rule under them
   is a hairline rather than a heavy black bar. */
.masthead { position: sticky; top: 0; z-index: 50; background: var(--paper);
            border-bottom: 1px solid var(--line-mid); }
.masthead-top { display: flex; justify-content: space-between; gap: 16px;
                padding-block: 9px; }
.masthead-main { display: flex; align-items: center; justify-content: space-between;
                 gap: 24px; padding-block: 14px; }
/* --- masthead lockup -------------------------------------------------
   Two lines set to the same optical width. The main line's tracking was not
   chosen by eye: the top line measures 215.41px at its settings, and 0.220em
   on the main line lands at 215.44px. Both lines carry a negative right
   margin to cancel the trailing letter-space CSS adds after the final glyph,
   so the right edges align on the glyphs rather than on empty air. */
.logo { display: inline-flex; align-items: center; gap: 14px;
        text-decoration: none; white-space: nowrap; }
.logo-mark { display: block; flex: none; height: 42px; }
.logo-mark svg { height: 42px; width: auto; display: block; }
.logo-type { display: block; line-height: 1; }
.logo-top, .logo-main {
  display: block; font-family: var(--sans); text-transform: uppercase;
}
.logo-top {
  font-size: 12px; font-weight: 600; letter-spacing: 0.42em;
  margin-right: -0.42em; color: var(--accent);
}
.logo-main {
  font-size: 25px; font-weight: 700; letter-spacing: 0.220em;
  margin-right: -0.220em; margin-top: 6px; color: var(--ink);
}
/* the condensed state keeps the ratio, so the lockup shrinks as one object */
.masthead.compact .logo-mark, .masthead.compact .logo-mark svg { height: 32px; }
.masthead.compact .logo-top { font-size: 10px; }
.masthead.compact .logo-main { font-size: 19px; margin-top: 4px; }
.nav { display: flex; gap: 28px; align-items: center; }
.nav a { font-family: var(--sans); font-size: 13px; font-weight: 600; line-height: 16px;
         letter-spacing: 0.06em; text-transform: uppercase; text-decoration: none;
         color: var(--ink-2); padding-block: 6px; position: relative; }
.nav a:hover { color: var(--ink); }
.nav a[aria-current] { color: var(--ink); }
.nav a[aria-current]::after { content: ""; position: absolute; left: 0; right: 0;
        bottom: 0; height: 2px; background: var(--accent); }
.nav-cta { border: 1px solid var(--ink); padding: 8px 14px; color: var(--ink) !important; }
.nav-cta:hover { background: var(--ink); color: var(--paper) !important; }
.nav-toggle { display: none; font-family: var(--sans); font-size: 11px; font-weight: 700;
        letter-spacing: 0.12em; text-transform: uppercase; background: none;
        border: 1px solid var(--ink); padding: 8px 12px; cursor: pointer; }

/* ---------- page opener ---------- */
/* Sep 23 2026. Three rejections' worth of history lives in this block.
   It was centred to avoid the empty-half-canvas failure, which traded one
   fault for two: a centred headline on a page whose every other element is
   left-aligned, and a stat strip that then had nowhere to sit but underneath,
   costing another 90px on a page whose entire job is the list below it.
   Measured before: the happy hour table head began 493px down and the right
   620px of the opener held nothing at all.
   The fix uses the dead half rather than avoiding it. Title and lede on the
   left rail, the live tally set as a right-aligned ledger column on the right.
   It is a ledger site, so the numbers are typeset like a ledger: label left,
   figure right, hairline between. The empty canvas is gone because real
   information is in it, which is the only honest way to close that gap. */
.opener { padding-block: clamp(18px, 2vw, 26px) 0; }
.opener-head { display: grid; grid-template-columns: minmax(0, 1fr) 300px;
               gap: clamp(28px, 4vw, 64px); align-items: end;
               padding-bottom: 20px; border-bottom: 1px solid var(--ink); }
.opener h1 { font-size: clamp(32px, 3.4vw, 46px); line-height: 1.04;
             letter-spacing: -0.018em; max-width: 19ch; }
.opener .lede { margin: 12px 0 0; max-width: 46ch; }

/* The tally. Baseline-aligned label and figure on one line with a hairline
   under each row, so four numbers read as one object instead of four. */
.tally { margin: 0; display: grid; gap: 0; align-self: end; }
.tally > div { display: flex; align-items: baseline; justify-content: space-between;
               gap: 16px; padding: 7px 0; border-top: 1px solid var(--line-mid); }
.tally > div:first-child { border-top: 0; padding-top: 0; }
.tally dt { font-family: var(--sans); font-size: 11px; font-weight: 600;
            letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-2); }
.tally dd { margin: 0; font-family: var(--sans); font-weight: 700; font-size: 25px;
            line-height: 1.04; font-variant-numeric: tabular-nums;
            font-feature-settings: "tnum" 1; }
.tally .live dd { color: var(--accent); }
/* The sweep date is a date, not a score. Same row shape, figure type dropped
   to the label's weight so it does not read as a fourth statistic. */
.tally .asof dd { font-size: 12px; font-weight: 600; letter-spacing: 0.06em;
                  color: var(--ink-2); text-transform: uppercase; }

/* ---------- earned motion ----------
   The headline sets itself line by line, and the live count counts up. The
   count is the one number on the page that changes with the clock, so the
   motion carries information rather than decorating. Nothing else moves. */
/* The mask clips to the line box, and at 0.94 leading that box is shorter than
   the glyphs, so descenders (the g in Virginia, the g in right) lost their
   tails. Pad the clip box and pull the same amount back off the layout. */
.line { display: block; overflow: hidden;
        padding-bottom: 0.16em; margin-bottom: -0.16em; }
.line > span { display: block; animation: lineup 0.62s cubic-bezier(0.16, 0.9, 0.24, 1) both; }
.line:nth-child(2) > span { animation-delay: 0.08s; }
@keyframes lineup { from { transform: translateY(105%); } to { transform: none; } }

/* ---------- the ledger: a typeset listing, tile at a fixed left edge ---------- */
/* Sep 23 2026. The row carried three visual languages at once: underlined
   text chips for When/Detail/Area, native <select> menus for Day and Cuisine,
   and a bare underlined search box hanging off the end on a line of its own.
   One bar, three idioms, and the search orphaned. That reads as unfinished.
   Everything is now the same pill on the same baseline, search included and
   moved to the front where people reach for it. Groups are separated by a
   hairline rather than by guessing at gap sizes, so the bar has a rhythm at
   any width. Height is the constraint: the bar is sticky under a 92px
   masthead, so it stays on two lines at 1440 and never grows a third. */
.filters { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center;
           padding-block: 13px 12px; position: sticky;
           top: var(--masthead-h, 92px); background: var(--paper); z-index: 20;
           border-bottom: 1px solid var(--line-mid); }
.fgroup, .fsel { display: inline-flex; align-items: center; gap: 7px; }
/* A hairline before every group but the first gives the bar its structure. */
.fgroup + .fgroup, .fgroup + .fsel, .fsel + .fsel, .fsel + .fgroup {
           border-left: 1px solid var(--line-mid); padding-left: 14px; }
.fgroup .meta, .fsel .meta { font-size: 10px; letter-spacing: 0.13em;
           text-transform: uppercase; color: var(--ink-3); white-space: nowrap; }

.chip { font-family: var(--sans); font-size: 12.5px; font-weight: 600; line-height: 1;
        background: none; border: 1px solid transparent; border-radius: 999px;
        padding: 7px 11px; cursor: pointer; color: var(--ink-2); white-space: nowrap;
        transition: background-color .14s ease, color .14s ease, border-color .14s ease; }
.chip:hover { color: var(--ink); background: var(--paper-2); }
.chip[aria-pressed="true"] { color: var(--paper); background: var(--ink);
        border-color: var(--ink); }

.fsel select { font-family: var(--sans); font-size: 12.5px; font-weight: 600;
        color: var(--ink); background-color: var(--paper-2);
        border: 1px solid var(--line-mid); border-radius: 999px;
        padding: 7px 26px 7px 12px; cursor: pointer; appearance: none; line-height: 1;
        background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' stroke='%2366635c' stroke-width='1.4' fill='none'/%3E%3C/svg%3E");
        background-repeat: no-repeat; background-position: right 11px center; }
.fsel select:hover { border-color: var(--ink-3); }

/* order:-1 puts search first without moving it in the markup, where it sits
   last on purpose: a screen reader meets the filter groups before the box. */
.search { order: -1; flex: 0 0 auto; }
.search input { font-family: var(--sans); font-size: 12.5px; color: var(--ink);
        background: var(--paper-2); border: 1px solid var(--line-mid);
        border-radius: 999px; padding: 7px 14px; width: 232px; line-height: 1; }
.search input:focus { outline: 0; border-color: var(--ink); background: var(--paper);
        width: 282px; }
.search input { transition: width .18s ease, border-color .14s ease; }
.search input::placeholder { color: var(--ink-3); }
.fbreak { display: none; }

/* The phone filter panel's toggle row and close button exist in the markup
   at every width. Above the phone breakpoint they are never drawn, so the
   desktop bar is exactly what it was. */
@media (min-width: 721px) { .fbar, .fdone { display: none; } }

@media (max-width: 720px) {
  /* Sep 24 2026, the owner, swiping sideways through these settings on a
     phone: "we need to make it easier and more visible". The earlier fix for
     six wrapped groups (200px of controls over the table) was one sideways
     track, which hid four of the five groups off the right edge with
     nothing saying they were there, at a 29px tap height. Showing
     every group at once costs about 300px of sticky bar and puts the first
     result below the fold. So the phone bar is two full width rows: search,
     then a Filters button beside the live count and a line naming what is
     on. One tap opens every group as a labelled row that wraps. Nothing
     scrolls sideways and every target is 44px. */
  .filters { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px;
             padding-block: 8px; }
  .search { order: -2; }
  /* 16px, or iOS zooms the page in the moment the field takes focus */
  .search input, .search input:focus { width: 100%; height: 44px;
             font-size: 16px; padding: 0 16px; }
  .fbar { order: -1; display: flex; align-items: center; gap: 14px; }
  .ftoggle { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 8px;
             height: 44px; padding: 0 15px 0 17px; font-family: var(--sans);
             font-size: 15px; font-weight: 600; line-height: 1; color: var(--ink);
             background: var(--paper); border: 1px solid var(--ink);
             border-radius: 999px; cursor: pointer; }
  /* the same chevron the selects carry, turned over while the panel is open */
  .ftoggle::after { content: ""; width: 9px; height: 6px;
             background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' stroke='%2312110f' stroke-width='1.4' fill='none'/%3E%3C/svg%3E") no-repeat;
             transition: transform .18s ease; }
  .ftoggle[aria-expanded="true"]::after { transform: rotate(180deg); }
  .fbadge { display: inline-grid; place-items: center; min-width: 20px; height: 20px;
             padding: 0 6px; border-radius: 999px; background: var(--ink);
             color: var(--paper); font-size: 12px; font-weight: 700;
             font-variant-numeric: tabular-nums; }
  .fbadge[hidden] { display: none; }
  .fstatus { flex: 1 1 auto; margin: 0; min-width: 0; display: flex;
             flex-direction: column; gap: 1px; }
  .fcount { font-family: var(--sans); font-size: 15px; line-height: 19px;
             font-weight: 700; font-variant-numeric: tabular-nums; }
  .fsum { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

  /* The panel. Each group is a row: its label in a fixed left column, its
     choices wrapping to the right of it, a hairline between rows. */
  .fgroup, .fsel { display: none; }
  .filters.open .fgroup, .filters.open .fsel {
             display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
             position: relative; padding: 10px 0 0 76px;
             border-left: 0; border-top: 1px solid var(--line); }
  .filters.open .fgroup .meta, .filters.open .fsel .meta {
             position: absolute; left: 0; top: 24px; font-size: 10.5px; }
  .filters .chip { min-height: 44px; padding: 0 14px; font-size: 14.5px;
             border-color: var(--line-mid); }
  .filters .chip[aria-pressed="true"] { border-color: var(--ink); }
  .fsel select { width: 100%; height: 44px; font-size: 16px;
             padding: 0 38px 0 16px; background-position: right 16px center; }
  .fdone { display: none; }
  .filters.open .fdone { display: block; width: 100%; min-height: 48px; margin-top: 4px;
             font-family: var(--sans); font-size: 15px; font-weight: 600;
             color: var(--paper); background: var(--ink); border: 0;
             border-radius: 999px; cursor: pointer; }
  /* A phone narrower than the open panel can still reach its close button. */
  .filters.open { max-height: calc(100vh - var(--masthead-h, 62px));
             max-height: calc(100dvh - var(--masthead-h, 62px));
             overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
}

@media (prefers-reduced-motion: reduce) { .search input { transition: none; } }

.ledger { width: 100%; border-collapse: collapse; }
.ledger thead th { box-shadow: 0 1px 0 var(--ink); position: sticky;
        top: calc(var(--masthead-h, 92px) + var(--filters-h, 52px));
        background: var(--paper); z-index: 10; text-align: left;
        font-family: var(--sans); font-size: 11px; line-height: 16px; font-weight: 600;
        letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-2);
        padding: 12px 16px 10px 0; border-bottom: 1px solid var(--ink); }
.ledger td { padding: 16px 16px 16px 0; border-bottom: 1px solid var(--line);
             vertical-align: top; }
.ledger tbody tr:hover { background: color-mix(in srgb, var(--band) 45%, transparent); }
.ledger .c-tile { width: 56px; padding-right: 16px; }
.ledger .c-venue { width: 27%; }
.ledger .c-days { width: 116px; }
/* The days strip and the window had no fixed width, so the auto table let the
   deal column push them around and the two never held a straight edge down the
   page. Both are fixed now and the strip is tabular. */
.ledger .c-when { width: 132px; font-variant-numeric: tabular-nums;
                  white-space: nowrap; }
.ledger .c-deal { font-size: var(--b3); line-height: var(--b3-lh); }
/* Sep 24 2026, the owner: the days strip sat 6px below the window on every
   row ("these aren't congruent"). Top alignment lined up the cell TOPS, but a
   12px strip on a 30px line and 15px times on a 24px line put their text at
   different heights. Every text column now shares the row's first baseline:
   name, days, window and deal read as one line. The tile stays top-aligned. */
.ledger td.c-venue, .ledger td.c-days, .ledger td.c-when, .ledger td.c-deal {
  vertical-align: baseline; }
.v-name { font-family: var(--sans); font-size: var(--d4); line-height: var(--d4-lh);
          font-weight: 700; font-stretch: 88%; letter-spacing: 0; display: block; }
.v-name a { text-decoration: none; }
.v-name a:hover { color: var(--accent); }

/* the tile: one shape, one size, down all 507 rows. A straight left edge is
   worth more than 507 mediocre photographs. */
/* One shape at one size down all 507 rows. A photograph fills it; a venue
   without one gets quiet initials on paper, so the pictures are the only thing
   carrying colour and the column still reads as a straight edge. Loud coloured
   initial blocks were tried at 120px and read as a placeholder wall. */
.tile { width: 56px; height: 56px; border-radius: 2px; overflow: hidden;
        display: grid; place-items: center; }
.tile img { width: 100%; height: 100%; object-fit: cover; }
.tile-type { font-family: var(--sans); font-weight: 700; font-stretch: 75%;
             font-size: 15px; line-height: 1; letter-spacing: 0.06em;
             color: var(--ink-3); }
.tile-a, .tile-b, .tile-c { background: none; border: 1px solid var(--line); }

.daystrip { font-family: var(--sans); font-size: 12px; font-weight: 700;
            letter-spacing: 0.16em; color: var(--ink-3); white-space: nowrap; }
.daystrip .on { color: var(--ink); }
.count { margin-top: 16px; }
.ledger-empty { padding: 48px 0; text-align: center; color: var(--ink-2); }

/* ---------- the lead: three rooms, not one floating plate ----------
   A single feature under a centred headline left a hole between them and said
   nothing about the size of the guide. Three cards fill the fold, prove the
   breadth, and each is 384 CSS px so a 2400px source is retina sharp twice
   over. Card anatomy follows the reference set: picture, place, name, the deal,
   the window. */
.lead-grid { display: grid; grid-template-columns: repeat(3, 1fr);
             gap: 28px; padding-block: 8px 8px; }
.lead-card { display: flex; flex-direction: column; }
.lead-card a.pic { display: block; overflow: hidden; background: var(--band); }
.lead-card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
                 transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.3, 1); }
.lead-card a.pic:hover img { transform: scale(1.035); }
.lead-card .place { margin-top: 14px; }
.lead-card h3 { margin-top: 4px; font-size: var(--d3); line-height: var(--d3-lh); }
.lead-card h3 a { text-decoration: none; }
.lead-card h3 a:hover { color: var(--accent); }
.lead-card .deal { margin-top: 8px; font-size: var(--b3); line-height: var(--b3-lh); }
/* Deals differ in length, so aligned dates and equal gaps cannot both come free:
   one line of difference has to go somewhere. Reserving blank space put it in
   the open and it read as a hole. A hairline anchors it instead. Every date now
   sits on the same baseline under its own rule, and the space above the rule is
   card footer rather than a gap nobody asked for. */
.lead-card .when { margin-top: auto; padding-top: 12px;
                   border-top: 1px solid var(--line); }
@media (max-width: 900px) { .lead-grid { grid-template-columns: 1fr 1fr; gap: 24px; } }
@media (max-width: 620px) { .lead-grid { grid-template-columns: 1fr; gap: 32px; } }

/* ---------- feature: image never wider than 690, per the honest crop math --- */
/* The text used to be pinned to the top of the row (align-items:start), so a
   434px photo sat beside a 180px text block with 250px of void under it and the
   copy read as stranded in the corner. Centred against the picture, the pair
   reads as one unit. */
.feature { display: grid; grid-template-columns: 690px 1fr; gap: 48px;
           align-items: center; padding-block: 56px; }

.feature figure { margin: 0; }
.feature img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: var(--band); }
.feature cite { display: block; margin-top: 8px; font-style: normal; }
.feature h2 { margin-bottom: 10px; }
.feature .deal { font-size: var(--b2); line-height: var(--b2-lh); margin-top: 12px; }

/* ---------- dispatch list (openings) ---------- */
.dispatch { list-style: none; margin: 0; padding: 0; }
.dispatch li { border-bottom: 1px solid var(--line); }
.dispatch a { display: grid; grid-template-columns: 130px 1fr 90px; gap: 20px;
              align-items: baseline; padding: 16px 0; text-decoration: none; }
.dispatch a:hover h3 { color: var(--accent); }
.dispatch h3 { font-size: var(--d4); line-height: var(--d4-lh); font-stretch: 88%; }
.dispatch .d-detail { font-size: var(--b3); line-height: var(--b3-lh); color: var(--ink-2);
                      margin: 4px 0 0; }

/* ---------- section head ---------- */
.shead { display: flex; align-items: baseline; justify-content: space-between; gap: 20px;
         padding-bottom: 12px; margin-bottom: 4px;
         border-bottom: 1px solid var(--line-mid); }
.section { padding-block: 48px; }

/* ---------- held tier + faq ---------- */
.held-tier, .faq { padding-top: 44px; margin-top: 56px;
                   border-top: 1px solid var(--line-mid); }
.held-grid { list-style: none; margin: 0; padding: 0; columns: 3; column-gap: 40px; }
.held-grid li { break-inside: avoid; padding: 7px 0; border-bottom: 1px solid var(--line);
                font-size: var(--b3); line-height: var(--b3-lh); }
/* Sep 23 2026. This was nine tinted panels on a three-column grid. Panels of
   very unequal length stretched to their row's tallest item, so a two-line
   answer sat in a box built for eight lines and the page read as a template
   with holes in it. Boxed card grids are on the house blocklist for exactly
   this reason, and stretching them only hides the symptom.
   A policy page is a reference document, so it is set as one: term in a fixed
   left rail, answer across the measure, a hairline between entries. Rows are
   as tall as their own answer, nothing stretches, nothing is boxed, and the
   left rail gives the eye one edge to run down. */
/* One right edge. The hairlines ran to the full 1320px wrap while the answers
   stopped at a 72ch measure, so every rule overshot its own text by 340px.
   The policy page now holds every rule, heading and answer to one 1040px
   column: the section head, the entries, the lot. The thesis keeps a shorter
   measure on purpose, because a pull quote set to the full column stops
   reading as a quote. */
.faq dl { margin: 18px 0 0; }
.page-method .section { max-width: 1040px; padding-block: 40px 44px; }
.faq dl > div { display: grid; grid-template-columns: 210px minmax(0, 1fr);
                gap: 28px; padding: 18px 0;
                border-top: 1px solid var(--line-mid); }
.faq dl > div:first-child { border-top: 0; padding-top: 4px; }
.faq dt { font-family: var(--sans); font-weight: 700; font-size: 13.5px;
          line-height: 20px; letter-spacing: -0.005em; margin: 0;
          text-wrap: balance; }
.faq dd { margin: 0; font-size: var(--b3);
          line-height: var(--b3-lh); color: var(--ink-2); }

@media (max-width: 760px) {
  /* The rail becomes a heading above its answer. Still one row per entry,
     still one hairline between, so the shape of the page does not change. */
  .faq dl > div { grid-template-columns: minmax(0, 1fr); gap: 6px; padding: 16px 0; }
  .faq dd { font-size: 15px; line-height: 24px; }
}


/* ---------- the openings radar table ----------
   Sep 23 2026. A "Lane" column printed the word Independent on 208 of 236
   rows: a full column of width spent saying "not the exception". The
   exception now rides as a tag on the place itself, which is where it
   belongs, and the reclaimed width goes to Spotted, whose dates were wrapping
   to two lines inside a 100px column. */
#radar .c-place { width: 27%; }
#radar .c-area { width: 108px; }
#radar .c-spotted { width: 124px; white-space: nowrap; }
/* A permit class set as a classification, not as a sentence. It sits in the
   same register as the source credit under it, because that is what it is:
   metadata off the filing, not something a reporter wrote. */
.permit { display: inline-block; font-family: var(--sans); font-size: 11px;
          font-weight: 600; line-height: 16px; letter-spacing: 0.08em;
          text-transform: uppercase; color: var(--ink-2); }
.lane { display: inline-block; margin-left: 7px; vertical-align: 1px;
        font-family: var(--sans); font-size: 9.5px; font-weight: 600;
        letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3);
        border: 1px solid var(--line-mid); border-radius: 3px; padding: 2px 5px; }

/* ---------- footer ---------- */
.footer { margin-top: 72px; background: var(--ink); color: var(--paper); padding-block: 48px 32px; }
.footer a { text-decoration: none; }
.footer a:hover { color: color-mix(in srgb, var(--paper) 70%, var(--accent)); }
.footer-grid { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 40px; }
.footer h4 { font-family: var(--sans); font-size: 11px; line-height: 16px; font-weight: 600;
             letter-spacing: 0.12em; text-transform: uppercase;
             color: color-mix(in srgb, var(--paper) 60%, transparent); margin-bottom: 12px; }
.footer ul { list-style: none; margin: 0; padding: 0; }
.footer li { padding: 5px 0; font-size: var(--b3); line-height: var(--b3-lh); }
.footer .logo span { color: color-mix(in srgb, var(--paper) 75%, var(--accent)); }
.colophon { grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 20px;
            padding-top: 24px; margin-top: 16px;
            border-top: 1px solid color-mix(in srgb, var(--paper) 18%, transparent); }

/* ---------- one earned moment: the opener sets itself on load ---------- */
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.enter-1, .enter-2, .enter-3 { animation: rise 0.5s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
.enter-2 { animation-delay: 0.07s; }
.enter-3 { animation-delay: 0.13s; }

/* ---------- mobile: a stacked record, not a squeezed table ---------- */
@media (max-width: 900px) {
  .feature { grid-template-columns: 1fr; gap: 20px; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .held-grid { columns: 2; }
}
@media (max-width: 720px) {
  /* the lockup is a fixed-width object, so on a phone it crowds the Menu
     button. Step the whole thing down as one, keeping the two lines matched. */
  .logo { gap: 10px; }
  .logo-mark, .logo-mark svg { height: 34px; }
  .logo-top { font-size: 9.5px; letter-spacing: 0.42em; margin-right: -0.42em; }
  .logo-main { font-size: 19.8px; letter-spacing: 0.220em; margin-right: -0.220em;
               margin-top: 5px; }
  /* Sep 24 2026. The base .nav-toggle rule never set a text colour, so iOS
     Safari painted MENU in its system blue inside a black frame. */
  .nav-toggle { display: block; color: var(--ink); }
  .nav { display: none; position: absolute; left: 0; right: 0; top: 100%;
         flex-direction: column; align-items: flex-start; gap: 0; background: var(--paper);
         border-bottom: 1px solid var(--ink); padding: 8px var(--gutter) 16px; }
  .nav.open { display: flex; }
  .nav a { padding-block: 10px; font-size: 15px; }
  .masthead-main { position: relative; }
  .masthead-top span:nth-child(2) { display: none; }
  .ledger, .ledger tbody, .ledger tr, .ledger td { display: block; width: auto; }
  .ledger thead { display: none; }
  .ledger tbody tr { display: grid; grid-template-columns: 56px 1fr; gap: 4px 14px;
                     padding: 16px 0; border-bottom: 1px solid var(--line); }
  .ledger td { border: 0; padding: 0; }
  .ledger .c-tile { grid-row: 1 / 5; width: 56px; }
  /* The desktop column percentages outrank `.ledger td { width: auto }` on
     specificity, so they survived the stack and squeezed every venue name into
     27% of a 390px screen and every window into 16%. In a stacked record the
     cell owns the row. */
  .ledger .c-venue, .ledger .c-when, .ledger .c-deal { width: auto; }
  .ledger .c-when { font-variant-numeric: tabular-nums; }
  .held-grid { columns: 1; }
  .footer-grid { grid-template-columns: 1fr; }
  .dispatch a { grid-template-columns: 1fr; gap: 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .enter-1, .enter-2, .enter-3, .line > span {
    animation: none !important; opacity: 1; transform: none; }
  * { transition: none !important; scroll-behavior: auto !important; }
}


/* ---------- interactive states. Every hover, focus and active state moves.
   The site previously had zero CSS transitions on any page, which is why it
   felt inert to the touch. ---------- */
a, .chip, .nav a, .fsel select, .search input, .ledger tbody tr, .tile,
.dispatch a, .nav-cta, .v-name a {
  transition: color 0.18s ease, background-color 0.18s ease,
              border-color 0.18s ease, opacity 0.18s ease, transform 0.18s ease;
}
.dispatch a:hover { background: color-mix(in srgb, var(--band) 40%, transparent); }
.dispatch a { padding-inline: 8px; margin-inline: -8px; }
.tile { transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.3, 1); }
.ledger tbody tr:hover .tile { transform: scale(1.06); }
.v-name a { text-decoration: underline; text-decoration-color: transparent;
            text-underline-offset: 3px; }
.ledger tbody tr:hover .v-name a { text-decoration-color: var(--accent); }
.meta a { border-bottom: 1px solid var(--line-mid); }
.meta a:hover { border-bottom-color: var(--accent); color: var(--accent); }

/* the openings table: a name is not an address */
.venue .cuisine, .v-name + .meta { display: block; }
#radar td.venue .cuisine { display: block; margin-top: 3px; }

@media (max-width: 720px) {
  /* the radar table gets the same stacked-record treatment as the ledger,
     instead of crushing five columns into 390px and printing one word a line */
  #radar, #radar tbody, #radar tr, #radar td { display: block; width: auto; }
  #radar thead { display: none; }
  #radar tbody tr { padding: 14px 0; border-bottom: 1px solid var(--line); }
  #radar td { border: 0; padding: 2px 0; }
  #radar td.venue { font-size: var(--d4); line-height: var(--d4-lh);
                    font-family: var(--sans); font-weight: 700; }
  /* The address lives inside the place cell, so it inherited the display
     treatment and every row read as two headings stacked. It is a location,
     not a title. */
  #radar td.venue .cuisine { display: block; margin-top: 2px;
                    font-family: var(--serif); font-weight: 400;
                    font-size: 14px; line-height: 20px; color: var(--ink-2); }
  /* One column on a phone: headline, lede, then the tally as full-width rows.
     The old centred treatment pushed the first data row 690px down a 844px
     screen, so a list page opened showing no list. */
  .opener-head { grid-template-columns: minmax(0, 1fr); gap: 20px;
                 align-items: start; }
  .opener h1 { max-width: none; font-size: clamp(28px, 8vw, 36px); }
  .opener .lede { max-width: none; font-size: 15.5px; line-height: 24px; }
  .tally > div { padding: 9px 0; }
  .tally dd { font-size: 22px; }
  /* the explicit line breaks are set for a wide measure; on a phone let the
     headline find its own break so it does not read as four ragged lines */
  .opener h1 .line { display: inline; overflow: visible;
                     padding-bottom: 0; margin-bottom: 0; }
  .opener h1 .line > span { display: inline; animation: none; }
  .opener h1 .line + .line::before { content: " "; }
  /* the nav panel needs a ground of its own */
  .nav.open { box-shadow: 0 18px 40px -12px rgba(0,0,0,0.28); }
}

/* the methodology thesis: one statement, set across the measure so it does not
   leave half the page empty beside it */
/* Sep 23 2026. This ran the full 1200px measure at display size, three lines
   of heavy sans with no anchor, and then left 100px of nothing before the
   entries. It is a thesis, so it gets a thesis's shape: a fixed measure, the
   accent rule on the left edge that the rest of the site uses to mark a
   quoted position, and enough weight to lead without shouting. */
.rule-statement { margin: 20px 0 6px; max-width: 34ch; font-family: var(--sans);
                  font-weight: 700; font-size: var(--d3); line-height: 1.22;
                  letter-spacing: -0.005em;
                  border-left: 2px solid var(--accent); padding-left: 20px; }
@media (min-width: 900px) { .rule-statement { max-width: 46ch; font-size: 29px;
                  line-height: 1.24; font-stretch: 92%; padding-left: 26px; } }

/* ---------- browse index: every collection reachable from the ledger ----------
   Collection pages that nothing links to are orphans, so the index is both the
   reader's way in and the crawl path. */
.browse { padding-top: 44px; margin-top: 56px; border-top: 1px solid var(--line-mid); }
.browse-grid { display: grid; grid-template-columns: repeat(4, 1fr);
               gap: 28px; margin-top: 24px; }
.browse-grid ul { list-style: none; margin: 10px 0 0; padding: 0; }
.browse-grid li { padding: 5px 0; border-bottom: 1px solid var(--line);
                  font-size: var(--b3); line-height: var(--b3-lh); }
.browse-grid a { display: flex; justify-content: space-between; gap: 12px;
                 text-decoration: none; }
.browse-grid a:hover { color: var(--accent); }
@media (max-width: 900px) { .browse-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .browse-grid { grid-template-columns: 1fr; } }

/* --- the changelog ---------------------------------------------------
   Only renders when a sweep actually moved rows, so an empty week prints
   nothing rather than an empty promise. */
.changelog { border-top: 1px solid var(--rule); padding-top: 34px; margin-top: 46px; }
.changelog .dek { max-width: 56ch; margin: 10px 0 26px; color: var(--ink-3); }
.changelog-grid { display: grid; gap: 26px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.changelog-grid > div { background: var(--paper-2); padding: 20px 22px; }
.changelog-grid ul { list-style: none; margin: 12px 0 0; padding: 0; }
.changelog-grid li { padding: 7px 0; border-bottom: 1px solid var(--rule); font-size: 15px; }
.changelog-grid li:last-child { border-bottom: 0; }
.changelog-grid li b { font-weight: 500; }

/* Prose computed from the rows on the page. Sits between the headline and the
   table, so a short list page still says something before the data starts. */
.pattern {
  max-width: 62ch; margin: 0 0 30px; font-size: 17.5px; line-height: 1.6;
  color: var(--ink-2); border-left: 2px solid var(--rule); padding-left: 18px;
}


/* Area and collection pages carry one photograph, large, above the pattern
   paragraph. The picture was already being chosen for og:image and then thrown
   away, so these pages published nothing but type. */
.area-hero { margin: 0 0 34px; }
.area-hero img {
  width: 100%; aspect-ratio: 1180 / 620; object-fit: cover;
  background: var(--band); display: block;
}
.area-hero figcaption { margin-top: 8px; }
@media (max-width: 720px) { .area-hero { margin-bottom: 24px; } }

/* Source credit on every radar row. A hyperlocal newsroom did the reporting on
   a good share of these, so the row names them and links back. */
.credit { display: inline-block; margin-top: 5px; }
.credit a { color: var(--ink-2); text-decoration-thickness: 1px;
            text-underline-offset: 2px; }
.credit a:hover { color: var(--accent); }
