/* ==========================================================================
   CRAWLLOWEEN page-level composition

   Load order: tokens.css, base.css, components.css, THEN this file.

   Scope, deliberately narrow. This file holds only the page-level blocks that
   components.css does not cover: the hero shells, a data table, a key/value
   spec list, a captioned figure and the long-form legal prose. It adds nothing
   and overrides nothing that already exists in components.css.

   Every value is a token. No hex, no raw px outside a border hairline, no
   duration that is not from tokens.css.
   ========================================================================== */

/* ================================================================ hero ====

   Three variants, one shell:

     .hero            photographic. Needs .hero__media + .hero__scrim.
     .hero--art       key art or type only, no photograph.
     .hero--plain     shorter text-only hero for the utility pages.

   The shell carries its own nav offset, so a page using it puts
   `.main--flush` on <main>. Never add a second offset.
   ======================================================================== */

.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: min(88vh, 780px);
  padding-block: calc(var(--nav-h) + var(--sp-8)) var(--sp-8);
  background: var(--bg-sunk);
}

.hero--art   { min-height: min(74vh, 660px); }
.hero--plain { min-height: 0; padding-block: calc(var(--nav-h) + var(--sp-7)) var(--sp-7); }

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}

.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The hero carries a video on desktop and a still on phones, stacked in the same
   box. The video sits on top where it exists; where it does not, or where
   motion.js has stripped it, the still shows through underneath.

   The still is NOT display:none on desktop. It stays in the layout as the thing
   that establishes the hero's height and as the fallback if the video 404s or
   the codec is refused. */
.hero__video,
.hero__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero__video { z-index: 1; }
.hero__still { z-index: 0; }

/* motion.js adds this when it declines to play the video. Drop it out of the way
   so the still underneath is what renders, rather than a black poster box. */
.hero__video.is-poster-only { display: none; }

@media (max-width: 860px) {
  .hero__video { display: none; }
}

/* Type never sits on a photograph directly. But a flat vertical scrim at 0.55 to
   0.93 buried the artwork it was protecting: the flyer creatures and the Chicago
   landmarks are the whole reason those images are there, and they were reading as
   texture.
   
   So on anything wide enough, the scrim is SIDE-weighted instead. Dark down the
   left where the copy sits, clearing to nothing on the right where the creature
   is. Same contrast under the type, and the art is actually visible. A light
   vertical pass underneath keeps the bottom edge from clipping hard. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--scrim-hard);
}

@media (min-width: 861px) {
  .hero__scrim {
    background: var(--scrim-side), var(--scrim-soft);
  }
}

/* On a phone the scrim needs to be stronger, and the reason is geometric rather
   than aesthetic. --scrim-hard is a vertical gradient that is weakest at the top
   (0.55) and darkest at the bottom (0.93). On desktop the H1 is enormous and
   sits in that weak upper band quite happily. At 390px the block is taller than
   the viewport, so the subhead and the fact line land in the same weak band,
   directly over the brightest part of a crowd frame, and drop below a readable
   contrast ratio.

   So this is not "make it darker on mobile" for taste. It restores the contrast
   the desktop layout gets for free from type size. */
@media (max-width: 860px) {
  .hero__scrim { background: var(--scrim-phone); }
}

.hero__inner { position: relative; }

.hero__title { margin-bottom: var(--sp-4); }

.hero__lead { max-width: 58ch; }

/* ---------------------------------------------------------- hero meta row ===

   The neighborhood's hard numbers, sitting between the subhead and the fact
   block: the venue count first, then the measured walk where one exists.

   The count is the claim a buyer can most easily test, so the wording is not
   interchangeable and DATA.md 5.9 is the only source for it. River North and
   West Loop say "venues in 2025", past tense, because those rosters ran and are
   published in full further down the page. The four new neighborhoods say
   "stops planned" and carry the lock-date qualifier in the same row, because
   nothing is signed. Writing "12 venues" on Wrigley today is the bait-and-switch
   that put 48 BBB complaints on the biggest operator in this category.

   Same visual language as .hood-card__meta on the index, deliberately: the card
   and the page it opens should state the number identically.
   ======================================================================== */
.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin-top: var(--sp-4);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-faint);
}

.hero__meta strong {
  color: var(--accent);
  font-weight: var(--fw-bold);
}

/* The fact line: age policy, welcome shot, credit, door windows. It sits
   between the subhead and the CTA because a buyer must not be able to reach a
   buy button without passing it. */
.hero__fact {
  margin-top: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  max-width: 64ch;
  background: var(--bg-elev);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  font-size: var(--fs-sm);
  color: var(--text);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
}

.hero__caption {
  margin-top: var(--sp-6);
  font-size: var(--fs-micro);
  font-weight: var(--fw-semi);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Oversized neighborhood name behind the H1, in that neighborhood's accent.
   Used only where no key art exists yet. Clipped by the hero's overflow so it
   can never push the body sideways. */
.hero__glyph {
  display: block;
  margin-bottom: var(--sp-2);
  font-family: var(--font-display);
  font-weight: var(--fw-black);
  font-size: clamp(3rem, 13vw, 9rem);
  line-height: 0.86;
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.18;
  pointer-events: none;
}

/* ============================================================== figure ==== */

.figure { margin: 0; }

.figure img {
  width: 100%;
  border-radius: var(--r-lg);
}

.figure figcaption {
  margin-top: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--text-faint);
}

/* ============================================================ speclist ====

   Key/value rows: group rate, minimum, deadline, sponsorship tier, pickup
   window. Reads as a spec sheet rather than a paragraph, which is the whole
   "specificity over urgency" argument in layout form.
   ======================================================================== */

.speclist {
  border: var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.speclist__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--sp-2) var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--line);
}

.speclist__row:last-child { border-bottom: 0; }

.speclist__key {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-faint);
}

.speclist__val { color: var(--text); }

.speclist__val small {
  display: block;
  margin-top: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--text-faint);
}

@media (max-width: 560px) {
  .speclist__row { grid-template-columns: 1fr; }
}

/* =========================================================== data table ===

   The comparison table. Wide, so it scrolls inside its own container. The
   body never scrolls sideways.
   ======================================================================== */

.data-table {
  width: 100%;
  min-width: 620px;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

.data-table caption {
  text-align: left;
  padding-bottom: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--text-faint);
}

.data-table th,
.data-table td {
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
}

.data-table thead th {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}

.data-table tbody th {
  font-weight: var(--fw-bold);
  color: var(--text);
}

.data-table tbody tr:last-child th,
.data-table tbody tr:last-child td { border-bottom: 0; }

/* Our own row. Marked so a reader can find it, not so it wins the table. */
.data-table tbody tr.is-ours th,
.data-table tbody tr.is-ours td { background: var(--bg-elev); }
.data-table tbody tr.is-ours th { box-shadow: inset 3px 0 0 0 var(--accent); }

/* ============================================================== notice ====

   A short block that has to be read before a decision: the name collision,
   the check-in remedy, a policy dependency.
   ======================================================================== */

.notice {
  padding: var(--sp-5);
  background: var(--bg-elev);
  border: 1px solid var(--accent);
  border-radius: var(--r-lg);
}

.notice__title {
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-black);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  margin-bottom: var(--sp-3);
}

/* ========================================================== numbered ======

   "How it works" and "Three questions". A number, a heading, a paragraph.
   ======================================================================== */

.steps { counter-reset: step; }

.steps__item {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-4) var(--sp-5);
  padding-block: var(--sp-5);
  border-bottom: 1px solid var(--line);
}

.steps__item:last-child { border-bottom: 0; }

.steps__num {
  counter-increment: step;
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-black);
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.steps__num::before { content: counter(step); }

.steps__body > * + * { margin-top: var(--sp-3); }

.steps__title {
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
}

/* ========================================================= legal prose ====

   terms.html. Long, dense, and read under stress. Measure stays short and
   headings stay plain.
   ======================================================================== */

.legal-prose { max-width: 72ch; }

.legal-prose > * + * { margin-top: var(--sp-4); }

.legal-prose h2 {
  font-size: var(--fs-h3);
  margin-top: var(--sp-7);
}

.legal-prose h3 {
  font-size: var(--fs-h4);
  margin-top: var(--sp-6);
}

.legal-prose p,
.legal-prose li { color: var(--text-dim); }

.legal-prose li {
  position: relative;
  padding-left: var(--sp-5);
}

.legal-prose li::before {
  content: '\00b7';
  position: absolute;
  left: var(--sp-2);
  color: var(--accent);
  font-weight: var(--fw-bold);
}

.legal-prose li + li { margin-top: var(--sp-2); }

/* ============================================================ mast list ===

   The 404's route list and any other short "pick one of these" list.
   ======================================================================== */

.mast-list {
  display: grid;
  gap: var(--sp-3);
  max-width: var(--max-narrow);
}

.mast-list a {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  text-decoration: none;
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-black);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  transition:
    border-color var(--dur-fast) var(--ease-standard),
    color var(--dur-fast) var(--ease-standard);
}

.mast-list a:hover { border-color: var(--accent); color: var(--accent); }

.mast-list a span {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  letter-spacing: var(--track-wide);
  color: var(--text-faint);
}

/* ============================================================ utilities ===

   `.row` in base.css is a left-aligned wrap. A centred section needs its CTA
   row centred too, and `text-align` does nothing to a flex container.
   ======================================================================== */

.row--center { justify-content: center; }

/* Sticky buy bar at 390px.

   `.mobile-cta .btn { flex: 1 }` splits the bar in half, which sent a 25
   character pre-launch label ("Text me when tickets drop") onto three lines and
   pushed the bar to 121px tall. That overran the 76px `body` padding
   components.css reserves for it, so the bar covered the end of the footer.

   The secondary sizes to its own content and the primary takes what is left. */
.mobile-cta .btn {
  font-size: var(--fs-xs);
  padding-inline: var(--sp-3);
}

.mobile-cta .btn--ghost {
  flex: 0 1 auto;
  white-space: nowrap;
}

/* ============================================================== forms ===== */

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-5);
}

.form-grid__wide { grid-column: 1 / -1; }

.form-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
}

/* ============================================================== drop ===== */

.drop-modal__body > * + * { margin-top: var(--sp-4); }

/* ============================================================= alter ego ===

   The costume-planner activation on the hub. Two columns on desktop: the pitch on
   the left, the four possible results on the right.

   The four names are the whole reason this reads as an invitation rather than a
   chore. "Take a quiz" is work; "you might be The Party Poltergeist" is a
   question you want answered. They are set in the display face at heading weight
   so they carry as artwork, not as a list of links, because they are not links:
   the single CTA is.
   ======================================================================== */

.alter-cta {
  display: grid;
  gap: var(--sp-7);
  align-items: center;
}

@media (min-width: 861px) {
  .alter-cta { grid-template-columns: 1.15fr 1fr; gap: var(--sp-8); }
}

.alter-cta .lead { max-width: 46ch; }
.alter-cta .dim { max-width: 46ch; margin-top: var(--sp-3); }
.alter-cta .row { margin-top: var(--sp-6); }

.alter-cta__egos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  overflow: hidden;
}

.alter-cta__egos li {
  background: var(--bg-elev);
  padding: var(--sp-4) var(--sp-5);
  font-family: var(--font-display);
  font-weight: var(--fw-black);
  font-size: var(--fs-h4);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  line-height: var(--lh-snug);
  color: var(--text-dim);
  /* A counter rather than a bullet: four numbered outcomes reads as a set you are
     being sorted into, which is what the quiz actually does. */
  display: grid;
  grid-template-columns: 2ch 1fr;
  gap: var(--sp-4);
  align-items: baseline;
  counter-increment: ego;
}

/* Set at full brightness, not --text-dim. These four names are the reason anyone
   clicks: "you might be The Party Poltergeist" is a question people want answered,
   where "take a quiz" is a chore. Dimming them made the panel read as a disabled
   list of things that had already happened. */
.alter-cta__egos li { color: var(--text); }

.alter-cta__egos {
  counter-reset: ego;
}

.alter-cta__egos li::before {
  content: counter(ego, decimal-leading-zero);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* None of the four is highlighted, because the page has no idea which is yours yet.
   The hover is the only movement, and it hints that a result is a thing that gets
   picked. */
.alter-cta__egos li { transition: color var(--dur-fast) var(--ease-standard); }
.alter-cta__egos li:hover { color: var(--accent); }

/* --------------------------------------------- a night that is not a choice ---
   set by site.js when a crawl runs only one night, so the tablist stops looking
   like something to interact with and reads as a statement of the date.
   ------------------------------------------------------------------------ */

.nights[data-single-night] .night-tab {
  cursor: default;
  pointer-events: none;
}

.nights[data-single-night] .night-tab:hover { transform: none; }

/* =============================================================== picker ====

   Six neighborhoods by three nights, in one block.

   THE SIZE IS THE POINT. This replaced nine 16:10 image tiles that were correct in
   structure and far too big: three rows of artwork put most of the choice below the
   fold, so a page whose only job is "see everything, pick one" made you scroll to
   see everything. All eighteen cells now fit in roughly the height one old tile
   occupied.

   The artwork is not lost, it moved. Every neighborhood page opens on its own card
   header, which is where a full-bleed piece of art earns its space.
   ======================================================================== */

.picker {
  /* CLEARS THE FIXED NAV ITSELF, rather than letting <main> do it.

     index.html keeps .main--flush, which switches off the nav offset in base.css.
     That was right when a full-bleed hero sat here and it is still wanted, because
     the background footage should run up behind the transparent nav rather than
     starting 68px down with a visible seam.

     The cost is that this section has to clear the nav on its own, and until it did
     the eyebrow rendered underneath the wordmark. --nav-h plus breathing room.

     Tight beyond that: every pixel here is one the grid does not get, and the grid
     fitting on one screen is the entire point of the section. */
  padding-top: calc(var(--nav-h) + clamp(var(--sp-3), 3vh, var(--sp-5)));
  /* The background sits inside this section, so it needs a stacking context and it
     must clip: the parallax on .picker__bg translates it beyond the section bounds
     and without this it would bleed into the sections above and below. */
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/* ------------------------------------------------------- the atmosphere ---
   Footage behind the grid rather than a hero above it. Overscaled by 12% because
   data-parallax translates it vertically, and at 1:1 that would expose the page
   background at whichever edge it moves away from.
   ---------------------------------------------------------------------- */

.picker__bg {
  position: absolute;
  inset: -6% 0;
  z-index: -2;
  pointer-events: none;
}

.picker__video,
.picker__still {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
  display: block;
}

/* One or the other, never both. motion.js adds .is-poster-only when it strips the
   video source, which is the signal that the still should show instead. */
.picker__still { display: none; }
.picker__video.is-poster-only + .picker__still { display: block; }

@media (max-width: 860px) {
  /* The <source media> query means the video has no usable source here anyway, so
     the still carries it with JS off as well. */
  .picker__video { display: none; }
  .picker__still { display: block; }
}

/* HEAVY, AND HEAVIER THAN A HERO SCRIM WOULD BE. A hero puts three lines of large
   type over footage; this puts a data table over it, with hairline rules and
   --text-faint metadata that vanish over anything busy. So the scrim is close to
   opaque in the middle third where the grid sits and eases off at the edges, which
   keeps the atmosphere readable at the margins without touching legibility. */
.picker__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(11, 7, 9, 0.72) 0%,
      rgba(11, 7, 9, 0.90) 34%,
      rgba(11, 7, 9, 0.94) 70%,
      var(--bg) 100%);
}

@media (prefers-reduced-motion: reduce) {
  .picker__bg { transform: none !important; }
}

.picker__head { max-width: 60ch; }
.picker__head .lead { max-width: 56ch; }

.picker__title {
  font-size: var(--fs-h1);
  font-family: var(--font-display);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
}

/* The table is narrow enough to fit a phone, but this guarantees it can never push
   the page sideways if a neighborhood name grows. */
.picker__scroll {
  overflow-x: auto;
  margin-top: var(--sp-5);
  -webkit-overflow-scrolling: touch;
}



/* ------------------------------------------------------- snapshot strips ---
   Real 2025 photographs pinned up between sections, bleeding off both edges.

   NOT A GALLERY, and the difference is the point. #gallery is a grid under a claim
   worth making; this has no heading and nothing to read, so it breaks the scroll
   without asking for attention. Its whole job is that the page keeps showing real
   people at the real event while you read the operational detail.

   FULL-BLEED WITHOUT A WRAPPER. It sits between sections rather than inside one, so
   it takes the viewport width directly. 100vw would be wrong: on a page with a
   vertical scrollbar 100vw includes the scrollbar and the strip would push the body
   sideways, which is the classic full-bleed bug. 100% of <main> is already the right
   width because nothing constrains it.
   ---------------------------------------------------------------------- */

.snapstrip {
  width: 100%;
  overflow: hidden;
  /* Rotated children poke above and below their box, so the strip needs vertical
     room to clip against rather than letting them collide with the sections. */
  padding-block: clamp(var(--sp-6), 5vw, var(--sp-8));
  background: var(--bg-sunk);
  border-block: 1px solid var(--line);
}

.snapstrip__row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  /* Negative gap: the snapshots overlap slightly, which is what makes a row of
     photographs read as a pile of them. */
  gap: clamp(-2rem, -1.6vw, -0.75rem);
  /* Wider than the viewport on purpose, and shifted left, so the strip is cut off at
     both edges and reads as part of something longer. */
  width: 112%;
  margin-left: -6%;
  justify-content: center;
}

/* Sized so seven prints are WIDER than the viewport, which is what actually produces
   the bleed. The first attempt used 13vw, and seven of those plus the negative gaps
   came to less than 1440, so the row centred itself with black margins either side
   and the strip read as a centred row of photos rather than as part of something
   longer. 17vw overshoots by roughly 70px a side at desktop width, and .snapstrip
   clips it. */
.snap {
  flex: 0 0 auto;
  width: clamp(7.5rem, 17vw, 15rem);
  transform: rotate(var(--tilt, 0deg));
  /* The white edge and the drop shadow are what make these read as physical prints
     on a dark page rather than as cropped images. */
  border: 5px solid var(--bone);
  border-bottom-width: 18px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.55);
  background: var(--bone);
  transition: transform var(--dur-base) var(--ease-standard);
}

.snap img {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
}

/* Landscape frames would letterbox badly inside a 2:3 box, so they crop to it. The
   manifest records orientation per frame; the mix is deliberate and cropping is what
   keeps the row an even height. */

/* Straighten and lift on hover. The only movement here, and it reads as picking one
   up off the wall. */
.snap:hover {
  transform: rotate(0deg) translateY(-6px) scale(1.03);
  z-index: 1;
  position: relative;
}

@media (prefers-reduced-motion: reduce) {
  .snap { transition: none; }
  .snap:hover { transform: rotate(var(--tilt, 0deg)); }
}

/* On a phone the row cannot show seven prints at a usable size, so it becomes a
   swipeable strip instead of shrinking them to thumbnails. Scroll snapping so it
   settles on a photograph rather than between two. */
@media (max-width: 700px) {
  .snapstrip__row {
    width: 100%;
    margin-left: 0;
    justify-content: flex-start;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    gap: var(--sp-3);
    padding-inline: var(--gutter);
    /* Room for the tilt and the shadow, which are clipped by overflow-x otherwise. */
    padding-block: var(--sp-4);
    -webkit-overflow-scrolling: touch;
  }
  .snap { scroll-snap-align: center; width: 8.5rem; }
  /* Less tilt at small sizes: at this width a 3.5 degree rotation on a 136px print
     looks like a mistake rather than a choice. */
  .snap { transform: rotate(calc(var(--tilt, 0deg) * 0.5)); }
}

/* ---------------------------------------------------- nights and towns ---
   Three nights stacked, three small flyer cards under each.

   THE NIGHT IS THE STRUCTURE. An earlier version flattened this into six cards in one
   row with the dates as chips, which was compact and worse: the date stopped
   organising the page and became something to read nine times. Grouping by night
   means the date is read once and answers "what is on that night" before any
   neighborhood is considered.

   The date sits in its own column on desktop so the three groups share a left edge
   and the eye can run down the nights without reading the cards.
   ---------------------------------------------------------------------- */

.pnight {
  display: grid;
  gap: var(--sp-4);
  padding-top: var(--sp-5);
  margin-top: var(--sp-5);
  border-top: 1px solid var(--line-strong);
}

@media (min-width: 861px) {
  .pnight { grid-template-columns: 13rem 1fr; gap: var(--sp-6); align-items: start; }
}

.pnight__date {
  font-family: var(--font-stencil);
  font-weight: var(--fw-body);
  font-size: var(--fs-h3);
  letter-spacing: var(--track-normal);
  text-transform: uppercase;
  line-height: var(--lh-snug);
}

.pnight__name {
  font-family: var(--font-type);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--accent);
  margin-top: var(--sp-1);
}

.pnight__count {
  font-size: var(--fs-micro);
  color: var(--text-faint);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}

/* Three across, and capped so they do not balloon on a wide screen. The complaint
   that produced this whole layout was that the cards were too big; three cards
   filling 1150px would be exactly that mistake again. */
/* EXPLICITLY THREE COLUMNS, because there are always exactly three crawls a night.

   This was auto-fill with a minmax, and it broke at intermediate widths: auto-fill
   counts how many tracks fit at the MIN size and then caps each at the max, so around
   a 1000px viewport it made four tracks, could only fill two, and orphaned the third
   card onto its own row beside a large gap. Three is a known quantity here; there is
   nothing for auto-fill to work out. */
/* THREE EQUAL COLUMNS THAT FILL THE ROW. No max-width cap.

   The cap was 15rem, and it left 184px of dead space to the right of every row: the
   cards sat at 19% of the content width where they should be nearer a quarter, and
   the whole block read as under-scaled rather than as condensed. Condensed was the
   brief; small with a gap beside it was not.

   minmax(0, 1fr) rather than plain 1fr so a long neighborhood name cannot push a
   column past its share. */
.pnight__cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* ------------------------------------------------------------ PHONE ---
   THREE CARDS A NIGHT DO NOT STACK WELL AT 390px.

   One column gives nine tall cards and a very long page. Two columns orphans the
   third of each three onto its own row. So each night becomes a swipeable row
   instead: the page stays three rows deep, and swiping within a night is the natural
   gesture for "three options in this group".

   Cards at 62vw so a second one is always partly visible, which is what tells anyone
   there is more to the right. Snapping so it settles on a card rather than between
   two. The negative inline margin and matching padding let the row bleed to the
   screen edges while the first card still lines up with the page gutter.
   ------------------------------------------------------------------ */

@media (max-width: 700px) {
  .pnight__cards {
    display: flex;
    gap: var(--sp-3);
    overflow-x: auto;
    /* proximity, not mandatory. mandatory fights a deliberate half-swipe and makes the
       row feel like it is correcting you. */
    scroll-snap-type: x proximity;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    /* WITHOUT THIS THE FIRST CARD DOES NOT LINE UP WITH THE DATE ABOVE IT.
       A snap point defaults to the scrollport edge, which ignores padding, so the
       browser scrolled the row 18px on load to align card one with the raw edge and
       cancelled out the padding that was there to hold the gutter. scroll-padding
       moves the snap edge inside the padding, which is what it is for. */
    scroll-padding-inline: var(--gutter);
    /* Room for the hover lift and the card border, which overflow-x would clip. */
    padding-block: 2px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }

  .pnight__cards::-webkit-scrollbar { display: none; }

  .fcard {
    flex: 0 0 62vw;
    scroll-snap-align: start;
  }
}

/* -------------------------------------------------------------- the card --- */

.fcard { display: flex; }

/* The whole card is one link to one crawl-night, so there is a single target per
   buyable thing rather than an art link and a name link to the same place. */
.fcard__link {
  display: flex;
  flex-direction: column;
  width: 100%;
  text-decoration: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--bg-elev);
  transition:
    border-color var(--dur-base) var(--ease-standard),
    transform var(--dur-base) var(--ease-standard);
}

.fcard__link:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.fcard__art {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  position: relative;
}

.fcard__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slower) var(--ease-standard);
}

.fcard__link:hover .fcard__art img { transform: scale(1.05); }

/* The accent bar at the join, the colour key the rest of the site uses to tell six
   neighborhoods apart at a glance. */
.fcard__art::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 3px;
  background: var(--accent);
}

/* Roomier than it was. Measured against the reference the three lines sat about 20px
   apart where they should be nearer 30, which read as cramped rather than compact.
   Compact is the card being small; cramped is the type inside it having nowhere to
   breathe, and they are not the same thing. */
.fcard__body {
  display: block;
  padding: var(--sp-4) var(--sp-4) var(--sp-5);
}

.fcard__name {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--fw-black);
  font-size: var(--fs-body);
  line-height: var(--lh-snug);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  color: var(--text);
}

.fcard__meta {
  display: block;
  font-size: var(--fs-micro);
  color: var(--text-faint);
  margin-top: var(--sp-3);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-snug);
}

/* The date repeated on the card even though the group above already states it. A card
   is the thing people screenshot and send to a friend, so it has to carry both facts
   on its own once it leaves the page. */
/* Sits directly under the name, because after "which town" the next thing anyone
   needs is "which night". The venue count is context and comes after both. */
.fcard__go {
  display: block;
  margin-top: var(--sp-3);
  font-family: var(--font-display);
  font-weight: var(--fw-black);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------- short viewports ---
   Three stacked groups do not fit a 720px laptop and are not meant to; the header is
   what gives way so the first two nights are visible without scrolling.
   ------------------------------------------------------------------ */

@media (max-height: 820px) and (min-width: 861px) {
  .picker { padding-top: calc(var(--nav-h) + var(--sp-3)); }
  .picker__title { font-size: var(--fs-h2); }
  .picker__head .drip { display: none; }
}
