/* nCommunity: Dark Editorial IRL, on the web.
 *
 * Colour, space, radius and the type scale come from styles/tokens.css, which
 * is generated from packages/ui/tokens.json. Nothing here hard-codes a brand
 * colour: if a token moves in the app it moves here in the same commit.
 *
 * Two rules do most of the work visually:
 *   1. Monochrome first. Near-white on true black.
 *   2. Cyan is not a brand wash. It appears on live/urgent states and the one
 *      active selection in the hero rail. Nowhere else.
 */

/* ------------------------------------------------------------------ reset */

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

:root {
  /* The nav is fixed, so anything that pins to the top of the viewport has to
   * clear it. Declaring the height here and applying it to .nav__inner means
   * this value is true by construction rather than measured off a screenshot. */
  --nc-nav-height: 72px;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* Without this, every in-page anchor lands with its heading under the nav. */
  scroll-padding-top: calc(var(--nc-nav-height) + var(--nc-space-lg));
}

body {
  margin: 0;
  background: var(--nc-color-background);
  color: var(--nc-color-text-primary);
  /* From the token, so the site cannot drift from the app. */
  font-family: var(--nc-font-family);
  font-size: var(--nc-type-body-size);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* `clip`, not `hidden`. `overflow-x: hidden` makes body a scroll container in
   * its own right, which moves scrolling off the document: window.scrollY stays
   * 0, `position: sticky` measures against the wrong box, and scroll-driven
   * observers stop firing. `clip` trims the same overflow without any of that. */
  overflow-x: clip;
}

/* `height: auto` matters: every <img> here carries width/height attributes so
 * the browser reserves the right box before the file lands, and without this
 * the attribute height wins over the CSS aspect-ratio. */
img,
svg {
  max-width: 100%;
  height: auto;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

:focus-visible {
  outline: 2px solid var(--nc-color-accent);
  outline-offset: 3px;
  border-radius: var(--nc-radius-sm);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--nc-space-lg);
  top: -100px;
  z-index: 100;
  padding: var(--nc-space-md) var(--nc-space-lg);
  background: var(--nc-color-primary);
  color: var(--nc-color-on-primary);
  border-radius: var(--nc-radius-pill);
  font-weight: 600;
  transition: top var(--nc-duration-base) ease;
}

.skip-link:focus {
  top: var(--nc-space-lg);
}

/* --------------------------------------------------------------- layout */

.section-h {
  font-size: clamp(28px, 4.4vw, 52px);
  line-height: 1.08;
  letter-spacing: -0.03em;
  font-weight: 700;
  margin: 0;
  max-width: 18ch;
}

.section-sub {
  color: var(--nc-color-text-secondary);
  max-width: 46ch;
  margin: var(--nc-space-lg) 0 0;
}

/* --------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--nc-min-touch-target);
  padding: 0 var(--nc-space-xl);
  border-radius: var(--nc-radius-pill);
  font-size: var(--nc-type-label-size);
  font-weight: var(--nc-type-label-weight);
  letter-spacing: -0.01em;
  transition:
    transform var(--nc-duration-fast) ease,
    background-color var(--nc-duration-fast) ease,
    border-color var(--nc-duration-fast) ease;
  white-space: nowrap;
}

.btn:active {
  transform: scale(0.98);
}

.btn--primary {
  background: var(--nc-color-primary);
  color: var(--nc-color-on-primary);
}

.btn--primary:hover {
  /* Was `#ffffff`, the only invented colour on the site's loudest control, on
     a page whose README claims nothing hard-codes one. Tokens are generated
     from `packages/ui/tokens.json` and there is no "brighter than primary"
     token to reach for, so the hover is expressed as a relationship to the
     token rather than as a second colour that would not follow it if it
     changed. */
  background: color-mix(in srgb, var(--nc-color-primary) 82%, white);
}

.btn--ghost {
  border: 1px solid var(--nc-color-border-strong);
  color: var(--nc-color-text-primary);
}

.btn--ghost:hover {
  background: var(--nc-color-surface-raised);
}

.btn--sm {
  min-height: 40px;
  padding: 0 var(--nc-space-lg);
  font-size: 15px;
}

/* ------------------------------------------------------------------- nav */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  backdrop-filter: blur(14px);
  background: color-mix(in srgb, var(--nc-color-background) 72%, transparent);
  border-bottom: 1px solid transparent;
  transition:
    border-color var(--nc-duration-base) ease,
    background-color var(--nc-duration-base) ease;
}

.nav.is-stuck {
  border-bottom-color: var(--nc-color-border);
}

.nav__inner {
  max-width: 1440px;
  margin: 0 auto;
  min-height: var(--nc-nav-height);
  padding: 0 clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--nc-space-lg);
}

/* The lockup: the mark, then the name.
 *
 * The `n` used to be set in text-secondary, a two-tone word standing in for a
 * symbol the product did not have. It has one now, so the mark carries the
 * identity and the word is just the name -- a grey `n` as well would be a
 * third thing competing for the same job.
 *
 * `assets/mark.svg` is GENERATED by scripts/prepare-app-icon.py from the same
 * constants as the app icon, so the header and the home screen cannot drift.
 * `pnpm icon:check` fails if it was hand-edited. */
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.42em;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.04em;
}

/* Sized in `em` on purpose: the wordmark drops to 17px below 420px, and a
   mark in fixed pixels would grow relative to the word exactly where the
   header is tightest. One rule, and the two cannot disagree. */
.wordmark__mark {
  display: block;
  width: 1.15em;
  height: 1.15em;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(var(--nc-space-lg), 2.5vw, var(--nc-space-xxl));
  font-size: 15px;
  color: var(--nc-color-text-secondary);
}

.nav__links a:not(.btn):hover {
  color: var(--nc-color-text-primary);
}

/* The nav on a phone.
 *
 * `.nav__links a:not(.btn) { display: none }` used to live here, and nothing
 * replaced it: below 720px the whole primary nav simply vanished, so the only
 * route from the home page to partners.html was scrolling roughly nine
 * thousand pixels to the footer. A hidden nav with no menu behind it is not a
 * responsive nav, it is a missing one.
 *
 * Rather than a disclosure button, the links move to their own row. That is
 * why `.nav__actions` is a sibling of `.nav__links` in the markup rather than
 * a child of it -- flex `order` cannot lift a child out of its parent. The
 * cost is a two-row header on a phone, which `--nc-nav-height` is raised to
 * account for so anchored sections still clear it.
 *
 * It needs no JavaScript, which matters: this file's promise is that the page
 * is fully navigable as a plain document, and a scripted menu would have made
 * the nav the one part of it that is not. */
@media (max-width: 720px) {
  :root {
    --nc-nav-height: 108px;
  }

  .nav__inner {
    flex-wrap: wrap;
    row-gap: var(--nc-space-xs);
    padding-top: var(--nc-space-sm);
    padding-bottom: var(--nc-space-sm);
    gap: var(--nc-space-sm);
  }

  /* Full width on its own row, and scrollable rather than wrapping again:
     partners.html carries five links, and a nav that reflows to three rows
     pushes the page down further than the links are worth. */
  .nav__links {
    order: 3;
    flex: 1 0 100%;
    gap: var(--nc-space-lg);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

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

  .nav__links a {
    white-space: nowrap;
  }

  /* Two pills and a wordmark do not fit at 375px at full size -- `get early
     access` ran off the right edge. Both shrink rather than one of them
     disappearing: a venue manager checking a booking from their phone is
     exactly the person who needs the door, and the stranger still needs the
     offer. */
  .nav__actions {
    gap: var(--nc-space-sm);
  }

  .nav__actions .btn--sm {
    min-height: 36px;
    padding: 0 var(--nc-space-md);
    font-size: 13.5px;
  }
}

/* At 320px the wordmark and the two pills came to 313px against 288px of
   content box, so they took a row each and the header became three rows of a
   fixed element on the smallest screen anybody uses. Shrinking both by a few
   points puts them back on one row, which is what `--nc-nav-height` above is
   sized for. */
@media (max-width: 420px) {
  .wordmark {
    font-size: 17px;
  }

  /* Below 420px the word comes off and the mark stands alone.
   *
   * This is what having a symbol buys: the lockup put ~27px back onto the
   * wordmark, and at 320px that pushed it and the two pills to a row each
   * again -- the exact regression the note above this block says was fixed
   * once. Shrinking the type a third time would have made the name small
   * before it made it fit.
   *
   * The accessible name survives because it comes from the anchor's own
   * aria-label, not from this text node. That is the difference between this
   * and `.home-out__label` in Studio, which is clipped rather than removed
   * because there the word IS the accessible name. */
  .wordmark__name {
    display: none;
  }

  .wordmark__mark {
    width: 26px;
    height: 26px;
  }

  .nav__actions .btn--sm {
    padding: 0 var(--nc-space-sm);
    font-size: 12.5px;
  }
}

/* ------------------------------------------------------------------ hero */

.hero {
  padding: clamp(120px, 16vh, 200px) 0 clamp(64px, 9vh, 120px);
}

.hero__headline-wrap {
  padding: 0 clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl));
  max-width: 1440px;
  margin: 0 auto clamp(48px, 7vh, 96px);
}

.hero__kicker {
  margin: 0 0 var(--nc-space-lg);
  font-size: var(--nc-type-caption-size);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--nc-color-text-secondary);
}

/* The signature move: lowercase, oversized, periodic. The period is a separate
 * element so it never wraps away from the word it belongs to. */
.hero__headline {
  margin: 0;
  font-size: clamp(56px, 13vw, 176px);
  line-height: 0.92;
  letter-spacing: -0.055em;
  font-weight: 700;
  display: flex;
  align-items: baseline;
}

.hero__rotator {
  position: relative;
  display: inline-grid;
  justify-items: start;
}

/* The words share one grid cell, so a plain crossfade renders both at once and
 * you read "art." through "music.". Staggered instead: the outgoing word clears
 * in `fast`, and the incoming one waits that long before it starts. */
.hero__word {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(0.18em);
  transition:
    opacity var(--nc-duration-base) ease,
    transform var(--nc-duration-base) cubic-bezier(0.2, 0.8, 0.2, 1);
}

.hero__word.is-active {
  opacity: 1;
  transform: none;
  transition-delay: var(--nc-duration-fast);
}

.hero__word.is-leaving {
  opacity: 0;
  transform: translateY(-0.18em);
  transition-duration: var(--nc-duration-fast);
  transition-delay: 0s;
}

.hero__sub {
  margin: var(--nc-space-xl) 0 0;
  font-size: clamp(17px, 2vw, 22px);
  color: var(--nc-color-text-secondary);
  max-width: 34ch;
}

/* ------------------------------------------------------------------ rail */

.rail {
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}

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

.rail__track {
  display: flex;
  gap: var(--nc-space-lg);
  padding: var(--nc-space-xl) clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl));
  width: max-content;
}

.rail__item {
  scroll-snap-align: center;
}

.poster {
  display: block;
  width: clamp(150px, 15vw, 210px);
  text-align: left;
  transition: transform var(--nc-duration-slow) cubic-bezier(0.2, 0.8, 0.2, 1);
}

.poster__art {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--nc-radius-md);
  border: 1px solid var(--nc-color-border);
  background: var(--nc-color-surface);
  transition:
    box-shadow var(--nc-duration-base) ease,
    border-color var(--nc-duration-base) ease,
    filter var(--nc-duration-base) ease;
  /* Photography is what carries this interface, so unselected cards are held
   * back rather than dressed up. The active one comes forward on its own. */
  filter: saturate(0.85) brightness(0.78);
}

.poster:hover .poster__art {
  filter: none;
}

.poster__title {
  display: block;
  margin-top: var(--nc-space-md);
  min-height: 2.6em;
  line-height: 1.3;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--nc-color-text-secondary);
  transition: color var(--nc-duration-base) ease;
}

/* Distance is always visible on an event surface. It is a UI convention here,
 * not a feature that some cards opt into. */
.poster__meta {
  display: block;
  margin-top: 2px;
  font-size: var(--nc-type-caption-size);
  color: var(--nc-color-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* The single cyan moment above the fold: the card currently being described. */
.poster.is-active {
  transform: translateY(-14px) scale(1.04);
}

.poster.is-active .poster__art {
  border-color: var(--nc-color-accent);
  box-shadow: 0 0 0 1px var(--nc-color-accent);
  filter: none;
}

.poster.is-active .poster__title {
  color: var(--nc-color-text-primary);
}

.hero__caption {
  max-width: 1440px;
  margin: 0 auto;
  padding: clamp(var(--nc-space-xl), 4vh, var(--nc-space-xxxl))
    clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl)) 0;
}

.hero__caption-title {
  margin: 0;
  font-size: clamp(24px, 3vw, 34px);
  letter-spacing: -0.03em;
  font-weight: 700;
}

.hero__caption-line {
  margin: var(--nc-space-sm) 0 0;
  color: var(--nc-color-text-secondary);
  font-variant-numeric: tabular-nums;
}

.hero__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nc-space-md);
  margin-top: var(--nc-space-xl);
}

/* The line under the hero: what this rail is, and how to stop it moving. */
.hero__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--nc-space-md);
  margin-top: var(--nc-space-xl);
}

.hero__note {
  margin: 0;
  flex: 1 1 24ch;
  max-width: 52ch;
  font-size: var(--nc-type-caption-size);
  color: var(--nc-color-text-secondary);
}

/* Quiet on purpose. It is a control somebody needs to be able to find, not one
   the page should be arguing for -- so it is legible and reachable and gets no
   more weight than that. Deliberately not cyan: that is reserved for live and
   urgent, and a pause button is neither. */
.motion-toggle {
  flex: 0 0 auto;
  min-height: var(--nc-min-touch-target);
  padding: 0 var(--nc-space-lg);
  border-radius: var(--nc-radius-pill);
  border: 1px solid var(--nc-color-border-strong);
  background: transparent;
  color: var(--nc-color-text-secondary);
  font: inherit;
  font-size: var(--nc-type-caption-size);
  cursor: pointer;
  transition:
    color var(--nc-duration-base) ease,
    border-color var(--nc-duration-base) ease;
}

.motion-toggle:hover {
  color: var(--nc-color-text-primary);
  border-color: var(--nc-color-primary);
}

/* The marquee is the one of the three that CSS drives, so CSS stops it. The
   headline word and the rail are both on the single timer in site.js, which
   the same button clears. */
html.is-motion-paused .marquee__track {
  animation-play-state: paused;
}

/* ----------------------------------------------------------------- beats */

.beats {
  position: relative;
  padding: clamp(80px, 12vh, 160px) clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl));
  max-width: 1440px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(var(--nc-space-xl), 5vw, 96px);
  align-items: start;
}

.beats__sticky {
  position: sticky;
  top: 42vh;
  display: grid;
  min-height: 4.5em;
}

.beats__copy {
  grid-area: 1 / 1;
  margin: 0;
  font-size: clamp(26px, 3.4vw, 46px);
  line-height: 1.12;
  letter-spacing: -0.035em;
  font-weight: 700;
  color: var(--nc-color-text-secondary);
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity var(--nc-duration-fast) ease,
    transform var(--nc-duration-fast) ease;
  pointer-events: none;
}

.beats__copy.is-active {
  opacity: 1;
  transform: none;
  color: var(--nc-color-text-primary);
  transition-duration: var(--nc-duration-base);
  transition-delay: var(--nc-duration-fast);
}

.beats__stage {
  display: grid;
  gap: clamp(80px, 22vh, 260px);
}

.beat {
  display: flex;
  justify-content: center;
  min-height: 60vh;
  align-items: center;
}

/* ---------------------------------------------------------------- phones */

/* The phone is a container, and everything inside it is sized in `cqw` or in
 * `em` derived from that. This is the whole trick: the mock scales as one
 * piece at any viewport width, so it cannot reach a size where the artwork has
 * shrunk but the type has not and the content no longer fits.
 *
 * The previous version mixed a fixed-height flex column with pixel type. Past a
 * certain width the cards flex-shrank, their bodies rendered outside their own
 * boxes, and `overflow: hidden` clipped the event title and distance clean off
 * the card. Proportional units remove the failure mode rather than patching the
 * width it happened at. */
.phone {
  /* The bezel is sized from this, not from `cqw`. An element cannot query
   * itself as a container: `container-type` below makes .phone a container for
   * its DESCENDANTS, so a `cqw` used here resolves against the nearest ancestor
   * container, and with none it silently falls back to the viewport. That is
   * how a 13cqw radius became 187px of pill on a 1440px screen instead of 44px
   * of phone corner. Descendants may use cqw freely; this element may not. */
  --phone-width: min(340px, 84vw);
  width: var(--phone-width);
  aspect-ratio: 9 / 17.5;
  container-type: inline-size;
  border-radius: calc(var(--phone-width) * 0.13);
  padding: calc(var(--phone-width) * 0.03);
  background: linear-gradient(160deg, #2a2a2a, #0c0c0c);
  box-shadow:
    0 0 0 1px var(--nc-color-border),
    0 40px 90px -30px rgba(0, 0, 0, 0.9);
}

.phone__screen {
  height: 100%;
  border-radius: 10.5cqw;
  background: var(--nc-color-background);
  overflow: hidden;
  font-size: 4.5cqw;
  padding: 7cqw 4.7cqw;
  display: flex;
  flex-direction: column;
  gap: 0.8em;
}

/* Nothing in here may shrink. A shrunk card is what pushed its own body out of
 * its clipping box. */
.phone__screen > * {
  flex: none;
}

.phone__screen--center {
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 1.1em;
}

.ui-chip-row {
  display: flex;
  gap: 0.45em;
  flex-wrap: wrap;
}

.ui-chip {
  padding: 0.35em 0.75em;
  border-radius: var(--nc-radius-pill);
  border: 1px solid var(--nc-color-border);
  background: var(--nc-color-surface);
  font-size: 0.82em;
  line-height: 1.35;
  color: var(--nc-color-text-secondary);
}

.ui-card {
  border-radius: 3.5cqw;
  overflow: hidden;
  background: var(--nc-color-surface);
  border: 1px solid var(--nc-color-border);
}

.ui-card--peek {
  opacity: 0.55;
}

/* Portrait, as the app's real event cards are. It also makes the second card
 * run past the bezel, which is what a feed looks like: the first card complete,
 * the next one obviously continuing below. */
.ui-card__art {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

.ui-card--peek .ui-card__art {
  aspect-ratio: 2 / 1;
}

.ui-card__body {
  padding: 0.75em;
}

.ui-card__title {
  margin: 0;
  font-weight: 600;
  font-size: 1em;
  line-height: 1.3;
  letter-spacing: -0.02em;
}

.ui-card__meta {
  margin: 0.15em 0 0;
  font-size: 0.84em;
  line-height: 1.4;
  color: var(--nc-color-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* The screen is an event screen, so it opens with the event: name, place,
 * distance. It also stops the mock reading as a caption floating in black. */
.ui-eventhead {
  margin: 0 0 0.9em;
}

.ui-eventhead__name {
  margin: 0;
  font-size: 1.25em;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.03em;
}

.ui-h {
  margin: 0;
  font-size: 1.45em;
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.ui-meta {
  margin: 0;
  font-size: 0.84em;
  line-height: 1.4;
  color: var(--nc-color-text-secondary);
  font-variant-numeric: tabular-nums;
}

.ui-meta--center {
  line-height: 1.6;
}

.ui-avatars {
  display: flex;
}

.ui-avatar {
  width: 2.4em;
  height: 2.4em;
  border-radius: var(--nc-radius-pill);
  background: var(--nc-color-surface-raised);
  border: 0.13em solid var(--nc-color-background);
  display: grid;
  place-items: center;
  font-size: 0.78em;
  font-weight: 600;
  color: var(--nc-color-text-secondary);
  margin-right: -0.6em;
}

.ui-avatar--more {
  background: var(--nc-color-surface);
  color: var(--nc-color-text-secondary);
}

.ui-safety {
  margin-top: 0.2em;
  padding: 1em;
  border-radius: 3.5cqw;
  background: var(--nc-color-surface);
  border: 1px solid var(--nc-color-border);
}

.ui-safety__head {
  margin: 0 0 0.75em;
  font-size: 1em;
  line-height: 1.3;
  font-weight: 600;
}

.ui-fact {
  margin: 0 0 0.5em;
  font-size: 0.84em;
  color: var(--nc-color-text-secondary);
  display: flex;
  gap: 0.5em;
  line-height: 1.4;
}

.ui-tick {
  color: var(--nc-color-text-primary);
  flex: none;
}

.ui-safety__foot {
  margin: 0.75em 0 0;
  padding-top: 0.75em;
  border-top: 1px solid var(--nc-color-border);
  font-size: 0.76em;
  color: var(--nc-color-text-secondary);
  line-height: 1.45;
}

/* Live/urgent: the sanctioned use of the accent. */
.ui-live {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.35em 0.75em;
  border-radius: var(--nc-radius-pill);
  background: var(--nc-color-accent-subtle);
  color: var(--nc-color-accent);
  font-size: 0.76em;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.ui-live__dot {
  width: 0.45em;
  height: 0.45em;
  border-radius: 50%;
  background: var(--nc-color-accent);
}

.ui-display {
  margin: 0;
  font-size: 2.1em;
  line-height: 1.05;
  letter-spacing: -0.04em;
  font-weight: 700;
}

/* Success is green *and* a tick *and* the word, never hue alone. That rule is
 * in tokens.json under nonColourCueRequired, and check-in is one of the three
 * surfaces it names. */
.ui-verified {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  color: var(--nc-color-success);
  font-weight: 600;
  font-size: 0.95em;
}

.ui-verified__mark {
  display: grid;
  place-items: center;
  width: 1.6em;
  height: 1.6em;
  border-radius: 50%;
  border: 0.1em solid var(--nc-color-success);
  font-size: 0.85em;
}

.ui-nudge {
  margin: 0;
  font-size: 0.84em;
  color: var(--nc-color-text-secondary);
}

/* Going solo is first-class in this product, so it is on the event screen
 * rather than buried. It is also real behaviour: the Welcome Group opens the
 * day before, which is what the app does. */
.ui-solo {
  padding: 0.85em 1em;
  border-radius: 3.5cqw;
  border: 1px solid var(--nc-color-border);
}

.ui-solo__h {
  margin: 0 0 0.2em;
  font-size: 0.95em;
  font-weight: 600;
}

/* `margin-top: auto` pushes the action bar to the bottom of the screen, which
 * is where an event screen actually puts it, and stops the mock reading as a
 * half-empty phone. */
.ui-actions {
  margin-top: auto;
  display: grid;
  gap: 0.5em;
  justify-items: center;
}

.ui-btn {
  display: grid;
  place-items: center;
  width: 100%;
  padding: 0.7em;
  border-radius: var(--nc-radius-pill);
  background: var(--nc-color-primary);
  color: var(--nc-color-on-primary);
  font-weight: 600;
  font-size: 0.95em;
}

.ui-actions__note {
  font-size: 0.78em;
  color: var(--nc-color-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ loop */

.loop {
  padding: clamp(80px, 12vh, 160px) clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl));
  max-width: 1440px;
  margin: 0 auto;
  border-top: 1px solid var(--nc-color-border);
}

.loop__steps {
  display: grid;
  /* `min(...)` rather than a bare pixel floor: a track that cannot shrink
     below its floor overflows a padded container at 320px, which is the
     horizontal body scroll WCAG 1.4.10 rules out. */
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: 1px;
  margin: clamp(48px, 7vh, 80px) 0 0;
  background: var(--nc-color-border);
  border: 1px solid var(--nc-color-border);
  border-radius: var(--nc-radius-md);
  overflow: hidden;
}

.loop__step {
  background: var(--nc-color-background);
  padding: var(--nc-space-xl) var(--nc-space-lg);
  font-size: clamp(17px, 1.5vw, 20px);
  font-weight: 600;
  letter-spacing: -0.02em;
  min-height: 148px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--nc-space-xl);
}

.loop__n {
  font-size: var(--nc-type-caption-size);
  font-weight: 500;
  color: var(--nc-color-text-secondary);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
}

/* Verified attendance is the spine of the product; it is the one step that
 * gets weight. */
.loop__step--spine {
  background: var(--nc-color-surface);
}

.loop__step--spine .loop__n {
  color: var(--nc-color-text-secondary);
}

.loop__anti {
  margin-top: clamp(48px, 6vh, 72px);
  padding: var(--nc-space-xl);
  border: 1px dashed var(--nc-color-border);
  border-radius: var(--nc-radius-md);
  max-width: 72ch;
}

.loop__anti-label {
  margin: 0;
  font-size: var(--nc-type-caption-size);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--nc-color-text-secondary);
}

.loop__anti-chain {
  margin: var(--nc-space-md) 0 var(--nc-space-lg);
  font-size: clamp(18px, 2.2vw, 26px);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--nc-color-text-secondary);
}

.loop__anti-chain s {
  text-decoration-thickness: 1px;
}

.loop__anti-note {
  margin: 0;
  color: var(--nc-color-text-secondary);
  font-size: 15px;
}

/* ------------------------------------------------------------- statement */

.statement {
  position: relative;
  isolation: isolate;
  /* Asymmetric on purpose. The section above it fades in from black, so the
   * top needs room; the section below now opens with the marquee's rule, and
   * a matching band of padding underneath put a third of a screen of nothing
   * between the last word of the statement and the first word of the next
   * section. */
  padding: clamp(120px, 26vh, 300px) clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl))
    clamp(64px, 11vh, 132px);
  background:
    linear-gradient(
      180deg,
      var(--nc-color-background) 0%,
      rgba(0, 0, 0, 0.42) 30%,
      rgba(0, 0, 0, 0.42) 82%,
      var(--nc-color-background) 100%
    ),
    url('../assets/photos/statement.jpg') center / cover no-repeat;
}

.statement__line {
  max-width: 1200px;
  margin: 0 auto;
  font-size: clamp(32px, 5.6vw, 84px);
  line-height: 1.04;
  letter-spacing: -0.04em;
  font-weight: 700;
  color: var(--nc-color-text-secondary);
  text-shadow: 0 2px 30px rgba(0, 0, 0, 0.8);
}

.statement__turn {
  display: block;
  color: var(--nc-color-text-primary);
}

/* ----------------------------------------------------------------- hosts */

.hosts,
.trust {
  padding: clamp(80px, 12vh, 160px) clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl));
  max-width: 1440px;
  margin: 0 auto;
}

/* The hosts section leads with the marquee rather than its headline.
 *
 * The headline used to be the first thing after the statement photograph, and
 * the photograph fades out before its own edge -- so "turn what you're good at
 * into somewhere to be." arrived alone in a tall band of black, reading as a
 * gap rather than as the start of something. Leading with the rule and the
 * moving type gives the eye the handover, and the headline then lands between
 * the marquee and the panels instead of floating between two voids.
 *
 * The marquee is aria-hidden, so the section still begins at its own h2 for
 * anybody not looking at it. */
.hosts {
  padding-top: 0;
}

.hosts .marquee {
  margin-top: 0;
}

.hosts__head .btn {
  margin-top: var(--nc-space-xl);
}

.marquee {
  margin: clamp(48px, 7vh, 88px) 0;
  overflow: hidden;
  border-block: 1px solid var(--nc-color-border);
  padding: var(--nc-space-lg) 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}

.marquee__track {
  display: flex;
  gap: var(--nc-space-xl);
  width: max-content;
  /* 24px is the floor deliberately: at this weight that is where WCAG
     large text begins, and tertiary (3.94:1 on black) only clears its 3:1
     bar there. It used to clamp to 20px, which is normal text and needs
     4.5:1. Dim is the point of this band, so it takes the size half of the
     remedy rather than the colour half. */
  font-size: clamp(24px, 2.6vw, 34px);
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--nc-color-text-tertiary);
  animation: marquee 46s linear infinite;
}

@keyframes marquee {
  to {
    transform: translateX(-50%);
  }
}

.trust .section-sub {
  margin-bottom: clamp(40px, 6vh, 64px);
}

/* --------------------------------------------- hosts: proof, on the picture */

/* The plates come first and the headline closes the section.
 *
 * Centred, because it is no longer a heading introducing what follows -- it is
 * the ask at the end. Left-aligned under a two-column grid it sat in the left
 * half with the right half empty, which reads as a column that failed to load
 * rather than as a deliberate close. */
.hosts__head {
  /* Roughly the section's own bottom padding, and for the same reason: this is
   * the seam between two different things -- the proof, and the ask -- not the
   * space inside one of them. At half that it read as the caption's own
   * trailing line rather than as the start of the close. */
  margin-top: clamp(72px, 13vh, 150px);
  text-align: center;
}

.hosts__head .section-h {
  /* The measure that keeps it to two lines has to stop centring it on the
   * left edge of the section. */
  margin-inline: auto;
}

.hosts__head .section-sub {
  margin-inline: auto;
}

.proof {
  display: grid;
  gap: var(--nc-space-lg);
  align-content: start;
}

.proof__plate {
  position: relative;
  border-radius: var(--nc-radius-lg);
  overflow: hidden;
  border: 1px solid var(--nc-color-border);
}

.proof__img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* The scrim covers the bottom band only, so the photograph survives above it.
 * The stops are the ones partners.css arrived at by measuring against the
 * brightest pixel behind the type -- anything softer fell under 4.5:1 on the
 * lighter plates. Keep them in step if either is retuned. */
.proof__h {
  position: absolute;
  inset: auto 0 0 0;
  margin: 0;
  padding: clamp(var(--nc-space-lg), 2.4vw, var(--nc-space-xl));
  font-size: clamp(21px, 2.2vw, 30px);
  line-height: 1.14;
  letter-spacing: -0.03em;
  font-weight: 700;
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.7);
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(0, 0, 0, 0.84) 26%,
    rgba(0, 0, 0, 0.95) 100%
  );
}

.proof__line {
  margin: 0;
  max-width: 44ch;
  color: var(--nc-color-text-secondary);
}

.proof__line strong {
  color: var(--nc-color-text-primary);
  font-weight: 600;
}

.hosts__grid,
.trust__grid {
  display: grid;
  /* See `.loop__steps`: a bare 300px floor overflows at 320px once the
     section padding is taken off. */
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: var(--nc-space-lg);
}

.panel {
  padding: clamp(var(--nc-space-xl), 3vw, var(--nc-space-xxl));
  border: 1px solid var(--nc-color-border);
  border-radius: var(--nc-radius-lg);
  background: var(--nc-color-surface);
}

.panel__h {
  margin: 0 0 var(--nc-space-lg);
  font-size: var(--nc-type-heading-size);
  line-height: 1.2;
  letter-spacing: -0.025em;
  font-weight: 600;
}

.panel p {
  margin: 0;
  color: var(--nc-color-text-secondary);
}

.trust__foot {
  margin: clamp(40px, 6vh, 64px) 0 0;
  max-width: 66ch;
  color: var(--nc-color-text-secondary);
  font-size: 15px;
}

/* ----------------------------------------------------------- four sides */

.sides {
  padding: clamp(80px, 12vh, 160px) clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl));
  max-width: 1440px;
  margin: 0 auto;
  border-top: 1px solid var(--nc-color-border);
}

/* 4, 2 or 1 across, never 3. There are four cards, so auto-fit will happily
 * leave one stranded on a second row beside an empty cell. Only divisors of
 * four are allowed to happen. */
.sides__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  margin-top: clamp(40px, 6vh, 64px);
  background: var(--nc-color-border);
  border: 1px solid var(--nc-color-border);
  border-radius: var(--nc-radius-lg);
  overflow: hidden;
}

/* One hairline grid rather than four floating cards: these are four faces of
 * one network, and separate cards would read as four products. */
.side {
  display: flex;
  flex-direction: column;
  gap: var(--nc-space-sm);
  padding: clamp(var(--nc-space-xl), 2.4vw, var(--nc-space-xxl));
  background: var(--nc-color-background);
  min-height: 200px;
  transition: background-color var(--nc-duration-base) ease;
}

.side:hover {
  background: var(--nc-color-surface);
}

.side__kicker {
  margin: 0;
  font-size: var(--nc-type-caption-size);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--nc-color-text-secondary);
}

.side__h {
  margin: 0;
  font-size: clamp(19px, 1.7vw, 23px);
  line-height: 1.2;
  letter-spacing: -0.03em;
  font-weight: 700;
}

.side__line {
  margin: auto 0 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--nc-color-text-secondary);
}

@media (min-width: 560px) {
  .sides__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1040px) {
  .sides__grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* ----------------------------------------------------------------- doors */

.doors {
  display: grid;
  /* A bare 320px floor is exactly the viewport at the narrowest width
     anybody tests, so the section padding pushed the page sideways every
     time. See `.loop__steps`. */
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--nc-space-lg);
  padding: 0 clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl)) clamp(80px, 12vh, 140px);
  max-width: 1440px;
  margin: 0 auto;
}

.door {
  display: block;
  padding: var(--nc-space-lg);
  border-radius: var(--nc-radius-lg);
  border: 1px solid var(--nc-color-border);
  transition:
    border-color var(--nc-duration-base) ease,
    transform var(--nc-duration-base) ease;
}

.door:hover {
  border-color: var(--nc-color-border-strong);
  transform: translateY(-4px);
}

.door__art {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--nc-radius-md);
  margin-bottom: var(--nc-space-xl);
  filter: brightness(0.8);
  transition: filter var(--nc-duration-slow) ease;
}

.door:hover .door__art {
  filter: brightness(1);
}

.door__kicker {
  display: block;
  font-size: var(--nc-type-caption-size);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--nc-color-text-secondary);
  margin-bottom: var(--nc-space-sm);
}

.door__title {
  display: block;
  font-size: clamp(26px, 3.4vw, 40px);
  letter-spacing: -0.035em;
  font-weight: 700;
}

/* ---------------------------------------------------------------- footer */

.footer {
  padding: clamp(80px, 14vh, 160px) clamp(var(--nc-space-lg), 4vw, var(--nc-space-xxxl))
    clamp(48px, 7vh, 80px);
  border-top: 1px solid var(--nc-color-border);
  max-width: 1440px;
  margin: 0 auto;
}

.footer__display {
  margin: 0;
  font-size: clamp(34px, 7vw, 104px);
  line-height: 0.98;
  letter-spacing: -0.045em;
  font-weight: 700;
}

.footer__display--dim {
  color: var(--nc-color-text-tertiary);
}

.signup {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nc-space-md);
  margin: clamp(40px, 6vh, 64px) 0 clamp(64px, 9vh, 112px);
  max-width: 560px;
}

.signup__input {
  flex: 1 1 260px;
  min-height: var(--nc-min-touch-target);
  padding: 0 var(--nc-space-lg);
  border-radius: var(--nc-radius-pill);
  border: 1px solid var(--nc-color-border-strong);
  background: var(--nc-color-surface);
  color: var(--nc-color-text-primary);
  font-size: var(--nc-type-label-size);
}

.signup__input::placeholder {
  color: var(--nc-color-text-secondary);
}

.signup__input:focus {
  outline: none;
  border-color: var(--nc-color-primary);
}

.signup__input[aria-invalid='true'] {
  border-color: var(--nc-color-danger);
}

.signup__status,
.signup__fine {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--nc-type-caption-size);
  color: var(--nc-color-text-secondary);
}

.signup__status:empty {
  display: none;
}

.signup__status[data-state='error'] {
  color: var(--nc-color-danger);
}

/* A social name with nowhere to go yet.
 *
 * An `<a>` without an `href` is already inert -- not focusable, not clickable
 * -- but it inherits the column's link styling and would look live. Dimmer,
 * and no pointer, so it reads as a name rather than as a link that is broken.
 * It brightens to a normal footer link the moment scripts/site.js finds a URL
 * for it. */
.footer__col a:not([href]) {
  color: var(--nc-color-text-secondary);
  cursor: default;
}

.footer__cols {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(var(--nc-space-xxl), 6vw, 96px);
  align-items: start;
  padding-top: var(--nc-space-xxl);
  border-top: 1px solid var(--nc-color-border);
}

.footer__col {
  display: grid;
  gap: var(--nc-space-sm);
  font-size: 15px;
  color: var(--nc-color-text-secondary);
}

/* `[href]` deliberately: the href-less social names sit in this column too,
   and now that they are legible rather than dimmed to 3.94:1 the only thing
   left telling them apart from a real link is that they do not respond. */
.footer__col a[href]:hover {
  color: var(--nc-color-text-primary);
}

.footer__col-h {
  margin: 0 0 var(--nc-space-xs);
  font-size: var(--nc-type-caption-size);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--nc-color-text-secondary);
}

.footer__mark {
  margin: 0 0 0 auto;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.04em;
  color: var(--nc-color-text-tertiary);
}

/* ----------------------------------------------------------- responsive */

@media (max-width: 900px) {
  .beats {
    grid-template-columns: 1fr;
    gap: var(--nc-space-xl);
    /* Stacked, the sticky copy already separates the hero from the first
     * phone. Keeping the desktop padding on top of that leaves most of a
     * viewport of empty black between them. */
    padding-top: var(--nc-space-xxl);
  }

  .hero {
    padding-bottom: var(--nc-space-xxl);
  }

  /* Stacked, the copy does NOT stick -- it reads as a list above the phones.
   *
   * It used to stick at the top with a gradient scrim behind it, on the
   * two-column idea that the phones scroll past a fixed line of copy. In one
   * column there is nowhere for the copy to go but ON the phone, and a phone
   * is mostly photograph: what that produced was a black band laid across a
   * picture of a 5k, with the runners showing above the band and below it.
   *
   * The scrim was tuned twice before it was clear that tuning was the wrong
   * move. A band over a photograph reads as a mistake at every opacity,
   * because the thing that reads as broken is the overlap, not its edge.
   *
   * This is the same answer, for the same reason, that the reduced-motion
   * block below already reaches: without room to pass each other, let them
   * scroll together and read as a list. Change the two together. */
  .beats__sticky {
    position: static;
    min-height: 0;
    background: none;
  }

  .beats__copy {
    grid-area: auto;
    opacity: 1;
    transform: none;
    /* All three at full strength. The crossfade dimmed the two that were not
       current, which says "this one is next" while they scroll past a fixed
       line; as a list it would just look like two of the three had failed to
       load. */
    color: var(--nc-color-text-primary);
    margin-bottom: var(--nc-space-lg);
  }

  .beat {
    min-height: auto;
  }

  .beats__stage {
    gap: clamp(56px, 12vh, 120px);
  }
}

/* ------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .marquee__track {
    animation: none;
    transform: none;
  }

  /* Without the crossfade the three lines stack, so a sticky block would sit
   * over the phones. Let them scroll with the page and read as a list. */
  .beats__sticky {
    position: static;
    min-height: 0;
    background: none;
  }

  .beats__copy {
    grid-area: auto;
    opacity: 1;
    transform: none;
    margin-bottom: var(--nc-space-lg);
  }

  .hero__word {
    grid-area: auto;
    opacity: 1;
    transform: none;
  }

  .hero__rotator {
    display: inline;
  }

  .hero__word:not(.is-active) {
    display: none;
  }

  .poster.is-active {
    transform: none;
  }
}

/* ══════════ the way in to Studio ══════════ */

/* Both of these ship with `hidden` and are revealed by site.js only when a
   Studio address is configured. `!important` because `hidden` loses to any
   `display` rule, and a stray one here would put a dead link in the nav of
   every deployment that has not stood Studio up yet. */
[hidden] {
  display: none !important;
}

/* An outlined pill beside the filled one, not a nav link.
 *
 * It was `text-secondary` at 14px sitting next to a large white button, which
 * made the door for people who already have an account the quietest thing in
 * the row -- easy to walk straight past. A ghost button is the same height and
 * shape as the primary, so it reads as an action, while the fill difference
 * keeps the hierarchy: early access is what a stranger wants, signing in is
 * what a customer wants, and a customer is looking for it.
 *
 * Being a `.btn` also means it survives the ≤720px rule that hides plain nav
 * links -- correct, because somebody who already runs a venue checking a
 * booking from their phone is exactly who needs it. */
.nav__signin {
  white-space: nowrap;
}

/* The two pills are a pair, not two more items in the link row.
 *
 * They were a child of `.nav__links` and are now its sibling, so that a phone
 * can put the links on their own row underneath without the buttons going with
 * them. Grouping them was always right for spacing -- the gap that separates
 * four text links is far too wide between two buttons sitting shoulder to
 * shoulder -- and being a sibling is what makes the responsive rule possible. */
.nav__actions {
  display: inline-flex;
  align-items: center;
  gap: var(--nc-space-sm);
}

.pcta__signin {
  margin-top: 1.75rem;
  font-size: 0.9375rem;
  color: var(--nc-color-text-secondary);
}

.pcta__signin a {
  color: var(--nc-color-text-primary);
  text-underline-offset: 0.25em;
}
