/* ══════════════════════════════════════════════════════════════════════════
   LA PARTY LIGHTS — THE DESIGN SYSTEM
   ══════════════════════════════════════════════════════════════════════════

   THE SINGLE SOURCE OF DESIGN TRUTH. Every page, every panel, every tab, every
   dropdown, every field on this site comes from this file. If a screen needs something
   that is not here, it gets ADDED here under a name — it does not get a <style> block of
   its own. Eight pages once each carried a byte-identical copy of another stylesheet's
   opening, and fixing the real sheet changed nothing on any of them, invisibly, because
   the rules being written were right and simply were not the ones in use.

   ── gold is split three ways, by job AND by size ─────────────────────────────
   --metal    #d8bc7e   1.7:1 — the coin's ring. A LINE. Never a word.
   --gold-lg  #a9782a   3.6:1 — display type only, 24px and up (AA large bar is 3.0)
   --gold-ink #956a25   4.5:1 — eyebrows, links, labels, anything smaller
   Same hue, same saturation, three luminances. Using the display gold on a 12px label
   fails AA and looks fine to whoever picked it, which is why it is a token and not a
   judgement call.

   ── type ─────────────────────────────────────────────────────────────────────
   Montserrat carries the whole interface: 800 is the mark's "LIGHTS", 500 wide-tracked
   uppercase is its "PLAN. CONNECT. CELEBRATE." Great Vibes appears ONLY as the display
   script line — one or two words, never a sentence, never a control.

   ── motion ───────────────────────────────────────────────────────────────────
   One ambient behaviour: the coin answering the cursor, driven from a single pointer
   handler so every coin on the page agrees. Everything else moves only in response to a
   hover, a focus or a panel opening. Nothing loops and nothing animates on scroll.  */

:root {
  /* ── surface — #7's own values ── */
  --face:        #f5f3f0;   /* the page — Direction B */
  --paper:       #ffffff;
  --paper-warm:  #f2ece1;
  --sink:        #f7f1e4;   /* the coin's outer ring, and recessed bands */
  --cream:       #f3ead7;   /* the coin's inner ring */
  --night:       #171320;   /* #7's ink and its footer */
  --night-soft:  #2c2440;

  /* ── ink ── */
  --ink:         #171320;
  --ink-2:       #5d5669;   /* 7.4:1 on --face */
  --ink-3:       #756e81;   /* 4.6:1 — meta. #7's #7d7589 was 4.11 and failed */
  --ink-on-night:rgba(255, 255, 255, .66);   /* 7.4:1 on --night */

  /* ── gold, by job AND BY SIZE ──
     #7's gold is #a9782a. Measured, it is 3.63:1 on #faf7f2 — which passes AA for LARGE
     text (≥24px, needs 3.0) and FAILS for normal text (needs 4.5). #7 used it for both:
     the big script tagline, fine; the 11.5px uppercase kicker, not fine.

     So the gold splits three ways rather than two, and the third one is the size rule:
       --metal       #d8bc7e  1.7:1  — the coin's ring. A LINE. Never a word.
       --gold-lg     #a9782a  3.6:1  — #7's gold, for display type ≥24px only
       --gold-ink    #956a25  4.5:1  — the same hue darkened, for anything smaller
     Same hue (36.9°), same saturation. Nobody will see the difference; a screen reader
     user and anybody outdoors will. */
  --metal:       #d8bc7e;
  --metal-pale:  #e8c98a;
  /* ⚠ DIRECTION B, chosen by the owner 2026-08-07 from the /proto/palette.html
     comparison. The brand gold is #b8860b. The SIZE RULE that was already here survives
     the change and is the reason there are still two golds rather than the one the brief
     named: measured in the browser, #b8860b is 3.05:1 on --face and 3.25:1 on a card —
     fine for display type, and a fail for a 13px eyebrow, link or tag, which is most of
     where gold is actually used. --gold-ink is the same hue carried darker so small text
     clears 4.5:1 (measured 5.37:1 on a card). Nobody sees the difference at 13px; a
     screen-reader user and anybody outdoors does. */
  --gold-lg:     #b8860b;   /* display type only — 24px and up */
  --gold-ink:    #8a6410;   /* 5.37:1 — eyebrows, links, labels, anything small */
  --gold-on-night:#e8c98a;  /* 11.4:1 on --night */

  --line:        #ece5d9;
  --line-strong: #ddd5c8;
  --line-gold:   rgba(184, 134, 11, .34);
  --danger:      #c31450;   /* 5.9:1 on --paper — the only red the site uses */

  /* ══ SPACING — an 8px grid, and the gap CLAUDE.md said to close first ══════
     "Type has tokens, radius has tokens, spacing has none — so 46 distinct
     padding/margin values are hand-picked, 202 uses of them on odd px
     (3/5/7/9/11/13/15/17). Every new screen re-invents its padding. Build --sp-*
     before building more screens or this returns."

     ⚠ THESE ARE DECLARED, NOT YET MIGRATED. The 46 existing values are still in the
     file. Use these for anything NEW and convert a component when you are already
     editing it — a single sweeping find-and-replace across 202 sites would change
     spacing on every screen at once with nothing measuring the result. --sp-3 (12px)
     and --sp-5 (24px) absorb most of the odd values; 11px and 13px round to 12,
     15px and 17px to 16. */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;

  --lift-1: 0 1px 2px rgba(20,18,12,.05), 0 6px 20px rgba(20,18,12,.06);
  --lift-2: 0 2px 4px rgba(20,18,12,.06), 0 14px 40px rgba(20,18,12,.10);
  --lift-3: 0 30px 80px rgba(20,18,12,.16);

  /* ── type: Montserrat, and only Montserrat ── */
  --font: "Montserrat", -apple-system, "Segoe UI", Arial, sans-serif;

  --t-xs:  .75rem;   --t-sm:  .8125rem; --t-base: .9375rem;
  --t-md:  1.0625rem;--t-lg:  1.3125rem;--t-xl:   1.75rem;
  --t-2xl: 2.25rem;  --t-3xl: clamp(2.1rem, 3.4vw, 3.25rem);
  --t-4xl: clamp(2.6rem, 5vw, 4.5rem);

  /* ── space & shape ── */
  --page: clamp(20px, 4vw, 56px);
  --max:  1200px;
  --band: clamp(64px, 8vw, 116px);
  --r-sm: 10px; --r-md: 16px; --r-lg: 26px; --r-pill: 999px;

  /* ── motion ── */
  --fast: 130ms; --mid: 220ms;
  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* ══ base ══════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }

/* Zero-specificity reset. Written as `body p {…}` this is (0,0,2) and outranks any
   single class, so a component's own margin silently loses to it and the rule that was
   supposed to apply sits in the file looking correct. A reset must never be the thing a
   component has to fight. */
:where(h1,h2,h3,h4,h5,h6,p,figure,blockquote,dl,dd) { margin: 0; }
:where(ul,ol) { margin: 0; padding: 0; list-style: none; }
/* `height: auto` is not optional here. With width/height ATTRIBUTES on the tag (which
   are worth keeping — they let the browser reserve the right box and avoid layout shift)
   a bare `max-width:100%` caps the width and leaves the height at the attribute value.
   The coin came out 340px wide and 1024px tall: a gold ellipse. */
:where(img,svg) { display: block; max-width: 100%; height: auto; }
:where(button,input,select,textarea) { font: inherit; color: inherit; }

html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--face);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--t-base);
  font-weight: 500;                 /* Montserrat 400 is thin for body at this size */
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;

  /* A SHORT PAGE MUST STILL PUT THE FOOTER AT THE BOTTOM.
     /vendor with no listing on the account is three lines — a heading, a sentence and a
     button — so the footer landed halfway up the window with white below it, which reads
     as a half-loaded page rather than a short one. Every gated, empty and error state on
     the site has the same shape: #nobiz, #gate, /404, an empty search. Fixing it here
     fixes all of them at once, and costs nothing on a page that already fills the screen.
     `main` is the only child that grows; the sticky header and the footer keep their own
     height. Modals are position:fixed, so they are out of this flow entirely. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
body > main { flex: 1 0 auto; }

:focus-visible {
  outline: 3px solid var(--gold-ink);   /* the metal gold is 3:1 and cannot be seen */
  outline-offset: 3px;
  border-radius: 4px;
}
::selection { background: var(--metal-pale); color: var(--ink); }

/* ══ type scale ════════════════════════════════════════════════════════════
   ExtraBold display is the logo's "LIGHTS". Tight tracking, because Montserrat is
   geometric and opens up badly at large sizes if left alone. */

.display {
  font-size: var(--t-4xl); font-weight: 800; line-height: 1.04;
  letter-spacing: -.028em; text-wrap: balance;
}
/* Under the medallion the headline shares the first screen with a 310px coin and four
   doors. At the full display size the first door lands below the fold on a laptop, and
   the one thing this screen exists to do is show the doors. */
.display--under-mark { font-size: clamp(2.1rem, 3.4vw, 3rem); }
.h1 { font-size: var(--t-3xl); font-weight: 800; line-height: 1.1;  letter-spacing: -.022em; }
.h2 { font-size: var(--t-xl);  font-weight: 700; line-height: 1.2;  letter-spacing: -.014em; }
.h3 { font-size: var(--t-lg);  font-weight: 700; line-height: 1.28; letter-spacing: -.01em; }
.h4 { font-size: var(--t-md);  font-weight: 700; line-height: 1.35; }

/* "PLAN. CONNECT. CELEBRATE." — Medium, wide, uppercase. Straight off the mark.
   --gold-ink, not --gold-lg: at 12px the lighter gold is 3.6:1 and fails. */
.eyebrow {
  font-size: var(--t-xs); font-weight: 700; text-transform: uppercase;
  letter-spacing: .22em; color: var(--gold-ink);
}
.eyebrow--quiet { color: var(--ink-3); }

/* ── the script line ───────────────────────────────────────────────────────
   I argued against a script webfont and I was wrong: #7 uses one for the tagline and
   that is the line the owner likes. It is the "Party" of the mark, set large.

   Great Vibes stands in for Brittany Signature, which is a retail font (HTTP 400 from
   Google Fonts, not licensable for web). One or two words, never a sentence, never a
   control, never a label — a script that has to be READ is a script in the wrong place.
   Only ever at display size, where #7's gold is legitimately 3.6:1 against a 3.0 bar. */
.script {
  font-family: "Great Vibes", "Brittany Signature", cursive;
  font-size: clamp(30px, 4.4vw, 62px); line-height: 1.05;
  color: var(--gold-lg); font-weight: 400;
}

/* Words that answer the cursor — #7's headline behaviour. Split per word in JS so the
   copy stays editable rather than being cut into spans by hand. */
.w { display: inline-block; transition: transform .28s cubic-bezier(.2,.9,.25,1.35), color .22s; }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .w:hover { transform: translateY(-9px) rotate(-2deg); color: var(--gold-lg); }
}
/* #7 set this in Bodoni. Replaced with Montserrat 800 on the owner's instruction —
   the mark's own "LIGHTS" — so the page and the logo share one family. */
.h1--rule {
  font-weight: 800; text-transform: uppercase;
  font-size: clamp(19px, 2.2vw, 32px); letter-spacing: .06em; line-height: 1.12;
}

.lede  { font-size: var(--t-md); color: var(--ink-2); line-height: 1.62; }
.muted { color: var(--ink-2); }
.meta  { font-size: var(--t-sm); color: var(--ink-3); }
.measure { max-width: 62ch; }
.center  { text-align: center; margin-inline: auto; }

/* ══ layout ════════════════════════════════════════════════════════════════ */

.wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--page); }
.wrap--tight { max-width: 900px; }
.band { padding-block: var(--band); }
.band--paper { background: var(--paper); }
.band--sink  { background: var(--sink); }
.band--night { background: var(--night); color: var(--ink-on-night); }
.stack > * + * { margin-top: var(--gap, 18px); }
.row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.skip {
  position: fixed; left: 12px; top: -80px; z-index: 200;
  background: var(--paper); padding: 12px 18px; border-radius: var(--r-sm);
  box-shadow: var(--lift-2); font-weight: 600; text-decoration: none; color: var(--ink);
  transition: top var(--fast) var(--ease);
}
.skip:focus { top: 12px; }

/* ══ THE MEDALLION ═════════════════════════════════════════════════════════
   The one piece of ambient motion on the site.

   A struck coin under glass: it tilts toward the cursor on two axes, a specular band
   travels across the metal as it turns, and the shadow moves opposite the light. The
   logo's gold ring sits at 0.909 of the disc radius, so the outer 9% is plain face —
   that margin is lit as the coin's raised rim, which is what gives it depth rather than
   a filter faking one.

   --mx / --my are set by ds.js from pointer position, -1..1. With no pointer they stay
   at 0 and the coin sits flat and perfectly still. */

/* ══ the hero ══════════════════════════════════════════════════════════════
   Composed to LAND: the whole first screen — mark, script, headline, promise and both
   buttons — resolves inside the viewport on a laptop instead of cutting the buttons off
   at the fold. The coin is sized in vh as well as vw for that reason: on a short window
   it is the coin that gives way, not the call to action. */

/* ── the backdrop ───────────────────────────────────────────────────────────
   Warm light falling across the page, and behind it the real trades this platform
   carries, set as a faint tapestry. The words are read from the database, so it is the
   actual list rather than a decorative impression of one.

   Everything is masked away from the middle. A mark sitting on a busy field loses its
   edges, so the centre stays almost clean and the texture only gathers at the corners —
   which is also where a gold sheen would fall on a real printed card. */
/* `overflow: hidden` was on .hero, to clip the scrolling word wall. It also clipped
   anything else that dared leave the section — including the Find menu, which opens
   downward from a button near the bottom of the hero and was being cut off at the fold.
   Reported as the button "hiding behind the tabs".

   The wall lives in .hero__bg, which is absolutely positioned across the whole hero, so
   the clipping belongs THERE. The hero itself no longer clips anything, and a menu can
   open out of it. `isolation: isolate` stays — it keeps the z-index:-1 background behind
   the content without escaping to the page. */
.hero { position: relative; isolation: isolate;
        padding: 0 0 clamp(26px, 3.2vw, 48px); }
.hero__bg {
  position: absolute; inset: 0; z-index: -1; pointer-events: none; overflow: hidden;
  /* The artwork as drawn. Mirroring it into a seamless tile did remove the seams and
     made the two halves reflections of each other, which reads as a pattern rather than
     a scene — the repetition here is the WORD field's job, not the illustration's. */
  background: url('/hero-bg-1280.jpg') center top / cover no-repeat;
}
@media (min-width: 1300px), (min-resolution: 1.5dppx) {
  .hero__bg { background-image: url('/hero-bg.jpg'); }
}
/* The wallpaper is logo-safe by construction — its rays open out around a clear centre,
   so nothing is painted over it. No veil and no bright spot: the only thing left here is
   the join at the bottom, which stops the band ending on a hard horizontal edge against
   the next section. */
.hero__bg::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(250,247,242,0) 78%, var(--face) 100%);
}

.hero__words {
  position: absolute; inset: 0; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-start;
  padding: 0;
  height: auto;
  font-size: clamp(16px, 1.7vw, 22px); font-weight: 800; letter-spacing: .12em;
  text-transform: uppercase; text-align: left;
  opacity: .55; user-select: none;
  -webkit-mask-image: radial-gradient(ellipse 38% 76% at 50% 48%,
      transparent 0 52%, rgba(0,0,0,.4) 74%, #000 100%);
  mask-image: radial-gradient(ellipse 38% 76% at 50% 48%,
      transparent 0 52%, rgba(0,0,0,.4) 74%, #000 100%);
}

/* Each row is a track carrying the trade list TWICE, and the animation runs it from 0
   to -50%. That is seamless only because the two halves are identical character for
   character — the moment they differ, the jump back is visible on every lap. The
   duplicate is built by copying the same array, never by regenerating it. */
/* A touch of separation between rows so the wall reads as bands drifting rather than a
   solid block of moving type. */
.walltrack span {
  background: linear-gradient(176deg,
    #fce4b4 0%, #e8c98a 22%, #a9782a 52%, #d8bc7e 74%, #8a6520 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
}
.wallrow { flex: 0 0 auto; overflow: hidden; white-space: nowrap;
           line-height: 2.05; margin-bottom: .34em; }
.walltrack { display: inline-flex; will-change: transform; }
.walltrack span { padding-right: 1.15em; flex: 0 0 auto; }

@media (prefers-reduced-motion: no-preference) {
  .walltrack { animation: wallscroll var(--secs, 1800s) linear infinite; }
  @keyframes wallscroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
}

@media (max-width: 860px) { .hero__words { opacity: .36; font-size: 14px; } }
@media (prefers-reduced-motion: no-preference) { .hero__bg { will-change: auto; } }
/* Its own band, with the place line optically centred in it — so the gap above the word
   and the gap below it to the ring are the same, at every width. */
.hero__top { min-height: clamp(62px, 8.6vh, 120px); display: grid; place-items: center; }

/* ── the place line ────────────────────────────────────────────────────────
   Sits above the mark and names where the celebration is. It is a real value, not a
   caption: it says Los Angeles until somebody sets a city on their event, and then it
   says theirs. Two hairlines and a node so it reads as a plate on the coin rather than
   a stray label — and it is `min-content` wide so the rules always meet the word,
   whatever the word turns out to be. */
.placeline {
  display: grid; grid-template-columns: 1fr auto auto auto 1fr;
  align-items: center; gap: 12px;
  width: max-content; max-width: 100%; margin-inline: auto;
}
.placeline::before, .placeline::after {
  content: ''; height: 1px; width: clamp(24px, 6vw, 72px);
  background: linear-gradient(90deg, transparent, var(--metal));
}
.placeline::after { background: linear-gradient(90deg, var(--metal), transparent); }
/* The nodes are real grid items, one each side. As a pseudo-element on the word they
   sat on the TEXT baseline and rode up and down with the cap height; as grid children
   they centre on the line itself and the pair stays level with each other. */
.placeline i {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--metal); font-style: normal;
}
.placeline b {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .24em;
  text-transform: uppercase; color: var(--gold-ink); white-space: nowrap;
  /* .24em tracking adds a trailing space after the last letter; without this the word
     sits visibly closer to the left node than the right one. */
  text-indent: .24em;
}
.hero .script { margin-top: 8px; font-size: clamp(26px, 3.6vw, 50px); }
.hero .lede { margin-top: 12px; font-size: var(--t-base); max-width: 46ch; }
.hero__cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 20px; }
.hero__after { margin-top: 10px; }

.coinwrap { perspective: 1100px; perspective-origin: 50% 45%; }

/* Nothing about the coin is text, so nothing about it should behave like text. Without
   this, clicking it repeatedly — which is exactly what it invites — starts a selection
   and paints a blue rectangle across the artwork, and a slow drag lifts a ghost image
   of it. Both make a solid object feel like a picture in a document. */
.coin, .coin__f {
  user-select: none; -webkit-user-select: none;
  -webkit-user-drag: none; -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
.coin__f { pointer-events: none; }

.coin {
  position: relative;
  /* min() against vh too, so a short laptop window shrinks the mark rather than
     pushing the buttons past the fold. */
  width: var(--size, min(44vw, 36vh, 360px));
  font-size: calc(var(--size, min(44vw, 36vh, 360px)) / 25);   /* the ring unit */
  aspect-ratio: 1;
  /* Every part of the coin is derived from these, because a coin is a SHAPE, not a set
     of pixel values. Fixed numbers that look right at 320px turn a small coin into a
     beige disc with a smudge in it — an 18px edge is 5% of a 320px coin and 21% of an
     85px one. Anything rendering the coin at another size overrides these four. */
  /* 8px against a 320px face — about 2.5% of the diameter, which is roughly a 2mm edge
     on a 40mm medal. At 34px it was a tenth of its own width: turning it revealed a slab
     rather than a coin, and a slab with a flat disc for a side reads as broken. Thin
     enough that the edge is a detail you notice, not a wall you look at. */
  /* Rings in em against the coin's own font-size, which is set from its width below —
     so a 42px mark gets a true hairline and a 320px one gets a fine rim, from one set
     of numbers rather than an override per placement. Kept deliberately thin: the
     artwork already ends in a drawn gold circle, so a heavy CSS ring outside it reads as
     a second competing frame rather than the edge of a coin. */
  /* A large mark carries a proper rim; a small one must not. Scaling one set of numbers
     linearly gives the big coin a rim that is too thin OR the 42px header mark a collar
     thicker than the artwork inside it — there is no single ratio that suits both. So
     the coin keeps a real ring and every small placement sets its own hairline. */
  /* Thickness in em like the rings, so it stays 5mm-on-a-50mm-medal at every size
     rather than being a fixed 8px that is a tenth of a small coin and nothing on a big
     one. 2.5em = 10% of the diameter = 5mm on 50mm. The reason this can be a real
     thickness now, where 34px once read as a broken slab, is that the edge is drawn only
     as far as the coin is turned — it is never a wall sitting in front of a face. */
  --depth: 3.4em; --ring-in: .40em; --ring-mid: .50em; --ring-out: .92em;
  /* The edge is FLUSH with the face, not proud of it. Standing proud it showed an equal
     band all the way round at every angle, which is a rim — a coin seen square-on shows
     no edge at all. Flush, the only thing that reveals it is the Z offset: turn the coin
     and a crescent of milled gold swings out on the far side and disappears on the near
     one. That swing is the depth. */
  --edge-out: var(--ring-out);
  /* The three rings below are box-shadow, and box-shadow sits OUTSIDE the layout box —
     the browser reserves no space for it. The outermost is 22px, so without this margin
     the coin silently overlaps 22px of whatever sits above it. Reserved here rather than
     by nudging the thing above, because the space belongs to the coin: anything ever put
     there would hit the same edge. */
  margin: 0 auto;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform .18s ease-out;
}
/* THE EDGE — what makes it a coin rather than a printed circle.
   A second disc pushed back in Z inside the same preserve-3d context. Flat on, it hides
   exactly behind the face; the moment the coin tilts, a crescent of milled gold edge
   swings out on the far side, and it swings to the correct side on its own because it is
   real 3D rather than a shadow pretending. The banding in the gradient is the milling. */
/* A REAL CHILD, not ::before. A parent's pseudo-element does not depth-sort reliably
   against that parent's own children inside preserve-3d: at 180° this disc sat at z=0
   and painted straight over the back face, which is genuinely 4px nearer the viewer, so
   turning the coin showed the milled edge instead of the reverse. As a sibling of the
   two faces it sorts correctly at every angle. */
.coin__edge {
  position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  transform: translate3d(0, 0, 0.01px);
  /* Driven by how far the coin is turned, and this is not a workaround dressed up as
     physics — it IS the physics. A coin face-on shows no edge at all; edge-on it shows
     nothing but. opacity = |sin(turn)| gives exactly that.

     It also settles a fight with the compositor that geometry could not win: the two
     faces sit only 4px either side of this disc, and Chrome would not reliably sort a
     z=0 sibling behind a face at z=-4 no matter how the transform was written. Tying the
     edge to the turn angle means there is nothing to sort — at every angle where a face
     is showing, the edge is simply not painted. */
  opacity: var(--edge-a, 0);
  background:
    linear-gradient(100deg, rgba(255,255,255,.42) 0 12%, rgba(0,0,0,.20) 46%,
                    rgba(255,255,255,.30) 72%, rgba(0,0,0,.26) 100%),
    repeating-linear-gradient(90deg, #b99a58 0 3px, #e0c88c 3px 5px, #9c7c37 5px 8px);
  box-shadow: 0 0 0 var(--ring-in) #c9ab6b, 0 0 0 var(--ring-mid) #a8873f,
              0 0 0 var(--edge-out) #b9994f;
}

/* TWO FACES. The coin is struck on both sides: the mark on the front, the infinity and
   its ornaments on the back. They sit half the thickness apart either side of the edge,
   so the object has a real front, a real back and a real middle rather than being one
   picture that flips.

   backface-visibility on each is what stops the front showing through mirrored during
   the half of the turn it is facing away. */
.coin__f {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border-radius: 50%; backface-visibility: hidden; object-fit: cover;
  box-shadow: 0 0 0 var(--ring-in) var(--cream), 0 0 0 var(--ring-mid) var(--metal),
              0 0 0 var(--ring-out) var(--sink);
}
.coin__f--front {
  transform: translateZ(calc(var(--depth) / 2));
  /* THE SIDE, SEEN AT REST. The 3D edge only appears once the coin turns, so square-on
     it read as a printed circle — a real medal still shows a band of metal below the
     face because you are never looking at it from exactly on-axis. These are offset
     solid shadows: with a 50% radius they emerge as a crescent under the lower rim,
     darkening as they go, which is the side wall catching less light than the face. */
  box-shadow:
    0 0 0 var(--ring-in) var(--cream),
    0 0 0 var(--ring-mid) var(--metal),
    0 0 0 var(--ring-out) var(--sink),
    0 .10em 0 calc(var(--ring-out) - .02em) #cbb079,
    0 .20em 0 calc(var(--ring-out) - .06em) #b2955c,
    0 .30em 0 calc(var(--ring-out) - .12em) #93783f,
    0 .38em 0 calc(var(--ring-out) - .20em) #7d6533;
}
.coin__f--back  { transform: translateZ(calc(var(--depth) / -2)) rotateY(180deg); }
.coin__gloss {                       /* the specular, tracking the pointer */
  position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  transform: translateZ(calc(var(--depth) / 2 + 1px)); backface-visibility: hidden;
  background: radial-gradient(circle 46% at var(--sx, 50%) var(--sy, 50%),
    rgba(255,255,255,.85), rgba(255,255,255,.18) 42%, transparent 66%);
  mix-blend-mode: overlay;
}
/* the shadow it casts, sliding opposite the light and flattening as it leans back */
.coin-shadow {
  width: min(32vw, 26vh, 264px); height: 26px; margin: 18px auto 0; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(60,44,20,.42), transparent 74%);
  transform: translateX(calc(var(--ry, 0deg) * -1.4))
             scaleX(calc(1 - (var(--rx, 0deg) / 90)));
  transition: transform .18s ease-out;
}


/* ── the flip ──────────────────────────────────────────────────────────────
   Click the coin and it turns once on its own axis. This is the one place the 34px of
   edge is the whole point: at ninety degrees the face is gone and you are looking
   straight at the milled side.

   backface-visibility matters here. Without it, everything between 90° and 270° shows
   the face MIRRORED — the mark backwards, "PARTY LIGHTS" reversed. Hidden, the reverse
   of the coin is the blank milled disc behind it, which is what the back of a medallion
   actually looks like.

   TO TURN THIS OFF: set SPIN to false at the top of ds.js. One line, nothing else. */
.coin[data-spin] { cursor: pointer; }


@media (prefers-reduced-motion: reduce) {
  .coin, .coin-shadow { transition: none; transform: none; }
}

/* ══ ornament ══════════════════════════════════════════════════════════════
   Fine gold rules and marks. Drawn once, never animated: an ornament that performs is
   an ornament you notice twice and resent the third time. */

/* margin-inline:auto, not `margin:0`. Written as `margin:0` this beats `.center`'s
   `margin-inline:auto` on source order alone and the rule sits hard against the left
   edge of its column while the class that was meant to centre it looks correct. */
.rule {
  height: 1px; border: 0; margin-block: 0; margin-inline: auto;
  background: linear-gradient(90deg, transparent, var(--line-gold) 22%,
              var(--metal) 50%, var(--line-gold) 78%, transparent);
}
.rule--short { width: 132px; }
.orn-star { width: 18px; height: 18px; color: var(--metal); flex: 0 0 auto; }
.orn-rule { display: flex; align-items: center; gap: 14px; }
.orn-rule::before, .orn-rule::after {
  content: ''; height: 1px; flex: 1 1 auto;
  background: linear-gradient(90deg, transparent, var(--line-gold), transparent);
}
.corner {                        /* a hairline gold corner on premium surfaces only */
  position: absolute; width: 26px; height: 26px; pointer-events: none;
  border: 1px solid var(--line-gold); border-radius: 3px;
}
.corner--tl { top: 12px; left: 12px;  border-right: 0; border-bottom: 0; }
.corner--br { bottom: 12px; right: 12px; border-left: 0; border-top: 0; }

/* ══ buttons ═══════════════════════════════════════════════════════════════
   Dark label on gold. White on #c08430 is 2.4:1; the ink is 7.9:1. */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 48px; padding: 13px 26px;
  font-size: var(--t-base); font-weight: 600; letter-spacing: -.005em; line-height: 1;
  border: 1px solid transparent; border-radius: var(--r-pill);
  text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: transform var(--fast) var(--ease), box-shadow var(--fast) var(--ease),
              background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn--gold {
  background: linear-gradient(178deg, var(--metal-lo), var(--metal));
  color: var(--ink); border-color: rgba(120, 84, 24, .5);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.5), var(--lift-1);
}
.btn--gold:hover { transform: translateY(-2px); box-shadow: inset 0 1px 0 rgba(255,255,255,.5), var(--lift-2); }

.btn--ghost { background: var(--paper); color: var(--ink); border-color: var(--line-strong); }
.btn--ghost:hover { border-color: var(--metal); background: var(--paper-warm); transform: translateY(-2px); }

/* #7's primary is INK, not gold — the gold is the coin's job and a gold button beside a
   gold medallion makes the page argue with itself. Gold stays a rule and a rim. */
.btn--pri, .btn--night { background: var(--night); color: #fff; border-color: var(--night); }
.btn--pri:hover, .btn--night:hover { background: var(--night-soft); border-color: var(--night-soft);
                                     transform: translateY(-2px); box-shadow: var(--lift-2); }
.btn--sec { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn--sec:hover { border-color: var(--gold-ink); color: var(--gold-ink); transform: translateY(-2px); }

.btn--quiet { background: none; color: var(--ink-2); min-height: 40px; padding: 8px 12px; }
.btn--quiet:hover { color: var(--gold-ink); }

/* The one destructive button. OUTLINED, not filled: a solid red block is the loudest
   thing on a page and this sits at the bottom of a settings panel next to "Save" — it
   should be findable when you want it and not pull the eye when you do not. It fills on
   hover, so the press itself is unambiguous.
   --danger is the only red the site uses (line 66); a second one here would be the third
   shade of red on a site that had decided on one. */
.btn--danger { background: var(--paper); color: var(--danger); border-color: var(--danger); }
.btn--danger:hover { background: var(--danger); color: #fff; border-color: var(--danger);
                     transform: translateY(-2px); }
.btn--sm { min-height: 40px; padding: 10px 18px; font-size: var(--t-sm); }
.btn--lg { min-height: 56px; padding: 17px 34px; font-size: var(--t-md); }
.btn--wide { width: 100%; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; }
.btn.is-busy { color: transparent; position: relative; pointer-events: none; }
.btn.is-busy::after {
  content: ''; position: absolute; width: 17px; height: 17px; border-radius: 50%;
  border: 2px solid var(--ink); border-top-color: transparent; animation: turn .7s linear infinite;
}
@keyframes turn { to { transform: rotate(360deg); } }

.link {
  color: var(--gold-ink); font-weight: 600; text-decoration: none;
  display: inline-flex; align-items: center; gap: 6px;
}
.link:hover { text-decoration: underline; text-underline-offset: 4px; }
/* .link on a <button>. A button carries the UA's border, background and font, so the
   same class that reads as a link on an <a> reads as a small grey box on a <button> —
   which is what it looked like in the shortlist gate. The element is chosen by what it
   DOES (this one does not navigate, so it must be a button); the class decides how it
   looks. Those two should not have to agree. */
button.link {
  border: 0; background: none; padding: 0; margin: 0;
  font: inherit; font-weight: 600; cursor: pointer; text-align: left;
}
.link--on-night { color: var(--gold-on-night); }

/* ══ surfaces ══════════════════════════════════════════════════════════════ */

.card {
  position: relative; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: clamp(18px, 2vw, 26px); box-shadow: var(--lift-1);
}
.card--flat { box-shadow: none; }
.card--pad-lg { padding: clamp(24px, 3vw, 40px); }
.grid { display: grid; gap: 18px; align-items: start; }   /* start: short cards must not stretch */
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); }

/* ══ THE DOORS — the mode gate ═════════════════════════════════════════════
   The first decision, and the only thing on the first screen that can be clicked apart
   from signing in. Nothing on this site searches, filters or recommends until it knows
   which of these you are, because the same directory answers a different question for
   each of them — and the answer is changeable at any point from the mode bar. */

.doors { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.door {
  position: relative; display: flex; flex-direction: column; gap: 10px;
  padding: clamp(22px, 2.4vw, 30px);
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-md);
  text-decoration: none; color: inherit; text-align: left;
  box-shadow: var(--lift-1); cursor: pointer;
  transition: transform var(--mid) var(--ease), box-shadow var(--mid) var(--ease),
              border-color var(--mid) var(--ease);
}
.door:hover { transform: translateY(-3px); box-shadow: var(--lift-2); border-color: var(--line-gold); }
.door__n { font-size: var(--t-xs); font-weight: 700; letter-spacing: .2em; color: var(--metal); }
.door__t { font-size: var(--t-lg); font-weight: 800; letter-spacing: -.02em; line-height: 1.2; }
.door__d { font-size: var(--t-sm); color: var(--ink-2); line-height: 1.6; }
.door__go { margin-top: auto; padding-top: 6px; font-size: var(--t-sm); font-weight: 600; color: var(--gold-ink); }
.door[aria-current="true"] { border-color: var(--metal); box-shadow: var(--lift-2), 0 0 0 1px var(--metal) inset; }

/* ══ quick links ═══════════════════════════════════════════════════════════
   The bottom row. For the person who already knows what they want and does not want to
   answer a question first — one tap straight into the trade they came for. It does not
   replace the doors, it skips them, which is the whole point of a quick link.

   Read from the database in the app's own `sort` order, so the row cannot drift from
   what the directory actually contains. `coordinator` is excluded: it is the one service
   the platform provides itself, not a trade you browse. */

.quick { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.quick__l {
  width: 100%; text-align: center; margin-bottom: 4px;
  font-size: var(--t-xs); font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
.quick a {
  display: inline-flex; align-items: center; min-height: 38px; padding: 8px 16px;
  border-radius: var(--r-pill); border: 1px solid var(--line-gold);
  background: var(--paper); color: var(--ink);
  font-size: var(--t-sm); font-weight: 600; text-decoration: none;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease),
              transform var(--fast) var(--ease);
}
.quick a:hover { border-color: var(--metal); background: var(--paper-warm); transform: translateY(-2px); }
.quick a.quick--all { border-style: dashed; color: var(--gold-ink); }

/* ══ mode bar — how you change your mind ═══════════════════════════════════ */

.modebar {
  display: flex; align-items: center; gap: 10px 16px; flex-wrap: wrap;
  padding: 12px 18px; border-radius: var(--r-pill);
  background: var(--paper); border: 1px solid var(--line-gold); box-shadow: var(--lift-1);
}
.modebar__l { font-size: var(--t-sm); color: var(--ink-2); }
.modebar__v { font-weight: 700; color: var(--ink); }

.chip {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 38px; padding: 8px 15px; border-radius: var(--r-pill);
  background: var(--paper); border: 1px solid var(--line-strong);
  font-size: var(--t-sm); font-weight: 600; cursor: pointer; color: inherit;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.chip:hover { border-color: var(--metal); }
.chip[aria-pressed="true"] { background: var(--metal-pale); border-color: var(--metal); }

/* ══ fields ════════════════════════════════════════════════════════════════
   Always a real label. Never placeholder-only — the label disappears exactly when a
   person is typing and most needs it. */

.field { display: block; }
.field__l {
  display: block; font-size: var(--t-xs); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2); margin-bottom: 7px;
}
/* A field is a RAISED surface, not a hole cut in the paper.
   These were flat white rectangles with a grey hairline and a grey chevron — correct,
   and completely inert next to a struck-coin medallion and gold rules. Four things fix
   that, and none of them is decoration for its own sake:

     1. a barely-there vertical gradient, paper into warm paper. One value apart. It is
        what stops a white box on off-white paper reading as a gap.
     2. an inset highlight along the top edge, which is how a raised surface catches
        light and the same trick the coin uses.
     3. a real shadow, small and warm-tinted rather than grey — a grey shadow on cream
        paper looks like dirt.
     4. the chevron in the site's own metal, not the OS's grey.

   All of it is one or two percent. A form somebody fills in every visit should be
   pleasant and then get out of the way; anything louder is a field competing with its
   own contents. */
/* The bare selectors carry the same look, so an input with no class is styled rather
   than raw. This is the third time unstyled fields have shipped: an input written
   without `class="field__c"` is invisible in review — it is valid, it works, and it
   looks like the browser's default sitting in the middle of a designed form. There were
   41 of them across 12 pages when this was written, including every field of the
   "suggest a business" block on eight pages and eight more on /plan.
   Remembering a class on every input is not a system. This is.
   Checkboxes and radios are excluded: they are drawn by .tick and .pw, and giving them
   a 50px text-field box would break both. */
input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]),
textarea,
.field__c, .select {
  width: 100%; min-height: 50px; padding: 13px 15px;
  color: var(--ink);
  background-color: var(--paper);
  background-image: linear-gradient(180deg, #fff 0%, var(--paper) 46%, #fdfbf7 100%);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9),
              0 1px 1.5px rgba(60, 42, 20, .05),
              0 2px 6px rgba(60, 42, 20, .035);
  transition: border-color var(--mid) var(--ease), box-shadow var(--mid) var(--ease),
              background-color var(--mid) var(--ease);
}

/* Hover warms the edge toward gold and lifts it a hair. Nothing moves position: a field
   that jumps under the pointer is a field you have to aim at twice. */
input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]):hover:not(:disabled), textarea:hover:not(:disabled), .field__c:hover:not(:disabled), .select:hover:not(:disabled) {
  border-color: var(--line-gold);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9),
              0 1px 2px rgba(60, 42, 20, .06),
              0 4px 12px rgba(60, 42, 20, .06);
}

input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]):focus, textarea:focus, .field__c:focus, .select:focus {
  outline: none; border-color: var(--metal);
  background-image: linear-gradient(180deg, #fff 0%, #fff 60%, var(--paper) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .95),
              0 0 0 3px rgba(192, 132, 48, .18),
              0 4px 14px rgba(60, 42, 20, .07);
}

input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]):disabled, textarea:disabled, .field__c:disabled, .select:disabled {
  background-image: none; background-color: var(--sink);
  box-shadow: none; color: var(--ink-3); cursor: not-allowed;
}

/* A native select renders as the OS control — grey, square, matching nothing here.
   The chevron is drawn in --gold-lg and sits inside a faint recessed well, so the part
   you press looks like a part you press rather than an icon floating on the edge. */
.select {
  appearance: none; -webkit-appearance: none; padding-right: 44px; cursor: pointer;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23a9782a' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.75 6 6.25 11 1.75'/%3E%3C/svg%3E"),
    linear-gradient(180deg, rgba(243, 234, 215, 0) 0%, rgba(243, 234, 215, .55) 100%),
    linear-gradient(180deg, #fff 0%, var(--paper) 46%, #fdfbf7 100%);
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: right 16px center, right 0 top 0, 0 0;
  background-size: 12px 8px, 40px 100%, 100% 100%;
}
/* The chevron settles a pixel on hover — the smallest possible "this opens".
   `center 1px` is not legal in a background-position; the offset has to be measured from
   an edge, so it is `top calc(50% + 1px)`. The first version silently invalidated the
   whole declaration, which is how a background rule disappears without any error. */
.select:hover:not(:disabled) {
  background-position: right 16px top calc(50% + 1px), right 0 top 0, 0 0;
}
.select:disabled { background-image: none; }

/* Safari and Firefox draw their own focus ring on top of ours. */
.select:focus-visible, .field__c:focus-visible { outline: none; }
.field__h { font-size: var(--t-sm); color: var(--ink-3); margin-top: 6px; }
.field__e { font-size: var(--t-sm); color: #a1301b; font-weight: 600; margin-top: 6px; }

/* ══ menu — one dropdown, used everywhere ══════════════════════════════════ */

.menu { position: relative; }
.menu__p {
  position: absolute; right: 0; top: calc(100% + 10px); z-index: 90; min-width: 264px;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--lift-2); padding: 8px;
  /* These panels hang off the header, so their top is fixed and everything they add
     grows downwards. The account menu already runs 380px; on a laptop with the window
     shortened, or a phone in landscape, the last item — Sign out — went off the bottom
     with no way to reach it. Capped to what is left below the header and given its own
     scroll, so a panel can grow without ever putting an item out of reach. */
  max-height: calc(100dvh - var(--shell-h, 72px) - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.menu__p[hidden] { display: none; }
.menu__i {
  display: block; padding: 11px 13px; border-radius: var(--r-sm);
  text-decoration: none; color: var(--ink); width: 100%; text-align: left;
  background: none; border: 0; cursor: pointer;
}
.menu__i:hover { background: var(--paper-warm); }
.menu__i b { display: block; font-size: var(--t-sm); font-weight: 700; }
.menu__i span { display: block; font-size: var(--t-xs); color: var(--ink-2); }
.menu__sep { height: 1px; background: var(--line); margin: 8px 4px; }

/* ══ shell — one header and one footer, on every page ══════════════════════ */

.shell { position: sticky; top: 0; z-index: 80; background: rgba(253,253,251,.86);
         border-bottom: 1px solid var(--line); backdrop-filter: blur(12px); }
/* ── the header, stacked ────────────────────────────────────────────────────
   The wordmark and the city sat side by side and took 413px of a 1200px bar between
   them, leaving the nav 559px for eight tabs that will not get fewer. Stacking the city
   under the wordmark hands about 166px back — roughly thirty per cent more room — and
   reads better besides: the city is a property OF this place, not a sibling of it.

   Done in grid rather than by rewriting the header, because that markup is repeated in
   every page on the site and there is a check that fails the build if two of them
   disagree. Two siblings, two rows of the same column: no markup moves, nothing can
   drift, and the header stays one thing to edit.

   Both stay independently clickable — the wordmark goes home, the city opens the place
   picker. The city is deliberately smaller and quieter so the two are never mistaken for
   one target, and it keeps its pin, which is what says "this is a place control" rather
   than a strapline. nav.js measures the header on load and on resize, so the second nav
   row follows whatever height this ends up being. */
.shell__in {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 16px;
  min-height: 68px;
  /* The containing block for the medallion, which is positioned against this box so it
     can centre across BOTH rows rather than only the one its link sits in. */
  position: relative;
  /* BREATHING ROOM, and it was measured at zero. The two rows come to 74px — taller than
     the 68px min-height — so `align-items: center` had nothing left to centre with and the
     medallion sat flush against the top edge of the page: 0px above it, 1px below the
     city. It read as a logo that had been cropped by the header rather than placed in it.
     Asymmetric on purpose: the optical centre of a circular mark with a wordmark beside it
     sits slightly high, so a hair more room below than above looks even. nav.js measures
     the result into --shell-h, so the subnav follows without being told. */
  padding-block: 7px 9px;
}
.shell__brand { grid-column: 1; grid-row: 1; }
.place        { grid-column: 1; grid-row: 2; margin-top: -6px; }
.shell__nav   { grid-column: 2; grid-row: 1 / 3; }
.shell__r     { grid-column: 3; grid-row: 1 / 3; }
.burger       { grid-column: 3; grid-row: 1 / 3; }

/* One column on a phone: the nav is a burger there, so the grid would otherwise leave a
   wide empty middle and push the city away from the name it belongs to. */
@media (max-width: 860px) {
  .shell__in { grid-template-columns: auto 1fr auto; }
  /* This `.place { margin-left: 36px }` was dead: an identical selector 2200 lines later
     wins on source order regardless of the media query, so the phone got the desktop
     value anyway. Removed rather than left to be read as the rule that governs phones. */
}
/* 46px medallion + 11px gap = 57px, which is where the wordmark's letters begin. The
   city control below the brand is offset by exactly this, so the pin starts on the "L"
   of LA PARTY. Kept as a variable because it is the SAME distance in two places and they
   drifted apart the moment they were two numbers. */
:root { --brand-text-x: 57px; }

/* ══ anchors land below the sticky header, not behind it ═══════════════════════
   Every in-page link on this site landed wrong before this rule. Measured, on the real
   pages, as the gap between the target's top and the bottom of the header:

       /about#refer      -85px    the "Suggest a business" subtab landed on nothing —
                                  the target sat entirely behind the header
       /terms#listing    -85px    same
       /terms#access     +11px    from `scroll-margin-top: 96px` on `.legal h2`, guessed
       /privacy#delete   +11px    once against a header that is now 85px tall

   One rule, taking the number from the header's own measurement rather than from anybody's
   memory of it. --shell-h is written by nav.js from getBoundingClientRect, so this follows
   the header at every width and through a font landing late; the fallback only applies for
   the instant before that runs, and nav.js re-lands the scroll once it has measured.

   14px is a deliberate breathing space, not slack — a heading flush against the underside
   of a sticky bar reads as clipped.

   Scoped to `main` on purpose. `scroll-margin` applies to whatever scrollport does the
   scrolling, so putting this on every [id] in the document would also push targets inside
   the page's own scrolling panels — the admin queue, the trade ticklist, the vendor
   preview column — down by the height of the page header, which has nothing to do with
   them. */
/* --sticky-h, not --shell-h. The header is not the only thing that sticks: the subnav
   row sits under it, and an offset that counts only the header lands the target BEHIND
   that row. Measured on /pro#judgment before this: target at 99px, subnav occupying
   85-137, so the section's first 38px were covered. nav.js measures the whole stack. */
main :where([id]) { scroll-margin-top: calc(var(--sticky-h, var(--shell-h, 85px)) + 14px); }
/* THE MEDALLION IS CENTRED AGAINST BOTH LINES, not against the wordmark alone.
   The brand link lives in row 1 of the header grid and the city in row 2, so a medallion
   centred inside the link centres on the wordmark only — measured at y=30 while the text
   block (wordmark top 16, city bottom 71) centres at 43. Thirteen pixels high, and it
   read exactly as reported: the logo sitting above the words instead of beside them.

   It cannot span both rows from inside the link, and the markup is repeated in 36 files
   so it cannot be re-nested. So the medallion is taken out of the link's flow and
   centred against the header's own box, which is the same thing as the two lines. The
   link keeps its space with padding, and the image is still inside the <a>, so clicking
   it still goes home. */
.shell__brand { display: flex; align-items: center; gap: 11px; text-decoration: none; color: inherit;
                padding-left: var(--brand-text-x);
                /* The medallion is out of flow now, so it contributes no height and the
                   header collapsed from 85px to 69px the moment it was lifted out. This
                   holds the row open to the size of the thing sitting in it. */
                min-height: 46px; }
.shell__brand img { width: 46px; height: 46px; border-radius: 50%;
                    box-shadow: 0 0 0 1px var(--metal);
                    position: absolute; left: var(--page); top: 50%; translate: 0 -50%; }
.shell__wm { font-weight: 800; letter-spacing: -.02em; font-size: 1.08rem; }
.shell__wm i { font-style: normal; color: var(--gold-ink); }
.shell__nav { display: flex; gap: clamp(8px, 1.4vw, 22px); margin-inline: auto; }
.shell__nav a { color: var(--ink-2); text-decoration: none; font-size: var(--t-sm); font-weight: 600; padding: 8px 2px; }
.shell__nav a:hover { color: var(--gold-ink); }
.shell__r { display: flex; align-items: center; gap: 10px; }

/* ── who is signed in, in the bar itself ────────────────────────────────────
   Two lines that mirror the brand opposite: name over role, as the wordmark sits over
   the city. Right-aligned so the outer edge stays flush with the Find button beside it,
   which is what stops a long business name pushing the row about.

   `--who-max` rather than a character count: a name is cut by the space the header has,
   not by how many letters it happens to contain. It ellipsises instead of wrapping,
   because a header that changes height when somebody signs in moves every anchor on the
   page — --shell-h is measured from it. */
.who { display: grid; justify-items: end; gap: 1px; line-height: 1.18; text-align: right; }
.who__n {
  font-weight: 700; font-size: var(--t-sm); color: var(--ink);
  max-width: var(--who-max, 168px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.who__r {
  font-size: var(--t-xs); font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-3);
  max-width: var(--who-max, 168px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.who:hover .who__n { color: var(--gold-ink); }
@media (max-width: 1100px) { .who { --who-max: 118px; } }
.burger { display: none; width: 44px; height: 44px; align-items: center; justify-content: center;
          background: var(--paper); border: 1px solid var(--line-strong); border-radius: var(--r-sm); cursor: pointer; }
@media (max-width: 860px) {
  .burger { display: inline-flex; margin-left: auto; }
  .shell__nav, .shell__r { display: none; }
  /* ── the open burger, on a GRID header ──────────────────────────────────
     These rules were written when .shell__in was a flex row: `flex-wrap: wrap` let the
     nav and the buttons wrap onto their own lines. The header became a GRID when the
     brand and city were stacked, and flex-wrap does nothing to a grid — so the nav kept
     its base placement of `grid-column: 2` and opened as a 97px-wide column, 2634px
     tall, while the buttons sat at x=361 in a 390px viewport, off the edge. The header
     went from 82px to 2666px. Measured, on /, at 390px wide.

     On a grid the two have to be told to span every column and take their own rows. */
  /* Every tab's children are shown at once, which is 22 links — a 1519px header on an
     800px phone. Nothing was off-screen exactly; it was just taller than the screen, with
     the page behind it locked, which is what "stuck and not moving" is. Capped to the
     viewport and allowed to scroll inside itself, so the last item is always reachable
     and the page never grows a second scrollbar. */
  .shell.is-open .shell__in { padding-bottom: 16px;
                              max-height: calc(100dvh - 1px); overflow-y: auto; overscroll-behavior: contain; }
  .shell.is-open .shell__nav,
  .shell.is-open .shell__r { grid-column: 1 / -1; grid-row: auto; width: 100%; }
  /* THE MEDALLION GOES BACK IN FLOW WHEN THE MENU IS OPEN.
     It is absolutely positioned and centred on .shell__in so it can sit across both the
     wordmark and the city rows. Open, that box becomes as tall as the screen — so the
     medallion centred itself halfway down the menu, floating over the links with its left
     edge clipped. Seen in a screenshot; the measurements all looked fine. In flow it sits
     beside the wordmark where it belongs, and the padding that reserved its absolute slot
     comes off with it. */
  .shell.is-open .shell__brand { padding-left: 0; }
  .shell.is-open .shell__brand img { position: static; translate: none; }
  .shell.is-open .shell__nav { display: flex; flex-direction: column; margin: 8px 0 0;
                               border-top: 1px solid var(--line); padding-top: 10px; }
  .shell.is-open .shell__nav a { padding: 12px 2px; font-size: var(--t-md); }
  .shell.is-open .shell__r { display: flex; margin-top: 10px; }
  /* Each tab's own children sit under it, in flow — there is no room for a floating
     panel on a phone and nothing to hover with. */
  .shell.is-open .nd { width: 100%; }
  .shell.is-open .nd__p { position: static; display: block; opacity: 1; transform: none;
                          box-shadow: none; border: 0; background: none; padding: 0 0 0 12px;
                          min-width: 0; width: 100%; }
  .shell.is-open .nd__p a { padding: 9px 2px; }
  .shell.is-open .nd__c { display: none; }
  body.is-locked { overflow: hidden; }
}

/* ══ the proof row ═════════════════════════════════════════════════════════
   Three numbers that are all true of the app as built: the quote cap is six, the
   commission is zero. Numerals set large so they carry the band, which is the one place
   --gold-lg is legitimate. */

.gains { display: grid; gap: clamp(12px, 1.6vw, 20px);
         grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }
.gain {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 26px 24px 28px; text-align: left;
  transition: transform .22s cubic-bezier(.2,.9,.25,1.2), box-shadow .24s, border-color .22s;
}
.gain:hover { transform: translateY(-5px); border-color: var(--metal);
              box-shadow: 0 20px 40px -18px rgba(60, 44, 20, .32); }
.gain b { display: block; font-size: 46px; font-weight: 800; line-height: 1;
          letter-spacing: -.04em; color: var(--gold-lg); margin-bottom: 12px; }
.gain h3 { font-size: var(--t-md); font-weight: 800; margin-bottom: 6px; }
.gain p { font-size: var(--t-sm); color: var(--ink-2); }

/* ══ footer ════════════════════════════════════════════════════════════════ */

.foot { background: var(--night); color: var(--ink-on-night);
        padding: clamp(44px, 5vw, 74px) 0 34px; }
.foot__g { display: grid; gap: clamp(24px, 3vw, 50px);
           grid-template-columns: minmax(220px, .85fr) 2.2fr; align-items: start; }
@media (max-width: 860px) { .foot__g { grid-template-columns: 1fr; } }
/* The DARK mark on the dark footer. The light artwork paints its tile warm-white, so on
   #171320 it reads as a bright disc punched into the panel — and the cream ring that
   frames it beautifully on paper is the brightest thing on the whole band. Two files,
   one per background, neither of them edited. */
.foot__brand img { width: 104px; height: auto; border-radius: 50%; margin-bottom: 16px;
                   box-shadow: 0 0 0 1px var(--metal),
                               0 10px 26px rgba(0, 0, 0, .45); }
/* The same two-part lockup as the hero: script for the first half, the uppercase rule
   face for the second. One tagline, set one way, wherever it appears. */
/* Both halves stay on ONE line each. A 16ch cap broke the second half after "LAST" and
   left GUEST stranded on a line of its own — a two-word tagline reading as three. The
   sizes are set to fit the narrowest the footer column ever gets rather than trusting a
   character count, which does not know how wide the tracking makes these. */
.motto { max-width: none; }
.motto .script { display: block; font-size: clamp(23px, 2.1vw, 29px);
                 color: var(--gold-on-night); white-space: nowrap; }
.motto .rule   { display: block; margin-top: 3px; white-space: nowrap;
                 font-weight: 800; text-transform: uppercase; letter-spacing: .055em;
                 font-size: clamp(12px, 1.05vw, 14.5px); color: #fff; }
.foot__cols { display: grid; gap: 24px;
              grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr)); }
.foot h5 { font-size: 11px; font-weight: 800; letter-spacing: .2em;
           text-transform: uppercase; color: #fff; margin-bottom: 13px; }
.foot a { display: block; font-size: var(--t-sm); color: var(--ink-on-night);
          text-decoration: none; margin-bottom: 9px; }
.foot a:hover { color: var(--gold-on-night); }
.foot__tags { display: flex; flex-wrap: wrap; gap: 9px; margin-top: clamp(28px, 3.2vw, 46px); }
.foot__tags span { font-size: var(--t-sm); font-weight: 600; padding: 8px 15px;
                   border-radius: var(--r-pill); background: rgba(255, 255, 255, .07); }
.foot__end { display: flex; flex-wrap: wrap; gap: 12px 18px; justify-content: space-between;
             margin-top: 26px; padding-top: 22px;
             border-top: 1px solid rgba(255, 255, 255, .12); font-size: var(--t-sm); }

/* The start menu is wider than the sign-in one and centred under its button, because it
   opens from the middle of the hero rather than the right edge of the header. */
.menu__p--wide { min-width: 300px; left: 50%; right: auto; transform: translateX(-50%); }
@media (max-width: 420px) {
  .menu__p--wide { position: fixed; left: 12px; right: 12px; transform: none;
                   min-width: 0; width: auto; }
}

/* Two doors, not four: the front door is for the everyday user. A planner and a vendor
   both arrive through Sign in, so neither gets a card competing for the same attention. */
.doors--two { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); max-width: 860px; margin-inline: auto; }

/* The back doors: reachable, never advertised. A planner and a vendor both know they
   have an account here; somebody planning their own birthday should not have to read
   past two audiences that are not them to find their own way in. */
.menu__i--quiet { font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
.menu__i--quiet:hover { color: var(--gold-ink); }

/* A component that sets its own display would otherwise beat the UA's [hidden] rule,
   and an element meant to be hidden stays on screen. */
[hidden] { display: none !important; }

/* ══ the feed ══════════════════════════════════════════════════════════════
   A column, not a grid. Posts are wildly uneven — three words or four hundred, with a
   photograph or without — and a grid of uneven cards leaves ragged holes down the page.
   A single column reads in one motion and never has a gap in it. */

.feedbar { display: flex; gap: 14px 20px; align-items: flex-end; flex-wrap: wrap;
           padding-bottom: 18px; border-bottom: 1px solid var(--line); }
.feedbar .row { gap: 8px; }

/* The feed scrolls inside its own column so the rail beside it stays put and the page
   itself never becomes a mile long. Capped in vh rather than px: on a short laptop a
   fixed height would push the footer off, and on a tall screen it would waste half of it. */
/* flex: 0 0 auto on the children is load-bearing, not tidiness. A flex item's automatic
   minimum size (min-height: auto) is what normally stops it shrinking below its content
   — and that only applies while `overflow` is `visible`. `.deco-notch`, which marks an
   event card, sets `overflow: hidden`; that flipped the minimum to 0 and every event in
   the feed collapsed to a 54px sliver showing nothing but its badge, while ordinary
   posts beside it stayed 196px. The content was in the DOM the whole time. */
.feed > * { flex: 0 0 auto; }
.feed {
  display: flex; flex-direction: column; gap: 16px;
  max-height: clamp(420px, 74vh, 900px); overflow-y: auto;
  padding-right: 6px; scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}
.feed::-webkit-scrollbar { width: 8px; }
.feed::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 99px; }
.feed::-webkit-scrollbar-track { background: transparent; }
.post {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: clamp(18px, 2vw, 26px); box-shadow: var(--lift-1);
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.post:hover { border-color: var(--line-gold); box-shadow: var(--lift-2); }
.post__head { display: flex; align-items: baseline; justify-content: space-between;
              gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.post__who { text-decoration: none; color: inherit; }
.post__who b { display: block; font-size: var(--t-base); font-weight: 700; }
.post__who span { display: block; font-size: var(--t-xs); color: var(--ink-3); }
.post__who:hover b { color: var(--gold-ink); }
.post__ago { font-size: var(--t-xs); color: var(--ink-3); white-space: nowrap; }
.post__t { font-size: var(--t-lg); font-weight: 800; letter-spacing: -.015em;
           line-height: 1.25; margin-bottom: 8px; }
.post__when {
  display: inline-block; margin-bottom: 10px; padding: 5px 12px;
  border-radius: var(--r-pill); background: var(--paper-warm);
  border: 1px solid var(--line-gold);
  font-size: var(--t-xs); font-weight: 700; color: var(--gold-ink);
}
.post__b { color: var(--ink-2); line-height: 1.7; }
.post__img { width: 100%; border-radius: var(--r-sm); margin-top: 14px; }

/* ══ the split page ════════════════════════════════════════════════════════
   Not every page is a centred column. The homepage is one because it has a single
   decision on it; a feed has two jobs at once — steering and reading — and stacking
   them means scrolling past the controls to reach the content and back up to change it.

   So: a rail that holds the title, the controls and the filters, and a column that
   holds the reading. The rail sticks on desktop and leads on mobile, where a sticky
   sidebar is just a thing in the way. */

.split { display: grid; gap: clamp(24px, 3vw, 44px); align-items: start; }
@media (min-width: 900px) { .split { grid-template-columns: 300px minmax(0, 1fr); } }
@media (min-width: 900px) {
  /* Follows the chrome instead of guessing at it. `top: 88px` was written when the
     header was the only sticky thing and was about that tall; the header is 85px now and
     on /browse, /idea, /news, /nightout, /contact, /tools and /post-event there is a
     subnav under it, making the stack 137px — so the rail stuck UNDERNEATH the bar and
     its first line was clipped. Same variable the anchors use, so the two can never
     disagree again. */
  .split__rail { position: sticky; top: calc(var(--sticky-h, var(--shell-h, 85px)) + 14px); }
}
.split__rail .field + .field { margin-top: 14px; }
.rail__t { font-size: var(--t-2xl); font-weight: 800; letter-spacing: -.024em;
           line-height: 1.08; margin-bottom: 10px; }
.rail__sec { padding-top: 18px; margin-top: 18px; border-top: 1px solid var(--line); }
/* Inside a filter rail the sections are tighter — every pixel of rail height is a
   pixel of sticky travel lost. */
.split__rail form .rail__sec { padding-top: 14px; margin-top: 14px; }
.split__rail form .field + .field { margin-top: 10px; }
.split__rail form .field__c, .split__rail form .select { min-height: 42px; }

/* One dropdown, everywhere. A bare <select> renders as the operating system's own
   control — grey, square, a different shape on every machine — and one of those beside
   a styled one is the fastest way to make a page look half-finished. This catches any
   select that reaches a page without its class rather than relying on remembering. */
select:not([multiple]) { appearance: none; -webkit-appearance: none; }
select:not(.select):not([multiple]) {
  width: 100%; min-height: 50px; padding: 13px 40px 13px 15px;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm); cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%235d5669' stroke-width='1.7' stroke-linecap='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 15px center; background-size: 12px 8px;
}
select:not(.select):not([multiple]):focus {
  outline: none; border-color: var(--metal); box-shadow: 0 0 0 3px rgba(192,132,48,.18);
}

/* ── an event in the feed ──────────────────────────────────────────────────
   Marked, because "come to this on Thursday" and "look what we made" ask completely
   different things of a reader and a feed that treats them identically wastes both. */
.post--event { border-color: var(--line-gold); }
.post__kind {
  display: inline-block; margin-bottom: 10px; padding: 4px 11px;
  border-radius: var(--r-pill); background: var(--night); color: var(--gold-on-night);
  font-size: 10px; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
}
.tix {
  display: flex; align-items: center; gap: 10px 16px; flex-wrap: wrap;
  margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line);
}
.tix__v { font-size: var(--t-sm); font-weight: 600; }
.tix__p { font-size: var(--t-sm); color: var(--ink-2); }
.tix .btn { margin-left: auto; }

/* ══ deco ══════════════════════════════════════════════════════════════════
   Gatsby line-work, at the weight of a hairline. Every one of these is drawn with a
   border or a gradient rather than an image, so it stays sharp at any size and costs
   nothing to load — and every one is tied to a real element (a trade, a business, a
   post) rather than floated over the page as decoration, which is what makes ornament
   feel designed in rather than added afterwards.

   The rule they all obey: --metal for the line, never for a word, and never more than
   one motif on a single surface. Two flourishes on one card is a doily. */

/* A stepped deco crown — the fan over a theatre door. Sits on premium surfaces only. */
.deco-crown { position: relative; }
.deco-crown::before {
  content: ''; position: absolute; left: 50%; top: 0; translate: -50% -1px;
  width: 64px; height: 10px; pointer-events: none;
  background:
    linear-gradient(var(--metal), var(--metal)) center bottom / 100% 1px no-repeat,
    linear-gradient(var(--metal), var(--metal)) center 4px / 40px 1px no-repeat,
    linear-gradient(var(--metal), var(--metal)) center 8px / 16px 1px no-repeat;
  opacity: .8;
}

/* Two hairlines with a lozenge between them — the divider on a menu card. */
.deco-rule { display: flex; align-items: center; gap: 10px; color: var(--metal); }
.deco-rule::before, .deco-rule::after {
  content: ''; height: 1px; flex: 1;
  background: linear-gradient(90deg, transparent, currentColor);
}
.deco-rule::after { background: linear-gradient(90deg, currentColor, transparent); }
.deco-rule i {
  width: 7px; height: 7px; rotate: 45deg; flex: 0 0 auto;
  border: 1px solid currentColor;
}

/* A chevron notch in one corner. For a card that has earned a mark — a featured
   business, an event with tickets — and deliberately not for every card. */
.deco-notch { position: relative; overflow: hidden; }
.deco-notch::after {
  content: ''; position: absolute; right: -1px; top: -1px; width: 30px; height: 30px;
  pointer-events: none;
  background:
    linear-gradient(225deg, transparent 0 48%, var(--metal) 48% calc(48% + 1px), transparent calc(48% + 1px)) no-repeat,
    linear-gradient(225deg, transparent 0 66%, var(--metal) 66% calc(66% + 1px), transparent calc(66% + 1px)) no-repeat;
  opacity: .75;
}

/* The trade chips wear the quietest of the three: a single hairline underscore that
   fills on hover, so the ornament is also the affordance. */
.quick a { position: relative; overflow: hidden; }
.quick a::after {
  content: ''; position: absolute; left: 50%; bottom: 6px; translate: -50% 0;
  width: 0; height: 1px; background: var(--metal);
  transition: width var(--mid) var(--ease);
}
.quick a:hover::after { width: 42%; }

/* ══ directory tiles ═══════════════════════════════════════════════════════
   These five class names are emitted by site.js, which drives the whole directory —
   the daily shuffle, the filters, the featured flag. Restyling them here rather than
   rewriting that file means the conversion cannot break the ordering fairness or the
   search, and site.js stays the single place that logic lives. */

.tiles { display: grid; gap: 16px; align-items: start;
         grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); }
.tile {
  position: relative; display: block; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: clamp(18px, 2vw, 24px); box-shadow: var(--lift-1);
  text-decoration: none; color: inherit;
  transition: transform var(--mid) var(--ease), box-shadow var(--mid) var(--ease),
              border-color var(--mid) var(--ease);
}
.tile:hover { transform: translateY(-3px); box-shadow: var(--lift-2); border-color: var(--line-gold); }
.tile h3 { font-size: var(--t-md); font-weight: 800; letter-spacing: -.012em; margin-bottom: 6px; }
.tile p { font-size: var(--t-sm); color: var(--ink-2); line-height: 1.6; }
.ours__tag {
  display: inline-block; margin-bottom: 10px; padding: 4px 11px;
  border-radius: var(--r-pill); background: var(--night); color: var(--gold-on-night);
  font-size: 10px; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
}
.cats { display: flex; flex-wrap: wrap; gap: 8px; }
.cat {
  display: inline-flex; align-items: center; min-height: 40px; padding: 9px 16px;
  border-radius: var(--r-pill); border: 1px solid var(--line-gold);
  background: var(--paper); color: var(--ink);
  font-size: var(--t-sm); font-weight: 600; text-decoration: none;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease),
              transform var(--fast) var(--ease);
}
.cat:hover { border-color: var(--metal); background: var(--paper-warm); transform: translateY(-2px); }
.quiet { color: var(--ink-2); font-size: var(--t-sm); }
.quiet a { color: var(--gold-ink); font-weight: 600; }

/* the search row: one panel, the app's own fields */
/* Five controls now: search, trade, kind (which appears and disappears), place and
   radius. A fixed four-column grid put the fifth on a row of its own with three
   columns of white space beside it — and the "kind" select makes the count change
   while somebody is using it, so no fixed count is right.

   auto-fit with a minimum lets the row reflow on its own and keeps every control at a
   usable width. The radius is narrower than the rest because "30 miles" is short and
   giving it an equal share is what pushed the row over the edge in the first place. */
.find { display: grid; gap: 12px; grid-template-columns: 1fr; align-items: start; }
@media (min-width: 720px) {
  .find { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
  .find > #q { grid-column: span 2; }
  .find > #radius { max-width: 170px; }
}
/* A THIRD copy of the field styling used to be here, for `.find input[type=search]`,
   and it beat `.field__c` on specificity while setting `background` as a shorthand —
   so the search boxes lost their gradient exactly the way the selects lost their
   chevron. Three duplicates of one component, all from the same port, all invisible
   until the component grew a background.

   The inputs carry `.field__c` now. One rule owns how a field looks. */

/* ══ carried-over page classes ═════════════════════════════════════════════
   The marketing and sign-up pages were written against shared.css. Rather than rewrite
   their copy — which is the part that took the longest to get right and has nothing
   wrong with it — their structural classes are given the new look here. Same markup,
   same words, new system. As pages are rewritten properly these can retire. */

.page { display: block; }
.glow { display: none; }                    /* a dark-theme artefact; nothing to glow on paper */

.hero2 { padding: clamp(34px, 4.4vw, 66px) 0 clamp(12px, 1.6vw, 22px); text-align: center; }
.hero2 h1 { font-size: clamp(2rem, 3.6vw, 3.1rem); font-weight: 800; letter-spacing: -.026em;
            line-height: 1.08; text-wrap: balance; }
.hero2 h1 em { font-style: normal; font-family: "Great Vibes", cursive; font-weight: 400;
               color: var(--gold-lg); font-size: 1.18em; letter-spacing: 0; }
.hero2 .lede { margin: 16px auto 0; max-width: 58ch; }

.sect { padding-block: clamp(30px, 3.8vw, 58px); }
.sect__k { display: block; font-size: var(--t-xs); font-weight: 700; letter-spacing: .22em;
           text-transform: uppercase; color: var(--gold-ink); margin-bottom: 10px; }
.sect__h { font-size: clamp(1.5rem, 2.6vw, 2.1rem); font-weight: 800; letter-spacing: -.02em;
           line-height: 1.15; margin-bottom: 14px; }
.sect__p { color: var(--ink-2); line-height: 1.72; max-width: 68ch; }
.sect__p b { color: var(--ink); font-weight: 700; }
.sub { color: var(--ink-2); max-width: 62ch; }

.notice { border-color: var(--line-gold); background: var(--paper-warm); }
.cta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 20px; }

/* A FLEX ROW DOES NOT OBEY text-align.
   `.hero2` is `text-align: center`, so the eyebrow, the headline and the lede all centre —
   and then the buttons under them did not, because `.cta` is a flex container and
   `text-align` says nothing about how a flex container distributes its items. On /business
   the row sat 294px left of the column it belongs under, which is far enough to read as a
   different page element. It is left ranged deliberately everywhere else — inside a card,
   on /day, on /signin — so this is scoped to the two contexts that have already declared
   themselves centred, rather than fixed on `.cta` itself. Measured across all 13 pages
   that use it: /business's hero was the only one out. */
:where(.hero2, .center) > .cta { justify-content: center; }
.row2 { display: grid; gap: 14px; grid-template-columns: 1fr; }
@media (min-width: 640px) { .row2 { grid-template-columns: 1fr 1fr; } }

.btn--gold { background: var(--night); color: #fff; border-color: var(--night); }
.btn--gold:hover { background: var(--night-soft); border-color: var(--night-soft);
                   transform: translateY(-2px); box-shadow: var(--lift-2); }

/* ── the eyebrow ───────────────────────────────────────────────────────────
   The small gold capital line that sits over a heading. It was written out three
   separate times — identical each time — which meant a change to "the eyebrow"
   reached whichever of the three I happened to remember.

   It sits HERE, up with the utilities, rather than at the end of the file: the
   modifiers that recolour it (.promo__k--soon) are defined below, and a base
   rule placed after its own modifiers silently wins. .sho__sep is deliberately
   NOT in this list — it is grey on purpose, and folding it in turned it gold. */
.promo__k, .vhead__cat, .vsec__h {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .18em;
  text-transform: uppercase; color: var(--gold-ink);
}

/* ── section heading ────────────────────────────────────────────────────────
   `.vh` meant a VISIBLE HEADING in shared.css — display face, letterspaced, gold — and
   the pages were written against that meaning. When ds.css took over it defined the same
   class name as a screen-reader-only utility: position absolute, 1px by 1px, clipped.
   Identical name, opposite meaning, and nothing anywhere would say so.

   The result was fifteen headings across five live pages collapsed to a 1×1 box. Not
   missing from the markup, not unstyled — laid out, coloured, correct in every
   inspector, and one pixel square. Among them "About", "On offer" and "Get in touch" on
   EVERY public vendor page, which is a large part of why a real listing looked like an
   unfinished template with no structure to it.

   It is the visible heading again, in ds.css's own tokens rather than shared.css's dark
   theme. Actual hiding is `.sr-only` below, which is what that job should always have
   been called. */
.vh {
  font-weight: 700;
  font-size: var(--t-md); letter-spacing: .07em; text-transform: uppercase;
  color: var(--gold-ink); margin-bottom: 12px;
}

/* Hidden from the screen, still read aloud. The pattern every label that names a field
   without repeating it visually needs — and it did not exist, so `class="sr-only"`
   silently did nothing wherever it was used. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.ic { width: 18px; height: 18px; flex: 0 0 auto; }
.ic--in { vertical-align: -3px; margin-right: 6px; }

/* the sign-up form */
.field { display: block; margin-bottom: 16px; }
.field > span:first-child, .field > label { display: block; font-size: var(--t-xs);
  font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-2); margin-bottom: 7px; }
/* `.field input, .field select, .field textarea` used to live here — a duplicate of
   `.field__c, .select` that came across with the workbench port. It was flagged then as
   redundant and left alone because it looked harmless.

   It was not. `.field select` is (0,1,1) and `.select` is (0,1,0), so the duplicate WON —
   and it set `background` as a SHORTHAND, which resets background-image to none. The
   moment the select grew a chevron and a gradient, both silently vanished: the computed
   background-image was empty and nothing in the file that drew it was wrong.

   A shorthand in a duplicate rule is a landmine. Fields are styled in exactly one place
   now, by class. Anything inside `.field` that needs the look should carry `.field__c`. */
.hint { display: block; font-size: var(--t-sm); color: var(--ink-3); margin-top: 6px; }
/* ══ A HINT INSIDE A LABEL IS PART OF THE LABEL ═══════════════════════════════
   `.hint` is display:block, which is right underneath a field and wrong inside its
   label: `<label>Email <span class="hint">optional</span></label>` became a 47px
   two-line label next to a 20px one, so the two inputs in that row started 28px
   apart. Measured on /v: e-email at 1601, e-date at 1573.

   Reported as "the fields next to each other are not aligned — Email and Date of your
   event", and reported after I had already been asked once to make alignment a rule
   rather than a page-by-page fix. This is that rule for the inside of a label: a word
   like "optional" modifies the label, so it sits ON the label's line.

   `font-size: 0.92em` is the part that actually finishes it, and the reason is worth
   writing down. `.field__l` is 12px; `.hint` is var(--t-sm) = 13px — a hint LARGER than
   the label it sits in. `line-height: inherit` does not rescue that, because the label's
   line-height is unitless, so inheriting it multiplies by the CHILD's font-size: 1.65 x
   13px = 21.45px against the label's own 19.8px. That is the 2px the inputs were still
   apart after the first attempt.
   Sizing the hint relative to the label (em, not the global small size) makes its line
   box smaller than the label's, so the label's height stops depending on whether it has
   a hint at all. Measured on /v: both labels 19.80px, controls 0px apart. */
.field__l .hint,
label .hint { display: inline; margin-top: 0; margin-left: 6px; font-weight: 400;
              font-size: 0.92em; line-height: inherit; vertical-align: baseline; }
.msg { font-size: var(--t-sm); font-weight: 600; margin-top: 12px; }
.msg.bad { color: #a1301b; }
.msg.good { color: #2c6b3f; }
.pw { position: relative; }
.pw input { padding-right: 46px; }
.pw__eye { position: absolute; right: 6px; top: 50%; translate: 0 -50%;
  width: 38px; height: 38px; display: grid; place-items: center;
  background: none; border: 0; cursor: pointer; color: var(--ink-3); border-radius: 50%; }
.pw__eye:hover { color: var(--gold-ink); background: var(--paper-warm); }
.pwic { width: 19px; height: 19px; }
.linkbtn { background: none; border: 0; padding: 0; cursor: pointer;
           color: var(--gold-ink); font-weight: 600; font-size: var(--t-sm); }
.linkbtn:hover { text-decoration: underline; text-underline-offset: 3px; }

/* The converted pages drop their content straight into main, so the band padding that
   the rebuilt pages get from .band has to come from somewhere. */
main.wrap { padding-block: clamp(28px, 3.4vw, 52px) clamp(40px, 5vw, 74px); }
main.wrap > h1:first-child, main.wrap > .sect:first-child { margin-top: 0; }
main.wrap h1 { font-size: clamp(1.9rem, 3.4vw, 2.9rem); font-weight: 800;
               letter-spacing: -.024em; line-height: 1.1; margin-bottom: 12px; }
main.wrap > .lede { max-width: 62ch; margin-bottom: clamp(20px, 2.4vw, 32px); }
main.wrap .card { margin-inline: auto; }

/* admin: tabs and panes */
.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px;
        border-bottom: 1px solid var(--line); padding-bottom: 14px; }
.tab { min-height: 40px; padding: 9px 16px; border-radius: var(--r-pill);
       background: var(--paper); border: 1px solid var(--line-strong);
       font-size: var(--t-sm); font-weight: 600; cursor: pointer; color: var(--ink); }
.tab:hover { border-color: var(--metal); }
.tab.is-on { background: var(--night); border-color: var(--night); color: #fff; }
.pane[hidden] { display: none !important; }
.apptop { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
          margin-bottom: clamp(18px, 2.2vw, 30px); }
/* ⚠ AN EMPTY ONE STILL PUSHED. On /plan's board this div renders at height 0 — there is
   nothing in it there — and its margin-bottom alone put the page header 30px below the
   line every other rail destination shares. Zero height, zero content, thirty pixels of
   consequence, and invisible in any inspector that shows you boxes rather than margins. */
.apptop:empty,
/* …and one holding nothing but its own flex spacer, which is /plan's board: the div
   renders at height 0 but `:empty` does not match it, because a `<span>` with no content
   is still a child. Zero height, thirty pixels of margin, and the page header sitting
   below the line every other rail destination shares. */
.apptop:has(> .apptop__sp:only-child) { margin-bottom: 0; }
.apptop__sp { flex: 1 1 auto; }
.stats { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }

/* One measured row: a label, a bar, a number. Used for the admin's "most read pages",
   "most pressed buttons", "what people search for" and the day-by-day count.

   A bare list of numbers makes you do the comparing; the bar does it for you, which is
   the entire reason to look at a list like this. Scaled against the largest row rather
   than a fixed maximum, so the shape reads the same whether the top row is 9 or 9000.
   The number stays as text at the end — a bar alone tells you the ratio and not the
   amount, and both matter. */
.sbar {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(70px, 34%) auto;
  align-items: center; gap: 12px; padding: 7px 0;
  border-top: 1px solid var(--line);
}
.sbar:first-child { border-top: 0; }
.sbar__l {
  font-size: var(--t-sm); color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sbar__t { height: 8px; border-radius: 999px; background: var(--sink); overflow: hidden; }
.sbar__f {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--gold-lg, #c08430), var(--gold-ink));
}
.sbar__n {
  font-size: var(--t-sm); font-weight: 800; color: var(--ink);
  font-variant-numeric: tabular-nums; min-width: 3ch; text-align: right;
}

/* ══ sponsorship ═══════════════════════════════════════════════════════════
   Three tiers. Platinum takes the homepage and is given real room — a poster, a film,
   a wide still. Gold and silver run on the inner pages at a size that reads as a
   partner rather than an interruption.

   TWO RULES THIS HAS TO OBEY.

   It must never be mistakable for the directory. Every slot is labelled with its tier,
   sits in its own band, and no sponsor is ever counted in "N businesses listed" — the
   daily shuffle only stays believable if paid placement is visibly a different thing.

   And an EMPTY slot has to look deliberate. A hole where an advert belongs reads as a
   broken page; a card that says what the space is and what it costs to hold reads as a
   product with something to sell. So the slot always renders, and the sponsor row is
   only what fills it. The site looks finished on the day there are no sponsors at all. */

.spon { position: relative; }
.spon__k {
  display: flex; align-items: center; gap: 10px; justify-content: center;
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .22em;
  text-transform: uppercase; margin-bottom: 14px;
}
.spon__k::before, .spon__k::after {
  content: ''; height: 1px; width: clamp(20px, 5vw, 64px);
  background: linear-gradient(90deg, transparent, currentColor);
}
.spon__k::after { background: linear-gradient(90deg, currentColor, transparent); }

.spon--platinum .spon__k { color: #6f6a74; }
.spon--gold     .spon__k { color: var(--gold-ink); }
.spon--silver   .spon__k { color: #6f6a74; }

.spon__slot {
  position: relative; display: block; overflow: hidden;
  border-radius: var(--r-lg); background: var(--paper);
  border: 1px solid var(--line); box-shadow: var(--lift-1);
  text-decoration: none; color: inherit;
  transition: transform var(--mid) var(--ease), box-shadow var(--mid) var(--ease),
              border-color var(--mid) var(--ease);
}
a.spon__slot:hover { transform: translateY(-3px); box-shadow: var(--lift-2); }

/* Platinum reads as metal; gold as the mark's own; silver stays quiet on purpose so the
   three tiers are legible as a hierarchy at a glance rather than needing the word. */
.spon--platinum .spon__slot { border-color: #c9c6cc;
  box-shadow: 0 0 0 1px rgba(255,255,255,.7) inset, var(--lift-2); }
.spon--gold     .spon__slot { border-color: var(--line-gold); }
.spon--silver   .spon__slot { border-color: #d7d4d9; }

.spon__slot img, .spon__slot video { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The tiers are legible as a hierarchy by SIZE before anyone reads the word: platinum
   takes the column, gold three quarters of it, silver half. Set as a width with the
   aspect held, so the shape stays the same and only the presence changes — a sponsor
   moving up a tier sees their space grow rather than change shape. */
.spon--platinum .spon__slot { aspect-ratio: 21 / 6; }
.spon--gold     .spon__slot { aspect-ratio: 21 / 6; max-width: 75%; margin-inline: auto; }
.spon--silver   .spon__slot { aspect-ratio: 21 / 5; max-width: 50%; margin-inline: auto; }
@media (max-width: 860px) {
  /* Below this a half-width slot is too narrow to read, and the hierarchy is carried by
     the label and the order of the page instead. */
  .spon--gold .spon__slot, .spon--silver .spon__slot { max-width: 100%; }
}
@media (max-width: 700px) {
  .spon--platinum .spon__slot { aspect-ratio: 16 / 9; }
  .spon--gold .spon__slot, .spon--silver .spon__slot { aspect-ratio: 16 / 7; }
}

.spon__name { position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 18px;
  background: linear-gradient(to top, rgba(23,19,32,.72), transparent);
  color: #fff; font-size: var(--t-sm); font-weight: 700; }

/* An EMPTY slot is sized by its content, never by the tier's aspect ratio. The ratio
   exists to frame a sponsor's artwork; applied to a card containing a headline, a line
   of text and a button it produced a 130px box holding 156px of content, and
   `overflow:hidden` cut the "Enquire" button clean off — so the one clickable thing in
   the slot was not there at all. Silver was worst because it is the shortest ratio. */
/* ...and it has to be written at the SAME specificity as the rules it is undoing.
   `.spon__slot--open` alone is (0,1,0); `.spon--silver .spon__slot` is (0,2,0), so the
   forced ratio kept winning no matter that this rule sits later in the file. The button
   was still clipped after the "fix", which is the worst kind of fix — it looks done. */
.spon--platinum .spon__slot--open,
.spon--gold     .spon__slot--open,
.spon--silver   .spon__slot--open {
  /* ONLY the ratio. The first attempt at this also capped the width at 560px "so the
     button fits" — which was never the problem, and it shrank the PLATINUM slot from the
     full column to a small box. Platinum being the widest thing on the page is the entire
     point of platinum. The clipping was the forced 21:5 box, nothing else: with the ratio
     released the slot grows to whatever its content needs and each tier keeps its width. */
  aspect-ratio: auto;
  min-height: 0;
}

/* the empty state — a thing for sale, not a gap */
.spon__open {
  display: grid; place-items: center; text-align: center; height: 100%;
  padding: clamp(20px, 3vw, 38px);
  background:
    radial-gradient(60% 80% at 50% 0%, rgba(255,255,255,.9), transparent 70%),
    repeating-linear-gradient(114deg, rgba(169,120,42,.05) 0 1px, transparent 1px 22px),
    var(--paper-warm);
}
.spon--platinum .spon__open {
  background:
    radial-gradient(60% 80% at 50% 0%, #fff, transparent 70%),
    repeating-linear-gradient(114deg, rgba(90,86,96,.05) 0 1px, transparent 1px 22px),
    #f4f3f5;
}
.spon__open b { display: block; font-size: var(--t-lg); font-weight: 800;
                letter-spacing: -.015em; margin-bottom: 6px; }
.spon__open span { display: block; font-size: var(--t-sm); color: var(--ink-2); max-width: 46ch; }
.spon__open .btn { margin-top: 14px; }

/* a running line of smaller partners, for pages that only warrant a strip */
.spon__strip { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.spon__strip .spon__slot { flex: 1 1 260px; aspect-ratio: 16 / 6; }
/* only cap when there is genuinely a row of them */
.spon__strip--many .spon__slot { max-width: 340px; }

/* the trade finder — rank and highlight, never hide */
.cat--hit { border-color: var(--metal); background: var(--paper-warm); }
.cat--hit mark { background: none; color: var(--gold-ink); font-weight: 800; }
.cat--dim { opacity: .38; }
.cats { transition: opacity var(--fast) var(--ease); }
.findtrade { display: grid; gap: 8px; max-width: 460px; margin: 0 auto 20px; }
.findtrade input { width: 100%; min-height: 48px; padding: 12px 16px;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--r-pill); }
.findtrade input:focus { outline: none; border-color: var(--metal);
  box-shadow: 0 0 0 3px rgba(192,132,48,.18); }
.findtrade p { min-height: 1.2em; text-align: center; font-size: var(--t-sm); color: var(--ink-2); }

/* A .wrap inside a .wrap must not constrain again. The converted pages use
   <main class="wrap">, so any band that carries its own .wrap — every sponsor slot —
   was getting the page padding applied twice and the max-width applied twice. Gold
   measured 61% of the column where it should read 75%, and the tiers stopped being
   distinguishable by size, which is the whole point of them. */
.wrap .wrap { max-width: none; padding-inline: 0; }

/* ══ the house band ════════════════════════════════════════════════════════
   A short page cannot carry three sponsor slots — they end up stacked on top of each
   other with nothing between, which makes the page look like an advert with an article
   attached rather than the other way round.

   So a page that is short gets one of these between the slots: our own mark and one
   line, doing the job a sponsor would do if there were one. It is house promotion, it
   is honest about being ours, and it gives the tiers the air they need to read as
   separate things. */
.house {
  display: grid; place-items: center; text-align: center; gap: 12px;
  padding: clamp(30px, 4vw, 56px) clamp(20px, 3vw, 40px);
  border-radius: var(--r-lg); border: 1px solid var(--line-gold);
  background:
    radial-gradient(70% 90% at 50% 0%, #fff, transparent 72%),
    var(--paper-warm);
}
.house img { width: clamp(78px, 9vw, 112px); height: auto; border-radius: 50%;
             box-shadow: 0 0 0 1px var(--metal); }
.house b { font-size: var(--t-lg); font-weight: 800; letter-spacing: -.018em; }
.house p { color: var(--ink-2); font-size: var(--t-sm); max-width: 48ch; }
.post__more { margin-top: 10px; font-size: var(--t-sm); }

/* the referral card — the house band's working variant */
.house form { display: grid; gap: 10px; width: min(560px, 100%); margin-top: 4px; }
.house .row2 { gap: 10px; }
.house input, .house select { width: 100%; min-height: 46px; padding: 12px 14px;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm); font-size: var(--t-sm); }
.house input:focus, .house select:focus { outline: none; border-color: var(--metal);
  box-shadow: 0 0 0 3px rgba(192,132,48,.18); }
.house textarea { width: 100%; padding: 12px 14px; background: var(--paper); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  font: inherit; font-size: var(--t-sm); resize: vertical; }
.house textarea:focus { outline: none; border-color: var(--metal);
  box-shadow: 0 0 0 3px rgba(192,132,48,.18); }

/* ══ the partners page ═════════════════════════════════════════════════════ */
.sponsec { margin-bottom: clamp(30px, 3.6vw, 52px); }
.sponsec:empty { display: none; }
.spongrid { display: grid; gap: 14px; align-items: start; }
.spongrid--1 { grid-template-columns: 1fr; }
.spongrid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr)); }
.spongrid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); }
.sponcard {
  display: block; text-decoration: none; color: inherit; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden;
  box-shadow: var(--lift-1);
  transition: transform var(--mid) var(--ease), box-shadow var(--mid) var(--ease),
              border-color var(--mid) var(--ease);
}
.sponcard:hover { transform: translateY(-3px); box-shadow: var(--lift-2); border-color: var(--line-gold); }
.sponcard__m { aspect-ratio: 16/7; background: var(--paper-warm); display: grid; place-items: center; }
.sponcard__m img, .sponcard__m video { width: 100%; height: 100%; object-fit: cover; }
.sponcard__b { padding: 16px 18px 18px; }
.sponcard__b b { display: block; font-size: var(--t-md); font-weight: 800; }
.sponcard__b span { display: block; font-size: var(--t-sm); color: var(--ink-2); margin-top: 4px; }

/* ══ the keep prompt ═══════════════════════════════════════════════════════
   Bottom corner, never centred: a centred dialog over a page somebody is reading is an
   interruption with a shadow behind it. This sits out of the way, moves nothing, blocks
   nothing, and is dismissible with Escape or a button. */
.keep {
  position: fixed; right: clamp(12px, 2vw, 24px); bottom: clamp(12px, 2vw, 24px);
  z-index: 95; width: min(340px, calc(100vw - 24px));
  padding: 18px 20px 16px; border-radius: var(--r-lg);
  background: var(--paper); border: 1px solid var(--line-gold);
  box-shadow: var(--lift-3);
  opacity: 0; transform: translateY(14px);
  transition: opacity var(--mid) var(--ease), transform var(--mid) var(--ease);
}
.keep.is-in { opacity: 1; transform: none; }
.keep__x { position: absolute; right: 8px; top: 6px; width: 32px; height: 32px;
  border: 0; background: none; cursor: pointer; color: var(--ink-3);
  font-size: 20px; line-height: 1; border-radius: 50%; }
.keep__x:hover { color: var(--ink); background: var(--paper-warm); }
.keep__t { font-size: var(--t-md); font-weight: 800; letter-spacing: -.015em; padding-right: 26px; }
.keep__s { font-size: var(--t-sm); color: var(--ink-2); margin-top: 6px; line-height: 1.55; }
.keep__r { display: flex; gap: 8px; align-items: center; margin-top: 14px; }
@media (prefers-reduced-motion: reduce) { .keep { transition: none; } }

/* a consent checkbox, sized so it can actually be hit */
.check { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: start;
         cursor: pointer; font-size: var(--t-sm); color: var(--ink-2); }
.check input { width: 20px; height: 20px; margin-top: 2px; accent-color: var(--gold-ink); }
.check span { line-height: 1.55; }


/* Where you are. A colour change alone is not enough — it fails for anyone who cannot
   distinguish it, and on a nav of six items it is easy to miss. The mark under the
   current tab is a shape, so it does not depend on seeing a hue. */
.shell__nav a[aria-current] { color: var(--gold-ink); border-bottom-color: transparent; position: relative; }
.shell__nav a[aria-current]::after {
  content: ''; position: absolute; left: 50%; bottom: -2px; translate: -50% 0;
  width: 26px; height: 7px; pointer-events: none;
  background:
    linear-gradient(var(--metal), var(--metal)) center bottom / 100% 1px no-repeat,
    linear-gradient(var(--metal), var(--metal)) center 2px / 12px 1px no-repeat;
}
.shell__nav a[aria-current]::before {
  content: ''; position: absolute; left: 50%; bottom: 4px; translate: -50% 0;
  width: 4px; height: 4px; rotate: 45deg; border: 1px solid var(--metal);
}


/* ══ long-form documents ═══════════════════════════════════════════════════
   Terms and the privacy notice. These are the two pages on the site somebody
   reads because they are worried, and a wall of grey 13px type is how a site
   says "we would rather you didn't". So: the same measure as everything else,
   real headings, and lists that a person can scan.

   The global reset strips list markers from every ul/ol (line 99) because
   navigation and card grids are lists and none of them want bullets. A legal
   clause does. This is the one place they are put back. */
.legal { max-width: 72ch; }
.legal > * + * { margin-top: 14px; }
.legal h2 {
  font-size: var(--t-xl); font-weight: 800; letter-spacing: -.02em; line-height: 1.18;
  margin-top: 44px; padding-top: 22px; border-top: 1px solid var(--line);
  /* The `scroll-margin-top: 96px` that was here is gone — see the [id] rule near the top
     of this file. 96 was a guess against a header that has since changed height, it only
     ever covered `.legal h2`, and the h3s and every non-legal anchor on the site had no
     offset at all. It is measured now, in one place, for everything. */
}
.legal > h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.legal h3 {
  font-size: var(--t-md); font-weight: 700; letter-spacing: -.01em;
  margin-top: 26px; color: var(--ink);
}
.legal p, .legal li { color: var(--ink-2); line-height: 1.72; }
.legal b, .legal strong { color: var(--ink); font-weight: 700; }
.legal ul, .legal ol { padding-left: 1.25em; }
.legal ul { list-style: disc; }
.legal ol { list-style: decimal; }
.legal li { margin-top: 8px; padding-left: .25em; }
.legal li::marker { color: var(--metal); }
.legal code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .88em; background: var(--paper-warm);
  border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px;
  color: var(--ink); overflow-wrap: anywhere;
}

/* The date this was last changed, and the plain-language summary above it. Both
   sit at the top because a document nobody can date is a document nobody can
   trust, and a summary is not a substitute for the terms — it is the way in. */
.legal__d { font-size: var(--t-sm); color: var(--ink-3); }
.legal__sum {
  border: 1px solid var(--line-gold); background: var(--paper-warm);
  border-radius: var(--r-lg); padding: clamp(16px, 2vw, 22px);
}
.legal__sum p, .legal__sum li { color: var(--ink-2); }
.legal__sum > * + * { margin-top: 10px; }

/* Contents. On a narrow screen it runs before the document as a plain list; from
   900px up .split makes it a sticky rail. */
.toc a {
  display: block; padding: 5px 0; font-size: var(--t-sm); line-height: 1.45;
  color: var(--ink-2); text-decoration: none; border-bottom: 1px solid transparent;
}
.toc a:hover { color: var(--gold-ink); }
/* Below the .split breakpoint the rail runs ABOVE the document, so 23 stacked
   links become 23 rows a phone has to scroll past to reach section 1. Two columns
   halves it and still reads as a contents list. */
@media (max-width: 899px) {
  .toc { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
}
@media (max-width: 420px) { .toc { grid-template-columns: 1fr; } }

/* `.foot a` is display:block with a bottom margin, because in the footer's link
   columns each one is its own row. In the end strip they run inline on one line,
   and inheriting that rule stacks "Terms" and "Privacy" as two blocks with a gap
   between them. */
.foot__end a { display: inline; margin: 0; }
.foot__end .sep { opacity: .38; padding-inline: 2px; }


/* ══ Night Out ═════════════════════════════════════════════════════════════
   An event is read differently from a blog post. The first thing anybody wants
   is WHEN, so the time is its own column on the left and the eye can run down
   it — the same reason a train timetable puts departures in a column. */
.ev {
  display: grid; grid-template-columns: 82px minmax(0, 1fr); gap: clamp(14px, 2vw, 22px);
  padding: clamp(15px, 1.9vw, 20px) 0; border-top: 1px solid var(--line);
}
.ev:first-child { border-top: 0; }
.ev__when {
  display: flex; flex-direction: column; gap: 2px; text-align: right;
  padding-top: 2px; border-right: 1px solid var(--line); padding-right: clamp(12px, 1.6vw, 18px);
}
.ev__when b { font-size: var(--t-sm); font-weight: 700; letter-spacing: -.006em; color: var(--ink-2); }
.ev__when span { font-size: var(--t-sm); color: var(--ink-3); }
/* Tonight is the only thing that gets colour. Everything coloured is nothing
   coloured — if every row has a gold date, the gold has stopped meaning
   "this one is happening in the next few hours". */
.ev--tonight .ev__when b { color: var(--gold-ink); font-weight: 800; }
.ev--tonight .ev__when { border-right-color: var(--line-gold); }

/* The kind was gold and uppercase on every single row — fifty rows of gold
   capitals is a page shouting one word at you. It is a quiet label now and the
   title carries the row. */
.ev__k { font-size: var(--t-xs); font-weight: 600; letter-spacing: .13em;
         text-transform: uppercase; color: var(--ink-3); }
.ev__t { font-size: var(--t-lg); font-weight: 800; letter-spacing: -.018em;
         line-height: 1.2; margin-top: 4px; }
.ev__m { font-size: var(--t-sm); color: var(--ink-2); margin-top: 5px; }
.ev__m i { font-style: normal; color: var(--metal); padding-inline: 2px; }
.ev__d { font-size: var(--t-sm); color: var(--ink-2); line-height: 1.6; margin-top: 8px; max-width: 62ch; }

.ev__tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.ev__pill {
  font-size: var(--t-xs); font-weight: 600; letter-spacing: .02em;
  padding: 3px 9px; border-radius: var(--r-pill);
  border: 1px solid var(--line); color: var(--ink-3); background: none;
}
.ev__pill--tag { border-style: dashed; color: var(--ink-3); font-weight: 600; letter-spacing: 0; }
/* A facet is a fact the vendor chose from a fixed list; a tag is whatever they typed. So
   the facet is solid and carries the site's metal, and the free-text tag stays dashed and
   grey beside it. Three shades of the same pill would say they are the same kind of
   thing, and the whole reason 112 exists is that they are not. */
.ev__pill--facet { border-style: solid; border-color: var(--line-gold);
                   background: var(--metal-pale); color: var(--gold-ink); font-weight: 600; }
.ev__go { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }

@media (max-width: 640px) {
  .ev { grid-template-columns: 1fr; gap: 8px; }
  .ev__when { flex-direction: row; gap: 8px; text-align: left; border-right: 0;
              padding-right: 0; border-bottom: 0; }
  .ev--tonight .ev__when { border-right: 0; }
}

/* The when-bar. Segmented, because the choices are exclusive and few. */
.whenbar { display: flex; flex-wrap: wrap; gap: 6px; }
/* In a rail: two columns, not five stacked rows. Stacked they made the rail 725px
   tall against a 786px column, which left `position:sticky` exactly 61px of travel —
   so it looked like it was not sticking at all. A sticky element can only move
   (parent height − its own height); a tall rail is a rail that does not stick. */
.whenbar--stack {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
  flex-direction: unset;
}
.whenbar--stack button { text-align: center; padding: 8px 6px; }
/* "Everything" is the reset, so it spans and reads as the way back out. */
.whenbar--stack button:last-child { grid-column: 1 / -1; }
.whenbar button {
  font: inherit; font-size: var(--t-sm); font-weight: 700; cursor: pointer;
  padding: 8px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--line-strong); background: var(--paper); color: var(--ink-2);
}
.whenbar button:hover { border-color: var(--metal); color: var(--ink); }
.whenbar button.is-on { background: var(--night); border-color: var(--night); color: #fff; }


/* ══ nav drop-downs ════════════════════════════════════════════════════════
   Six top items instead of seven, each one opening what sits under it. The
   bar was already crowding at 1000px with seven, and the sections kept
   growing — Night Out, the blog, the tools. Grouping is the only thing that
   scales; hiding items in a "More" is not.

   Opens on hover AND on click AND on focus, because those are three different
   people. Hover alone locks out anyone using a keyboard and anyone on a
   touchscreen, where hover fires on the first tap and the link follows on the
   same gesture. */
.nd { position: relative; display: flex; align-items: center; }
.nd > a { display: inline-flex; align-items: center; gap: 5px; }

/* The caret is drawn, not a character. A "▾" glyph renders at a different
   size and baseline in every font that has it, and in some it is missing. */
.nd__c::after { content: ''; position: absolute; inset: -9px -10px; }
.nd__c {
  /* It is a CONTROL now, not decoration: the tab navigates and this opens the panel, so
     it needs something a finger or a fast mouse can actually land on. 7px of drawn
     chevron with a 27x25 hit area around it — the ::after is invisible and does not move
     anything, so the bar looks identical and the target is nine times the size. */
  position: relative;
  width: 7px; height: 7px; flex: 0 0 auto; margin-top: -2px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  rotate: 45deg; opacity: .5;
  transition: rotate .3s cubic-bezier(.16, 1.02, .3, 1), opacity .2s ease, margin-top .3s cubic-bezier(.16, 1.02, .3, 1);
}
.nd.is-open > a .nd__c { rotate: 225deg; margin-top: 2px; opacity: 1; }

.nd__p {
  position: absolute; top: calc(100% + 12px); left: 50%;
  min-width: 268px; z-index: 60;
  background: var(--paper); border: 1px solid var(--line-strong);
  border-radius: var(--r-lg); padding: 8px;
  pointer-events: none;
  /* Same reasoning as .menu__p: anchored to the header, so it can only grow downwards
     and off the screen. Four entries fit anywhere today, but the About panel has grown
     twice already and nothing here would have told us when it stopped fitting. */
  max-height: calc(100dvh - var(--shell-h, 72px) - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;

  /* It grows DOWNWARD FROM THE TAB rather than sliding in as a finished rectangle.
     `transform-origin: top center` with a small scale is what makes a menu feel attached
     to the thing that opened it — a panel that fades in place reads as a layer that
     happened to appear near the tab, which is the flat, un-app-like version. */
  transform-origin: top center;
  opacity: 0;
  transform: translateX(-50%) translateY(-8px) scale(.94);

  /* The shadow lifts as it opens. A menu at rest on the page and a menu floating above
     it are different objects, and animating only position says nothing about which. */
  box-shadow: 0 4px 10px rgba(23, 19, 32, .04);

  /* Slightly overshooting easing on the way OUT of the closed state, plain ease on the
     way back. Opening should feel like it arrives; closing should get out of the way. */
  transition:
    opacity .16s ease,
    transform .16s cubic-bezier(.4, 0, .2, 1),
    box-shadow .22s ease;
}
.nd.is-open .nd__p {
  opacity: 1;
  transform: translateX(-50%) translateY(0) scale(1);
  pointer-events: auto;
  box-shadow: 0 22px 50px rgba(23, 19, 32, .15), 0 2px 6px rgba(23, 19, 32, .05);
  transition:
    opacity .2s ease,
    transform .34s cubic-bezier(.16, 1.02, .3, 1),
    box-shadow .34s ease;
}

/* The rows arrive a beat after the panel and just behind each other. Four items at 26ms
   apart is 78ms across the whole set — under the threshold where it reads as a queue,
   but enough that the panel feels like it is filling rather than switching on. */
.nd__p > * {
  opacity: 0; transform: translateY(-5px);
  transition: opacity .2s ease, transform .2s cubic-bezier(.2, .7, .3, 1);
}
.nd.is-open .nd__p > * { opacity: 1; transform: none; }
.nd.is-open .nd__p > *:nth-child(1) { transition-delay: .05s; }
.nd.is-open .nd__p > *:nth-child(2) { transition-delay: .076s; }
.nd.is-open .nd__p > *:nth-child(3) { transition-delay: .102s; }
.nd.is-open .nd__p > *:nth-child(4) { transition-delay: .128s; }
.nd.is-open .nd__p > *:nth-child(5) { transition-delay: .154s; }

/* A gold hairline along the top edge, the same detail the sponsor cards use. */
.nd__p::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-gold), transparent);
}

/* The gap between the tab and the panel would close the menu as the cursor
   crosses it. This bridges it without being visible or clickable-through. */
.nd__p::after {
  content: ''; position: absolute; left: 0; right: 0; top: -14px; height: 14px;
}

.nd__p a {
  display: block; padding: 10px 12px; border-radius: var(--r-sm);
  text-decoration: none; color: var(--ink);
}
.nd__p a:hover, .nd__p a:focus-visible { background: var(--paper-warm); color: var(--ink); }
.nd__p a b { display: block; font-size: var(--t-sm); font-weight: 700; letter-spacing: -.008em; }
.nd__p a span { display: block; font-size: var(--t-xs); color: var(--ink-3); margin-top: 2px; line-height: 1.45; }
.nd__p .menu__sep { margin: 6px 4px; }

@media (prefers-reduced-motion: reduce) {
  /* Everything, including the per-row delays — a stagger is still motion, and leaving the
     delays in place makes the menu appear in pieces for somebody who asked for none. */
  .nd__p, .nd__c, .nd__p > * { transition: none; }
  .nd__p { transform: translateX(-50%); }
  .nd.is-open .nd__p { transform: translateX(-50%); }
  .nd.is-open .nd__p > * { transition-delay: 0s; }
}

/* On the burger menu everything is already a vertical list, so the panels are
   simply always open and indented. A drop-down inside a drop-down is nobody's
   idea of elegant on a phone. */
@media (max-width: 860px) {
  .nd { display: block; }
  .nd > a { width: 100%; }
  .nd__c { display: none; }
  .nd__p, .nd.is-open .nd__p, .nd__p > *, .nd.is-open .nd__p > * {
    transform: none; opacity: 1; transition: none; transition-delay: 0s;
  }
  .nd__p {
    position: static; pointer-events: auto; min-width: 0; box-shadow: none; border: 0;
    background: none; padding: 0 0 6px 14px;
    border-left: 1px solid var(--line);
    margin: 2px 0 6px 4px;
  }
  .nd__p::before, .nd__p::after { display: none; }
  .nd__p a { padding: 9px 8px; }
}


/* ══ stacked filters ═══════════════════════════════════════════════════════
   One trade is a drop-down. Several is a set of things you can see and remove
   one at a time — a multi-select listbox hides what is chosen behind a scroll,
   which is exactly the information you need while you are choosing. */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.chips:empty { display: none; }
.chipx {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--t-sm); font-weight: 700; padding: 7px 8px 7px 14px;
  border-radius: var(--r-pill); border: 1px solid var(--line-gold);
  background: var(--paper-warm); color: var(--ink);
}
.chipx button {
  width: 22px; height: 22px; border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(23,19,32,.06); color: var(--ink-2); font: inherit; font-size: 15px;
  line-height: 1; display: grid; place-items: center; padding: 0;
}
.chipx button:hover { background: var(--night); color: #fff; }

/* ══ a modal ═══════════════════════════════════════════════════════════════
   Used for the one moment worth interrupting: somebody reaching for a second
   trade, which is where an account starts being worth having. */
.modal {
  position: fixed; inset: 0; z-index: 120; display: grid; place-items: center;
  padding: 20px; background: rgba(23,19,32,.42);
  opacity: 0; transition: opacity .18s ease;
}
.modal.is-in { opacity: 1; }
.modal__c {
  width: min(520px, 100%); background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: var(--r-lg);
  box-shadow: 0 30px 80px rgba(23,19,32,.28);
  padding: clamp(22px, 3vw, 34px); position: relative;
  transform: translateY(10px) scale(.985); transition: transform .18s ease;
  /* Two lines, and the order matters. On a phone `100vh` is the height the page WOULD
     have with the browser's toolbars hidden — taller than what you can actually see
     while they are showing. A modal sized to it runs off the bottom of the screen, and
     because the page behind is fixed there is nothing to scroll to reach the rest: the
     buttons at the foot of the box are simply gone. `dvh` is the height you can see,
     and it follows the toolbars as they come and go. The vh line stays underneath it
     as the fallback for anything too old to know dvh. */
  max-height: calc(100vh - 40px);
  max-height: calc(100dvh - 40px);
  overflow: auto;
}
.modal.is-in .modal__c { transform: none; }
.modal__x {
  position: absolute; right: 10px; top: 8px; width: 36px; height: 36px;
  border: 0; background: none; cursor: pointer; color: var(--ink-3);
  font-size: 22px; line-height: 1; border-radius: 50%;
}
.modal__x:hover { color: var(--ink); background: var(--paper-warm); }
@media (prefers-reduced-motion: reduce) {
  .modal, .modal__c { transition: none; }
}


/* ══ where the sticky search bar used to be ════════════════════════════════
   Both list pages had a filter bar ABOVE the results that stuck to the header
   and compacted as you scrolled. It worked, and it was solving a problem it
   had created: a bar above a list has to be made sticky, then made smaller so
   it does not eat the screen, and then it changes shape while you read.

   The filters live in the RAIL now — on Night Out, on the directory, and on
   the blog, which already read that way. Always visible, never overlapping the
   list, and the results column keeps one width the whole way down. Three list
   pages, one layout language.

   `sticky.js` and `.stickybar` were deleted with this. If a sticky bar is ever
   wanted again, it is in the history — but read this paragraph first. */


/* ══ the house band ════════════════════════════════════════════════════════
   What the app itself does. It sits next to paid sponsor slots and the
   fastest way to make it worthless is to make it look like one more of them,
   so it is deliberately a different object: no bordered box, no tier label,
   no logo. An editorial band set in the page's own type.

   The cards have no card background either — three boxes in a row IS the
   sponsor grid. They are separated by a hairline and a gold rule above each
   heading, which reads as an index rather than an advert. */
.promo { padding-block: clamp(6px, 1vw, 14px); }

/* The band used to sit on `.band--paper` — a surface a shade off the page colour, whose
   edges read as a faint seam rather than an intentional change. Two nearly-equal tones
   meeting is the worst of both: not flat, not a panel. So the surface goes entirely and
   the band is held by a hairline above it and its own spacing. */
.band--promo { background: none; padding-block: clamp(34px, 4.4vw, 62px); }
.band--promo > .wrap { border-top: 1px solid var(--line); padding-top: clamp(30px, 3.6vw, 48px); }
.promo__head { max-width: 62ch; }
.promo__h {
  font-size: clamp(1.45rem, 2.4vw, 2rem); font-weight: 800; letter-spacing: -.022em;
  line-height: 1.15; margin-top: 8px;
}
.promo__h em {
  font-style: normal; font-family: "Great Vibes", cursive; font-weight: 400;
  color: var(--gold-lg); font-size: 1.3em; letter-spacing: 0;
}

.promo__g {
  display: grid; gap: clamp(18px, 2.6vw, 34px);
  grid-template-columns: 1fr; margin-top: clamp(20px, 2.6vw, 30px);
}
@media (min-width: 760px) { .promo__g { grid-template-columns: repeat(3, 1fr); } }

.promo__c {
  display: block; text-decoration: none; color: inherit;
  padding-top: 16px; border-top: 1px solid var(--line);
  position: relative;
}
/* The gold tick above each one. Short, so it reads as a mark rather than a rule. */
.promo__c::before {
  content: ''; position: absolute; left: 0; top: -1px; width: 34px; height: 2px;
  background: var(--metal);
}
.promo__c--soon::before { background: var(--line-strong); }


.promo__k--soon { color: var(--ink-3); }
.promo__t {
  font-size: var(--t-md); font-weight: 800; letter-spacing: -.014em;
  line-height: 1.25; margin-top: 7px;
}
.promo__b { font-size: var(--t-sm); color: var(--ink-2); line-height: 1.62; margin-top: 8px; }
.promo__go {
  display: inline-block; margin-top: 11px; font-size: var(--t-sm);
  font-weight: 700; color: var(--gold-ink);
}
a.promo__c:hover .promo__go { text-decoration: underline; text-underline-offset: 4px; }
a.promo__c:hover .promo__t { color: var(--gold-ink); }

/* A `soon` card is visibly quieter and is not a link — see promo.js. It still
   earns its place: knowing what is coming is worth something, and pretending
   it is here is worth less than nothing. */
.promo__c--soon .promo__t { color: var(--ink-2); }
.promo__c--soon .promo__b { color: var(--ink-3); }

.promo__f { font-size: var(--t-sm); color: var(--ink-3); margin-top: clamp(18px, 2.2vw, 26px); }

/* the button that opens the showcase — a link in prose, not a call to action
   competing with the page's own */
.promo__all {
  font: inherit; font-size: var(--t-sm); font-weight: 700; cursor: pointer;
  background: none; border: 0; padding: 0; color: var(--gold-ink);
  text-decoration: underline; text-underline-offset: 4px;
}
.promo__all:hover { color: var(--ink); }

/* ══ the showcase ══════════════════════════════════════════════════════════
   The advertising moment, on click rather than on the page. The band beside
   the reading stays quiet; this is where there is room to make the case. */
.sho { max-width: 620px; }
.sho__h {
  font-size: clamp(1.5rem, 2.6vw, 2.1rem); font-weight: 800; letter-spacing: -.022em;
  line-height: 1.14; margin: 8px 0 10px;
}
.sho__h em {
  font-style: normal; font-family: "Great Vibes", cursive; font-weight: 400;
  color: var(--gold-lg); font-size: 1.28em; letter-spacing: 0;
}
.sho__lede { font-size: var(--t-sm); color: var(--ink-2); line-height: 1.65; }

.sho__l { margin-top: 18px; }
.sho__i {
  display: block; text-decoration: none; color: inherit;
  padding: 14px 0; border-top: 1px solid var(--line); position: relative;
}
.sho__i::before {
  content: ''; position: absolute; left: 0; top: -1px; width: 28px; height: 2px;
  background: var(--metal);
}
.sho__i--soon::before { background: var(--line-strong); }
.sho__k { font-size: var(--t-xs); font-weight: 700; letter-spacing: .16em;
          text-transform: uppercase; color: var(--gold-ink); }
.sho__k--soon { color: var(--ink-3); }
.sho__t { font-size: var(--t-md); font-weight: 800; letter-spacing: -.012em; margin-top: 5px; }
.sho__b { font-size: var(--t-sm); color: var(--ink-2); line-height: 1.6; margin-top: 5px; }
.sho__i--soon .sho__t { color: var(--ink-2); }
.sho__i--soon .sho__b { color: var(--ink-3); }
.sho__go { display: inline-block; margin-top: 8px; font-size: var(--t-sm);
           font-weight: 700; color: var(--gold-ink); }
a.sho__i:hover .sho__t { color: var(--gold-ink); }
a.sho__i:hover .sho__go { text-decoration: underline; text-underline-offset: 4px; }

.sho__sep { font-size: var(--t-xs); font-weight: 700; letter-spacing: .18em;
            text-transform: uppercase; color: var(--ink-3); margin-top: 24px; }
.sho__cta { margin-top: 24px; }


/* ══ the page ornament ═════════════════════════════════════════════════════
   A gold flourish between the header and the page title. It is a mark, not a
   picture: small, centred, and quiet enough that the eye passes over it on
   the way to the heading rather than stopping on it.

   ┌─ TO TURN IT OFF EVERYWHERE ────────────────────────────────────────────┐
   │  Set the next line to `none`. That is the whole rollback — the element  │
   │  stays in the markup doing nothing, so nothing else has to be touched.  │
   └────────────────────────────────────────────────────────────────────────┘ */
:root { --orn: block; }

.ornhead {
  display: var(--orn);
  /* ── the two gaps, set once ──────────────────────────────────────────────
     A frontispiece belongs to the page BELOW it, not to the chrome above. So the
     space under it is smaller than the space over it — the standard pairing rule,
     and it is what stops the ornament reading as a picture somebody dropped in.

     Measured across eight pages, the gap under it was 0, 49, 52 or 69 depending on
     which section happened to lead the page, because each brought its own top
     padding. The rule below zeroes that, so these two numbers are the only ones
     that decide it, everywhere. The art carries ~5% internal padding top and
     bottom, which is inside these figures. */
  margin: 30px auto 20px;
  width: min(300px, 56vw);
  aspect-ratio: 2172 / 724;

  /* The source is gold on WHITE, not a transparent PNG. `multiply` makes the white
     become exactly the page colour — white × anything IS that thing — so it drops out
     perfectly with no halo, which is the failure mode this project has already paid for
     twice when keying white out of a logo. It also keeps the pale ivory florals, which a
     luminance key would have erased along with the background. */
  background: url('/ornament-560.png') center/contain no-repeat;
  mix-blend-mode: multiply;
  opacity: .9;
  pointer-events: none;
}
@media (min-resolution: 1.5dppx) {
  .ornhead { background-image: url('/ornament-1120.png'); }
}

/* On a dark surface multiply would black it out, so it is simply not drawn there —
   the footer and any night band have their own ornamentation. */
.band--night .ornhead, .foot .ornhead { display: none; }

/* ⚠⚠ NO LINE ABOVE THE HEADER INSIDE THE PROGRAM. `.ornhead` is the marketing
   frontispiece — a body-level 9px ornament sitting between the bar and `main`. On the
   public pages it belongs. On an app screen it is a decorative rule above a workspace,
   and it pushed `main` down 27px, which is why /browse's header measured top=111 where
   /dashboard's measures 84. The owner named it exactly: "there is no line at the top of
   the header, and the bottom line is looking just what i want."

   Hidden, not deleted, and keyed on `data-app` — which ds.js only sets for somebody
   SIGNED IN — so signed out /browse is still the public directory with its ornament.
   `display:none` leaves it in the DOM, so the `.ornhead ~ main` rules below still apply
   and main's top padding does not jump back. */
html[data-app] .ornhead { display: none; }

@media (max-width: 640px) { .ornhead { width: min(210px, 60vw); } }

/* Whatever leads the page gives up its own top padding — the ornament's margin is
   the gap. Three separate boxes were contributing to it and only one was obvious:
   `main` carries its own padding on some pages, and the leading `.band` on the
   directory had 51.7px of its own. Measured, then removed, rather than guessed at. */
/* `~`, not `+`. /v puts a hidden preview banner between the two, and an adjacent
   selector silently stopped matching — the ornament kept its own bottom margin AND
   the page kept its top padding, which is the doubled gap this rule exists to
   prevent. A general sibling combinator cannot be broken by inserting an element. */
.ornhead ~ main { padding-top: 0; }
.ornhead ~ main > :first-child,
.ornhead ~ main > section:first-of-type { padding-top: 0; }


/* ══ the run of the day ════════════════════════════════════════════════════
   A timetable, read down. The time is its own column so the eye can run the
   left edge and see the shape of the day before reading a single title —
   the same reason a departures board puts times in a column. */
.dslot {
  display: grid; grid-template-columns: 96px 5px minmax(0, 1fr);
  gap: clamp(14px, 2vw, 22px);
  padding: clamp(14px, 1.8vw, 18px) 0;
  border-top: 1px solid var(--line);
  transition: background .18s ease;
}
.dslot:first-child { border-top: 0; }

.dslot__t { text-align: right; padding-right: clamp(12px, 1.6vw, 18px);
            border-right: 1px solid var(--line); display: flex;
            flex-direction: column; gap: 2px; }
.dslot__t b { font-size: var(--t-md); font-weight: 800; letter-spacing: -.014em; color: var(--ink); }
/* The old time, struck through, next to the new one. Showing only the new time
   asks somebody to remember what it used to say. */
.dslot__t s { font-size: var(--t-xs); color: var(--ink-3); text-decoration-thickness: 1px; }
.dslot__t span { font-size: var(--t-xs); color: var(--ink-3); }

.dslot__h { font-size: var(--t-md); font-weight: 800; letter-spacing: -.014em;
            display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.dslot__w { font-size: var(--t-sm); color: var(--ink-2); margin-top: 3px; }
.dslot__w--open { color: var(--gold-ink); font-weight: 600; }
.dslot__n { font-size: var(--t-xs); color: var(--gold-ink); font-weight: 700; margin-top: 6px; }
.dslot__n--clash { color: #a3423a; }

.dslot__pin {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 2px 8px; border-radius: var(--r-pill);
  border: 1px solid var(--line-strong); color: var(--ink-3);
}

/* A slot that is about to move, and one that refuses to. Colour is doing real
   work here — it is the difference between "fine" and "this is where your day
   breaks" — so it is the only place on the page that gets any. */
.dslot--moving { background: linear-gradient(90deg, rgba(192,132,48,.07), transparent 70%); }
.dslot--moving .dslot__t b { color: var(--gold-ink); }
.dslot--clash  { background: linear-gradient(90deg, rgba(163,66,58,.07), transparent 70%); }
.dslot--clash .dslot__pin { border-color: #a3423a; color: #a3423a; }
.dslot--pin .dslot__t b { color: var(--ink-2); }

/* The space between two things. Not dead air — the gap is exactly where the
   directory belongs, which is the whole reason the schedule is on this site. */
/* ── the day, to scale ──────────────────────────────────────────────────────
   The run sheet was a LIST: every row the same height whether it held a ten-minute
   handover or a four-hour reception. It told you the order and hid the shape, so the
   one question you actually open it to answer — is there room — needed arithmetic.

   Now a row's height is its duration. Sixty minutes is sixty minutes tall wherever it
   appears, an hour of free time takes up an hour of the page, and a day with a hole in
   the afternoon LOOKS like a day with a hole in the afternoon.

   --mins is set per row by day.js. The floor matters: at 0.9px a minute a fifteen-minute
   slot would be 13px tall and unreadable, so nothing goes below 58px — which does mean
   very short items read slightly generous. That is the right way round. A schedule that
   is honest about proportion but unreadable at the bottom end has failed at both. */
/* The first attempt set 0.9px a minute and it did nothing: a 30-minute slot and a
   90-minute slot both came out 137px, because the row's CONTENT — title, then who, then
   the Edit button, stacked — was taller than either bar. min-height cannot shrink a box
   below what is inside it, so the scale was decorative.

   Two changes make it real. The content lies down (text left, actions right) so a row's
   natural height is about 56px instead of 137px, and a minute is worth 1.5px instead of
   0.9. Now 30 minutes is 56px, an hour is 90px, ninety minutes is 135px, and they look
   like what they are.

   The ceiling is the other half. A four-hour reception at true scale is 360px of mostly
   empty card, and a six-hour one pushes the end of the evening off the screen — so long
   blocks stop growing at 340px. That is a deliberate lie, and the smallest one available:
   it only ever affects the longest item on the page, whose length is never in doubt. */
.dslot {
  --mins: 60;
  min-height: max(56px, min(calc(var(--mins) * 1.5px), 340px));
  align-items: stretch;
}
.dslot__b {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
}
.dslot__b > div:first-child { min-width: 0; }
.dslot__acts { margin: 0; flex: 0 0 auto; }

/* The measure. A filled bar for booked time, so the eye reads the day as a column of
   solid and empty rather than a stack of equal cards. */
.dspine { position: relative; width: 5px; border-radius: 999px; background: var(--sink); }
.dspine__f {
  position: absolute; inset: 0; border-radius: 999px;
  background: linear-gradient(180deg, var(--gold-lg, #c08430), var(--gold-ink));
}
.dslot--open .dspine__f { background: none; border: 1px dashed var(--gold-ink); }
.dslot--done .dspine__f { background: var(--ink-3); }
.dslot--cancelled { opacity: .55; }
.dslot--cancelled .dslot__h { text-decoration: line-through; }

/* The summary, at the top: one bar, booked against free, for the whole day at a glance. */
.dtot { display: flex; height: 12px; border-radius: 999px; overflow: hidden; background: var(--sink); }
.dtot__s { height: 100%; }
.dtot__s--busy { background: linear-gradient(90deg, var(--gold-lg, #c08430), var(--gold-ink)); }
.dtot__s--free { background: repeating-linear-gradient(135deg,
                   var(--sink) 0 6px, var(--paper-warm) 6px 12px); }
.dtot__k { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 10px; }
.dtot__i { display: inline-flex; align-items: center; gap: 7px; font-size: var(--t-sm); color: var(--ink-2); }
.dtot__d { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.dtot__d--busy { background: var(--gold-ink); }
.dtot__d--free { background: repeating-linear-gradient(135deg,
                   var(--sink) 0 4px, var(--paper-warm) 4px 8px); border: 1px solid var(--line); }

.dslot__acts { display: flex; gap: 8px; flex-wrap: wrap; }

.dgap {
  /* A COLUMN, not a centred row. It was `flex; align-items: center` for a single line of
     controls; with a summary and a panel under it the panel became a centred flex item
     that could neither stretch to the width nor grow in height, so opening it measured
     zero and nothing moved. `justify-content: center` keeps the closed line sitting in
     the middle of the gap's scaled height, which is what the centring was for. */
  display: flex; flex-direction: column; justify-content: center; align-items: stretch; gap: 4px;
  margin-left: 96px; padding: 8px 0 8px clamp(14px, 2vw, 22px);
  border-left: 1px dashed var(--line-strong);
  /* Free time is drawn to the same scale as booked time. An hour of nothing should
     occupy an hour of page, or the gaps read as thin dividers rather than as the
     opportunity they are. */
  --mins: 30; min-height: max(42px, min(calc(var(--mins) * 1.5px), 340px));
}
.dgap__t { font-size: var(--t-xs); font-weight: 700; letter-spacing: .1em;
           text-transform: uppercase; color: var(--ink-3); }
.dgap__q { font-size: var(--t-xs); color: var(--ink-3); }
.dgap__go { font-size: var(--t-sm); font-weight: 700; color: var(--gold-ink); text-decoration: none; }
.dgap__go:hover { text-decoration: underline; text-underline-offset: 4px; }

@media (max-width: 640px) {
  .dslot { grid-template-columns: 1fr; gap: 6px; }
  /* The measure needs a column to be a column. On one column it would become a 5px
     block sitting between the time and the title, which reads as damage. */
  .dspine { display: none; }
  .dslot__t { flex-direction: row; align-items: baseline; gap: 8px;
              text-align: left; border-right: 0; padding-right: 0; }
  .dgap { margin-left: 0; }
}

/* the delay slider */
input[type="range"] { width: 100%; accent-color: var(--gold-ink); }


/* ══ the directory card ════════════════════════════════════════════════════
   A business here is a real one with a look of its own, and the old tile —
   a name and a line of grey text — gave somebody nothing to choose on. This
   carries their cover, their mark, their trade, their city and whether we
   have checked them.

   Everything below is optional and nothing collapses when it is absent. Most
   businesses fill this in over weeks rather than on the day they join, and a
   card that looks broken until then is a card that discourages them from
   ever finishing it. */
.vgrid {
  display: grid; gap: clamp(16px, 2vw, 24px); align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
}
.vgrid--quiet .vcard { opacity: .82; }

.vcard {
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--lift-1);
  text-decoration: none; color: inherit;
  transition: transform var(--mid) var(--ease), box-shadow var(--mid) var(--ease),
              border-color var(--mid) var(--ease);
}
.vcard:hover { transform: translateY(-3px); box-shadow: var(--lift-2); border-color: var(--line-gold); }

.vcard__top { position: relative; display: block; aspect-ratio: 16 / 9; background: var(--paper-warm); }
.vcard__cov { display: block; width: 100%; height: 100%; object-fit: cover; }

/* No cover: their initials on the warm paper. Not a grey box, and emphatically
   not a stock photograph of somebody else's wedding — a card that shows work
   the business did not do is a card that lies about them. */
.vcard__cov--none {
  display: grid; place-items: center; width: 100%; height: 100%;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(192,132,48,.10), transparent 70%),
    var(--paper-warm);
}
.vcard__cov--none i {
  font-style: normal; font-family: var(--font-display, inherit);
  font-size: clamp(28px, 4vw, 40px); font-weight: 800; letter-spacing: .06em;
  color: var(--metal);
}

.vcard__flag {
  position: absolute; left: 10px; top: 10px;
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  padding: 4px 10px; border-radius: var(--r-pill);
  background: rgba(23,19,32,.72); color: #fff; backdrop-filter: blur(6px);
}

.vcard__b { display: block; padding: clamp(15px, 1.8vw, 20px); position: relative; }

/* The mark sits ON the seam between cover and body — it belongs to both, which
   is what makes the card read as theirs rather than as our template. */
.vcard__logo {
  position: absolute; top: 0; right: clamp(15px, 1.8vw, 20px); translate: 0 -50%;
  width: 52px; height: 52px; border-radius: 50%; object-fit: contain;
  background: var(--paper); border: 1px solid var(--line);
  box-shadow: var(--lift-1); padding: 4px;
}

.vcard__k { display: block; font-size: var(--t-xs); font-weight: 700; letter-spacing: .15em;
            text-transform: uppercase; color: var(--ink-3); }
.vcard__n { display: block; font-size: var(--t-md); font-weight: 800; letter-spacing: -.014em;
            margin-top: 5px; padding-right: 58px; }
.vcard__tick { font-style: normal; color: var(--gold-ink); margin-left: 6px; font-size: .82em; }
.vcard__c { display: block; font-size: var(--t-sm); color: var(--ink-2); margin-top: 3px; }
.vcard__t { display: block; font-size: var(--t-sm); color: var(--ink-2); line-height: 1.55;
            margin-top: 8px; }
.vcard__go { display: block; font-size: var(--t-sm); font-weight: 700;
             color: var(--gold-ink); margin-top: 12px; }
.vcard__go i { font-style: normal; }
.vcard:hover .vcard__go i { margin-left: 3px; }


/* ══ the trade shelves ═════════════════════════════════════════════════════
   The directory opens here. Not cards — a list of ways in, set close enough
   together that the whole trade list is one glance rather than a scroll.

   Empty trades stay visible and say "none yet". That is information: it is
   the moment somebody realises we have no calligraphers and tells us about
   theirs. Hiding them would make the directory look complete and lose the
   lead. */
/* The lines between cells are drawn BY the cells, not by a 1px gap over a coloured
   background. The gap trick needs every row full: twenty trades in three columns leaves
   one cell empty on the last row, and that empty cell rendered as a tinted block — an
   unfinished shelf rather than the end of a list. Cells that draw their own edges simply
   stop where the list stops. */
/* ── the trade grid, demoted from front door to drawer ─────────────────────
   /browse opens on the businesses now, so the grid sits above the list behind one
   press. The button is left-aligned with the list rather than centred: it is a control
   on the page, not a heading for it. */
.tradepanel { margin-bottom: 18px; }
.tradepanel #tradeview { margin-top: 14px; }

/* ── narrowing, in a row, over the list it narrows ─────────────────────────
   Chips rather than a select: the whole point is that the trades most people want are
   READABLE without opening anything. "Everyone" is first and is a chip like the rest,
   so clearing the filter is the same gesture as setting it. */
/* ── what you described, answered in trades ────────────────────────────────
   Set apart from the plain "popular" chips because it is a RESULT, not a control: these
   are the trades the sentence pointed at, best first, and the number on each is why it
   ranked there. Warm panel so it reads as the page answering rather than more filters. */
.qtrades {
  background: var(--paper-warm); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 13px 15px; margin-bottom: 16px;
}
.qtrades__l {
  margin: 0 0 10px; font-size: var(--t-sm); font-weight: 600; color: var(--ink-2);
}
.qtrades__r { display: flex; flex-wrap: wrap; gap: 8px; }
.qtrades .poptrade { background: var(--paper); }

/* ── the event composer ────────────────────────────────────────────────────
   Signed out it is a page of its own and centres. Inside the program it is a screen in
   the shell and must start where every other screen starts — the sidebar already answers
   "where am I", so a centred column just breaks the left edge the eye is following. */
#start { width: min(760px, 100%); margin: 3vh auto; }
html[data-app] #start { margin: 0; max-width: 760px; width: 100%; }

/* ⚠ THE WHOLE ROW IS THE TARGET, EVERYWHERE `.prow` IS USED. Owner, on the Money board:
   "clicking on the right end 'budget' makes me spend time to look for the button… apply
   this logic to everywhere on the app." `data-go` is stamped on by trades.js from the
   row's own button, so the cursor never promises a destination the row does not have. */
.prow[data-go] { cursor: pointer; transition: background .14s var(--ease); }
.prow[data-go]:hover { background: var(--paper-warm); }
.prow[data-go]:hover .prow__acts .btn { border-color: var(--gold-ink); color: var(--ink); }

.poptrades { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.poptrade {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 40px; padding: 8px 14px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--paper); color: var(--ink-2);
  font: inherit; font-size: var(--t-sm); font-weight: 600;
  cursor: pointer; transition: background .15s, border-color .15s, color .15s;
}
.poptrade:hover { background: var(--paper-warm); border-color: var(--gold-ink); }
.poptrade.is-on {
  background: var(--night); border-color: var(--night); color: #fff;
}
.poptrade__n {
  font-style: normal; font-weight: 500; font-size: var(--t-xs);
  color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.poptrade.is-on .poptrade__n { color: rgba(255, 255, 255, .72); }

.trades {
  display: grid; gap: 0; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden;
  grid-template-columns: 1fr;
}
@media (min-width: 620px) { .trades { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .trades { grid-template-columns: repeat(3, 1fr); } }

/* Filtering down to one or two results left the rest of the row as empty cells inside
   the bordered box — the gap colour IS the grid's background, so an unfilled row reads
   as a shelf somebody forgot to finish. With few results the grid takes only the columns
   it needs and stops stretching. */
.trades--few { max-width: 460px; margin-inline: auto; }
@media (min-width: 620px)  { .trades--few  { grid-template-columns: 1fr; } }
@media (min-width: 1000px) { .trades--few  { grid-template-columns: 1fr; } }
.trades--pair { max-width: 760px; margin-inline: auto; }
@media (min-width: 1000px) { .trades--pair { grid-template-columns: 1fr 1fr; } }

.trade {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 15px 18px; background: var(--paper);
  text-decoration: none; color: inherit;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.trade { border-bottom: 1px solid var(--line); }
/* The container already draws the outer edge, so the last column must not draw its own
   or the right-hand side is two lines thick. One rule per breakpoint, matching the
   column counts above. */
@media (min-width: 620px) and (max-width: 999px) { .trade:nth-child(2n) { border-right: 0; } }
@media (min-width: 620px)  { .trade { border-right: 1px solid var(--line); } }
@media (min-width: 1000px) { .trade:nth-child(2n) { border-right: 1px solid var(--line); }
                             .trade:nth-child(3n) { border-right: 0; } }
.trade:hover { background: var(--paper-warm); }
.trade__n { font-size: var(--t-sm); font-weight: 700; letter-spacing: -.008em; }
.trade:hover .trade__n { color: var(--gold-ink); }
.trade__c {
  flex: 0 0 auto; font-size: var(--t-xs); font-weight: 700;
  color: var(--ink-3); font-variant-numeric: tabular-nums;
}
/* A trade with nobody in it is quieter but not hidden. */
.trade--none .trade__n { color: var(--ink-3); font-weight: 600; }
.trade--none .trade__c { font-weight: 600; letter-spacing: .04em; }


/* ══ comparing ═════════════════════════════════════════════════════════════
   Up to six. Three is a decision; twenty is a spreadsheet somebody abandons —
   and it is the same cap 16_planners_sharing put on how many vendors one
   request may reach, for the same reason. */
.vcell { display: flex; flex-direction: column; }
.vcell .vcard { flex: 1 1 auto; }

.vcard__cmp {
  margin-top: 8px; align-self: flex-start;
  font: inherit; font-size: var(--t-xs); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; cursor: pointer;
  padding: 7px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--line-strong); background: none; color: var(--ink-3);
  transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease),
              background var(--fast) var(--ease);
}
.vcard__cmp:hover { border-color: var(--metal); color: var(--ink); }
.vcard__cmp.is-on { background: var(--night); border-color: var(--night); color: #fff; }

/* The tray sits at the bottom and never covers the list it describes — a tray
   that obscures what you are choosing from is a tray people close. */
.cmptray {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 110;
  background: rgba(253,253,251,.94); backdrop-filter: blur(12px);
  border-top: 1px solid var(--line-strong);
  box-shadow: 0 -10px 30px rgba(23,19,32,.08);
}
.cmptray__in {
  max-width: var(--max); margin-inline: auto; padding: 12px var(--page);
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  justify-content: space-between;
}
.cmptray__l { display: flex; gap: 7px; flex-wrap: wrap; min-width: 0; }
.cmptray__c {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--t-xs); font-weight: 700;
  padding: 5px 6px 5px 12px; border-radius: var(--r-pill);
  border: 1px solid var(--line-gold); background: var(--paper-warm);
}
.cmptray__c button {
  width: 20px; height: 20px; border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(23,19,32,.07); color: var(--ink-2); font: inherit; font-size: 14px;
  line-height: 1; display: grid; place-items: center; padding: 0;
}
.cmptray__c button:hover { background: var(--night); color: #fff; }
.cmptray__r { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.cmptray__n { font-size: var(--t-xs); font-weight: 700; color: var(--ink-3);
              font-variant-numeric: tabular-nums; }
.cmptray__n.is-warn { color: #a3423a; }
.cmptray__h { font-size: var(--t-xs); color: var(--ink-3); }

/* The comparison itself. A table, because that is what a comparison is —
   and it scrolls sideways rather than squeezing six columns into a phone. */
.cmp { max-width: min(920px, 100%); }
.cmp__wrap { overflow-x: auto; }
.cmp__t { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
.cmp__t th, .cmp__t td {
  text-align: left; padding: 11px 14px 11px 0; vertical-align: top;
  border-top: 1px solid var(--line); min-width: 130px;
}
.cmp__t thead th { border-top: 0; padding-top: 0; font-weight: 800; }
.cmp__t tbody th {
  font-weight: 600; color: var(--ink-3); font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: .1em; white-space: nowrap;
  padding-right: 20px;
}
.cmp__no { color: var(--line-strong); }

@media (max-width: 640px) {
  .cmptray__in { padding-block: 10px; }
  .cmptray__l { max-height: 62px; overflow-y: auto; }
}


/* ══ a vendor's page ═══════════════════════════════════════════════════════
   /v used to run on shared.css with a theme the vendor picked — twelve
   palettes, six arrangements, five letterings, and nobody had looked at most
   of the 360 results. 68b pinned the look; this is that look, written as part
   of the design system rather than beside it.

   Everything here is optional. A business that has uploaded nothing still
   gets a page that reads as finished, because most of them will fill this in
   over weeks and a page that looks broken until then is a page they stop
   working on. */

.vcover {
  display: block; width: 100%; aspect-ratio: 21 / 8; object-fit: cover;
  border-radius: var(--r-lg); margin-bottom: clamp(18px, 2.4vw, 30px);
}

.vhead { display: flex; gap: clamp(16px, 2.4vw, 28px); align-items: flex-start;
         margin-bottom: clamp(18px, 2.2vw, 26px); }
.vhead__logo {
  width: clamp(64px, 9vw, 92px); height: clamp(64px, 9vw, 92px); flex: 0 0 auto;
  border-radius: 50%; object-fit: contain; padding: 6px;
  background: var(--paper); border: 1px solid var(--line); box-shadow: var(--lift-1);
}
/* ── the numbered set-up path on /vendor ────────────────────────────────────
   Every step is shown, finished ones included: a list that removes what you have done
   cannot tell you how far along you are. The number becomes a tick rather than
   disappearing, so the count never changes under you. Drawn by renderReady(). */
.setup { display: flex; gap: 12px; align-items: flex-start; list-style: none; margin: 0 0 14px; }
.setup__n {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; font-size: var(--t-sm); font-weight: 700;
  background: var(--paper-warm); color: var(--ink-2); border: 1px solid var(--line);
}
.setup--done .setup__n { background: var(--gold-ink); color: #fff; border-color: var(--gold-ink); }
/* block, not grid: the title and the OPTIONAL tag are inline and belong on the same
   line. As a grid every child became its own row, so the tag dropped underneath the
   heading and read as a second item. */
.setup__b { display: block; min-width: 0; }
.setup__t { font-weight: 700; color: var(--ink); text-decoration: none; }
.setup__t:hover { text-decoration: underline; }
.setup--done .setup__t { color: var(--ink-3); font-weight: 600; }
.setup__h { display: block; margin-top: 2px; color: var(--ink-3); font-size: var(--t-sm); line-height: 1.5; }
.setup__opt {
  font-size: var(--t-xs); color: var(--ink-3); text-transform: uppercase;
  letter-spacing: .06em; margin-left: 8px;
}

.vhead__body { min-width: 0; }

/* ── ONLY EVER SEEN BY THE OWNER, PREVIEWING THEIR OWN DRAFT ────────────────
   `.vplate` is the circle where the logo will go, and `.vscaf` lists the sections this
   trade recommends that are still empty. Both are drawn by scaffold() in v.js, which
   runs only when the viewer OWNS the listing and its status is not live — a customer
   must never be shown an outline of something the vendor chose not to write. Dashed,
   because the one thing they must not look like is finished. */
.vplate {
  display: grid; place-items: center;
  border-style: dashed; border-color: var(--line-gold);
  background: var(--paper-warm); box-shadow: none;
  color: var(--ink-3); font-size: var(--t-xs); letter-spacing: .06em;
  text-transform: uppercase;
}
.vscaf { border-style: dashed; border-color: var(--line-gold); background: var(--paper-warm); }
.vscaf__l { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.vscaf__l li {
  padding: 11px 13px; border: 1px dashed var(--line); border-radius: 10px;
  background: var(--paper); display: grid; gap: 2px;
}
.vscaf__l b { font-weight: 700; }
.vscaf__l span { color: var(--ink-3); font-size: var(--t-sm); line-height: 1.5; }

.vhead__tag { font-size: var(--t-md); color: var(--ink-2); line-height: 1.55; max-width: 62ch; }
.vhead__where { font-size: var(--t-sm); color: var(--ink-3); margin-top: 6px; }

/* Everything a customer can do, in one row. */
.vcta { display: flex; flex-wrap: wrap; gap: 9px; margin-bottom: clamp(18px, 2.2vw, 26px); }

/* A section heading. Quiet — the vendor's own words are the loud part. */
/* ── the eyebrow ───────────────────────────────────────────────────────────
   The small gold capital line that sits over a heading. It was written out four
   separate times — identical each time — which meant a change to "the eyebrow"
   reached whichever of the four I happened to remember. One block now. */
.vsec__h { margin-bottom: 12px; }

/* their photographs */
.vgal { display: grid; gap: 10px;
        grid-template-columns: repeat(auto-fill, minmax(min(100%, 180px), 1fr)); }
.vgal__i { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
           border-radius: var(--r-sm); cursor: zoom-in;
           transition: transform var(--fast) var(--ease); }
.vgal__i:hover { transform: scale(1.02); }

/* the lightbox */
.lbox { position: fixed; inset: 0; z-index: 130; display: grid; place-items: center;
        background: rgba(12, 9, 16, .92); padding: clamp(16px, 4vw, 48px); }
.lbox img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--r-sm); }
.lbox__x, .lbox__nav {
  position: absolute; border: 0; cursor: pointer; color: #fff;
  background: rgba(255,255,255,.12); backdrop-filter: blur(6px);
  width: 46px; height: 46px; border-radius: 50%; font-size: 20px; line-height: 1;
  display: grid; place-items: center;
}
.lbox__x:hover, .lbox__nav:hover { background: rgba(255,255,255,.24); }
.lbox__x { top: 18px; right: 18px; }
.lbox__nav { top: 50%; translate: 0 -50%; }
.lbox__nav--p { left: 18px; }
.lbox__nav--n { right: 18px; }

@media (max-width: 620px) {
  .vhead { flex-direction: column; gap: 14px; }
  .vcover { aspect-ratio: 16 / 9; }
  .lbox__nav { width: 40px; height: 40px; }
}


/* ══ what a business specifically offers ═══════════════════════════════════
   A menu, a price list, the sizes they carry. Five shapes cover every trade
   that asked, and the renderer switches on the SHAPE — so a new kind of
   section is a database row and no CSS at all.

   Deliberately quiet. This is somebody's price list, and a price list that
   is fighting the page for attention is a price list nobody reads to the
   bottom. The type does the work. */
.vsec { margin-bottom: 18px }
.vsec__i { color: var(--ink-2); line-height: 1.7; margin-bottom: 14px }

/* list — a thing and a line about it */
.vsec__l { list-style: none; margin: 0; padding: 0 }
.vsec__l li { padding: 11px 0; border-top: 1px solid var(--line) }
.vsec__l li:first-child { border-top: 0; padding-top: 0 }
.vsec__l b { display: block; font-size: 15.5px; font-weight: 700 }
.vsec__l span { display: block; font-size: 14px; color: var(--ink-2); margin-top: 3px; line-height: 1.6 }

/* priced — the same, with the money on the right where the eye can run it */
.vsec__p { list-style: none; margin: 0; padding: 0 }
.vsec__p li {
  display: flex; gap: 16px; align-items: baseline; justify-content: space-between;
  padding: 11px 0; border-top: 1px solid var(--line);
}
.vsec__p li:first-child { border-top: 0; padding-top: 0 }
.vsec__pn b { display: block; font-size: 15.5px; font-weight: 700 }
.vsec__pn span { display: block; font-size: 14px; color: var(--ink-2); margin-top: 3px; line-height: 1.6 }
/* Prices are free text — "from $200", "market price" — so the column sizes to
   its content rather than assuming a number. */
.vsec__pp {
  flex: 0 0 auto; font-size: 15px; font-weight: 700; color: var(--gold-ink);
  white-space: nowrap;
}

/* pairs — a label and a value, or a question and its answer */
.vsec__d { margin: 0 }
.vsec__d dt { font-size: 14px; font-weight: 700; margin-top: 12px }
.vsec__d dt:first-child { margin-top: 0 }
.vsec__d dd { margin: 3px 0 0; font-size: 14.5px; color: var(--ink-2); line-height: 1.65 }

/* grid — pictures with captions */
.vsec__g { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)) }
.vsec__g figure { margin: 0 }
.vsec__g img { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: 10px; display: block }
.vsec__g figcaption { font-size: 13px; color: var(--ink-2); margin-top: 5px }

@media (max-width: 520px) {
  .vsec__p li { flex-direction: column; gap: 4px }
  .vsec__pp { align-self: flex-start }
}

/* the vendor's section editor */
.secx { border: 1px solid var(--line-strong); border-radius: 12px; padding: 14px; margin-bottom: 12px }
.secx__h { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px }
.secx__h b { font-size: 15px }
.secx__x { width: 30px; height: 30px; border: 0; border-radius: 50%; cursor: pointer;
           background: var(--sink); color: var(--ink-2); font-size: 18px; line-height: 1 }
.secx__x:hover { background: var(--bad-bg, #f7e7e6); color: var(--bad-tx, #a3423a) }
.secx textarea { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px }

/* Prices are behind sign-in (69). The section still shows WHAT they offer — only the
   money is gated — and this line says so, because a price list with no prices reads as
   a business that forgot rather than a door. */
.vsec__gate {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 13px; border-top: 1px dashed var(--line-strong);
}
.vsec__gate span { font-size: 13px; color: var(--ink-2) }


/* ══ book & pay ════════════════════════════════════════════════════════════
   The disclaimer sits WITH the button rather than in a policy page. Somebody
   about to type a card number needs to know whose form it is at the moment
   they decide, and it has to be readable — so it is --mute (7.7:1), not a
   grey whisper under a gold button. */
/* ── the colours here are FIXED, not themed, and that is the point ──────────
   A vendor page is themed by the VENDOR: `--ink`, `--mute` and the rest are
   remapped by whichever palette they picked. The first version used those
   variables and measured 1.01:1 — the vendor's own name in the sentence
   "you pay X directly, not LA Party Lights" rendered #faf6ef on #faf7f2 and
   was invisible.

   That is not a contrast bug to nudge; it is a vendor being able to choose a
   palette that hides the disclaimer protecting their customer from them. A
   notice about who is taking your money must not be styleable by the person
   taking it. So: literal values, on a literal background, measured — 8.9:1
   for the body and 13.9:1 for the names. */
.vpay { margin: -6px 0 18px; padding: 14px 16px; border-radius: 14px;
        border: 1px solid rgba(23,19,32,.16); background: #ffffff }
.vpay__go { margin-bottom: 10px }
.vpay__note { font-size: 13px; color: #4a4453; line-height: 1.6; margin: 0 }
.vpay__note b { color: #171320; font-weight: 700 }


/* ══ the two signed-in workbenches ═════════════════════════════════════════
   /vendor and /plan were the last pages on shared.css. Their components are
   ported here rather than rewritten: they are working screens, and a rewrite
   risks behaviour for a visual gain nobody asked for. Palette names are mapped
   to the design system's — the two files named the same colours differently. */

.forgot__row { display: flex; gap: 8px; flex-wrap: wrap }
.forgot__row input { flex: 1 1 190px }
/* ── uploads ───────────────────────────────────────────────
   A big obvious target rather than a bare file input. Vendors do this on a phone as
   often as on a laptop, and a 90px tap target beats a 20px "Choose file". */
.drop {
  position: relative; display: grid; place-items: center; gap: 8px;
  min-height: 104px; padding: 14px; cursor: pointer; text-align: center;
  border: 1.5px dashed var(--line-strong); border-radius: 14px;
  background: var(--paper); transition: border-color .15s, background .15s;
}
.drop:hover, .drop:focus-visible, .drop.is-over {
  border-color:var(--gold-ink); background: rgba(169, 120, 42, .10); outline: none;
}
.drop--wide { min-height: 78px }
.drop__say { color: var(--ink-2); font-size: 14px }
.drop__pv { max-height: 108px; max-width: 100%; border-radius: 10px; object-fit: contain }
.drop__pv--sq { max-height: 84px }
.drop.is-busy { opacity: .55; pointer-events: none }
.gal { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 10px; margin-top: 12px }
.gal__i { position: relative; border-radius: 12px; overflow: hidden; background: var(--paper); cursor: grab }
.gal__i.is-drag { opacity: .35 }
.gal__i img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover }
.gal__x {
  position: absolute; top: 5px; right: 5px; width: 26px; height: 26px;
  display: grid; place-items: center; border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(8,4,14,.78); color: #fff; font-size: 15px; line-height: 1;
}
.gal__x:hover { background: var(--danger) }
.gal__n {
  position: absolute; bottom: 5px; left: 5px; padding: 1px 7px; border-radius: 20px;
  background: rgba(8,4,14,.78); color:var(--gold-ink); font-size: 11px; font-weight: 700;
}
.stat { padding: 13px; border-radius: 13px; background: var(--paper); text-align: center }
.stat b { display: block; font-family: var(--font); font-size: 27px; color:var(--gold-ink); line-height: 1.1 }
.stat span { display: block; margin-top: 3px; font-size: 12.5px; color: var(--ink-2) }
/* ── video ─────────────────────────────────────────────────
   Sized by ratio, not by height. A fixed height letterboxes on a phone and crops on a
   desktop, and there is no single number that is right on both. */
.vid { position: relative; aspect-ratio: 16/9; border-radius: 14px; overflow: hidden; background: #000 }
.vid iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0 }
/* ══════════════════════════════════════════════════════════
   THE LOOK PICKER
   ══════════════════════════════════════════════════════════
   A PANEL THAT SITS STILL. The whole thing is sized to the viewport and never grows
   past it, so choosing a colour never moves the page under you and the live preview is
   never scrolled away. Anything that does not fit scrolls INSIDE the options column —
   which is the one place scrolling costs nothing, because the preview stays put while
   it happens.

   The first version stacked all three dials down the page, ~1050px tall, with the
   preview stuck beside them. `position: sticky` cannot survive that: an element only
   stays stuck for (container height − its own height), and measuring it showed the
   preview held for 280px and was gone before you reached Lettering. Exactly as
   reported. Tabs plus a fixed panel fix the cause rather than fighting the symptom. */

.look { display: flex; flex-direction: column }
.look__top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 4px }
.look__h {
  font-family: var(--font); font-weight: 400; font-size: 19px; letter-spacing: .06em;
  text-transform: uppercase; color:var(--gold-ink);
}
.look__lab {
  font-size: 11.5px; font-weight: 900; letter-spacing: .11em; text-transform: uppercase;
  color:var(--gold-ink);
}
/* Options narrow and scrolling, the live page wide. The preview is the thing being
   judged, so it gets the room — on a desktop it was the smaller half, which is
   backwards. */
.look__grid { display: grid; gap: 18px; grid-template-columns: 1fr; margin-top: 14px }
@media (min-width: 900px) {
  .look__grid { grid-template-columns: 344px minmax(0, 1fr) }
}
.look__side { display: flex; flex-direction: column; min-height: 0 }
.look__scroll {
  overflow-y: auto; overscroll-behavior: contain; padding-right: 4px;
  scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent;
}
.look__scroll::-webkit-scrollbar { width: 7px }
.look__scroll::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 4px }
/* ── the live page ─────────────────────────────────────────
   Their REAL page, not a drawing of it. An approximate preview is worse than none — it
   makes somebody confident about something they have not actually seen. */
.look__pv { display: flex; flex-direction: column; min-height: 0 }
.look__pvtop { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 9px }
.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 999px; overflow: hidden }
.seg__b {
  padding: 6px 14px; border: 0; cursor: pointer; font: inherit; font-size: 12.5px;
  font-weight: 700; background: none; color: var(--ink-3);
}
.seg__b.is-on { background: var(--metal); color: var(--night) }
/* The frame is the "screen". It keeps its place in the layout and only its width
   changes — a phone-shaped screen centred in the same box, or the full width of it. */
.frame {
  position: relative; border-radius: 16px; overflow: hidden; flex: 1 1 auto;
  border: 1px solid var(--line-strong); background: var(--paper);
  margin-inline: auto; width: 100%; max-width: 340px; min-height: 0;
}
/* No transition on max-width. The desktop scale is worked out from the box's measured
   width, and an animating box measures whatever it happens to be part-way through — so
   the preview came out at 0.28 scale instead of 0.6, from a width that was still moving.
   A snap is honest; a 220ms animation that corrupts a measurement is not. */
.frame--wide { max-width: 100% }
.frame iframe { width: 100%; height: 100%; border: 0; display: block; background: var(--paper) }
/* Desktop: a real 1180px-wide page scaled down. The scale is set in JS from the actual
   box, so it fills whatever room there is instead of a number guessed once. */
/* ABSOLUTE, and that is a bug fix rather than a tidy-up.
   scaleFrame() sets this iframe's height FROM `.frame`'s own clientHeight, and a
   ResizeObserver on `.frame` calls scaleFrame again whenever it changes size. While the
   iframe was in flow those two fed each other: taller iframe → taller box → taller
   iframe. It only stayed still because fitPanel() pins the box to a fixed height first —
   and fitPanel BAILS when the panel is not laid out yet (`card.offsetParent === null`),
   which is exactly the window in which the preview iframe finishes loading and calls
   scaleFrame from its onload.
   Measured while testing: the /vendor document reached 436,188px.
   Out of flow, the iframe cannot change the box's height, so the loop has no path back
   round — no guard to get right, no clamp to tune. */
.frame--wide iframe {
  width: 1180px; height: 1500px; transform-origin: 0 0;
  position: absolute; top: 0; left: 0;
}
.frame__wait {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: var(--ink-3); font-size: 13.5px; background: var(--paper); pointer-events: none;
}
/* ── the couple's checklist ────────────────────────────────
   Every line is one thing that needs sorting, and every line can be settled three ways.
   The status is the loudest thing on the row because "what is left" is the only question
   anybody opens this page to answer. */
/* The page header for every rail destination. It and .dashhead were two components
   doing one job: /dashboard closed its header with a hairline and labelled it with an
   eyebrow, while /find, /trades and /messages had neither — so the screen the owner
   picked out as right ("Your events / Welcome back, Grig") was the only one that looked
   finished. Same geometry as .dashhead now; the eyebrow is markup and was added to each. */
.plan__head {
  display: flex; gap: 22px; flex-wrap: wrap;
  align-items: flex-end; justify-content: space-between;
  padding-bottom: 14px; margin-bottom: 20px;
  border-bottom: 1px solid var(--line);
}
/* ⚠ THE TOP SECTION MUST START AT THE SAME HEIGHT ON EVERY RAIL DESTINATION.
   It did not. Measured: /dashboard's header began at y=84 and /find, /trades and
   /messages at y=127 — forty-three pixels lower, made of two unrelated things stacked.
   Twenty-seven of it was `.ornhead`, the marketing frontispiece, still sitting above a
   workspace on three pages that had otherwise become app screens. The other sixteen was
   a margin on the `#app` section that /dashboard did not have.

   The ornament is gone from those three; this kills the margin. Together they put every
   rail destination's header on the same line as the one the owner picked out as right. */
/* Every top-level section of an app screen, not just #app — /plan's board is #board and
   kept its own 16px, which is why "My events" still sat below the line the others share.
   One rule for all of them; a screen's first section starts where main's padding says. */
html[data-app] main > section:first-of-type,
html[data-app] main > section#app,
html[data-app] main > section#board { margin-top: 0; }

/* And the eyebrow sits 6px above its title everywhere, which is what .dashhead got from
   an inline `margin:6px 0 0` on its h1 — so the pages that copied the markup without the
   inline style ran the two together at 0px. Set once, on the component. */
html[data-app] .plan__head .eyebrow,
html[data-app] .dashhead .eyebrow { margin: 0 0 6px; }
/* ⚠ AND THE LINE UNDER THE TITLE, ONCE. It was `style="margin-bottom:2px"` on /find and
   /trades and `4px` on /plan and /messages — written inline on each page's h1, which is
   the identical pattern that produced the 43px header offset fixed earlier the same day.
   Two pixels is not visible on one screen and is exactly visible when you click between
   two. Set on the component; the inline styles are deleted. */
html[data-app] .plan__head h1,
html[data-app] .dashhead h1 { margin-bottom: 4px; }
html[data-app] .plan__head .eyebrow + h1,
html[data-app] .dashhead .eyebrow + h1 { margin-top: 0; }

/* ── the tracker says how late you are, and what to do first ────────────────
   The list was twelve identical rows all reading "still to sort", so an event six days
   away with nothing booked looked the same as one a year off. These two are the whole
   difference between a list and a tracker: a sentence about time, and a heading that
   admits the list is already in priority order. */
.plan__urg {
  margin-top: 8px; font-size: var(--t-sm); font-weight: 600; line-height: 1.5;
  max-width: 62ch;
}
.plan__urg--soon { color: var(--gold-ink); }
.plan__urg--late { color: #9a2c2c; }
.plan__urg--ok   { color: var(--ink-3); }

.plan__grp {
  margin: 22px 0 10px; font-size: var(--t-xs); font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-3);
}
.plan__grp:first-child { margin-top: 0; }
.plan__prog { flex: 1 1 240px; max-width: 340px }
.bar2 { height: 9px; border-radius: 999px; background: var(--sink); overflow: hidden }
.bar2 i { display: block; height: 100%; width: 0; border-radius: 999px;
  background: linear-gradient(90deg, var(--gold-lg), var(--metal)); transition: width .35s }

/* ── the checklist row ────────────────────────────────────────────────────────
   ⚠ `.prow` HAD NO RULE AT ALL, AND NOR DID ANY `.tag--*`. Both were written in
   shared.css, which nothing live loads — the same fault the `.vrow` block below carries
   a note about, and the one CLAUDE.md names first. MEASURED ON THE LIVE SITE BEFORE
   FIXING: every row was `padding 0, border 0, radius 0, margin-bottom 0` and every one
   of the twelve status pills was `rgba(0,0,0,0)`. So the checklist was twelve bare divs
   stacked edge to edge, and "booked", "still to sort" and "not having this" were the
   same colourless grey capitals. It read as a broken list because it WAS one.

   Rebuilt on ds.css's own tokens rather than copied over from shared.css, whose
   `--surf3`/`--ok-w`/`--mute` do not exist here — a copy would have rendered just as
   raw while looking present in the file, which is worse.

   The status colours are the .pill palette EXACTLY: done is the same green in a tag as
   in a pill, waiting the same gold, wrong the same --danger. Two vocabularies for one
   set of states is how an interface starts feeling assembled rather than made. */
.prow {
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: clamp(13px, 1.5vw, 17px);
  margin-bottom: 10px;
}
.prow:last-child { margin-bottom: 0 }
/* ⚠ NO COLOURED EDGE ON THE CARD. There was a 3px --danger border-left on every overdue
   row and it was rejected outright: "remove that red stupid edge thing… the app is a
   delicate design and must stay as such."
   The reasoning holds up. Eleven rows were overdue at once, so the device that was meant
   to mark the exception was painting the whole list — an alarm on everything is an alarm
   on nothing, and a slab of red down eleven cards reads as a page that is broken rather
   than a page telling you something.
   ALARM IS SAID ONCE, WHERE IT COUNTS: the group heading carries "Overdue · 11" in the
   danger tint, and the row carries a 7px dot and the number of days. Two quiet marks
   instead of a red wall. */
.prow__dot {
  width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
  background: var(--line-strong);
}
.prow--overdue .prow__dot { background: var(--danger) }
.prow--pending .prow__dot { background: var(--gold-lg) }
.prow--done    .prow__dot { background: #1c6b41 }
.prow--skipped .prow__dot { background: transparent; box-shadow: inset 0 0 0 1px var(--line-strong) }

/* Settled and skipped lines RECEDE. They are not the work; they are the proof the work
   was done, and they should not compete with the things that still need doing. */
.prow--done, .prow--skipped { background: var(--sink); border-color: transparent }
.prow--skipped .prow__n { color: var(--ink-3); text-decoration: line-through }

/* ── THE ROW IS A GRID, SO THE LIST READS DOWN AS WELL AS ACROSS ──────────────
   It was `display:flex; flex-wrap:wrap`, which means every row laid itself out from its
   own content: the status pill started wherever the trade name happened to end, so
   "STILL TO SORT" sat at a different x on all twelve rows and nothing lined up. Reported
   as "still to sort and all other things must be nicely structured and columnized".

   Fixed tracks instead — trade, state, when, who, actions — so the columns are columns.
   `minmax(0, …)` on the flexible ones, or a long business name or pasted note stretches
   its track and drags the whole grid wider than the card. */
.prow__top {
  display: grid; align-items: center; gap: 10px 12px;
  grid-template-columns: 8px minmax(0, 1.25fr) 132px 104px minmax(0, 1.5fr) auto;
}
.prow__n { font-weight: 800; font-size: 16px; min-width: 0;
  overflow-wrap: anywhere }
.prow__who { color: var(--ink-2); font-size: 14px }
/* Who / what it cost / the note share one track and stay on one line. Three separate
   grid columns would leave two of them empty on most rows and the fourth column would
   never line up with anything. */
.prow__meta { display: flex; gap: 10px; align-items: baseline; min-width: 0;
  flex-wrap: wrap }
.prow__acts { display: flex; gap: 7px; align-items: center; justify-content: flex-end }
/* ⚠ FIXED WIDTH, OR THE LABEL MOVES THE COLUMNS. The last grid track is `auto`, so it
   sizes to its contents — and this button says "Sort this" on an unsorted line and
   "Change" on a settled one. Measured on the live page: the one skipped row's track came
   out 89.78px against everybody else's 93.62px, which handed the difference to the `fr`
   tracks and put that row's status pill 2px left of the other eleven. A column that is
   right on eleven rows and wrong on the twelfth is not a column. */
.prow__acts .btn { min-width: 94px; justify-content: center }
.prow__sp { display: none }        /* the grid does the spacing now */

/* Below the point where five tracks stop fitting, it goes back to flowing — a 132px
   status column in a 340px card is a column of clipped words. */
@media (max-width: 760px) {
  .prow__top { display: flex; flex-wrap: wrap; gap: 9px 11px }
  .prow__acts { margin-left: auto }
}
.tag { padding: 4px 12px; border-radius: 999px; font-size: 11px; font-weight: 900;
  letter-spacing: .09em; text-transform: uppercase; white-space: nowrap;
  background: var(--paper-warm); color: var(--ink-2) }
/* Six statuses, and `open` never had a colour in shared.css at all — it was added in 13,
   two years after that stylesheet stopped being loaded. */
.tag--needed     { background: var(--paper-warm);            color: var(--ink-3) }
.tag--open,
.tag--pending    { background: rgba(169, 120, 42, .12);      color: var(--gold-ink) }
.tag--booked     { background: rgba(28, 107, 65, .12);       color: #1c6b41 }
.tag--outsourced { background: rgba(28, 107, 65, .09);       color: #1c6b41 }
.tag--skipped    { background: transparent; color: var(--ink-3);
                   border: 1px dashed var(--line-strong) }

/* ── how late a line is ───────────────────────────────────────────────────────
   Its own pill beside the status, not a colour ON the status: they answer different
   questions. "Waiting on them" and "31 days late" are both true at once, and a design
   that can only show one of them has to drop the more useful one. */
.prow__due { font-size: 12.5px; font-weight: 700; color: var(--ink-3); white-space: nowrap }
.prow__due--late { color: var(--danger) }
.prow__due--set  { color: var(--gold-ink) }

/* The bucket heading, with its count. `.plan__grp` above is the plain version; this one
   carries the number, because "OVERDUE" alone makes somebody count the rows themselves. */
.plan__bkt {
  display: flex; align-items: baseline; gap: 9px;
  margin: 26px 0 11px; font-size: var(--t-xs); font-weight: 800;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-3);
}
.plan__bkt:first-child { margin-top: 0 }
.plan__bkt b { font-size: var(--t-xs); font-weight: 900 }
.plan__bkt--overdue b { color: var(--danger) }
.plan__bkt--pending b { color: var(--gold-ink) }
.plan__bkt--ready   b { color: #1c6b41 }
.plan__bkt__n {
  padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 900;
  background: var(--paper-warm); color: var(--ink-2);
}
.plan__bkt--overdue .plan__bkt__n { background: rgba(195, 20, 80, .1);  color: var(--danger) }
.plan__bkt--pending .plan__bkt__n { background: rgba(169, 120, 42, .12); color: var(--gold-ink) }
.plan__bkt--ready   .plan__bkt__n { background: rgba(28, 107, 65, .12);  color: #1c6b41 }
.plan__bkt__why { font-weight: 600; letter-spacing: 0; text-transform: none;
  font-size: var(--t-sm); color: var(--ink-3) }

/* The four counts under the progress bar. A summary you can read in one glance, which is
   the thing the single "N of M sorted" line could never be. */
.plan__tally { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px }
.plan__tally span {
  font-size: 12px; font-weight: 700; padding: 3px 10px; border-radius: 999px;
  background: var(--paper-warm); color: var(--ink-2);
}
.plan__tally .is-overdue { background: rgba(195, 20, 80, .1);  color: var(--danger) }
.plan__tally .is-pending { background: rgba(169, 120, 42, .12); color: var(--gold-ink) }
.plan__tally .is-ready   { background: rgba(28, 107, 65, .12);  color: #1c6b41 }

/* ══ ONE EVENT, ONE PAGE, ITS OWN TABS (direction A) ══════════════════════════
   The event's five sections were five cards stacked down one page. The checklist — the
   thing anybody opens this for — shared the screen with the guest list, the music, the
   run of the day and the sharing links, so the page was a mile long and the important
   part was one fifth of it.

   THE RAIL BEHIND THIS DOES NOT CHANGE. The tabs belong to the EVENT; the rail belongs to
   the PERSON. That is the distinction between the direction that was chosen and the one
   that was not, and it is worth keeping straight: if a future change makes the rail
   depend on which event is open, it has quietly become the rejected design. */
.crumb {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 10px; font-size: 12.5px; color: var(--ink-3);
}
.crumb a { color: var(--gold-ink); font-weight: 700; }

.etabs {
  display: flex; gap: 2px; overflow-x: auto;
  border-bottom: 1px solid var(--line); margin-bottom: 20px;
}
.etab {
  font: inherit; font-size: 13.5px; font-weight: 700; cursor: pointer; white-space: nowrap;
  padding: 11px 15px; border: 0; background: none; color: var(--ink-3);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.etab:hover { color: var(--ink); }
/* The current tab is marked with a rule under it, not a fill — a filled tab in a row of
   six reads as a button somebody has pressed rather than the place they are. */
.etab.is-on { color: var(--ink); border-bottom-color: var(--gold-lg); }

/* ══ /plan's WORKING AREA ═════════════════════════════════════════════════════
   The event on the left, finding a business on the right.

   NOT `.split`. That component is rail-LEFT / content-right and carries the "two first
   lines meet" alignment rule keyed to the content column's opener (check.sh 4k). This is
   the mirror of it — content-left, rail-right — and reusing the name would have meant one
   class with two layouts, which is the `.ev` and `.vh` mistake that has now cost this
   project three separate bugs. One name, one component.

   `minmax(0, 1fr)` on the main column, not `1fr`: a grid track defaults to `min-width:
   auto`, so one long unbroken string in a row (a business name, a pasted note) would push
   the column wider than the page and take the rail off-screen with it. */
.planwork {
  display: grid; align-items: start;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(18px, 2.2vw, 30px);
}
@media (min-width: 1000px) {
  .planwork { grid-template-columns: minmax(0, 1fr) 336px; }
}
.planwork__main { min-width: 0 }

/* Sticky, because the search is used AGAINST the whole list rather than against one line
   — scrolling to a row further down must not scroll the search away from it.
   `--sticky-h` is the header+subnav stack nav.js measures; the hardcoded 88px that used
   to be in .split__rail was written when the header was the only sticky thing and left
   seven pages clipped. Do not put a number here. */
@media (min-width: 1000px) {
  .planwork__side {
    position: sticky; top: calc(var(--sticky-h, 85px) + 14px);
    max-height: calc(100dvh - var(--sticky-h, 85px) - 28px);
    display: flex; flex-direction: column; min-width: 0;
  }
}

/* Padding matched to `.card` EXACTLY, not eyeballed. The column beside this one opens
   with a .card, and the house rule is that the two FIRST LINES meet — not the two boxes.
   At a flat 16px the rail's heading sat 10px above the paragraph next to it, measured on
   the live page. The sides stay tighter than the top so the rail does not eat width it
   has none of. */
.finder {
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: clamp(18px, 2vw, 26px) 16px 16px;
  display: flex; flex-direction: column; min-height: 0;
}
.finder__head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin-bottom: 13px }
.finder__t { font-size: 15px; font-weight: 800; letter-spacing: -.01em; margin: 0 }
/* Which line the results book into. Gold, because it is the one piece of state in this
   panel that changes under you rather than because you typed something. */
.finder__for { margin: 0; font-size: 12.5px; font-weight: 700; color: var(--gold-ink) }

.finder__ctl { display: grid; grid-template-columns: 52px minmax(0, 1fr);
  gap: 8px 10px; align-items: center }
.finder__l { font-size: 12px; font-weight: 700; color: var(--ink-3) }
.finder__ctl select, .finder__ctl input[type="search"], .finder__ctl input[type="text"] {
  width: 100%; min-width: 0; padding: 9px 11px; border-radius: var(--r-sm);
  font: inherit; font-size: 13.5px; background: var(--paper);
  border: 1px solid var(--line-strong); color: var(--ink);
}
.finder__chk { grid-column: 2; display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 700; color: var(--ink-2) }
/* Same reason as .prow__paid: a bare checkbox inherits the site-wide input styling and
   comes out as a 44px text field. */
.finder__chk input[type="checkbox"] {
  width: 16px; height: 16px; min-height: 0; padding: 0; margin: 0; flex: 0 0 auto;
  accent-color: var(--gold-ink);
}
.finder__n { margin: 13px 0 8px; font-size: 12px; font-weight: 700; color: var(--ink-3) }

/* The results scroll INSIDE the panel, so the rail itself never grows taller than the
   window — a sticky element taller than the viewport stops sticking and scrolls away,
   which is the failure this whole panel exists to avoid. */
.finder__res { overflow-y: auto; min-height: 0; margin: 0 -4px; padding: 0 4px }
.fres {
  display: block; width: 100%; text-align: left; cursor: pointer; font: inherit;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 11px 12px; margin-bottom: 8px;
}
.fres:hover { border-color: var(--gold-ink); background: var(--sink) }
.fres__n { display: block; font-weight: 800; font-size: 14px; color: var(--ink) }
.fres__m { display: block; font-size: 12.5px; color: var(--ink-3); margin-top: 2px }
.fres__tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px }
.fres__tag { font-size: 10.5px; font-weight: 900; letter-spacing: .07em;
  text-transform: uppercase; padding: 2px 8px; border-radius: 999px;
  background: var(--paper-warm); color: var(--ink-2) }
.fres__tag--ok { background: rgba(28, 107, 65, .12); color: #1c6b41 }
.fres__acts { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 10px }
.fres__acts .btn { padding: 7px 13px; font-size: 12.5px }

/* The empty state is doing real work here: the directory has ZERO live listings, so this
   is what almost every search returns today. It must say something true and offer the way
   forward rather than shrugging. */
.finder__none { font-size: 13px; color: var(--ink-2); line-height: 1.6 }
.finder__none b { color: var(--ink) }

/* ── which order the list is in ───────────────────────────────────────────────
   Two orderings that genuinely disagree, so it is a switch and not a preference buried
   in a menu. Segmented rather than two loose buttons, because the pair is one choice. */
/* The order switch and "Print the checklist" on one line. The switch keeps its own
   bottom margin for every other place it is used, so the bar cancels it rather than
   adding a second gap under the row. */
.ckbar {
  display: flex; flex-wrap: wrap; gap: 10px;
  align-items: center; justify-content: space-between;
  margin-bottom: 16px;
}
.ckbar .ordsw { margin-bottom: 0; }

.ordsw {
  display: inline-flex; gap: 3px; padding: 3px; margin-bottom: 16px;
  background: var(--sink); border-radius: 999px; border: 1px solid var(--line);
}
.ordsw__b {
  padding: 8px 16px; border-radius: 999px; border: 0; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 700;
  background: transparent; color: var(--ink-2);
}
.ordsw__b:hover { color: var(--ink) }
.ordsw__b.is-on { background: var(--paper); color: var(--ink); box-shadow: var(--lift-1) }

/* Up and down. Deliberately not drag-and-drop — see the note in plan.js. */
.prow__mv { display: inline-flex; gap: 3px }
.prow__mvb {
  width: 30px; height: 30px; padding: 0; cursor: pointer; font: inherit; font-size: 10px;
  border: 1px solid var(--line-strong); border-radius: 9px;
  background: var(--paper); color: var(--ink-2);
}
.prow__mvb:hover:not(:disabled) { border-color: var(--gold-ink); color: var(--gold-ink) }
/* Not hidden at the ends — a control that disappears makes the row below jump under the
   pointer. Dimmed and inert instead, so the pair keeps its width all the way down. */
.prow__mvb:disabled { opacity: .3; cursor: default }

/* A note on the row. Clamped, because somebody will paste a paragraph into it and a
   checklist row is not the place to read one — the full text is the title attribute. */
.prow__note {
  font-size: 13px; color: var(--ink-3); font-style: italic;
  max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Cost, note and paid. The three columns that have existed since 01 and never had a
   control between them. */
.prow__edit {
  display: flex; flex-wrap: wrap; gap: 9px; align-items: center;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--sink);
}
.prow__edit .prow2 { flex: 1 1 320px }
.prow__paid { display: inline-flex; align-items: center; gap: 7px;
  font-size: 13.5px; font-weight: 700; color: var(--ink-2); white-space: nowrap }
/* `input` is styled by default site-wide, which would give this checkbox a 44px-tall
   text-field box. Bare checkboxes have shipped wrong here before — see the ds.css note
   about unstyled fields — so this one names itself out of the default. */
.prow__paid input[type="checkbox"] {
  width: 17px; height: 17px; min-height: 0; padding: 0; margin: 0; flex: 0 0 auto;
  accent-color: var(--gold-ink);
}

/* ══ THE PROGRAM ══════════════════════════════════════════════════════════════
   "Once they sign in, they enter into the SaaS: settings, side bar, options… like any
   program, now they are in the program."

   `data-app` is put on <html> by DS.APP once the sidebar has something in it. Everything
   below hangs off that one attribute, so a page is either the website or the program and
   never half of each.

   THE WEBSITE'S TABS GO. Home / Vendors / Night Out / The blog / Pro / About are the
   website, and a person working inside their own account is not browsing it. The brand
   and the account menu stay: a program still needs a way out and a way to sign out. */
/* ⚠ EVERYTHING THE WEBSITE PUTS ON A PAGE COMES OFF, NOT JUST THE TAB BAR.
   The first version of this hid the nav and the subnav and stopped, and the result was
   a website with a sidebar bolted to it — reported as "at the bottom it's showing website
   links… it must be like a program platform, nothing from the website".

   Measured on the live dashboard before fixing: the signed-in screen carried the full
   marketing FOOTER with **23 links** (Start here, Create an account, The directory,
   Advertise with us, Our partners, Terms, Privacy…), the website's "Find" chooser inside
   the account area, and a 46.4px display headline. None of that belongs in somebody's
   workspace.

   The brand and the account menu are the only things that stay: a program needs a way to
   see whose account it is and a way out. */
html[data-app] .shell__nav,
html[data-app] .subnav,
html[data-app] .foot,                   /* 23 website links at the bottom of a workspace */
html[data-app] .vendorout,              /* "run a business? list it" — an advert */
html[data-app] .navback,                /* website breadcrumb; the sidebar is the map */
html[data-app] [data-spon],             /* belt and braces; check.sh 4l already forbids it */
html[data-app] .shell__r .menu:not([data-signedin]) {   /* the website's Find chooser */
  display: none !important;
}
/* nav.js measures the header into --shell-h and the subnav into --sticky-h. With the
   subnav gone the second measurement is stale, and anything that offsets itself by it
   would sit 52px low. Pinned here rather than re-measured, because the value is now a
   constant: header only. */
html[data-app] { --sticky-h: var(--shell-h, 85px); }

@media (min-width: 900px) {
  /* ⚠ THE COLUMN IS MADE BY PADDING THE BODY, NOT BY MARGINING THE MAIN.
     `margin-left: 232px` on <main> was tried first and measured 568px wide instead of
     1280: <body> is a flex column, and a flex item whose width is `auto` shrink-to-fits
     rather than stretching once you start overriding widths on it. Padding the container
     has no such subtlety — the sidebar is `position: fixed` and sits in the padding, and
     <main> keeps `.wrap`'s plain `width: 100%` of whatever is left. */
  .appside {
    position: fixed; z-index: 30;
    top: var(--shell-h, 85px); left: 0; bottom: 0; width: 232px;
    padding: 20px 14px 28px;
    overflow-y: auto;
    background: var(--paper);
    border-right: 1px solid var(--line);
  }
}
/* Below 900px the sidebar is not a sidebar — it rides at the top of the page as a
   scrollable row of the same links. A 232px fixed column in a 390px viewport leaves 158px
   of content, and the burger already owns navigation at that width. */
@media (max-width: 899.98px) {
  .appside {
    display: flex; gap: 6px; align-items: center; overflow-x: auto;
    padding: 10px clamp(16px, 4vw, 22px); margin-bottom: 6px;
    background: var(--paper); border-bottom: 1px solid var(--line);
    -webkit-overflow-scrolling: touch;
  }
  .appside__k { display: none; }        /* group headings do not fit on one line */
  .appside__l { white-space: nowrap; }
}

.appside__k {
  margin: 18px 0 7px; font-size: var(--t-xs); font-weight: 800;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-3);
}
.appside__k:first-child { margin-top: 0; }
.appside__l {
  display: block; padding: 9px 11px; border-radius: var(--r-sm);
  font-size: 14px; font-weight: 600; color: var(--ink-2); text-decoration: none;
}
.appside__l:hover { background: var(--sink); color: var(--ink); }
/* ⚠ THE ONE YOU ARE ON, SAID QUIETLY. This was a grey fill plus a 3px gold slab down the
   left edge, and it read exactly as heavy as it sounds — "that stupid highlight next to
   the sidebar tabs looks super boring and terrible".

   The fill and the slab both go. What is left is the thing that actually carries the
   meaning — the word turns gold and gains weight — plus one small diamond, the same mark
   the linear ornament uses, so the platform has ONE decorative vocabulary instead of a
   fresh idea per component. Nothing draws a box around anything. */
.appside__l.is-on {
  color: var(--gold-ink); font-weight: 800; position: relative;
}
.appside__l.is-on::before {
  content: ''; position: absolute; left: 1px; top: 50%;
  width: 5px; height: 5px; margin-top: -2.5px;
  background: var(--gold-lg); transform: rotate(45deg);
  border-radius: 1px;
}
.appside__l.is-on:hover { background: none; }
.appside__l--out { color: var(--ink-3); font-weight: 600; }
/* The signed-out rail. No tools on it — a sidebar of things somebody cannot open is
   worse than no sidebar — just the sentence that says the tools are still there. */
.appside__out {
  margin: 0 0 12px; padding: 0 12px; font-size: 13px; line-height: 1.5;
  color: var(--ink-3);
}

/* ══ AND THE WORK AREA READS LIKE AN APPLICATION ══════════════════════════════
   A marketing page is built to be READ once: a 46px display headline, a 1200px measure
   centred in white space, generous air between six sections. A workspace is built to be
   WORKED IN, every day, by somebody who already knows what the page is. Those are
   opposite briefs, and until now the signed-in screens were using the first one.

   So under `data-app` the type scale steps down, the measure opens out, and the page
   gets a header BAR rather than a hero. Nothing here is a new colour or a new component —
   it is the same design system at working density. */

/* The page fills the space it has. `.wrap` centres content at 1200px with the rest as
   white space, which is right for a page somebody is reading and wrong for a tool
   somebody is using — it left a 232px sidebar, a 1200px column and two dead margins. */
/* ⚠ `width: auto`, NOT the inherited `width: 100%`. `.wrap` is
   `width:100%; max-width:var(--max); margin-inline:auto`, and dropping only the max-width
   left it at 100% OF THE BODY — so `main` was 1512px wide AND pushed 232px right by the
   sidebar margin, giving a document 1744px wide in a 1512px window. Measured: every
   element on the page reported a right edge of 1708-1744.
   `width: auto` on a block with a margin fills exactly what is left, which is the whole
   point of block layout and what the 100% was overriding. */
/* NO `margin-inline` HERE. Setting it to 0 cancelled the 232px sidebar offset set in the
   media query above — the overflow went away and the content slid UNDERNEATH the
   sidebar, which is worse and looks deliberate. With `width: auto` the auto margins
   resolve to 0 on their own, so the only margin left is the one that positions the
   column. Fixing one measurement by breaking another is the thing to watch for here. */
html[data-app] body > main.wrap {
  max-width: none;
  padding: clamp(18px, 2.2vw, 30px) clamp(18px, 2.6vw, 36px);
}

/* ⚠ AFTER the shorthand above, not before it. `padding: a b` sets all four sides, so a
   `padding-left` written earlier in the file is simply overwritten — measured: the logo
   moved to the corner correctly and the CONTENT slid under the sidebar, starting at 36px.
   The offset belongs to the work area and NOT to <body>: padding on the body shifts the
   sticky header with it, which is what put the logo at x=232 in the first place. */
@media (min-width: 900px) {
  html[data-app] body > main.wrap {
    padding-left: calc(232px + clamp(18px, 2.6vw, 36px));
  }
}

/* Page titles. 46px is a headline for a stranger; this is a label for somebody who knows
   where they are. The display face stays — it is the brand — at working size. */
html[data-app] main .h2 { font-size: clamp(19px, 1.7vw, 23px); }
html[data-app] main .h3 { font-size: 17px; }
/* ⚠ THE h1 RULE COMES AFTER THE CLASS RULES AND NAMES THE CLASSES TOO. Page titles here
   are written `<h1 class="h2">` and `<h1 class="h3">`, so a bare `main h1` selector has
   the same specificity as `main .h3` and simply loses to whichever is written later.
   Measured live: the dashboard's title came out at 17px — smaller than its own body text.
   Whatever a page title is CALLED, it is the page title. */
html[data-app] main h1,
html[data-app] main h1.h1,
html[data-app] main h1.h2,
html[data-app] main h1.h3 { font-size: clamp(22px, 2.1vw, 28px); line-height: 1.2; }
/* The eyebrow above a page title is a marketing device — it introduces a section to
   somebody who has just arrived. In a workspace it is a second, quieter title saying the
   same thing, so it goes. */
html[data-app] main > .hero2 .eyebrow,
html[data-app] .apphead .eyebrow { display: none; }

/* A header BAR: title on the left, actions on the right, a rule under it. The single
   most recognisable thing about a program as opposed to a page. */
.apphead {
  display: flex; flex-wrap: wrap; gap: 12px 18px;
  align-items: baseline; justify-content: space-between;
  padding-bottom: 14px; margin-bottom: 20px;
  border-bottom: 1px solid var(--line);
}
.apphead__t { margin: 0; }
.apphead__s { margin: 4px 0 0; font-size: var(--t-sm); color: var(--ink-3); }
.apphead__acts { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

/* ⚠ THE TILE GRIDS OVERFLOWED THE MOMENT THE PAGE WENT FULL WIDTH.
   `.grid--3` is `repeat(auto-fill, minmax(280px, 1fr))`, which is right inside a 1200px
   marketing column and wrong in a 1744px work area: measured live, it laid out FIVE 273px
   columns running to x=1708 with the document 232px wider than the window. A workspace
   that scrolls sideways is the clearest possible sign the layout was never designed for
   it. Capped so tiles stay a readable width and the row simply ends. */
html[data-app] main .grid--2,
html[data-app] main .grid--3,
html[data-app] main .grid--4 {
  grid-template-columns: repeat(auto-fill, minmax(240px, 320px));
  justify-content: start;
}

/* THE WEBSITE'S OWN DESTINATIONS COME OUT OF THE WORKSPACE. /dashboard ended with four
   cards pointing at /tools, /pro, /nightout and /contact — marketing pages, inside
   somebody's account, under the heading "Everything else". The sidebar's "Back to the
   website" is the one door out and it is enough. */
html[data-app] #d-rest-wrap { display: none; }

/* ── the top bar: whose workspace, and who is in it ──────────────────────────
   Brand left, context beside it, person right. The layout every platform of this kind
   uses, because it answers the two questions a screen full of numbers cannot: which
   account am I looking at, and who am I signed in as. */
.appctx {
  display: flex; align-items: baseline; gap: 9px; min-width: 0;
  padding-left: clamp(12px, 1.6vw, 20px); margin-left: clamp(10px, 1.4vw, 18px);
  border-left: 1px solid var(--line-strong);
}
.appctx__t {
  font-size: 14px; font-weight: 800; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 34ch;
}
.appctx__k {
  font-size: 10.5px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase;
  color: var(--gold-ink); background: rgba(169, 120, 42, .12);
  padding: 2px 8px; border-radius: 999px; white-space: nowrap;
}
@media (max-width: 720px) { .appctx { display: none; } }

/* ⚠ THE LOGO GOES IN THE TOP-LEFT CORNER, OVER THE SIDEBAR COLUMN.
   It was sitting at x≈327 — after the sidebar, in the middle of the bar — because the
   header is a centred `.wrap` and the sidebar was simply laid underneath it. That is a
   website header with a sidebar beside it; every program of this kind puts the product
   mark in the corner, aligned with the rail it labels.

   So under `data-app` the bar stops being a centred wrap, the brand takes exactly the
   sidebar's width, and everything else flows to its right. */
@media (min-width: 900px) {
  html[data-app] .shell .wrap.shell__in {
    max-width: none; width: 100%; margin-inline: 0;
    padding-inline: 0;
    grid-template-columns: 232px minmax(0, 1fr) auto;
    column-gap: 0;
  }
  html[data-app] .shell__brand {
    grid-column: 1; grid-row: 1 / 3;
    height: 100%; padding-left: clamp(14px, 1.6vw, 20px);
    border-right: 1px solid var(--line);
    gap: 9px;
  }
  /* ⚠ THE MEDALLION GOES BACK INTO FLOW HERE. On the website it is absolutely positioned
     and centred against the whole header box — which was right when the brand block was
     the full width of a 1200px bar, and is wrong in a 232px column: it sat ON TOP of the
     wordmark, reading "LA P●LIGHTS". Seen in a screenshot, not in a measurement.
     `padding-left` on the brand exists only to clear that absolute medallion, so it goes
     back to 0 at the same time. */
  html[data-app] .shell__brand img {
    position: static; translate: none; left: auto; top: auto;
    width: 30px; height: 30px; flex: 0 0 auto;
  }
  html[data-app] .shell__brand { padding-left: clamp(14px, 1.6vw, 20px) !important; }
  html[data-app] .shell__wm { font-size: 13.5px; line-height: 1.15; }
  /* ⚠ THIS RULE USED TO HIDE THE CITY, on the reasoning that "the city belongs to the
     visitor's browsing, not to the workspace". That was wrong, and it was wrong in the
     way that matters: THE WORKSPACE IS WHERE YOU SEARCH FOR VENDORS. The directory, the
     cross-event trade board and the finder inside an event all answer "who is near me",
     and with this hidden there was no global way to change where "near me" is — asked
     directly as "how can we change the location where we are searching?".

     It stays out of the BRAND block, which is the product mark and nothing else. It sits
     at the end of the context row instead, so the top bar reads: what this screen is,
     then where you are looking, then who you are. */
  html[data-app] .place {
    display: inline-flex; grid-column: 2; grid-row: 1 / 3;
    justify-self: end; align-self: center; margin-right: 4px;
  }
  html[data-app] .appctx { justify-self: start; }
  html[data-app] .appctx { grid-column: 2; grid-row: 1 / 3; border-left: 0; margin-left: 0; }
  html[data-app] .shell__r { grid-column: 3; grid-row: 1 / 3;
    padding-right: clamp(14px, 1.8vw, 24px); }
}

.appwho { display: flex; align-items: center; gap: 10px; }
.appwho__n { font-size: 13.5px; font-weight: 800; color: var(--ink); white-space: nowrap; }
/* The eight-digit number from 118/119. It is what somebody reads out on the phone, so it
   is monospaced — a number people transcribe should not be in a proportional face. */
.appwho__a {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; font-weight: 600; color: var(--ink-3);
  background: var(--sink); padding: 3px 8px; border-radius: 7px; white-space: nowrap;
}
/* A REAL BUTTON. It read as "an afterthought in the middle" because it was a bare word
   at the same weight as the name beside it — nothing said it could be pressed. Filled,
   dark, its own shape, separated from the identity block by a rule. */
.appwho__out {
  font: inherit; font-size: 12.5px; font-weight: 800; cursor: pointer;
  padding: 9px 16px; border-radius: 999px; white-space: nowrap;
  background: var(--night); color: #fff; border: 1px solid var(--night);
  margin-left: 4px;
}
.appwho__out:hover { background: var(--night-soft); border-color: var(--night-soft); }
.appwho__out:active { transform: translateY(1px); }
/* The identity and the action are two different things, so a rule sits between them. */
.appwho::before {
  content: ''; width: 1px; align-self: stretch; margin: 8px 4px 8px 0;
  background: var(--line-strong);
}
@media (max-width: 720px) { .appwho__n, .appwho__a { display: none; } }
/* The old name/role button is the website's account menu. In the program the same three
   facts are on the bar itself, so the button would be a second copy of them. */
html[data-app] .shell__r .menu[data-signedin] { display: none; }

/* A tab whose screen is not built. Visible so the shape of the platform is legible,
   inert so nobody lands on nothing. */
.appside__l--soon {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  color: var(--ink-3); opacity: .62; cursor: default;
}
.appside__l--soon:hover { background: none; color: var(--ink-3); }
.appside__l--soon i {
  font-style: normal; font-size: 9.5px; font-weight: 900; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-3);
  border: 1px solid var(--line-strong); border-radius: 999px; padding: 1px 6px;
}

/* Panels sit tighter and flatter than marketing cards — a lift-2 shadow on nine tiles is
   a page that shimmers. Working surfaces are quiet. */
html[data-app] main .card {
  padding: clamp(14px, 1.5vw, 18px);
  box-shadow: none;
  border-color: var(--line-strong);
}
html[data-app] main .card--pad-lg { padding: clamp(16px, 1.8vw, 22px); }

/* The top bar is chrome, not a masthead: shorter, and it does not shout. */
html[data-app] .shell { box-shadow: none; border-bottom: 1px solid var(--line); }
html[data-app] .shell__wm { font-size: 15px; }
/* A real left arrow, not an escape printed as text. Written `\\2190` first, which is a
   literal backslash followed by 2190 — the sidebar read "\2190\00a0Back to the website".
   One backslash in CSS, and the trailing \00a0 is the non-breaking space after it. */
.appside__l--out::before { content: '\2190\00a0'; }

/* ── pick a place, never type one (125) ───────────────────────────────────────
   The dropdown for `input[data-place-pick]`. One control for every city field on the
   site — see the note in ds.js for why free text does not survive a blur. */
.placepick { position: relative }
.placepick__list {
  position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% + 5px);
  max-height: 268px; overflow-y: auto; padding: 5px;
  background: var(--paper); border: 1px solid var(--line-strong);
  border-radius: var(--r-sm); box-shadow: var(--lift-2);
}
.placepick__o {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  width: 100%; text-align: left; cursor: pointer; font: inherit; font-size: 14px;
  padding: 9px 11px; border: 0; border-radius: 8px; background: none; color: var(--ink);
}
.placepick__o span { font-size: 11.5px; font-weight: 900; letter-spacing: .08em;
  color: var(--ink-3) }
.placepick__o:hover, .placepick__o.is-on { background: var(--sink); color: var(--gold-ink) }
.placepick__o.is-on span { color: var(--gold-ink) }
.placepick__none { margin: 0; padding: 9px 11px; font-size: 13.5px; color: var(--ink-3) }

/* ── rating who you used (123) ────────────────────────────────────────────────
   PRIVATE, and the panel says so out loud. Gold rather than a new colour: the site has
   --danger and the golds and nothing else, and a rating is not an alarm. */
.prow__rate {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--sink);
}
.prow__rate__l { font-size: 13px; font-weight: 700; color: var(--ink-2) }
.prow__rate__p { font-size: 12px; color: var(--ink-3); flex: 1 1 100% }
.stars { display: inline-flex; gap: 2px }
.star {
  width: 28px; height: 28px; padding: 0; cursor: pointer; font-size: 17px; line-height: 1;
  border: 0; background: none; color: var(--line-strong);
}
.star:hover { color: var(--metal) }
.star.is-on { color: var(--gold-lg) }
.prow__again { display: inline-flex; align-items: center; gap: 7px;
  font-size: 13px; font-weight: 700; color: var(--ink-2); white-space: nowrap }
/* Same reason as .prow__paid and .finder__chk — a bare checkbox inherits the site-wide
   input styling and renders as a 44px text field. Third time, so it is worth a rule:
   ANY checkbox inside a .card on this site needs this. */
.prow__again input[type="checkbox"] {
  width: 16px; height: 16px; min-height: 0; padding: 0; margin: 0; flex: 0 0 auto;
  accent-color: var(--gold-ink);
}

/* A reminder that has been ticked off recedes rather than disappearing — the point of
   ticking it is seeing that it is done. */
.lrow--done .lrow__b { color: var(--ink-3); text-decoration: line-through }
.rm-tick { width: 16px; height: 16px; min-height: 0; padding: 0; margin: 0;
  flex: 0 0 auto; accent-color: var(--gold-ink); cursor: pointer }

/* Setting your own date on a line. Sits in the open panel under the five ways. */
.prow__duebox { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--sink) }
.prow__duebox label { font-size: 13px; font-weight: 700; color: var(--ink-2) }
/* `flex: 0 1 210px`, because a bare date input in a flex row has no basis and takes the
   whole line — which pushed its own "Sort this by" label onto the row above it and left
   a 960px-wide field for eight characters. Measured on the live page. */
.prow__duebox input[type="date"] {
  flex: 0 1 210px; min-width: 0;
  padding: 9px 12px; border-radius: var(--r-sm); font: inherit; font-size: 13.5px;
  background: var(--paper); border: 1px solid var(--line-strong); color: var(--ink);
}
.prow__duebox .quiet { flex: 1 1 100%; font-size: 12.5px; margin: 0 }
.prow__open { margin-top: 13px; padding-top: 13px; border-top: 1px solid var(--sink) }
.prow__ways { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px }
.way { padding: 9px 15px; border-radius: 999px; cursor: pointer; font: inherit; font-size: 13.5px;
  font-weight: 700; border: 1px solid var(--line-strong); background: var(--paper); color: var(--ink) }
.way:hover { border-color: var(--ink-3) }
.way.is-on { border-color:var(--gold-ink); background: rgba(169, 120, 42, .17); color:var(--gold-ink) }
.prow2 { display: flex; gap: 9px; flex-wrap: wrap; align-items: center }
.prow2 select, .prow2 input { flex: 1 1 170px; padding: 11px 13px; border-radius: 11px; font: inherit;
  font-size: 14.5px; background: var(--paper); border: 1px solid var(--line-strong); color: var(--ink) }
.vpick { display: grid; gap: 7px; max-height: 230px; overflow-y: auto; margin-bottom: 11px }
.vpick button { padding: 11px 14px; border-radius: 12px; cursor: pointer; font: inherit; text-align: left;
  border: 1px solid var(--line-strong); background: var(--paper); color: var(--ink) }
.vpick button:hover { border-color:var(--gold-ink) }
.vpick b { display: block; font-size: 14.5px }
.vpick span { color: var(--ink-3); font-size: 12.5px }
/* ── getting started ───────────────────────────────────────
   One question a screen. Three short questions asked in order beat one form with three
   sections — nobody abandons a screen with a single question on it, and each answer
   genuinely changes what the next screen should say. */
.step__n { font-size: 11.5px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
  color:var(--gold-ink); margin-bottom: 8px }
.picks { display: grid; gap: 12px; grid-template-columns: 1fr }
@media (min-width: 700px) {
  .picks { grid-template-columns: 1fr 1fr }
}
.pick {
  display: block; width: 100%; padding: 22px; cursor: pointer; font: inherit; text-align: left;
  border: 1.5px solid var(--line-strong); border-radius: 18px; color: var(--ink);
  background: linear-gradient(180deg,var(--paper),var(--paper));
  transition: border-color .16s, background .16s, transform .16s;
}
.pick:hover { border-color:var(--gold-ink); transform: translateY(-2px) }
.pick:focus-visible { outline: 2px solid var(--metal); outline-offset: 3px }
.pick b { display: block; font-size: 18px; margin-bottom: 5px }
.pick span:not(.pick__e) { display: block; color: var(--ink-2); font-size: 14px; line-height: 1.55 }
/* ── ticks ──────────────────────────────────────────────────────────────────
   Tick-all-that-apply, for the short factual lists: Halal, Kosher, Vegetarian —
   the things the directory filters on.

   Not .pick, which is a 22px card built for choosing ONE of two or three big things
   and would turn nine catering options into a page of its own. Not a bare checkbox
   either: this sits inside forms made of .field and .select, and a raw checkbox next
   to those looks like a control somebody forgot to style. Same borders, same radii,
   same gold as everything else — the difference is size, because the job is smaller. */
.ticks { display: flex; flex-wrap: wrap; gap: 9px; }
.ticks--scroll { max-height: 216px; overflow-y: auto; padding: 3px; overscroll-behavior: contain; }
.tick {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 9px 14px; border: 1.5px solid var(--line-strong); border-radius: 999px;
  background: var(--paper); color: var(--ink-2);
  font-size: var(--t-sm); font-weight: 600; line-height: 1;
  transition: border-color .14s, background .14s, color .14s;
}
.tick:hover { border-color: var(--gold-ink); color: var(--ink); }
.tick input { position: absolute; opacity: 0; width: 0; height: 0; }
/* The dot is the only thing that moves. A chip that changes size when ticked makes the
   whole group reflow under the pointer, so the next one is no longer where it was. */
.tick__d {
  width: 15px; height: 15px; border-radius: 50%; flex: 0 0 auto;
  border: 1.5px solid var(--line-strong); background: var(--paper);
  transition: border-color .14s, background .14s;
}
.tick:has(input:checked) {
  border-color: var(--gold-ink); background: var(--paper-warm); color: var(--ink);
}
.tick:has(input:checked) .tick__d {
  border-color: var(--gold-ink); background: var(--gold-ink);
  box-shadow: inset 0 0 0 2.5px var(--paper);
}
.tick:has(input:focus-visible) { outline: 2px solid var(--metal); outline-offset: 3px; }

.types { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)) }
.type {
  display: block; padding: 16px 15px; cursor: pointer; font: inherit; text-align: left;
  border: 1.5px solid var(--line-strong); border-radius: 15px; color: var(--ink);
  background: var(--paper); transition: border-color .16s, background .16s, transform .16s;
}
.type:hover { border-color:var(--gold-ink); background: rgba(169, 120, 42, .10); transform: translateY(-2px) }
.type__e { display: block; font-size: 24px; line-height: 1; margin-bottom: 8px }
.type b { display: block; font-size: 15px; margin-bottom: 3px }
.type span:not(.type__e) { display: block; color: var(--ink-3); font-size: 12.5px; line-height: 1.45 }
.plan__pick { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; margin-top: 10px }
.plan__pick select {
  padding: 9px 13px; border-radius: 11px; font: inherit; font-size: 14px; max-width: 260px;
  background: var(--paper); border: 1px solid var(--line-strong); color: var(--ink);
}
/* ── people and songs ──────────────────────────────────────
   Compact rows rather than cards. These are lists you scan and add to, not things you
   study one at a time. */
.lrow { display: flex; gap: 11px; align-items: center; flex-wrap: wrap;
  padding: 10px 0; border-top: 1px solid var(--sink) }
.lrow:first-child { border-top: 0 }
.lrow__b { font-weight: 700; font-size: 14.5px }
.lrow__m { color: var(--ink-3); font-size: 13px }
.lrow__x { margin-left: auto; width: 26px; height: 26px; display: grid; place-items: center;
  border: 0; border-radius: 50%; cursor: pointer; background: var(--sink);
  color: var(--ink-2); font-size: 15px; line-height: 1 }
.lrow__x:hover { background: var(--danger); color: #fff }
.roletag { padding: 3px 10px; border-radius: 999px; font-size: 10.5px; font-weight: 900;
  letter-spacing: .08em; text-transform: uppercase; background: var(--sink); color: var(--ink-2) }
/* ══ an event on the /plan board ════════════════════════════════════════════
   `.evc`, deliberately not `.ev` — that name is Night Out's and carries an 82px time
   column. These rules were the only half that existed, so the card fell through to that
   grid and came out 108px wide with a zero-width progress bar. */
.evc {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: clamp(15px, 1.9vw, 20px); margin-bottom: 12px; box-shadow: var(--lift-1);
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease),
              transform var(--fast) var(--ease);
}
.evc:hover, .evc:focus-visible {
  border-color: var(--line-gold); box-shadow: var(--lift-2); transform: translateY(-2px);
  outline: none;
}
/* Finished is kept, not celebrated — quieter, and it says "finished" in the row itself.
   `.ev--done` had no rule at all before, so a finished event looked identical to a live
   one on a screen whose whole job is telling them apart. */
.evc--done { background: var(--sink); box-shadow: none; }
.evc--done:hover { transform: none; }
.evc__top { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; margin-bottom: 11px }
.evc__n { font-size: var(--t-md); font-weight: 800; letter-spacing: -.012em; color: var(--ink) }
.evc__m { font-size: var(--t-sm); color: var(--ink-2) }
.evc__sp { flex: 1 1 auto }
/* How long is left. The one number on this screen somebody actually acts on, so it is
   the only thing allowed to shout — and only inside three weeks. */
.evc__when { font-size: var(--t-sm); font-weight: 700; color: var(--ink-2); white-space: nowrap }
.evc__when--soon { color: var(--gold-ink) }
.evc__bar { height: 7px; border-radius: 999px; background: var(--sink); overflow: hidden }
.evc--done .evc__bar { background: var(--line) }
.evc__bar i { display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg,var(--gold-lg),var(--metal)) }
.board__h { font-size: 11.5px; font-weight: 900; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 12px }
.pick .ic, .type .ic { margin-bottom: 11px }
.rig .drop{stroke:currentColor;stroke-width:1.2;opacity:.32}
/* The small working-screen headings stay sans — a didone at 18px in a dashboard is
   decoration where a label is wanted. */
.look__h, .ph{
  font-family:var(--font); font-weight:800;
  letter-spacing:-.005em; color:var(--ink);
}
.step__n, .board__h, .look__lab,
.foot__cols h4, .vhead__cat, .dtab__k{ font-family:var(--font); font-weight:800 }
@media (hover:hover) and (prefers-reduced-motion:no-preference) {
  .pick, .type{
    transition:transform .22s cubic-bezier(.2,.8,.2,1), box-shadow .22s, border-color .18s;
  }
  .pick:hover, .type:hover{
    transform:translateY(-4px);
    box-shadow:0 2px 4px rgba(20, 18, 12, .10), 0 18px 40px rgba(20, 18, 12, .10);
    border-color:var(--line-gold);
  }
}
.hero__art .ring, .hero__art .glyph, .hero__art .drop, .hero__art .spk{ display:none }
/* The string keeps its bulbs but gets its hangers back — a wire with nothing on it read
   as a stray line. Delicate, and behind everything. */
.hero__art .drop{ display:none }


/* The five panel headings on /vendor were the same inline style written out five
   times — 19px, uppercase, gold. At that size gold has to be --gold-ink to clear
   4.5:1; --gold-lg is for display type only, and the inline version used neither. */
.panel__h {
  font-family: var(--font); font-weight: 400; font-size: 19px;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--gold-ink); margin-bottom: 6px;
}


/* A field that failed its check, and the line that says why. --danger is the only red
   the site uses; at 5.9:1 on --paper it is readable as words, not just as a colour —
   which matters because the colour alone says nothing to anyone who cannot see it.
   That is why the message text changes as well as the border. */
/* `:focus` is listed too, on purpose. DS.bad focuses the field it is complaining
   about, so the error state and the focus state are ALWAYS on at the same moment —
   and .field__c:focus and .field__c.is-bad have identical specificity, which makes
   which-one-wins depend on where they happen to sit in the file. Naming it here means
   the red survives the focus ring rather than tying with it. */
.field__c.is-bad, input.is-bad, textarea.is-bad, select.is-bad,
.field__c.is-bad:focus, input.is-bad:focus, textarea.is-bad:focus, select.is-bad:focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px rgba(195, 20, 80, .14);
}
.field__h.is-bad, .hint.is-bad { color: var(--danger); font-weight: 600; }

/* Where a shortlisted vendor has got to, on the chip itself. Three states and each
   carries its own word — the colour is the second signal, never the only one, because
   "the amber one" means nothing to somebody who cannot see it. */
.cmptray__s {
  font-style: normal; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; padding: 2px 7px; margin-left: 7px;
  border-radius: var(--r-pill); background: var(--sink); color: var(--ink-3);
}
.cmptray__s.is-wait { background: rgba(169, 120, 42, .14); color: var(--gold-ink); }
.cmptray__s.is-done { background: rgba(30, 120, 70, .12); color: #1c6b41; }
.cmptray__s.is-off  { background: var(--sink); color: var(--ink-3); text-decoration: line-through; }

/* The trade finder. One field, centred over the grid it filters — the same field
   component as everywhere else, at the width of a phrase rather than the width of the
   page, because "flowers" is six characters and a full-bleed input invites an essay. */
.tradefind { max-width: 420px; margin: 0 auto clamp(14px, 2vw, 20px); }
.tradefind .field__l { text-align: center; display: block; }
.tradefind .field__c { text-align: center; }
/* Safari draws its own clear button on a search input; it lands on the paper as a grey
   blob that matches nothing else here, and Escape already clears the box. */
.tradefind input[type="search"]::-webkit-search-decoration,
.tradefind input[type="search"]::-webkit-search-cancel-button { appearance: none; }

/* Why a trade matched, when the word typed was not its name. It sits with the label
   rather than under it: at a glance the eye reads "Dresses & Suits · suits" as one
   answer, and the answer to "why is this here" should not need a second look. */
.trade__why {
  font-style: normal; font-size: 11px; font-weight: 600; letter-spacing: .02em;
  color: var(--gold-ink); margin-left: 8px;
}
.trade__why::before { content: "· "; color: var(--ink-3); }

/* The trades people ask for most, set in bold inside the one alphabet. Weight, not a
   separate section: hiding 35 of 56 behind a button meant a trade nobody could see was a
   trade nobody would list under, and the directory's whole claim is that it has all of
   them. Bold is found by the eye without anything being taken away.

   Which ones are bold is data (categories.common), changed from /admin — so it can
   follow what people actually search for instead of what somebody guessed once. */
.trade--common .trade__n { font-weight: 700; color: var(--ink); }

/* Kept: the toggle renders nothing now, but paintRest([]) still targets it, and a page
   that grows a second shelf later should not need the CSS written again. */
.moretrades { margin-top: clamp(18px, 2.4vw, 28px); text-align: center; }
.moretrades .trades { margin-top: 14px; text-align: left; }

/* ══ where are you ═════════════════════════════════════════════════════════
   A strip under the header, not a modal. Somebody who has just arrived has not agreed
   to answer anything, and a dialog in front of the thing they came to see is how a site
   teaches people to press Escape before reading. It sits on the recessed tone so it
   reads as chrome rather than as content, and it takes one line on a phone. */
.placebar {
  background: var(--sink); border-bottom: 1px solid var(--line);
}
.placebar__in {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(12px, 2vw, 24px); padding-block: 12px; flex-wrap: wrap;
}
.placebar__q { font-size: var(--t-base); color: var(--ink-2); margin: 0; }
.placebar__f { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.placebar__f .field__c { min-height: 40px; padding: 8px 12px; }
#pb-zip  { width: 92px; }
#pb-city { width: 150px; }
@media (max-width: 620px) {
  .placebar__in { flex-direction: column; align-items: stretch; }
  .placebar__f { justify-content: space-between; }
  #pb-city { flex: 1 1 auto; }
}

/* ══ the head of a shelf ═══════════════════════════════════════════════════
   What this shelf is, how many are on it, and in what order — at the top, where the
   question gets asked. It used to be answered in a note below the list, which is the
   right answer in the wrong place. */
/* ══ the head of a shelf ═══════════════════════════════════════════════════
   TWO ROWS, NOT TWO COLUMNS. It was one flex row — title on the left, Order on the
   right — so a trade with a long description made the left column taller and pushed the
   control down. Every shelf put it somewhere different, which is exactly the kind of
   thing that reads as sloppy without anybody being able to say why.

   The title now takes whatever height it needs and the controls sit on their own line
   underneath, at the same place on every shelf in the directory. */
.shelfhead {
  padding-bottom: 14px; margin-bottom: clamp(14px, 2vw, 20px);
  border-bottom: 1px solid var(--line);
}
.shelfhead__t { margin-bottom: 12px; }
/* The description is pinned to TWO LINES — a floor and a ceiling. Trades have
   descriptions of wildly different lengths ("Bouquets, arches, centrepieces." against
   "Not a full planner — one person who takes the timeline off you on the day itself."),
   and without this the head is a different height on every shelf, so the controls land
   somewhere new each time you open one. That is the thing that reads as sloppy without
   anybody being able to point at what is wrong.

   Two lines fits every current description at the widths this sits at, and clamps the
   one that eventually will not rather than letting it move the page. */
.shelfhead__t .eyebrow {
  max-width: 62ch; line-height: 1.45; min-height: 2.9em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
@media (max-width: 560px) {
  /* Narrow enough that two lines is optimistic; three, still fixed. */
  .shelfhead__t .eyebrow { min-height: 4.35em; -webkit-line-clamp: 3; }
}
.shelfhead__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
}

/* The segmented picker, on paper rather than on the dashboard's darker card. The
   difference is one hairline and a hover state: the vendor preview toggle lives inside a
   panel that already has an edge, and this one does not. */
.shelfhead .seg { background: var(--paper); box-shadow: var(--lift-1); }
.shelfhead .seg__b { padding: 7px 15px; transition: background var(--fast) var(--ease),
                                                    color var(--fast) var(--ease); }
.shelfhead .seg__b:hover:not(.is-on):not(:disabled) { background: var(--sink); color: var(--ink-2); }
.shelfhead .seg__b.is-on { color: var(--night); }
/* Disabled rather than hidden. "Nearest" with nowhere to measure from is unavailable,
   not non-existent, and a control that vanishes teaches people it was never there. */
.shelfhead .seg__b:disabled { opacity: .42; cursor: not-allowed; }

@media (max-width: 560px) {
  .shelfhead__bar { flex-direction: column; align-items: stretch; }
  .shelfhead .seg { width: 100%; }
  .shelfhead .seg__b { flex: 1 1 0; text-align: center; }
}

/* ══ where you are, in the header ══════════════════════════════════════════
   Beside the brand, never on it. It shows its current answer at rest — the point of a
   place control is that you can see it is wrong without pressing anything — and it is
   the city NAME that is the button, which is the shortest distance between noticing and
   fixing.

   The animation is a hairline that draws itself from the pin outwards, and a pin that
   lifts a hair. Nothing scales, nothing bounces: this sits next to a struck-coin
   medallion, and anything springy beside that reads as a different website. */
.place {
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; background: none; cursor: pointer;
  /* Trimmed from `6px 2px`. The header needed room at the TOP, where the medallion was
     flush against the edge — not inside this button, which was padding the city away from
     a wordmark it belongs under. Taking 4px back from here pays for the new breathing room
     above and keeps the sticky bar at 85px instead of 91px.

     PADDING-LEFT IS ZERO, and that is what makes the alignment survive. The offset below
     has to land the PIN on the wordmark, so any left padding here has to be subtracted
     from it — and the phone breakpoint sets its own padding, which silently broke the sum
     and put the pin 43px off. With no left padding the offset is one number meaning one
     thing at every width. */
  padding: 4px 2px 4px 0;
  /* Lines the PIN up with the "LA PARTY" wordmark above it — the whole control starts
     where the letters start, and the pin is the leftmost thing in it.

     It used to align the city TEXT there instead, which put the pin 19px further left,
     hanging alone in the gap under the medallion with nothing above it. Measured: pin at
     250, wordmark at 269. Two left edges 19px apart in a header this small read as a
     mistake, and the eye picks the pin as the start of the line because it is the first
     mark it meets.

     The wordmark begins 57px in (46px medallion + 11px gap) and this button carries 2px of
     its own padding, so 55px puts the pin on 57px exactly. Set HERE and not in the grid
     block above, because that block is 2200 lines earlier at the same specificity — it
     lost, and the city ended up hard against the logo. */
  margin-left: var(--brand-text-x);
  font: inherit; font-size: var(--t-xs); font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); position: relative;
  transition: color var(--mid) var(--ease);
}
.place__pin {
  flex: 0 0 auto; color: var(--metal);
  transition: transform var(--mid) var(--ease), color var(--mid) var(--ease);
}
/* The rule grows from the left, under the words, and stops at their width. */
.place::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 2px; height: 1px;
  background: var(--line-gold); transform: scaleX(0); transform-origin: left center;
  transition: transform var(--mid) var(--ease);
}
.place:hover, .place:focus-visible { color: var(--gold-ink); outline: none; }
.place:hover .place__pin, .place:focus-visible .place__pin {
  color: var(--gold-ink); transform: translateY(-1px);
}
.place:hover::after, .place:focus-visible::after { transform: scaleX(1); }
/* 15ch cut "Philadelphia, PA" to "PHILADELPHI…". The city is the one thing this control
   exists to show, so it gets the room a real place name needs; the nav gives instead. */
.place__c { white-space: nowrap; max-width: 22ch; overflow: hidden; text-overflow: ellipsis; }

/* THE CITY KEEPS ITS NAME ON A PHONE.
   This used to hide the name and keep the pin, on the reasoning that the phone header is
   already brand + burger and the city is written across the page anyway. Both halves were
   wrong. `.place` is on its OWN grid row under the wordmark, not squeezed in beside the
   burger — measured at 386px the row runs 77→233 and the burger starts at 322, so there
   was never a shortage of room. And a lone 13px pin inside a 156px button is not a
   smaller version of the control, it is a glyph with nothing to say: the one thing this
   button exists to do is tell you which city you are being shown, and on a phone it told
   you nothing at all. 77 is also exactly the wordmark's left edge, so with the name back
   the pin, "LA PARTY LIGHTS" and the city all start on the same line. */
@media (max-width: 900px) {
  /* `margin-left: 8px` parked the pin under the medallion, 43px adrift of the letters.
     Padding-left stays zero for the same reason it does at desktop: it would be
     subtracted from the offset. */
  .place { margin-left: var(--brand-text-x); padding: 6px 6px 6px 0; }
  .place__c { max-width: 18ch; }   /* narrower window, same ellipsis, still a real name */
  .place::after { display: none; }
}

/* The same control, wherever the city is written into the page. Clicking the word that
   is wrong is the affordance nobody has to be taught. */
.placeline b[data-city] { cursor: pointer; border-bottom: 1px dashed var(--line-gold); }
.placeline b[data-city]:hover { color: var(--gold-ink); }

/* Zip · City · State. The state is two characters and gets the width of two characters —
   a full-width box for "CA" invites somebody to write "California" into a field that
   takes two. */
.row3 { display: grid; gap: 12px; grid-template-columns: 1fr; }
@media (min-width: 480px) { .row3 { grid-template-columns: 100px 1fr 88px; } }

/* An empty shelf. It is the most-seen state in a directory that is still filling up, so
   it gets the same care as a full one: centred, unhurried, and it asks for the one thing
   that would actually fix it. */
.empty {
  text-align: center; padding: clamp(30px, 5vw, 60px) 20px;
  border: 1px dashed var(--line-strong); border-radius: var(--r-lg); background: var(--paper);
}
.empty__h { font-size: var(--t-lg); font-weight: 800; letter-spacing: -.012em; margin-bottom: 8px; }
.empty .quiet { max-width: 46ch; margin-inline: auto; }

/* ══ where something has got to ════════════════════════════════════════════
   One notice, four states, used wherever the site owes somebody an answer about
   something it is holding: a listing waiting to be checked, a suggestion waiting to be
   contacted, a page that is live.

   The vendor dashboard had four of these written inline with dark-theme colours —
   rgba(255,255,255,.2) borders and #8bf5b3 text — left over from shared.css. On the
   light page that is a white border on white paper. One component now.

   The dot is the second signal and the WORDS are the first. A state told only by colour
   is a state some people cannot read at all, so every variant says what it is. */
.memo {
  display: flex; align-items: flex-start; gap: 11px;
  /* Left, whatever it is dropped into. The referral form sits in a centred block, and a
     notice that inherits centring reads as a slogan rather than as a status. */
  text-align: left;
  padding: 14px 16px; border-radius: var(--r-md);
  border: 1px solid var(--line-strong); background: var(--paper);
  box-shadow: var(--lift-1); margin-bottom: 18px;
}
.memo__dot {
  flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%;
  background: var(--ink-3); margin-top: 6px;
}
.memo__b { display: block; font-weight: 800; letter-spacing: -.01em; margin-bottom: 3px; }
.memo__t { font-size: var(--t-sm); color: var(--ink-2); line-height: 1.55; }
.memo__t a { color: var(--gold-ink); font-weight: 600; }

/* Waiting on us. Gold, because it is the house colour for "we are on it" and this is
   the state most people will see first. */
.memo--wait { border-color: var(--line-gold); background: rgba(169, 120, 42, .05); }
.memo--wait .memo__dot { background: var(--gold-lg); }
/* Slowly, so it reads as a pulse rather than a blink. Nothing loops fast on this site. */
@media (prefers-reduced-motion: no-preference) {
  .memo--wait .memo__dot { animation: memopulse 2.6s var(--ease) infinite; }
}
@keyframes memopulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(169, 120, 42, .34); }
  55%      { box-shadow: 0 0 0 6px rgba(169, 120, 42, 0); }
}

.memo--live { border-color: rgba(28, 107, 65, .34); background: rgba(28, 107, 65, .05); }
.memo--live .memo__dot { background: #1c6b41; }

.memo--fix { border-color: rgba(195, 20, 80, .3); background: rgba(195, 20, 80, .045); }
.memo--fix .memo__dot { background: var(--danger); }

.memo--off .memo__dot { background: var(--ink-3); }

/* ══ a row about one thing ═════════════════════════════════════════════════
   A listing, a trade, a referral, a person — anything with a name, a state, a line of
   detail and something you can do to it. /adminla, /vendor and /businesses are built
   almost entirely out of these.

   They were written in shared.css, and NOTHING LIVE LOADS SHARED.CSS ANY MORE — only
   four unreferenced pages do. So every one of these rows has been rendering completely
   unstyled: the admin panel's listings read "Shoeberdraft Custom Shoes & Belts" as one
   run of text with no card, no spacing and no pill, and the same on the vendor page.
   They were never wrong, just never dressed. Rebuilt here on ds.css's own tokens rather
   than copied across, so they match the cards beside them instead of a dark theme that
   no longer exists.

   The status colours reuse the .memo palette exactly — a live thing is the same green in
   a pill as it is in a memo, and something waiting is the same gold. Two vocabularies
   for one set of states is how an interface starts feeling assembled rather than made. */
.vrow {
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: clamp(14px, 1.6vw, 18px);
  margin-bottom: 12px;
}
.vrow:last-child { margin-bottom: 0; }
.vrow__top { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; margin-bottom: 6px; }
.vrow__name { font-size: 17px; font-weight: 800; color: var(--ink); }
.vrow__line { margin: 3px 0; font-size: var(--t-sm); color: var(--ink-2); line-height: 1.55; }
.vrow__acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 13px; align-items: center; }
.vrow__acts .btn { padding: 9px 16px; font-size: 13.5px; }
/* The note field in the admin queue. It sits in a row of buttons, so it has to take the
   leftover width rather than the full line, or the buttons wrap under it every time. */
.vrow__acts input[type="text"] {
  flex: 1 1 220px; min-width: 0; padding: 9px 12px; font: inherit; font-size: 13.5px;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
}
.vrow__acts input[type="text"]:focus-visible { outline: 2px solid var(--metal); outline-offset: 1px; }

.pill {
  display: inline-block; font-size: 10.5px; font-weight: 900; letter-spacing: .1em;
  text-transform: uppercase; padding: 4px 10px; border-radius: 999px;
  background: var(--paper-warm); color: var(--ink-2); white-space: nowrap;
}
.pill--tag { background: var(--paper-warm); color: var(--ink-2); }
/* An account carrying nothing — signed up and stopped. Marked rather than left blank,
   because "no pill" reads as "ordinary customer" and this is the row worth chasing. */
.pill--wait { background: transparent; border: 1px dashed var(--line-gold); color: var(--ink-3); }
.pill--draft { background: var(--paper-warm); color: var(--ink-3); }
.pill--pending,
.pill--changes_requested { background: rgba(169, 120, 42, .12); color: var(--gold-ink); }
.pill--live { background: rgba(28, 107, 65, .12); color: #1c6b41; }
.pill--paused,
.pill--rejected { background: rgba(195, 20, 80, .1); color: var(--danger); }

/* ══ a form in named steps ═════════════════════════════════════════════════
   Nine fields in one card reads as long even when it is two minutes of typing, and
   /join is the first thing a business ever does here — so it is the first evidence of
   whether the rest is worth their time.

   The groups are the questions somebody would ask out loud and in this order: who are
   you, what is the business, where do you work, who do we ring. The rule between them
   is a hairline rather than a box, because four boxes inside one card is a form that
   looks MORE complicated than the wall it replaced. */
.fsec { padding-bottom: clamp(18px, 2.4vw, 24px); margin-bottom: clamp(18px, 2.4vw, 24px);
        border-bottom: 1px solid var(--line); }
.fsec--last { padding-bottom: 4px; margin-bottom: 18px; border-bottom: 0; }
.fsec__n {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--gold-ink); margin-bottom: 14px;
}
/* Every field inside a step gets the same gap, so the rhythm does not depend on which
   fields happen to carry a hint under them. */
.fsec .field + .field, .fsec .row2 + .field, .fsec .field + .row2, .fsec .row2 + .row2 {
  margin-top: 14px;
}
/* ...but NOT when the two fields are side by side. `.field + .field` is a STACKING rule —
   it means "the one below the last one" — and inside a `.row2` grid the second field is
   not below anything, it is beside it. The margin pushed the whole right-hand column down
   by exactly 14px: password sat lower than email, zip lower than city, phone lower than
   your name, label and box together. Every two-column row on /join, and anywhere else a
   `.row2` lives inside a `.fsec`.
   The grid's own `gap` already spaces these; the margin was only ever for the vertical
   case. */
.fsec .row2 > .field + .field,
.fsec .row3 > .field + .field { margin-top: 0; }
.fsec .hint { margin-top: 7px; }

/* A checkbox that is ON by default has to look like a decision, not like a line of small
   print somebody skimmed. It gets a box of its own, a warm ground, and enough room that
   the tick is obviously a control rather than a bullet.

   The tick is drawn rather than left to `accent-color`, which renders differently in
   every browser and reads as the OS's blue in some of them — the one control on the page
   that belongs to somebody else's design system. */
.check--box {
  padding: 14px 16px; border: 1px solid var(--line-strong); border-radius: var(--r-md);
  background: linear-gradient(180deg, #fff 0%, var(--paper) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 1px 2px rgba(60,42,20,.045);
  transition: border-color var(--mid) var(--ease), background var(--mid) var(--ease);
  gap: 13px; align-items: center;
}
.check--box:hover { border-color: var(--line-gold); }
/* Ticked is the loud state on purpose: it is on, and it should look on. */
.check--box:has(input:checked) {
  border-color: var(--line-gold); background: linear-gradient(180deg, #fff 0%, rgba(169,120,42,.05) 100%);
}
.check--box input {
  appearance: none; -webkit-appearance: none;
  width: 22px; height: 22px; margin: 0; flex: 0 0 auto;
  border: 1.5px solid var(--line-strong); border-radius: 6px; cursor: pointer;
  background: var(--paper);
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.check--box input:checked {
  background: var(--gold-lg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5 6.5 12 13 4.5'/%3E%3C/svg%3E") center / 13px 13px no-repeat;
  border-color: var(--gold-lg);
}
.check--box input:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(192,132,48,.22); }
.check--box span b { color: var(--ink); font-weight: 700; }

/* :has() is not everywhere yet. Without it the box simply does not warm when ticked —
   the tick itself still draws, which is the part that matters. */
@supports not (selector(:has(*))) { .check--box { background: var(--paper); } }

/* ══ the signed-in home ════════════════════════════════════════════════════
   Cards that are doors, not summaries. Each carries the one number that answers "is
   there anything new" — a dashboard that renders the first three of everything is four
   screens pretending to be one, and it loads like it. */
.dashhead {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding-bottom: 14px; border-bottom: 1px solid var(--line);
}
/* ── which part of your account ─────────────────────────────────────────────
   Only drawn when an account holds more than one role, so most people never see it.
   Styled as a segmented control rather than tabs with a rule under them: it sits above
   the content it filters, and a full-width tab rule would read as the page's own
   heading. Sized off the same tokens as the sub-nav so the two never disagree. */
.hats {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: clamp(18px, 2.2vw, 26px);
  padding: 5px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--paper-warm); width: fit-content; max-width: 100%;
}
.hat {
  appearance: none; border: 0; cursor: pointer; border-radius: 999px;
  padding: 9px 18px; font-size: var(--t-sm); font-weight: 700;
  background: transparent; color: var(--ink-2); white-space: nowrap;
}
.hat:hover { color: var(--gold-ink); }
.hat.is-on { background: var(--paper); color: var(--ink); box-shadow: var(--lift-1); }

.dash__k {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--gold-ink); margin-bottom: 14px;
}
.dcard {
  display: block; position: relative; text-decoration: none; color: inherit;
  padding: 18px 18px 17px; border: 1px solid var(--line-strong); border-radius: var(--r-md);
  background: linear-gradient(180deg, #fff 0%, var(--paper) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 1px 2px rgba(60,42,20,.05);
  transition: border-color var(--mid) var(--ease), box-shadow var(--mid) var(--ease),
              transform var(--mid) var(--ease);
}
.dcard:hover {
  border-color: var(--line-gold); transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 6px 18px rgba(60,42,20,.08);
}
.dcard__n {
  display: block; font-size: var(--t-2xl); font-weight: 800; line-height: 1;
  letter-spacing: -.02em; color: var(--ink); margin-bottom: 8px;
}
.dcard__t { display: block; font-weight: 700; margin-bottom: 4px; }
.dcard__l { display: block; font-size: var(--t-sm); color: var(--ink-3); line-height: 1.5; }
/* Nothing to look at yet — legible, not shouting. A zero styled like a number you should
   act on is a dashboard crying wolf every morning. */
.dcard--quiet .dcard__n { color: var(--ink-3); font-weight: 600; }
.dcard--wait { border-color: var(--line-gold); }
.dcard--wait .dcard__n { color: var(--gold-lg); }
.dcard--live { border-color: rgba(28,107,65,.34); }
.dcard--live .dcard__t { color: #1c6b41; }

/* Pro Planner in the nav. It is a different product, not a tier of the directory, so it
   reads as its own thing: gold, and set apart by a hairline rather than shouting. A
   paid item styled identically to the free ones is how a free site starts feeling like
   a trial. */
/* Seven items is what this header can hold, and only just. Adding Dashboard and Pro
   wrapped the brand onto two lines and broke "Night Out" and "The blog" in half at
   1389px — a nav that reflows into a paragraph. The label is "Pro" for the same reason
   a tab is not a sentence; the page it opens says the full name.

   Below 1180px the whole strip tightens rather than wrapping. Below 1000px the burger
   takes over anyway, which is where seven items belong. */
.shell__nav .pro-tab {
  color: var(--gold-ink); font-weight: 700;
  margin-left: 6px; padding-left: 16px; position: relative;
}
.shell__nav .pro-tab::before {
  content: ""; position: absolute; left: 0; top: 50%; translate: 0 -50%;
  width: 1px; height: 15px; background: var(--line-strong);
}
.shell__nav .pro-tab:hover { color: var(--gold-lg); }
@media (max-width: 1000px) {
  .shell__nav .pro-tab { margin-left: 0; padding-left: 0; }
  .shell__nav .pro-tab::before { display: none; }
}


/* Room for seven. Nothing shrinks below where it is still comfortably readable — at that
   point the burger is the right answer, not smaller type. */
@media (min-width: 1000px) and (max-width: 1180px) {
  .shell__nav { gap: 14px; }
  .shell__nav > a, .shell__nav .nd > a { font-size: var(--t-sm); white-space: nowrap; }
  .shell__brand .shell__wm { font-size: var(--t-base); }
}
.shell__nav > a, .shell__nav .nd > a { white-space: nowrap; }


/* The wordmark never wraps. Seven nav items squeezed the brand until "LA PARTY LIGHTS"
   broke onto three lines — a logo reflowing into a column is worse than any nav problem
   it was solving. The brand takes the width it needs; the nav is what gives.

   The tightening window is wide (up to 1280px) because the nav is now seven items plus a
   place control, an account menu and a button. Below 1000px the burger takes over. */
.shell__brand { flex: 0 0 auto; }
.shell__wm { white-space: nowrap; }
@media (min-width: 1000px) and (max-width: 1280px) {
  .shell__nav { gap: 13px; }
  .shell__nav > a, .shell__nav .nd > a { font-size: var(--t-sm); }
  /* COLUMN-gap, not `gap`. This said `gap: 12px`, meaning to tighten the space between the
     brand, the nav and the buttons — but the shorthand sets row-gap too, and the base rule
     only ever sets `column-gap`, leaving row-gap at 0. So between 1000px and 1280px a 12px
     gap appeared between the brand row and the city row and the sticky header stood 97px
     tall instead of 85px, on laptops, which is most of them. Nothing looked broken; the
     header was just quietly twelve pixels fatter on the machines it is used on most. */
  .shell__in { column-gap: 12px; }
  /* `margin-left: 8px` was here, to claw back 28px for the nav at laptop widths. It buys
     the nav nothing: the city button is in the brand's COLUMN, and that column is as wide
     as the wider of the two, so pulling the city left only moves it out from under the
     wordmark. It stays aligned; the nav gap above is what actually makes the room. */
  .place { margin-left: var(--brand-text-x); }
}

/* Who you are, at the top of your own menu. Set on the recessed tone so it reads as a
   label on the panel rather than as another thing to press — it is the only row in there
   that is not a destination. */
.menu__me {
  padding: 12px 14px; margin: -6px -6px 6px;
  background: var(--sink); border-bottom: 1px solid var(--line);
  border-radius: var(--r-md) var(--r-md) 0 0;
}
.menu__me b { display: block; font-size: var(--t-base); letter-spacing: -.01em; }
.menu__me span {
  display: block; font-size: var(--t-xs); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold-ink); margin-top: 3px;
}

/* ══ the second row ════════════════════════════════════════════════════════
   A drop-down tells you where you could go and then takes it away. This keeps the
   section on screen underneath the main bar, so somebody on "Tell us your idea" can see
   they are under Vendors, beside the two siblings they did not pick.

   It sits on the recessed tone so the two rows read as one header rather than as a
   header with a toolbar stuck under it, and it carries the hairline instead of the
   header so there is only ever one line under the whole thing.

   ROLLBACK: `SUBNAV = false` at the top of nav.js. These rules then match nothing and
   the drop-downs behave exactly as before — they were never removed. */
.subnav {
  background: var(--sink);
  border-bottom: 1px solid var(--line);
  /* BELOW the header, not above it. At 90 against the header's 80 this bar covered the
     account menu, which drops out of the header and down across it — so opening "My
     account" put the panel behind a strip of chrome. A second row is subordinate to the
     bar it hangs off; its z-index should say so. */
  position: sticky; top: var(--shell-h, 0); z-index: 70;

  /* THE ROW IS MOVED, SO THE BAR HAS TO HOLD IT IN.
     alignUnderTab() in nav.js centres the sub-tabs under the tab they belong to by
     translating `.subnav__in`. It clamps the shift so the LINKS never leave the gutter —
     but `.subnav__in` is the full 1200px wrap, and the links sit in the middle of it, so
     a legal shift still slides the container's empty right-hand edge off the page. That
     widened the document: /about measured 1473px of scroll width in a 1280px window,
     which is a horizontal scrollbar and a dark footer that stops short of the edge.
     `clip` rather than `hidden`: hidden would make this a scroll container, and a scroll
     container cannot be `position: sticky` against the header. Nothing visible is lost —
     the clamp already guarantees the links themselves are inside. */
  overflow-x: clip;
}
.subnav__in {
  display: flex; align-items: center; gap: clamp(14px, 2.2vw, 30px);
  padding-block: 9px; flex-wrap: wrap;
  /* CENTRED, not ranged left against the page gutter. Asked for, and right: the row is
     subordinate to the tabs above it, and those sit in the middle of the bar. Left-aligned
     it read as a third, unrelated strip of links that happened to be under the header
     rather than as the tab's own children. */
  justify-content: center;
}
.subnav__i {
  font-size: var(--t-sm); font-weight: 600; color: var(--ink-2);
  text-decoration: none; padding: 3px 0; position: relative; white-space: nowrap;
  transition: color var(--fast) var(--ease);
}
.subnav__i:hover { color: var(--gold-ink); }
/* Where you are, as a shape and not only a colour — the same rule the main bar follows,
   for the same reason: a hue alone is not readable by everybody. */
/* The SAME mark the main tabs use — two hairlines and a small struck diamond — not a
   2px bar. A second row wearing a different marker from the first is the definition of
   an afterthought: it says the two were drawn by different people on different days.
   Copied deliberately rather than approximated, so a change to one is a change to both. */
.subnav__i.is-on { color: var(--gold-ink); font-weight: 700; }
.subnav__i.is-on::after {
  content: ''; position: absolute; left: 50%; bottom: -4px; translate: -50% 0;
  width: 26px; height: 7px; pointer-events: none;
  background:
    linear-gradient(var(--metal), var(--metal)) center bottom / 100% 1px no-repeat,
    linear-gradient(var(--metal), var(--metal)) center 2px / 12px 1px no-repeat;
}
.subnav__i.is-on::before {
  content: ''; position: absolute; left: 50%; bottom: 2px; translate: -50% 0;
  width: 4px; height: 4px; rotate: 45deg; border: 1px solid var(--metal);
}
.subnav__in { padding-bottom: 15px; }   /* room for the mark to sit under the word */

/* Looking at another section is not the same as being in one. While previewing, the row
   says so quietly — no marker can appear (nothing here is where you are) and the ground
   lifts off the recessed tone, so the bar reads as "a look ahead" rather than "you are
   here". Without this the two states are indistinguishable and the bar starts lying. */
.subnav--preview { background: var(--paper); }
.subnav--preview .subnav__i { color: var(--ink-3); }
.subnav--preview .subnav__i:hover { color: var(--gold-ink); }

/* With a second row doing the job, the floating panel is noise on a wide screen. It
   stays in the markup and stays reachable by keyboard on narrow screens, where the
   burger menu is the navigation and there is no room for a second row. */
@media (min-width: 861px) {
  .has-subnav .shell__nav .nd__p { display: none; }
  .has-subnav .shell__nav .nd__c { opacity: .5; }
}

/* Drawn icons, not emoji. One stroke weight, one colour, and they take the card's own
   ink so they sit in the design rather than on top of it. Sized to read at a glance
   without competing with the label underneath — the label is what people scan. */
.typeic { width: 26px; height: 26px; display: block; color: var(--gold-lg); }
.type__e { display: block; margin-bottom: 10px; font-size: 22px; line-height: 1; }
.type:hover .typeic { color: var(--gold-ink); }


/* ══ the city line above a split ═══════════════════════════════════════════
   In a rail-plus-card layout the placeline and the card were starting on exactly the
   same pixel — measured at top 265 on /idea, a zero-pixel offset with 42px between them
   horizontally. Two different things beginning on one line read as one shelf, and the
   placeline's own rule ran straight into the card's edge.

   The city is a label for the PAGE, not a heading for the column it happens to sit in.
   So it lifts clear of the card and the card starts below it. The lift is on the rail's
   placeline only — the centred one on marketing pages is already doing a different job
   and is not touching anything. */
.split__rail > .placeline:first-child {
  margin-top: -6px;
  margin-bottom: clamp(16px, 2vw, 24px);
}
@media (min-width: 861px) {
  /* The content column starts below the label rather than beside it. clamp so it stays
     proportionate rather than being a magic number that only works at one width.

     SCOPED TO RAILS THAT ACTUALLY HAVE THAT LABEL. This was applied to every `.split` on
     the site, and only three of thirteen rails lead with a `.placeline` — the other ten
     open with a heading or a control, so they were pushing their content column down by
     31px to clear a label that is not there. On /contact that put "Or reach us directly"
     31px above the card beside it before the card's own padding was even counted. */
  .split:has(> .split__rail > .placeline:first-child) > :not(.split__rail) {
    padding-top: clamp(18px, 2.4vw, 34px);
  }

  /* AND THE TWO FIRST LINES MEET.
     A rail is bare text and the column beside it is usually a padded card, so their
     headings never started at the same height: the card's first line sits its own
     padding below its border, and the rail's sits at the top of the grid row. Reported
     as "the title and the next modal are not aligned" — measured at 70px apart on
     /contact, which is 31px of the rule above plus 38px of card padding.
     The same clamp as `.card--pad-lg`, so the two move together at every width rather
     than agreeing at one. Both shapes are matched: a card that IS the column, and a card
     wrapped in a plain div because the column holds more than one.

     NOT when the rail leads with a `.placeline`. That label is deliberately lifted clear
     of the card — the rule above exists to make the card start BELOW it — so dropping the
     rail to meet the card would undo the thing it was written for. Measured on /idea:
     69px apart by design, and this rule had cut it to 38 before the exclusion. */
  .split:not(:has(> .split__rail > .placeline:first-child)):has(> :not(.split__rail).card--pad-lg) > .split__rail,
  .split:not(:has(> .split__rail > .placeline:first-child)):has(> :not(.split__rail) > .card--pad-lg:first-child) > .split__rail {
    /* MARGIN, not padding. The rail is position:sticky, and padding is inside the sticky
       box — so once it sticks, the heading sat 38px BELOW the line everything else stops
       at, which is the same misalignment again in the state you actually land in. A
       margin moves the box in flow and leaves the stuck position alone. */
    margin-top: clamp(24px, 3vw, 40px);
  }

  /* THE SAME RULE FOR THE OTHER PADDED BLOCK ON THE SITE.
     The rule above only knows about `.card--pad-lg`, so /privacy and /terms — whose
     column opens with `.legal__sum`, a bordered summary box and not a card — were left
     23px out. Asked for as "the modal and the titles next to them must have the same top
     alignment on EVERY page, every time, everywhere", which is a rule and not a list of
     pages, so the answer is another selector here rather than a nudge on two files.

     The value is `.legal__sum`'s own padding (line 1765), exactly as the rule above uses
     `.card--pad-lg`'s. That is the whole mechanism: the rail drops by however much the
     box beside it insets its first line, so the two first lines meet at every width.

     check.sh rule 4k fails the build if a new padded opener appears in a column and is
     not listed here — otherwise this drifts back out one page at a time. */
  .split:not(:has(> .split__rail > .placeline:first-child)):has(> :not(.split__rail) > .legal__sum:first-child) > .split__rail {
    margin-top: clamp(16px, 2vw, 22px);
  }
}

/* ══ print ═══════════════════════════════════════════════════════════════════
   There were no print styles at all, so printing any page gave you the header, the
   navigation, the drop-downs, the footer and its four columns of links — and the run
   sheet somewhere in the middle. The run of the day is the one page here that genuinely
   gets printed: it goes in a pocket, on a fridge, into a coordinator's folder, and it
   has to work when the venue's wifi does not.

   Everything that is a control on screen is furniture on paper, so it goes. What is left
   is the schedule, black on white, at a size somebody can read at arm's length in a
   church hall. Rows are kept whole across page breaks — a slot split down the middle of
   a page is exactly the one that gets missed. */
@media print {
  .shell, .subnav, .foot, .ornhead, .skip, .menu__p, .nd__p, .navback__at,
  .btn, .dgap__go, .dslot__acts, [data-noprint] { display: none !important; }

  html, body { background: #fff !important; }
  body { font-size: 11.5pt; color: #000; }
  main, .wrap { max-width: none !important; padding: 0 !important; margin: 0 !important; }

  /* On paper the scale is a picture of the day, so it is kept — but bars need ink, and
     browsers drop backgrounds by default. */
  .dtot, .dtot__s, .dspine, .dspine__f, .pill, .sbar__t, .sbar__f {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }

  .dslot, .dgap { break-inside: avoid; page-break-inside: avoid; }
  .dslot { min-height: 0; border-top: 1px solid #ccc; padding: 8px 0; }
  .dgap  { min-height: 0; padding: 5px 0 5px 14px; }
  .card  { border: 1px solid #ccc !important; box-shadow: none !important; break-inside: avoid; }

  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
  a[href^="/"]::after, a[href^="#"]::after { content: ""; }

  /* ── the letterhead and the disclaimer ────────────────────────────────────
     Both carry `hidden` in the markup so they never appear on screen. `display: block`
     alone does not undo `hidden` — the UA rule is `[hidden] { display: none }` and it is
     as specific as this one, so the override has to name the attribute. */
  #printhead[hidden], #printfoot[hidden] { display: block !important; }

  #printhead { margin: 0 0 14pt; padding-bottom: 10pt; border-bottom: 2px solid #111; }
  .phead__top { display: flex; align-items: flex-start; gap: 14pt; }
  .phead__logo {
    width: 54pt; height: 54pt; flex: 0 0 auto;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
  .phead__id { flex: 1 1 auto; min-width: 0; }
  .phead__by { text-align: right; flex: 0 0 auto; }
  .phead__k {
    margin: 0 0 2pt; font-size: 7.5pt; font-weight: 700; letter-spacing: .12em;
    text-transform: uppercase; color: #666;
  }
  .phead__t { margin: 0 0 3pt; font-size: 19pt; font-weight: 800; letter-spacing: -.01em; }
  .phead__b { margin: 0; font-size: 10.5pt; font-weight: 700; }
  .phead__m { margin: 0; font-size: 9pt; color: #444; }

  #printfoot {
    margin-top: 16pt; padding-top: 9pt; border-top: 1px solid #999;
    break-inside: avoid; page-break-inside: avoid;
  }
  .pfoot__h {
    margin: 0 0 4pt; font-size: 7.5pt; font-weight: 700; letter-spacing: .12em;
    text-transform: uppercase; color: #666;
  }
  .pfoot__p { margin: 0; font-size: 8.5pt; line-height: 1.5; color: #333; max-width: none; }
  .pfoot__m { margin: 6pt 0 0; font-size: 8pt; color: #777; }

  /* The letterhead repeats if the day runs to a second sheet; a page of times with no
     name on it is exactly the thing being fixed. */
  @page { margin: 14mm 12mm; }
}

/* ── a sheet printed INSTEAD of the page (DS.PRINT.sheet) ────────────────────
   ⚠ THESE TWO RULES EXISTED ONLY IN `shared.css`, WHICH NO LIVE PAGE LOADS. That is
   the `.vrow`/`.pill` failure in CLAUDE.md happening again on a real button: /plan's
   song sheet was appended to the body with nothing hiding it, so it sat visible at the
   bottom of the workspace, and printing produced the whole /plan screen with the song
   list underneath it. Nobody noticed because a broken print is silent.

   Keyed on `data-printing` because /day prints the PAGE ITSELF — its real rows, with
   the break rules above that keep a slot off a page fold — and must keep doing so. A
   bare `body > *:not(#printsheet)` would empty that sheet. DS.PRINT.sheet sets the
   attribute for the length of one print and drops it on `afterprint`. */
#printsheet { display: none; }

@media print {
  html[data-printing="sheet"] body > *:not(#printsheet) { display: none !important; }
  html[data-printing="sheet"] #printsheet {
    display: block !important; color: #000; background: #fff; padding: 0;
  }

  /* ── the body of a sheet ──
     A checklist is read standing up, beside somebody, with a pen. Columns that line up
     down the page are the difference between a list you can tick off and a paragraph
     you have to parse — so it is a table, and every row is kept whole across a fold,
     because a line split down the middle of a page is exactly the one that gets
     missed. */
  .psheet { width: 100%; border-collapse: collapse; margin: 0 0 12pt; }
  .psheet__r, .psheet tr { break-inside: avoid; page-break-inside: avoid; }
  .psheet td {
    border-bottom: 1px solid #ddd; padding: 5pt 6pt 5pt 0;
    font-size: 10.5pt; vertical-align: baseline;
  }
  /* The box somebody actually ticks. Sized so a pen fits. */
  .psheet__box { width: 16pt; font-size: 13pt; line-height: 1; padding-right: 4pt; }
  .psheet__k { font-weight: 700; white-space: nowrap; padding-right: 10pt; }
  .psheet__m { color: #555; }
  .psheet__due { text-align: right; white-space: nowrap; color: #444; font-size: 9.5pt; }
  .psheet__no td { font-weight: 700; }      /* "do not play" has to survive a photocopy */

  .psheet__h {
    margin: 14pt 0 5pt; font-size: 11pt; font-weight: 800;
    letter-spacing: .06em; text-transform: uppercase;
    border-bottom: 1.5pt solid #111; padding-bottom: 3pt;
    break-after: avoid; page-break-after: avoid;   /* never a heading alone at the foot */
  }
  .psheet__n { font-weight: 600; color: #666; margin-left: 6pt; }
  .psheet__lead { margin: 0 0 10pt; font-size: 9.5pt; color: #444; }
  .psheet__none { margin: 0; font-size: 10.5pt; color: #444; }
}

/* ══ the admin's working sheet ════════════════════════════════════════════════
   Fields on the left, the vendor's real page on the right. Judging what a listing is
   missing from a checklist means trusting the checklist. Seeing the page means you
   cannot be wrong about it — "no logo" is a page with no logo, and you know at a glance
   whether it is worth publishing.

   Wider than a normal modal because it is two things at once, and it still obeys the
   same height rule as every other modal: never taller than the screen, scrolls inside. */
/* WIDTH, not max-width. `.modal__c` sets `width: min(520px, 100%)`, and a larger
   max-width cannot widen an element whose width is already fixed — the sheet came out
   512px with two 212px columns. The property has to be the same one to win. */
.avsheet { width: min(1140px, 100%); max-width: min(1140px, 100%); }
.avsheet__g { display: grid; gap: clamp(16px, 2vw, 26px); grid-template-columns: 1fr; }
@media (min-width: 900px) {
  .avsheet__g { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  /* Sticky, so the page stays in view while you work down a long column of fields. */
  .avsheet__pv { position: sticky; top: 0; }
}
.avsheet__work { min-width: 0; }
.avsheet__pv { min-width: 0; }
.avsheet__pvhead {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 8px;
}
.avsheet__frame {
  width: 100%; height: min(560px, 60vh); border: 1px solid var(--line-strong);
  border-radius: var(--r-md); background: var(--paper); display: block;
}

/* ══ /links — the whole site as one list ══════════════════════════════════════
   A sitemap the owner asked for so a page could be reached without hunting through
   tabs. Two decisions worth keeping:

   The box is centred and NARROW. It takes a word — "guest", "terms", "adminla" — and a
   full-bleed input invites a sentence, which this does not answer well. The same
   reasoning as `.tradefind` on the directory, and the same width.

   The groups are a single column of headed blocks rather than a grid of cards, because
   the filter empties them unevenly: a grid reflows on every keystroke and rows jump
   under the cursor you are about to click with. A column only ever gets shorter. */
.lkfind { max-width: 30rem; margin: clamp(18px, 2.4vw, 28px) auto 0; }
.lkfind input[type="search"] {
  width: 100%; min-height: 50px; text-align: center; font-size: var(--t-md);
}
/* Safari draws its own grey clear button on the paper. Same removal as .tradefind. */
.lkfind input[type="search"]::-webkit-search-decoration,
.lkfind input[type="search"]::-webkit-search-cancel-button { appearance: none; }
/* Reserved, not collapsed. The count appears and disappears as you type, and a line
   that takes up no room when empty shoves the entire list up and down under the
   cursor — the one thing this page must never do. */
.lkfind__n { min-height: 1.35em; margin-top: 10px; text-align: center; }

.lkg { margin-top: clamp(26px, 3.2vw, 40px); }
.lkg:first-child { margin-top: 0; }
.lkg__h {
  font-size: var(--t-xs); font-weight: 800; letter-spacing: .14em;
  text-transform: uppercase; color: var(--gold-ink);
  padding-bottom: 10px; margin-bottom: 14px; border-bottom: 1px solid var(--line);
}
.lkg__i { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.lkg__i a {
  display: block; padding: 13px 15px; border-radius: var(--r-sm);
  border: 1px solid transparent; text-decoration: none; color: var(--ink);
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.lkg__i a:hover, .lkg__i a:focus-visible { background: var(--paper); border-color: var(--line-gold); }
.lkg__i a b { display: block; font-size: var(--t-base); font-weight: 700; }
.lkg__i a span { display: block; font-size: var(--t-sm); color: var(--ink-2); line-height: 1.5; margin-top: 3px; }
/* "needs sign-in" — a fact about the destination, not a warning. It reads at the end of
   the description in the same breath, so it is styled as an aside rather than a badge:
   a pill here would give five rows a shout the other twenty-five do not have.

   BRACKETS, not a middot. It was "· needs sign-in", and on the three rows whose
   description fills the line it wrapped onto the next one — where a leading "·" stops
   being a separator and reads as a bullet, so the row looked like a list of one. A
   bracket is unambiguous wherever the line happens to break. */
.lk__nb { font-style: normal; color: var(--ink-3); white-space: nowrap; }
.lk__nb::before { content: "("; }
.lk__nb::after  { content: ")"; }

/* ══ the section editor: order, visibility, and the trade's own template ══════
   `sort` and `hidden` were columns on vendor_sections from the start and neither had a
   control, so a vendor could add a price list and had no way to say where it went or to
   take it down for a season without deleting it. */
/* Two rows, because four controls and a heading do not fit one. The header was
   `space-between` with a title and an × — adding ↑ ↓ and Hide overflowed this narrow
   column, wrapped "Our work" onto two lines and pushed the × past the panel edge where it
   was clipped in half. The heading takes its own line and the controls sit under it,
   right-aligned, so the row is the same shape whatever the heading is called. */
.secx__h { flex-wrap: wrap; }
.secx__h b { flex: 1 1 100%; }
.secx__btns { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.secx__b {
  min-width: 30px; height: 30px; padding: 0 8px; border: 1px solid var(--line-strong);
  border-radius: 8px; background: var(--paper); color: var(--ink-2); cursor: pointer;
  font-size: 12px; font-weight: 700; line-height: 1;
}
.secx__b:hover:not(:disabled) { border-color: var(--line-gold); color: var(--gold-ink); }
/* Not `visibility:hidden` and not removed. The arrows keep their place at the ends of the
   list, so the row of controls does not change width as you move a section up it — the
   buttons would slide out from under the cursor mid-reorder. */
.secx__b:disabled { opacity: .32; cursor: default; }
/* A statement, not a warning: an empty section simply is not on the page yet, and the
   colour of a problem would make eight seeded headings look like eight faults. */
.secx__tag {
  font-style: normal; font-size: var(--t-xs); font-weight: 600; color: var(--ink-3);
  background: var(--sink); border-radius: var(--r-pill); padding: 3px 9px; white-space: nowrap;
}
/* Switched off, and it has to LOOK switched off from across the panel — the whole point
   of hiding is that you can see at a glance what is not on your page. */
.secx.is-off { border-style: dashed; background: var(--sink); }
.secx.is-off .field { opacity: .55; }

.secrec {
  border: 1px solid var(--line-gold); border-radius: 12px; padding: 14px;
  margin-bottom: 14px; background: rgba(169, 120, 42, .05);
}
.secrec__k {
  font-size: var(--t-xs); font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--gold-ink); margin-bottom: 6px;
}
.secrec__p { font-size: 13px; color: var(--ink-2); line-height: 1.55; }
.secrec__l { list-style: none; margin: 12px 0; padding: 0; display: grid; gap: 8px; }
.secrec__l li { font-size: 13px; line-height: 1.45; }
.secrec__l b { display: block; font-weight: 700; color: var(--ink); }
.secrec__l span { display: block; color: var(--ink-3); }

/* The quiet vendor door, last thing inside <main> on the customer-facing pages. Spaced
   well clear of the silver band above it so it reads as a footnote to the page rather
   than a caption on the sponsor. */
.vendorout { margin-top: clamp(26px, 3.4vw, 44px); padding-bottom: clamp(8px, 1.4vw, 18px); }

/* The vendor's own photographs, offered as buttons under a picture section. The gallery
   shape used to ask for "image URL | caption", which is a fine instruction for a
   developer and an impossible one for a shoemaker with a phone full of photos: there is
   no step they could take to turn one into a public address. They have already uploaded
   these under Pictures; clicking one writes the line. */
.shotpick {
  display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px;
  padding: 8px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--sink);
}
.shotpick__b {
  width: 54px; height: 54px; padding: 0; cursor: pointer; overflow: hidden;
  border: 1px solid var(--line-strong); border-radius: 8px; background: var(--paper);
}
.shotpick__b img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shotpick__b:hover, .shotpick__b:focus-visible {
  border-color: var(--gold-ink); outline: none; transform: translateY(-1px);
}

/* ══ the composed description, and the vendor's own words under it ═══════════
   `about` was a free textarea and every listing filled it differently. It is answered now
   — see 95/96 — and the page prints the composed paragraph first, then whatever they
   wrote themselves. The rule between them says "they are speaking now" without a heading,
   which would make a two-line personal note look like an unfinished section. */
.vabout__std { color: var(--ink-2); line-height: 1.7; }
.vabout__own {
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
}

/* ══ the questionnaire in the vendor editor ══════════════════════════════════ */
.qz__q { margin-bottom: 16px; }
.qz__q .field__l { display: block; margin-bottom: 4px; }
/* No `.qz__ticks` here. The multi-choice questions use `.ticks` / `.tick`, the chip
   component this site already has — a second grid of my own would have been a third way
   of drawing the same control. */
/* The line the questions write. Shown as text rather than in an input, because it is not
   editable here — editing it would put the vendor back in the business of composing it. */
.qz__line {
  font-size: var(--t-base); line-height: 1.55; color: var(--ink);
  background: var(--sink); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 10px 12px;
}
.qz__pv {
  margin-top: 16px; padding: 14px; border-radius: 12px;
  border: 1px solid var(--line-gold); background: rgba(169, 120, 42, .05);
}
.qz__pvk {
  font-size: var(--t-xs); font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--gold-ink); margin-bottom: 8px;
}
.qz__pvt { font-size: var(--t-base); line-height: 1.7; color: var(--ink); }

/* ── back to where you came from ────────────────────────────────────────────
   Inserted as the first child of <main> by nav.js, and only when the referrer is a
   different page on this site. Quiet on purpose: it is a way out, not a call to action,
   and it must not compete with the page's own heading two lines below it. */
/* THE WAY BACK LIVES IN THE SUB-NAV BAR.
   Three positions were tried. In the flow at the top of <main> it pushed every page down
   by its own height — "it should adjust to the page, not adjust the page to it".
   Absolutely positioned inside <main> it stopped shifting anything and started landing on
   top of things, because something with no height must overlap whatever is under it; on
   /idea it sat across the city label.

   The bar is the right home: it is already there, already sticky, already measured into
   --sticky-h, and its left side is empty because the sub-tabs are centred under their own
   tab. Absolute inside the bar, so it does not join that row and cannot disturb the
   centring. */
.subnav { position: sticky; }        /* already is — restated so the absolute below anchors here */
.navback__at {
  position: absolute; left: 0; top: 50%; translate: 0 -50%;
  padding-inline: var(--page); pointer-events: none;
}
.navback {
  pointer-events: auto;
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--t-xs); font-weight: 600;
  color: var(--ink-3); text-decoration: none; white-space: nowrap;
}
.navback:hover { color: var(--gold-ink); }

/* The drive estimate in a gap. Quiet, and the word "estimate" is not optional — this is
   a straight line padded for real streets, not a route. It is the one number on the page
   that somebody might plan a morning around, so it must never look authoritative. */
.dgap__drive { font-size: var(--t-xs); color: var(--ink-3); white-space: nowrap; }
.dgap__drive i {
  font-style: normal; text-transform: uppercase; letter-spacing: .07em;
  font-size: .92em; opacity: .8; margin-left: 4px;
}

/* ── a gap that opens between two events ────────────────────────────────────
   Closed, a gap is one quiet line saying how long it is. Pressing it parts the two
   events either side and shows what the time sits between, and what can be put in it.

   `grid-template-rows: 0fr -> 1fr` is the animation: it is the only way to transition to
   a height the content decides, and it degrades to an instant open where it is not
   supported rather than to a broken one. The row's own height grows with it, which is
   what makes the two events visibly separate rather than a panel sliding over them. */
.dgap__sum {
  appearance: none; background: none; border: 0; width: 100%; cursor: pointer;
  display: flex; align-items: center; gap: 10px; padding: 2px 0; text-align: left;
  color: inherit; font: inherit;
}
.dgap__sum:hover .dgap__t { color: var(--gold-ink); }
.dgap__cue {
  margin-left: auto; width: 9px; height: 9px; flex: 0 0 auto;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  rotate: 45deg; opacity: .45; transition: rotate .22s ease, opacity .22s ease;
}
.dgap.is-open .dgap__cue { rotate: 225deg; opacity: .85; }

/* `hidden`, TOGGLED FROM JS — not a height animation, and that is a deliberate retreat.
   Two height techniques were tried and measured on the live page. `0fr -> 1fr` needs the
   grid to size against its content, and this panel is a flex item whose height the flex
   pass had already resolved to zero. A max-height pair then MATCHED the element —
   `.matches()` confirmed it — and still computed to 0px, and so did an inline
   `max-height: 75px` set from JS, which nothing in this stylesheet should be able to
   beat. Three passes, no explanation found.

   So the open state is `hidden` coming off, which cannot be overridden by anything here,
   and the motion is a fade-and-rise on the inner block — which animates reliably because
   opacity and transform do not depend on layout resolving a height. The events still part,
   because the panel genuinely takes up room once it is not hidden. */
.dgap__panel { width: 100%; flex: none; }
.dgap__in {
  opacity: 0; translate: 0 -4px;
  transition: opacity .24s ease, translate .24s ease;
}
.dgap.is-open .dgap__in { opacity: 1; translate: 0 0; }
.dgap__ba {
  margin: 10px 0 10px; font-size: var(--t-sm); color: var(--ink-3); line-height: 1.6;
}
.dgap__ba b { color: var(--ink-2); font-weight: 700; }
.dgap__acts { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; padding-bottom: 4px; }

/* On paper every gap is open: a printed sheet cannot be pressed, and "1h 30m free" with
   no idea what it sits between is the thing the panel exists to answer. */
@media print {
  .dgap__panel[hidden] { display: block !important; }
  .dgap__in { opacity: 1 !important; translate: none !important; }
  .dgap__cue, .dgap__acts { display: none !important; }
  .dgap__sum { padding: 0; }
}

/* ── the social row in the footer ───────────────────────────────────────────
   One icon per channel, drawn by DS.SOCIAL from `settings`. A channel with no address
   is still shown, dimmed and unclickable, so the row does not change width the day one
   opens and the owner can see at a glance which are still missing. */
.foot__social {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin: clamp(18px, 2.4vw, 26px) 0 0;
}
.soc {
  display: grid; place-items: center; width: 38px; height: 38px;
  border-radius: 50%; border: 1px solid var(--line-gold);
  color: var(--paper); text-decoration: none;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.soc:hover { color: var(--gold); border-color: var(--gold); background: rgba(255,255,255,.05); }
/* Not open yet: visibly present, visibly not a link, and not in the tab order. */
.soc--soon { opacity: .3; cursor: default; }
.soc--soon:hover { color: var(--paper); border-color: var(--line-gold); background: none; }

/* ══ THE OVERVIEW TILES — the blueprint ═══════════════════════════════════════
   Every part of the event, each saying the one number that matters and opening the tab
   that owns it. A tile is a BUTTON, not a card, because every one of them goes somewhere
   — a summary you cannot act on is a poster. */
.ovc {
  display: flex; flex-direction: column; gap: 3px; align-items: flex-start;
  text-align: left; cursor: pointer; font: inherit; width: 100%;
  background: var(--paper); border: 1px solid var(--line-strong);
  border-radius: var(--r-md); padding: 15px 16px;
}
.ovc:hover { border-color: var(--gold-ink); background: var(--sink); }
.ovc__n { font-size: 27px; font-weight: 800; letter-spacing: -.02em; line-height: 1.05; }
.ovc__t { font-size: 14px; font-weight: 800; margin-top: 3px; }
.ovc__s { font-size: 12.5px; color: var(--ink-3); }
.ovc--bad  .ovc__n { color: var(--danger); }
.ovc--wait .ovc__n { color: var(--gold-ink); }
.ovc--ok   .ovc__n { color: #1c6b41; }

.ovnext {
  display: flex; align-items: baseline; gap: 12px; width: 100%; text-align: left;
  cursor: pointer; font: inherit; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 12px 14px; margin-bottom: 8px;
}
.ovnext:hover { border-color: var(--gold-ink); background: var(--sink); }
.ovnext b { font-size: 14.5px; }
.ovnext span { font-size: 12.5px; color: var(--danger); font-weight: 700; }
.ovnext__go { margin-left: auto; color: var(--gold-ink) !important; }

/* A button that reads as a link — used for the breadcrumb back out of a line's screen,
   where an <a href> would need somewhere to point and this goes nowhere but back. */
.linkish { font: inherit; font-size: inherit; background: none; border: 0; padding: 0;
  cursor: pointer; color: var(--gold-ink); font-weight: 700; }

/* ══ THE INVITATION, AS A GUEST SEES IT ═══════════════════════════════════════
   One card, centred, no navigation. A guest arrived from a link somebody they know sent
   them; the last thing they need is a website around it. */
.inv {
  max-width: 520px; margin: 4vh auto 0; padding: clamp(26px, 4vw, 44px);
  background: var(--paper); border: 1px solid var(--line-gold);
  border-radius: var(--r-lg); box-shadow: var(--lift-2); text-align: center;
}
.inv__k { font-size: var(--t-xs); font-weight: 800; letter-spacing: .16em;
  text-transform: uppercase; color: var(--gold-ink); margin: 0 0 10px; }
.inv__h { font-size: clamp(26px, 4vw, 38px); line-height: 1.15; margin: 0 0 12px; }
.inv__m { color: var(--ink-2); margin: 0 0 20px; line-height: 1.6; }
.inv__rows { display: grid; gap: 9px; text-align: left; max-width: 340px; margin: 0 auto;
  padding-top: 18px; border-top: 1px solid var(--line); }
.inv__r { display: flex; gap: 12px; margin: 0; font-size: 14px; }
.inv__r b { flex: 0 0 74px; color: var(--ink-3); font-size: 12px; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase; padding-top: 2px; }
/* The nine themes are a change of paper, not a change of layout: a guest should be able
   to read any of them, and a theme that rearranges the card is a theme that can hide the
   address. */
.inv[data-theme="midnight"] { background: var(--night); color: var(--ink-on-night);
  border-color: var(--night-soft); }
.inv[data-theme="midnight"] .inv__h { color: #fff; }
.inv[data-theme="midnight"] .inv__k { color: var(--gold-on-night); }
.inv[data-theme="midnight"] .inv__rows { border-top-color: var(--night-soft); }
.inv[data-theme="garden"] { background: #f4f7f1; border-color: #cfe0c6; }
.inv[data-theme="deco"] { background: var(--cream); border-color: var(--metal); }
.inv[data-theme="script"] .inv__h { font-family: "Great Vibes", cursive;
  font-size: clamp(34px, 5vw, 50px); font-weight: 400; }

/* ══ A BARE PAGE ══════════════════════════════════════════════════════════════
   `/i` is somebody's invitation, opened because a friend sent them a link. Wrapping it
   in the site's navigation, the city control, the Find chooser and a 23-link footer is
   the same fault the signed-in screens had, pointed at a guest: they did not come to the
   website, they came to a card. One quiet line at the bottom says where it was made,
   which is the whole of the marketing this page should do. */
html[data-bare] .shell__nav,
html[data-bare] .place,
html[data-bare] .burger,
html[data-bare] .shell__r,
html[data-bare] .subnav,
html[data-bare] .foot,
html[data-bare] .orn,
html[data-bare] [data-spon] { display: none !important; }
html[data-bare] .shell { background: transparent; border-bottom: 0; backdrop-filter: none;
  position: static; }
html[data-bare] .shell__in { justify-content: center; }
html[data-bare] .shell__brand { padding-left: 0; opacity: .55; }
html[data-bare] .shell__brand img { position: static; translate: none; width: 30px; height: 30px; }
html[data-bare] .shell__wm { font-size: 12.5px; }

/* Asking several businesses for a price at once. The cap lives in `ask_vendors`
   (the `ask_limit` setting) — this only offers the choosing. */
.fres__pick { display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 700; color: var(--ink-2); white-space: nowrap; }
.fres__pick input[type="checkbox"] { width: 15px; height: 15px; min-height: 0;
  padding: 0; margin: 0; flex: 0 0 auto; accent-color: var(--gold-ink); }
.fres__bulk { position: sticky; bottom: 0; background: var(--paper);
  border-top: 1px solid var(--line); padding: 10px 0 2px; margin-top: 4px; }
.prow__quotes { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px;
  padding-top: 12px; border-top: 1px solid var(--sink); }
.qrow { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; width: 100%;
  padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--r-sm); }

/* ══ A CONVERSATION (135) ══════════════════════════════════════════════════
   An enquiry had no reply field at all: a business could read the message a customer
   sent and had nowhere to answer it. This is both ends of that.

   ⚠ NOT A CHAT APP. There is no typing indicator, no read receipt and no timestamp on
   every line, because nothing here is instant — a florist answers between weddings. The
   two things that matter are WHO SAID IT and WHEN, and the second one only matters at
   the granularity of a day.

   Sides, not colours-by-person: `--me` is whoever is reading, which is the vendor on one
   screen and the customer on the other. One rule, two meanings, no second stylesheet. */
.thr { display: flex; flex-direction: column; gap: 10px; margin: 4px 0 16px }
.thr__m {
  max-width: min(88%, 60ch); padding: 11px 14px; border-radius: var(--r-md);
  background: var(--paper-warm); border: 1px solid var(--line);
  align-self: flex-start;
}
.thr__m--me {
  align-self: flex-end; background: var(--sink); border-color: transparent;
}
.thr__b { margin: 0; font-size: 15px; line-height: 1.55; white-space: pre-wrap;
  overflow-wrap: anywhere }
.thr__w { display: block; margin-top: 6px; font-size: 12px; color: var(--ink-3);
  font-weight: 700; letter-spacing: .02em }
/* The composer sits under the thread and never floats: a fixed bar over a short
   conversation is a chat app pretending there is more to read. */
.thr__f { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap }
.thr__f textarea { flex: 1 1 260px; min-height: 76px; resize: vertical }
/* Nobody to send it to. A visitor's enquiry has no account behind it, so a reply typed
   here would reach nothing — the box is replaced by the phone number, not disabled with
   no explanation. */
.thr__none { padding: 13px 15px; border: 1px dashed var(--line-strong);
  border-radius: var(--r-md); font-size: 14px; color: var(--ink-2) }

/* ══ WORKING DENSITY ══════════════════════════════════════════════════════════
   MEASURED BEFORE TOUCHING ANYTHING, on a 716px-tall window, on /plan with an event
   open: **the first card began at y=433**. Sixty per cent of the screen was spent before
   any of the work appeared — header 69, a 30px page pad, a 201px title block, a
   breadcrumb, then a 47px tab strip. That is the whole of "things should be smaller so
   more fits, and headers and buttons should sit nearer the top".

   THE LEVER IS THE TOKENS, NOT A HUNDRED FONT-SIZES. The system is built on --t-* and
   --r-*, and custom properties inherit — so stepping them down on <html data-app> moves
   every component that was built correctly, once, coherently. The hand-written px sizes
   below are only the handful that do not read from a token.

   This is a workspace at working size. It is NOT a different design: same faces, same
   colours, same components. A planning tool is looked at for hours, so the goal is
   quiet and compact rather than loud and roomy. */
html[data-app] {
  --t-xs:   .6875rem;   /* 11 */
  --t-sm:   .75rem;     /* 12 */
  --t-base: .875rem;    /* 14 */
  --t-md:   .9375rem;   /* 15 */
  --t-lg:   1.125rem;   /* 18 */
  --t-xl:   1.375rem;   /* 22 */
  --r-md: 13px; --r-lg: 18px;
}

/* The page starts where the work starts. 30px of pad above a title is air a stranger
   needs and a person who lives here does not. */
html[data-app] body > main.wrap { padding-top: clamp(10px, 1.1vw, 15px); }

/* Titles. A page title is a label for somebody who already knows where they are — the
   sidebar and the top bar have both already said it. Named with the classes as well as
   the tag, because `main h1` alone loses to `main .h3` written later — that is how the
   dashboard title once came out smaller than its own body text. */
html[data-app] main h1,
html[data-app] main h1.h1,
html[data-app] main h1.h2,
html[data-app] main h1.h3 { font-size: clamp(18px, 1.5vw, 21px); line-height: 1.25; }
html[data-app] main .h2 { font-size: clamp(16px, 1.3vw, 18px); }
html[data-app] main .h3 { font-size: 15px; }

/* ⚠ A MARKETING HERO ON AN APP SCREEN. /day still opens with `.hero2` — 66px of padding
   and a 28px display headline with a line break in it — which put its first card at
   y=335 on the same window. Under `data-app` it becomes a header, like every other. */
html[data-app] main > .hero2,
html[data-app] main > .sect { padding-top: 0; padding-bottom: 0; }
html[data-app] main > .hero2 + .sect { padding-top: 14px; }
html[data-app] main > .hero2 h1 br { display: none; }

/* ⚠⚠ A MARKETING HERO CANNOT BE MADE INTO THE TEMPLATE, AND I TRIED IT FIRST.
   Restyling `html[data-app] main > .hero2` to `.plan__head`'s box looked right and was
   not: MEASURED at 1512px it gave /day 58.5px, /tools 114.5px and /links 165.5px, three
   different header heights, because a hero's h1 is a SENTENCE — "Everything you need to
   run the day — and none of this is the paid one" — and it wraps to a different number
   of lines on every page. Its `.eyebrow` is hidden on app screens too (ds.css:3584), so
   the title sat at y=84 where every other screen puts the eyebrow.

   A template needs a SHORT title and three fixed parts. So the hero comes off entirely
   inside the program and `HEADS` in ds.js gives these pages a real `.plan__head` — the
   same one /plan //trades //messages carry. Signed out they are still marketing pages
   and the hero is untouched. */
html[data-app] main > .hero2 { display: none; }

/* The event header. 201px for a title, a date, one sentence and a progress bar. */
html[data-app] .plan__head { gap: 10px 16px; margin-bottom: 12px; }
html[data-app] .plan__urg  { margin-top: 5px; line-height: 1.4; }
html[data-app] .plan__prog { max-width: 300px; }
html[data-app] .bar2 { height: 6px; }

/* ⚠ THE BREADCRUMB IS SAID THREE TIMES. "My events › Grig Bachelor Party" repeats the
   h1 directly above it, the sidebar row that is lit, and the "← All my events" button
   beside it — 30px to say what three other things already say. The existing rule hiding
   `.navback` under data-app carries exactly this reasoning: the sidebar is the map. */
html[data-app] .crumb { display: none; }

/* The tab strip: 47px tall for 13.5px labels. */
html[data-app] .etab { padding-top: 7px; padding-bottom: 7px; font-size: 13px; }
html[data-app] .etabs { margin-bottom: 14px; }

/* The rail. 41px rows over ten tools is 410px of sidebar for a 716px window. */
html[data-app] .appside__l { padding-top: 7px; padding-bottom: 7px; font-size: 13.5px; }
html[data-app] .appside__k { margin-top: 16px; margin-bottom: 4px; }

/* Cards, rows and tiles carry the same air a landing page uses between sections. */
html[data-app] .card { padding: clamp(13px, 1.4vw, 18px); }
html[data-app] .card--pad-lg { padding: clamp(16px, 1.8vw, 24px); }
html[data-app] .prow { padding: clamp(9px, 1vw, 12px); margin-bottom: 7px; }
html[data-app] .prow__n { font-size: 14.5px; }
html[data-app] .stat { padding: 10px; }
html[data-app] .stat b { font-size: 21px; }
html[data-app] .stat span { font-size: 11.5px; }
html[data-app] .stats { gap: 9px; }
html[data-app] .field { margin-bottom: 10px; }
html[data-app] .field__c { padding-top: 8px; padding-bottom: 8px; }
html[data-app] .btn { padding-top: 8px; padding-bottom: 8px; }

/* Section rhythm. Every `margin-top: clamp(20px, 2.6vw, 30px)` written inline on an app
   screen adds up to a screenful across four sections. */
html[data-app] main section + section { margin-top: 16px; }

/* ══ ARTWORK ON AN INVITATION (136) ═══════════════════════════════════════════
   Nine themes is nine weddings that look alike, so somebody can bring their own picture.
   The rules the card still has to keep, because a guest has to be able to READ it:

     · the words never move off the card, only up and down it — three positions, not free
       dragging. A guest opens this on a phone in portrait and a laptop in landscape, and
       anything pinned to an (x,y) lands somewhere else on one of them;
     · there is always a scrim between the picture and the words. A photograph is not a
       background colour: the same white text is perfect over a dark corner and invisible
       fifty pixels along;
     · the address rows keep their own panel. It is the one part a guest has to be able to
       copy, and it must survive any picture. */
.inv { position: relative; overflow: hidden; }
.inv__in { position: relative; z-index: 1; }
.inv__art {
  position: absolute; inset: 0; background-size: cover; background-position: center;
  z-index: 0;
}
/* THE SCRIM. Sits over the picture and under the words. Two directions, because the ink
   choice is really a choice about which end of the card the picture is dark at. */
.inv[data-art] .inv__art::after {
  content: ''; position: absolute; inset: 0;
}
.inv[data-art][data-ink="light"] .inv__art::after {
  background: linear-gradient(180deg, rgba(11,12,16,.30), rgba(11,12,16,.62));
}
.inv[data-art][data-ink="dark"] .inv__art::after {
  background: linear-gradient(180deg, rgba(255,255,255,.62), rgba(255,255,255,.82));
}
.inv[data-art] { padding: 0; border-color: transparent; }
.inv[data-art] .inv__in {
  padding: clamp(26px, 4vw, 44px); min-height: clamp(320px, 46vh, 460px);
  display: flex; flex-direction: column;
}
.inv[data-art][data-at="top"]    .inv__in { justify-content: flex-start; }
.inv[data-art][data-at="middle"] .inv__in { justify-content: center; }
.inv[data-art][data-at="bottom"] .inv__in { justify-content: flex-end; }

.inv[data-art][data-ink="light"] .inv__h,
.inv[data-art][data-ink="light"] .inv__k { color: #fff; }
.inv[data-art][data-ink="light"] .inv__m { color: rgba(255,255,255,.9); }
.inv[data-art][data-ink="light"] .inv__rows { border-top-color: rgba(255,255,255,.28); }
.inv[data-art][data-ink="light"] .inv__r,
.inv[data-art][data-ink="light"] .inv__r b { color: #fff; }
/* The one part that must stay copyable whatever the picture does. */
.inv[data-art] .inv__rows {
  margin-top: 18px; padding: 14px 16px 4px; border-radius: var(--r-sm);
  backdrop-filter: blur(3px);
}
.inv[data-art][data-ink="light"] .inv__rows { background: rgba(11,12,16,.34); }
.inv[data-art][data-ink="dark"]  .inv__rows { background: rgba(255,255,255,.55); }

/* ── the designer, beside the card ──────────────────────────────────────────
   Two columns on a desk, stacked on a phone: the card is the point, so on a narrow
   screen it goes FIRST and the controls follow it. */
.invd { display: grid; gap: 16px; align-items: start; }
@media (min-width: 1020px) { .invd { grid-template-columns: minmax(0,1fr) 380px; } }
.invd__prev { position: sticky; top: calc(var(--shell-h, 72px) + 12px); }
.invd__prev .inv { margin: 0; max-width: none; }
/* Themes are chosen by LOOKING at them. A drop-down of nine names is a list of words
   for a decision that is entirely visual. */
.invsw { display: flex; flex-wrap: wrap; gap: 7px; }
.invsw__b {
  border: 1px solid var(--line-strong); background: var(--paper); cursor: pointer;
  border-radius: var(--r-sm); padding: 7px 11px; font: inherit; font-size: 12.5px;
  font-weight: 700; color: var(--ink-2);
}
.invsw__b.is-on { border-color: var(--gold-ink); color: var(--gold-ink);
  box-shadow: inset 0 0 0 1px var(--gold-ink); }

/* The list inside a chooser. Rows, not radio buttons: choosing IS the action here, so a
   second "OK" press would be a step that exists only to be pressed. */
.pickl { display: grid; gap: 8px; }
.pickl__b {
  display: block; width: 100%; text-align: left; cursor: pointer; font: inherit;
  padding: 11px 13px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--paper); color: var(--ink);
}
.pickl__b:hover, .pickl__b:focus-visible {
  border-color: var(--gold-ink); background: var(--paper-warm);
}
.pickl__b b { display: block; font-size: 15px; }
.pickl__b span { display: block; margin-top: 2px; font-size: 12.5px; color: var(--ink-3); }

/* ══ THE FRONTISPIECE DOES NOT BELONG IN THE PROGRAM ══════════════════════════
   The 300px medallion is a FRONTISPIECE: it introduces a page to somebody who has just
   arrived and is deciding whether to stay. Inside the platform nobody is deciding that —
   "this is a program now, down to the business and not looking to sell things" — and it
   was costing ~150px at the top of every workspace, on a platform whose stated goal is
   that a screen fits in a screen.

   It is not deleted, because it is right everywhere else on the site, and because the
   element carries layout rules that other rules key off (`.ornhead ~ main`). Under
   `data-app` it becomes what the owner asked for instead: a LINEAR ornament that takes
   almost no room — a hairline that fades in from both ends with a single small diamond
   at its centre. Enough to say the brand is still here, and nothing more.

   ⚠ AND IT IS CENTRED ON THE WORK AREA, NOT THE WINDOW. `margin: … auto` centres in the
   BODY, which is the full window — under a 232px rail that put it 116px left of the
   middle of everything below it. */
html[data-app] .ornhead {
  width: auto; aspect-ratio: auto; height: 9px;
  margin: 6px 0 12px;
  background: none; mix-blend-mode: normal; opacity: 1;
  position: relative;
}
@media (min-width: 900px) {
  html[data-app] .ornhead { margin-left: calc(232px + clamp(18px, 2.6vw, 36px)); }
}
html[data-app] .ornhead { margin-right: clamp(18px, 2.6vw, 36px); }
/* The rule: strongest in the middle, gone at both ends, so it reads as a flourish rather
   than as a border somebody forgot to remove. */
html[data-app] .ornhead::before {
  content: ''; position: absolute; left: 0; right: 0; top: 4px; height: 1px;
  background: linear-gradient(90deg,
    transparent, var(--line-gold) 22%, var(--line-gold) 78%, transparent);
}
/* One small diamond, on the page colour so the rule appears to pass behind it. */
html[data-app] .ornhead::after {
  content: ''; position: absolute; left: 50%; top: 0;
  width: 9px; height: 9px; margin-left: -4.5px;
  background: var(--face);
  box-shadow: 0 0 0 1px var(--line-gold) inset;
  transform: rotate(45deg);
}

/* ══ THE EVENTS BOARD: A BREAKDOWN, NOT A BAR ═════════════════════════════════
   The card carried one progress bar and a sentence. A bar at 40% says nothing about
   WHICH forty per cent — an event with five things overdue and one with none looked
   identical, which is the opposite of what a dashboard is for. Asked for twice:
   "a nice breakdown of what's in there… not just a long stupid progress bar."

   Three columns where there is room, two where there is not, so four events fit on one
   screen — the standing rule for every screen here. */
.evgrid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 330px), 1fr));
}
.evc {
  display: block; width: 100%; text-align: left; cursor: pointer; font: inherit;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 13px 14px 12px; color: var(--ink);
  transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}
.evc:hover, .evc:focus-within {
  border-color: var(--line-gold); box-shadow: var(--lift-1, 0 6px 20px rgba(23,19,32,.07));
  transform: translateY(-1px);
}
.evc--done { background: var(--sink); border-color: transparent; }
.evc--done:hover { transform: none; }
.evc__top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.evc__n { font-weight: 800; font-size: 15.5px; min-width: 0; overflow-wrap: anywhere; }
.evc__m { color: var(--ink-3); font-size: 12.5px; }
.evc__sp { flex: 1 1 auto; }
.evc__when { font-size: 12.5px; font-weight: 800; color: var(--ink-2); white-space: nowrap; }
.evc__when--soon { color: var(--gold-ink); }
.evc__when--late { color: var(--danger); }

/* ── the breakdown ──
   Four segments in one 6px rail, in the order somebody reads them: what is done, what is
   waiting on somebody else, what is late, what has not been started. Same four colours as
   the checklist tags, because two vocabularies for one set of states is how an interface
   starts feeling assembled rather than made. */
.evseg { display: flex; height: 6px; border-radius: 999px; overflow: hidden;
  background: var(--sink); margin: 11px 0 8px; }
.evseg i { display: block; height: 100%; transition: width .3s ease; }
.evseg .s-ready   { background: #1c6b41; }
.evseg .s-pending { background: var(--gold-lg); }
.evseg .s-overdue { background: var(--danger); }
.evseg .s-todo    { background: transparent; }
.evkey { display: flex; flex-wrap: wrap; gap: 4px 11px; font-size: 12px; color: var(--ink-2); }
.evkey b { font-weight: 800; }
.evkey span { display: inline-flex; align-items: center; gap: 5px; }
.evkey i { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
.evkey .k-ready   { background: #1c6b41; }
.evkey .k-pending { background: var(--gold-lg); }
.evkey .k-overdue { background: var(--danger); }
.evkey .k-todo    { background: var(--line-strong); }

/* ── who is actually booked ──
   The businesses on the event, as their own marks. A logo is recognised faster than its
   name is read, which is the whole reason this is worth the row. Initials when there is
   no logo, never an empty circle. */
.evwho { display: flex; align-items: center; gap: 5px; margin-top: 10px; flex-wrap: wrap; }
.evdot {
  width: 26px; height: 26px; border-radius: 50%; flex: 0 0 auto;
  border: 1px solid var(--line); background: var(--paper-warm) center/cover no-repeat;
  display: grid; place-items: center; font-size: 10px; font-weight: 900;
  color: var(--ink-3); text-decoration: none; overflow: hidden;
}
.evdot:hover, .evdot:focus-visible { border-color: var(--gold-ink); color: var(--gold-ink); }
.evwho__more { font-size: 12px; color: var(--ink-3); }

/* ── the detail, on hover and on focus ──
   ⚠ `focus-within` as well as `hover`, or this is invisible to a keyboard and to a phone,
   which between them are most people. The card grows rather than the panel floating over
   the next card: a layer that covers what is under it makes a grid of cards feel like a
   pile of them. */
.evc__more {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows .18s ease;
}
.evc:hover .evc__more, .evc:focus-within .evc__more { grid-template-rows: 1fr; }
.evc__more > div { overflow: hidden; min-height: 0; }
.evc__more ul { margin: 9px 0 0; padding: 9px 0 0; list-style: none;
  border-top: 1px solid var(--line); display: grid; gap: 4px; }
.evc__more li { display: flex; gap: 8px; align-items: baseline; font-size: 12.5px; }
.evc__more li b { font-weight: 700; color: var(--ink-2); }
.evc__more li span { color: var(--ink-3); margin-left: auto; }
@media (prefers-reduced-motion: reduce) {
  .evc, .evc__more, .evseg i { transition: none; }
}

/* The filter row above the board. Same chips as everywhere else. */
.evfil { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 12px; }

/* ══ A LINEAR WIZARD ══════════════════════════════════════════════════════════
   "All building/creating things must be canonical, linear workflows. No back and forth,
   start from A and finish at Z. Save and next."

   The rail is not decoration: it answers the two questions somebody has in the middle of
   any multi-step thing — how many are left, and can I go back. Steps already done are
   pressable; steps ahead are not, because arriving at "how it looks" before there are any
   words to look at is the back-and-forth this shape exists to remove. */
/* 18px, matching what every tab's top section leaves under itself. It was 12px, so
   Invitations sat six pixels tighter than the other nine — the kind of difference you
   only see by clicking between two tabs, which is exactly how it went unnoticed. */
.wiz { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.wiz__rail { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0;
  list-style: none; flex: 1 1 auto; }
.wiz__s {
  display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px;
  border-radius: 999px; font-size: 12.5px; font-weight: 700; color: var(--ink-3);
  background: var(--sink); border: 1px solid transparent; cursor: default;
}
.wiz__s b { display: grid; place-items: center; width: 17px; height: 17px;
  border-radius: 50%; background: var(--line-strong); color: var(--paper);
  font-size: 10.5px; font-weight: 900; }
.wiz__s--done { cursor: pointer; color: var(--ink-2); }
.wiz__s--done b { background: #1c6b41; }
.wiz__s--on { background: var(--paper); border-color: var(--gold-ink);
  color: var(--gold-ink); }
.wiz__s--on b { background: var(--gold-lg); color: var(--night); }
.wiz__acts { display: flex; align-items: center; gap: 8px; margin-top: 16px;
  padding-top: 13px; border-top: 1px solid var(--line); }

/* The "?" — one mark, always in the same place, never a paragraph of prose. */
.wiz__help {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--line-strong); background: var(--paper);
  font: inherit; font-size: 13px; font-weight: 900; color: var(--ink-3);
}
.wiz__help:hover, .wiz__help:focus-visible {
  border-color: var(--gold-ink); color: var(--gold-ink); }

/* ── the samples the "?" opens ──
   ⚠ REAL CARDS, NOT A DESCRIPTION. "Actual fake/test samples of what people will see,
   will get, will receive, will need to answer, how they will need to answer." Rendered
   with DS.invite.render — the same renderer the guest page uses — so a sample cannot show
   something the real thing would not. */
.samp { display: grid; gap: 14px; }
@media (min-width: 700px) { .samp { grid-template-columns: repeat(3, 1fr); } }
.samp__o { min-width: 0; }
.samp__h { font-size: 12px; font-weight: 900; letter-spacing: .08em;
  text-transform: uppercase; color: var(--gold-ink); margin: 0 0 6px; }
.samp__n { margin: 7px 0 0; font-size: 12.5px; color: var(--ink-3); line-height: 1.5; }
.samp .inv { margin: 0; max-width: none; padding: 14px; transform: none; }
.samp .inv__h { font-size: 17px; }
.samp .inv__m { font-size: 12px; margin-bottom: 10px; }
.samp .inv__rows { padding-top: 9px; gap: 5px; max-width: none; }
.samp .inv__r { font-size: 11px; }
.samp .inv__r b { flex-basis: 52px; font-size: 9.5px; }
.samp .inv__k { font-size: 9.5px; }
/* A box showing three cards side by side needs more than 520px, and only that box. */
.modal__c--wide { width: min(920px, 100%); }

/* The back arrow, in the program bar. Quiet until wanted — it is a safety net, not a
   destination — and never on the website, where the browser's own arrow is the whole
   convention. */
.appback {
  display: none; width: 28px; height: 28px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--line); background: var(--paper); color: var(--ink-2);
  font-size: 15px; line-height: 1; padding: 0; flex: 0 0 auto;
  align-items: center; justify-content: center;
}
html[data-app] .appback { display: inline-flex; }
.appback:hover, .appback:focus-visible {
  border-color: var(--gold-ink); color: var(--gold-ink); background: var(--paper-warm);
}
@media (min-width: 900px) {
  html[data-app] .appback { grid-column: 2; grid-row: 1 / 3; justify-self: start;
    margin-right: 8px; }
  /* Back, then what this screen is, then where you are looking — reading order. */
  html[data-app] .appctx { margin-left: 40px; }
}

/* ══ THE SEARCH IS THE PAGE ═══════════════════════════════════════════════════
   It was a 200px box in a rail beside a checklist. "Searching for vendors must be a fun
   experience — that field should be a little bigger and have more features."

   One big field with the action attached to it, and everything narrowing it on the line
   below. Nothing here searches as you type: this hits the database and a request per
   keystroke is how a search box becomes slow for everybody at once. */
.fx { margin-bottom: 4px; }
.fx__q { display: flex; align-items: center; gap: 10px; position: relative; }
.fx__i { position: absolute; left: 16px; color: var(--ink-3); pointer-events: none; }
.fx__q input {
  flex: 1 1 auto; min-width: 0; padding: 14px 16px 14px 44px;
  font-size: 16px; font-weight: 600; border-radius: var(--r-md);
  border: 1px solid var(--line-strong); background: var(--paper); color: var(--ink);
}
.fx__q input:focus { outline: none; border-color: var(--gold-ink);
  box-shadow: 0 0 0 3px rgba(169,120,42,.13); }
.fx__q .btn { padding-top: 13px; padding-bottom: 13px; }
.fx__row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 9px; }
.fx__row input[type="text"], .fx__row input:not([type]) {
  flex: 0 1 260px; min-width: 0; padding: 9px 12px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--paper);
}
.fx__chk { display: inline-flex; align-items: center; gap: 7px; font-size: 13px;
  color: var(--ink-2); font-weight: 600; }

/* A result. The whole card is the checkbox target — hunting a 14px box eleven times is
   the thing that makes choosing several feel like work. */
.fxr {
  display: flex; gap: 12px; align-items: flex-start; width: 100%; text-align: left;
  padding: 11px 13px; margin-bottom: 8px; cursor: pointer; font: inherit;
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--paper);
}
.fxr:hover { border-color: var(--line-gold); }
.fxr.is-on { border-color: var(--gold-ink); box-shadow: inset 0 0 0 1px var(--gold-ink); }
.fxr__l { width: 38px; height: 38px; border-radius: 9px; flex: 0 0 auto;
  background: var(--paper-warm) center/cover no-repeat; border: 1px solid var(--line);
  display: grid; place-items: center; font-size: 12px; font-weight: 900; color: var(--ink-3); }
.fxr__b { min-width: 0; flex: 1 1 auto; }
.fxr__n { font-weight: 800; font-size: 14.5px; }
.fxr__m { color: var(--ink-3); font-size: 12.5px; margin-top: 2px;
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.fxr__c { flex: 0 0 auto; }

/* The bar that appears once something is chosen. Sticky at the bottom of the work area,
   because the choosing happens down the list and the action must not be at the top. */
.fxbar {
  position: sticky; bottom: 12px; z-index: 5; margin-top: 12px;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; border-radius: var(--r-md);
  background: var(--paper); border: 1px solid var(--gold-ink);
  box-shadow: 0 10px 30px rgba(23,19,32,.14);
}

/* ══ THE PROGRAM BAR, TIDIED ══════════════════════════════════════════════════
   "The top bar looks bad — sign out / account# / name / back are all bad, and city/state
   is not aligned."

   Everything in it had been added one at a time, each defended on its own and none of
   them measured against the others: a filled black pill for Sign out (added because the
   old one "looked like an afterthought", and overcorrected into the loudest thing on the
   screen), a grey monospace slab for the account number, a bare bold name, and a city in
   uppercase letter-spaced type sitting on a different baseline from all of it.

   One idea instead: the right-hand side is a QUIET IDENTITY STRIP. Nothing there is an
   action you take often — you sign out once a day at most — so nothing there should
   outweigh the work. The name and number stack into one block at one baseline, the city
   sits on the same line as everything else, and Sign out is an outline, not a slab. */
html[data-app] .appwho { gap: 8px; align-items: center; }
html[data-app] .appwho::before { margin: 9px 6px 9px 2px; background: var(--line); }

/* Name over number, one block, one alignment. Two facts about the same person do not
   need to be two separate objects on the bar. */
html[data-app] .appwho__id {
  display: flex; flex-direction: column; align-items: flex-end; line-height: 1.15;
}
html[data-app] .appwho__n { font-size: 13px; font-weight: 800; }
html[data-app] .appwho__a {
  background: none; padding: 0; font-size: 11px; color: var(--ink-3);
  letter-spacing: .02em;
}
html[data-app] .appwho__out {
  background: none; color: var(--ink-2); border: 1px solid var(--line-strong);
  padding: 7px 13px; font-size: 12px; font-weight: 700; margin-left: 2px;
}
html[data-app] .appwho__out:hover {
  background: var(--paper-warm); border-color: var(--gold-ink); color: var(--gold-ink);
}

/* ⚠ THE CITY WAS ON ITS OWN BASELINE. `.place` is a website control — a pin, then the
   city in small uppercase with .1em tracking — sitting in a row of 13px sentence-case
   text. Nothing lined up because nothing was meant to. In the program it is the same
   size and case as its neighbours, and it reads as what it is: a filter you can change. */
html[data-app] .place {
  align-items: center; gap: 6px; padding: 6px 11px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--paper);
}
html[data-app] .place:hover { border-color: var(--gold-ink); }
html[data-app] .place__c {
  font-size: 12.5px; font-weight: 700; letter-spacing: 0; text-transform: none;
  color: var(--ink-2);
}
html[data-app] .place:hover .place__c { color: var(--gold-ink); }
html[data-app] .place__pin { width: 12px; height: 12px; color: var(--ink-3); }

/* Back sits with the context it acts on, not wedged against the brand. */
@media (min-width: 900px) {
  html[data-app] .appback { margin-right: 10px; }
  html[data-app] .appctx { margin-left: 42px; }
}

/* ══ ONE PAGE HEADER, ON EVERY SCREEN ═════════════════════════════════════════
   "Every page should have a nice header… and all must be aligned properly — that's
   something you must screenshot and make sure we are consistent." Fair: they had drifted
   into three shapes. /plan and /trades used `.plan__head`, /dashboard used `.apphead`
   with an eyebrow, and /browse opened with a full MARKETING HERO — script type, "meet
   someone / worth the night", and two paragraphs selling the platform to somebody who has
   already signed up for it. Reported as "your shortlist looks like an afterthought from
   the website, half built".

   One shape now, on all of them: the page name in the display face, one quiet line under
   it saying what the screen is for, actions on the right, a hairline under the lot. The
   display face is what makes it feel like this product and not a spreadsheet — the same
   Great Vibes accent the brand uses would be unreadable at this size, so the weight and
   spacing carry the character instead. */
/* ⚠ AND `.dashhead` JOINS THEM. It was the last header still keeping its own box —
   padding-bottom 14px against everyone else's 11px, and no margin-bottom at all against
   their 14px — which is why /dashboard and /businesses measured 76px and 70px where the
   other four measured 89-91. Four rules for one component is how the drift got in; this
   is the same fix as the eyebrow and the sub-line above, one step further. */
html[data-app] .apphd,
html[data-app] .plan__head,
html[data-app] .dashhead,
html[data-app] .apphead {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 10px 18px;
  margin: 0 0 14px; padding: 0 0 11px;
  border-bottom: 1px solid var(--line);
}
html[data-app] .apphd__t { margin: 0; }
/* THE LINE UNDER THE TITLE, WHEREVER IT LIVES. /dashboard ("Looking around LA") and
   /businesses ("3 listings — 2 live") had the same line as the other four and hung it to
   the RIGHT of the title instead of under it — so the drift the owner reported was never
   missing copy, it was placement. Both are now the second child of the title group and
   take this rule with the rest. */
html[data-app] .apphd__s,
html[data-app] .plan__head > div > .quiet:first-of-type,
html[data-app] .dashhead > div > .quiet:first-of-type {
  margin: 3px 0 0; font-size: 13px; color: var(--ink-3);
}
/* The eyebrow above a title introduces a section to a stranger. Inside the program the
   sidebar has already said where you are, so it is the same words twice. */
html[data-app] .apphead .eyebrow,
html[data-app] .apphd .eyebrow { display: none; }

/* ⚠ THE MARKETING BLOCKS COME OFF THE APP SCREENS. `/browse` is a tool once you are
   signed in — it is where your shortlist lives — and it was still opening with the pitch.
   Hidden rather than deleted: signed out, /browse is the public directory and every word
   of that hero belongs there. */
html[data-app] #dirhero,
html[data-app] .band > .wrap > .center.stack:first-child,
html[data-app] main > .hero2 .lede + .lede { display: none; }

/* The search field. ⚠ `html[data-app]` on the front, because the density block's own
   input rules are (0,2,1) and a bare `.fx__q input` at (0,1,1) simply lost — measured:
   padding-left came out 15px against the 44px written here, so the icon sat on top of the
   placeholder. Specificity, not a bigger number. */
html[data-app] .fx__q input,
.fx__q input { padding: 13px 16px 13px 46px; }
html[data-app] .fx__row input { padding: 9px 12px; }

/* ══ THE IDENTITY STRIP, SEPARATE AND DELICATE ════════════════════════════════
   Stacking the name over the number made one object of two facts, and was rejected:
   "account number and name stay separate — nice structure, stylish, delicate."

   So: one baseline, two weights, a 3px dot between them. The number is not in a box and
   not in a monospace slab — it is quieter than the name, which is the whole hierarchy it
   needs. Nothing here is a control except Sign out. */
html[data-app] .appwho { gap: 9px; }
html[data-app] .appwho__id { display: contents; }   /* if an older cache emitted one */
html[data-app] .appwho__n {
  font-size: 13px; font-weight: 800; color: var(--ink); letter-spacing: -.005em;
}
html[data-app] .appwho__d {
  width: 3px; height: 3px; border-radius: 50%; background: var(--line-strong);
  flex: 0 0 auto;
}
html[data-app] .appwho__a {
  background: none; padding: 0; border-radius: 0;
  font-family: inherit; font-size: 12px; font-weight: 600; color: var(--ink-3);
  letter-spacing: .01em;
}
html[data-app] .appwho__a::before { content: '#'; opacity: .55; }

/* ⚠ THE BACK CONTROL, MATCHING WHAT IS BESIDE IT. It was a bare circle with an arrow in
   it — a different shape, a different height and a different border from the city pill
   two inches away, which is what made it read as bolted on. Same pill, same height, same
   hairline: the bar has ONE control shape now. */
html[data-app] .appback {
  width: auto; height: auto; border-radius: 999px;
  padding: 6px 12px; font-size: 13px; line-height: 1;
  border: 1px solid var(--line); background: var(--paper); color: var(--ink-3);
}
html[data-app] .appback:hover, html[data-app] .appback:focus-visible {
  border-color: var(--gold-ink); color: var(--gold-ink); background: var(--paper);
}

/* ⚠ NOT EVERY PAGE'S <main> CARRIES `.wrap`. The offset that moves the work area clear of
   the 232px rail was written as `body > main.wrap`, which is most pages — and /browse,
   /idea and /nightout put `.wrap` on the sections INSIDE main instead. On those the
   offset never applied and the content ran straight under the sidebar: measured, the
   trade table began at x=232 with its first column clipped and the page header cut in
   half. Reported as "everything is all over the place".

   Matched on `main` itself now, whatever classes it has, with the inner wraps released so
   the padding is applied exactly once. */
html[data-app] body > main {
  max-width: none;
  padding: clamp(10px, 1.1vw, 15px) clamp(18px, 2.6vw, 36px);
}
@media (min-width: 900px) {
  html[data-app] body > main {
    padding-left: calc(232px + clamp(18px, 2.6vw, 36px));
  }
}
/* The inner wraps stop centring and stop padding — `main` is doing both now, and two
   boxes each contributing a gutter is how a 36px margin becomes 72. */
html[data-app] body > main .wrap {
  max-width: none; padding-left: 0; padding-right: 0; margin-inline: 0;
}
/* Section bands on those pages carry their own vertical padding for a landing page. */
html[data-app] body > main > .band { padding-top: 0; padding-bottom: 14px; }

/* ══ THE BAR CONTROLS: ONE HEIGHT, ONE AXIS ═══════════════════════════════════
   MEASURED on the live bar rather than judged by eye, and all three faults were real:

     · THREE DIFFERENT HEIGHTS in one row — back 27px, the city pill 35px, Sign out 36px.
       I had claimed to unify these and had only unified the SHAPE;
     · THE CITY SAT 3px HIGH — centre-y 30 against 33 for everything else, which is the
       "city/state is not aligned" that survived the last two attempts. It comes from
       `.place` being a website control with its own box, not from the grid;
     · BACK WAS FLUSH AGAINST THE RAIL at x=232 — the sidebar's own right edge — with no
       gap at all, so it read as stuck to the divider.

   One rule for all three: same height, same radius, same hairline, centred on the same
   axis. The bar has one control now, in three lengths. */
@media (min-width: 900px) {
  html[data-app] .appback,
  html[data-app] .place,
  html[data-app] .appwho__out {
    height: 32px; box-sizing: border-box;
    display: inline-flex; align-items: center; justify-content: center;
    padding-top: 0; padding-bottom: 0;
    align-self: center; margin-top: 0; margin-bottom: 0;
    border-radius: 999px; line-height: 1;
  }
  /* ⚠ `align-self: center` is not enough on its own for `.place`: it is a grid ITEM
     spanning two rows, so it centres against the pair — and the two rows are not equal.
     Pinned to one row instead, which is where everything else in the bar lives. */
  html[data-app] .place { grid-row: 1 / 3; align-self: center; }
  html[data-app] .appback { margin-left: 14px; }
}

/* ══ FOUR PILLS, ONE FAMILY ═══════════════════════════════════════════════════
   "Make sure the city button doesn't have weird line artefacts — a clean working button,
   like Sign out. And make the Grig button also, so we have 4 button-type things."

   ⚠ THE ARTEFACT WAS `.place::after` — a hover underline pinned to `bottom: 2px` and
   stretched `left: 0; right: 0`. On the website it grows under the words and stops at
   their width, which is a nice thing there. Inside a bordered pill it is a stray hairline
   sitting near the bottom edge. Off, along with the 57px `margin-left` that exists to
   land the pin on the wordmark in the WEBSITE header, and the divider and dot that were
   separating things that are now separated by being four distinct objects. */
html[data-app] .place::after { display: none; }
html[data-app] .place { margin-left: 0; }
html[data-app] .appwho::before { display: none; }
html[data-app] .appwho__d { display: none; }

html[data-app] .appwho { gap: 8px; }
html[data-app] .appwho__p,
html[data-app] .place {
  height: 32px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 0 13px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--paper);
  font: inherit; font-size: 12.5px; font-weight: 700; line-height: 1;
  letter-spacing: 0; text-transform: none; white-space: nowrap;
}
/* The two that are facts rather than actions. */
html[data-app] .appwho__p--ro { cursor: default; color: var(--ink-2); }
html[data-app] .appwho__p--ro:hover { border-color: var(--line); background: var(--paper); }
html[data-app] .appwho__a { color: var(--ink-3); font-weight: 600; font-size: 12px; }
html[data-app] .appwho__a::before { content: '#'; opacity: .5; margin-right: 1px; }
html[data-app] .appwho__n { color: var(--ink); font-weight: 800; }

/* The two that DO something get the hover the other two must not have. */
html[data-app] .place,
html[data-app] .appwho__out { cursor: pointer; color: var(--ink-2); }
html[data-app] .place:hover, html[data-app] .place:focus-visible,
html[data-app] .appwho__out:hover, html[data-app] .appwho__out:focus-visible {
  border-color: var(--gold-ink); color: var(--gold-ink); background: var(--paper-warm);
  outline: none;
}
html[data-app] .place:hover .place__c, html[data-app] .place:focus-visible .place__c {
  color: var(--gold-ink);
}
html[data-app] .place__c {
  font-size: 12.5px; font-weight: 700; letter-spacing: 0; text-transform: none;
  color: inherit;
}
html[data-app] .place__pin { width: 12px; height: 12px; color: currentColor; }
html[data-app] .place:hover .place__pin { transform: none; }

/* ══ NO GREY SLABS IN THE PROGRAM ═════════════════════════════════════════════
   "All buttons in here — make sure they follow app styling and design rules, no stupid
   grey colours."

   A bare `.btn` with no modifier gets a transparent border and no background, so it
   picked up whatever the surface under it was and read as a grey slab — which is exactly
   what "All my events", "Mark it done" and "Share this plan" were. The top bar already
   settled what a control looks like here: a pill, a hairline, gold on hover. This makes
   the rest of the program speak it too.

   The named variants are left alone on purpose. `--gold` is the one action a screen
   actually wants you to take, `--danger` is the one that destroys something, and both
   must keep standing out from this. */
html[data-app] .btn:not(.btn--gold):not(.btn--danger):not(.btn--quiet):not(.btn--pri) {
  background: var(--paper);
  border-color: var(--line);
  color: var(--ink-2);
  font-weight: 700;
}
html[data-app] .btn:not(.btn--gold):not(.btn--danger):not(.btn--quiet):not(.btn--pri):hover,
html[data-app] .btn:not(.btn--gold):not(.btn--danger):not(.btn--quiet):not(.btn--pri):focus-visible {
  border-color: var(--gold-ink); color: var(--gold-ink); background: var(--paper-warm);
  transform: none;                 /* the bar's controls do not lift; nor should these */
}
/* One height for the small controls, matching the 32px the bar settled on. */
html[data-app] .btn--sm { min-height: 32px; padding: 0 14px; font-size: 12.5px; }
html[data-app] .btn { min-height: 36px; padding: 0 18px; }
html[data-app] .btn--gold { padding: 0 20px; }

/* ⚠ A `:not()` CHAIN IS A SPECIFICITY BOMB, AND THIS ONE OUTRANKED EVERYTHING.
   The base input rule is written as

     input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"])
          :not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"])

   which LOOKS like a plain element selector and scores (0,7,1) — each `:not()` takes the
   specificity of its argument, and an attribute selector is a whole class. So it beat
   `html[data-app] .fx__q input` at (0,2,2), and the search field kept `padding-left: 15px`
   against the 46px written for it. Measured twice: the magnifier sat on top of the
   placeholder, and my first attempt at "write a bigger number" changed nothing, because
   the number was never the problem.

   The id wins outright and cannot be out-ranked by another `:not()` chain later. Used
   deliberately, and noted here so the next person does not "tidy" it back to a class. */
html[data-app] #fx-q, #fx-q {
  padding: 0 18px 0 46px; height: 52px; font-size: 16px;
}
html[data-app] #fx-city, #fx-city { padding: 0 14px; height: 38px; }
.fx__i { top: 50%; transform: translateY(-50%); }
.fx__q .btn { height: 52px; padding: 0 26px; font-size: 14px; }

/* ══ THE PROGRAM'S OWN TYPOGRAPHY — A + D ═════════════════════════════════════
   Chosen from four prototypes: "I like A and D." So the editorial title from A — Fraunces,
   a modern serif with real warmth, which reads as MADE rather than generated — with D's
   caption rule under it and D's dark subtab rail.

   ⚠ THE SERIF ONLY EVER MEANS "TITLE". Montserrat carries every other word in the
   program. A second face used for two different jobs is how a design becomes a collage,
   and this one has exactly one job. */
html[data-app] { --font-display: Fraunces, Georgia, 'Times New Roman', serif; }

html[data-app] main h1,
html[data-app] main h1.h1,
html[data-app] main h1.h2,
html[data-app] main h1.h3,
html[data-app] .apphd__t,
html[data-app] #p-title {
  font-family: var(--font-display);
  font-weight: 600; font-size: clamp(22px, 2vw, 29px);
  letter-spacing: -.015em; line-height: 1.08;
}
/* The numbers on a stat tile are the thing people actually read across a room. */
html[data-app] .stat b, html[data-app] .evc__n {
  font-family: var(--font-display); font-weight: 600; letter-spacing: -.02em;
}
html[data-app] .evc__n { font-size: 16px; }

/* D's caption rule: the metadata sits under a hairline, like a gallery label. */
html[data-app] .plan__head,
html[data-app] .apphd { position: relative; border-bottom-color: var(--line); }
/* A's rule draws itself in — once, on arrival, not on every hover. Motion that says
   "this screen just opened", and nothing else. */
html[data-app] .plan__head::after,
html[data-app] .apphd::after {
  content: ''; position: absolute; left: 0; bottom: -1px; height: 1px; width: 0;
  background: linear-gradient(90deg, var(--gold-lg), transparent);
  animation: lpl-draw .7s .08s cubic-bezier(.22,.61,.36,1) forwards;
}
@keyframes lpl-draw { to { width: 100%; } }

/* ══ THE EVENT'S TABS, AS A RAIL ══════════════════════════════════════════════
   D's dark rail. It is the one dark surface in the program, and that is deliberate: it
   marks "you are inside one event" without a word, and it makes the work area beside it
   read as the paper it is. */
.ework { display: grid; gap: 0; align-items: start; }
@media (min-width: 1040px) {
  .ework { grid-template-columns: 186px minmax(0, 1fr); }
  /* ⚠ THIS RAIL WAS FILLED WITH --night. The idea was that a dark panel "marks you are
     inside one event without a word" — but it was the only dark surface in a cream app,
     sitting directly beside a paper-coloured sidebar doing the same job, and the owner
     read it as exactly that: "the black is not matching with the app".

     Two rails, one screen, one visual language. It now takes the same paper surface, the
     same radii and the same quiet gold current-item as .appside beside it. What says you
     are inside an event is the event's own title above the work — not a slab of black. */
  html[data-app] .etabs {
    position: sticky; top: calc(var(--shell-h, 70px) + 10px);
    display: flex; flex-direction: column; gap: 1px; overflow: visible;
    border: 1px solid var(--line); border-radius: var(--r-md); padding: 8px;
    background: var(--paper); margin: 0 18px 0 0;
  }
  html[data-app] .etab {
    text-align: left; border: 0; border-radius: var(--r-sm); margin: 0;
    padding: 8px 11px; font-size: 13px; color: var(--ink-2);
  }
  html[data-app] .etab:hover { color: var(--ink); background: var(--sink); }
  /* Matches .appside__l.is-on — gold ink and weight, no fill. A filled row here and a
     text-only row in the rail beside it would be two answers to one question. */
  html[data-app] .etab.is-on {
    color: var(--gold-ink); background: none; font-weight: 800;
    border-bottom-color: transparent;
  }
}
/* Below that width the rail would leave nothing for the work, so it goes back to a strip
   — compact, and it scrolls sideways rather than wrapping to two rows. */
@media (max-width: 1039px) {
  html[data-app] .etabs { margin-bottom: 12px; }
}

/* ══ MOTION, WHERE IT MEANS SOMETHING ═════════════════════════════════════════
   "Add nice animations to some things so it's not boring." The line this stays on: every
   one of these marks a CHANGE — a screen arriving, a pane swapping, a number landing.
   Nothing loops, nothing moves while you are reading, and the whole lot is off for anyone
   who has asked their system for less motion. */
html[data-app] .epane:not([hidden]) { animation: lpl-pane .28s cubic-bezier(.22,.61,.36,1); }
@keyframes lpl-pane { from { opacity: 0; transform: translateY(5px); } }

/* Cards arrive in order rather than all at once — the eye follows the sequence, which is
   the reading order anyway. Six deep, then it stops: a stagger you can still perceive at
   card twelve is a page that feels slow. */
html[data-app] .stats > .stat,
html[data-app] .evgrid > .evc { animation: lpl-rise .38s cubic-bezier(.22,.61,.36,1) backwards; }
html[data-app] .stats > .stat:nth-child(1), html[data-app] .evgrid > .evc:nth-child(1) { animation-delay: .02s }
html[data-app] .stats > .stat:nth-child(2), html[data-app] .evgrid > .evc:nth-child(2) { animation-delay: .06s }
html[data-app] .stats > .stat:nth-child(3), html[data-app] .evgrid > .evc:nth-child(3) { animation-delay: .10s }
html[data-app] .stats > .stat:nth-child(4), html[data-app] .evgrid > .evc:nth-child(4) { animation-delay: .14s }
html[data-app] .stats > .stat:nth-child(5), html[data-app] .evgrid > .evc:nth-child(5) { animation-delay: .18s }
html[data-app] .stats > .stat:nth-child(6), html[data-app] .evgrid > .evc:nth-child(6) { animation-delay: .22s }
@keyframes lpl-rise { from { opacity: 0; transform: translateY(7px); } }

/* A pill that is about to be pressed lifts a hair. The bar's own controls do not — they
   are chrome, and chrome that moves is chrome that distracts. */
html[data-app] .btn--gold:hover { transform: translateY(-1px); }

/* ⚠ AND ALL OF IT OFF ON REQUEST. Vestibular disorders are not a preference setting. */
@media (prefers-reduced-motion: reduce) {
  html[data-app] .plan__head::after, html[data-app] .apphd::after { animation: none; width: 100%; }
  html[data-app] .epane:not([hidden]),
  html[data-app] .stats > .stat, html[data-app] .evgrid > .evc { animation: none; }
  html[data-app] .btn--gold:hover { transform: none; }
}

/* ⚠ A HIDDEN ASIDE STILL OWNS ITS COLUMN. `.planwork` is a two-track grid, and hiding the
   finder with `hidden` removes the element, not the track — so every tab but the checklist
   laid its work out in 656px with 340px of reserved white beside it. Measured on Overview:
   four stat cards stacked one per row down the left with half the screen empty.

   The class is toggled by etab(), in the same place that hides the aside, so the two
   cannot get out of step. */
.planwork--solo { grid-template-columns: minmax(0, 1fr) !important; }

/* ⚠ THE FIRST THING IN A PANE STARTS AT THE TOP OF THE PANE.
   Five of the ten carried `margin-top: 16px` on their first card — written when the cards
   were one stacked column and every card after the first needed air above it. On the
   first card it is not spacing, it is an indent, and it made half the tabs open 16px
   lower than the other half. Reported exactly: "some tabs start from the top nice and
   smooth, some start like an afterthought from the middle."

   Measured before and after: overview/checklist/invite/budget/docs opened at y220,
   reminders/guests/music/run/sharing at y236. One rule rather than five edits, so a new
   pane written next month cannot reintroduce it. */
html[data-app] .epane > :first-child { margin-top: 0 !important; }

/* ══ THE EVENT'S PROGRESS, AS ONE OBJECT ══════════════════════════════════════
   A bare 6px bar, a sentence and a row of state chips were sitting loose in the corner
   with nothing holding them together — "0 of 11 sorted looks afterthought", and it did.
   They are ONE fact about the event, so they read as one thing: a ring you can take in
   without reading, the count beside it, the states under both.

   ⚠ THE RING IS NOT DECORATION. A number needs reading; a filled arc is understood at a
   glance from across a desk, which is how somebody actually checks "how far along am I".
   Conic gradient rather than SVG — no extra markup, and it animates by moving one custom
   property, which is what `@property` below makes possible at all. */
@property --pc { syntax: '<number>'; initial-value: 0; inherits: false; }

.prog { display: flex; align-items: center; gap: 11px; }
.prog__ring {
  --pc: 0;
  width: 44px; height: 44px; flex: 0 0 auto; border-radius: 50%;
  display: grid; place-items: center;
  background:
    conic-gradient(var(--gold-lg) calc(var(--pc) * 1%), var(--sink) 0);
  transition: --pc 1s cubic-bezier(.22,.61,.36,1);
}
.prog__ring::before {
  content: ''; position: absolute; width: 34px; height: 34px; border-radius: 50%;
  background: var(--face);
}
.prog__ring { position: relative; }
.prog__pc {
  position: relative; font-size: 11.5px; font-weight: 800; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.prog__b { min-width: 0; }
.prog__n { margin: 0 0 6px; font-size: 13px; font-weight: 700; color: var(--ink-2);
  white-space: nowrap; }
html[data-app] .prog .bar2 { width: 150px; height: 5px; }
html[data-app] .plan__prog { max-width: none; flex: 0 0 auto; }
html[data-app] .plan__tally { margin-top: 8px; justify-content: flex-start; }

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

/* ══ EVERY PANE OPENS THE WAY OVERVIEW DOES ═══════════════════════════════════
   "Only Overview has proper alignment and header and things that start in the right
   position, and doesn't have weird bad-looking ornaments and lines."

   Measured across all ten: Overview's first child is a GRID OF TILES sitting directly on
   the page. Every other pane's first child is one wide `.card` wrapping the whole tab —
   heading, prose, form and all — so its heading started inside a bordered box, indented
   from where Overview's content starts, with a rule around the lot. That box is the
   "weird line", and it is the only reason the tabs disagreed.

   The WRAPPER card loses its chrome; every card nested inside it keeps everything. So a
   pane now opens with its heading in the same place Overview's first tile does, and real
   cards still look like cards. */
/* The top section of a tab. Stripped of its card so it reads as the page talking to
   you rather than a box — but it is a COMPONENT, not an absence, and the spacing under
   it is set HERE for every tab at once.

   It was not, before: the margin came from inline styles written per pane, so the gap
   between the lede and the first real content was 16px on Checklist and Budget, 12px on
   Invitations and 0px on the other seven. Overview — the one the owner picked out as
   right — was 0px, because its first child is the tile grid and nobody had put a margin
   on it. Ten tabs, three different gaps, and the only way to see it was to click through
   them one after another. */
html[data-app] .epane > .card:first-child {
  background: none; border: 0; border-radius: 0; padding: 0; box-shadow: none;
  margin: 0 0 18px;
}
/* The explanatory line every tab opens with. One size, one colour, one measure —
   they were inline `font-size:14px` repeated per pane, which is how three of them
   drifted to a different size from the rest. */
html[data-app] .epane > .card:first-child > p {
  font-size: var(--t-base); color: var(--ink-2); line-height: 1.55;
  max-width: 78ch; margin: 0;
}
html[data-app] .epane > .card:first-child > p + p { margin-top: 8px; }
/* A second top-level card is a genuine second section, so it gets a hairline above it
   rather than a full box — the same separator the page header uses. */
html[data-app] .epane > .card:first-child + .card {
  background: none; border: 0; border-radius: 0; box-shadow: none;
  padding: 16px 0 0; margin-top: 18px; border-top: 1px solid var(--line);
}
/* The pane's own heading, in the same voice as the page title but a step down. */
html[data-app] .epane .vh {
  font-family: var(--font-display); font-weight: 600; font-size: 17px;
  letter-spacing: -.01em; text-transform: none; color: var(--ink);
}

/* The work column: the event's header AND its panes, so they share one left edge. The
   rail is the only thing outside it. */
.ecol { min-width: 0; }
@media (min-width: 1040px) {
  /* The rail runs the height of the work beside it rather than stopping at its last
     item, which left a black box floating in the middle of the page. */
  html[data-app] .ework { align-items: stretch; }
  html[data-app] .etabs { align-self: stretch; height: auto; }
}
/* ⚠ THE RING NEEDS A TRACK. At 0% the conic gradient was entirely `--sink` with the
   inner disc covering it, so a brand-new event showed a grey dot and the label beside it
   read as loose text. A ring is only legible if the UNFILLED part is visible too. */
.prog__ring { box-shadow: inset 0 0 0 1px var(--line); }
.prog__ring::before { box-shadow: 0 0 0 1px var(--line); }

/* ══ THE EVENT HEADER, BALANCED ═══════════════════════════════════════════════
   Three blocks in a flex row with `space-between` put the title left, the buttons in the
   middle and the progress hard left underneath the memo — competing with it for the same
   corner. It still read as an afterthought because it still WAS one: nothing said where
   it belonged.

   A two-column grid says it plainly. What the event IS runs down the left; what you can
   DO and how far along you are stack on the right, in that order, because the buttons are
   the thing you reach for and the progress is the thing you glance at. */
@media (min-width: 900px) {
  html[data-app] .plan__head {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    align-items: start; column-gap: 26px; row-gap: 10px;
  }
  html[data-app] .plan__head > :first-child { grid-column: 1; grid-row: 1 / 3; }
  html[data-app] .plan__pick { grid-column: 2; grid-row: 1; justify-self: end; }
  html[data-app] .plan__prog { grid-column: 2; grid-row: 2; justify-self: end; }
}
/* The progress reads as one line: ring, count, states. The bar under the words is gone —
   the ring already says the same thing, and two readings of one number is what made the
   corner look cluttered. */
html[data-app] .prog .bar2 { display: none; }
html[data-app] .prog__n { margin: 0; font-size: 13.5px; color: var(--ink); }
html[data-app] .plan__prog {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
html[data-app] .plan__tally { margin: 0; }
html[data-app] .prog__ring { width: 40px; height: 40px; }
html[data-app] .prog__ring::before { width: 31px; height: 31px; }

/* ══ A FIELD IS AS WIDE AS WHAT GOES IN IT ════════════════════════════════════
   "Some fields should be wider, some don't need to be screen wide — a budget amount is
   eight digits at the most, but a song name or an artist name can be long. Consider this
   logic for everything."

   Right, and it is a rule rather than a set of one-off widths: the width of a field is a
   PROMISE about what belongs in it. A screen-wide box for "8000" says the app has no idea
   what it just asked for — measured on Budget, that input was 875px for four characters.

   Sized by what the field HOLDS, using the type the markup already declares, so a field
   added next month inherits the right width without anybody remembering this block. */
html[data-app] input[type="number"] { max-width: 200px; }
html[data-app] input[type="date"]   { max-width: 190px; }
html[data-app] input[type="tel"]    { max-width: 240px; }
html[data-app] input[type="time"]   { max-width: 150px; }
/* Money is a number with a currency in front of it — the same eight digits. */
html[data-app] input[inputmode="decimal"] { max-width: 200px; }
/* A name, a title, a song, a place: these genuinely run long and must not be clipped. */
html[data-app] input[type="text"],
html[data-app] input:not([type]),
html[data-app] input[type="url"],
html[data-app] input[type="email"],
html[data-app] textarea { max-width: none; }
/* ⚠ THE ROW STOPS STRETCHING ITS CHILDREN. `.prow2` is a flex row, so a capped input just
   left a gap where the rest of the width used to be — the field looked short AND the row
   looked broken. `flex: 0 1 auto` lets each field be its own size and keeps them packed
   from the left, which is how a form reads. */
html[data-app] .prow2 { justify-content: flex-start; }
html[data-app] .prow2 > input[type="number"],
html[data-app] .prow2 > input[type="date"],
html[data-app] .prow2 > input[type="time"],
html[data-app] .prow2 > input[type="tel"] { flex: 0 1 auto; }

/* ══ the two things the app is for ═══════════════════════════════════════════
   "Plan my epic event" and "Find a talent or a service" — centred on the app bar,
   on every screen. The owner's reasoning, which is the right one: "basically thats all
   this app is doing." Everything else in the program is a consequence of one of those
   two, so neither should ever need navigating to.

   The first carries the gold because starting an event is the act the whole platform
   exists for; the second is quieter because finding somebody is what you do INSIDE one.
   Both are real links with real hrefs, so they work with the script disabled and can be
   middle-clicked — the rule the nav already follows. */
.appdo { display: none; }
html[data-app] .appdo {
  display: flex; gap: 8px; align-items: center;
  grid-column: 2; grid-row: 1 / 3; justify-self: center;
}
.appdo__b {
  display: inline-flex; align-items: center; white-space: nowrap;
  padding: 8px 15px; border-radius: var(--r-pill);
  font-size: var(--t-sm); font-weight: 700; text-decoration: none;
  color: var(--ink-2); border: 1px solid var(--line-strong); background: var(--paper);
  transition: border-color .16s var(--ease), color .16s var(--ease),
              background .16s var(--ease), transform .16s var(--ease);
}
.appdo__b:hover { color: var(--ink); border-color: var(--gold-ink); transform: translateY(-1px); }
.appdo__b:active { transform: none; }

/* ⚠ THE PRIMARY SHORTCUT IS GOLD, NOT BLACK. Owner: "have a nice/cool animation for the
   buttons: plan something, and find a vendor… black is not a good color." They were the
   only pure-black object on a warm cream bar, which is what made them read as pasted on
   rather than part of the brand. Gold is the brand's own accent and it already carries
   the primary role everywhere else on the site.

   `--gold-ink` at #a9782a is 3.63:1 on white — it passes AA only at ≥24px, so it is used
   here as a BACKGROUND with white on top, never as small text on cream. */
.appdo__b--go {
  background: linear-gradient(135deg, var(--gold-lg), var(--gold-ink));
  border-color: var(--gold-ink); color: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .10);
}
.appdo__b--go:hover {
  background: linear-gradient(135deg, var(--gold-ink), var(--gold-lg));
  border-color: var(--gold-lg); color: #fff;
  box-shadow: 0 3px 10px rgba(169, 120, 42, .30);
}

/* The animation: a soft specular sweep across the pill on hover, the same "struck coin
   under glass" idea the medallion already uses. Transform-only, so it costs no layout,
   and it is a pseudo-element so neither button needs extra markup. */
.appdo__b { position: relative; overflow: hidden; }
.appdo__b::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 38%,
              rgba(255, 255, 255, .55) 50%, transparent 62%);
  transform: translateX(-120%);
  pointer-events: none;
}
.appdo__b:hover::after { animation: appdoSheen .62s var(--ease) forwards; }
@keyframes appdoSheen { to { transform: translateX(120%); } }

/* The secondary pill lifts its border to gold and warms its face, so the pair move
   together instead of one being decorated and the other not. */
.appdo__b:not(.appdo__b--go):hover { background: var(--paper-warm); }

/* Anybody who has asked the system to calm down gets the colour and none of the motion. */
@media (prefers-reduced-motion: reduce) {
  .appdo__b:hover { transform: none; }
  .appdo__b:hover::after { animation: none; }
}
/* Below the point where the bar has room for them, they fold under the brand rather than
   pushing the account pills off the edge. */
@media (max-width: 1100px) {
  html[data-app] .appdo { grid-column: 1 / -1; grid-row: 3; justify-self: start; margin-top: 6px; }
}
@media (max-width: 620px) {
  .appdo__b { padding: 7px 12px; font-size: var(--t-xs); }
}

/* ══ the app's titles speak in the same voice as the hero ════════════════════
   The owner, on /day's "Nails at seven. / Everything else follows.": "i like the
   typography… empliment this style into the saas for headers... titles."

   Not the SIZE — a 3.1rem hero on a workspace somebody lives in for hours would be the
   opposite of the working density this app was tuned for. The VOICE: the tight tracking,
   the balanced wrap, and the script accent in gold that the marketing headlines use for
   the phrase that matters. An app title can now mark one word with <em> and get the same
   flourish at working scale. */
html[data-app] main h1 {
  letter-spacing: -.026em; line-height: 1.12; text-wrap: balance;
}
html[data-app] main h1 em {
  font-style: normal; font-family: "Great Vibes", cursive; font-weight: 400;
  color: var(--gold-lg); font-size: 1.22em; letter-spacing: 0;
  /* The script sits on a different optical baseline to the sans beside it; without this
     it rides high and reads as a different line rather than the same sentence. */
  vertical-align: -.04em;
  /* ⚠ AND IT MUST NOT GROW THE LINE BOX. This was `line-height: 1`, which sounds
     neutral and is not: at `font-size: 1.22em` it is 1.22 line-heights of the h1, so
     "Welcome back, <em>Grig</em>" made /dashboard's h1 37.3px tall against 31.3px
     everywhere else — a 6px taller header on the ONE screen carrying the accent, which
     is exactly the drift the rest of this section was fixing. Zero keeps the glyph
     drawn and keeps it out of the strut, so a decorative accent can never change a
     header's height. */
  line-height: 0;
}
