/* ============================================================
   MRKT. Mobile-first. Base styles are 390px. Every breakpoint
   below is min-width only, because the brief is mobile first
   and max-width queries invert that intent.
   ============================================================ */

:root{
  --ink:#1A1917;
  --ink-2:#2E2C29;
  /* Eyebrows only. Darker than --grey, which fails 4.5:1 on both papers. */
  --eyebrow:#6B665F;
  --paper:#FAF8F5;
  --paper-2:#F0EDE7;
  --line:#DDD8D0;
  --blue:#2B5672;
  --blue-dk:#1F415A;
  --grey:#8A857F;
  --white:#fff;

  --pad:20px;
  --maxw:1180px;
  --r:2px;
  /* Cards that should read as tappable objects rather than as flat editorial
     blocks. Deliberately separate from --r, which stays near-square for the
     sharp look everything else on the site has. Shared by the audience fork
     and the calendar cards so the two never drift apart. */
  --r-card:14px;
  /* Buttons that sit on a photograph or inside a card, where the site's
     near-square 2px reads as a hard corner against a rounded neighbour. The
     calendar's Get Notified buttons had this as a bare 10px; the hero buttons
     were asked to match them 2026-08-19, and a shared token is what makes
     "match" structural rather than two numbers that happen to agree today.
     Same reasoning as --r-card, which the audience fork and the calendar cards
     share for exactly this reason.

     8px, down from 10 on 2026-08-19 after Ty saw it. 8 rather than a number
     nobody else uses: it is the countdown plate's radius, so this is the site
     reusing a corner it already has rather than gaining a third one. Moving it
     moves the calendar buttons too, which is the token doing its job, not a
     side effect to be worked around. */
  --r-btn:8px;
  /* The large-card corner, and the one place on this site that is not
     near-square: the testimonial cards on /vendors. The home ticket band used
     it too for a few hours on 2026-08-21 and no longer does, so this currently
     has a single user. Left as a token rather than folded back into that rule,
     because the value is a deliberate exception to the site's own language and
     naming it is what stops it being read as a stray 24. */
  --r-lg:24px;

  --step--1:clamp(.78rem,.76rem + .1vw,.84rem);
  --step-0:clamp(1rem,.97rem + .15vw,1.06rem);
  --step-1:clamp(1.2rem,1.1rem + .5vw,1.4rem);
  --step-2:clamp(1.5rem,1.3rem + 1vw,2rem);
  --step-3:clamp(2rem,1.6rem + 2vw,3rem);
  --step-4:clamp(2.6rem,1.9rem + 3.4vw,4.75rem);
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:'Raleway',system-ui,-apple-system,sans-serif;
  font-size:var(--step-0);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%;display:block;height:auto}
a{color:inherit}
h1,h2,h3,h4{margin:0;line-height:1.05;letter-spacing:-.02em;font-weight:800;text-transform:uppercase}
p{margin:0 0 1em}
p:last-child{margin-bottom:0}

.wrap{width:100%;max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad)}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:-999px;top:0;z-index:200;background:var(--ink);color:#fff;padding:12px 18px}
.skip:focus{left:8px;top:8px}

/* THE EYEBROW IS A <span>, NOT A <p>, AND THAT IS THE WHOLE POINT.

   As a <p> it was hit by every rule written for the SUBTITLE beside it.
   `.sec__head p` is one class plus one element, which outranks `.eyebrow`, one
   class, so the eyebrow could never win inside its own container. That is not
   a source-order accident that reordering fixes: it lost on specificity every
   time, and it had been losing on 26 of 30 eyebrows site-wide, taking the
   subtitle's colour and size instead of its own. Measured 2026-08-21.

   A span cannot be selected by a bare `p`, here or in a check, so the whole
   class of bug is gone rather than patched a sixth time. It needs display:block
   for the same reason the <p> gave it for free.

   TWO VARIANTS, because there really are two jobs:
     .eyebrow          a section label above an <h2>. 27 of them.
     .eyebrow--label   a small label above a list inside a column. 3 of them.
   The three labels were the same class and only looked different by accident,
   which is why they were among the four the bug happened not to reach.

   THE BASE IS THE BIG TREATMENT, at Ty's call 2026-08-21 after comparing both
   on the real pages. That is what 26 of these already rendered as, so the site
   does not move; what changes is that it is now chosen rather than inherited
   from a rule about paragraphs.

   --eyebrow is darker than --grey. --grey measures 3.13 to 3.45:1 on paper and
   paper2 against a 4.5 floor for text this size, so the three labels were
   failing it. This clears it at 4.87 worst case. */
.eyebrow{
  display:block;
  font-size:var(--step-0);font-weight:700;text-transform:uppercase;
  letter-spacing:.18em;color:var(--ink-2);margin:0 0 14px
}
.eyebrow--label{font-size:var(--step--1);color:var(--eyebrow)}

/* ---------- buttons: 48px min target ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.55em;
  min-height:48px;padding:14px 26px;
  font-family:inherit;font-size:var(--step--1);font-weight:700;
  text-transform:uppercase;letter-spacing:.1em;text-decoration:none;
  border:1.5px solid var(--ink);border-radius:var(--r);
  background:var(--ink);color:var(--paper);cursor:pointer;
  transition:transform .18s ease,background .18s ease,color .18s ease;
}
.btn:hover{background:var(--blue);border-color:var(--blue);transform:translateY(-2px)}
.btn:focus-visible{outline:3px solid var(--blue);outline-offset:3px}
.btn--ghost{background:transparent;color:var(--ink)}
.btn--ghost:hover{background:var(--ink);color:var(--paper);border-color:var(--ink)}
.btn--light{background:var(--paper);color:var(--ink);border-color:var(--paper)}
.btn--light:hover{background:var(--blue);border-color:var(--blue);color:#fff}
.btn--outline-light{background:transparent;color:var(--paper);border-color:rgba(250,248,245,.55)}
.btn--outline-light:hover{background:var(--paper);color:var(--ink);border-color:var(--paper)}
.btn--sm{min-height:44px;padding:11px 18px}

/* ---------- header ---------- */
.hdr{
  position:sticky;top:0;z-index:90;
  background:rgba(250,248,245,.94);backdrop-filter:blur(10px);
  border-bottom:1px solid var(--line);
}
.hdr__in{display:flex;align-items:center;justify-content:space-between;gap:14px;min-height:64px}
.brand{display:flex;align-items:center;gap:10px;text-decoration:none;flex-shrink:0}
.brand img{width:44px;height:44px}
.brand span{font-weight:800;letter-spacing:.14em;font-size:.9rem}
.nav{display:none}
.hdr__cta{display:none}
/* Ported from the Utopia Labs site, which is the reference for this control.
   Kept at a 44px box rather than Utopia's 40px, because 44 is the minimum
   comfortable tap target and this is a mobile-first build.

   The animation is not the usual bars-to-cross fade. The whole icon rotates
   -45deg while the top path's dash pattern is redrawn, so the two strokes
   sweep round and join into one continuous line. The dash values and the
   -32.42 offset are the ones that land the join cleanly, so treat them as a
   set and do not tune them individually. */
.burger{
  display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;padding:0;
  background:none;border:0;color:var(--ink);cursor:pointer
}
.burger__svg{width:26px;height:26px;transition:transform 500ms ease-in-out}
.burger[aria-expanded="true"] .burger__svg{transform:rotate(-45deg)}
.burger__top{
  stroke-dasharray:12 63;
  transition:stroke-dasharray 500ms ease-in-out,stroke-dashoffset 500ms ease-in-out
}
.burger[aria-expanded="true"] .burger__top{stroke-dasharray:20 300;stroke-dashoffset:-32.42}

/* drawer opens on tap, never on hover.

   It sits BELOW the header rather than over it, which is the whole point of
   the Utopia behaviour: the header stays put, the icon stays visible, and the
   icon itself is the close control. Hence z-index 85 against the header's 90,
   and a top pad that clears the header instead of running under it.

   The header is pinned while open so that pad stays correct regardless of
   scroll position. That is belt and braces now the announcement bar is gone
   and the header sits at the top anyway, but it is what keeps the pad honest
   if any strip is ever put back above the header. */
.drawer{
  position:fixed;top:0;left:0;right:0;bottom:0;z-index:85;
  background:var(--ink);color:var(--paper);
  padding:calc(65px + 26px) var(--pad) 40px;
  transform:translateY(-100%);transition:transform .32s cubic-bezier(.4,0,.2,1);
  overflow-y:auto;
}
.drawer[data-open="true"]{transform:translateY(0)}
body.menu-open .hdr{position:fixed;top:0;left:0;right:0}
.drawer a.drawer__link{
  display:block;padding:16px 0;border-bottom:1px solid rgba(250,248,245,.16);
  text-decoration:none;font-size:1.6rem;font-weight:800;text-transform:uppercase;letter-spacing:-.01em
}
.drawer .btn{width:100%;margin-top:26px}

/* ---------- hero ---------- */
.hero{position:relative;background:var(--ink);color:var(--paper);isolation:isolate;overflow:hidden}
.hero__bg{position:absolute;inset:0;z-index:-2}
.hero__bg img{width:100%;height:100%;object-fit:cover;opacity:.42;filter:grayscale(.35)}
.hero::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(26,25,23,.72) 0%,rgba(26,25,23,.55) 45%,rgba(26,25,23,.94) 100%)
}
/* padding-block, not the padding shorthand, because .hero__in also carries
   .wrap and the shorthand would wipe out its padding-inline gutter */
.hero__in{padding-block:64px 44px}
.hero .eyebrow{color:#C9C3BA;font-size:var(--step--1)}
.hero h1{font-size:var(--step-4);margin-bottom:22px}
.hero h1 em{font-style:normal;color:#C9C3BA;display:block}
.hero__lede{font-size:var(--step-1);max-width:30ch;color:#E6E1DA;margin-bottom:30px;line-height:1.35;font-weight:500}
.hero__meta{
  display:flex;flex-direction:column;gap:2px;margin-bottom:28px;
  padding-left:16px;border-left:3px solid var(--paper)
}
.hero__meta b{font-size:1.05rem;font-weight:800;text-transform:uppercase;letter-spacing:.02em}
.hero__meta span{font-size:.9rem;color:#C9C3BA}
.hero__btns{display:flex;flex-direction:column;gap:12px}
.hero__btns .btn{width:100%;border-radius:var(--r-btn)}
/* Opt-in via .hero--balanced on the section, currently home only. This was a
   .hero__lede + .hero__btns sibling selector, which broke the moment
   /autumn-edit also lost its meta block and put its buttons straight after its
   lede. A class says which hero wants this instead of inferring it from markup
   that can change underneath.

   The value deliberately equals .hero__in's padding-bottom. The buttons are the
   last thing in the hero, so the space below them IS that padding, and matching
   it above centres the button block between the bottom of the text and the
   bottom of the image. Margins collapse against the lede's own 30px rather than
   adding to it, so the effective gap is this number, not 74.

   If .hero__in's padding-bottom ever changes, change this with it, at both
   breakpoints, or the block drifts off centre again. */
.hero--balanced .hero__btns{margin-top:44px}

/* On /autumn-edit the poster IS the hero. Nothing over it, nothing beside it,
   nothing under it inside the section: no gutter, no padding, no gradient, no
   background showing through at any width.

   The overlay and the ink background that every other hero relies on are turned
   off here rather than left to sit on top of the artwork.

   It keeps its natural aspect ratio instead of being cropped to a hero shape,
   because the poster carries the dates on its bottom line and the "MRKT Markets
   presents" on its top line, and any crop eats those first. The consequence is
   a tall section on a wide screen, which is what a 4:3 image full-bleed means.

   It stays a real h1 with the wording in the alt, because a page whose only
   title is an image has no heading at all to Google or a screen reader. */
.hero--poster{padding:0;background:none;isolation:auto}
.hero--poster::after{content:none}
/* Scoped through .hero--poster rather than written flat, because `.hero h1`
   sets a 22px bottom margin and outranks a lone class, which left a strip of
   background showing under the image. */
.hero--poster .hero__poster{margin:0;font-size:0;line-height:0}
.hero--poster .hero__poster img{width:100%;height:auto}

/* ---------- stat band ---------- */
/* Padding lives on the band now. It used to come from .stat's own 26px, which
   went with the tiles when this became a single countdown. */
.stats{background:var(--ink-2);color:var(--paper);padding:44px 0 38px}

/* ---------- the live countdown ----------
   Reuses .stats and .stat rather than inventing a component, because four
   ticking units want exactly the layout four stat tiles already had: two up on
   a phone, four across from 600px.

   tabular-nums is the whole trick. Raleway's default figures are
   proportional, so a seconds counter cycling 1 to 0 changes width every tick
   and the number visibly jitters against its neighbours. Tabular figures are
   fixed width, so the digits change and nothing moves.

   Without JS the hours, minutes and seconds are hidden and the day count is
   left standing alone across the full width, which is what this band showed
   before it became a clock. Committed 00s would otherwise render as a stopped
   timer, which is worse than no timer. The .js class is set inline in <head>,
   so this never flashes. */
/* Its own component rather than a reskin of .stats__grid. That grid draws a
   hairline between every cell, which is right for four unrelated statistics and
   wrong for one number split into four parts: it read as a table rather than as
   a clock. No dividers and no cell backgrounds here, just digits on the band.

   Four across at every width, including a 320px phone. A clock is read left to
   right in one line, and the 2x2 the stat grid falls back to below 600px loses
   that reading completely.

   The digits are sized off the viewport rather than the type scale, because the
   brief is that they fill the bar, and the site's largest step tops out well
   short of that. 11vw is what keeps two digits inside an 87px cell on a 390px
   screen while still reaching the 7.5rem cap on a desktop. */
.cdown{display:grid;grid-template-columns:repeat(4,1fr);align-items:end}
.cdown__u{text-align:center;min-width:0}

/* THE PLATE. Not a flip clock any more, and the name is all that is left of
   one: an ink panel on the ink-2 band, a small radius, and nothing else. The
   turn, the split and the hinge all went on 2026-08-19. What that skeuomorphism
   was ever buying was an animation, and with the animation gone it was just a
   line drawn across four numbers.

   inline-block, not block, so the plate is sized by its digits rather than by
   the grid cell. A cell is 279px on a desktop and a plate stretched to fill it
   would be mostly empty ink with a number floating in the middle. Padding is in
   em so the plate scales with the type instead of needing a value per
   breakpoint. */
/* ON A PHONE THE FOUR PLATES ARE ONE BAR. Separate cards sized to their digits
   is right on a desktop, where a cell is 279px wide and there is room to spare.
   On a 390px screen a cell is 87px, so a plate that shrinks to its digits ends
   up 52px with 35px of dead band either side, and four small cards floating in
   a wide dark strip is what that produced.

   Filling the cell instead recovers the whole width for the numbers: the plates
   butt up against each other into a single strip, divided by hairlines rather
   than gaps, with only the outer corners rounded. The digits grow to match,
   from 8.5vw to 13vw, which is the actual point of the change.

   Above 600px they separate back into individual cards, further down this file. */
.flip{
  display:block;position:relative;
  border-radius:0;
  /* FLAT INK, no split and no hinge. Both went 2026-08-19 at Ty's request:
     a 1px hairline sat at 50% above the digits, and behind them the panel was
     two shades meeting at the same line, so the numbers had a rule drawn
     through them twice over.

     Removing them is the honest end of removing the turn. The split's only job
     was to make the panel read as a card mid-flip, which it stated in this
     comment and which stopped being true the moment the animation went. What
     is left is what the band actually needs: an ink panel per unit on the
     ink-2 strip, and a number on it. */
  background:var(--ink);
  /* THE TYPE SIZE LIVES HERE, not on .flip__face, and the padding is in em so
     the plate grows with its digits instead of needing a value per breakpoint.
     Set on the face instead, em resolved against the inherited 16px body size
     rather than the 96px digits, so the padding came out at 4px and the numbers
     hung over both edges of the plate. */
  font-size:clamp(2.4rem,13vw,6rem);
  padding:.14em 0;
  margin-bottom:14px;
}
/* Only the ends of the strip are rounded, and neighbours are divided by a
   hairline rather than a gap, so the four read as one object. */
.cdown__u:first-child .flip{border-radius:8px 0 0 8px}
.cdown__u:last-child .flip{border-radius:0 8px 8px 0}
.cdown__u + .cdown__u .flip{border-left:1px solid rgba(0,0,0,.5)}
/* Both single-plate states: there is no strip to be an end of, so the plate is
   rounded all round and has no neighbour to be divided from. */
.cdown.is-today .flip,
html:not(.js) .cdown .flip{border-radius:8px;border-left:0}

.flip__face{
  display:block;position:relative;z-index:1;
  font-size:1em;
  font-weight:800;line-height:1;letter-spacing:-.03em;
  /* TWO numeral features, and the second one is not cosmetic.

     tabular-nums, because Raleway's proportional digits change width as they
     cycle, so the seconds jitter and the plate resizes on every tick.

     lining-nums, because RALEWAY SHIPS OLD-STYLE FIGURES AS ITS DEFAULT and
     nothing on this site had noticed. Measured off the rendered glyphs: 6 and
     8 rise above the cap line, 3 4 5 7 9 drop below the baseline, and 0 1 2 sit
     at x-height, a spread of 29 units in a 200px sample. At the countdown's
     96px that is about 14px of wobble across four plates, which is what made it
     look, in Ty's words, wonky and cheap. lnum is the same typeface, not a
     different one: it is Raleway's own lining set, so nothing else about the
     digits changes.

     THE SAME DEFAULT APPLIES TO EVERY NUMBER ON THIS SITE, the booth prices and
     the ticket tiers included. Only the countdown is fixed here, because that
     is what was asked for. See the README. */
  font-variant-numeric:tabular-nums lining-nums;
  font-feature-settings:"tnum","lnum";
  transform-origin:center center
}
/* THERE IS NO TURN. A rotateX keyframe ran here until 2026-08-19 and was
   removed at Ty's request: the digits change instantly now. The plate keeps its
   flip-clock face, the split and the hinge, and simply does not move.

   Gone with it: the .flip.is-flip rule, the cdflip keyframes, the perspective
   on .flip, and the reduced-motion branch in the script, which existed only to
   skip an animation that no longer exists. `is-flip` is in the orphan guard, so
   a stray rule or class for it fails the suite. */
/* .cdown__lab, not .cdown__u span. The plate is a <span> too, and
   ".cdown__u span" outranks ".flip", so display:block landed on the plate and
   stretched it to the full width of its grid cell instead of letting it shrink
   to its digits. */
.cdown__lab{
  display:block;
  font-size:.56rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;
  color:#B9B3AB
}
/* Without JS the hours, minutes and seconds are hidden and the day count stands
   alone, which is what this band showed before it became a clock. Committed 00s
   would render as a stopped timer, which is worse than no timer. The .js class
   is set inline in <head>, so this never flashes. */
html:not(.js) .cdown{grid-template-columns:1fr}
html:not(.js) .cdown__hms{display:none}
/* The market is today. Counting down to something already happening is
   nonsense, so the units collapse to one cell that says so. "Today" is a word,
   not two digits, so it cannot take the digit size at any viewport. */
.cdown.is-today{grid-template-columns:1fr}
.cdown.is-today .cdown__hms{display:none}
.cdown.is-today .flip{font-size:var(--step-3)}
.cdown.is-today .flip__face{letter-spacing:-.02em}

/* ---------- intro paragraph: the standfirst under a poster ----------
   Replaced the When/Where/Admission/Vendors band. Serif and centred so it reads
   as a spoken line rather than another block of interface, and set at the
   quote's size because it is doing the same job the quote block used to. */
/* 54ch, not the 34ch this started at. At this serif size a narrow measure broke
   the sentence into seven short ragged lines and read as a tall column rather
   than as a spoken line. */
/* The opening statement on /autumn-edit, directly under the poster.

   MRKT's own sentence, reproduced word for word, so every decision here is
   typographic and none of it touches the copy.

   It is the first set type on the page and the poster does all the work above
   it, so it is sized as a statement rather than as body copy. What this fixes:
   at 32px in a 1440px field it read as a caption of the poster rather than the
   opening line of the page, it broke to five lines with "expect." stranded
   alone on the last one, and it sat on a plain stripe with no anchor at all.

   text-wrap:balance is what evens the lines and kills the widow. Where it is
   not supported the text simply wraps greedily, which is what it does today,
   so this degrades to the current behaviour rather than to something broken.

   The rule above it is drawn with ::before rather than added to the markup,
   which keeps this a pure stylesheet change. */
/* ---------- the opening statement on /autumn-edit ----------

   MRKT's own sentence, reproduced word for word. Not one word is ours.

   There is almost nothing here on purpose. This block took four redesigns
   before the actual problem showed up, and the problem was never the styling.

   Every content section on this site is the same unit: an eyebrow, an
   uppercase Raleway h2, then supporting text. This section had an eyebrow and
   no heading, so beside its neighbours it read as a fragment no matter how it
   was set. Each redesign made it a more distinctive special case, which made
   it fit less: centred and small, then centred and large, then a 48px EB
   Garamond block with a drop cap on a two-column grid, on a site that sets
   serif at 2.4rem and smaller and has no drop cap anywhere.

   It is a .sec__head now, the same component #find, #plan and #details use, so
   it inherits the page's structure instead of imitating it. All that is left
   here is a size bump on the paragraph, because MRKT's sentence is the point
   of the section rather than a subtitle under it.

   Ink stays, at Ty's call. The poster above is a dark full-bleed image and
   cutting straight to a pale stripe left the sentence looking detached from
   the thing it describes. It does not disturb the alternating run, because ink
   differs from both neighbours. */
.intro{font-size:var(--step-1);line-height:1.5;max-width:46ch}

/* Binds the last two words so the sentence cannot end on one stranded word,
   which it did at 600px. text-wrap:pretty handles most widths but is not
   supported everywhere, and this guarantees it where it is not. A span rather
   than a &nbsp; entity so textContent stays byte-identical, which is what lets
   the checks matching MRKT's wording keep matching. */
.nowrap{white-space:nowrap}

/* ---------- admission tiers ----------
   Deliberately NOT the .booths component the vendor page uses. They look alike
   today, but one is admission for shoppers and the other is booth pricing for
   vendors, and sharing a component would couple two things that have no reason
   to change together.

   One option is meant to win. Pre-sale gets the blue, the badge and the lift;
   Door is left plain, so which is better reads at a glance instead of the
   reader having to compare two numbers. */
/* ============================================================
   ADMISSION CARDS. Hand-ported from a pricing-card component Ty supplied,
   with the source's dark zinc palette swapped for MRKT's own tokens and its
   monthly/yearly switch removed, because a ticket is bought once.

   Radius is 8px, --r-btn, not the site's 2px default and not the 20px the
   original ticket cards used. The source component is rounded and Ty picked it
   for that; 8px is the roundest value already in this stylesheet, so this
   borrows a corner the site has rather than adding a new one.
   ============================================================ */
.tix{display:grid;gap:18px;max-width:760px}
.tix__card{
  display:flex;flex-direction:column;
  padding:28px 26px;border-radius:var(--r-btn);
  background:var(--paper);border:1px solid var(--line)
}
/* The featured card is the one you can act on. Blue edge and a touch more
   weight, no badge: the button is the only badge it needs. */
.tix__card--lead{border-color:var(--blue);border-width:1.5px}
.tix__top{margin-bottom:20px;padding-bottom:20px;border-bottom:1px solid var(--line)}
.tix__name{
  margin:0;display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  font-size:.72rem;font-weight:800;text-transform:uppercase;letter-spacing:.14em;
  color:var(--eyebrow)
}
.tix__card--lead .tix__name{color:var(--blue-dk)}
.tix__desc{margin:10px 0 0;font-size:.92rem;color:var(--grey);line-height:1.5}
.tix__price{
  margin:14px 0 0;
  font-size:var(--step-3);font-weight:800;line-height:1;letter-spacing:-.02em;
  color:var(--ink)
}
.tix__card--lead .tix__price{color:var(--blue-dk)}
/* The 26px is the MINIMUM gap under the list, and margin-top:auto on the
   button is what adds to it so the two cards' feet line up. Those are two
   different jobs and both are needed: auto alone resolves to 0 on whichever
   card is taller, which put the button flush against the last feature line. */
.tix__list{list-style:none;margin:0 0 26px;padding:0;display:grid;gap:12px}
.tix__list li{
  display:grid;grid-template-columns:18px 1fr;gap:10px;align-items:start;
  font-size:.95rem;color:var(--ink-2);line-height:1.45
}
/* The tick is the source component's CircleCheck, drawn inline rather than
   pulled from lucide-react, so nothing extra loads. currentColor, so it takes
   the card's own colour instead of needing a second rule. */
.tix__ic{width:18px;height:18px;margin-top:2px;color:var(--blue);flex:0 0 auto}
.tix__card:not(.tix__card--lead) .tix__ic{color:var(--grey)}
/* margin-top:auto so the button sits on the card's floor whatever the lists
   measure, which is what keeps the two cards' feet level. */
/* margin-top:auto pushes the button to the card's floor whatever the two
   feature lists measure, which is what keeps the cards' feet level.

   NO padding-top override here. An earlier version set padding-top:0 while
   zeroing the gap, which stripped the top half of .btn's 14px and left the
   label sitting high in its own box. .btn already centres its content with
   flex; it only needs its padding left alone. */
.tix__btn{margin-top:auto}
.tix .btn{margin-bottom:0;justify-content:center}
.tix__note{margin:24px 0 0;font-size:.82rem;color:var(--grey);line-height:1.5}

/* The on-sale indicator, restyled from a filled pill to a label.
   MARKUP SHAPE IS PINNED BY build.js, which rewrites the class and the text of
   this span by regex on data-ticket-status. Restyle it freely; do not rename
   .tier__status or .tier__dot and do not move the <i>. The class names still
   read "tier" because that regex is keyed on them, not because a tier component
   still exists.

   The pill background, the 100px radius and the pulse animation are all gone.
   A filled, rounded, pulsing badge is the visual signature of a live checkout,
   and this section is not one. The dot survives because it is what makes the
   three states legible at a glance without reading the words. */
.tier__status{
  display:inline-flex;align-items:center;gap:.55em;
  margin-left:1.5em;white-space:nowrap;
  font-size:.92rem;font-weight:800;text-transform:uppercase;letter-spacing:.09em
}
/* .92rem against the .68rem label it shares a line with, in blue against that
   label's grey. It was .62rem and it read as a footnote on the end of a line
   nobody had a reason to look at. It is now the biggest thing on that line by
   half again, the only blue in the text column, and it sits directly beside the
   blue $5, so the two make one blue cluster the eye catches before it reads
   anything. Size, weight, colour and space. No fill, no pill, no motion.

   1.5em of clearance before it, not the .9em it had. Sitting close to the label
   the two read as one run of small caps; at this distance it reads as a
   separate statement about the row. */
.tier__dot{width:8px;height:8px;border-radius:50%;background:currentColor;flex:0 0 auto}
/* HIDDEN WHILE LIVE, and this is the whole point of the rule.

   "On sale now" directly above a button reading Get Tickets is the same fact
   twice, which is the rule the rest of this site follows. But the element
   cannot be deleted: build.js rewrites it from the sheet, and every sold-out
   rule below selects on it.

   So it is hidden in the one state where it says nothing, and visible in the
   two states where it is the most important thing on the page. Removing this
   line does not break anything, it just puts the redundant label back. */
.tier__status.is-live{display:none}
.tier__status.is-soon,
.tier__status.is-out{color:var(--grey)}

/* ---------- category tiles ----------
   Not the .chip control from the line-up, even though they name the same ten
   categories, because these are not filters and must not look tappable. */

/* ---------- the quieter markets below a featured one ----------
   No photo, no ticket button and a text link rather than a .btn, all on
   purpose. Everything above these on the page is about the featured market and
   a second set of buttons down here would split the decision. */
.upnext{display:grid;gap:12px}
.upcard{background:var(--paper);border:1px solid var(--line);border-radius:var(--r-card);padding:22px 20px}
.upcard__top{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:6px}
.upcard h3{font-size:1.02rem}
.upcard__city{font-size:.64rem;font-weight:700;text-transform:uppercase;letter-spacing:.18em;color:var(--grey)}
.upcard__when{font-size:.85rem;color:var(--ink-2);margin-bottom:14px}
.upcard__link{
  display:inline-flex;align-items:center;min-height:44px;
  font-size:.7rem;font-weight:800;text-transform:uppercase;letter-spacing:.12em;
  color:var(--blue);text-decoration:none;border-bottom:1.5px solid transparent
}
.upcard__link::after{content:"\2192";margin-left:.5em;transition:transform .2s ease}
.upcard__link:hover{border-bottom-color:var(--blue)}
.upcard__link:hover::after{transform:translateX(4px)}
.upcard__link:focus-visible{outline:3px solid var(--blue);outline-offset:3px}

/* ---------- sections ---------- */
.sec{padding:64px 0}
.sec--paper2{background:var(--paper-2)}
.sec--ink{background:var(--ink);color:var(--paper)}
/* #C9C3BA, not the #A9A39B this rule asked for while it was being beaten by
   `.sec--ink .sec__head p`. The lighter value is what these have actually
   rendered as, and it is the right one over ink. */
.sec--ink .eyebrow{color:#C9C3BA}
.sec__head{margin-bottom:34px;max-width:52ch}
/* That margin exists to separate the head from the content under it. Where the
   head IS the section, as on #about, there is nothing to separate it from and
   the margin just makes the band bottom-heavy. Only matches when the head is
   the last thing in its wrap, so every other section is untouched. */
.sec__head:last-child{margin-bottom:0}
.sec__head h2{font-size:var(--step-3);margin-bottom:14px}
.sec__head p{color:var(--ink-2);font-size:var(--step-0)}
.sec--ink .sec__head p{color:#C9C3BA}
.note{font-size:.78rem;color:var(--grey);font-style:italic;margin-top:10px}

/* ---------- events ---------- */
/* end, not stretch: the cards with no photo stay as short as their content
   instead of padding themselves out to match the height of the one that has
   an image. That height difference is the point, since it makes the market you
   can actually buy a ticket for the tallest thing in the row.

   They hang from the bottom rather than sitting at the top, changed
   2026-08-18 on Ty's call, so the short cards line up on one baseline with the
   foot of the featured card and the row reads as a single block stepping down
   from the left. With start they sat on the top edge and left a ragged gap
   underneath. Only affects the row where heights differ: at two columns that
   is the first row, and the two undated cards in the second row are the same
   height as each other either way. Below 600px the grid is one column and this
   does nothing at all. */
.events{display:grid;gap:18px;align-items:end}
.ev{
  position:relative;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-card);
  display:flex;flex-direction:column;overflow:hidden
}
/* Stretched link. The title's ::after covers the whole card, so anywhere on
   the card opens the event page, while the Get Tickets button sits above it
   and keeps its own hit area. Done this way rather than wrapping the card in
   an <a> because that would nest a link inside a link, which is invalid and
   breaks keyboard navigation. Only cards with a detail page get this. */
.ev__link{color:inherit;text-decoration:none}
.ev__link::after{content:"";position:absolute;inset:0;z-index:1}
.ev__link:hover{text-decoration:underline;text-underline-offset:3px}
.ev__body .btn{position:relative;z-index:2}
/* The next market, and only ever one card at a time. Same blue as the Tickets
   Live pill, so the ring and the pill read as one signal rather than two
   competing ones. Border width rather than an outline, so the ring cannot
   overlap the neighbouring card, and box-sizing keeps the card's outer size
   identical either way, so nothing in the grid shifts when the ring moves. */
.ev--next{border-color:var(--blue);border-width:4px}
/* The image is positioned, not just sized, and that is load bearing.

   The file here was assets/browsing.jpg, 933x1400, a portrait photo. Left in
   normal flow at width:100% its natural height is 1.5x its width, and an
   aspect-ratio box takes its automatic minimum size from its content, so the
   taller child won and this box rendered at roughly 16/24 rather than 16/10.
   object-fit:cover never applied, because the image was never constrained by
   anything to be cropped into. It had been wrong since the card was built and
   was only caught on 2026-08-14, when a fourth market narrowed the column and
   made this card twice the height of its neighbours.

   The file is assets/apples-calendar.jpg now, which is already 16/10, so
   nothing here is currently doing any correcting. That is not a reason to
   remove it: the next photo MRKT supplies will not be 16/10 either, and the
   whole point below is that this box does not depend on the file.

   Taking the image out of flow means the box keeps the ratio the design asked
   for and the photo is cropped into it, whatever shape the file happens to be.
   That matters more than this one photo: MRKT supplies these, and the next one
   will not be 16/10 either. There is a check measuring the rendered box, not
   reading this rule, because this rule said 16/10 the whole time it was wrong. */
.ev__media{position:relative;aspect-ratio:16/10;background:var(--paper-2);overflow:hidden}
.ev__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.pill{
  position:absolute;top:12px;left:12px;
  font-size:.62rem;font-weight:800;text-transform:uppercase;letter-spacing:.14em;
  padding:7px 11px;border-radius:100px;background:var(--ink);color:var(--paper)
}
.pill--live{background:var(--blue)}
.pill--soon{background:var(--paper);color:var(--ink);border:1.5px solid var(--ink)}
.pill--past{background:var(--grey)}
/* 24px top and bottom, equal, at Ty's request 2026-08-19. It was 22 and 24,
   which was invisible while a pill sat at the top and obvious once the pill
   came off and the city line became the first thing in the card. Measured from
   the card's outer edge, so both gaps read 25px with the 1px border. */
.ev__body{padding:24px 20px;display:flex;flex-direction:column;flex:1}
/* No rule for a pill inside .ev__body any more. The three cards that carried
   one lost it on 2026-08-19, and the only pill left sits on the ticketed card's
   photograph, which .pill already positions. Not in the orphan guard, because
   .pill itself is very much still in use. */
.ev__city{font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.18em;color:var(--blue);margin-bottom:8px}
.ev__body h3{font-size:var(--step-1);margin-bottom:12px}
.ev__facts{list-style:none;margin:0 0 20px;padding:0;font-size:.88rem;color:var(--ink-2)}
/* block, not flex. The flex and its gap existed for a `<b>` label in front of
   each fact, which these four cards have never used: there is no `<b>` in any
   .ev__facts on the site. It has to be a block container for text-overflow to
   work at all, since ellipsis cannot be applied to the anonymous flex item a
   bare text node becomes. `.ev__facts b` below is now orphaned by this and is
   left in place rather than deleted on a whim, but it is dead and its
   min-width will not do anything without the flex parent. */
.ev__facts li{display:block;padding:4px 0;border-bottom:1px dotted var(--line)}
.ev__facts li:last-child{border-bottom:0}
.ev__facts b{min-width:62px;font-weight:700;text-transform:uppercase;font-size:.68rem;letter-spacing:.1em;color:var(--grey);padding-top:2px}
/* Rounded rectangle, not the near-square site default and not a full pill.
   Scoped to the calendar cards so every other button stays sharp. Sits a few
   px inside the card's own --r-card so the two curves nest properly; matching
   the card exactly would make the button look like it is bulging out of it. */
.ev__body .btn{margin-top:auto;width:100%;border-radius:var(--r-btn)}

/* ---------- what MRKT is: 3 columns, 25 words max each ---------- */
.cols{display:grid;gap:30px}
.col__n{font-family:'EB Garamond',Georgia,serif;font-size:2.4rem;line-height:1;color:var(--blue);margin-bottom:12px;display:block}
.col h3{font-size:var(--step-1);margin-bottom:10px}
.col p{color:var(--ink-2);font-size:.94rem}

/* ---------- the story block on /about ----------
   Prose beside a photograph. Deliberately NOT .apply__grid, which looks like
   the same thing at a glance: that one is text against text on the vendor
   page and this is text against an image, so sharing it would couple two
   blocks that have no reason to change together. Same call, same reason, as
   the admission tiers against .booths.

   One column at 390px, because a photograph next to a reading measure is not
   a layout on a phone, it is two things squeezed. The desktop split is in the
   900px block near the bottom of this file.

   62ch, not the 52ch .sec__head uses. This is the only running prose on the
   site long enough to need a real measure, and a heading's width is not a
   paragraph's. */
.story{display:grid;gap:30px}
/* Photo first on a phone, at Ty's request 2026-08-19. In one column the source
   order put four paragraphs between the heading and the only picture in the
   section, so the image was below the fold and most readers never reached it.
   Lifting it gives the section something to look at immediately.

   Done with order rather than by moving the figure in the HTML, because at 900
   and up the two-column layout wants body left, photo right, which IS the
   source order. Reordering the markup would fix the phone and break the
   desktop. The reset lives with the two-column rule at that breakpoint.

   Safe to diverge visual order from DOM order here because the figure is not
   interactive: it takes no focus, so keyboard order is untouched, and a screen
   reader simply reads the alt text after the prose instead of before it. */
.story .photo{order:-1}
.story__body{max-width:62ch;color:var(--ink-2)}
/* Shared by the story photograph and the team band below it, because the two
   want exactly the same thing and a second class differing in nothing is how
   two photographs drift apart later.

   --r, not --r-card. 14px is reserved for cards that read as tappable
   objects, and a photograph is not one. */
.photo{margin:0}
.photo img{width:100%;border-radius:var(--r)}
/* ---------- the full-bleed photograph ----------
   Used by the team shot on /about and nothing else. It is a component rather
   than a modifier on .photo because almost nothing it does is what .photo does.

   IT IS A DIRECT CHILD OF THE SECTION, NOT OF .wrap. That is the whole trick,
   and it is deliberate: a figure inside .wrap can only reach the viewport edges
   through `width:100vw; margin-inline:calc(50% - 50vw)`, which is a real
   technique but brings the scrollbar with it. 100vw counts the scrollbar and
   the content box does not, so on any platform with classic scrollbars the
   image overhangs by that width, gets clipped by the body's overflow-x, and the
   right edge quietly stops touching the screen. Outside .wrap the figure is
   simply as wide as the section, which is as wide as the page, at every width
   and with no arithmetic.

   SIZING, which is the part worth reading. The frame is 2.85:1 and the image is
   always full width, so its natural height is width/2.85: 505px at 1440, but
   only 137px at 390, which is a strip rather than a photograph. min-height is a
   floor under that:

       min-height: min(50vw, 320px)

   50vw IS the 2:1 crop, expressed so it holds at every width rather than at one
   tested one. Below about 640px the floor wins and the box is exactly 2:1, so
   the crop is capped there no matter how narrow the window gets: a fixed 195px
   floor would have kept cropping past 2:1 on a 320px phone. 320px is where the
   floor stops growing, so between about 640 and 912 the crop tapers off on its
   own, and from 912 up the natural height wins outright and the frame is
   uncropped and simply scales with the window. No breakpoint, no jump, one
   expression.

   32% object-position, not centre. The eight faces span x 240 to 1165 of 1530
   and a 2:1 window is 1074 wide; 32% places it at x 145 to 1219 so every face
   keeps a margin, and what the crop takes is the barrel and pumpkin on the
   right, empty backdrop on the left, and the outer edge of two arms. Crop past
   2:1 and the next thing lost is a face, which is not a crop.

   CONSEQUENCE WORTH KNOWING: past 1530px of viewport the image is upscaling
   past its own resolution, so it softens, and at 2560 the band is 899px tall.
   That is the accepted trade for edges that always touch the screen. A larger
   original would fix the softness; MRKT do not have one. */
.fullshot{margin:0}
.fullshot img{
  width:100%;border-radius:0;
  min-height:min(50vw,320px);
  object-fit:cover;object-position:32% center
}

/* Bottom padding off, so a full-bleed photograph sits straight on top of
   whatever the next section leads with. Used by the team band on /about, whose
   picture meets the Join us photograph edge to edge with no paper between them,
   at every width. */
/* Written .sec.sec--flush rather than .sec--flush. `.sec{padding:96px 0}`
   inside the 900px block ties with it on specificity and comes later in the
   file, so the single-class version was silently overridden from 900px up and
   the photograph floated 96px clear of the band below it. Fourth time source
   order has bitten in this stylesheet today; two classes settles it wherever
   the rule happens to sit.

   The top padding is reduced and the heading's bottom gap is raised to match,
   which centres the heading in its own strip of colour. It was not centred and
   it looked wrong: `.sec`'s 96px above, then 14px below, put the heading 70% of
   the way down a 202px band. **The 14px is not a value anybody chose.**
   `.sec__head` is the only child of `.wrap` here, so `.sec__head:last-child`
   zeroes its 34px margin, and what is left is the h2's own 14px collapsing
   through the parent. A heading block sitting on top of a photograph is a label
   plate, and a label plate reads as a mistake unless it is centred in its
   plate. Above and below are now the same number at both widths, and the band
   height barely moves: 202px to 204px at 1440. */
.sec.sec--flush{padding-top:40px;padding-bottom:0}
/* Three classes deliberately: this has to beat `.sec__head:last-child`, which
   ties with a two-class version and would win or lose on where this block
   happens to sit in the file. */
.sec.sec--flush .sec__head{margin-bottom:40px}
/* The sign-off line. MRKT's own last sentence, set in the serif the column
   numbers and the step counters already use, so the page closes on the one
   typeface this site keeps for accents rather than on another bold sans. */
.story__sign{
  font-family:'EB Garamond',Georgia,serif;font-size:var(--step-1);
  line-height:1.35;color:var(--ink);margin-top:26px
}

/* ---------- vendor directory ---------- */
.filters{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:26px}
.chip{
  min-height:44px;padding:10px 16px;border:1.5px solid var(--line);background:var(--paper);
  border-radius:100px;font:inherit;font-size:.75rem;font-weight:700;text-transform:uppercase;
  letter-spacing:.08em;cursor:pointer;color:var(--ink-2);
  transition:background .16s ease,color .16s ease,border-color .16s ease
}
.chip:hover{border-color:var(--ink)}
.chip[aria-pressed="true"]{background:var(--ink);color:var(--paper);border-color:var(--ink)}
.chip:focus-visible{outline:3px solid var(--blue);outline-offset:2px}

/* Search chip. Rests as a plain 44px icon circle, same height as the other
   chips. Opening it (site.js toggles .is-searching on .filters) grows it to
   fill the row and hides the category chips outright, which is the
   "stretches across the screen and turns into a search bar" the icon does.
   Contained by .wrap like everything else on the page, not the literal
   viewport edge. Reduced motion needs no override here: the site-wide
   *{transition:none!important} rule already makes this instant instead of
   animated, same as the drawer and burger icon. */
.chip--search{
  width:44px;flex:0 0 44px;padding:0;overflow:hidden;display:inline-flex;align-items:center;
  transition:width .3s cubic-bezier(.4,0,.2,1),flex-basis .3s cubic-bezier(.4,0,.2,1)
}
.filters.is-searching .chip--search{width:100%;flex:1 1 100%}
.filters.is-searching .chip[data-cat]{display:none}
.chip--search__toggle{
  width:44px;height:44px;flex:0 0 44px;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:0;padding:0;color:inherit;cursor:pointer
}
.chip--search__toggle:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.chip--search__icon{width:18px;height:18px;flex-shrink:0}
.chip--search__input{
  flex:1;min-width:0;border:0;background:transparent;font:inherit;font-size:1rem;
  color:var(--ink);padding:0 8px;opacity:0;transition:opacity .18s ease
}
.chip--search__input::placeholder{color:var(--ink-2)}
.chip--search__input:focus-visible{outline:none}
/* Hides the browser's own built-in clear-x for type="search", which would
   otherwise sit right beside this control's own close button and read as
   two different X icons doing two different things. */
.chip--search__input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
.filters.is-searching .chip--search__input{opacity:1}
.chip--search__close{
  width:32px;height:32px;flex:0 0 32px;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:0;padding:0;margin-right:8px;color:var(--ink-2);cursor:pointer;opacity:0;transition:opacity .18s ease
}
.chip--search__close:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.chip--search__close svg{width:14px;height:14px;flex-shrink:0}
.filters.is-searching .chip--search__close{opacity:1}
.vendors{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}
.vendor{
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r);
  padding:20px 16px;text-align:center;min-height:118px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px
}
.vendor[hidden]{display:none}
/* A vendor tile is an <a> when the sheet supplies a link and a <div> when it
   does not, so the two have to look identical at rest. Only the linked ones
   respond to hover, which is the affordance telling you which are clickable. */
a.vendor{text-decoration:none;color:inherit;transition:border-color .2s ease,transform .2s ease}
a.vendor:hover{border-color:var(--ink);transform:translateY(-2px)}
a.vendor:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.vendor b{font-size:.94rem;font-weight:800;text-transform:uppercase;letter-spacing:-.01em;line-height:1.2}
.vendor span{font-size:.62rem;text-transform:uppercase;letter-spacing:.14em;color:var(--grey)}
.vendors__empty{grid-column:1/-1;padding:34px 0;text-align:center;color:var(--grey);font-style:italic}
/* Clipped by the show-more control, as opposed to [hidden] which is the
   category filter. Two separate mechanisms on purpose: a tile can be filtered
   out, clipped, or both, and neither may clobber the other. */
.vendor.is-clipped{display:none}
.showmore__wrap{text-align:center}
.showmore{
  display:inline-flex;align-items:center;gap:.6em;margin-top:22px;
  background:none;border:1.5px solid var(--ink);border-radius:100px;
  padding:13px 24px;cursor:pointer;color:var(--ink);
  font:inherit;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.12em;
  transition:background .2s ease,color .2s ease
}
.showmore:hover{background:var(--ink);color:var(--paper)}
.showmore[hidden]{display:none}
.showmore svg{width:14px;height:14px;transition:transform .25s ease}
.showmore[aria-expanded="true"] svg{transform:rotate(180deg)}

/* ---------- vendor apply ---------- */
.booths{display:grid;gap:12px;margin-bottom:38px}
/* The cards close the pricing section now that the diagram sits above them,
   and .wrap has no padding to stop this margin landing on top of the section's
   own, which made the stripe run 102px past the cards instead of the site's
   64px. Nothing else uses .booths. */
.booths:last-child{margin-bottom:0}
.booth{
  border:1px solid rgba(250,248,245,.22);border-radius:var(--r);padding:22px 20px;
  display:flex;align-items:baseline;justify-content:space-between;gap:14px
}
.booth b{font-size:1rem;font-weight:800;text-transform:uppercase;letter-spacing:.04em}
.booth i{font-style:normal;font-size:var(--step-2);font-weight:800;color:var(--paper)}
.steps{list-style:none;margin:0 0 34px;padding:0;display:grid;gap:0;counter-reset:s}
.steps li{
  counter-increment:s;position:relative;padding:18px 0 18px 52px;
  border-bottom:1px solid rgba(250,248,245,.16);font-size:.92rem;color:#D6D1C9
}
.steps li:last-child{border-bottom:0}
.steps li::before{
  content:counter(s,decimal-leading-zero);position:absolute;left:0;top:17px;
  font-family:'EB Garamond',Georgia,serif;font-size:1.35rem;color:var(--paper);opacity:.55
}
.steps b{display:block;color:var(--paper);font-weight:800;text-transform:uppercase;font-size:.82rem;letter-spacing:.08em;margin-bottom:3px}
.incl{list-style:none;margin:0 0 32px;padding:0;font-size:.9rem;color:#D6D1C9}
.incl li{padding:9px 0 9px 26px;position:relative;border-bottom:1px solid rgba(250,248,245,.12)}
.incl li::before{content:"+";position:absolute;left:0;top:9px;font-weight:800;color:var(--paper)}
.fine{font-size:.76rem;color:#A9A39B;margin-top:16px}

/* ---------- spec list: event detail facts ---------- */
/* Labels are kept here, unlike the calendar cards where they were dropped,
   because a bare address and a bare set of opening hours do not announce what
   they are the way a date and a venue name do. */
.spec{list-style:none;margin:0 0 26px;padding:0}
.spec li{padding:16px 0;border-bottom:1px solid var(--line);display:grid;gap:5px}
.spec li:last-child{border-bottom:0}
.spec b{font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.14em;color:var(--grey)}
.spec span{font-size:1rem;color:var(--ink);line-height:1.55}
/* Open in Maps sits beside the address it points to, rather than as a
   separate call to action under the whole Getting there list. Wraps below
   the address text on narrow phones where there is not room for both. */
.addr{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.addr span{flex:1 1 220px}

/* ============================================================
   Seen on. White cards for the same reason the sponsor row uses
   them: these are the outlets' real supplied logo files, a mix
   of black-on-transparent SVGs and one flat JPG, and white is
   the only backdrop that keeps all four clean without showing
   a box around the JPG.

   Left in full colour, unlike the greyscale sponsor row below,
   so the two logo rows on this page read as different claims.
   These are outlets that covered the event. Those are partners
   who back it.

   The four logos have wildly different aspect ratios, from a
   792x77 masthead to a 170x152 square, so each is capped
   individually. A shared max-height would render the masthead
   as a hairline and the square one as a postage stamp.
   ============================================================ */
.press{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;align-items:center}
.press__item{
  background:#fff;border:1px solid var(--line);border-radius:var(--r);
  min-height:112px;display:flex;align-items:center;justify-content:center;
  padding:18px 16px;overflow:hidden
}
.press__item img{max-width:100%;width:auto;object-fit:contain}
.press__item img.wide{max-height:30px}   /* CTV News, 151x24 */
.press__item img.mast{max-height:38px}   /* The Leader-Post masthead, 792x77 */
.press__item img.stack{max-height:54px}  /* Global News, 107x61 */
.press__item img.sq{max-height:82px}     /* Big Dog 92.7, 170x152 */

/* ---------- sponsors ---------- */
/* stretch, not center. Centring sized each card to its own logo, so the one
   with a taller image stood taller than its neighbours in the same row.
   Stretching makes every card in a row match whatever the tallest needs,
   which is the behaviour a row of partner cards should have whatever MRKT
   sends next. */
.sponsors{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;align-items:stretch;margin-bottom:30px}
/* The 30px above is there to clear the Become a Sponsor button, which came out
   2026-08-20 at the client's request. Left on its own it lands on top of the
   section's own 64px and makes #sponsors 30px taller than every other section
   on this page, which is the same trap the footer's 130px left behind when the
   mobile action bar went. Zeroed only when the grid really is the last thing in
   its wrap, so restoring the button restores the gap with no edit here. */
.sponsors:last-child{margin-bottom:0}
/* white card, not paper, because supplied logos are flat JPGs on white and
   any other backdrop shows their box */
.sponsor{
  background:#fff;border:1px solid var(--line);border-radius:var(--r);
  min-height:146px;display:flex;align-items:center;justify-content:center;padding:20px 18px;overflow:hidden
}
.sponsor img{max-height:62px;width:auto;object-fit:contain;filter:grayscale(1);opacity:.8;transition:filter .22s ease,opacity .22s ease}
/* Country Thunder only. The supplied PNG was 600x600 with the logo occupying
   just 590x318 of it, so 282px of the file was transparent nothing. That dead
   space did both of the things it was blamed for: it forced the card taller
   than its neighbours, and it made the mark look small because a 118px box was
   rendering a 62px logo.

   The file is cropped to its content now, so this is no longer compensating
   for padding, it is a deliberate size. 78px against the others' 62px, because
   the cropped mark is a compact roundel while Special Event Rentals is a
   5:1 wordmark: matched by height they read as very different weights, and
   this brings them closer to matching by presence. It stays under the card's
   128px min-height, so the card does not grow. The original file is kept as
   sponsor-country-thunder-uncropped.png. */
.sponsor img.tall{max-height:100px}
.sponsor:hover img{filter:grayscale(0);opacity:1}

/* ---------- mailing list ---------- */
.signup{max-width:520px}
.signup__form{display:flex;flex-direction:column;gap:10px;margin-top:22px}
.signup input{
  min-height:52px;padding:14px 16px;font:inherit;font-size:1rem;
  border:1.5px solid var(--line);border-radius:var(--r);background:var(--paper);color:var(--ink);width:100%
}
.signup input:focus{outline:none;border-color:var(--ink)}
.signup .btn{width:100%}
.signup__ok{margin-top:14px;font-size:.88rem;font-weight:700;color:var(--blue)}
.signup__ok[hidden]{display:none}
/* Error wording reuses the same element, so only the colour changes. */
.signup__ok.is-err{color:var(--ink)}
/* Honeypot. Off-screen rather than display:none, because some bots skip
   fields they can tell are not rendered. */
.signup__hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.signup__form[data-busy] .btn{opacity:.6;pointer-events:none}

/* ---------- footer ---------- */
/* 56px both ends. It carried 130px at the bottom to clear the fixed mobile
   action bar, which was removed 2026-08-19; without that the footer sat on a
   slab of empty ink on every phone. */
.ftr{background:var(--ink);color:var(--paper);padding:56px 0}
.ftr__top{display:grid;gap:34px;margin-bottom:38px}
.ftr img.mark{width:118px;margin-bottom:16px}
.ftr h4{font-size:.72rem;letter-spacing:.18em;color:#A9A39B;margin-bottom:14px}
.ftr ul{list-style:none;margin:0;padding:0}
.ftr li{margin-bottom:4px}
.ftr a{
  display:inline-flex;align-items:center;min-height:40px;text-decoration:none;
  color:#E0DBD4;font-size:.92rem;border-bottom:1px solid transparent
}
.ftr a:hover{color:var(--paper);border-bottom-color:var(--paper)}
.ftr__base{
  border-top:1px solid rgba(250,248,245,.16);padding-top:24px;
  display:flex;flex-direction:column;gap:8px;font-size:.76rem;color:#8F8A83
}

/* The brand column is a flex column purely so the social marks can be pushed
   to its foot with margin-top:auto at the breakpoint below. */
.ftr__brand{display:flex;flex-direction:column;align-items:flex-start}

/* Social marks, left edge of the brand column.

   Inline SVG rather than image files: nothing extra to load, they stay sharp
   at any size, and taking currentColor means they inherit the footer's own
   colour and hover instead of needing a second set of rules.

   The 44px box is the tap target and the glyph inside it is 20px, which is why
   the box looks bigger than the mark. The negative left margin pulls that
   padding back out so the first glyph optically lines up with the text under
   it rather than sitting a dozen pixels inboard of everything else.

   border-bottom is reset because .ftr a draws an underline on hover, which
   under an icon reads as a rendering fault rather than a hover state. */
/* Written as .ftr .ftr__social, not .ftr__social. `.ftr ul` sets margin:0 and
   outranks a lone class, so the negative margin below was silently dropped and
   the glyphs sat 12px inboard of everything above them. */
/* The 30px bottom margin is PHONE ONLY and it is the gap under the marks,
   added 2026-08-21 at Ty's request. Below 600px the footer is one column, so
   the marks are the last thing in the brand block and "Visit" is the next
   heading straight under them; 2px plus the grid's own 34px row gap put them
   36px apart, which read as cramped. It is 64px now.

   IT DOES NOT REACH THE DESKTOP, and not by a media query of its own: the
   600px block further down already rewrites this margin to -2px for the
   column alignment, so raising the base value here cannot touch any width
   where the footer has columns. Base styles are the phone styles on this site,
   which is why there is no max-width query. */
.ftr .ftr__social{display:flex;gap:2px;margin:0 0 30px -12px;padding:0;list-style:none}
.ftr__social a{
  display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;min-height:44px;
  color:#B9B3AB;border-bottom:0;transition:color .18s ease
}
.ftr__social a:hover{color:var(--paper);border-bottom-color:transparent}
.ftr__social svg{width:20px;height:20px;fill:currentColor;display:block}

/* ---------- motion ----------
   The hidden state is gated behind .js, set by an inline script in <head>.
   Without it, a JS failure or a static render would strand every section
   at opacity 0, which is exactly what a mockup must never do. */
.js .rise{opacity:0;transform:translateY(22px);transition:opacity .7s ease,transform .7s cubic-bezier(.22,1,.36,1)}
.js .rise.in{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important}
  .rise{opacity:1;transform:none}
}

/* ---------- FAQ accordion ---------- */
/* Ported from the Utopia Labs site, which is the reference for this control,
   and retokenised rather than copied: their --rule, --muted and --radius
   become this site's --line, --ink-2 and --r, so it inherits the near-square
   2px corners everything else here has instead of Utopia's softer radius.

   A <button> per question, not a <details>/<summary> pair. Native details
   cannot animate its own height, and the whole point of the Utopia control is
   that the panel slides rather than snapping. The trade is that the open and
   closed state has to be announced with aria-expanded by hand, which
   site.js does.

   One open at a time, also from Utopia. On a short list it keeps the block
   from growing tall enough to push the next section off screen while someone
   is reading. */
.faq{display:flex;flex-direction:column;gap:8px}
.faq__item{
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r);overflow:hidden
}
.faq__q{
  width:100%;text-align:left;
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  /* 20px vertical against Utopia's 24 because this site's body face is a step
     smaller, and the tap target is still well over 48px either way. */
  padding:20px 22px;margin:0;
  font-family:inherit;font-size:1rem;font-weight:700;letter-spacing:-.01em;
  line-height:1.3;color:var(--ink);
  background:none;border:0;cursor:pointer;
  -webkit-tap-highlight-color:transparent;
  transition:background .2s ease
}
/* hover only where there is a real pointer, so a phone does not leave the last
   tapped question stuck in its hover colour */
@media (hover:hover){.faq__q:hover{background:var(--paper-2)}}
.faq__q:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}
.faq__chev{flex-shrink:0;color:var(--grey);transition:transform .3s cubic-bezier(.4,0,.2,1)}
.faq__item.is-open .faq__chev{transform:rotate(180deg)}
/* height rather than max-height, because a max-height large enough for the
   longest answer makes every short one animate at the wrong speed. site.js
   sets the pixel height from scrollHeight. */
.faq__body{height:0;overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}
.faq__a{font-size:.94rem;line-height:1.6;color:var(--ink-2);padding:0 22px 20px;margin:0}
.faq__a a{color:var(--blue);text-underline-offset:3px}

/* ---------- /application-received ---------- */
/* Where Dubsado sends a vendor after they submit. Dubsado's own confirmation
   screen cannot be styled: it injects the form's custom CSS inside the form
   component, so the stylesheet is destroyed when the form unmounts and the
   thank-you renders. Verified by counting style tags before and after a real
   submission, 12 down to 7 with ours among the five that went. Hence a real
   page here instead.

   min-height rather than a fixed one, and 62vh rather than 100, so the block
   is centred in a screenful on a laptop but a phone in landscape still gets a
   page it can scroll rather than content pinned off the bottom. */
.received{display:flex;align-items:center;min-height:62vh;padding-block:64px 80px}
.received__in{text-align:center;max-width:52ch;margin-inline:auto}
.received__tick{width:78px;height:78px;display:block;margin:0 auto 30px}
.received h1{font-size:var(--step-3);margin-bottom:20px}
.received__lede{font-size:var(--step-1);line-height:1.4;color:var(--ink);margin-bottom:16px;font-weight:500}
.received__note{color:var(--ink-2);margin-bottom:34px}
.received__btns{display:flex;flex-direction:column;gap:12px}
.received__btns .btn{width:100%}

/* ---------- /apply, the embedded application ---------- */
/* The form is a Dubsado iframe, styled at their end with custom CSS to match
   this site. See the README.

   No .rise on this section, deliberately. Everything else on the site fades
   up on scroll, but this is the only thing on the page and the reader came
   here to fill it in, so it is there the instant the page paints.

   Padding is light at the top because the form brings its own 60px of it. */
.apply-embed{padding-block:8px 40px}
/* The one thing said before the form starts. It exists because the Dubsado
   photo block is field 16 of 21, so a vendor types about fourteen fields
   before being told they need something that may not be on their phone, and
   because the uploader may not be settable to Required: if it is not, the
   failure is not a blocked submit, it is a rejection weeks later.

   A note, not a heading. The form carries its own title and intro directly
   below, and a heading here would print a second one above it.

   Ink on paper2 rather than grey, unlike the fallback below, because that one
   is for the few people the iframe fails and this one is for everybody. It is
   a left border rather than a box, so it reads as an aside to the form instead
   of competing with the form's own fields. */
.apply-embed__prep{
  margin:0 0 26px;padding:14px 18px;
  background:var(--paper-2);border-left:3px solid var(--blue);
  font-size:.94rem;line-height:1.55;color:var(--ink-2)
}
.apply-embed__prep b{font-weight:800}
.apply-embed iframe{display:block;width:100%;border:0;background:var(--paper)}
/* The escape hatch under the frame. Quiet, because it only matters to the
   handful of people the iframe fails for, and it must not compete with the
   form's own submit button. */
.apply-embed__fallback{
  margin-top:28px;text-align:center;
  font-size:.86rem;color:var(--grey)
}
.apply-embed__fallback a{color:var(--blue);text-underline-offset:3px}

/* Below 900px .apply__grid is not yet a grid (see the min-width:900px
   override), so "Getting there" and "On the day" stack as plain blocks with
   no breathing room between them. Reset to 0 once the grid kicks in, where
   the two columns sit side by side and this margin would throw off their
   top alignment. */
.apply__grid > .rise + .rise{margin-top:40px}

/* ============================================================
   Desktop layers up from here. min-width only.
   ============================================================ */
@media (min-width:600px){
  /* Confirmation: the tick grows, and the two buttons sit side by side and
     stop being full width, so they read as a choice rather than a stack of
     instructions. */
  .received__tick{width:96px;height:96px;margin-bottom:36px}
  .received__btns{flex-direction:row;justify-content:center}
  .received__btns .btn{width:auto}

  .events{grid-template-columns:repeat(2,1fr)}
  /* One line per fact, truncated with an ellipsis rather than wrapping.

     A long venue name is what makes this necessary. "Bunge International Trade
     Centre" wraps to two lines in a four-column row and adds 23px to that card
     alone, so one card in the row stands taller than the other three for a
     reason that is nothing to do with the market. Measured before and after
     rather than eyeballed: at 1024px the YQR card was 334px against its
     neighbours' 311.

     Scoped from 600px up, which is exactly where cards start sharing a row and
     a height difference becomes visible. Below it the grid is one column, every
     card is its own row, so wrapping costs nothing and truncating would only
     hide a venue name for no gain. The rule is written against any long value
     rather than tuned to today's four, because MRKT supplies these and the next
     venue could be longer again.

     The full text stays in the DOM, so screen readers read the whole name and
     only the visual line is cut. The venue lines also carry a title attribute
     so a hover gives the full string back on the pointer devices this
     breakpoint implies. */
  .ev__facts li{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .vendors{grid-template-columns:repeat(3,1fr)}
  .sponsors{grid-template-columns:repeat(3,1fr)}
  .hero__btns{flex-direction:row}
  .hero__btns .btn{width:auto}
  .signup__form{flex-direction:row}
  .signup .btn{width:auto;flex-shrink:0}
  /* Room for the labels at full size once the cells are 150px rather than 87. */
  .cdown__lab{font-size:.72rem;letter-spacing:.18em}
  /* The strip breaks back into four separate cards. From here a cell is wide
     enough that a plate sized to its own digits looks deliberate rather than
     stranded. See the note on .flip for why a phone gets the joined version.

     THE FIRST AND LAST SELECTORS ARE NOT DECORATION. `.cdown .flip` is two
     classes and `.cdown__u:first-child .flip` is two classes and a
     pseudo-class, so the end-of-strip rules outranked this one and survived
     into the desktop layout: the first card kept square corners on its right
     and the last kept square corners on its left, on four cards standing 120px
     apart with no strip to be the end of. Naming them here is what beats them,
     and it has to be by name rather than by order. */
  .cdown .flip,
  .cdown__u:first-child .flip,
  .cdown__u:last-child .flip{
    display:inline-block;
    border-radius:8px;border-left:0;
    font-size:clamp(2.1rem,8.5vw,6rem);
    padding:.14em .22em
  }
  /* Equal columns AND equal heights, so the featured card wins on treatment
     rather than on being physically bigger, which would read as a layout
     accident. Left to size themselves the two differ, because only Pre-sale
     carries an on-sale line. */
    .upnext{grid-template-columns:repeat(2,1fr)}
  .booths{grid-template-columns:repeat(3,1fr)}
  .ftr__top{grid-template-columns:2fr 1fr 1fr}
  .ftr__base{flex-direction:row;justify-content:space-between}
  /* Pushes the marks to the foot of the brand column. Grid rows stretch, so
     the column is as tall as the tallest one beside it, which puts the marks
     level with the last link in the Visit list rather than at a hardcoded
     offset. Add or remove a footer link and they follow it.

     The -2px bottom is not a nudge by eye. Bottom-aligning to the column edge
     leaves the glyph centre 4px above the centre of "Mailing list", because
     `.ftr li` carries a 4px bottom margin that the last item in that list
     still contributes. This cancels it, and there is a check pinning the two
     centres to within a pixel. */
  .ftr .ftr__social{margin-top:auto;margin-bottom:-2px}
}

@media (min-width:900px){
  :root{--pad:32px}
  .sec{padding:96px 0}
  /* Tracks .sec, which changes here. Above and below stay equal. See
     .sec.sec--flush. */
  .sec.sec--flush{padding-top:56px}
  .sec.sec--flush .sec__head{margin-bottom:56px}
  .hero__in{padding-block:120px 96px}
  .hero__lede{max-width:36ch}
  /* Tracks the padding-bottom directly above. See the note by the base rule. */
  .hero--balanced .hero__btns{margin-top:96px}
  .nav{display:flex;align-items:center;gap:30px}
  .nav a{
    text-decoration:none;font-size:.76rem;font-weight:700;text-transform:uppercase;
    letter-spacing:.14em;padding:6px 0;border-bottom:1.5px solid transparent
  }
  .nav a:hover{border-bottom-color:var(--ink)}
  .hdr__cta{display:inline-flex}
  .burger{display:none}
  /* THE CALENDAR'S FOUR-ACROSS RULE USED TO LIVE HERE AND HAS MOVED TO ITS OWN
     1100px BLOCK at the foot of this file. Between 900 and 1099 four columns
     are too tight for the content: it wrapped "Bunge International Trade
     Centre" onto a second line, and later a longer market name onto a third,
     which made single cards taller than their neighbours in a row that hangs
     from a shared bottom edge. See that block for the measurements. Do not put
     it back here. */
  /* Three "also coming up" cards, same reason. Between 600 and 899 they sit
     two-up with one below, which is accepted: it is a deliberately quiet
     section at the foot of the page. */
  .upnext{grid-template-columns:repeat(3,1fr)}
  /* The asymmetry. The label takes a narrow left column and the statement
     takes the rest, so the two share a left edge and the block has a spine.
     Below this width they stack, which is the same relationship read top to
     bottom rather than left to right. */
  .cols{grid-template-columns:repeat(3,1fr);gap:44px}
  .vendors{grid-template-columns:repeat(5,1fr)}
  .sponsors{grid-template-columns:repeat(3,1fr);gap:20px}
  .apply__grid{display:grid;grid-template-columns:1fr 1fr;gap:60px;align-items:start}
  .apply__grid > .rise + .rise{margin-top:0}
  /* Prose slightly wider than the photograph, so the text column is the thing
     the eye lands on and the image supports it rather than competing. start,
     not stretch, because a stretched figure would force the photo to the
     height of a paragraph block that changes whenever the copy does. */
  .story{grid-template-columns:1.08fr .92fr;gap:56px;align-items:start}
  /* Back to source order once there are two columns. See the note by
     .story .photo: the phone lifts the image above the prose, the desktop
     wants it beside it on the right. */
  .story .photo{order:0}
  .sec__head--split{display:flex;align-items:flex-end;justify-content:space-between;gap:40px;max-width:none}
  .sec__head--split h2{max-width:16ch}
  .sec__head--split p{max-width:40ch;margin-bottom:0}
}

/* ============================================================
   Audience fork. Two tap targets, no descriptions, no nested
   buttons, because the whole card is the link and a card with
   two buttons inside it makes the reader choose twice.
   ============================================================ */
.fork{display:grid;gap:12px}
.fork__card{
  /* --r-card, not --r: rounder than the site default so these read as
     tappable objects. overflow:hidden clips the photo to match. */
  position:relative;isolation:isolate;overflow:hidden;border-radius:var(--r-card);
  min-height:150px;display:flex;flex-direction:column;justify-content:flex-end;
  padding:22px 20px;color:var(--paper);text-decoration:none;
  transition:transform .2s ease
}
.fork__card img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-2}
.fork__card::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(26,25,23,.42) 0%,rgba(26,25,23,.72) 55%,rgba(26,25,23,.93) 100%);
  transition:background .25s ease
}
.fork__card:hover{transform:translateY(-3px)}
.fork__card:hover::after{background:linear-gradient(180deg,rgba(43,86,114,.5) 0%,rgba(26,25,23,.78) 55%,rgba(26,25,23,.95) 100%)}
.fork__card:focus-visible{outline:3px solid var(--blue);outline-offset:3px}
.fork__tag{font-size:.66rem;font-weight:800;text-transform:uppercase;letter-spacing:.2em;color:#C9C3BA;margin-bottom:6px}
.fork__card h3{font-size:var(--step-2);display:flex;align-items:center;gap:.4em}
.fork__card h3::after{content:"\2192";font-weight:400;transition:transform .2s ease}
.fork__card:hover h3::after{transform:translateX(5px)}

/* ---------- booth availability ---------- */
.avail{display:grid;gap:14px;margin-bottom:18px}
/* HOW THE WAITLIST WORKS, under the four cards. Added 2026-08-21, because two
   cards say "join the waitlist" and nothing on the page said how.

   `#C9C3BA` rather than the flatter `#A9A39B`, matching `.availcard__when`
   above it. This section is ink and the darker grey is for eyebrows, which sit
   at 12px and uppercase where a lighter weight reads as faint rather than
   quiet. Running prose at this size needs the step up.

   Left aligned with the cards and capped at a real measure, because a single
   line stretched across a 1240px section reads as a stray fragment rather than
   a note attached to the thing above it. */
.avail__note{
  max-width:56ch;margin:0;
  font-size:.85rem;color:#C9C3BA
}
.availcard{border:1px solid rgba(250,248,245,.22);border-radius:var(--r);padding:22px 20px}
.availcard__top{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:6px}
.availcard h3{font-size:1.02rem}
.availcard__city{font-size:.64rem;font-weight:700;text-transform:uppercase;letter-spacing:.18em;color:#A9A39B}
.availcard__when{font-size:.85rem;color:#C9C3BA;margin-bottom:0}
/* Availability badge, injected by script only when a valid number is set.
   The low state is the loudest thing on the card by design, because it is the
   only state that is time-sensitive. Applications open is deliberately quiet:
   it is a status, not a prompt. */
.availcard__status{
  margin:16px 0 0;display:inline-flex;align-items:center;
  padding:9px 15px;border-radius:100px;
  font-size:.66rem;font-weight:800;text-transform:uppercase;letter-spacing:.14em;
  border:1.5px solid rgba(250,248,245,.34);color:#C9C3BA
}
.availcard__status--low{background:var(--paper);border-color:var(--paper);color:var(--ink)}
.availcard__status--out{border-color:rgba(250,248,245,.2);color:#A9A39B}

/* ---------- vendor testimonials: the marquee ----------
   Replaced the old .reviews grid on 2026-08-20. Hand-ported from a React +
   Tailwind + framer-motion component (21st.dev "testimonial-v2") that could
   not be installed here, because this site has no React, no Tailwind, no
   bundler and no package.json, and being zero dependency is the premise of the
   build. See the comment above #reviews in vendors.html.

   NO JAVASCRIPT. The roll is one CSS animation on the list and each column
   renders its cards twice, so the second set is already in place when the
   first has travelled its own height. translateY(-50%) is exactly one set,
   whatever the cards measure, which is why nothing here has a pixel value in
   it and why the loop cannot be knocked out of step by a longer quote.

   THE CARD IS A DELIBERATE EXCEPTION TO THE SITE'S OWN LANGUAGE, at Ty's call:
   white, 24px and shadowed, where everything else is paper, 14px or 2px, and
   flat. It is the only place on this site that does this. Do not "fix" it into
   line with the rest without asking, and do not copy it outward either. */
/* THE SPACING NUMBERS ARE ABSOLUTE, NOT INCREMENTS, and that is deliberate.
   `.sec__head` carries margin-bottom:34px and .tstm is its next sibling, so the
   two top margins COLLAPSE to whichever is larger. Writing "add 38px" here
   would have produced a 38px gap rather than 72, which is the trap the room
   diagram on this page hit from the other direction. So this value IS the gap.

   The bottom margin has nothing beside it to collapse with, so it adds to the
   section's own padding and carries the paper stripe further down past the last
   card, which is what moves the boundary with the paper2 section below. Both
   asked for by Ty 2026-08-20: the heading stays where it is, the testimonials
   drop, and the background drops with them. */
.tstm{
  margin-top:56px;margin-bottom:40px;
  /* FULL BLEED ON PHONES, so the outer cards are cut off by the SCREEN rather
     than stopping inside the page's own gutter. Written against the gutter
     token rather than as `100vw` arithmetic: `width:100vw` counts the scrollbar
     and the content box does not, which is the overhang this project already
     hit on the /about team photograph. Cancelling `.wrap`'s own padding cannot
     be wrong by a pixel and tracks --pad if it ever moves. */
  margin-inline:calc(var(--pad) * -1);
  position:relative;display:flex;justify-content:center;gap:14px;
  /* THE VIEWING WINDOW. 740px was the source component's max-h-[740px] and it
     was raised on 2026-08-20 at Ty's request, to 860 on a phone and 1080 from
     900px up, then eased back the same day to 800 and 980 on his eye. Nothing else had to move: .tstm is in normal flow, so the section
     grows by exactly the same amount and the paper stripe below it keeps the
     margin it was given.

     The fade is a percentage of this box, so it scales with it: 10% of 980 is
     98px of gradient at each end against 74 at the original 740, which is why
     the taller window still reads as a window rather than as a clipped list. */
  max-height:800px;overflow:hidden;
  /* Fades the first and last card into the paper instead of slicing them, so
     the column reads as continuous rather than as a clipped box. Both spellings
     because Safari still wants the prefix. */
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 10%,#000 90%,transparent);
  mask-image:linear-gradient(to bottom,transparent,#000 10%,#000 90%,transparent)
}
/* PHONES SHOW ALL THREE COLUMNS, WITH THE OUTER TWO MOSTLY OFF SCREEN.
   Changed 2026-08-20 at Ty's request. One column is readable and centred, the
   way it has been, but the cards either side now break the edges of the screen
   instead of the row stopping dead at the gutter.

   The peek is arithmetic rather than a number picked by eye. Each column is
   `100% - 88px` and the gap is 14, so the row is three of those plus two gaps,
   which overflows its container by exactly 88px a side; centred, that leaves
   `(88 - 14) / 2` on each edge. 37px of card, at any phone width, because the
   subtraction sets it and the viewport cancels out.

   `overflow:hidden` was already on `.tstm` for the vertical clip and does the
   horizontal one for free, so nothing spills onto the page. The 320px cap is
   the source component's max-w-xs and still applies once a screen is wide
   enough to reach it. */
.tstm__col{flex:0 0 auto;width:calc(100% - 88px);max-width:320px;min-width:0}

.tstm__list{
  list-style:none;margin:0;padding:0 0 24px;
  display:flex;flex-direction:column;gap:24px;
  animation:tstm-roll var(--tstm-dur,66s) linear infinite
}
@keyframes tstm-roll{from{transform:translateY(0)}to{transform:translateY(-50%)}}
/* Pause on hover, which the source component does not do. A quote that never
   stops moving cannot be read, and reading them is the entire point of the
   section.

   SCOPED TO THE COLUMN, not the whole marquee, at Ty's request 2026-08-20. It
   was written the other way first, on the argument that holding all three
   still stops the row shearing. He is right that that is the wrong trade: the
   reader is on one card, and freezing the two columns they are not reading
   takes the life out of a section whose whole job is to look busy. Shearing is
   what a marquee does anyway. */
/* BEHIND `hover: hover`, WHICH IS NOT DEFENSIVE TIDYING. A touch does apply
   `:hover` in Chrome, and it STICKS: after the finger lifts the rule keeps
   matching until something else is touched, so the marquee stopped dead and
   stayed stopped. Measured, not assumed. The finger-hold pause is handled by
   pointer events in assets/site.js, which release properly, and this rule must
   stay out of their way.

   `:focus-within` is left unscoped. It cannot fire today, since nothing in a
   card takes focus, and if anything ever does it should pause on a touch
   device too. */
@media (hover: hover){
  .tstm__col:hover .tstm__list{animation-play-state:paused}
}
.tstm__col:focus-within .tstm__list{animation-play-state:paused}

.tstm__item{margin:0}
.tstm__card{
  background:var(--white);border:1px solid var(--line);border-radius:var(--r-lg);
  /* 30px on a phone rather than the source component's 40. The card is
     narrower here because two neighbours are sharing the row, and 40px of
     padding either side of a 302px card leaves a 222px measure, which sets
     four or five words a line. */
  padding:30px;margin:0;
  /* THE OTHER HALF OF HOLD-TO-PAUSE. Holding a finger on a block of text runs
     straight into the OS: iOS starts a selection and raises the callout menu
     after about half a second, which is the same gesture the pause uses. This
     hands the gesture to the page. The source component carried `select-none`
     on its card for the same reason, and nothing here is worth copying out of
     a moving marquee anyway. */
  -webkit-user-select:none;user-select:none;-webkit-touch-callout:none;
  box-shadow:0 10px 30px -12px rgba(26,25,23,.12),0 2px 6px -2px rgba(26,25,23,.06);
  transition:transform .28s ease,box-shadow .28s ease
}
/* Same guard, same reason. Sticky hover on touch left a tapped card floating
   above its neighbours with no way to put it back. */
@media (hover: hover){
  .tstm__card:hover{
    transform:translateY(-8px) scale(1.03);
    box-shadow:0 25px 50px -12px rgba(26,25,23,.18),0 10px 10px -5px rgba(26,25,23,.05)
  }
}
.tstm__q{margin:0;font-size:1rem;line-height:1.65;color:var(--ink-2)}
.tstm__by{display:flex;align-items:center;gap:12px;margin-top:24px}
/* The initials monogram that stands in for the photograph the source component
   wants. MRKT has no photograph of any vendor, and a stock face beside a real
   named business owner is a fabricated likeness. */
.tstm__mark{
  flex:0 0 auto;width:40px;height:40px;border-radius:50%;
  display:grid;place-items:center;
  background:rgba(43,86,114,.12);color:var(--blue-dk);
  font-size:.72rem;font-weight:800;letter-spacing:.06em
}
.tstm__who{display:flex;flex-direction:column;min-width:0}
.tstm__name{font-style:normal;font-weight:700;color:var(--ink);line-height:1.3}
.tstm__biz{font-size:.82rem;color:var(--grey);line-height:1.35;margin-top:2px}


/* ---------- photo band ----------
   Puts a photograph behind a .cta band. Layers only: everything about the type,
   the centring and the button belongs to .cta already, because a band with a
   photo behind it IS a .cta with a photo behind it, and forking the whole
   component to add two pseudo-layers is how two things that should look
   identical stop doing so.

   The image numbers are the hero's on purpose, .42 opacity under a light
   desaturation and a dark gradient, so the two photographic treatments on the
   site read as one thing rather than two. The gradient is a little heavier than
   the hero's at both ends, because this band carries a paragraph rather than a
   two-word headline and a paragraph needs more contrast than a display size
   does.

   isolation and the negative z-indexes are what keep the layers behind the
   content without the content needing a stacking context of its own. Same
   arrangement as .hero, same reason. */
/* background:var(--ink) is not decoration. The dark comes from the ::after
   gradient and the photograph, neither of which is a background-COLOUR, so
   without this the section computes as transparent: paper showing through
   under white text the moment the image fails to load, and a background check
   that reads the computed colour cannot see the band is dark at all. It found
   exactly that. */
.band{position:relative;isolation:isolate;overflow:hidden;background:var(--ink);color:var(--paper)}
.band__bg{position:absolute;inset:0;z-index:-2}
.band__bg img{width:100%;height:100%;object-fit:cover;opacity:.42;filter:grayscale(.35)}
.band::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(26,25,23,.84) 0%,rgba(26,25,23,.74) 50%,rgba(26,25,23,.9) 100%)
}

/* The light twin. MRKT run their ticket ask over a bright photograph with dark
   type, which is the inverse of their Join us band, and that inversion is the
   only reason two photo bands can sit next to each other here without the foot
   of the page becoming one long dark region.

   The image runs at FULL opacity under a paper wash, rather than at reduced
   opacity over paper the way .band does. The photograph is already black and
   white, and dimming an already desaturated crowd turns it into grey mush; the
   wash on top does the contrast work instead and leaves the faces intact. */
/* Text over a photograph needs more contrast than text over flat ink, because
   the local background moves under the line and the darkest part of the image
   is not where the paragraph happens to sit. Both of these are a step lighter
   than the .sec--ink values they override.

   Placement matters and has bitten twice in this file already: these tie with
   `.sec--ink .eyebrow` and `.sec--ink .sec__head p` on specificity, so they
   only win because the band block sits AFTER the sections block. Move either
   block and they stop working silently. Two checks pin the computed values. */
.band .eyebrow{color:#C9C3BA}
/* :not(.eyebrow) is not defensive tidiness, it is the fix for a bug this rule
   shipped with. .eyebrow is a <p>, so `.sec__head p` outranks `.eyebrow` on
   specificity and swallows it. THIRD time this exact trap has caught something
   in this file today, so state the rule plainly: any selector ending in a bare
   `p` inside a heading block also selects the eyebrow, and the eyebrow's own
   class cannot win against it.

   NOTE, NOT FIXED HERE: `.sec--ink .sec__head p` at line 548 has the identical
   problem and predates all of this, so the eyebrow on /autumn-edit's ink intro
   renders #C9C3BA rather than the #A9A39B its own rule asks for. Cosmetic, on
   another page, and not this change's business. Worth doing on its own. */
.band .sec__head p:not(.eyebrow){color:#E6E1DA}

/* A SOFTER WASH, so the photograph actually reads. Added 2026-08-21 after Ty
   said the inverted ticket band on home was so dark you could not see the
   crowd behind it. Home's band only: /about's Join us keeps the original,
   because that one sits under a heading block rather than over a single line
   and its picture is a stock florist's bench nobody needs to see.

   TWO LEVERS AND THE IMAGE IS THE BIGGER ONE. `.band` runs its picture at 42%
   under a wash of .84/.74/.9; at that pairing a mid-grey crowd shot lands
   around rgb(40) and reads as texture rather than as people. This is 58% under
   .74/.64/.8, which is a real lift on both rather than a nudge on one.

   THE EYEBROW HAD TO BE FIXED FOR IT, and the fix is a bug this file already
   documents. `.band .eyebrow` asks for #C9C3BA, a step lighter than flat ink
   because type over a photograph needs it, and `.cta .eyebrow` at #A9A39B ties
   with it on specificity and wins purely by sitting later in the file. So the
   band that most needs the lighter grey was rendering the darker one. Restated
   here at a weight that cannot lose. At the old wash it did not matter; at this
   one it is the difference between 4.3:1 and 5.1:1 against the worst case, and
   4.5 is the floor for small text.

   THE EYEBROW RULE THAT WAS HERE IS GONE, 2026-08-21, because the eyebrow went
   with it: home's band lost the word "TICKETS" above a heading reading "Get
   your ticket". It is recorded rather than deleted, because an eyebrow
   returning to this band without it renders the darker of two greys over a
   photograph:

     .band.band--soft .eyebrow{color:#C9C3BA}

   TWO CLASSES ON `.band`, AND THAT IS THE POINT. The first version was a single
   `.band--soft .eyebrow`, which ties with `.cta .eyebrow` on specificity and
   loses on source order because `.cta` sits further down this file, so the rule
   did nothing at all and the contrast check caught it. Three classes beat two
   wherever the blocks end up, the same fix `.sec.sec--flush` took. Position is
   not load-bearing.

   The suite computes that worst case from these three values rather than from
   this photograph, so this block cannot be lightened further without failing.
   See the ticket band checks in verify-bg.js. */
/* THE HEIGHT IS A RAMP WITH AN EARLY CEILING. `min(60vw, 460px)`.

   Two asks, a day apart, and the second narrowed the first. The band was 336px
   and read as a letterbox, so it went to `min(52vw, 640px)`. That grew until
   about 1230px, and on a wide screen 640px of ticket band is more room than the
   ask needs. So the ramp is steeper and the ceiling is much lower: it grows
   with the window up to **767px** and is flat at 460 from there on.

   WHAT THAT TRADES, STATED PLAINLY. The picture is 1080x608, so a 1440px-wide
   band wants 811px of height to show whole. At 460 it shows 57% of it. That is
   the point rather than a regression: past the ceiling the image simply scales
   and crops top and bottom, which is what was asked for, and the section stops
   growing. A phone is unaffected either way, since a 390px-wide band shows the
   picture whole at any of these numbers.

   TO RETUNE IT: the 460 is the ceiling and the 60vw is how fast it gets there.
   `460 / 0.6` is the width where it stops growing, so lowering the ceiling or
   raising the rate both cap it earlier.

   FLEX, BECAUSE MIN-HEIGHT ALONE LEAVES THE TEXT AT THE TOP. `.cta`'s padding
   centres the content only while the content is what sets the height. Once a
   min-height exceeds it the extra space all falls below, so the block is
   centred explicitly. The check on that measures the two gaps and requires them
   to match, so it holds at any height.

   Home's band only. `/about` runs the same photograph in its light twin and is
   deliberately untouched. */
.band--soft{
  min-height:min(60vw,460px);
  display:flex;flex-direction:column;justify-content:center;
  /* Square at every width. See the note above for why the corners came off. */
}
/* NO LIFT ON THIS BAND, AND NO ROUNDED CORNERS. Both were built on 2026-08-21
   and both came out the same day, which is worth a paragraph rather than a
   silent deletion.

   THE LIFT was `.rise` on the section plus a shadow growing from nothing as the
   card settled. Two things killed it. The content ended up travelling 34px
   rather than 12, because the inner `.wrap` already carried its own `.rise` and
   nested transforms add. And it made this the only one of home's eleven
   sections whose section element animated at all; every other one reveals its
   inner content only. The site has one motion language across eleven blocks and
   a second one here was inconsistency rather than polish.

   THE CORNERS went with it. They read as a card at 390px and as a full-bleed
   section with its corners chipped off at 1920, so they were scoped to phones
   first and then dropped entirely.

   The band still reveals: the `.wrap` inside it carries `.rise` like every
   other section on the site. What is gone is the second, different motion on
   top of that.

   `--r-lg` stays in :root, still used by the testimonial cards on /vendors.
   Both absences are pinned in verify-bg.js. */
.band--soft .band__bg img{opacity:.58}
.band--soft::after{
  background:linear-gradient(180deg,rgba(26,25,23,.74) 0%,rgba(26,25,23,.64) 50%,rgba(26,25,23,.8) 100%)
}

.band--light{background:var(--paper);color:var(--ink)}
.band--light .band__bg img{opacity:1;filter:grayscale(1)}
/* Reshaped 2026-08-19, Ty asked for a less bright wash so more of the crowd
   shows through. The naive version, dropping all three stops evenly, breaks
   the eyebrow: at the original .62/.46/.72 it measures 4.89:1 against its own
   #2E2C29, and small text needs 4.5, so there was almost no headroom to give
   away. An even reduction to .52/.38/.62 put it at 3.98 and failed.

   So the wash is not lower, it is reshaped. The top stop goes UP to .66 and
   the midpoint moves from 45% to 65%, which keeps the wash heavy across the
   eyebrow at roughly a third down while the middle drops from .46 to .34,
   which is where most of the photograph actually is.

   Measured, not eyeballed, by rendering the band with the text hidden and
   sampling every pixel behind each element: eyebrow 4.95:1 against a 4.5
   floor, better than the original, and the heading 4.10:1 against a 3:1
   floor for large text. Both at 1280 and 390.

   If this is ever lowered further, measure the eyebrow first. It is the
   binding constraint, not the heading, and it is the one that looks fine
   right up until it does not. */
.band--light::after{
  background:linear-gradient(180deg,rgba(250,248,245,.66) 0%,rgba(250,248,245,.34) 65%,rgba(250,248,245,.58) 100%)
}

/* ---------- closing CTA band ---------- */
.cta{text-align:center;padding:64px 0}
.cta h2{font-size:var(--step-3);margin-bottom:14px}
.cta p{max-width:44ch;margin-inline:auto;margin-bottom:26px;color:#C9C3BA}
.cta .btn{width:100%;max-width:340px}

/* Standalone photo strip between the Apply for a Booth CTA and the footer on
   /vendors, requested 2026-08-26. No text runs over this one, it is the image
   itself, so the overlay only needs to be dark enough to read as intentional
   rather than a leftover unstyled image, not dark enough to carry type. */
.photo-divider{position:relative;overflow:hidden;height:200px}
.photo-divider img{width:100%;height:100%;object-fit:cover;object-position:center;filter:grayscale(.35)}
.photo-divider::after{content:"";position:absolute;inset:0;background:rgba(26,25,23,.85)}
@media (min-width:900px){
  .photo-divider{height:320px}
}

@media (min-width:600px){
  .fork{grid-template-columns:repeat(2,1fr)}
  /* Four markets, not three, as of 2026-08-14. Two-up at this width rather than
     four across, because these cards carry a date and a venue name on one line
     and quartering a 1116px row wraps "Bunge International Trade Centre" onto
     three lines. The home calendar takes four across because its cards stack
     those facts as list items instead. */
  .avail{grid-template-columns:repeat(2,1fr)}
  .press{grid-template-columns:repeat(4,1fr)}
  .cta .btn{width:auto}
}
@media (min-width:900px){
  .fork__card{min-height:230px;padding:30px 28px}
  .cta{padding:110px 0}
}

/* ============================================================
   Light-background variants. The base .steps/.incl/.booth rules
   were written for the dark ink section on the home page, so on
   a paper background they need their own colours or they vanish.
   ============================================================ */
.steps--light li{border-bottom-color:var(--line);color:var(--ink-2)}
.steps--light li::before{color:var(--blue);opacity:1}
.steps--light b{color:var(--ink)}

.incl--light{color:var(--ink-2)}
.incl--light li{border-bottom-color:var(--line)}
.incl--light li::before{color:var(--blue)}

.booths--light .booth{border-color:var(--line);background:var(--paper)}
.booths--light .booth i{color:var(--ink)}

/* ---------- the room diagram ---------- */
/* One plan view of a market floor under all three rate cards, rather than a
   diagram inside each one. Redrawn 2026-08-19 from Michelle's own sketch,
   which shows the three booth types in one room and is the reason a reader
   can see that they are three positions in the same layout rather than three
   unrelated products.

   Three colours doing three jobs: grey is another vendor, blue is one of the
   three types being named, the heavy outline is the venue wall, and anything
   blank is floor. Classes against the tokens rather than fill attributes on
   forty rects, so a palette change lands here once and the blue cannot drift
   away from the site's own blue.

   THE VIEWBOX WIDTH IS LOAD-BEARING, and max-width is not. SVG text scales
   with the viewBox, so the label size on the narrowest screen is decided by
   the ratio between the phone's column width and the viewBox width, and by
   nothing else. A 390px phone gives this diagram 310px, so against a 480 unit
   viewBox an 18 unit label renders at 11.6px. Widen the viewBox or shrink the
   font and the labels quietly become unreadable on a phone while every
   desktop screenshot still looks fine. max-width only changes the desktop
   size and cannot rescue any of it. There is a check measuring the effective
   size on a real 390px viewport.

   Written down because the first version of that check tried to prove itself
   by raising max-width, which changed nothing on a phone and stayed green. */
/* The diagram comes BEFORE the rate cards, moved there 2026-08-19. The three
   words mean nothing to a first-time vendor, so the drawing defines them and
   the cards then price them, and the reader never has to look back up the page
   to connect "corner" to $600. See The room diagram in the README for the
   argument and the measurements.

   So this is the space UNDER it, holding the cards clear: 64px on a phone and
   88px from 900px up, where the drawing is twice the size.

   PADDING RATHER THAN MARGIN, but be honest about why. While the cards came
   FIRST this was load-bearing: their own 38px bottom margin met the figure's
   top margin, adjacent siblings collapse to the larger of the two, and a 26px
   margin here vanished into that 38 and moved nothing. In the order it is in
   now the figure leads, .booths has no top margin to collapse against, and
   margin and padding behave identically. Proved by swapping it back and
   watching the check stay green.

   So it is kept as padding for a weaker reason than it was chosen for: padding
   cannot collapse, so it survives anything above or below gaining a margin
   later, and the number in this file stays the number on the screen. Both
   values are measured by the suite, because neither is readable off this file
   alone. */
.roomfig{margin:0;padding-bottom:64px}
/* SIZE AND LABEL SIZE STEP TOGETHER, and they have to. SVG text scales with
   the viewBox, so making the drawing bigger makes the words bigger by exactly
   the same factor unless the font is stepped down to cancel it. Doubling the
   cap alone would have given 36px labels on a desktop.

   The geometry never changes. Only the cap and the font do, and they are
   tuned as a pair so the effective label size stays in a 13 to 19px band at
   every width. There is a check measuring it at 390, 700 and 1440.

     width      cap    font    scale        renders at
     390        480    18      0.73         13.1px
     599        480    18      1.00         18.0px
     600        660    13      1.17         15.2px
     899        660    13      1.38         17.9px
     900        960    9.5     1.79         17.0px
     1220+      960    9.5     2.00         19.0px */
.room{display:block;width:100%;max-width:480px;height:auto;margin:0 auto}
/* White, not paper-2. This diagram sits on a paper2 section, so a paper2
   booth is the same colour as the floor behind it and only its hairline
   shows. White separates booth from floor at the one contrast step this
   palette has spare, and matches the press and sponsor cards, which are
   white for the same reason. */
.room__b{fill:var(--white);stroke:var(--line);stroke-width:1.2}
.room__you{fill:var(--blue);stroke:var(--blue);stroke-width:1.2}
.room__wall{fill:none;stroke:var(--ink);stroke-width:5}
/* Every leader is drawn twice, a fat paper line under a thin ink one, because
   two of the three cross a row of booths on their way in. The arrowheads get
   the same halo from paint-order, which draws their stroke beneath their fill
   rather than over it. */
.room__leadbg{stroke:var(--paper);stroke-width:6;stroke-linecap:round}
.room__lead{stroke:var(--ink);stroke-width:1.6;stroke-linecap:round}
.room__head{fill:var(--ink);stroke:var(--paper);stroke-width:3.5;paint-order:stroke}
.room__lab{
  fill:var(--ink);font-family:'Raleway',system-ui,sans-serif;
  font-size:18px;font-weight:800;letter-spacing:.08em
}
.roomfig figcaption{margin-top:14px;text-align:center;color:var(--grey)}

/* the closing CTA band reuses .sec--ink for colour but needs its own rhythm */
/* 26px, not the eyebrow's usual 14. That gap was never chosen: as a <p> this
   eyebrow picked up `.cta p`'s margin-bottom, the rule written for the
   paragraph beside it. Written down rather than inherited by accident, and
   kept at 26 rather than tightened, because the point of this change was that
   nothing on the page moves. Tightening it to 14 to match every other eyebrow
   is a fair thing to want and is a separate decision. */
.cta .eyebrow{color:#A9A39B;font-size:var(--step--1);margin-bottom:26px}

/* A .cta on paper rather than ink. Exists because /about now has two closing
   bands in a row, Join us over a photograph and then the ticket ask, and two
   dark bands touching is the thing the alternating background rule exists to
   prevent. No background of its own: the body's paper shows through, which is
   what keeps it reading as paper to the background check as well as to the eye.
   Same shape as .steps--light and .incl--light, which exist for the same
   reason. */
.cta--light .eyebrow{color:var(--eyebrow)}
.cta--light h2{color:var(--ink)}
.cta--light p{color:var(--ink-2)}
/* Darker again when that light CTA sits over a photograph. --grey is legible on
   an even paper field and vanishes on a busy crowd, which it did.

   Written .cta--light.band--light and sitting HERE, after .cta .eyebrow, rather
   than up in the band block: .band--light .eyebrow and .cta .eyebrow are the
   same specificity and the band block comes first in this file, so the version
   written up there lost on source order and did nothing. Second time that exact
   mistake has been made in this stylesheet today; both were caught by a check
   pinned to the computed value rather than by looking at the page. */
.cta--light.band--light .eyebrow{color:var(--ink-2)}

/* ---------- the room diagram, responsive ---------- */
/* AT THE END OF THE FILE ON PURPOSE. The base .room rules sit further up with
   the rest of the pricing block, and every media query above them would have
   tied on specificity and LOST on source order, leaving the diagram stuck at
   its phone size on every screen. That is the fifth source-order defeat in
   this stylesheet, so this one is written where it cannot happen. */
/* The leader weights step with the font for the same reason: they are drawn
   in the same user units, so leaving them alone while the label shrinks makes
   the annotation twice as heavy as the type it belongs to. Only the stroke
   widths move. The arrowhead geometry stays put, because the checks read it
   with getBBox and a CSS transform is invisible to getBBox, which would leave
   the suite measuring something other than what is drawn. */
@media (min-width:600px){
  .room{max-width:660px}
  .room__lab{font-size:13px}
  .room__lead{stroke-width:1.2}
  .room__leadbg{stroke-width:4.6}
  .room__head{stroke-width:2.6}
}
@media (min-width:900px){
  .roomfig{padding-bottom:88px}
  .room{max-width:960px}
  .room__lab{font-size:9.5px}
  .room__lead{stroke-width:.9}
  .room__leadbg{stroke-width:3.4}
  .room__head{stroke-width:1.9}
}

/* ============================================================
   THE COUNTDOWN STRIP, home only
   ============================================================
   /vendors keeps the tall band: there it is the first thing under the hero and
   the page's opening statement. On home it is supporting information between
   the hero and the fork, so it is a shallow rule across the page instead,
   104px against 370.

   AT THE END OF THE FILE, and every selector carries .stats--line plus enough
   parts to beat the rules it overrides. The plate rules further up name
   :first-child and :last-child in order to beat the corner rules, so a
   two-part selector loses to them, which is what rendered the first and last
   plates at twice the size on an earlier pass. Specificity here, not order.

   TWO LAYOUTS, and the boundary is measured. From 768px the rectangle is the
   ROW and each unit reads "37 Days" with the word beside the number. Below it
   the rectangle is the four plates butted together with the word underneath.
   Inline, the widest unit is "47 MINUTES" at about 135px; a 390px phone gives
   each cell 87px, so the words ran into the dividers and SECONDS was clipped
   off the end. 728px of wrap is the first width where 135px fits with room. */
.stats--line{
  /* --readout is local to this block on purpose. The tall band on /vendors
     uses --ink-2 for its own panel and must not move with this, so the strip
     carries its own value rather than retuning a site-wide token.

     #232220 rather than a new colour: it is the shade the flip card's lighter
     half used before the split came off, so this is the stylesheet reusing a
     step it already had. It sits between the band's ink and the old --ink-2,
     which is what was asked for, still lighter than the band but not by as
     much. */
  --readout:#232220;
  /* 6px EACH SIDE. Equal, so the readout is centred, and small, so the band is
     a skinny rule across the page rather than a slab: 64px against the 82 it
     had when the label came off.

     THE TWO ARE INDEPENDENT AND THAT IS THE WHOLE HISTORY OF THIS RULE. It went
     16/14, then 6/14, then 6/6, then 15/15, then here, because "make the top
     smaller", "centre it" and "make it skinnier" are three separate asks and
     each of the first two got read as the other. Centring is equalising the two
     numbers. Skinnier is lowering both. Doing one must not do the other, which
     is why the suite checks them apart: the gaps equal as a comparison, and the
     band height by value.

     An earlier comment here argued the 16/14 asymmetry was right, tight to the
     hero above and looser from the fork below. Recorded as an argument that was
     made, not one that won.

     Nothing else is in this band: the screen-reader sentence is absolutely
     positioned and adds no height, so equal padding really does centre the only
     thing here. */
  background:var(--ink);padding:6px 0
}
/* 700, down from the site's 800 headline weight. Lighter type on a small
   readout, at Ty's request. Scoped to the strip: the tall band on /vendors
   keeps 800, where the digits are three times the size and carry it. */
.stats--line .flip__face{font-weight:700}
.stats--line .cdown{width:100%;grid-template-columns:repeat(4,1fr);margin:0}
.stats--line .cdown__lab{font-size:.54rem;letter-spacing:.14em}
/* WHEN THE STRIP MEETS A SECTION THAT SHARES ITS BACKGROUND.
   Used on /autumn-edit, where #about below it is also ink.

   This is not a suppression of the alternating rule, it is the rule's own
   condition met a different way. Two touching sections must not read as one
   flat field, and they do not when something visible marks where one ends. On
   /about that something is a photograph flush to the section's bottom edge;
   here it is a hairline. Either way the boundary is there to see.

   The alternative, moving #about off ink, was built and measured: it clashes
   #about against #lineup and cascades a background reshuffle down the rest of
   the page, which is the failure this project has had twice. */
.stats--ruled{border-bottom:1px solid rgba(250,248,245,.14)}

/* ONE LAYOUT AT EVERY WIDTH. Each unit reads "37 Days", word beside number,
   from 320px up, and the rectangle is always the row.

   It used to split at 768: inline above, word-under-number below, because
   inline the widest unit is "47 MINUTES" and at the old sizes that ran about
   135px into an 87px cell on a phone, clipping SECONDS off the end of the bar.
   Ty asked for the phone to match the desktop and said the numbers could give
   up room to make it fit, which is what removes the split.

   THE SIZES ARE FLUID RATHER THAN STEPPED, and the vw figures are derived
   rather than picked. A cell is (100vw - 40px) / 4, so about 25vw. A unit is
   roughly 1.2 x the digit size plus the gap plus 5.3 x the label size, since
   two tabular digits run about 1.2em and a seven-letter tracked label about
   5.3em. At 5.2vw digits and 2.1vw labels that comes to about 19vw against a
   25vw cell, which leaves room at every width rather than at the one that was
   tested. The clamps stop it shrinking past legibility on a 320 phone and past
   the old desktop size on a wide screen.

   There is a check sweeping seven widths for a unit overflowing its cell,
   because this is the exact failure the split existed to avoid. */
.stats--line .cdown{
  background:var(--readout);border-radius:8px;padding:9px 0;align-items:stretch
}
.stats--line .cdown .flip,
.stats--line .cdown__u:first-child .flip,
.stats--line .cdown__u:last-child .flip{
  display:block;background:none;border-radius:0;border-left:0;padding:0;margin:0;
  font-size:clamp(1rem,5.2vw,1.9rem)
}
.stats--line .cdown__u{
  display:flex;align-items:baseline;justify-content:center;gap:clamp(3px,1.6vw,9px)
}
.stats--line .cdown__u + .cdown__u{border-left:1px solid rgba(0,0,0,.45)}
.stats--line .cdown__lab{
  display:inline;margin:0;font-size:clamp(.4rem,2.1vw,.6rem);letter-spacing:.1em
}
/* One cell on the day of the market, so nothing to divide from and no word
   beside it. */
.stats--line .cdown.is-today{grid-template-columns:1fr}
.stats--line .cdown.is-today .flip{font-size:clamp(1rem,5.2vw,1.9rem)}


/* ---------- the testimonial marquee, responsive ----------
   AT THE VERY END OF THIS FILE ON PURPOSE. `.tstm__col--2` inside a media query
   is the same specificity as the `.tstm__col--2,.tstm__col--3{display:none}`
   base rule further up, so whichever comes last wins. Written above the base
   rule these lose silently and the section stays one column on every screen.
   That is the trap this stylesheet has been caught by five times already, most
   recently by the room diagram, and the fix is position rather than
   specificity every time.

   600 and 900 are the site's own grid breakpoints. The source component used
   768 and 1024, which are Tailwind's md and lg and mean nothing here. */
/* From 600px the peek is dropped and the columns go back to whole cards. Two
   of them until 900, three above it, which is the source component's own
   arrangement. The step from three peeking columns to two full ones at 600 is a
   real discontinuity and it is deliberate: a phone has room for one card and a
   hint of its neighbours, a tablet has room for two whole ones, and half a card
   at a width where a whole one fits is just a cut-off card. */
/* A sentence given its own line once there is room for one. Used by the closing
   apply pitch on /vendors, where "Booth" otherwise sat alone on the end of the
   first line at every width from 600 to 2560.

   It has to live down here rather than beside .cta. Written up there it would
   tie with the base rules on specificity and lose on source order, which is the
   defeat this stylesheet has taken five times.

   Inline below 600px deliberately: on a phone the first sentence already wraps
   and ends on one word, so breaking there strands "markets." instead. */
@media (min-width:600px){
  .cta__line{display:block}
  .tstm{gap:24px;margin-inline:0}
  .tstm__col{flex:0 1 320px;width:auto;max-width:none}
  .tstm__card{padding:40px}
  .tstm__col--3{display:none}
}
@media (min-width:900px){
  .tstm__col--3{display:block}
  /* Absolute again, for the collapsing reason in the base rule. 72px of gap and
     56px of stripe under the last card, against .sec's own 96px, so the section
     closes 152px after the marquee ends rather than 96. */
  .tstm{margin-top:72px;margin-bottom:56px;max-height:980px}
}

/* ---------- the testimonial marquee, reduced motion ----------
   AT THE END OF THE FILE, BELOW THE RESPONSIVE BLOCK ABOVE, and that position
   is load-bearing. This lived further up until 2026-08-20, when the desktop
   step gained `max-height:1080px`. Both rules are a bare `.tstm`, so they tie
   on specificity and the later one wins: at 1440 with reduced motion on, the
   desktop clip beat this `none` and trapped 34 of the 51 quotes inside a box
   with no way to reach them. **The suite caught it**, which is the whole
   argument for pinning the fallback rather than trusting the media query.

   Reduced motion gets the honest fallback rather than a frozen marquee: no
   clip, no fade, the duplicate set hidden, and the columns simply stack as a
   plain list of 51. The roll itself is already stopped by the global
   `*{animation:none!important}` near the top of this file, so it is
   deliberately not repeated here. The hover lift is a transform rather than a
   transition, so it survives that global rule and is turned off by hand. */
@media (prefers-reduced-motion:reduce){
  .tstm{max-height:none;overflow:visible;flex-wrap:wrap;align-items:flex-start;
    margin-inline:0;-webkit-mask-image:none;mask-image:none}
  /* The phone peek is an overflow effect and there is no overflow left here, so
     a column sized to overflow would just sit inset from the page's own left
     edge for no reason. Full width, capped where the cards are capped. */
  .tstm__col{width:100%;max-width:320px}
  .tstm__item[aria-hidden="true"]{display:none}
  .tstm__card:hover{transform:none}
}

/* ---------- the testimonial marquee, reduced motion ----------
   AT THE END OF THE FILE, BELOW THE RESPONSIVE BLOCK ABOVE, and that position
   is load-bearing. This lived further up until 2026-08-20, when the desktop
   step gained `max-height:1080px`. Both rules are a bare `.tstm`, so they tie
   on specificity and the later one wins: at 1440 with reduced motion on, the
   desktop clip beat this `none` and trapped 34 of the 51 quotes inside a box
   with no way to reach them. **The suite caught it**, which is the whole
   argument for pinning the fallback rather than trusting the media query.

   Reduced motion gets the honest fallback rather than a frozen marquee: no
   clip, no fade, the duplicate set hidden, and the columns simply stack as a
   plain list of 51. The roll itself is already stopped by the global
   `*{animation:none!important}` near the top of this file, so it is
   deliberately not repeated here. The hover lift is a transform rather than a
   transition, so it survives that global rule and is turned off by hand. */
@media (prefers-reduced-motion:reduce){
  .tstm{max-height:none;overflow:visible;flex-wrap:wrap;align-items:flex-start;
    margin-inline:0;-webkit-mask-image:none;mask-image:none}
  /* The phone peek is an overflow effect and there is no overflow left here, so
     a column sized to overflow would just sit inset from the page's own left
     edge for no reason. Full width, capped where the cards are capped. */
  .tstm__col{width:100%;max-width:320px}
  .tstm__item[aria-hidden="true"]{display:none}
  .tstm__card:hover{transform:none}
}

/* ---------- the ticket band's lift: the two states it must NOT animate in ----
   ORDER MATTERS AND IS THE WHOLE REASON THESE ARE HERE RATHER THAN BESIDE THE
   RULE THEY OVERRIDE. Reduced motion first, the 900px cutoff second, so a wide
   screen with reduced motion gets the desktop answer (nothing) rather than the
   phone one (a shadow).

   Under reduced motion the phone gets the END state immediately: no lift, and
   the shadow already there. That is the honest fallback, since the alternative
   is a transition the global reduced-motion rule kills, leaving the card to
   snap into place. */
@media (prefers-reduced-motion:reduce){
  .js .band--soft.rise,
  .js .band--soft.rise.in{
    transform:none;box-shadow:0 16px 30px -20px rgba(26,25,23,.5)
  }
}
/* Phones only, both the lift and the corners.

   THE LIFT, because on a desktop the band is a full-width architectural block
   rather than a card in the hand, and a 12px hop on something 460px tall reads
   as a glitch rather than as an arrival.

   THE CORNERS, at Ty's call 2026-08-21. Same reasoning arrived at from the
   other end: a rounded 390px band reads as a card you could pick up, and a
   rounded 1920px band reads as a full-bleed section with its corners chipped
   off. The card idea does not survive the width. */
@media (min-width:900px){
  .js .band--soft.rise,
  .js .band--soft.rise.in{transform:none;box-shadow:none;transition:none}
  .band--soft{border-radius:0}
}

/* ---------- the calendar goes four across at 1100, not 900 --------------------
   MOVED HERE 2026-08-21, out of the 900px block. Four columns from 900px was
   too tight for the content at every width below about 1100, and it failed in
   two ways a screenshot makes look like a rounding error rather than a bug.

   WHAT WAS ACTUALLY WRONG. `.events` carries `align-items:end`, so the cards
   hang from a shared bottom edge. That is a good rule and it is what turns any
   single card being taller into that card visibly sticking up out of the row.
   Two separate things did that between 900 and 1099:

     - "Bunge International Trade Centre" wrapped to two lines, which is the
       bug the venue ellipsis at 600px was added for. That ellipsis is still
       there and still correct; it just stopped being the thing that fires,
       because the cards are wide enough now.
     - "The 2nd Holiday Edit YXE" set to three lines where its neighbours set
       to two, 23px taller at 900, 24 at 960, 23 at 1024, even again at 1100.
       Measured at all four before choosing this over shortening the name.

   THIS IS THE FIX THE README ALREADY NAMED. It was written up on 2026-08-18 as
   "the alternative that loses nothing, not taken", parked with the note that it
   was worth doing if MRKT ever added a fifth market or a longer venue. A longer
   market name is the same thing, so it is taken now.

   IT MUST SIT AFTER THE 600px BLOCK, not inside it and not before it. Both
   rules are a bare `.events` and tie on specificity, so at 1100 and up the
   later one wins and that is the only reason four columns happen at all. This
   stylesheet has lost on source order six times; this is the seventh place it
   could. The end of the file is where the responsive rules live for exactly
   this reason.

   WHAT IT COSTS, STATED PLAINLY. Between 900 and 1099 the calendar is now two
   rows of two rather than one row of four, so it is taller on a small laptop.
   That is the trade: a taller section against a ragged row with a truncated
   venue name in it. `align-items:end` still does its job inside each row.

   WHY 1150 AND NOT 1100, WHICH IS WHAT THIS WAS FIRST WRITTEN AS. 1100 was
   picked as a round number and it was wrong: at 1100 the titles are fine, but
   "Bunge International Trade Centre" still overflows its line by 11px and gets
   the ellipsis. Forced into four columns and measured, it overflows by 11px at
   1100, 9 at 1110, 6 at 1120, 4 at 1130, and fits whole from 1140. 1150 is
   that threshold plus a little margin, so the ellipsis at 600px goes back to
   being the safety net it was written as rather than something that fires at a
   real width. Not 1024 either way, because 1024 is exactly a common laptop
   width and a breakpoint sitting on top of one is a coin toss for those
   readers.

   MEASURED BY FORCING FOUR COLUMNS AT EACH WIDTH rather than by reading which
   rule was winning, because otherwise the probe only ever sees the layout the
   breakpoint already chose and can never tell you where to put it. */
@media (min-width:1150px){
  .events{grid-template-columns:repeat(4,1fr)}
}
/* The two admission cards sit side by side once there is room for them, and
   stretch so their feet line up. Written at the end of the file because a
   media query above the base rules ties on specificity and loses on source
   order, which this stylesheet has been caught by five times. */
@media (min-width:600px){
  .tix{grid-template-columns:1fr 1fr;gap:20px;align-items:stretch}
}

/* ============================================================
   SOLD OUT, on the two-card layout.

   This is the state that can publish something false, so it is written
   deliberately rather than left to inherit. When Michelle's sheet says Sold
   out, build.js rewrites the status span and these rules fire off it.

   The two-card layout solves most of it by itself: the Door card is a real,
   honest, still-available option sitting right there, which the single-column
   version had to work to provide. So all that is needed is to stop the
   pre-sale card offering something that does not exist.

   THE BUTTON IS REMOVED, NOT DIMMED. A greyed-out button still reads as "try
   me", and this one would lead to a checkout for a sold-out ticket.
   ============================================================ */
#details:has(.tier__status.is-out) .tix__btn{display:none}
#details:has(.tier__status.is-out) .tix__card--lead{border-color:var(--line);border-width:1px}
#details:has(.tier__status.is-out) .tix__card--lead .tix__price,
#details:has(.tier__status.is-out) .tix__card--lead .tix__name{color:var(--grey)}
#details:has(.tier__status.is-out) .tix__card--lead .tix__list li{color:var(--grey)}
#details:has(.tier__status.is-out) .tix__card--lead .tix__ic{color:var(--line)}
/* The door card becomes the live one, because it is the only thing left that
   a reader can actually act on. */
#details:has(.tier__status.is-out) .tix__card:not(.tix__card--lead){border-color:var(--blue);border-width:1.5px}
#details:has(.tier__status.is-out) .tix__card:not(.tix__card--lead) .tix__name{color:var(--blue-dk)}
#details:has(.tier__status.is-out) .tix__card:not(.tix__card--lead) .tix__price{color:var(--blue-dk)}
#details:has(.tier__status.is-out) .tix__card:not(.tix__card--lead) .tix__ic{color:var(--blue)}
#details:has(.tier__status.is-out) .sec__head p{color:var(--grey)}

/* More air between "Get your ticket" and its button, at Ty's request
   2026-08-21. It was 14px, the h2's own bottom margin, which is the spacing
   for a heading sitting above body copy rather than above the one action on
   the section.

   SCOPED TO .band--soft, WHICH IS HOME ONLY. /about's ticket band is
   deliberately a different treatment, light where this one is dark, and the
   two diverged on 2026-08-21 for a reason recorded under Structure. Changing
   both here would quietly re-couple them.

   The band stays centred: it is a flex column, so a taller content block
   re-centres itself inside the same min-height rather than dropping. */
.band--soft .btn{margin-top:18px}

/* ============================================================
   THE COUPON BOOK, /coupons
   ============================================================ */

/* The gate. One field, one button, alone on a dark section, because it is a
   door rather than a form. Everything here is scoped under #couponGate or the
   .cpn prefix.

   .cpn and not .coupon: the orphan guard in verify-bg.js carries a
   prefix-aware regex of removed component names, and a class starting "cat",
   "stat", "bar", "map", "path", "review" or "quote" trips it. .cpn collides
   with nothing. */
.cpn__gate{max-width:34rem;margin:0}
.cpn__label{
  display:block;font-size:.75rem;font-weight:700;text-transform:uppercase;
  letter-spacing:.08em;color:#C9C3BA;margin:0 0 10px
}
/* Column on a phone, row once there is width for it. The button is full width
   on a phone on purpose: it is the only action on the screen. */
.cpn__row{display:flex;flex-direction:column;gap:10px}
.cpn__input{
  font:inherit;font-size:1rem;font-weight:700;letter-spacing:.06em;
  /* The code is typed in capitals and read back against a printed ticket, so
     it is set in capitals whatever the keyboard sends. text-transform is
     display only; the value posted is untouched and the server forgives case
     anyway. */
  text-transform:uppercase;
  min-height:52px;padding:14px 16px;width:100%;
  color:var(--ink);background:var(--paper);
  border:1.5px solid transparent;border-radius:var(--r-btn)
}
.cpn__input::placeholder{color:var(--grey);font-weight:400;letter-spacing:.04em}
.cpn__input:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.cpn__gate[data-busy] .cpn__input,
.cpn__gate[data-busy] .btn{opacity:.6;pointer-events:none}

/* The message line holds "checking" as well as errors, so its resting colour
   is the section's ordinary light text and only the error state goes red. */
.cpn__msg{font-size:.94rem;line-height:1.5;color:#C9C3BA;margin:12px 0 0;min-height:1.5em}
.cpn__msg[data-err]{color:#FFB4A8}
.cpn__help{font-size:.9rem;line-height:1.6;color:#C9C3BA;margin:28px 0 0;max-width:44ch}
.cpn__help a{color:var(--paper);text-decoration:underline;text-underline-offset:3px}

/* The book. Rows are .faq__item, so they inherit the accordion's card, border,
   hover, chevron rotation and height animation from the FAQ block above.
   Everything here is the difference: a saving amount on the right of the
   question, and the inside of the answer. */
.cpn__list{margin-top:22px}
.cpn__q{gap:12px}
/* min-width:0 so a long business name wraps instead of shoving the saving off
   the edge of a 320px phone.

   overflow-wrap:anywhere as well, because min-width:0 only lets it wrap at a
   space. A name with no space in it long enough to overrun the row ran
   straight under the saving pill instead, which the screenshot caught and no
   check would have. Real business names rarely do this, but the sheet is
   typed by a third party and a wrapped name is always better than an
   unreadable one. */
.cpn__vendor{min-width:0;flex:1;overflow-wrap:anywhere}
.cpn__save{
  flex-shrink:0;font-size:.8rem;font-weight:800;letter-spacing:.04em;
  color:var(--blue-dk);background:#E8EFF4;
  padding:6px 10px;border-radius:100px;white-space:nowrap
}
.cpn__save:empty{display:none}
.cpn__a{display:flex;flex-direction:column;gap:10px}
.cpn__offer{font-size:.94rem;line-height:1.6;color:var(--ink-2);margin:0}
.cpn__terms{font-size:.82rem;line-height:1.5;color:var(--grey);margin:0}
.cpn__cat{
  font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
  color:var(--eyebrow);margin:0
}
.cpn__cat:empty{display:none}

/* Optional, one per coupon, from a plain image link on the Coupons tab.
   Ratio fixed rather than left to whatever a vendor happened to send in,
   because a book of mixed portrait and landscape photos looks like an
   accident. object-fit:cover crops to that ratio instead of squashing. */
.cpn__img{
  display:block;width:100%;aspect-ratio:16/9;object-fit:cover;
  border-radius:var(--r-card);background:var(--paper-2)
}

/* Redeeming is not built. The button is absent rather than present and
   disabled, for the same reason the sold-out ticket button is removed rather
   than greyed: a dimmed button still reads as "try me", and there is nothing
   behind this one yet. The line below says so instead. */
.cpn__soon{
  font-size:.82rem;line-height:1.5;color:var(--grey);margin:2px 0 0;
  padding-top:10px;border-top:1px solid var(--line)
}

.cpn__empty{font-size:.94rem;color:var(--ink-2);margin:22px 0 0}
.cpn__foot{font-size:.82rem;line-height:1.6;color:var(--grey);margin:26px 0 0;max-width:60ch}

@media (min-width:600px){
  .cpn__row{flex-direction:row;align-items:stretch}
  .cpn__input{flex:1}
  .cpn__row .btn{flex-shrink:0}
}

/* The photograph at the top of the coupon gate.

   A fixed aspect box with the image covering it, rather than the image's own
   4:3-ish portrait shape, because a 933x1400 portrait at full width pushes the
   code field off a phone screen entirely and the whole page is meant to be one
   field and one button.

   Taller on a phone than on a desktop: 4:3 keeps both shoppers and the tote
   wording in frame at 390px, where 16:9 crops to a band of shoulders. */
.cpn__hero{
  margin:0 0 30px;border-radius:var(--r-card);overflow:hidden;
  background:var(--ink-2);
  /* Height, not aspect-ratio, on phones too. 4/3 was tried and measured: on a
     320x568 screen it took 240px and pushed the code field off the bottom.
     A photograph is not allowed to bury the one field this page exists for,
     so it gives up height on a short screen instead. */
  height:clamp(170px,30vh,300px)
}
.cpn__hero img{
  width:100%;height:100%;display:block;
  object-fit:cover;object-position:center 46%
}
/* HEIGHT-CAPPED ON A LAPTOP, NOT ASPECT-RATIO'D.

   2/1 was tried first and measured: at 1280x900 it made the image 558px tall
   and pushed the code field to 972px, off the bottom of the screen. A page
   whose entire job is one field and one button cannot open with the field
   below the fold, however good the photograph is.

   A viewport-relative height with a floor and a ceiling keeps the image
   generous on a big screen and out of the way on a short one. */
@media (min-width:600px){
  .cpn__hero{aspect-ratio:auto;height:clamp(240px,30vh,360px)}
}

/* ON A VERY SHORT SCREEN THE PHOTOGRAPH GOES, AND THE FIELD STAYS.

   Measured at 320x568: the code field lands at 471px with no photo and 671px
   with one, against a 568px screen. The photo is unambiguously the cause, and
   it would have to shrink to about 70px, a useless sliver of somebody's coat,
   to fit. So it is dropped instead.

   Keyed on viewport HEIGHT rather than width on purpose, so a modern phone
   turned sideways gets the same treatment for the same reason.

   This is the right way round: the image is decorative and the field is the
   entire purpose of the page. */
@media (max-height:640px){
  .cpn__hero{display:none}
}

/* The coupon search box. Reuses the gate field's shape but on paper rather
   than over ink, so it reads as part of the list instead of a second door. */
/* .filters carries its own top margin for the line-up, where it sits directly
   under a section heading. Here it follows an input, so the pair needs air
   between them rather than the heading spacing above. */
.cpn__search{margin:22px 0 14px}
.cpn__searchinput{
  font:inherit;font-size:1rem;
  min-height:48px;padding:12px 16px;width:100%;
  color:var(--ink);background:var(--paper);
  border:1.5px solid var(--line);border-radius:var(--r-btn)
}
.cpn__searchinput::placeholder{color:var(--grey)}
.cpn__searchinput:focus-visible{outline:3px solid var(--blue);outline-offset:2px;border-color:var(--blue)}
/* Safari draws its own clear button; this keeps it from sitting under the
   1.5px border on the right edge. */
.cpn__searchinput::-webkit-search-cancel-button{margin-right:2px}

/* ---------- the coupon promo on home ---------- */

/* A VOUCHER, NOT A CONTENT BLOCK.

   The first version of this had a figure, a paragraph, four vendor names, a
   day count and a button, about 750px of it. Every part was defensible and
   together they read as a briefing. Home's job is to make somebody click; the
   detail belongs on the page they click to.

   Dark on a light section so it separates from the page rather than continuing
   it, and a dashed edge with two notches so it reads as a coupon at a glance.
   The notches are pseudo-elements in the section's own background colour,
   which is why this is scoped to .sec--paper2: on a different background they
   would show as two grey circles. */
.cpn__promo{
  position:relative;display:block;
  background:var(--ink);color:var(--paper);
  border-radius:var(--r-card);
  padding:34px 26px 30px;
  text-align:center;
  /* The whole card is an <a>, so every heading and paragraph inside it
     inherits the link underline. Without this the figure ships with a rule
     struck through it. */
  text-decoration:none;
  transition:transform .2s ease
}
.cpn__promo *{text-decoration:none}
/* The dashed rule, inset, so the card has a ticket's double edge. */
.cpn__promo::before{
  content:"";position:absolute;inset:9px;
  border:1.5px dashed rgba(250,248,245,.28);border-radius:calc(var(--r-card) - 5px);
  pointer-events:none
}
/* NO NOTCHES. A pair of punched circles was built here to make the card read
   as a torn ticket and was removed the same day: clipped against the card's
   own overflow they rendered as two dark blobs sitting outside it, and
   unclipped they depend on hardcoding the section's background colour, so any
   future background change leaves two grey circles behind. The dashed inner
   rule carries the voucher idea on its own. */
@media (hover:hover){
  .cpn__promo:hover{transform:translateY(-2px)}
}
.cpn__promo:focus-visible{outline:3px solid var(--blue);outline-offset:3px}

/* The eyebrow sits on ink here, so it takes the over-ink treatment the same
   way .sec--ink .eyebrow does elsewhere. */
.cpn__promo .eyebrow{color:#C9C3BA;margin-bottom:10px}

/* Under the figure since 2026-08-24, so the margin moved to the top. */
.cpn__promoh{
  font-size:var(--step-1);line-height:1.15;margin:10px 0 0;color:var(--paper)
}

/* The figure is the argument, so it is typeset as one rather than buried in a
   sentence, which is where it sat before. */
.cpn__figv{
  font-size:clamp(3rem,15vw,4.6rem);font-weight:800;letter-spacing:-.03em;
  line-height:.95;margin:0;color:var(--paper)
}
/* .cpn__figl removed 2026-08-24 with the "free with every ticket bought
   online" line it styled. Nothing uses it. */

/* A span rather than an anchor, because the whole card is already the link.
   It has to look like a button and must not behave like a second one.

   --r-btn, the same 8px the hero buttons and the calendar card buttons use,
   rather than the near-square 2px of a button sitting on a plain background.
   That token exists for exactly this case: its own comment says it is for
   buttons on photos or inside cards, and this one is inside a card. It shipped
   at 2px by inheriting .btn, which was an oversight rather than a decision.

   Written as the token, not as 8px, so if --r-btn ever moves this moves with
   the hero button instead of drifting away from it. */
.cpn__promobtn{margin-top:22px;pointer-events:none;border-radius:var(--r-btn)}

/* Plain inline text. A flex row here gave a double space, and deleting the
   whitespace to fix that made a screen reader announce "33daysleft". Real
   spaces, ordinary inline layout.

   LIGHT, BECAUSE THIS LINE LIVES ON INK. It was written for the home page,
   which is paper, and moved to the gate on /coupons, which is not. The colours
   came with it: the number shipped as var(--ink) on a var(--ink) background,
   contrast 1.0, completely invisible. The checks read textContent and were
   blind to it; a screenshot caught it. There is a contrast check now. */
.cpn__when{font-size:.94rem;line-height:1.6;color:#C9C3BA;margin:0 0 22px}
.cpn__when b{font-size:1.5rem;font-weight:800;letter-spacing:-.02em;color:var(--paper)}

/* The second door on /coupons, for the reader with no code. Separated by a
   rule rather than only by space, because it is a different question from the
   one above it, not a continuation of it. */
.cpn__second{
  margin:34px 0 0;padding:26px 0 0;border-top:1px solid rgba(255,255,255,.14);
  max-width:34rem
}
.cpn__nocode{font-size:1rem;line-height:1.5;color:var(--paper);margin:0 0 14px}
.cpn__second .btn{width:100%;text-align:center}
.cpn__help{font-size:.86rem;line-height:1.6;color:#C9C3BA;margin:16px 0 0;max-width:44ch}
.cpn__help a{color:var(--paper);text-decoration:underline;text-underline-offset:3px}
@media (min-width:600px){
  .cpn__second .btn{width:auto}
}

/* The coupon link inside the $5 ticket card's feature list. Underlined rather
   than buttoned on purpose: the next click on that card should be Get Tickets,
   and a second button would compete with it. */
.tix__link{color:var(--blue-dk);text-decoration:underline;text-underline-offset:2px;font-weight:700}
.tix__link:hover{color:var(--blue)}


/* ============================================================
   Utopia Labs — "Powered by" credit badge
   ------------------------------------------------------------
   Drop-in footer credit for client websites.
   No dependencies, no web fonts, no external requests, no JS.

   How it adapts: the wordmark is filled with `currentColor`, so it
   picks up whatever text colour the client's footer already uses.
   The gold dot is the only fixed brand colour, because that dot is
   the one element that has to stay Utopia Labs on every site.

   One knob: --ul-mark (the wordmark height). Everything else scales.

   Why the doubled `.ul-credit.ul-credit` selectors and the handful of
   !important flags: this badge is dropped into stylesheets it does not
   control. A client footer rule as ordinary as `footer a { display:block }`
   or `footer svg { max-width:100% }` will otherwise win on specificity and
   visibly break the badge. Only the properties that break it are forced.
   ============================================================ */

.ul-credit{
  --ul-gold:#D4A843;
  --ul-mark:14px;                       /* wordmark height */
  --ul-gap:calc(var(--ul-mark) * .44);
  --ul-label:calc(var(--ul-mark) * .58);   /* label size */
  --ul-label-lift:calc(var(--ul-mark) * -.11); /* optical nudge, see note below */
  --ul-ease:cubic-bezier(.32,.72,0,1);
  --ul-label-op:.46;
  --ul-mark-op:.72;
}

.ul-credit.ul-credit{
  display:inline-flex !important;
  flex-direction:row;
  flex-wrap:nowrap;
  align-items:baseline;                 /* label and wordmark share a baseline */
  align-self:center;                    /* never stretch inside a flex parent */
  gap:var(--ul-gap);
  width:auto;
  max-width:100%;
  margin:0;
  padding:0;
  border:0;
  color:inherit;
  background:none;
  font:inherit;
  font-size:calc(var(--ul-mark) * .78);
  font-weight:400;
  font-style:normal;
  line-height:1;
  letter-spacing:0;
  text-transform:none;
  text-decoration:none !important;
  border-radius:999px;
  vertical-align:middle;
  -webkit-tap-highlight-color:transparent;
}

.ul-credit.ul-credit .ul-credit__label{
  display:inline;
  margin:0;
  padding:0;
  font-family:inherit;
  font-size:var(--ul-label);
  font-weight:300;
  font-style:normal;
  line-height:1;
  letter-spacing:.015em;
  color:inherit;
  white-space:nowrap;
  /* Sharing a baseline with the wordmark is typographically correct but reads too low,
     because the label's lowercase is roughly half the height of the wordmark's. This
     lifts the label so its optical centre sits level with the middle of "utopia labs",
     which is where the eye expects it. Raise or lower --ul-label-lift to taste. */
  transform:translateY(var(--ul-label-lift));
  opacity:var(--ul-label-op);
  transition:opacity .55s var(--ul-ease);
}

.ul-credit.ul-credit .ul-credit__mark{
  display:block !important;
  height:var(--ul-mark) !important;
  width:auto !important;
  max-width:none !important;
  min-width:0;
  margin:0;
  flex:0 0 auto;
  overflow:visible;
  /* the traced wordmark box runs ascender to descender. nudging it down by
     8% of its height puts the type baseline on the label's baseline. */
  transform:translateY(8%);
}
.ul-credit--icon.ul-credit--icon .ul-credit__mark{ transform:none; }
/* the icon's box is entirely x-height, so its optical centre sits higher than the
   wordmark's and the label needs a correspondingly larger lift */
.ul-credit--icon.ul-credit--icon .ul-credit__label{ --ul-label-lift:calc(var(--ul-mark) * -.3); }

.ul-credit.ul-credit .ul-credit__letters{
  fill:currentColor;
  stroke:none;
  opacity:var(--ul-mark-op);
  transition:opacity .55s var(--ul-ease);
}

.ul-credit.ul-credit .ul-credit__dot{
  fill:var(--ul-gold);
  stroke:none;
  transform-box:fill-box;
  transform-origin:50% 50%;
  transition:transform .55s var(--ul-ease);
}

/* Idle state stays quiet. All the life is on hover, because the badge sits on
   a client's site and must never compete with the client's own footer. */
@media (hover:hover){
  .ul-credit.ul-credit:hover{ text-decoration:none !important; }
  .ul-credit.ul-credit:hover .ul-credit__label{ opacity:calc(var(--ul-label-op) + .32); }
  .ul-credit.ul-credit:hover .ul-credit__letters{ opacity:1; }
  .ul-credit.ul-credit:hover .ul-credit__dot{ transform:scale(1.34); }
}
.ul-credit.ul-credit:focus-visible{
  outline:none;
  box-shadow:0 0 0 2px color-mix(in srgb, currentColor 22%, transparent);
}
.ul-credit.ul-credit:focus-visible .ul-credit__letters{ opacity:1; }
.ul-credit.ul-credit:focus-visible .ul-credit__dot{ transform:scale(1.34); }

@media (prefers-reduced-motion:reduce){
  .ul-credit.ul-credit *{ transition-duration:.01ms !important; }
  .ul-credit.ul-credit:hover .ul-credit__dot{ transform:none; }
}

/* ---------- variant: hairline capsule ---------- */
.ul-credit--pill.ul-credit--pill{
  padding:calc(var(--ul-mark) * .5) calc(var(--ul-mark) * .78) calc(var(--ul-mark) * .4);
  background:color-mix(in srgb, currentColor 5%, transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 11%, transparent);
  transition:background .55s var(--ul-ease), box-shadow .55s var(--ul-ease);
}
@media (hover:hover){
  .ul-credit--pill.ul-credit--pill:hover{
    background:color-mix(in srgb, currentColor 8%, transparent);
    box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent);
  }
}

/* ---------- variant: stacked ---------- */
.ul-credit--stacked.ul-credit--stacked{
  flex-direction:column;
  align-items:center;
  gap:.72em;
  text-align:center;
}
.ul-credit--stacked.ul-credit--stacked .ul-credit__label{
  --ul-label-lift:0px;
  font-size:calc(var(--ul-mark) * .5);
  text-transform:uppercase;
  letter-spacing:.24em;
}
.ul-credit--stacked.ul-credit--stacked .ul-credit__mark{ transform:none; }

/* ---------- variant: gold chip (highest visibility) ---------- */
.ul-credit--gold.ul-credit--gold{
  --ul-label-op:.6;
  --ul-mark-op:.92;
  padding:calc(var(--ul-mark) * .5) calc(var(--ul-mark) * .78) calc(var(--ul-mark) * .4);
  color:#2A1F00;
  background:var(--ul-gold);
  box-shadow:0 1px 2px rgba(42,31,0,.14), 0 4px 12px -4px rgba(42,31,0,.22);
  transition:transform .45s var(--ul-ease), box-shadow .55s var(--ul-ease);
}
.ul-credit--gold.ul-credit--gold .ul-credit__dot{ fill:#2A1F00; }
@media (hover:hover){
  .ul-credit--gold.ul-credit--gold:hover{
    transform:translateY(-1px);
    box-shadow:0 2px 4px rgba(42,31,0,.16), 0 8px 20px -6px rgba(42,31,0,.28);
  }
}
.ul-credit--gold.ul-credit--gold:active{ transform:translateY(0) scale(.985); }

/* ---------- placement: pin to the right of a flex bottom bar ---------- */
/* margin-left:auto rather than justify-content on the parent, because it keeps the
   credit on the right even when the copyright line wraps onto its own row. */
.ul-credit--right.ul-credit--right{ margin-left:auto; }

/* ---------- tone utilities ---------- */
.ul-credit--quiet.ul-credit--quiet { --ul-label-op:.34; --ul-mark-op:.56; }
.ul-credit--strong.ul-credit--strong{ --ul-label-op:.66; --ul-mark-op:.95; }

/* ---------- optional: full-width footer strip ---------- */
.ul-credit-bar{
  display:flex;
  justify-content:center;
  padding:22px 20px calc(22px + env(safe-area-inset-bottom, 0px));
  border-top:1px solid color-mix(in srgb, currentColor 10%, transparent);
}

/* ---- Utopia Labs credit, scoped to this footer ----
   The bar is forced to a row at every width, not just 600px up. It now holds only
   the copyright and the credit, and those two fit side by side down to 320px, so
   there is no reason to stack them on a phone.

   margin-left:auto rather than the row's space-between, because auto still pins the
   credit right if anything is ever added back to this bar. */
.ftr__base{ flex-direction:row; align-items:baseline; flex-wrap:nowrap; }
/* align-self:baseline, not center. The credit's box is taller than the copyright text
   because it contains the wordmark's descender, so centring the two boxes leaves the
   credit sitting visibly higher. Sharing a baseline is what makes them read as one line. */
.ftr__base .ul-credit{ --ul-mark:13px; margin-left:auto; align-self:baseline; }

/* Under 380px the two would start to crowd, so the credit steps down a point. */
@media (max-width:379px){
  .ftr__base .ul-credit{ --ul-mark:12px; }
}

/* ---- Footer link columns, two-up on mobile ----
   Visit and Vendors sit side by side below 600px instead of stacking, which cuts
   about 190px off the footer on a phone and matches the desktop structure.

   This only works now that the Vendors column no longer holds a raw email address.
   "hello@mrktmarkets.com" has no break points, so its min-content width forced the
   column open to 165px and squeezed Visit down to 95px with zero slack. "Contact us"
   removes that constraint, so 1fr 1fr splits evenly. Do not put a long unbreakable
   string back in these lists without checking this again. */
@media (max-width:599px){
  .ftr__top{ grid-template-columns:1fr 1fr; column-gap:20px; row-gap:34px; }
  .ftr__brand{ grid-column:1 / -1; }
}

/* The coupon promo card gets a bigger entrance than everything else.

   The site's shared reveal is a 22px lift, which is a fade with a nudge: right
   for a paragraph or a section heading that should not draw attention to
   itself arriving. This card is the one block on home whose entire job is to
   be noticed, so it travels further, starts fractionally smaller, and lands
   with a slight overshoot. Together those read as a pop rather than a fade.

   MODEST OVERSHOOT ON PURPOSE. cubic-bezier(.34,1.4,.64,1) passes its
   resting position by a few percent and settles back. A springier curve was
   tried at 1.8 and looked like a toy on a block this size; the point is to
   catch the eye, not to bounce.

   Scale rather than more travel alone, because 40px of movement on its own
   reads as the page still loading, while a card growing slightly into place
   reads as something arriving.

   Transform only. No margin, no top, nothing that forces layout, so this
   cannot shift the sections around it while it animates.

   THE REDUCED-MOTION OVERRIDE BELOW IS NOT OPTIONAL. The site-wide kill switch
   is written as `.rise`, specificity (0,1,0), and this rule is (0,3,0), so it
   would win and strand the card 40px low and slightly small for anybody who
   has asked their machine to stop animating things. */
.js .cpn__promo.rise{
  transform:translateY(40px) scale(.96);
  transition:opacity .55s ease,transform .75s cubic-bezier(.34,1.4,.64,1)
}
.js .cpn__promo.rise.in{transform:none}

@media (prefers-reduced-motion:reduce){
  .js .cpn__promo.rise,
  .js .cpn__promo.rise.in{opacity:1;transform:none;transition:none}
}

/* The figure's landing. Fires once, the moment the count stops.

   The count itself decelerates into its final value, which is smooth but ends
   quietly: there is no moment that says "done". This gives it one. The number
   overshoots its own size slightly, dips a hair under, and settles.

   TRANSFORM ONLY, so it cannot move anything around it. A scale on a
   block-level element changes nothing about layout, and the figure is centred
   in the card, so it grows from its own middle.

   1.1, not more. The number is already the largest thing on the page at up to
   4.6rem; at 1.2 it read as a glitch rather than a flourish.

   No reduced-motion override needed here, unlike the card's entrance. The
   site-wide kill switch is `*{animation:none!important}`, which catches this
   because it is an animation rather than a transition. The card's entrance
   needed its own override precisely because that one IS a transition and the
   switch's `.rise` selector lost on specificity. */
@keyframes cpn-land{
  0%   {transform:scale(1)}
  38%  {transform:scale(1.1)}
  68%  {transform:scale(.97)}
  100% {transform:scale(1)}
}
.cpn__figv.is-landed{animation:cpn-land .5s cubic-bezier(.34,1.3,.64,1) both}

/* ---------- contact page ---------- */
/* Everything here is scoped under .contact so it cannot reach any other page.
   The form deliberately does NOT reuse .signup: that block is sized for a
   single email field in a narrow column, and its 10px gap is too tight once
   labels are involved. Only .signup__hp and .signup__ok are borrowed, because
   those two are self-contained utilities rather than layout. */
.contact__in{display:grid;gap:44px}
.contact h1{font-size:var(--step-3);margin-bottom:18px}
.contact .lede{font-size:var(--step-1);line-height:1.4;color:var(--ink-2);max-width:34ch}

.contact__quick{list-style:none;margin:28px 0 0;padding:0;display:grid;gap:14px;max-width:42ch}
.contact__quick li{
  font-size:var(--step--1);line-height:1.6;color:var(--ink-2);
  padding-left:16px;border-left:2px solid var(--line)
}
.contact__quick strong{color:var(--ink);font-weight:700}
.contact__quick a{color:var(--blue-dk);text-underline-offset:2px}

.contact__form{display:grid;gap:18px}
.contact__row{display:grid;gap:18px}
.contact__field{display:grid;gap:7px}
.contact__field label{
  font-size:var(--step--1);font-weight:700;letter-spacing:.04em;color:var(--ink-2)
}
/* Matches .signup input so the two forms on the site feel like one system.
   font:inherit on the textarea and select is not optional: both default to the
   browser's own UI font and would otherwise be the only elements on the page
   not set in Raleway. */
.contact__field input,
.contact__field select,
.contact__field textarea{
  min-height:52px;padding:14px 16px;font:inherit;font-size:1rem;
  border:1.5px solid var(--line);border-radius:var(--r);
  background:var(--paper);color:var(--ink);width:100%
}
.contact__field textarea{min-height:150px;resize:vertical;line-height:1.55}
.contact__field select{appearance:none;background-image:none}
.contact__field input:focus,
.contact__field select:focus,
.contact__field textarea:focus{outline:none;border-color:var(--ink)}
.contact__form .btn{width:100%}
/* Same busy treatment as the mailing list, so a double tap cannot fire twice. */
.contact__form[data-busy] .btn{opacity:.6;pointer-events:none}

@media (min-width:560px){
  .contact__row{grid-template-columns:1fr 1fr}
}
@media (min-width:900px){
  /* Intro narrower than the form. The form is the job of this page and should
     be the wider of the two, which is the opposite of the usual split. */
  .contact__in{grid-template-columns:0.85fr 1fr;gap:64px;align-items:start}
}

/* ---------- vendor shop (/vendor-shop) ---------- */
/* New page 2026-08-26. MRKT's real "Vendor Shop" page, rebuilt: their own
   product picks, real affiliate links, images downloaded and self-hosted
   here rather than hotlinked to Wix, same rule this site already follows for
   the press logos. Its own block names throughout rather than reusing
   .contact__form for the form at the bottom, matching this site's own
   practice of giving visually-similar things on different pages separate
   ownership (see .apply__grid's own comment for the same call). */
.rent{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;
  margin-top:24px;padding:16px 20px;
  background:var(--paper-2);border-left:3px solid var(--blue);
  font-size:.94rem;line-height:1.55;color:var(--ink-2)
}
.rent b{color:var(--ink)}
.rent a{color:var(--blue-dk);text-underline-offset:2px}

.shop__grid{display:grid;gap:24px;grid-template-columns:1fr}
.shop__card{
  display:flex;flex-direction:column;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-card);
  overflow:hidden
}
.shop__card img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block;background:var(--paper-2)}
.shop__card__body{padding:20px 20px 22px;display:flex;flex-direction:column;flex:1;gap:8px}
.shop__card h3{font-size:1rem}
.shop__card p{font-size:.86rem;line-height:1.55;color:var(--ink-2);flex:1}
.shop__card .btn{margin-top:6px;width:100%}

@media (min-width:560px){
  .shop__grid{grid-template-columns:repeat(2,1fr)}
}
@media (min-width:900px){
  .shop__grid{grid-template-columns:repeat(3,1fr)}
}
@media (min-width:1200px){
  .shop__grid{grid-template-columns:repeat(4,1fr)}
}

/* Same field recipe as .contact__field, kept as a separate block on purpose:
   see the comment at the top of this section. */
.shop__form{display:grid;gap:18px;max-width:640px}
.shop__row{display:grid;gap:18px}
.shop__field{display:grid;gap:7px}
.shop__field label{font-size:var(--step--1);font-weight:700;letter-spacing:.04em;color:var(--ink-2)}
.shop__field input,
.shop__field select,
.shop__field textarea{
  min-height:52px;padding:14px 16px;font:inherit;font-size:1rem;
  border:1.5px solid var(--line);border-radius:var(--r);
  background:var(--paper);color:var(--ink);width:100%
}
.shop__field textarea{min-height:130px;resize:vertical;line-height:1.55}
.shop__field select{appearance:none;background-image:none}
.shop__field input:focus,
.shop__field select:focus,
.shop__field textarea:focus{outline:none;border-color:var(--ink)}
.shop__form .btn{width:100%}
.shop__form[data-busy] .btn{opacity:.6;pointer-events:none}

@media (min-width:560px){
  .shop__row{grid-template-columns:1fr 1fr}
}


/* ============================================================
   /privacy
   ============================================================
   A legal page has no design problem to solve beyond being read, so this is
   deliberately the smallest block on the site: one column, one measure, and
   heading spacing that lets somebody scanning for one answer find it.

   65ch rather than the 34ch .lede uses elsewhere. That short measure is right
   for a display line above a form and wrong for four screens of body copy,
   where it turns every paragraph into a narrow ribbon and doubles the scroll.

   No cards, no rules between sections, no accordions. An accordion in
   particular would be the wrong call here: content nobody can find with a
   browser's own Find is content that is technically published and practically
   hidden, which is the thing a privacy policy must not be. */
.legal__in{max-width:65ch}
.legal h1{font-size:var(--step-3);margin-bottom:10px}
.legal__date{font-size:var(--step--1);color:var(--ink-2);margin-bottom:26px}
.legal .lede{font-size:var(--step-1);line-height:1.45;color:var(--ink-2);max-width:none}

/* h2 carries its own top space rather than relying on the paragraph above it,
   so a section that opens with a list looks the same as one that opens with a
   paragraph. */
.legal h2{font-size:var(--step-1);margin:44px 0 14px}
.legal p{line-height:1.65;margin-bottom:16px;color:var(--ink-2)}
.legal p strong{color:var(--ink);font-weight:700}
.legal a{color:var(--blue-dk);text-underline-offset:2px}

.legal__list{list-style:none;margin:0 0 20px;padding:0;display:grid;gap:12px}
.legal__list li{
  font-size:var(--step--1);line-height:1.6;color:var(--ink-2);
  padding-left:16px;border-left:2px solid var(--line)
}
.legal__list strong{color:var(--ink);font-weight:700}

/* The footer's privacy link.

   .ftr a is sized for the nav columns: .92rem and a 40px tap target. Dropped
   into the .76rem bottom bar unchanged it is visibly bigger than the copyright
   line beside it and its min-height pushes the bar 16px taller. Both are reset
   here rather than by weakening .ftr a, because the nav columns want that tap
   target and this one line does not. */
.ftr__base a.legal-link{
  display:inline;min-height:0;font-size:inherit;color:#B9B3AB
}
.ftr__base a.legal-link:hover{color:var(--paper)}
