@charset "UTF-8";
/* =====================================================================
   E-Venz Africa Event Hub — design system
   Ported from the Tailwind theme (tailwind.config.ts + index.css):
   brand #c92737 / #1f1629, Lato, --radius 0.75rem, light + dark themes.
   Self-contained: no CDN, no build step.
   ===================================================================== */

:root {
  --primary: #c92737;
  --primary-600: #b3202f;
  --primary-50: #fef2f2;
  --primary-100: #fee2e2;
  --secondary: #1f1629;
  --secondary-800: #2c2038;

  --bg: #ffffff;
  --bg-alt: #f8f7fb;
  --fg: #101418;
  --muted: #e5e3ea;
  --muted-fg: #6b6880;
  --card: #ffffff;
  --card-fg: #101418;
  --border: #e6eaef;
  --input-bg: #f7f9fa;
  --ring: rgba(201, 39, 55, .35);
  --destructive: #ef2b41;
  --success: #12a05e;
  --warning: #d08700;

  /* Status colours split by the job they do, because one value cannot do
     both. A fill carries white text on it; a foreground sits on a card.
     --destructive at #ef2b41 gives white text only 4.12:1, so the fill
     is a shade deeper, and the foregrounds are set per theme below —
     no red is legible on both a white card and a near-black one.
     Every value here was measured, not chosen by eye. */
  --danger: #e11d33;          /* fill: 4.75 with white text            */
  --danger-fg: #b3151f;       /* text: 6.90 on a card, 5.69 on a tint  */
  --warning-fg: #8a5a00;      /* text: 5.93 on a card, 5.15 on a tint  */
  --ok: #1c7c43;              /* text: 5.23 on a card, 4.53 on a tint  */

  /* Secondary text has two jobs and one of them is harder: --muted-fg
     reads on a card, but a badge sits on --muted, and the same grey
     against that only reaches 4.21. */
  --muted-strong: #5b586e;    /* 5.38 on --muted                       */

  /* Fills for a whole panel of white text — the scanner's result banner
     is the case that matters, read on a phone at a door in daylight.
     --success and --warning are tuned as accents on a card and give
     white only 3.38 and 2.94, so a banner painted in them puts its
     small print below AA. These are the same hues carried deeper. */
  --success-fill: #0f7a48;    /* 5.39 with white text                  */
  --warning-fill: #b45309;    /* 5.02                                  */

  /* The brand as text. #c92737 reads on white at 5.48 but only 3.28 on
     a dark card, and 4.49 on the brand's own tint — where a badge puts
     it. So the word is carried a step deeper than the fill. */
  --primary-fg: #b3202f;      /* 6.64 on a card, 5.43 on a tint        */

  /* --success is a fill colour; as text on white it is only 3.38, which
     is why --ok exists rather than reusing it. */

  --accent: var(--primary);   /* one accent, and it is the brand        */
  --primary-rgb: 201, 39, 55; /* commas: rgba(var(--primary-rgb), .06)  */

  --radius: 0.75rem;
  --radius-sm: 0.5rem;
  --radius-lg: 1rem;

  --shadow-sm: 0 1px 2px rgba(16, 20, 24, .06);
  --shadow: 0 2px 8px rgba(16, 20, 24, .08);
  --shadow-lg: 0 12px 32px rgba(16, 20, 24, .12);

  /* ---- the type scale --------------------------------------------
     The interface range had grown 22 sizes between .58rem and 1rem —
     .84, .85, .86 and .87 all present, which nobody chose on purpose.
     Six steps now, each one a value already common in the file, so the
     largest move was .03rem: half a pixel, invisible on its own and
     the reason nothing lines up when it is repeated 150 times.

     Above 1rem is display type — hero headings, the scanner's verdict,
     a certificate's name — chosen per use and left alone. Snapping
     those would be a redesign, not a tidy-up. */
  --t-2xs:  0.68rem;   /* micro caps: chips, badges          */
  --t-xs:   0.74rem;   /* meta, timestamps                   */
  --t-sm:   0.8rem;    /* secondary lines                    */
  --t-md:   0.86rem;   /* dense table and card text          */
  --t-base: 0.93rem;   /* controls and body                  */
  --t-lg:   1rem;      /* the top of the interface range     */

  --font-sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* Glass: one set of tokens so every translucent surface agrees. Tuned
     separately per theme — the same alpha that reads as frosted on a
     light background reads as dirty on a dark one. */
  --glass-bg: rgba(255, 255, 255, 0.72);
  --glass-line: rgba(255, 255, 255, 0.55);
  --glass-shadow: 0 8px 32px rgba(16, 24, 40, 0.10);
  --glass-blur: 18px;

  --container: 1200px;
  --header-h: 68px;
}

html[data-theme="dark"] {
  --glass-bg: rgba(22, 20, 30, 0.62);
  --glass-line: rgba(255, 255, 255, 0.10);
  --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.42);

  /* The fills keep their value — white text still reads on them — but a
     status colour used as TEXT has to lighten against a #16161d card.
     The light-theme reds and ambers land at 2.3–3.0 here, which is why
     these are redefined rather than inherited. Measured on the card. */
  --danger-fg: #f4566a;       /* 5.47 on the card, 4.92 on a tint       */
  --warning-fg: #e0a92a;      /* 8.47 / 6.97                            */
  --ok: #34d399;              /* 9.36 / 8.02                            */
  --primary-fg: #e8636f;      /* 5.52 / 4.85 — the brand lifted to read */
  --muted-strong: #9b98ab;    /* 5.89 on --muted; the card value holds  */

  --bg: #0b0b0e;
  --bg-alt: #131319;
  --fg: #e8e9ea;
  --muted: #1e1e26;
  --muted-fg: #9b98ab;
  --card: #16161d;
  --card-fg: #e8e9ea;
  --border: #26262f;
  --input-bg: #1b1b23;
  --primary-50: #2a1418;
  --primary-100: #3a181e;
  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }

html { font-size: 16px; -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.65;
  color: var(--fg);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }
/* Links wear the brand as a word, not as a fill: --primary reaches
   only 3.28 against a dark card, so every body link on the site was
   under AA in dark mode. */
a { color: var(--primary-fg); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4, h5 { margin: 0 0 .5em; line-height: 1.25; font-weight: 700; letter-spacing: -.01em; }
h1 { font-size: 2.25rem; }
h2 { font-size: 1.75rem; }
h3 { font-size: 1.3rem; }
h4 { font-size: 1.1rem; }
p  { margin: 0 0 1em; }

hr { border: 0; border-top: 1px solid var(--border); margin: 2rem 0; }

/* ---------------------------------------------------------------- layout */
.container { max-width: var(--container); margin: 0 auto; padding: 0 20px; }
.container-narrow { max-width: 780px; margin: 0 auto; padding: 0 20px; }
.section { padding: 56px 0; }
.section-sm { padding: 32px 0; }
.section-alt { background: var(--bg-alt); }

.grid { display: grid; gap: 24px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-auto { grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); }

.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: 6px; } .gap-2 { gap: 12px; } .gap-3 { gap: 18px; }
.wrap { flex-wrap: wrap; }

/* On a phone a row of controls either wraps or shoves the page
   sideways, and sideways is the worse answer — the skill's own rule is
   that page-level horizontal scroll is a blocker. 92 of the 127 flex
   rows in the codebase already ask for wrap; the other 35 are rows
   nobody happened to open at 375px. A button row on Ticketing was one
   of them, reaching 421px inside a 360px viewport. */
@media (max-width: 640px) { .flex { flex-wrap: wrap; } }




.grow { flex: 1 1 auto; }

.text-center { text-align: center; }
.text-right { text-align: right; }
.muted { color: var(--muted-fg); }
.small { font-size: 0.86rem; }
.tiny { font-size: 0.8rem; }
.strong { font-weight: 700; }
.nowrap { white-space: nowrap; }
.mt-0 { margin-top: 0; } .mt-1 { margin-top: 8px; } .mt-2 { margin-top: 16px; } .mt-3 { margin-top: 24px; } .mt-4 { margin-top: 36px; }
.mb-0 { margin-bottom: 0; } .mb-1 { margin-bottom: 8px; } .mb-2 { margin-bottom: 16px; } .mb-3 { margin-bottom: 24px; } .mb-4 { margin-bottom: 36px; }

.section-head { margin-bottom: 32px; }
.section-head h2 { margin-bottom: 6px; }
.section-head p { color: var(--muted-fg); margin: 0; }

/* ---------------------------------------------------------------- header */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
}
.site-header .inner { display: flex; align-items: center; gap: 24px; height: var(--header-h); }

.brand { display: flex; align-items: center; gap: 10px; font-weight: 800; font-size: 1.1rem; color: var(--fg); text-decoration: none; letter-spacing: -.02em; }
.brand:hover { text-decoration: none; opacity: .85; }
.brand-mark {
  width: 32px; height: 32px; border-radius: 9px; flex: 0 0 auto;
  background: linear-gradient(135deg, var(--primary), var(--secondary));
  color: #fff; display: grid; place-items: center; font-size: 0.8rem; font-weight: 800;
}
.brand em { font-style: normal; color: var(--primary-fg); }

/* An uploaded logo stands in for the wordmark. Height-capped so a
   large upload cannot blow out the header. */
.brand-logo { max-height: 40px; width: auto; max-width: 220px; object-fit: contain; }

.nav { display: flex; align-items: center; gap: 22px; margin-left: 8px; }
.nav a { color: var(--muted-fg); font-weight: 600; font-size: 0.93rem; text-decoration: none; padding: 6px 0; border-bottom: 2px solid transparent; }
.nav a:hover { color: var(--fg); text-decoration: none; }
.nav a.active { color: var(--primary-fg); border-bottom-color: var(--primary); }

.header-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.nav-toggle, .theme-toggle {
  display: none; background: none; border: 1px solid var(--border);
  border-radius: var(--radius-sm); width: 38px; height: 38px;
  cursor: pointer; color: var(--fg); font-size: 1rem; line-height: 1;
}
.theme-toggle { display: grid; place-items: center; }
.theme-toggle:hover { background: var(--muted); }

@media (max-width: 900px) {
  .nav-toggle { display: grid; place-items: center; }
  .nav {
    display: none; position: absolute; top: var(--header-h); left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--bg); border-bottom: 1px solid var(--border);
    padding: 8px 20px 16px; box-shadow: var(--shadow);
  }
  .nav.open { display: flex; }
  .nav a { padding: 11px 0; border-bottom: 1px solid var(--border); }
  .nav a.active { border-bottom-color: var(--border); }
}

/* user menu */
.usermenu { position: relative; }
.usermenu-btn {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  background: var(--muted); border: 1px solid var(--border);
  border-radius: 999px; padding: 5px 12px 5px 5px; font: inherit; font-size: 0.86rem; font-weight: 600; color: var(--fg);
}
.usermenu-btn:hover { border-color: var(--primary); }
.avatar {
  width: 28px; height: 28px; border-radius: 50%; flex: 0 0 auto;
  background: var(--primary); color: #fff; display: grid; place-items: center;
  font-size: 0.8rem; font-weight: 700; text-transform: uppercase;
}
.usermenu-panel {
  display: none; position: absolute; right: 0; top: calc(100% + 8px); min-width: 220px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: 6px; z-index: 60;
}
.usermenu-panel.open { display: block; }
.usermenu-panel a, .usermenu-panel button {
  display: block; width: 100%; text-align: left; padding: 9px 12px; border-radius: var(--radius-sm);
  color: var(--fg); font-size: 0.93rem; font-weight: 500; background: none; border: 0; cursor: pointer; font-family: inherit;
}
.usermenu-panel a:hover, .usermenu-panel button:hover { background: var(--muted); text-decoration: none; }
.usermenu-head { padding: 10px 12px 8px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.usermenu-head strong { display: block; font-size: 0.93rem; }
.usermenu-head span { font-size: 0.8rem; color: var(--muted-fg); }
.usermenu-sep { height: 1px; background: var(--border); margin: 6px 0; }

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 20px; border-radius: var(--radius-sm);
  font-family: inherit; font-size: 0.93rem; font-weight: 600; line-height: 1.2;
  border: 1px solid transparent; cursor: pointer; text-decoration: none;
  transition: filter .15s, background .15s, border-color .15s;
  white-space: nowrap;
}
.btn:hover { text-decoration: none; }
.btn:focus-visible { outline: 3px solid var(--ring); outline-offset: 1px; }
.btn:disabled, .btn.disabled { opacity: .55; pointer-events: none; }

.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-600); color: #fff; }
.btn-secondary { background: var(--secondary); color: #fff; }
.btn-secondary:hover { background: var(--secondary-800); color: #fff; }
.btn-outline { background: transparent; color: var(--fg); border-color: var(--border); }
.btn-outline:hover { border-color: var(--primary); color: var(--primary-fg); }
.btn-ghost { background: transparent; color: var(--muted-fg); }
.btn-ghost:hover { background: var(--muted); color: var(--fg); }
.btn-danger { background: var(--destructive); color: #fff; }
.btn-danger:hover { filter: brightness(.92); color: #fff; }
.btn-sm { padding: 6px 13px; font-size: 0.86rem; }
.btn-lg { padding: 13px 28px; font-size: 1rem; }
.btn-block { width: 100%; }

/* ---------------------------------------------------------------- cards */
.card {
  background: var(--card); color: var(--card-fg);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); overflow: hidden;
}
.card-body { padding: 22px; }
.card-head { padding: 16px 22px; border-bottom: 1px solid var(--border); font-weight: 700; }
.card-foot { padding: 14px 22px; border-top: 1px solid var(--border); background: var(--bg-alt); }

/* event card */
.event-card { display: flex; flex-direction: column; height: 100%; transition: transform .18s, box-shadow .18s; }
.event-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.event-card .thumb {
  position: relative; aspect-ratio: 16 / 9; background: var(--muted);
  display: grid; place-items: center; overflow: hidden;
}
.event-card .thumb img { width: 100%; height: 100%; object-fit: cover; }
.event-card .thumb .placeholder {
  width: 100%; height: 100%; display: grid; place-items: center;
  background: linear-gradient(135deg, var(--secondary), var(--primary));
  color: #fff; font-size: 1.6rem; font-weight: 800; letter-spacing: .04em; opacity: .92;
}
.event-card .body { padding: 18px; display: flex; flex-direction: column; gap: 8px; flex: 1 1 auto; }
.event-card h3 { font-size: 1.05rem; margin: 0; }
.event-card h3 a { color: var(--fg); }
.event-card h3 a:hover { color: var(--primary-fg); text-decoration: none; }
.event-card .meta { display: flex; flex-direction: column; gap: 4px; font-size: 0.86rem; color: var(--muted-fg); }
.event-card .meta span { display: flex; align-items: center; gap: 7px; }
.event-card .foot { margin-top: auto; padding-top: 12px; display: flex; align-items: center; justify-content: space-between; gap: 10px; }

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 999px;
  font-size: 0.74rem; font-weight: 700; letter-spacing: .02em;
  background: var(--muted); color: var(--muted-strong); text-transform: uppercase;
}
/* A badge is a tint with a word on it, which is the hardest place to
   put a status colour: the accent that reads on a white card is sitting
   on its own pale self here. Measured on the composited tint, the
   accents gave 2.55 (warning), 2.92 (success), 3.40 (danger), 4.49
   (primary) and 4.21 (neutral) — the whole family under AA, on the
   component that appears on every event card. These are the same hues
   at the weight the ground needs. */
.badge-primary   { background: var(--primary-100); color: var(--primary-fg); }
.badge-featured  { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: #fff; }
.badge-success   { background: rgba(18,160,94,.13); color: var(--ok); }
.badge-warning   { background: rgba(208,135,0,.14); color: var(--warning-fg); }
.badge-danger    { background: rgba(239,43,65,.13); color: var(--danger-fg); }
.badge-free      { background: rgba(18,160,94,.13); color: var(--ok); }

.badge-float { position: absolute; top: 12px; left: 12px; z-index: 2; }
.badge-float-right { position: absolute; top: 12px; right: 12px; z-index: 2; left: auto; }

/* ------------------------------------------------------- event content
   Videos keep a 16:9 box whatever the provider does inside the iframe;
   sponsor logos are given equal room rather than equal width, because a
   wordmark and a roundel at the same width look nothing alike. */
.ev-videos { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.ev-video { margin: 0; }
.ev-video iframe { width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: 10px;
                   background: #000; display: block; }
.ev-video figcaption { font-size: 0.86rem; margin-top: 6px; }
.ev-video figcaption span { display: block; color: var(--muted-fg); font-size: 0.8rem; }

.ev-sponsors { display: flex; flex-wrap: wrap; gap: 18px; align-items: center; }
.ev-sponsor { display: grid; place-items: center; min-width: 120px; height: 56px;
              padding: 6px 12px; border: 1px solid var(--border); border-radius: 10px; }
.ev-sponsor img { max-height: 40px; max-width: 150px; object-fit: contain; }
.ev-sponsor span { font-size: 0.86rem; font-weight: 600; }

.ev-links { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.ev-links li a { font-weight: 600; }
.ev-links li span { display: block; font-size: 0.8rem; color: var(--muted-fg); }

/* ------------------------------------------------ what a ticket covers
   A priced tier is a promise about access, and the promise has to be
   readable at the moment of choosing. A tick per line rather than a
   paragraph: the eye compares two lists, it does not compare two
   sentences. */
.ticket-includes {
  list-style: none; margin: 8px 0 0; padding: 0;
  font-size: 0.8rem; color: var(--muted-fg); display: grid; gap: 3px;
}
.ticket-includes li { position: relative; padding-left: 18px; line-height: 1.35; }
.ticket-includes li::before {
  content: "\2713"; position: absolute; left: 0; top: 0;
  color: var(--success); font-weight: 700;
}
.ticket-includes li.count {
  padding-left: 18px; opacity: .75; font-variant-numeric: tabular-nums;
}
.ticket-includes li.count::before { content: "\00B7"; font-size: 1.2em; line-height: 1; }

/* ---------------------------------------------------------------- hero */
.hero {
  position: relative; color: #fff; overflow: hidden;
  background: linear-gradient(135deg, var(--secondary) 0%, #3a1f36 55%, var(--primary) 130%);
}
.hero .container { position: relative; z-index: 2; padding-top: 76px; padding-bottom: 76px; }
.hero h1 { font-size: clamp(2rem, 5vw, 3.1rem); margin-bottom: 14px; color: #fff; }
.hero p.lead { font-size: 1.1rem; max-width: 620px; color: rgba(255,255,255,.86); margin-bottom: 26px; }
.hero .actions { display: flex; gap: 12px; flex-wrap: wrap; }
.hero .btn-outline { color: #fff; border-color: rgba(255,255,255,.5); }
.hero .btn-outline:hover { background: rgba(255,255,255,.12); color: #fff; border-color: #fff; }
.hero-bg {
  position: absolute; inset: 0; z-index: 1; opacity: .22;
  background-size: cover; background-position: center;
}
.hero-stats { display: flex; gap: 38px; flex-wrap: wrap; margin-top: 38px; }
.hero-stats div strong { display: block; font-size: 1.9rem; line-height: 1.1; }
.hero-stats div span { font-size: 0.86rem; color: rgba(255,255,255,.72); }

/* ---- home hero ---------------------------------------------------------
   Copy left, the platform's own loop right. The decorative cards that
   used to fill the right-hand track are gone; what replaced them is a
   diagram of the product working, which earns the space the cards did
   not. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: 48px;
  align-items: center;
}
.hero-copy { min-width: 0; }
.hero-art  { min-width: 0; }

.hero-eyebrow {
  text-transform: uppercase;
  letter-spacing: .08em;
  font-size: 0.74rem;
  font-weight: 700;
  color: rgba(255,255,255,.62);
  margin-bottom: 14px;
}

.hero-search {
  display: flex;
  gap: 8px;
  max-width: 520px;
  margin-bottom: 18px;
}
.hero-search input {
  flex: 1 1 auto;
  min-width: 0;
  background: rgba(255,255,255,.12);
  border-color: rgba(255,255,255,.28);
  color: #fff;
}
.hero-search input::placeholder { color: rgba(255,255,255,.6); opacity: 1; }
.hero-search input:focus {
  background: rgba(255,255,255,.18);
  border-color: rgba(255,255,255,.7);
  box-shadow: 0 0 0 3px rgba(255,255,255,.16);
  color: #fff;
}
.hero-search .btn { flex: 0 0 auto; }

/* A text-weight action that still reads as a link on the dark hero. */
.btn-ghost-light {
  background: transparent;
  border: 1px solid transparent;
  color: rgba(255,255,255,.9);
}
.btn-ghost-light:hover { color: #fff; background: rgba(255,255,255,.1); }


/* Three flyers fanned out.

   This once declared aspect-ratio: 1/1 on the assumption that the source
   images were square. Real flyers are portrait — the ones on this
   platform are 650x900 — so a card rendered 30px taller than its
   container and sat on top of the caption beneath it.

   The fix is to stop assuming: the first card sits in normal flow and
   gives the stack its height, whatever shape the artwork is, and the
   others overlay it. The padding is the room the fan's lower cards need
   for their offsets. */
.flyer-stack {
  position: relative;
  max-width: 400px;
  margin: 0 auto;
  padding-bottom: 8%;
}

/* The room the fan needs below depends on how far its lowest card is
   offset, which depends on how many there are. */
.flyer-stack.stack-2 { padding-bottom: 13%; }
.flyer-stack.stack-3 { padding-bottom: 19%; }
.flyer-card {
  position: absolute;
  width: 74%;
  border-radius: var(--radius);
  box-shadow: 0 18px 44px rgba(0,0,0,.45);
  border: 1px solid rgba(255,255,255,.14);
  transition: transform .25s ease;
}
.flyer-card-1 { position: relative; top: 6%; left: 0; transform: rotate(-7deg); z-index: 1; }
.flyer-card-2 { top: 0;   left: 13%;  transform: rotate(-1deg); z-index: 3; }
.flyer-card-3 { top: 12%; left: 26%;  transform: rotate(6deg);  z-index: 2; }

.flyer-stack:hover .flyer-card-1 { transform: rotate(-11deg) translateX(-7px); }
.flyer-stack:hover .flyer-card-3 { transform: rotate(10deg) translateX(7px); }

/* Fewer than three published designs: centre what there is rather than
   leaving the fan leaning into empty space. */
.stack-1 .flyer-card-1 {
  top: 4%; left: 13%; width: 74%; transform: rotate(-2deg);
}
.stack-1:hover .flyer-card-1 { transform: rotate(-4deg); }

.stack-2 .flyer-card-1 { top: 8%; left: 2%;  transform: rotate(-6deg); }
.stack-2 .flyer-card-2 { top: 2%; left: 22%; transform: rotate(3deg); }
.stack-2:hover .flyer-card-1 { transform: rotate(-9deg) translateX(-6px); }
.stack-2:hover .flyer-card-2 { transform: rotate(6deg) translateX(6px); }

/* The showcase row is a 3-up grid; fewer images should not stretch. */


@media (max-width: 900px) {
  .flyer-stack { max-width: 300px; }
}

/* ---- value proposition band ------------------------------------------ */
.value-band { padding-top: 46px; padding-bottom: 46px; }
.value h3 { font-size: 1.06rem; margin-bottom: 6px; }
.value-icon {
  font-size: 1.5rem;
  width: 46px; height: 46px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius);
  background: var(--primary-50);
  margin-bottom: 12px;
}

/* ---- flyer showcase --------------------------------------------------- */
.showcase { background: var(--secondary); color: #fff; }
.showcase h2 { color: #fff; }
.showcase .muted { color: rgba(255,255,255,.72); }

/* .showcase-grid now lives with the flyer stack, at the end of this file. */

.eyebrow {
  text-transform: uppercase;
  letter-spacing: .08em;
  font-size: 0.74rem;
  font-weight: 700;
  color: var(--primary-fg);
  margin-bottom: 10px;
}
.showcase .eyebrow { color: #ff8a95; }

.steps { list-style: none; counter-reset: step; padding: 0; margin: 26px 0; }
.steps li {
  counter-increment: step;
  position: relative;
  padding-left: 44px;
  margin-bottom: 18px;
  font-size: 0.93rem;
  color: rgba(255,255,255,.72);
}
.steps li::before {
  content: counter(step);
  position: absolute; left: 0; top: -2px;
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--primary);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
}
.steps li strong { display: block; color: #fff; margin-bottom: 3px; font-size: 1rem; }

/* Stagger the middle one so the row reads as a set, not a filmstrip. */

@media (max-width: 900px) {
}

/* ---- categories with counts ------------------------------------------- */
.cat-list { display: flex; flex-wrap: wrap; gap: 10px; }
.cat {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 9px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--fg);
  font-size: 0.93rem;
  font-weight: 600;
  transition: border-color .15s, background .15s, transform .15s;
}
.cat:hover {
  border-color: var(--primary);
  background: var(--primary-50);
  color: var(--primary-fg);
  transform: translateY(-1px);
}
.cat-count {
  font-size: 0.74rem;
  font-weight: 700;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--muted);
  color: var(--muted-fg);
}
.cat:hover .cat-count { background: var(--primary); color: #fff; }

/* ---- closing call to action ------------------------------------------- */
.cta-band .actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; }
.justify-center { justify-content: center; }

/* event detail hero */
.event-hero { position: relative; background: var(--secondary); color: #fff; }
.event-hero .cover { position: absolute; inset: 0; opacity: .35; }
.event-hero .cover img { width: 100%; height: 100%; object-fit: cover; }
.event-hero .container { position: relative; z-index: 2; padding: 60px 20px; }
.event-hero h1 { color: #fff; font-size: clamp(1.7rem, 4vw, 2.6rem); }
.event-logo {
  width: 78px; height: 78px; border-radius: var(--radius); background: #fff;
  padding: 7px; box-shadow: var(--shadow); flex: 0 0 auto; margin-bottom: 16px;
}
.event-logo img { width: 100%; height: 100%; object-fit: contain; }
.event-meta-row { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 16px; font-size: 0.93rem; color: rgba(255,255,255,.9); }
.event-meta-row span { display: flex; align-items: center; gap: 8px; }

/* ---------------------------------------------------------------- forms */
.field { margin-bottom: 16px; }
label { display: block; font-size: 0.86rem; font-weight: 700; margin-bottom: 6px; color: var(--fg); }
label .req { color: var(--primary-fg); }
label .hint { font-weight: 400; color: var(--muted-fg); }

.input, input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=time], input[type=url], input[type=tel], input[type=search],
select, textarea {
  /* width:100% is not enough for a <select>: its intrinsic width comes
     from the longest option, and as a flex child min-width:auto stops
     it shrinking below that. An event picker holding "AfriLabs Annual
     Gathering 2026 -Cotonou" measured 446px inside a 360px viewport
     and pushed the whole page sideways. */
  width: 100%; max-width: 100%; min-width: 0; padding: 10px 13px;
  font-family: inherit; font-size: 0.93rem; color: var(--fg);
  background: var(--input-bg); border: 1px solid var(--border);
  border-radius: var(--radius-sm); transition: border-color .15s, box-shadow .15s;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--ring); background: var(--bg);
}
input::placeholder, textarea::placeholder { color: var(--muted-fg); opacity: .75; }
textarea { min-height: 120px; resize: vertical; }
select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%236b6880' d='M1 1l5 5 5-5'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 13px center; padding-right: 34px; }

.check { display: flex; align-items: flex-start; gap: 9px; font-size: 0.93rem; font-weight: 400; margin-bottom: 12px; }
.check input { width: auto; margin: 3px 0 0; flex: 0 0 auto; }
.check label { font-weight: 400; margin: 0; font-size: 0.93rem; }

.field-error { color: var(--destructive); font-size: 0.8rem; margin-top: 5px; }
.input-error { border-color: var(--destructive) !important; }

.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 640px) { .form-row { grid-template-columns: 1fr; } }

/* filter bar */
.filters { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 28px; }
.filters .field { margin: 0; min-width: 170px; }
.filters .grow { min-width: 220px; }

/* ---------------------------------------------------------------- alerts */
.alert { padding: 12px 16px; border-radius: var(--radius-sm); font-size: 0.93rem; margin-bottom: 18px; border: 1px solid; }
/* An alert is a tint with a paragraph on it, so the same rule as the
   badges: the accent that reads on a card does not read on a wash of
   itself. The warning alert measured 2.69 — the worst on any screen. */
.alert-success { background: rgba(18,160,94,.09); border-color: rgba(18,160,94,.3); color: var(--ok); }
.alert-error   { background: rgba(239,43,65,.08); border-color: rgba(239,43,65,.3); color: var(--danger-fg); }
.alert-info    { background: var(--primary-50); border-color: var(--primary-100); color: var(--primary-fg); }
.alert-warning { background: rgba(208,135,0,.09); border-color: rgba(208,135,0,.3); color: var(--warning-fg); }

/* ------------------------------------------------- form answer pills
   Scales, star ratings and NPS render as a row of selectable pills:
   the input hides, its label wears the state. */
.pillrow { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.pillrow-cap { font-size: 0.74rem; color: var(--muted-fg); padding: 0 4px; }
.pill-opt { position: relative; cursor: pointer; margin: 0; }
.pill-opt input { position: absolute; opacity: 0; pointer-events: none; }
.pill-opt span {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 38px; padding: 7px 10px; border: 1px solid var(--border);
  border-radius: 9px; font-size: 0.86rem; font-variant-numeric: tabular-nums;
  background: var(--card); transition: border-color .12s, background .12s;
}
.pill-opt:hover span { border-color: var(--fg); }
.pill-opt input:checked + span {
  border-color: var(--primary); background: var(--primary-50);
  color: var(--primary-fg); font-weight: 700;
}
.pill-opt input:focus-visible + span { outline: 3px solid var(--ring); outline-offset: 1px; }
table.likert td, table.likert th { padding: 8px 10px; }
table.likert input[type="radio"] { width: 17px; height: 17px; }

/* ------------------------------------------------------------- toasts
   The flash channel, delivered where the eye already is. A stack in
   the corner, each with a tone icon and its own exit; the inline
   .alert stays for content that belongs in the page flow. */
.toasts {
  position: fixed; right: 18px; bottom: 18px; z-index: 130;
  display: flex; flex-direction: column; gap: 10px;
  max-width: min(430px, calc(100vw - 36px));
}
.toast {
  display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px;
  border-radius: 10px; border: 1px solid var(--border);
  background: var(--card); color: var(--card-fg);
  box-shadow: 0 12px 34px rgba(31, 22, 41, .22);
  font-size: 0.93rem; line-height: 1.45;
  animation: toast-in .3s cubic-bezier(.2, .85, .3, 1.15) both;
}
.toast-success { border-left: 3px solid var(--success); }
.toast-error   { border-left: 3px solid var(--destructive); }
.toast-info    { border-left: 3px solid var(--primary); }
.toast-ic { width: 19px; height: 19px; flex: 0 0 19px; margin-top: 1px; }
.toast-success .toast-ic { color: var(--success); }
.toast-error   .toast-ic { color: var(--destructive); }
.toast-info    .toast-ic { color: var(--primary-fg); }
.toast-msg { flex: 1 1 auto; min-width: 0; overflow-wrap: break-word; }
.toast-x {
  background: none; border: 0; cursor: pointer; padding: 0 2px;
  color: var(--muted-fg); font-size: 1.15rem; line-height: 1;
}
.toast-x:hover { color: var(--fg); }
.toast-out { animation: toast-out .22s ease both; }
@keyframes toast-in  { from { opacity: 0; transform: translateY(16px) scale(.97); } }
@keyframes toast-out { to   { opacity: 0; transform: translateY(10px); } }
@media (max-width: 700px) {
  /* Full-width above the thumb, clear of the drawer button. */
  .toasts { left: 14px; right: 14px; bottom: 14px; max-width: none; }
}
@media (prefers-reduced-motion: reduce) {
  .toast, .toast-out { animation: none; }
}

/* ---------------------------------------------------------------- misc */
.empty { text-align: center; padding: 60px 20px; color: var(--muted-fg); }
.empty .icon { font-size: 2.4rem; opacity: .35; margin-bottom: 10px; }
.empty .icon .ni { width: 44px; height: 44px; color: var(--muted-fg); }
.badge .ni { vertical-align: -2px; margin-right: 1px; }
.empty h3 { color: var(--fg); }

.pagination { display: flex; gap: 6px; justify-content: center; margin-top: 36px; flex-wrap: wrap; }
.pagination a, .pagination span {
  min-width: 38px; height: 38px; padding: 0 12px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--fg); font-size: 0.86rem; font-weight: 600; text-decoration: none;
}
.pagination a:hover { border-color: var(--primary); color: var(--primary-fg); text-decoration: none; }
.pagination .current { background: var(--primary); border-color: var(--primary); color: #fff; }
.pagination .gap { border: 0; }

/* Long-form text sits in the common 1200px frame like every other
   page, but the words themselves keep a readable line: a full-width
   paragraph runs to about 150 characters, which the eye loses. */
.prose { font-size: 1rem; line-height: 1.75; max-width: 72ch; }
.prose img { border-radius: var(--radius); margin: 1.4em 0; }
.prose h2 { font-size: 1.45rem; margin-top: 1.8em; }
.prose h3 { font-size: 1.2rem; margin-top: 1.5em; }
.prose ul, .prose ol { padding-left: 1.4em; margin-bottom: 1.2em; }
.prose li { margin-bottom: .4em; }
.prose blockquote {
  margin: 1.4em 0; padding: 4px 0 4px 18px;
  border-left: 3px solid var(--primary); color: var(--muted-fg); font-style: italic;
}
.prose table { width: 100%; border-collapse: collapse; margin: 1.4em 0; }
.prose th, .prose td { border: 1px solid var(--border); padding: 9px 12px; text-align: left; }
.prose a { text-decoration: underline; }

.tag-list { display: flex; flex-wrap: wrap; gap: 7px; }
.tag { padding: 3px 10px; border-radius: 999px; background: var(--muted); color: var(--muted-strong); font-size: 0.74rem; font-weight: 600; }
a.tag:hover { background: var(--primary-100); color: var(--primary-fg); text-decoration: none; }

.sidebar-sticky { position: sticky; top: calc(var(--header-h) + 20px); }

.detail-grid { display: grid; grid-template-columns: minmax(0,1fr) 340px; gap: 32px; align-items: start; }
/* minmax(0,1fr), not 1fr. A bare 1fr means minmax(auto,1fr), so the grid
   item keeps min-width:auto and refuses to shrink below its content —
   which is how a horizontally scrolling strip inside this grid pushed
   the whole page sideways on a phone. The desktop rule above already
   had this right; the mobile one did not. */
@media (max-width: 940px) { .detail-grid { grid-template-columns: minmax(0, 1fr); } }

.info-list { list-style: none; margin: 0; padding: 0; }
.info-list li { display: flex; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--border); font-size: 0.93rem; }
.info-list li:last-child { border-bottom: 0; }
.info-list .k { color: var(--muted-fg); flex: 0 0 92px; }
.info-list .v { font-weight: 600; word-break: break-word; }

/* ---------------------------------------------------------------- footer */
.site-footer { background: var(--secondary); color: rgba(255,255,255,.72); margin-top: 64px; }
.site-footer a { color: rgba(255,255,255,.72); text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: none; }
.site-footer .cols { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1.4fr; gap: 36px; padding: 52px 0 36px; }
.site-footer h4 { color: #fff; font-size: 0.8rem; text-transform: uppercase; letter-spacing: .08em; margin-bottom: 14px; }
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: 9px; font-size: 0.93rem; }
.site-footer .brand { color: #fff; margin-bottom: 12px; }
.site-footer .bottom {
  border-top: 1px solid rgba(255,255,255,.12); padding: 20px 0;
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; font-size: 0.86rem;
}
.social { display: flex; gap: 10px; margin-top: 14px; }
.social a {
  width: 34px; height: 34px; border-radius: 50%; background: rgba(255,255,255,.1);
  display: grid; place-items: center; font-size: 0.8rem; font-weight: 700; color: #fff;
}
.social a:hover { background: var(--primary); }

.newsletter { display: flex; gap: 8px; margin-top: 12px; }
.newsletter input { background: rgba(255,255,255,.09); border-color: rgba(255,255,255,.18); color: #fff; }
.newsletter input::placeholder { color: rgba(255,255,255,.5); }
.newsletter input:focus { background: rgba(255,255,255,.14); border-color: #fff; box-shadow: none; }

@media (max-width: 900px) { .site-footer .cols { grid-template-columns: 1fr 1fr; gap: 28px; } }
@media (max-width: 560px) { .site-footer .cols { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------- auth pages */
.auth-wrap { min-height: calc(100vh - var(--header-h)); display: grid; place-items: center; padding: 48px 20px; background: var(--bg-alt); }
.auth-card { width: 100%; max-width: 440px; }
.auth-card.wide { max-width: 620px; }
.auth-head { text-align: center; margin-bottom: 26px; }
.auth-head h1 { font-size: 1.6rem; margin-bottom: 6px; }
.auth-head p { color: var(--muted-fg); margin: 0; font-size: 0.93rem; }
.auth-foot { text-align: center; margin-top: 20px; font-size: 0.93rem; color: var(--muted-fg); }

.role-switch { display: flex; gap: 0; background: var(--muted); border-radius: var(--radius-sm); padding: 4px; margin-bottom: 20px; }
.role-switch label {
  flex: 1; text-align: center; padding: 8px; border-radius: calc(var(--radius-sm) - 2px);
  font-size: 0.86rem; font-weight: 600; cursor: pointer; margin: 0; color: var(--muted-fg);
}
.role-switch input { display: none; }
.role-switch input:checked + label { background: var(--card); color: var(--primary-fg); box-shadow: var(--shadow-sm); }

.pw-meter { height: 4px; border-radius: 2px; background: var(--muted); margin-top: 7px; overflow: hidden; }
.pw-meter i { display: block; height: 100%; width: 0; transition: width .2s, background .2s; }

/* ---------------------------------------------------------------- pricing */
.price-card { display: flex; flex-direction: column; height: 100%; position: relative; }
.price-card.featured { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary), var(--shadow); }
.price-card .ribbon {
  position: absolute; top: 0; right: 22px; background: var(--primary); color: #fff;
  font-size: 0.68rem; font-weight: 700; padding: 4px 11px; border-radius: 0 0 6px 6px; text-transform: uppercase; letter-spacing: .05em;
}
.price-card .amount { font-size: 2.3rem; font-weight: 800; line-height: 1; margin: 12px 0 4px; }
.price-card .amount small { font-size: 0.93rem; font-weight: 500; color: var(--muted-fg); }
.price-card ul { list-style: none; margin: 18px 0; padding: 0; flex: 1 1 auto; }
.price-card li { padding: 7px 0 7px 26px; position: relative; font-size: 0.93rem; }
.price-card li::before { content: '✓'; position: absolute; left: 0; color: var(--success); font-weight: 700; }
.price-card li.off { color: var(--muted-fg); }
.price-card li.off::before { content: '×'; color: var(--muted-fg); }

/* ---------------------------------------------------------------- responsive */
@media (max-width: 1000px) { .grid-4 { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 860px)  { .grid-3 { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 620px)  {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
  .section { padding: 40px 0; }
  h1 { font-size: 1.75rem; }
  h2 { font-size: 1.4rem; }
}

@media print {
  .site-header, .site-footer, .no-print { display: none !important; }
  body { background: #fff; }
}

/* =====================================================================
   Dashboard shell (client + admin)
   ===================================================================== */
.dash { display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: 0; min-height: calc(100vh - var(--header-h)); }

.dash-side {
  background: var(--bg-alt); border-right: 1px solid var(--border);
  padding: 22px 0; position: sticky; top: var(--header-h); align-self: start;
  max-height: calc(100vh - var(--header-h)); overflow-y: auto;
}
.dash-side h5 {
  display: flex; align-items: center; gap: 7px;
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted-fg); margin: 20px 20px 8px; font-weight: 700;
}
.dash-side .dash-group:first-of-type h5 { margin-top: 0; }
/* Each group wears its colour twice: a small tile beside the heading,
   and the accent every active link inside it inherits. The eye learns
   the regions — red is the event, violet is the studio, teal is the
   day itself — without reading a single label. */
.dash-side h5 .gdot {
  width: 7px; height: 7px; border-radius: 2.5px; flex: 0 0 auto;
  background: var(--g, var(--muted-fg));
}
.dash-side a {
  display: flex; align-items: center; gap: 10px; padding: 9px 20px;
  color: var(--fg); font-size: 0.93rem; font-weight: 500; text-decoration: none;
  border-left: 3px solid transparent;
}
.dash-side .nav-ic {
  width: 17px; height: 17px; flex: 0 0 17px;
  color: var(--muted-fg); transition: color .15s ease;
}
.dash-side a:hover { background: var(--muted); text-decoration: none; }
.dash-side a:hover .nav-ic { color: var(--fg); }
.dash-side a.active {
  border-left-color: var(--g, var(--primary)); color: var(--fg);
  background: var(--primary-50);
  background: color-mix(in srgb, var(--g, var(--primary)) 10%, transparent);
  font-weight: 700;
}
.dash-side a.active .nav-ic { color: var(--g, var(--primary)); }
.dash-side .count { margin-left: auto; font-size: 0.74rem; background: var(--primary); color: #fff; border-radius: 999px; padding: 1px 7px; font-weight: 700; }

.dash-main { padding: 28px 30px 60px; min-width: 0; }
.dash-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin-bottom: 24px; }
.dash-head h1 { font-size: 1.6rem; margin: 0 0 4px; }
.dash-head p { margin: 0; color: var(--muted-fg); font-size: 0.93rem; }

/* The Menu button and the scrim exist only on small screens. */
.dash-menu { display: none; }
.dash-scrim { display: none; }

@media (max-width: 860px) {
  .dash { grid-template-columns: 1fr; }

  /* A drawer, not a squeezed sidebar: the full grouped navigation slides
     in from the left over a scrim. It runs on the #dashNav checkbox so
     it works with scripting off; the sidebar keeps its group headings
     and the signed-in identity block, exactly as on desktop. */
  .dash-side {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 70;
    width: min(300px, 84vw); max-height: none; padding: 18px 0 30px;
    border-right: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
    transform: translateX(-105%);
    transition: transform .22s ease;
  }
  .dash-nav-toggle:checked ~ .dash .dash-side { transform: translateX(0); }

  .dash-scrim {
    position: fixed; inset: 0; z-index: 60;
    background: rgba(0, 0, 0, .45);
    opacity: 0; pointer-events: none; transition: opacity .22s ease;
  }
  .dash-nav-toggle:checked ~ .dash .dash-scrim { display: block; opacity: 1; pointer-events: auto; }

  .dash-menu {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: 0.86rem; font-weight: 600; color: var(--fg);
    border: 1px solid var(--border); border-radius: 99px;
    padding: 7px 16px; margin-bottom: 14px; cursor: pointer;
    background: var(--card); min-height: 40px;
  }

  /* Fingers, not cursors. */
  .dash-side a { padding: 12px 20px; min-height: 44px; }
  .dash-main { padding: 20px; }
}

/* ---- signed-in identity, pinned under the sidebar nav ----------------- */
.dash-user {
  margin: 22px 14px 0;
  padding: 12px 14px;
  border-top: 1px solid var(--border);
  display: flex; align-items: center; gap: 10px;
}
.dash-user .avatar {
  width: 34px; height: 34px; flex: 0 0 auto;
  border-radius: 50%;
  background: var(--primary);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 0.86rem;
}
.dash-user .who { min-width: 0; line-height: 1.25; }
.dash-user .who strong {
  display: block; font-size: 0.86rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dash-user .who span { font-size: 0.74rem; color: var(--muted-fg); }
.dash-user .signout-form { margin-left: auto; flex: 0 0 auto; }
.dash-user a.signout,
.dash-user button.signout {
  margin-left: auto; flex: 0 0 auto;
  font-size: 0.74rem; color: var(--muted-fg); padding: 4px;
  background: none; border: 0; cursor: pointer; font-family: inherit;
}
.dash-user a.signout:hover,
.dash-user button.signout:hover { color: var(--primary-fg); }

/* ---- section headings ------------------------------------------------
   Every dashboard page repeated the same flex row with an inline-styled
   h2. One class instead, so they cannot drift apart. */
.section-title {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  margin: 0 0 14px;
}
.section-title h2 { font-size: 1.15rem; margin: 0; }
.section-title p { margin: 2px 0 0; font-size: 0.86rem; color: var(--muted-fg); }
.section-title + .table-wrap, .section-title + .grid { margin-top: 0; }

/* stat tiles */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 14px; margin-bottom: 26px; }
.stat {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 18px;
  display: block;
}
.stat .label { font-size: 0.74rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted-fg); font-weight: 700; }
.stat .value { font-size: 1.7rem; font-weight: 800; line-height: 1.2; margin-top: 3px; }
.stat .value.sm { font-size: 1.3rem; }

/* A badge riding inside a table cell next to text. */
.badge-xs { font-size: 0.68rem; }
.stat .sub { font-size: 0.8rem; color: var(--muted-fg); }
.stat.accent { border-color: var(--primary); }
.stat.accent .value { color: var(--primary-fg); }

/* A tile that leads somewhere should say so on hover. */
a.stat { color: inherit; text-decoration: none; transition: border-color .15s, box-shadow .15s, transform .15s; }
a.stat:hover {
  text-decoration: none;
  border-color: var(--primary);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
a.stat:hover .value { color: var(--primary-fg); }

/* Work that is actually waiting on someone, as opposed to a number that is
   merely informational. */
.stat.attention {
  border-color: var(--warning);
  background: linear-gradient(0deg, color-mix(in srgb, var(--warning) 7%, var(--card)), var(--card));
}
.stat.attention .value { color: var(--warning); }
.stat.attention .label::after {
  content: " •";
  color: var(--warning);
}

/* ---- activity feed inside a card -------------------------------------- */
.feed { display: flex; flex-direction: column; }
.feed-item { padding: 12px 22px; border-bottom: 1px solid var(--border); }
.feed-item:last-child { border-bottom: 0; }
.feed-item:hover { background: var(--bg-alt); }

/* Groups a run of tiles under a quiet caption. */
.stat-group-label {
  font-size: 0.74rem; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted-fg); font-weight: 700;
  margin: 0 0 10px;
}

/* data tables */
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--card); }
table.data { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
table.data th {
  text-align: left; padding: 11px 14px; background: var(--bg-alt);
  border-bottom: 1px solid var(--border); font-size: 0.74rem;
  text-transform: uppercase; letter-spacing: .05em; color: var(--muted-fg); white-space: nowrap;
}
table.data td { padding: 12px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
table.data tr:last-child td { border-bottom: 0; }
table.data tbody tr { transition: background .12s; }
table.data tbody tr:hover { background: var(--bg-alt); }

/* Row actions stay out of the way until the row is hovered — on a long
   table a button on every row is visual noise. Always visible on touch,
   where there is no hover to reveal them. */
@media (hover: hover) and (min-width: 861px) {
  table.data tbody .row-actions .btn-ghost { opacity: 0; transition: opacity .12s; }
  table.data tbody tr:hover .row-actions .btn-ghost,
  table.data tbody tr:focus-within .row-actions .btn-ghost { opacity: 1; }
}
table.data .num { text-align: right; font-variant-numeric: tabular-nums; }
table.data .actions { white-space: nowrap; text-align: right; }
table.data .actions .btn { margin-left: 4px; }

.row-actions { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }

/* ---- filter tabs ------------------------------------------------------ */
.tabs { display: flex; gap: 2px; flex-wrap: wrap; }
.tab {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px;
  border-radius: 999px;
  font-size: 0.86rem; font-weight: 600;
  color: var(--muted-fg);
  border: 1px solid transparent;
}
.tab:hover { background: var(--muted); color: var(--fg); text-decoration: none; }
.tab.active {
  background: var(--primary-50);
  border-color: var(--primary);
  color: var(--primary-fg);
}
.tab .count {
  font-size: 0.74rem; font-weight: 700;
  background: var(--muted); color: var(--muted-fg);
  border-radius: 999px; padding: 0 7px;
}
.tab.active .count { background: var(--primary); color: #fff; }

/* ---- contact message inbox -------------------------------------------- */
.msg-list {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  overflow: hidden;
}
.msg { border-bottom: 1px solid var(--border); }
.msg:last-child { border-bottom: 0; }

.msg > summary {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 2fr) auto;
  gap: 14px; align-items: center;
  padding: 13px 18px;
  cursor: pointer;
  list-style: none;
}
.msg > summary::-webkit-details-marker { display: none; }
.msg > summary:hover { background: var(--bg-alt); }
.msg[open] > summary { background: var(--bg-alt); border-bottom: 1px solid var(--border); }

/* Unread reads as unread: heavier text and a marker down the edge. */
.msg.unread > summary { box-shadow: inset 3px 0 0 var(--primary); }
.msg.unread .msg-subject { font-weight: 700; color: var(--fg); }

.msg-from { min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.msg-from strong { font-size: 0.93rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-from span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.msg-subject {
  min-width: 0; font-size: 0.93rem; color: var(--muted-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.msg-meta { display: flex; align-items: center; gap: 10px; justify-content: flex-end; }

.msg-body { padding: 16px 18px 20px; }
.msg-text {
  font-size: 0.93rem; line-height: 1.6;
  background: var(--bg-alt);
  border-radius: var(--radius-sm);
  padding: 14px 16px;
}
.msg-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }

@media (max-width: 720px) {
  .msg > summary { grid-template-columns: 1fr; gap: 6px; }
  .msg-meta { justify-content: flex-start; }
}

/* ---- uploaded asset preview in settings ------------------------------- */
.asset-preview {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 12px; margin-bottom: 10px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-alt);
}
.asset-preview img {
  max-height: 54px; max-width: 160px; width: auto;
  border-radius: 4px; background: #fff; padding: 4px;
}
.asset-preview.wide img { max-width: 260px; max-height: 76px; object-fit: cover; }
.asset-preview .check { margin: 0; font-size: 0.8rem; }

/* ---- flyer design chooser -------------------------------------------- */
.tpl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
.tpl-option {
  position: relative; display: block;
  border: 2px solid var(--border); border-radius: var(--radius-sm);
  overflow: hidden; background: var(--card); color: var(--fg);
  transition: border-color .15s, transform .15s;
}
.tpl-option:hover { border-color: var(--primary); transform: translateY(-2px); text-decoration: none; }
.tpl-option.chosen { border-color: var(--primary); box-shadow: 0 0 0 3px var(--ring); }
.tpl-option img, .tpl-blank { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
.tpl-blank { background: linear-gradient(135deg, var(--secondary), var(--primary)); }
.tpl-name { display: block; padding: 7px 8px; font-size: 0.8rem; text-align: center; }
.tpl-tick {
  position: absolute; top: 7px; right: 7px;
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--primary); color: #fff;
  display: grid; place-items: center; font-size: 0.8rem; font-weight: 700;
}
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---- reuse-a-design disclosure ---------------------------------------- */
.reuse { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border); }
.reuse > summary { cursor: pointer; color: var(--muted-fg); }
.reuse > summary:hover { color: var(--primary-fg); }
.reuse select { max-width: 210px; }

/* ---- flyer preview before generating ---------------------------------- */
.preview-wrap { position: relative; max-width: 460px; margin: 0 auto; }
.preview-img {
  width: 100%; border-radius: var(--radius);
  box-shadow: var(--shadow-lg); border: 1px solid var(--border);
}
.preview-tag {
  position: absolute; top: 12px; left: 12px;
  background: rgba(0,0,0,.62); color: #fff;
  font-size: 0.68rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; padding: 4px 10px; border-radius: 999px;
  backdrop-filter: blur(3px);
}

/* ---- storage usage breakdown ------------------------------------------ */
.storage-bar {
  display: flex; height: 12px; border-radius: 999px; overflow: hidden;
  background: var(--muted); margin-bottom: 10px;
}
.storage-bar .seg { display: block; height: 100%; }

.storage-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 0.8rem; }
.legend-item { display: inline-flex; align-items: center; gap: 6px; }
.legend-item .dot { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }

/* One colour per category, so the bar and legend agree. */
.seg-flyers      { background: #c92737; }
.seg-photos      { background: #2d5f7c; }
.seg-misc        { background: #6b6880; }
.seg-logos       { background: #12a05e; }
.seg-blog        { background: #d08700; }
.seg-backgrounds { background: #7c5a2d; }
.seg-heroes      { background: #3f6b4a; }
.seg-banners     { background: #6b3f5e; }
.seg-thumbs      { background: #9b98ab; }
.seg-qrcodes     { background: #3d4b7c; }
.seg-tickets     { background: #b3202f; }
.seg-previews    { background: #c9c6d4; }

/* ---- custom field definitions in the designer ------------------------- */
.cf-panel > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.cf-panel > summary::-webkit-details-marker { display: none; }
.cf-panel > summary::after { content: '+'; color: var(--muted-fg); font-weight: 700; }
.cf-panel[open] > summary::after { content: '−'; }

.cf-row {
  padding: 14px 0;
  border-bottom: 1px dashed var(--border);
}
.cf-row:last-child { border-bottom: 0; padding-bottom: 0; }
.cf-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }

/* ---- payment gateway cards -------------------------------------------- */
.gw > details > summary { cursor: pointer; list-style: none; }
.gw > details > summary::-webkit-details-marker { display: none; }
.gw-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.gw-name { display: inline-flex; align-items: center; gap: 8px; }
.gw-blurb { font-weight: 400; max-width: 46ch; text-align: right; }
.gw-live { border-color: var(--success); }
.gw-meta { display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center; }
.gw-actions { display: flex; gap: 8px; flex-wrap: wrap; padding-top: 14px; border-top: 1px solid var(--border); }

@media (max-width: 720px) { .gw-blurb { text-align: left; } }

/* ---- payment method choice at checkout -------------------------------- */
.pay-choice { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px 14px; }
.pay-option {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px 0; cursor: pointer; font-size: 0.93rem;
}
.pay-option + .pay-option { border-top: 1px solid var(--border); }
.pay-option input { margin-top: 3px; flex: 0 0 auto; width: auto; }
.pay-option strong { display: block; }

/* ---- platform gateway allowlist --------------------------------------- */
.gw-allow { display: grid; gap: 2px; }
.gw-allow-row { padding: 9px 10px; border-radius: var(--radius-sm); align-items: flex-start; margin: 0; }
.gw-allow-row:hover { background: var(--bg-alt); }
.gw-allow-row input[disabled] + span { opacity: .55; }
.gw-allow-row strong { margin-right: 6px; }

/* toolbar above tables */
.toolbar { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 16px; }
.toolbar .field { margin: 0; }
.toolbar .field input, .toolbar .field select { min-width: 160px; }

/* form sections */
.form-section { margin-bottom: 26px; }
.form-section > h3 { font-size: 1.05rem; padding-bottom: 8px; border-bottom: 1px solid var(--border); margin-bottom: 16px; }

.help { font-size: 0.8rem; color: var(--muted-fg); margin-top: 4px; }

.sticky-actions {
  position: sticky; bottom: 0; background: var(--bg); border-top: 1px solid var(--border);
  padding: 14px 0; margin-top: 24px; display: flex; gap: 10px; flex-wrap: wrap; z-index: 10;
}

/* image preview */
.img-preview {
  width: 100%; max-width: 260px; aspect-ratio: 16/9; border: 1px dashed var(--border);
  border-radius: var(--radius-sm); background: var(--muted); overflow: hidden;
  display: grid; place-items: center; color: var(--muted-fg); font-size: 0.8rem; margin-bottom: 8px;
}
.img-preview img { width: 100%; height: 100%; object-fit: cover; }

/* =====================================================================
   Utilities
   Consolidated from repeated inline styles so the same intent always
   produces the same result, and both themes stay in step.
   ===================================================================== */

/* Inline forms — used wherever a single-button action sits in a table row. */
.inline-form { display: inline; }

/* Semantic text colours */
/* Text by definition, so the foreground weights. These were the fills:
   a discount line reading "-USD 75.00" in --success measured 3.38. */
.text-danger  { color: var(--danger-fg); }
.text-success { color: var(--ok); }
.text-warning { color: var(--warning-fg); }
.text-primary { color: var(--primary-fg); }

/* A ghost button that reads as destructive without shouting */
.btn-ghost.btn-danger-text { color: var(--destructive); background: transparent; }
.btn-ghost.btn-danger-text:hover { background: rgba(239, 43, 65, .1); color: var(--destructive); }
.btn-outline.btn-danger-text { color: var(--destructive); border-color: var(--destructive); }
.btn-outline.btn-danger-text:hover { background: rgba(239, 43, 65, .08); color: var(--destructive); }

/* Codes, references and ticket numbers */
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.strike { text-decoration: line-through; }

/* A badge small enough to sit inline beside a table cell's title */
.badge-xs { font-size: 0.68rem; padding: 2px 7px; }

/* Display icons for empty states and result screens */
.icon-lg { font-size: 2.4rem; line-height: 1; }
.icon-xl { font-size: 3rem; line-height: 1; }

/* Roomier card body for confirmation and empty-state panels */
.card-body-lg { padding: 42px 28px; }
@media (max-width: 560px) { .card-body-lg { padding: 30px 20px; } }

/* Compact card body for the small stat cards above a form */
.card-body-sm { padding: 14px 18px; }

/* Images that fill their box */
.img-cover   { width: 100%; height: 100%; object-fit: cover; }
.img-contain { width: 100%; height: 100%; object-fit: contain; }

/* Consistent heading sizes inside dashboard sections */
.h-section { font-size: 1.2rem; margin: 0 0 16px; }
.h-panel   { font-size: 1.4rem; }

/* Colour swatch inputs need a usable hit area */
input[type="color"] {
  height: 44px;
  padding: 4px;
  cursor: pointer;
  max-width: 180px;
}

/* Range inputs inherit almost nothing by default */
input[type="range"] {
  width: 100%;
  accent-color: var(--primary);
  padding: 0;
  background: transparent;
  border: 0;
}
/* No ring while dragging with a mouse, which is why the rule exists.
   But it took the keyboard ring with it, and one of these is the zoom
   on the public photo portal — an attendee cropping their own badge
   photo, with nothing on screen to say where the focus went. */
input[type="range"]:focus { outline: none; box-shadow: none; }
input[type="range"]:focus-visible { outline: 3px solid var(--ring); outline-offset: 3px; }

/* File inputs — make them look like part of the system */
input[type="file"] {
  padding: 8px 10px;
  font-size: 0.86rem;
  cursor: pointer;
}
input[type="file"]::file-selector-button {
  margin-right: 12px;
  padding: 6px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--fg);
  font: inherit;
  font-size: 0.86rem;
  font-weight: 600;
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover {
  border-color: var(--primary);
  color: var(--primary-fg);
}

/* Disabled inputs should read as unavailable, not broken */
input:disabled, select:disabled, textarea:disabled {
  opacity: .6;
  cursor: not-allowed;
  background: var(--muted);
}

/* QR panels on tickets and verification screens */
.qr-box {
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: var(--muted);
  color: var(--muted-fg);
  font-size: 0.8rem;
  text-align: center;
}

/* Anything that must never widen the page */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Respect a reduced-motion preference across the whole app */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Keyboard focus must always be visible, including on links and rows */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--ring);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ---------------------------------------------------- add to calendar */
/* A plain .ics download is right for Apple and desktop Outlook and wrong
   for everyone on Google or Outlook web, who get a file their phone may
   not open. The menu offers each service its own link. */
.cal-menu { position: relative; }
.cal-menu > summary { list-style: none; cursor: pointer; }
.cal-menu > summary::-webkit-details-marker { display: none; }
.cal-options {
  /* In flow, not absolutely positioned.
     It used to float over the page, which meant the nearest .card — which
     carries overflow:hidden for its rounded corners — simply clipped it
     away. The menu was rendering at full size and being cut off, so it
     read as a button that did nothing.
     Three items in a sidebar card do not need an overlay; letting them
     push the card open is immune to clipping, needs no z-index, and
     behaves on a phone where an overlay would run off-screen. */
  margin-top: 6px;
  background: var(--bg-alt); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

/* A hint that the panel belongs to the button above it. */
.cal-menu[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.cal-options a {
  display: block; padding: 10px 14px; font-size: 0.86rem;
  color: var(--fg); text-decoration: none;
}
.cal-options a + a { border-top: 1px solid var(--border); }
.cal-options a:hover { background: var(--bg-alt); color: var(--primary-fg); }
.cal-options .tiny { display: block; }


/* ---- glass and display type ------------------------------------------
   Restraint is the point. Frosting everything reads as a novelty; the
   header and a few raised surfaces read as considered. Each rule has an
   opaque fallback first, because backdrop-filter is unsupported in a
   handful of browsers and an unsupported translucent panel is unreadable
   rather than merely plain. */

.glass,
.site-header {
  background: var(--bg);                      /* fallback, applied first */
  background: var(--glass-bg);
  border-bottom: 1px solid var(--glass-line);
  box-shadow: var(--glass-shadow);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .glass,
  .site-header {
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(180%);
    backdrop-filter: blur(var(--glass-blur)) saturate(180%);
  }
}

.site-header { position: sticky; top: 0; z-index: 50; }

/* A raised panel: same material, all four sides. */
.glass-card {
  background: var(--bg);
  background: var(--glass-bg);
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  box-shadow: var(--glass-shadow);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .glass-card {
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(180%);
    backdrop-filter: blur(var(--glass-blur)) saturate(180%);
  }
}

/* Manrope's display voice. No optical axis to drive — its wide
   apertures and squared terminals already read as designed at size;
   what it wants is firmer weight and a touch less tracking than
   Inter needed. */
h1, h2, h3,
.display {
  letter-spacing: -0.015em;
  font-weight: 800;
}

h1, .display { letter-spacing: -0.022em; line-height: 1.1; }
h2 { letter-spacing: -0.018em; font-weight: 700; }
h3 { font-weight: 700; }

/* Tabular figures wherever numbers are compared down a column, so digits
   line up instead of shimmying. */
.data td.num, .data th.num,
.stat .value, .rtile .v, .runpct {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* Manrope carries no optical axis; body copy needs no coaxing. */

/* ======================================================================
   LAYOUT UPGRADE
   ----------------------------------------------------------------------
   The front end was built page by page, and it showed: every section
   carried the same 56px of padding, so eight consecutive blocks all
   weighed exactly the same. A page with no variation in density has no
   hierarchy — the eye finds no entry point and reads everything, or
   nothing. What follows is a system rather than a set of tweaks.

   Four ideas, in order of how much they matter:

   1. RHYTHM. Sections now come in three weights. A hero breathes, a
      major section is generous, a minor one is tight. Adjacent sections
      of the same tone collapse their shared padding so two related
      blocks read as one region instead of two.

   2. MEASURE. Prose is capped at 68 characters. The container is
      1200px, which is right for a grid of cards and much too wide for a
      paragraph — at that width the eye loses the start of the next line.

   3. ELEVATION. One shadow scale, used consistently, so raised things
      are raised by the same amount and depth means something.

   4. RESTRAINT IN MOTION. Hover lifts by 2px and changes the border.
      Nothing scales — scaling resamples text and makes it blur, which
      reads as cheap however smooth the easing.
   ====================================================================== */

:root {
  /* A 4px base. Named for role, not size, so a change of scale does not
     mean renaming every use. */
  --space-2xs: .25rem;   /* 4  */
  --space-xs:  .5rem;    /* 8  */
  --space-sm:  .75rem;   /* 12 */
  --space-md:  1rem;     /* 16 */
  --space-lg:  1.5rem;   /* 24 */
  --space-xl:  2.5rem;   /* 40 */
  --space-2xl: 4rem;     /* 64 */
  --space-3xl: 6rem;     /* 96 */

  /* Elevation. Two layers each: a tight shadow for the edge, a soft one
     for the cast. A single blurred shadow always looks like a sticker. */
  --lift-1: 0 1px 2px rgba(16,24,40,.06), 0 1px 3px rgba(16,24,40,.10);
  --lift-2: 0 2px 4px rgba(16,24,40,.06), 0 4px 12px rgba(16,24,40,.10);
  --lift-3: 0 4px 8px rgba(16,24,40,.06), 0 12px 28px rgba(16,24,40,.14);

  --measure: 68ch;
}

[data-theme="dark"] {
  --lift-1: 0 1px 2px rgba(0,0,0,.30), 0 1px 3px rgba(0,0,0,.40);
  --lift-2: 0 2px 4px rgba(0,0,0,.30), 0 4px 12px rgba(0,0,0,.44);
  --lift-3: 0 4px 8px rgba(0,0,0,.34), 0 12px 28px rgba(0,0,0,.52);
}

/* ---- section rhythm -------------------------------------------------- */

.section          { padding-block: var(--space-2xl); }
.section-sm       { padding-block: var(--space-xl); }
.section-lg       { padding-block: var(--space-3xl); }

/* Two sections of the same tone in a row are one region, not two.
   Collapsing the seam is what stops a long page reading as a list. */
.section-alt + .section-alt,
.section:not(.section-alt) + .section:not(.section-alt) { padding-top: 0; }

/* ---- measure --------------------------------------------------------- */

.section p:not([class]),
.prose,
.lede { max-width: var(--measure); }

.lede {
  font-size: clamp(1.05rem, 1.6vw, 1.2rem);
  line-height: 1.6;
  color: var(--muted-fg);
}

/* ---- section headers ------------------------------------------------- */
/* Eyebrow, title, one line of support. The same three parts every time,
   so a reader scanning the page learns the shape once. */

.sec-head { margin-bottom: var(--space-xl); max-width: var(--measure); }
.sec-head .eyebrow {
  display: inline-block; margin-bottom: var(--space-xs);
  font-size: 0.74rem; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--primary-fg);
}
.sec-head h2 { margin: 0 0 var(--space-sm); font-size: clamp(1.6rem, 3.2vw, 2.25rem); }
.sec-head p  { margin: 0; color: var(--muted-fg); font-size: 1rem; line-height: 1.6; }
.sec-head.center { margin-inline: auto; text-align: center; }

/* ---- cards ----------------------------------------------------------- */

.card, .event-card {
  border-radius: 14px;
  box-shadow: var(--lift-1);
  transition: box-shadow .18s ease, transform .18s ease, border-color .18s ease;
}

a.event-card:hover, .event-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--lift-3);
  border-color: color-mix(in srgb, var(--primary) 32%, var(--border));
}

/* Images get a fixed ratio so a grid of cards has a straight top edge
   whatever shape the uploads are. Ragged card tops are the single most
   common reason a card grid looks unfinished. */
.event-card .thumb {
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--bg-alt);
}
.event-card .thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .4s cubic-bezier(.22,.61,.36,1);
}
.event-card:hover .thumb img { transform: scale(1.03); }

/* ---- grids ----------------------------------------------------------- */
/* auto-fit with a floor, so the layout answers the space it is given
   rather than to a handful of hard breakpoints. */

.grid-auto  { display: grid; gap: var(--space-lg); grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.grid-cards { display: grid; gap: var(--space-lg); grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); }

/* ---- hero ------------------------------------------------------------ */

.hero { padding-block: clamp(var(--space-2xl), 9vw, var(--space-3xl)); }
.hero h1 { font-size: clamp(2.1rem, 5.4vw, 3.6rem); margin-bottom: var(--space-md); }
.hero .lede { margin-bottom: var(--space-xl); }

/* ---- focus ----------------------------------------------------------- */
/* :focus-visible, so a mouse click does not draw a ring but a Tab key
   does. Removing focus rings outright is the most common accessibility
   failure on a marketing site. */

a:focus-visible, button:focus-visible,
input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---- motion ---------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- small screens --------------------------------------------------- */

@media (max-width: 640px) {
  .section    { padding-block: var(--space-xl); }
  .section-lg { padding-block: var(--space-2xl); }
  .sec-head   { margin-bottom: var(--space-lg); }
}

/* ------------------------------------------------ touch and small screens

   Two rules here, and the first one is a bug fix rather than a polish.

   iOS Safari zooms the viewport whenever a form field with a font size
   under 16px takes focus, and it does not zoom back out. Every input on
   this platform was .93rem — 14.88px — so tapping any field on an
   iPhone left the page magnified and wider than the screen for the rest
   of the visit. That is what "the checkout is too wide on mobile" was:
   not the layout, which fits, but what happens the moment somebody taps
   a name field. It affected every form on the site, so the fix belongs
   here rather than on one page.

   16px exactly. Anything larger is a design change; anything smaller
   brings the zoom back.

   The second is target size. A 40px control is hittable with a mouse and
   marginal with a thumb, and the checkout is the worst place to make
   somebody tap twice. */
@media (max-width: 860px) {
  .input,
  input[type=text], input[type=email], input[type=password], input[type=number],
  input[type=date], input[type=time], input[type=url], input[type=tel],
  input[type=search], input[type=datetime-local],
  select, textarea {
    font-size: 16px;
    min-height: 46px;
    padding: 11px 14px;
  }

  select { padding-right: 36px; }

  textarea { min-height: 120px; }

  /* Anything a thumb has to find. Small buttons stay visually small but
     gain the height they need to be hit reliably. */
  .btn { min-height: 46px; padding-inline: 20px; }
  .btn-sm { min-height: 40px; font-size: 0.86rem; }

  /* Checkboxes and radios are drawn at their intrinsic size, so the
     label carries the target instead. */
  label.check { min-height: 44px; display: flex; align-items: center; gap: 10px; }

  /* A summary panel pinned below a single column of form has nothing to
     stay beside — it just covers the thing underneath it as you scroll. */
  .sidebar-sticky { position: static; top: auto; }

  /* The menu toggle is the most-tapped control on a phone and was the
     smallest thing on the page. */
  .nav-toggle, .theme-toggle { width: 44px; height: 44px; }
}

/* ------------------------------------------- editable order summary line */
.sum-line { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px;
            align-items: center; padding: 10px 0; border-bottom: 1px solid var(--border); }
.sum-line:last-of-type { border-bottom: 0; }
.sum-what b { display: block; font-size: 0.93rem; }
.sum-each { font-size: 0.74rem; color: var(--muted-fg); }
.sum-qty { grid-column: 1; display: flex; align-items: center; gap: 6px; }
.sum-qty select { width: 74px; }
.sum-money { grid-column: 2; grid-row: 1 / span 2; align-self: center;
             font-weight: 600; font-size: 0.93rem; font-variant-numeric: tabular-nums; }
.sum-remove { background: none; border: 0; cursor: pointer; line-height: 1;
              font-size: 1.3rem; color: var(--muted-fg); padding: 4px 8px;
              border-radius: var(--radius-sm); }
.sum-remove:hover { color: var(--destructive); background: var(--bg-alt); }
.sum-remove:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

/* Screen-reader-only, for labels the layout does not have room to show. */
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0;
                   margin: -1px; overflow: hidden; clip: rect(0 0 0 0);
                   white-space: nowrap; border: 0; }

@media (max-width: 860px) { .sum-remove { min-width: 44px; min-height: 44px; } }

/* One attendee, and the control to drop them.

   The remove was first written as a float, which put it in no layout at
   all: the grid of fields that follows does not reserve space for a
   float, so the second block's button ended up underneath a field. It is
   anchored to the fieldset instead, level with the legend, where there
   is always room for it.

   formnovalidate on the button is what makes it work at all. The sibling
   blocks are required fields, so without it the browser refuses to
   submit and the buyer is stuck with a row they cannot delete. */
.att-block { position: relative; border: 1px solid var(--border);
             border-radius: var(--radius-sm); padding: 16px; margin-bottom: 14px; }
.att-block > legend { padding: 0 8px; }

.att-remove {
  position: absolute; top: -13px; right: 12px; z-index: 1;
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 999px; padding: 3px 12px; cursor: pointer;
  font-size: 0.74rem; font-weight: 600; color: var(--muted-fg); line-height: 1.6;
}
.att-remove .x { font-size: 1rem; line-height: 1; }
.att-remove:hover { color: var(--destructive); border-color: var(--destructive); }
.att-remove:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

@media (max-width: 860px) {
  /* Room for the control above the legend rather than beside it, since
     a long ticket name and a button do not share 375px. */
  .att-block { padding-top: 22px; }
  .att-remove { min-height: 36px; padding: 6px 14px; }
}

/* ===================================================================
   Sponsors

   One rule governs the lot: a logo is never distorted. Every tile sizes
   its box and lets the image sit inside with object-fit: contain, so a
   wide wordmark and a square badge both arrive whole. Nothing sets both
   width and height on an img.

   Sizing is in clamp() rather than at breakpoints, so a logo shrinks
   smoothly on a narrow phone instead of snapping between two sizes.
   =================================================================== */

.sp-tile { display: grid; place-items: center; padding: 10px 14px;
           border: 1px solid var(--border); border-radius: 10px;
           background: var(--card); min-height: 72px; text-decoration: none; }
.sp-tile img { max-width: 100%; max-height: 46px; width: auto; height: auto;
               object-fit: contain; }
.sp-tile .sp-name { font-size: 0.86rem; font-weight: 600; color: var(--fg);
                    text-align: center; }
a.sp-tile { transition: border-color .15s ease, transform .15s ease; }
a.sp-tile:hover { border-color: var(--primary); transform: translateY(-1px); }
a.sp-tile:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.sp-tile-sm { min-height: 56px; padding: 8px 12px; }
.sp-tile-sm img { max-height: 32px; }
.sp-tile-lg { min-height: clamp(96px, 22vw, 140px); padding: 18px 26px; }
.sp-tile-lg img { max-height: clamp(56px, 14vw, 88px); }

/* auto-fit means the columns are decided by the space available, so this
   is one rule for phone, tablet and desktop rather than three. */
.sp-grid { display: grid; gap: 14px;
           grid-template-columns: repeat(auto-fit, minmax(clamp(120px, 30vw, 170px), 1fr)); }
.sp-grid-sm { grid-template-columns: repeat(auto-fit, minmax(clamp(100px, 24vw, 130px), 1fr)); }

.sp-tier { margin-bottom: 22px; }
.sp-tier:last-child { margin-bottom: 0; }
.sp-tier-head { font-size: 0.74rem; letter-spacing: .1em; text-transform: uppercase;
                color: var(--muted-fg); font-weight: 700; margin: 0 0 10px; }

/* Tier is expressed as size, which is what prominence actually is — not
   as a louder colour.

   The scale arrives as a custom property set per group, derived from the
   tier's position in the organiser's own list. Naming the tiers in CSS
   would have meant only five names could ever work, and an organiser who
   sells Diamond and Knowledge Partner would get no hierarchy at all. */
.sp-tier { --sp-scale: 1; }
.sp-tier .sp-tile { min-height: calc(66px * var(--sp-scale) + 30px); }
.sp-tier .sp-tile img { max-height: calc(46px * var(--sp-scale) + 18px); }
.sp-tier .sp-grid {
  grid-template-columns: repeat(auto-fit,
    minmax(clamp(110px, calc(20vw * var(--sp-scale) + 8vw), calc(120px * var(--sp-scale) + 60px)), 1fr));
}

.sp-featured { margin-bottom: 18px; }
.sp-featured-row { display: grid; gap: 14px;
                   grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }

.sp-cards { display: grid; gap: 14px;
            grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr)); }
.sp-card { border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
           background: var(--card); display: flex; flex-direction: column; }
.sp-card .sp-tile { border: 0; border-bottom: 1px solid var(--border);
                    border-radius: 0; background: var(--bg-alt); min-height: 96px; }
.sp-card-body { padding: 12px 14px; }
.sp-card-body b { display: block; font-size: 0.93rem; }
.sp-card-tier { font-size: 0.68rem; letter-spacing: .07em; text-transform: uppercase;
                color: var(--muted-fg); }
.sp-card-body p { margin: 6px 0 0; font-size: 0.86rem; color: var(--muted-fg); }

/* A strip scrolls sideways rather than wrapping, so a long list stays
   one band on a phone. The page itself never scrolls sideways. */
.sp-strip { min-width: 0; display: flex; gap: 12px; overflow-x: auto; padding-bottom: 4px;
            scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; }
.sp-strip .sp-tile { flex: 0 0 auto; min-width: 132px; scroll-snap-align: start; }

.sp-marquee { min-width: 0; overflow: hidden; }
.sp-marquee-track { display: flex; gap: 12px; width: max-content;
                    animation: sp-scroll 38s linear infinite; }
.sp-marquee-copy { display: flex; gap: 12px; }
.sp-marquee:hover .sp-marquee-track,
.sp-marquee:focus-within .sp-marquee-track { animation-play-state: paused; }
.sp-marquee .sp-tile { flex: 0 0 auto; min-width: 132px; }
@keyframes sp-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

.sp-compact { margin-bottom: 14px; }
.sp-compact-tier { font-size: 0.68rem; letter-spacing: .09em; text-transform: uppercase;
                   color: var(--muted-fg); font-weight: 700; }
.sp-compact ul { list-style: none; margin: 4px 0 0; padding: 0;
                 display: flex; flex-wrap: wrap; gap: 4px 16px; }
.sp-compact li { font-size: 0.93rem; }

/* Motion is a preference, not a decoration. A moving strip becomes a
   still one that can still be scrolled by hand. */
@media (prefers-reduced-motion: reduce) {
  .sp-marquee-track { animation: none; }
  .sp-marquee { overflow-x: auto; }
  a.sp-tile:hover { transform: none; }
}

/* Sponsor placement slots. The band is a quiet full-width strip so the
   top and bottom placements read as a separate register from the page
   content rather than as another section competing with it. */
.sp-band { background: var(--bg-alt); border-block: 1px solid var(--border); }
.sp-slot-head { font-size: 1rem; margin: 0 0 12px; }
.sp-slot-sidebar .sp-slot-head { font-size: 0.8rem; letter-spacing: .08em;
                                 text-transform: uppercase; color: var(--muted-fg); }
/* The side column is narrow, so whatever layout was chosen collapses to
   something that fits rather than overflowing it. */
.sp-slot-sidebar .sp-grid { grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); gap: 8px; }
.sp-slot-sidebar .sp-tile { min-height: 52px; padding: 6px 8px; }
.sp-slot-sidebar .sp-tile img { max-height: 30px; }
.sp-slot-sidebar .sp-cards { grid-template-columns: 1fr; }

/* ------------------------------------------------- description editor */
.rt { border: 1px solid var(--border); border-radius: var(--radius-sm);
      background: var(--input-bg); overflow: hidden; }
.rt-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 2px;
          padding: 6px 8px; border-bottom: 1px solid var(--border);
          background: var(--bg-alt); }
.rt-bar button { background: none; border: 1px solid transparent; cursor: pointer;
                 font-size: 0.8rem; color: var(--fg); padding: 5px 9px;
                 border-radius: 6px; line-height: 1.2; min-width: 32px; }
.rt-bar button:hover { background: var(--card); border-color: var(--border); }
.rt-bar button:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.rt-sep { width: 1px; height: 18px; background: var(--border); margin: 0 4px; }

.rt-area { min-height: 190px; padding: 12px 14px; font-size: 0.93rem;
           line-height: 1.65; color: var(--fg); overflow-y: auto; max-height: 460px; }
.rt-area:focus { outline: none; }
.rt:focus-within { border-color: var(--primary); }
.rt-area.input-error { background: transparent; }
.rt-area p { margin: 0 0 10px; }
.rt-area p:last-child { margin-bottom: 0; }
.rt-area ul, .rt-area ol { margin: 0 0 10px; padding-left: 22px; }
.rt-area blockquote { margin: 0 0 10px; padding-left: 12px;
                      border-left: 3px solid var(--border); color: var(--muted-fg); }
.rt-area a { color: var(--primary-fg); }

/* Without JavaScript the toolbar cannot work, so it is not shown and the
   plain textarea is what the person gets. The script adds .rt-on, which
   is what flips them — so the fallback is the default state rather than
   something that depends on the script having failed politely. */
.rt-bar, .rt-area { display: none; }
.rt textarea { display: block; width: 100%; min-height: 190px; border: 0;
               background: transparent; padding: 12px 14px; resize: vertical;
               font-family: inherit; }
.rt-on .rt-bar { display: flex; }
.rt-on .rt-area { display: block; }
.rt-on textarea { display: none; }

@media (max-width: 860px) {
  .rt-bar button { min-height: 40px; }
  .rt-area { font-size: 16px; }
}

/* =====================================================================
   Shared components, phase two of the redesign.

   These existed before — as fifty-seven private copies inside page
   <style> blocks. Each pattern below is the canonical recipe, with the
   page-local class names kept as aliases so no markup had to change to
   adopt it. New pages use the canonical names; old pages shed their
   local copies without moving a single tag.
   ===================================================================== */

:root { --info: #2f6fd6; }
html[data-theme="dark"] { --info: #6ea8ff; }

/* ---- chips: one small statement of state ---------------------------
   This is the only pill in the system. Six screens had each grown their
   own — .an-state, .ct-state, .rv-pill, .au-state, .doc-state,
   .doc-status — between them five font sizes, five paddings, one with
   no border at all and two with a dot. They are all this, now.

   The tones are the foreground weights: a chip is text, and --success
   and --warning are accents that carry only 3.38 and 2.94 against a
   card, under the 4.5 small text owes. --primary was worse — legible on
   white at 5.48, but 3.28 on a dark card, so the attention chip was
   failing in dark mode wherever it appeared. */
.chip, .sp-state, .fm-chip, .spk-deadline {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.68rem; text-transform: uppercase;
  letter-spacing: .06em; border: 1px solid var(--border); border-radius: 99px;
  padding: 1px 9px; color: var(--muted-fg); vertical-align: middle;
  white-space: nowrap;
}

/* A dot, for the screens that led with one. It takes the chip's own
   tone, so a dot can never disagree with the word beside it. */
.chip > i { width: 7px; height: 7px; border-radius: 50%; flex: none;
            background: currentColor; }

/* The larger register: one standing status on a record or a public
   page, where the chip is the answer rather than a cell in a list and
   the label is a sentence — so it is not shouted. */
.chip-lead { font-size: 0.8rem; padding: 4px 12px;
             text-transform: none; letter-spacing: 0; }

.chip-ok,        .sp-state-ok,   .fm-chip.on { border-color: var(--ok); color: var(--ok); }
.chip-warn,      .sp-state-warn, .fm-chip.lock { border-color: var(--warning-fg); color: var(--warning-fg); }
.chip-danger    { border-color: var(--danger-fg); color: var(--danger-fg); }
.chip-info      { border-color: var(--info); color: var(--info); }
.chip-attention, .sp-state-attention { border-color: var(--primary-fg); color: var(--primary-fg); font-weight: 700; }

/* The words the data already uses, so a screen can keep interpolating
   its own status straight into the class — class="chip <?= $status ?>"
   — instead of every page inventing a mapping. Anything not named here
   stays neutral, which is the right answer for a state nobody has
   decided the meaning of yet. */
.chip.sent, .chip.published, .chip.accepted, .chip.yes, .chip.on, .chip.ok
  { border-color: var(--ok); color: var(--ok); }
.chip.draft, .chip.wait, .chip.dup, .chip.pending
  { border-color: var(--warning-fg); color: var(--warning-fg); }
.chip.rejected, .chip.no, .chip.off, .chip.failed
  { border-color: var(--danger-fg); color: var(--danger-fg); }

/* ---- step indicators: wizards say where you are --------------------- */
.steps, .im-steps, .mc-steps { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.step, .im-step, .mc-step {
  font-size: 0.8rem; padding: 4px 12px; border-radius: 99px;
  border: 1px solid var(--border); color: var(--muted-fg);
}
/* Text again, so the foreground weights — the same two the chips had
   wrong. "You are here" in a wizard is the one label on the screen a
   reader must not lose, and on a dark card --primary reached 3.28. */
.step.on,   .im-step.on,   .mc-step.on   { border-color: var(--primary-fg); color: var(--primary-fg); font-weight: 600; }
.step.done, .im-step.done, .mc-step.done { border-color: var(--ok); color: var(--ok); }

/* ---- stat tiles: a number with its name ----------------------------- */
.stat-grid, .im-tally {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); margin-bottom: 14px;
}
.stat, .im-num { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; }
.stat b, .im-num b { display: block; font-size: 1.6rem; line-height: 1.1; font-variant-numeric: tabular-nums; }
.stat span, .im-num span { font-size: 0.74rem; color: var(--muted-fg); }
.stat.good, .im-num.good { border-color: var(--success); }
.stat.bad,  .im-num.bad  { border-color: var(--destructive); }

/* ---- tabs: one page, several jobs ----------------------------------- */
.tabs, .sp-tabs { display: flex; gap: 6px; flex-wrap: wrap; border-bottom: 1px solid var(--border); }
.tab, .sp-tab {
  padding: 8px 16px; font-size: 0.86rem; color: var(--muted-fg); text-decoration: none;
  border: 1px solid transparent; border-bottom: 0;
  border-radius: 10px 10px 0 0; margin-bottom: -1px;
}
.tab:hover, .sp-tab:hover { color: var(--fg); text-decoration: none; }
.tab.on, .sp-tab.on { color: var(--fg); font-weight: 600; border-color: var(--border); background: var(--card); }

/* ---- toolbar: a row of actions with one line of context ------------- */
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 18px; }
.toolbar .hint { flex: 1 1 260px; }

/* ---- confirm dialogs ------------------------------------------------- */
dialog.confirm {
  border: 1px solid var(--border); border-radius: 12px; padding: 0;
  max-width: 460px; width: calc(100% - 32px);
  background: var(--card); color: var(--fg);
}
dialog.confirm::backdrop { background: rgba(0, 0, 0, .5); }
dialog.confirm .card-body { padding: 22px 24px; }

/* ---- loading: buttons that admit they are working -------------------- */
.btn.loading { position: relative; color: transparent !important; pointer-events: none; }
.btn.loading::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .4); border-top-color: #fff;
  animation: btn-spin .7s linear infinite;
}
.btn-outline.loading::after, .btn-ghost.loading::after {
  border-color: var(--border); border-top-color: var(--muted-fg);
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

.skeleton {
  border-radius: var(--radius-sm); background: var(--muted);
  background-image: linear-gradient(90deg, transparent, rgba(255, 255, 255, .35), transparent);
  background-size: 200% 100%; animation: skeleton-sweep 1.2s ease-in-out infinite;
  color: transparent; min-height: 1em;
}
@keyframes skeleton-sweep { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* ---- tables that become cards on a phone ----------------------------
   Opt in with class="data data-cards" plus data-th="Column name" on
   each td. Desktop is untouched; under 700px each row becomes a small
   card and every cell carries its own heading. */
@media (max-width: 700px) {
  table.data.data-cards thead { display: none; }
  table.data.data-cards, table.data.data-cards tbody,
  table.data.data-cards tr, table.data.data-cards td { display: block; width: 100%; }
  table.data.data-cards tr {
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: 10px 14px; margin-bottom: 10px;
  }
  table.data.data-cards td { border: 0; padding: 4px 0; }
  table.data.data-cards td[data-th]::before {
    content: attr(data-th); display: block; font-size: 0.68rem;
    text-transform: uppercase; letter-spacing: .06em; color: var(--muted-fg);
  }
}

/* ------------------------------------------------------------- charts
   Server-rendered SVG and bar markup from lib/charts.php. The marks
   stay thin, the grid stays recessive, and every value is ink — the
   series colour carries only the shape. */
.cts { display: block; width: 100%; height: auto; }
.cts-grid { stroke: var(--border); stroke-width: 1; }
.cts-ylab, .cts-xlab {
  fill: var(--muted-fg); font-size: 10.5px;
  font-variant-numeric: tabular-nums;
}
.cts-ylab { text-anchor: end; }
.cts-area { fill: var(--primary); opacity: .12; }
.cts-line { fill: none; stroke: var(--primary); stroke-width: 2;
            stroke-linecap: round; stroke-linejoin: round; }
.cts-end  { fill: var(--primary); }
.cts-pt circle, .cts-pt text { opacity: 0; transition: opacity .1s ease; pointer-events: none; }
.cts-pt circle { fill: var(--primary); stroke: var(--card); stroke-width: 2; }
.cts-pt text {
  fill: var(--muted-fg); font-size: 10.5px;
  paint-order: stroke; stroke: var(--card); stroke-width: 3.5px; stroke-linejoin: round;
}
.cts-pt text.v { fill: var(--fg); font-weight: 700; font-size: 12px;
                 font-variant-numeric: tabular-nums; }
.cts-pt:hover circle, .cts-pt:hover text { opacity: 1; }

/* --------------------------------------------------- event workspace
   One event's production office: a hero strip carrying its artwork,
   the pulse band, and a grid of department doors. */
.ws-hero { height: 150px; border-radius: var(--radius); background: var(--muted) center/cover;
           margin-bottom: 16px; position: relative; overflow: hidden; }
.ws-hero.has-img { background-size: cover; }
.ws-hero-scrim { position: absolute; inset: 0; display: flex; gap: 8px;
                 align-items: flex-start; justify-content: flex-end; padding: 12px 14px;
                 background: linear-gradient(180deg, rgba(31,22,41,.28), transparent 55%); }
@media (max-width: 640px) { .ws-hero { height: 110px; } }

.ws-areas { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; }
.ws-area { display: flex; gap: 12px; align-items: center; padding: 14px 15px;
           background: var(--card); border: 1px solid var(--border); border-radius: 12px;
           text-decoration: none; color: inherit;
           transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease; }
.ws-area:hover { transform: translateY(-2px); border-color: var(--primary);
                 box-shadow: 0 8px 22px rgba(31,22,41,.10); text-decoration: none; }
.ws-ic { width: 38px; height: 38px; border-radius: 10px; flex: 0 0 auto;
         display: inline-flex; align-items: center; justify-content: center;
         background: var(--primary-50); color: var(--primary-fg); }
.ws-tx { min-width: 0; flex: 1 1 auto; }
.ws-tx b { display: block; font-size: 0.93rem; }
.ws-tx span { display: block; font-size: 0.74rem; color: var(--muted-fg); line-height: 1.35; }
.ws-area .chev { color: var(--muted-fg); font-size: 1.1rem; }

/* The quiet three-dot menu on a card: a details element, so it needs
   no script and closes itself when another one opens focus. */
.more { position: relative; margin-left: auto; }
.more > summary { list-style: none; cursor: pointer; width: 32px; height: 32px;
                  display: inline-flex; align-items: center; justify-content: center;
                  border-radius: 8px; border: 1px solid var(--border);
                  color: var(--muted-fg); font-weight: 700; }
.more > summary::-webkit-details-marker { display: none; }
.more > summary:hover { color: var(--fg); border-color: var(--fg); }
.more[open] > summary { border-color: var(--primary); color: var(--primary-fg); }
.more-panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
              min-width: 185px; background: var(--card); border: 1px solid var(--border);
              border-radius: 10px; box-shadow: 0 12px 32px rgba(31,22,41,.16);
              padding: 6px; display: grid; }
.more-panel a { padding: 8px 11px; border-radius: 7px; font-size: 0.86rem;
                color: var(--fg); text-decoration: none; }
.more-panel a:hover { background: var(--muted); text-decoration: none; }

.ev-tally { display: flex; gap: 14px; flex-wrap: wrap; font-size: 0.8rem;
            color: var(--muted-fg); font-variant-numeric: tabular-nums; }
.ev-tally b { color: var(--fg); font-weight: 700; }

.hbars { display: grid; gap: 9px; }
.hb { display: grid; grid-template-columns: minmax(90px, 1fr) minmax(0, 2fr) auto;
      gap: 10px; align-items: center; font-size: 0.86rem; }
.hb-l { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hb-sub { display: block; font-size: 0.74rem; color: var(--muted-fg); }
.hb-track { height: 10px; border-radius: 5px; background: var(--muted); overflow: hidden; }
.hb-track i { display: block; height: 100%; background: var(--primary);
              border-radius: 2px 5px 5px 2px; }
.hb-v { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.hb-more { font-size: 0.74rem; color: var(--muted-fg); }
@media (max-width: 560px) {
  .hb { grid-template-columns: 1fr auto; }
  .hb-track { grid-column: 1 / -1; order: 3; }
}

.pipe { display: flex; gap: 2px; height: 10px; border-radius: 5px; overflow: hidden; }
.pipe i { display: block; min-width: 4px; }
.pipe-good { background: var(--success); }
.pipe-warn { background: var(--warning); }
.pipe-mute { background: var(--muted-fg); opacity: .45; }
.pipe-legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 7px;
               font-size: 0.8rem; color: var(--muted-fg); }
.pipe-legend b { color: var(--fg); font-variant-numeric: tabular-nums; }
.pipe-dot { display: inline-block; width: 8px; height: 8px; border-radius: 2.5px;
            margin-right: 5px; vertical-align: 0; }

/* ---- rhythm without inline margins ----------------------------------- */
.stack > * + * { margin-top: 14px; }
.stack-sm > * + * { margin-top: 8px; }
.stack-lg > * + * { margin-top: 24px; }

/* ---- accessibility ---------------------------------------------------- */
:focus-visible { outline: 3px solid var(--ring); outline-offset: 1px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* The import screens' column-mapping grid, shared by both importers. */
.im-map { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }

/* ---- event cards on the dashboard ----------------------------------- */
.ev-card { display: flex; flex-direction: column; overflow: hidden; }
.ev-card-media {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 8px; padding: 12px; height: 128px;
  background: linear-gradient(135deg, var(--secondary), var(--primary));
  background-size: cover; background-position: center;
}
.ev-card-body { display: flex; flex-direction: column; gap: 8px; flex: 1 1 auto; }
.ev-card-name { font-size: 1rem; line-height: 1.3; }
.ev-card-actions {
  display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto;
  padding-top: 10px; border-top: 1px solid var(--border);
}

/* ---- the speakers on a public event page ---------------------------- */
.ev-speakers { margin-top: 30px; }
.ev-sp-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
/* Each speaker is a tile: a surface with a border, the photograph, the
   name and the role, all in one card rather than a portrait floating on
   the page with text loose underneath it. */
.ev-sp {
  margin: 0; text-align: center;
  padding: 18px 12px 16px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 14px;
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}
.ev-sp:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--primary) 35%, var(--border));
  box-shadow: var(--shadow-lg);
}

/* A taste of the line-up: eight on a large screen, four on a phone.
   All eight are in the markup and the last four are hidden here, so the
   count follows the width rather than a guess made on the server. */
.ev-sp-more { margin-top: 16px; text-align: center; }

@media (max-width: 640px) {
  .ev-sp-extra { display: none; }
}
/* One shape per card, whatever it holds. The photograph, then the name
   in a box two lines tall, then the role in a box one line tall — so a
   name that wraps cannot push its neighbours' text out of line, which
   is what made a row of speakers read as a scatter. */
.ev-sp-shot { position: relative; display: block; width: 96px; margin: 0 auto 10px; }
.ev-sp img, .ev-sp-initial {
  width: 96px; height: 96px; border-radius: 50%; object-fit: cover;
  display: block; border: 3px solid var(--card);
  box-shadow: var(--shadow-sm);
}
.ev-sp-initial {
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-alt); color: var(--muted-fg);
  font-size: 1.6rem; font-weight: 800;
}

/* The featured mark rides on the photograph. In the text it sat between
   the name and the role, so whether somebody was featured decided where
   everybody else's role line landed. */
.ev-sp-flag {
  position: absolute; left: 50%; bottom: -6px; transform: translateX(-50%);
  font-size: 0.68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .08em; white-space: nowrap;
  padding: 2px 9px; border-radius: 99px;
  color: #fff; background: var(--primary);
  box-shadow: 0 0 0 2px var(--card);
}

.ev-sp figcaption { display: block; }
.ev-sp-name {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; font-size: 0.93rem; line-height: 1.3;
  min-height: 2.6em;                     /* two lines, always */
}
.ev-sp-role {
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
  overflow: hidden; font-size: 0.8rem; color: var(--muted-fg);
  line-height: 1.4; min-height: 1.4em;   /* reserved even when empty */
  margin-top: 2px;
}

/* =====================================================================
   Life: motion with a purpose, tone where it means something.
   Every animation dies under prefers-reduced-motion (rule above).
   ===================================================================== */

/* Things you can press acknowledge the press. */
.btn { transition: background .15s ease, border-color .15s ease, color .15s ease,
                   transform .1s ease, box-shadow .15s ease; }
.btn:active { transform: scale(.97); }

/* Cards and tiles lift to meet the cursor — only the interactive ones. */
a.stat, .ev-card, .value, .card-link {
  transition: transform .18s ease, box-shadow .18s ease;
}
a.stat:hover, .ev-card:hover, .value:hover, .card-link:hover {
  transform: translateY(-2px); box-shadow: var(--shadow);
}

/* Content arrives, gently. One short rise, not a parade. */
@keyframes rise { from { opacity: 0; transform: translateY(6px); } }
.dash-main > .card, .dash-main > .stats, .dash-main > .alert,
.dash-main > .grid, .dash-main > .table-wrap {
  animation: rise .3s ease both;
}

/* Stat tiles wear their tone on the left edge. */
.stat { border-left: 3px solid var(--border); }
.stat.accent    { border-left-color: var(--primary); }
.stat.attention { border-left-color: var(--warning); border-color: var(--warning); }
.stat.attention .value { color: var(--warning); }
.stat.good      { border-left-color: var(--success); }

/* ---- the meter: one number against its ceiling ----------------------
   A single-hue magnitude mark: 8px tall, 4px rounded ends, the fill
   animating to its width on load. Sold against capacity, used against
   quota — anywhere a number has a maximum worth seeing. */
.meter {
  position: relative; height: 8px; border-radius: 4px;
  background: var(--muted); overflow: hidden;
}
.meter > i {
  position: absolute; top: 0; bottom: 0; left: 0;
  border-radius: 4px; background: var(--primary);
  animation: meter-fill .6s ease both;
}
.meter.full > i { background: var(--success); }
@keyframes meter-fill { from { width: 0 !important; } }
.meter-line {
  display: flex; justify-content: space-between; gap: 8px;
  font-size: 0.74rem; color: var(--muted-fg); margin-top: 4px;
  font-variant-numeric: tabular-nums;
}

/* Admin's two daily tables, labelled by position on the phone —
   markup untouched; the headings live here in the sheet. */
@media (max-width: 700px) {
  .admin-clients td:nth-child(2)::before { content: "Plan"; }
  .admin-clients td:nth-child(3)::before { content: "Tokens"; }
  .admin-clients td:nth-child(4)::before { content: "Events"; }
  .admin-clients td:nth-child(5)::before { content: "Ticketing"; }
  .admin-clients td:nth-child(6)::before { content: "Status"; }
  .admin-events td:nth-child(2)::before  { content: "Organiser"; }
  .admin-events td:nth-child(3)::before  { content: "Date"; }
  .admin-events td:nth-child(4)::before  { content: "Status"; }
  .admin-clients td:nth-child(n+2)::before, .admin-events td:nth-child(n+2)::before {
    display: block; font-size: 0.68rem; text-transform: uppercase;
    letter-spacing: .06em; color: var(--muted-fg);
  }
}

/* ---- global search ---------------------------------------------------- */
.dash-search { padding: 0 14px 14px; }
.dash-search input {
  width: 100%; font-size: 0.86rem; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: 99px;
  background: var(--input-bg); color: var(--fg);
}
.search-hit {
  display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  padding: 9px 8px; margin: 0 -8px; border-radius: 8px;
  color: inherit; text-decoration: none;
  border-bottom: 1px solid var(--border);
}
.search-hit:last-child { border-bottom: 0; }
.search-hit:hover { background: var(--bg-alt); text-decoration: none; }
.search-hit-title { font-weight: 600; }
.search-hit-meta { font-size: 0.8rem; color: var(--muted-fg); }

/* ---- person cards: the membership platform's directory pattern ------ */
.person-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.person-card {
  display: flex; flex-direction: column; gap: 8px;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px; background: var(--card);
  transition: transform .15s ease, box-shadow .15s ease;
}
.person-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.person-head { display: flex; gap: 11px; align-items: center; min-width: 0; }
.person-avatar, .person-initial {
  width: 48px; height: 48px; border-radius: 50%; flex: 0 0 auto; object-fit: cover;
}
.person-initial {
  display: flex; align-items: center; justify-content: center;
  background: var(--primary); color: #fff; font-weight: 700; font-size: 1.1rem;
}
.person-head b { display: block; font-size: 0.93rem; overflow: hidden;
                 text-overflow: ellipsis; white-space: nowrap; }
.person-head .sub { font-size: 0.8rem; color: var(--muted-fg); overflow: hidden;
                    text-overflow: ellipsis; white-space: nowrap; }
.person-why { font-size: 0.8rem; color: var(--muted-fg); display: flex; gap: 4px; flex-wrap: wrap; }
.person-actions { display: flex; gap: 6px; margin-top: auto; padding-top: 6px; }

/* ---- the listing standard: pager, sort headers, bulk selection ------ */
.lister {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  padding: 10px 2px; font-size: 0.86rem; color: var(--muted-fg);
}
.lister-count { font-variant-numeric: tabular-nums; }
.lister-per a { color: var(--muted-fg); text-decoration: underline; }
.lister-per strong { color: var(--fg); }
.lister-pages { display: flex; gap: 8px; align-items: center; margin-left: auto;
                font-variant-numeric: tabular-nums; flex-wrap: wrap; }
.lister-pages a { color: var(--primary-fg); }
.lister-here { color: var(--fg); }
th .sort-link { color: inherit; text-decoration: none; white-space: nowrap; }
th .sort-link:hover { text-decoration: underline; }
.bulk-bar {
  position: sticky; bottom: 12px; z-index: 30;
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  margin-top: 12px; padding: 10px 14px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.bulk-bar .bulk-count { font-weight: 600; font-size: 0.86rem;
                        font-variant-numeric: tabular-nums; }
.bulk-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.bulk-bar .bulk-clear { margin-left: auto; }
.bulk-row, .bulk-all { width: 15px; height: 15px; accent-color: var(--primary); }

/* ---- value tiles: nine things the platform does --------------------- */
/* They were bare text in a grid — an icon, a bold line, a paragraph, no
   surface — so nine of them read as one wall rather than nine things.
   And nine tiles in a four-column auto-fit grid leaves a lone orphan on
   the last row. Both fixed here: a real card each, and a 3 x 3 grid that
   comes out even. */

.value-band .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 900px) { .value-band .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 580px) { .value-band .grid { grid-template-columns: 1fr; } }

.value {
  position: relative;
  padding: 22px 22px 24px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--card);
  transition: transform .22s cubic-bezier(.2, .7, .3, 1),
              border-color .22s ease, box-shadow .22s ease;
}

/* The accent traces the whole outline instead of sitting as a bar along
   the top: a light travels the perimeter, and the mask fades it out on
   the way down, so the top edge is bright and the bottom is barely
   there. Built from a conic gradient rotated by an animatable custom
   property, masked to a 1.5px ring by the padding-box XOR trick. */
.value::after {
  content: ''; position: absolute; inset: -1px; z-index: 3;
  border-radius: inherit; pointer-events: none;
  opacity: 0; transition: opacity .35s ease;
}
.value:hover {
  transform: translateY(-4px);
  border-color: color-mix(in srgb, var(--primary) 35%, var(--border));
  box-shadow: 0 14px 34px rgba(20, 14, 30, .18);
}
.value:hover::after { opacity: 1; }

.value h3 { font-size: 1.02rem; margin-bottom: 6px; }
.value p  { font-size: 0.93rem; line-height: 1.6; }

.value-icon {
  font-size: 1.4rem;
  width: 44px; height: 44px;
  transition: transform .22s cubic-bezier(.2, .7, .3, 1), background .22s ease;
}
.value:hover .value-icon { transform: translateY(-2px) scale(1.06); }

/* Entrance: the tiles rise in sequence on load. Written as an animation
   with fill-mode both and no opacity:0 in the resting rule — so if the
   animation never runs, for any reason, the tiles are simply there.
   (An earlier pass on this page hid content and leaned on a script to
   reveal it; a backgrounded tab left it invisible. Not again.) */
@media (prefers-reduced-motion: no-preference) {
  .value { animation: valueRise .5s cubic-bezier(.2, .7, .3, 1) backwards; }
  .value:nth-child(1) { animation-delay: .02s; }
  .value:nth-child(2) { animation-delay: .07s; }
  .value:nth-child(3) { animation-delay: .12s; }
  .value:nth-child(4) { animation-delay: .17s; }
  .value:nth-child(5) { animation-delay: .22s; }
  .value:nth-child(6) { animation-delay: .27s; }
  .value:nth-child(7) { animation-delay: .32s; }
  .value:nth-child(8) { animation-delay: .37s; }
  .value:nth-child(9) { animation-delay: .42s; }
}
/* Transform only, never opacity. An animation that is paused — a
   backgrounded tab, a throttled compositor — holds its from-state,
   and a from-state of opacity:0 means invisible content. The worst
   this can do is leave a tile 14px low, still perfectly readable. */
@keyframes valueRise {
  from { transform: translateY(14px); }
  to   { transform: none; }
}

/* ---- hero motion ---------------------------------------------------- */
/* Restrained, and deliberately transform-only. An animation that is
   paused — backgrounded tab, throttled compositor — holds its from-state,
   so a from-state of opacity:0 is a promise to sometimes show nothing.
   Every keyframe below moves things; none of them hide anything. */

@media (prefers-reduced-motion: no-preference) {
  .hero-copy > * { animation: heroRise .62s cubic-bezier(.2, .7, .3, 1) backwards; }
  .hero-copy > :nth-child(1) { animation-delay: .04s; }
  .hero-copy > :nth-child(2) { animation-delay: .12s; }
  .hero-copy > :nth-child(3) { animation-delay: .2s; }
  .hero-copy > :nth-child(4) { animation-delay: .28s; }
  .hero-copy > :nth-child(5) { animation-delay: .36s; }
  .hero-copy > :nth-child(6) { animation-delay: .44s; }

  /* The fan arrives a beat after the words, then keeps breathing. */
  .flyer-stack { animation: flyerFloat 7s ease-in-out 1.4s infinite alternate; }
}

@keyframes heroRise {
  from { transform: translateY(16px); }
  to   { transform: none; }
}
@keyframes heroArtIn {
  from { transform: translateY(22px) scale(.97); }
  to   { transform: none; }
}
@keyframes flyerFloat {
  to { transform: translateY(-12px); }
}

/* The fan already spreads on hover; the whole stack now lifts with it,
   and the search field answers the pointer too. */
.hero-search input[type="search"] { transition: border-color .2s ease, background .2s ease; }
.hero-search input[type="search"]:focus { background: rgba(255,255,255,.08); }
.hero-stats div strong { transition: transform .2s ease; }
.hero-stats div:hover strong { transform: translateY(-2px); }

/* ---- depth on the feature tiles and the hero flyer ------------------ */
/* What is left of a longer experiment. Glass came out muddy over a busy
   page and went; the pointer-following sheen lit the middle of a card
   instead of its edge and went too. What survives is a perspective tilt
   driven by --rx/--ry, which the script sets from the pointer, and the
   travelling stroke further down. CSS owns every visual decision here;
   the script only reports where the pointer is. */

/* Solid, not glass. The frosted treatment was tried here and did not
   earn its place — translucency over a busy page reads as muddy, and
   fifteen backdrop filters cost real frames. The depth now comes from
   the border, the shadow and the tilt, which is enough. */
.value {
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: 0 10px 28px rgba(10, 8, 20, .14);
  transform: perspective(1000px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transform-style: preserve-3d;
  transition: transform .18s ease-out, box-shadow .25s ease, border-color .25s ease;
}
/* No sheen. A bright spot tracking the pointer over a chromatic band was
   tried here and read as a smear of glare sitting in the middle of the
   card — light in the background rather than on the shape. The light on
   these tiles is the stroke that travels the border, and nothing else;
   see "the travelling beam" below. */

.value:hover {
  transform: perspective(1000px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg))
             translateY(-6px) scale(1.012);
  border-color: color-mix(in srgb, var(--primary) 40%, transparent);
  box-shadow: 0 22px 48px rgba(10, 8, 20, .3);
}

/* The icon lifts off its own card, which is what sells the depth. */
.value-icon { transform: translateZ(28px); }
.value h3, .value p { transform: translateZ(12px); }

/* The hero flyer: same grammar, applied to artwork rather than a panel.
   An <img> cannot carry pseudo-elements, so the sheen rides on the
   stack above it. */
/* The hero flyer had the same sheen the feature tiles did — a bright
   spot following the pointer over a blue-and-pink band. It is gone for
   the same reason: it lit the middle of the artwork instead of its
   edges, and the artwork is the thing worth looking at. The tilt stays;
   that reads as depth rather than glare. */

/* Touch and reduced motion get the flat, honest version: solid panels,
   no tilt, no sheen. Glass without a pointer is just a blurry box. */
@media (hover: none), (prefers-reduced-motion: reduce) {
  .value { transform: none; }
  .value-icon, .value h3, .value p { transform: none; }
}

/* ---- the travelling beam ------------------------------------------- */
/* An angle that CSS can actually animate. Declared with @property so the
   browser interpolates it as an angle rather than swapping a string. */
@property --beam {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

/* The ring is a full-size conic gradient with a hole punched through the
   middle: two mask layers, the inner excluded from the outer, leaving
   only the 1.5px edge the padding defines.

   Both layers must be fully opaque. `exclude` is the XOR operator —
   result alpha is a + b - 2ab — so a second layer that fades to .55
   leaves 1 + .55 - 1.1 = .45 alpha across the middle of the card, and
   the conic gradient washes through the interior at nearly half
   strength. That was the coloured glare: not a stray effect, this rule
   quietly failing to cut its own hole. The vertical fade it was there
   for is not worth a leak, so it is gone and this is a plain stroke. */
@supports ((mask-composite: exclude) or (-webkit-mask-composite: xor)) and (background: conic-gradient(from 0deg, red, blue)) {
  .value::after {
    padding: 1.5px;
    background: conic-gradient(from var(--beam),
      transparent 0deg,
      transparent 200deg,
      color-mix(in srgb, var(--primary) 70%, #fff) 268deg,
      #b98bff 302deg,
      color-mix(in srgb, var(--primary) 85%, #fff) 330deg,
      transparent 360deg);

    -webkit-mask:
      linear-gradient(#000 0 0) content-box,
      linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask:
      linear-gradient(#000 0 0) content-box,
      linear-gradient(#000 0 0);
    mask-composite: exclude;

    /* Parked until the pointer arrives: fifteen cards spinning a conic
       gradient nobody is looking at is work for nothing. */
    animation: beamTravel 4.5s linear infinite;
    animation-play-state: paused;
  }
  .value:hover::after { animation-play-state: running; }
}

@keyframes beamTravel { to { --beam: 360deg; } }

/* Without @property the angle cannot animate, so the beam would sit
   still. A static ring is a worse answer than the honest one: fall back
   to the lit top edge. */
@supports not (background: conic-gradient(from var(--beam), red, blue)) {
  .value::after {
    inset: -1px -1px auto -1px; height: 2px; border-radius: 14px 14px 0 0;
    background: linear-gradient(90deg, var(--primary), #7c4dbe);
  }
}

@media (hover: none), (prefers-reduced-motion: reduce) {
  .value::after { display: none; }
}

/* ---- events: a hero with light behind it ---------------------------- */
/* The page used to start cold: a heading, four controls, then cards.
   The search is the reason anybody opens this page, so it now sits on a
   lit surface of its own — a field of soft colour behind, the listing's
   own artwork blurred into it, and a frosted panel floating on top that
   carries the heading and every control. Inside the panel everything
   still lines up on one measure, which is what the layout pass fixed. */

.eh {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: 56px 0 50px;
  background: var(--bg);
}

/* The colour field. Three orbs in the brand's own hues rather than the
   reference's purples, sized in viewport units so the composition holds
   its proportions instead of clustering on a wide screen. */
.eh-bg { position: absolute; inset: -10% -5%; z-index: -2; }

.eh-orb {
  position: absolute;
  aspect-ratio: 1;
  border-radius: 50%;
  filter: blur(64px);
  will-change: transform;
}
.eh-orb-1 { width: 30vw; min-width: 300px; background: var(--primary); opacity: .32; top: -14%; right: 5%; }
.eh-orb-2 { width: 26vw; min-width: 260px; background: #6d3fa0;      opacity: .30; bottom: -26%; left: 17%; }
.eh-orb-3 { width: 20vw; min-width: 200px; background: #e08a2e;      opacity: .20; top: 20%; left: -5%; }

/* Real event artwork, blurred past recognition and used as colour. The
   backdrop is made of the listing it introduces, so it changes with the
   filters — and when no event carries its own image the orbs above
   carry the section alone. */
.eh-plate {
  position: absolute;
  width: 22vw; min-width: 200px; aspect-ratio: 1;
  object-fit: cover;
  border-radius: 50%;
  filter: blur(40px) saturate(1.45);
  opacity: .38;
}
.eh-plate-1 { top: 2%; right: 27%; }
.eh-plate-2 { bottom: -12%; right: 3%; }
.eh-plate-3 { top: 28%; left: 31%; opacity: .26; }

/* The band melts into the grid below rather than ending on a seam. */
.eh::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to bottom,
    transparent 0%, transparent 52%,
    color-mix(in srgb, var(--bg) 65%, transparent) 84%,
    var(--bg) 100%);
}

/* The glass. Its own tokens, so it agrees with every other translucent
   surface on the platform and is tuned per theme — the alpha that reads
   as frosted on white reads as dirty on near-black. */
.eh-panel {
  position: relative;
  padding: 30px 34px;
  border-radius: 24px;
  background: var(--glass-bg);
  border: 1px solid var(--glass-line);
  box-shadow: var(--glass-shadow);
  backdrop-filter: blur(var(--glass-blur)) saturate(160%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(160%);
}
/* The lit top edge that makes a pane read as glass rather than as a
   panel someone set the opacity on. */
.eh-panel::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(158deg, rgba(255, 255, 255, .16), transparent 44%);
}
.eh-panel > * { position: relative; }

.eh-eyebrow {
  margin: 0 0 7px;
  font-size: 0.68rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .16em;
  color: var(--primary-fg);
}
/* The brand crimson lands around 2:1 against the dark glass — fine for a
   button fill, far short of the 4.5:1 small text needs. Lifting it
   toward white keeps the brand reading without shouting. */
html[data-theme="dark"] .eh-eyebrow { color: color-mix(in srgb, var(--primary) 45%, #fff); }
.eh-panel h1 { margin: 0 0 4px; }
.events-count { color: var(--muted-fg); margin: 0 0 20px; }

.eh-results { padding-top: 32px; }

/* Full width of the panel, because it is the primary control here and
   the content it filters is full width too. */
.es-search {
  display: flex; gap: 10px; align-items: stretch;
  width: 100%; max-width: none; margin: 0 0 12px;
}
.es-search input[type="search"] {
  flex: 1 1 auto; min-width: 0;
  padding: 15px 20px; font-size: 1rem;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--card) 60%, transparent);
  transition: border-color .2s ease, box-shadow .2s ease, background .2s ease;
}
.es-search input[type="search"]:focus {
  outline: none;
  background: var(--card);
  border-color: color-mix(in srgb, var(--primary) 55%, var(--border));
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 14%, transparent);
}
.es-search .btn { border-radius: 12px; padding-inline: 30px; flex: 0 0 auto; }

/* The refinements sit on the same left edge as the search above them.
   The pill treatment itself now lives on .filters, shared with every
   other screen that narrows a list — see "filter pills" below. `width:
   auto` there is load-bearing: the base form rule sets every select to
   100%, which in a flex row put each one on a line of its own and turned
   four small filters into four full-width bars. */
.es-refine select { border-color: var(--glass-line); }
.es-reset { border-radius: 999px; align-self: center; }

/* A slow drift, so the light behind the glass is alive without asking to
   be watched. Transform-only keyframes: an opacity from-state leaves the
   orbs invisible in a backgrounded tab where the animation never starts. */
@media (prefers-reduced-motion: no-preference) {
  .eh-orb-1 { animation: ehDrift1 19s ease-in-out infinite alternate; }
  .eh-orb-2 { animation: ehDrift2 23s ease-in-out infinite alternate; }
  .eh-orb-3 { animation: ehDrift3 27s ease-in-out infinite alternate; }
}
@keyframes ehDrift1 { to { transform: translate3d(-5%, 6%, 0) scale(1.09); } }
@keyframes ehDrift2 { to { transform: translate3d(7%, -5%, 0) scale(1.06); } }
@keyframes ehDrift3 { to { transform: translate3d(4%, 8%, 0) scale(1.12); } }

@media (max-width: 900px) {
  .eh { padding: 40px 0 38px; }
  .eh-panel { padding: 24px 22px; border-radius: 20px; }
}

/* Two columns on a phone, deliberately. One column turns a listing into
   an endless scroll where nothing can be compared; two lets the eye
   scan. The cards give up their padding and step down a type size to
   earn the room. */
@media (max-width: 620px) {
  .grid-auto { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .event-card .card-body, .event-card .body { padding: 12px; }
  .event-card h3 { font-size: 0.93rem; line-height: 1.3; }
  .event-card .muted, .event-card .small { font-size: 0.8rem; }
  .event-card .thumb { aspect-ratio: 16 / 10; }

  /* A 161px card cannot carry what a 272px one carries. Everything a
     phone listing needs to answer is "what, when, how much" — the
     description, the venue line, the category and flyer badges and the
     View button are all reachable one tap away on the event itself, and
     at this width they turn the card into a wall. The whole card is
     already a link, so losing the button costs nothing. */
  .event-card .body > .flex.wrap { display: none; }
  .event-card .body > p.small { display: none; }
  .event-card .meta span:nth-child(2) { display: none; }
  .event-card .foot .btn { display: none; }
  .event-card .foot { padding-top: 2px; }
  .event-card .meta { gap: 2px; font-size: 0.74rem; }
  .eh { padding: 30px 0 30px; }
  .eh-panel { padding: 20px 18px; border-radius: 18px; }
  .eh-results { padding-top: 22px; }
  .es-search { flex-direction: column; }
  .es-search .btn { width: 100%; }
  .es-refine { gap: 7px; }
  /* `min-width: 0` is what makes the 50% basis stick: without it a
     select is at least as wide as its longest option, so the two with
     longer labels each claimed a full row and the four filters came out
     ragged across three. */
  .es-refine select { flex: 1 1 calc(50% - 4px); min-width: 0; }
}
/* ---- the dropdown list ---------------------------------------------- */
/* The list a select drops is drawn by the operating system, so no rule
   in this file could ever reach it — which is why a dark page opened a
   white box with a blue bar in it. assets/js/ui.js draws this one
   instead, as a child of <body> so a card's rounded overflow cannot cut
   it in half. Touch devices keep their native picker on purpose: a
   phone's own sheet beats anything a page can draw. */
.sel-menu {
  position: fixed; z-index: 300;
  max-height: min(320px, 60vh);
  overflow-y: auto;
  /* Reaching the end of a long list must not start scrolling the page
     behind it — that is how a menu appears to jump away mid-choice. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 6px;
  border-radius: 14px;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: 0 18px 44px rgba(10, 8, 20, .28), 0 2px 6px rgba(10, 8, 20, .12);
  /* No fill-mode and no from-state left standing: if the animation never
     ticks — a backgrounded tab, reduced motion — the natural style is
     already the visible one. */
  animation: selIn .13s cubic-bezier(.2, .7, .3, 1);
}
@keyframes selIn { from { opacity: 0; transform: translateY(-5px); } }
.sel-menu.is-up { animation-name: selInUp; }
@keyframes selInUp { from { opacity: 0; transform: translateY(5px); } }

@media (prefers-reduced-motion: reduce) { .sel-menu { animation: none; } }

.sel-opt {
  padding: 9px 12px;
  border-radius: 9px;
  font-size: 0.93rem; line-height: 1.3;
  color: var(--fg);
  cursor: pointer;
  /* The list is as wide as it needs to be, and a long option wraps
     rather than being cut off — a truncated choice is not a choice. */
  overflow-wrap: anywhere;
}
.sel-opt.is-active { background: var(--muted); }
.sel-opt.is-selected { font-weight: 700; color: var(--primary-fg); }
html[data-theme="dark"] .sel-opt.is-selected { color: color-mix(in srgb, var(--primary) 45%, #fff); }
.sel-opt.is-selected.is-active { background: color-mix(in srgb, var(--primary) 12%, var(--muted)); }
.sel-opt.is-disabled { opacity: .45; cursor: default; }

.sel-group {
  padding: 9px 12px 5px;
  font-size: 0.68rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted-fg);
}

/* An open select should look open, the way the native one does. */
select[aria-expanded="true"] {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--ring);
}

/* ---- filter pills ---------------------------------------------------- */
/* The rounded filter treatment the events hero introduced, available to
   any screen that filters a list: put .filters on the row. Kept separate
   from the base form styling, because a pill is right for "narrow this
   list" and wrong for "fill this in". */
.filters {
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
}
.filters select,
.filters input[type="search"],
.filters input[type="text"],
.filters input[type="date"] {
  width: auto; flex: 0 0 auto;
  padding: 9px 14px; font-size: 0.86rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background-color: color-mix(in srgb, var(--card) 55%, transparent);
  cursor: pointer;
  transition: border-color .18s ease, background-color .18s ease;
}
.filters select { padding-right: 32px; background-position: right 12px center; }
.filters select:hover,
.filters input:hover {
  border-color: color-mix(in srgb, var(--primary) 45%, var(--border));
  background-color: color-mix(in srgb, var(--card) 85%, transparent);
}
.filters input[type="search"], .filters input[type="text"], .filters input[type="date"] {
  cursor: text;
}
.filters .btn { border-radius: 999px; }

/* The toolbar is the same idea under an older name — every dashboard and
   admin screen that narrows a list uses it — so it gets the same
   treatment rather than a second look that means the same thing. Its
   labels stay: a toolbar's controls are not always self-describing the
   way "All categories" is, and a bare pill with no caption is a guess.
   They shrink to captions above the control instead of disappearing. */
.toolbar .field label {
  font-size: 0.68rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted-fg);
  margin-bottom: 5px; padding-left: 4px;
}
.toolbar .field input[type="search"],
.toolbar .field input[type="text"],
.toolbar .field select {
  border-radius: 999px;
  padding-left: 16px;
  transition: border-color .18s ease, box-shadow .18s ease;
}
.toolbar .field select { padding-right: 34px; background-position: right 13px center; cursor: pointer; }
.toolbar .field select:hover {
  border-color: color-mix(in srgb, var(--primary) 45%, var(--border));
}
.toolbar .btn { border-radius: 999px; }

/* While live results are on their way, the old ones go quiet rather than
   disappearing — a list that empties and refills reads as a page break. */
[data-live][aria-busy="true"] { opacity: .55; transition: opacity .15s ease; }

/* ---- pricing: the card ----------------------------------------------- */
/* A column, so the button sits at the bottom whatever the feature list
   above it does. This was an inline style on the element, where no
   template could override it — which is why the row layout below could
   set grid-template-columns all it liked and still get a flex column. */
.price-card > .card-body {
  display: flex; flex-direction: column; height: 100%;
}

/* ---- pricing: four across -------------------------------------------- */
/* Four plans in four columns is tighter than three, so the card gives up
   padding and steps its price down rather than letting the columns go
   ragged. It still folds to two and then one on the way down, which
   grid-4 already handles. */
.price-four .price-card > .card-body { padding: 20px 18px; }
.price-four .price-card .amount { font-size: 1.5rem; }
.price-four .price-card ul { font-size: 0.86rem; }

/* ---- pricing: the scroller ------------------------------------------- */
/* For a catalogue that will not fit a screen at any column count. One
   row, swiped or scrolled sideways, each card snapping into place. It is
   focusable and labelled because a scrolling region a keyboard cannot
   reach is a region some people simply do not get. */
.price-scroll {
  display: flex; gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Room for the raised card's lift and its shadow, which a plain
     overflow container would otherwise clip. */
  padding: 6px 4px 16px;
  scrollbar-width: thin;
}
.price-scroll .price-card {
  flex: 0 0 clamp(240px, 78vw, 300px);
  scroll-snap-align: start;
}
.price-scroll:focus-visible { outline: 2px solid var(--primary); outline-offset: 4px; border-radius: var(--radius); }

/* ---- pricing: one row per plan --------------------------------------- */
/* Three columns stop working once a catalogue outgrows them — the cards
   go narrow, the feature lists go ragged, and the prices stop lining up
   with each other, which is the one thing a pricing page has to do. This
   is the same cards, laid out one per row: the price and the button sit
   in a fixed column on the right so they line up down the page, and the
   plan's own copy takes whatever width is left. Chosen per site in
   /admin/packages. */
.price-rows { display: flex; flex-direction: column; gap: 12px; }
.price-rows .price-card { width: 100%; }

/* Explicit placement, not named areas. Named areas only work when every
   child is assigned one; the copy here was placed by column instead, so
   it auto-flowed into the grid and shoved the price out of the column it
   was supposed to line up in. Row and column on each child is longer to
   write and cannot drift. */
.price-rows .price-card > .card-body {
  display: grid;   /* beats the flex column above — same element, more specific */
  grid-template-columns: minmax(0, 1fr) 220px;
  column-gap: 28px; row-gap: 6px;
  align-items: start;
  height: auto;
}
.price-rows .price-card > .card-body > *          { grid-column: 1; }
.price-rows .price-card > .card-body > h3         { grid-row: 1; }
.price-rows .price-card > .card-body > .price-tag { grid-row: 2; }
.price-rows .price-card > .card-body > ul         { grid-row: 3; columns: 2; column-gap: 26px; margin-top: 10px; }
/* The price and its button share the right-hand column, which is what
   makes the prices line up down the page — the one thing a list of
   plans has to get right. */
.price-rows .price-card > .card-body > .amount {
  grid-column: 2; grid-row: 1 / span 2;
  text-align: right; margin: 0;
}
.price-rows .price-card > .card-body > .btn { grid-column: 2; grid-row: 3; }

@media (max-width: 720px) {
  /* A 220px price column on a phone leaves nothing for the plan. */
  .price-rows .price-card > .card-body { grid-template-columns: 1fr; }
  .price-rows .price-card > .card-body > * { grid-column: 1; grid-row: auto; }
  .price-rows .price-card > .card-body > .amount { text-align: left; }
  .price-rows .price-card > .card-body > ul { columns: 1; }
}

/* ---- report tables and their cut tabs -------------------------------- */
/* Shared by the session attendance report and the accreditation gate log,
   which ask the same question of different doors. These began inline on
   one page; the second page needing them is the moment they stop being
   that page's business. */
.att-tabs {
  display: flex; gap: 2px; flex-wrap: wrap;
  background: var(--bg-alt); padding: 3px;
  border-radius: 10px; border: 1px solid var(--border);
}
.att-tabs a {
  padding: 7px 14px; border-radius: 7px;
  font-size: 0.86rem; font-weight: 600;
  color: var(--muted-fg); text-decoration: none; white-space: nowrap;
}
.att-tabs a:hover { color: var(--fg); }
.att-tabs a[aria-current] { background: var(--card); color: var(--fg); box-shadow: var(--shadow-sm); }

table.att { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
table.att th {
  text-align: left; font-size: 0.74rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted-fg);
  padding: 10px 12px; border-bottom: 1px solid var(--border); white-space: nowrap;
}
table.att td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
table.att tr:last-child td { border-bottom: 0; }
/* Figures line up in a column or they cannot be compared down it. */
table.att td.t, table.att th.t {
  font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right;
}
table.att tfoot th { border-top: 1px solid var(--border); border-bottom: 0; color: var(--fg); }
table.att td.who b { display: block; }
table.att td.who span { font-size: 0.8rem; color: var(--muted-fg); }
.att-wrap { overflow-x: auto; }

/* ---- back to top ----------------------------------------------------- */
/* Every long page gets one. It stays out of the way until there is
   something to come back from, and it is a real link to #top so it works
   with scripting off. */
.to-top {
  position: fixed; right: 20px; bottom: 20px; z-index: 60;
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--card); color: var(--fg);
  border: 1px solid var(--border);
  box-shadow: 0 8px 22px rgba(10, 8, 20, .22);
  opacity: 0; visibility: hidden; transform: translateY(10px);
  transition: opacity .25s ease, transform .25s ease, visibility .25s;
}
.to-top.on { opacity: 1; visibility: visible; transform: none; }
.to-top:hover {
  border-color: color-mix(in srgb, var(--primary) 45%, var(--border));
  transform: translateY(-2px);
}
.to-top svg { width: 18px; height: 18px; stroke: currentColor; stroke-width: 2;
              fill: none; stroke-linecap: round; stroke-linejoin: round; }

@media (prefers-reduced-motion: reduce) { .to-top { transition: none; } }

/* =====================================================================
   Sheet — the editing popup
   ---------------------------------------------------------------------
   Editing used to mean following a link to ?edit=<id>, which reloaded
   the page and dropped an open <details> somewhere below the record you
   came from. On a programme with a hundred and eighteen sessions that is
   a scroll each way, and you lose your place in the list every time.

   Now the same form opens over the list. Built on <dialog> so the focus
   trap, the Escape key and the inert background are the browser's job
   rather than ours — the hand-rolled versions of those are where modals
   usually go wrong for anyone using a keyboard.

   Without scripting the link still navigates and the form still renders
   in the page. This is an upgrade to a working thing, not a replacement.
   ===================================================================== */

.sheet {
  padding: 0;
  border: 0;
  width: min(760px, calc(100vw - 2rem));
  max-height: min(88vh, 900px);
  border-radius: var(--radius-lg);
  background: var(--card);
  color: var(--card-fg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.sheet::backdrop {
  background: rgba(16, 20, 24, .55);
  backdrop-filter: blur(3px);
}

/* Opens from slightly below and small, which reads as the panel coming
   forward rather than blinking into existence. */
@keyframes sheet-in {
  from { opacity: 0; transform: translateY(12px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

.sheet[open] { animation: sheet-in .18s cubic-bezier(.2, .8, .3, 1); }
.sheet[open]::backdrop { animation: sheet-fade .18s ease; }

@keyframes sheet-fade { from { opacity: 0; } to { opacity: 1; } }

.sheet-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  /* Stays put while the form scrolls: on a long form the title is the
     one thing that says what you are editing. */
  position: sticky;
  top: 0;
  background: var(--card);
  z-index: 2;
}

.sheet-head h3 { margin: 0; font-size: 1.02rem; font-weight: 700; flex: 1 1 auto; min-width: 0; }
.sheet-head .note { display: block; font-size: 0.8rem; font-weight: 400; color: var(--muted-fg); }

.sheet-close {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--bg-alt);
  color: var(--muted-fg);
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}

.sheet-close:hover { background: var(--primary-50); color: var(--primary-fg); border-color: var(--primary); }

.sheet-body {
  padding: 18px 20px 22px;
  overflow-y: auto;
  max-height: calc(min(88vh, 900px) - 64px);
  overscroll-behavior: contain;
}

/* The form arrives from a page where it was a <details> fold. Inside the
   sheet the fold is redundant — the sheet is the disclosure — so its
   chrome is dropped rather than the markup being duplicated per page. */
.sheet-body > .fold,
.sheet-body > .card { border: 0; padding: 0; margin: 0; background: none; box-shadow: none; }
.sheet-body > .fold > summary { display: none; }

.sheet-busy {
  display: grid;
  place-items: center;
  min-height: 160px;
  color: var(--muted-fg);
  font-size: 0.93rem;
}

/* On a phone it is a bottom sheet: a centred dialog on a small screen
   puts the fields under the thumb and the keyboard over them. */
@media (max-width: 640px) {
  .sheet {
    width: 100vw;
    max-width: 100vw;
    max-height: 92vh;
    margin: auto 0 0;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }

  .sheet-body { max-height: calc(92vh - 64px); }

  @keyframes sheet-in {
    from { opacity: 0; transform: translateY(100%); }
    to   { opacity: 1; transform: none; }
  }
}

@media (prefers-reduced-motion: reduce) {
  .sheet[open], .sheet[open]::backdrop { animation: none; }
}

/* ---- touch targets the label strategy does not reach ---------------
   The mobile block further down already handles this properly: buttons
   gain height, and a checkbox stays its intrinsic size because the
   label.check wrapping it carries a 44px target. That is a better
   answer than fattening every box, and it is left alone.

   Two things sit outside it. A row-select checkbox in a table has no
   label to hide behind — it is bare in a <td>, and it measured 15px on
   a phone, on the screens where somebody ticks forty of them. And the
   small chrome — remove, close, sign out, back — measured 16 to 27px.

   24px is the WCAG 2.5.8 minimum and is deliberate for the table: a
   44px box in a list of five hundred attendees would push the row it
   belongs to off the screen. */
@media (max-width: 640px) {
  .bulk-row, .bulk-all, .cf-pick, .sp-on { width: 24px; height: 24px; }

  .x, .signout, .crumb-back, .att-remove,
  .tchip .x, .chip .x { min-height: 44px; min-width: 44px;
                        display: inline-flex; align-items: center; justify-content: center; }
}

/* ---- quick actions ---------------------------------------------------
   A bar of the jobs an organiser repeats all week — printing above all —
   pinned where a thumb already is.

   Four things already live at the bottom of a dashboard screen: the bulk
   bar on every listing, the save bar on every form, back-to-top, and the
   toast stack. All four are told how tall this is and move up by it, so
   the bar never covers a Save button or the actions for rows somebody
   just ticked. That is the whole reason --qa-h exists rather than a
   hardcoded offset in five places. */
:root { --qa-h: 0px; }
/* Measured, per breakpoint: on a desktop the bar is the same height
   open or closed, because closing only hides the strip beside a
   toggle that was already the tallest thing in it. */
body:has(.qa) { --qa-h: 51px; padding-bottom: calc(var(--qa-h) + 14px); }

.qa {
  position: fixed; z-index: 50;
  left: 50%; transform: translateX(-50%);
  bottom: 12px; max-width: min(1100px, calc(100vw - 24px));
  display: flex; align-items: center; gap: 8px;
  padding: 6px; border-radius: 999px;
  background: var(--glass-bg); backdrop-filter: blur(var(--glass-blur));
  border: 1px solid var(--glass-line); box-shadow: var(--glass-shadow);
}

.qa-toggle {
  display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto;
  border: 0; border-radius: 999px; cursor: pointer;
  padding: 8px 14px; font: inherit; font-size: var(--t-sm); font-weight: 700;
  background: var(--primary); color: #fff;
}
.qa-toggle-tx { white-space: nowrap; }
.qa-chev { transition: transform .15s ease; }
.qa[data-open="1"] .qa-chev { transform: rotate(90deg); }

/* The list scrolls inside the bar rather than pushing the page wide —
   an event with every module on has more of these than a phone is.
   Hidden rather than removed when closed, so the toggle keeps its
   place and nothing reflows. */
.qa-list {
  display: flex; align-items: center; gap: 4px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* No scrollbar, for a reason beyond looks: a horizontal bar
     claims ~10px of height only when the strip overflows, so the
     bar was 68px tall on a screen with eight actions and 58px on
     one with two — and --qa-h, which everything else positions
     against, cannot be two numbers. The fade says there is more.
     Scrolling still works by touch, wheel and keyboard. */
  scrollbar-width: none;
  mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
}
.qa-list::-webkit-scrollbar { display: none; }
.qa[data-open="0"] .qa-list { display: none; }

.qa-item {
  display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto;
  padding: 8px 13px; border-radius: 999px;
  font-size: var(--t-sm); font-weight: 600; white-space: nowrap;
  color: var(--fg); text-decoration: none;
}
.qa-item:hover { background: var(--muted); text-decoration: none; }
.qa-item .ni { color: var(--muted-fg); flex: none; }
.qa-item:hover .ni { color: var(--primary-fg); }

/* The event chip, shown only when the bar guessed which event these act
   on. Reads as a label rather than another action. */
.qa-ev { font-weight: 700; }
.qa-ev span { max-width: 15ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qa-ev .ni { color: var(--ok); }
.qa-ev + .qa-item { border-left: 1px solid var(--border); margin-left: 2px; padding-left: 12px; }

/* Out of the way of the things that were here first. */
body:has(.qa) .bulk-bar      { bottom: calc(12px + var(--qa-h)); }
body:has(.qa) .sticky-actions { bottom: var(--qa-h); }
body:has(.qa) .to-top        { bottom: calc(20px + var(--qa-h)); }
body:has(.qa) .toasts        { bottom: calc(18px + var(--qa-h)); }

@media (max-width: 640px) {
  /* The toggle grows to a 44px target here, so the bar is taller than
     it is on a desktop and everything told to sit above it has to know
     the new number. Measured, not guessed: 68 open, 58 closed. */
  body:has(.qa) { --qa-h: 58px; }

  .qa { left: 12px; right: 12px; transform: none; max-width: none; }
  .qa-toggle-tx { display: none; }          /* the icon carries it */
  .qa-toggle { min-height: 44px; min-width: 44px; justify-content: center; }
  .qa-item { min-height: 44px; }
}

@media print { .qa { display: none; } }

/* ---- the first-run tour --------------------------------------------
   Four steps around the furniture, once. The ring is drawn around the
   real element rather than over a screenshot, so it cannot go stale
   the way a picture of a sidebar does. */
.tour { position: fixed; inset: 0; z-index: 200; }
.tour-veil { position: absolute; inset: 0; background: rgba(8, 8, 12, .55); }

.tour-ring {
  position: fixed; border-radius: 12px; pointer-events: none;
  border: 2px solid var(--primary);
  box-shadow: 0 0 0 9999px rgba(8, 8, 12, .55);
  transition: top .18s ease, left .18s ease, width .18s ease, height .18s ease;
}
/* With a ring on screen its own shadow is the veil, so the flat one
   underneath would double the dimming. */
.tour:has(.tour-ring:not([hidden])) .tour-veil { opacity: 0; }

.tour-card {
  position: fixed; width: min(340px, calc(100vw - 24px));
  background: var(--card); color: var(--card-fg);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--lift-3); padding: 16px 18px;
  transition: top .18s ease, left .18s ease;
}
.tour-card:focus { outline: none; }
.tour-count { font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .07em;
              color: var(--muted-fg); font-weight: 700; margin: 0 0 6px; }
.tour-card h3 { font-size: var(--t-lg); margin: 0 0 6px; }
.tour-body { font-size: var(--t-md); color: var(--muted-fg); line-height: 1.55; margin: 0 0 14px; }
.tour-btns { display: flex; align-items: center; gap: 8px; }
.tour-btns .grow { flex: 1 1 auto; }

@media (prefers-reduced-motion: reduce) {
  .tour-ring, .tour-card { transition: none; }
}

/* ---- the filter bar stays reachable ---------------------------------- */
/*
 * A search you have to scroll back up to reach is a search people stop
 * using. On four hundred attendees the box is off screen by the second
 * row anyone cares about, and the way out is the browser's own find —
 * which searches the page rather than the data, and so lies about how
 * many matches there are.
 *
 * Sticky under the header rather than fixed: the header is itself
 * sticky at top:0 and exactly --header-h tall, so the two sit flush
 * with no gap for rows to show through. z-index sits below the header
 * (50) and above ordinary content.
 *
 * Padding rather than margin does the spacing, because the background
 * has to cover the full band — a margin leaves a transparent strip that
 * table rows scroll through.
 *
 * Every .toolbar on the platform is a direct child of the page column
 * and so sits on --bg. That was checked, all seven of them. If one is
 * ever put inside a card it will want --card instead, and it will look
 * obviously wrong straight away, which is the right kind of failure.
 */
.toolbar {
  position: sticky;
  top: var(--header-h);
  z-index: 20;
  background: var(--bg);
  padding-block: 12px;
  margin-bottom: 4px;
}

/*
 * Only once it has actually stuck. Applied at rest it reads as a second
 * header permanently parked under the first, which is heavier than the
 * page needs when nothing is scrolling. ui.js sets the class.
 */
.toolbar.is-stuck {
  border-bottom: 1px solid var(--border);
  box-shadow: 0 10px 16px -14px rgb(0 0 0 / 55%);
}

/* Printing a pinned bar over the first rows helps nobody. */
@media print {
  .toolbar { position: static; box-shadow: none; border-bottom: 0; }
}

/* ---- the public events search, pinned ---------------------------------
 *
 * Its own band below the hero rather than inside the glass panel, and
 * that is a requirement rather than a preference: sticky is bounded by
 * its parent's box, the panel ends before the results start, and the
 * panel's backdrop-filter makes it the containing block for fixed
 * children too — so neither mechanism works from in there.
 *
 * Only the bar pins. The eyebrow, the title and the count scroll away,
 * which is the point: what you need at row two hundred is the search,
 * not the word "Events".
 */
.events-dock {
  position: sticky;
  top: var(--header-h);
  z-index: 30;
  background: var(--bg);
  padding-block: 14px;
}

.events-dock .events-form { margin: 0; }

.events-dock.is-stuck {
  border-bottom: 1px solid var(--border);
  box-shadow: 0 10px 16px -14px rgb(0 0 0 / 55%);
}

/* Pinned over the first cards helps nobody on paper. */
@media print {
  .events-dock { position: static; box-shadow: none; border-bottom: 0; }
}

/* ---- a pill-shaped link ------------------------------------------------
 *
 * The guide grew its own copy of this and so did the landing page. One
 * component, in the design system, so the third place that needs it
 * inherits the focus ring and the touch target rather than reinventing
 * them at 5px of padding.
 */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 5px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--muted-fg);
  text-decoration: none;
  white-space: nowrap;
}

.pill:hover { border-color: var(--primary); color: var(--primary-fg); text-decoration: none; }

/* The chosen one reads as chosen without colour alone carrying it — the
   border weight and the ink both change. */
.pill.on {
  border-color: var(--primary);
  color: var(--primary-fg);
  background: var(--primary-tint, transparent);
  font-weight: 700;
}

/* ---- an organiser's own landing page -----------------------------------
 *
 * Their name is the masthead. Evenz appears once, small, at the bottom,
 * because whoever is buying a ticket is entitled to know who is handling
 * the payment — but this is the organiser's page, not ours.
 *
 * The accent is a FILL and only a fill. It fills the eyebrow and the
 * buttons, and --lp-on-accent is the ink that survives on it, worked out
 * from luminance rather than hoped for. It is never text on the page
 * background: an organiser picks from a swatch, and the first navy
 * chosen here put the title at roughly 1.3:1 against a dark page.
 */
.lp {
  --lp-ink: var(--lp-accent, var(--primary));
  --lp-on: var(--lp-on-accent, #fff);
}

/* The accent as a fill, everywhere it appears. */
.lp .pill.on,
.lp .btn-primary {
  background: var(--lp-ink);
  border-color: var(--lp-ink);
  color: var(--lp-on);
}

/* ---- the masthead ------------------------------------------------------
 *
 * A surface rather than a gap. The wash is the organiser's colour at a
 * few per cent — enough to tint the block and seat it against the page,
 * nowhere near enough to fight the type sitting on it. color-mix keeps
 * that honest whatever colour arrives.
 */
.lp-head {
  position: relative;
  padding: var(--space-2xl) 0 var(--space-xl);
  background:
    radial-gradient(120% 140% at 8% 0%,
      color-mix(in srgb, var(--lp-ink) 14%, transparent) 0%,
      transparent 62%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--lp-ink) 6%, transparent) 0%,
      transparent 100%);
  border-bottom: 1px solid var(--border);
}

.lp-logo {
  display: block;
  max-height: 72px;
  max-width: 240px;
  width: auto;
  margin-bottom: var(--space-md);
  object-fit: contain;
}

/* The count, wearing the accent. Small, upper, tracked — a label rather
   than a sentence, so the name below it is unmistakably the headline. */
.lp-eyebrow {
  display: inline-block;
  margin: 0 0 var(--space-sm);
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--lp-ink);
  color: var(--lp-on);
  font-size: var(--t-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.lp-title {
  margin: 0;
  /* Ordinary page ink. The accent is behind it, not in it. */
  color: var(--fg);
  font-size: clamp(2.1rem, 1.1rem + 3.4vw, 3.4rem);
  line-height: 1.04;
  letter-spacing: -0.028em;
  font-weight: 800;
  text-wrap: balance;
}

.lp-intro {
  margin: var(--space-sm) 0 0;
  max-width: var(--measure, 62ch);
  color: var(--muted-fg);
  font-size: 1.06rem;
  line-height: 1.6;
  text-wrap: pretty;
}

/* The one piece of us. Seated on a hairline so it reads as a colophon
   rather than an orphaned sentence. */
.lp-by {
  margin: var(--space-xl) 0 var(--space-2xl);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border);
  text-align: center;
  font-size: var(--t-sm);
  color: var(--muted-fg);
}

.lp-by a { color: var(--muted-strong); }

@media (max-width: 640px) {
  .lp-head { padding: var(--space-xl) 0 var(--space-lg); }
  .lp-logo { max-height: 52px; }
}

/* ---- landing page templates --------------------------------------------
 *
 * Four layouts, one markup. The template is a class on the wrapper and
 * everything below hangs off it, so a new one is a class and a rule
 * rather than a second view to keep in step with the first.
 *
 * --lp-accent is set inline by the view, from a value validated as six
 * hex digits before it goes anywhere near a style attribute. Unset, the
 * page falls back to the platform's own accent and looks exactly as it
 * did before any of this existed.
 */
/* Links pick up the accent on hover only — a whole page of accent-tinted
   links is a page with no emphasis left to give. */
.lp a:not(.btn):not(.pill):hover { color: var(--lp-ink); }

/* ---- Poster: a picture across the top, the name over it --------------- */
.lp-head.has-cover { position: relative; padding: 0; }

.lp-head.has-cover .lp-cover {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.lp-head.has-cover .lp-cover img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}

/* A scrim, because a photograph is not a background until the type on it
   can be read. Dark enough at the bottom for white text at any image. */
.lp-head.has-cover .lp-cover::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgb(0 0 0 / 25%) 0%, rgb(0 0 0 / 78%) 100%);
}

.lp-head.has-cover > .container {
  position: relative;
  padding-top: 130px;
  padding-bottom: 34px;
}

.lp-head.has-cover .lp-title,
.lp-head.has-cover .lp-intro,
.lp-head.has-cover .lp-count { color: #fff; }

.lp-head.has-cover .lp-intro,
.lp-head.has-cover .lp-count { opacity: 0.92; }

/* ---- Programme: a row per event rather than a card ------------------- */
.lp-rows { display: flex; flex-direction: column; gap: 10px; }

/* The same card, laid on its side. Reusing it rather than writing a row
   component keeps one card to maintain — the alternative is two, and the
   second one is always the one that misses a badge. */
.lp-rows .event-card {
  display: grid;
  grid-template-columns: 148px minmax(0, 1fr);
  align-items: stretch;
}

.lp-rows .event-card img { height: 100%; min-height: 104px; object-fit: cover; }

@media (max-width: 640px) {
  .lp-rows .event-card { grid-template-columns: 1fr; }
  .lp-rows .event-card img { min-height: 150px; }
}

/* ---- Next up: the nearest event, larger ------------------------------- */
.lp-lead { margin-bottom: 22px; }

.lp-lead .event-card {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: stretch;
}

.lp-lead .event-card img { height: 100%; min-height: 240px; object-fit: cover; }

@media (max-width: 760px) {
  .lp-lead .event-card { grid-template-columns: 1fr; }
  .lp-lead .event-card img { min-height: 180px; }
}

@media (max-width: 640px) {
  .lp-head.has-cover > .container { padding-top: 92px; padding-bottom: 26px; }
}


/* ---------------------------------------------------------------------
   A blog article, and the space beside it
   ---------------------------------------------------------------------
   .prose is capped at 72ch because that is the measure prose is read
   at, and widening it to fill a 1400px screen would make the article
   harder to read, not easier. The complaint was never really about the
   measure: it was that the column sat against the left edge with half
   the page empty beside it.

   So the space gets used. On a wide screen the article and a rail of
   its own metadata sit side by side; below that the rail drops under
   the article and everything is one column. */
.post-layout {
    display: grid;
    gap: 40px;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
}

.post-body { min-width: 0; }

.post-rail-inner {
    border-left: 3px solid var(--border);
    padding-left: 18px;
}

.post-rail p { margin-bottom: 4px; }
.post-rail p + .small.strong { margin-top: 16px; }

/* The standfirst, when somebody wrote one of their own. */
.post-stand {
    font-size: 1.12rem;
    line-height: 1.6;
    color: var(--muted-fg);
    border-left: 3px solid var(--primary);
    padding-left: 16px;
    margin-bottom: 28px;
    max-width: 72ch;
}

@media (min-width: 1000px) {
    .post-layout { grid-template-columns: minmax(0, 72ch) minmax(200px, 1fr); }

    /* Sticky so it stays with the reader down a long article, and
       bounded so a tall rail can still scroll on a short screen. */
    .post-rail-inner {
        position: sticky;
        top: 96px;
        max-height: calc(100vh - 120px);
        overflow-y: auto;
    }
}


/* ---------------------------------------------------------------------
   The blog editor
   --------------------------------------------------------------------- */
.editor-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 8px;
    border: 1px solid var(--border);
    border-bottom: 0;
    border-radius: var(--radius) var(--radius) 0 0;
    background: var(--bg-alt);
}

.editor-sep {
    width: 1px;
    align-self: stretch;
    background: var(--border);
    margin: 2px 4px;
}

/* Not monospace. The old field was, which quietly told everybody they
   were writing code rather than prose — and then collapsed their
   paragraphs when they wrote prose anyway. */
.editor-area {
    border-radius: 0 0 var(--radius) var(--radius);
    font-size: 1rem;
    line-height: 1.7;
    min-height: 420px;
}

.editor-preview {
    border: 1px solid var(--border);
    border-top: 0;
    border-radius: 0 0 var(--radius) var(--radius);
    padding: 20px 22px;
    min-height: 420px;
    max-width: none;
}

/* ---- the attendee's own links --------------------------------------
   One ticket opens several things, and until now each of them lived at
   an address somebody had to have been sent. This is the map: a card
   per destination, big enough to hit with a thumb, with a line saying
   what is behind it — because "Documents" tells nobody anything.       */
.plinks {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  margin: 0 0 18px;
}
.plink {
  display: flex; gap: 10px; align-items: flex-start;
  border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 14px; background: var(--card);
  color: inherit; text-decoration: none;
  transition: border-color .12s ease, transform .12s ease;
}
.plink:hover {
  border-color: var(--primary); text-decoration: none; transform: translateY(-1px);
}
.plink:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.plink .ic { flex: 0 0 auto; margin-top: 2px; color: var(--primary-fg); }
.plink b { display: block; font-size: 0.94rem; line-height: 1.25; }
.plink span { display: block; font-size: 0.76rem; color: var(--muted-fg); margin-top: 2px; }

@media (max-width: 480px) {
  .plinks { grid-template-columns: 1fr; }
}

/* ---- the attendee's pass ---------------------------------------------
   The identity card was a bordered box with a name in it, which is what
   a form field looks like. This is the one place on the page that gets
   to be bold: their own face, their name, the class of ticket, and the
   code — the things a printed pass would carry, in that order.         */
.ppass {
  background:
    radial-gradient(120% 140% at 100% 0%, color-mix(in srgb, var(--primary) 14%, transparent), transparent 60%),
    var(--card);
  border-color: color-mix(in srgb, var(--primary) 26%, var(--border));
}
.ppass-top { display: flex; gap: 14px; align-items: center; }
.ppass-face {
  flex: 0 0 auto; width: 56px; height: 56px; border-radius: 50%;
  object-fit: cover; background: var(--muted);
  border: 2px solid color-mix(in srgb, var(--primary) 35%, var(--border));
}
.ppass-initial {
  display: grid; place-items: center;
  font-size: 1.35rem; font-weight: 700; color: var(--primary-fg);
}
.ppass .who { font-size: 1.18rem; line-height: 1.2; }
.ppass-kind {
  display: inline-block; margin-top: 6px; padding: 2px 10px;
  border-radius: 999px; font-size: 0.72rem; font-weight: 600;
  letter-spacing: .03em; text-transform: uppercase;
  background: color-mix(in srgb, var(--primary) 15%, transparent);
  color: var(--primary-fg);
}
.ppass-code {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--border);
}
.ppass-code span { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted-fg); }
.ppass-code code {
  font-size: 1.05rem; font-weight: 600; letter-spacing: .12em;
  user-select: all; font-variant-numeric: tabular-nums;
}

/* ---- a link that is not ready yet ------------------------------------
   Deliberately quiet and deliberately present. Greyed so it does not
   compete, kept so nobody concludes their certificate was lost.        */
.plink.is-waiting {
  background: var(--muted); border-style: dashed; cursor: default;
}
.plink.is-waiting:hover { border-color: var(--border); transform: none; }
.plink.is-waiting .ic { color: var(--muted-fg); }
.plink.is-waiting b { color: var(--muted-fg); font-weight: 600; }
.plink.is-waiting b em {
  font-style: normal; font-size: 0.68rem; text-transform: uppercase;
  letter-spacing: .05em; margin-left: 6px; padding: 1px 7px;
  border-radius: 999px; background: var(--border); color: var(--muted-fg);
}

/* ---- fifty-six sessions, read on a phone -----------------------------
   At this length the day heading scrolls away in the first flick and
   every card after it looks the same as the last. The heading sticks,
   and each slot grows a rail so the eye has a spine to run down.       */
.pday > h2 {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg); padding-top: 10px;
}
.pslot { position: relative; padding-left: 16px; }
.pslot::before {
  content: ""; position: absolute; left: 3px; top: 6px; bottom: 14px;
  width: 2px; border-radius: 2px;
  background: linear-gradient(var(--border), transparent);
}
.pslot > h3 { position: relative; }
.pslot > h3::before {
  content: ""; position: absolute; left: -16px; top: 4px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--primary); box-shadow: 0 0 0 3px var(--bg);
}
.psess { transition: border-color .12s ease, box-shadow .12s ease; }
.psess:hover { border-color: color-mix(in srgb, var(--primary) 40%, var(--border)); }

@media (max-width: 560px) {
  .ppass-face { width: 48px; height: 48px; }
  .ppass .who { font-size: 1.06rem; }
}

/* ---- one day at a time -----------------------------------------------
   A three-day programme on a phone is a minute of flicking to answer
   "what is on after lunch". The strip cuts it to a day, and the count
   on each tab means nobody taps into an empty one to find out.        */
.pdays {
  display: flex; gap: 8px; flex-wrap: wrap;
  margin: 0 0 20px; padding-bottom: 2px;
}
.pdaytab {
  display: flex; align-items: baseline; gap: 8px;
  padding: 8px 14px; border: 1px solid var(--border); border-radius: 999px;
  color: var(--muted-fg); text-decoration: none; font-size: 0.86rem;
  transition: border-color .12s ease, color .12s ease, background .12s ease;
}
.pdaytab b { font-weight: 600; }
.pdaytab span {
  font-size: 0.72rem; font-variant-numeric: tabular-nums;
  padding: 1px 7px; border-radius: 999px; background: var(--muted);
}
.pdaytab:hover { color: var(--fg); border-color: var(--fg); text-decoration: none; }
.pdaytab.on {
  background: var(--primary); border-color: var(--primary); color: #fff;
}
.pdaytab.on span { background: rgba(255, 255, 255, .22); color: #fff; }
.pdaytab-mine { margin-left: auto; }
.pdaytab-mine.on { background: var(--success, #12a05e); border-color: var(--success, #12a05e); }

@media (max-width: 620px) {
  .pdays { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 8px; }
  .pdaytab { flex: 0 0 auto; }
  .pdaytab-mine { margin-left: 0; }
}

/* A long abstract is worth having and not worth six lines of every
   card. Folded by default; the summary row is the affordance.         */
.psess details.pdesc > summary {
  cursor: pointer; font-size: 0.82rem; color: var(--primary-fg);
  font-weight: 600; margin-top: 6px; list-style: none;
}
.psess details.pdesc > summary::-webkit-details-marker { display: none; }
.psess details.pdesc > summary::after { content: " \25BE"; }
.psess details.pdesc[open] > summary::after { content: " \25B4"; }
.psess details.pdesc p.desc { margin-top: 6px; }

/* ---- a timetable is a list, not a stack of cards ----------------------
   Day one of this event has fifty-six sessions across thirty-seven start
   times, many five minutes apart. At that granularity a card per session
   is the wrong component: what people do with it is scan for a time and
   a room. Rows collapse the same markup to one line each, and open into
   the full card in place.                                              */
.pviewbar {
  display: flex; align-items: center; gap: 8px;
  margin: -8px 0 18px; font-size: 0.8rem; color: var(--muted-fg);
}
.pviewbar > span { margin-right: auto; font-variant-numeric: tabular-nums; }
.pview {
  padding: 4px 12px; border: 1px solid var(--border); border-radius: 999px;
  color: var(--muted-fg); text-decoration: none;
}
.pview:hover { color: var(--fg); text-decoration: none; }
.pview.on { background: var(--fg); border-color: var(--fg); color: var(--bg); }

.pslot.rows { margin-bottom: 8px; }
.pslot.rows > h3 { margin-bottom: 4px; }

.psessrow { border-bottom: 1px solid var(--border); }
.psessrow > summary {
  display: flex; align-items: baseline; gap: 12px;
  padding: 9px 2px; cursor: pointer; list-style: none;
  font-size: 0.9rem;
}
.psessrow > summary::-webkit-details-marker { display: none; }
.psessrow > summary:hover { background: var(--muted); }
.psessrow .rt {
  flex: 0 0 auto; min-width: 3.4em; font-variant-numeric: tabular-nums;
  color: var(--muted-fg); font-size: 0.82rem;
}
.psessrow .rtitle {
  flex: 1 1 auto; min-width: 0; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.psessrow .rroom {
  flex: 0 0 auto; font-size: 0.76rem; color: var(--muted-fg);
  max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.psessrow .rmine { flex: 0 0 auto; color: var(--success, #12a05e); font-weight: 700; }
.psessrow.on > summary { background: color-mix(in srgb, var(--success, #12a05e) 8%, transparent); }
.psessrow[open] > summary { background: var(--muted); }
.psessrow[open] .rtitle { white-space: normal; }
/* The card inside a row has already been announced by the summary. */
.psessrow > .psess { border: 0; border-radius: 0; margin: 0 0 10px; padding: 4px 2px 12px; }
.psessrow > .psess.on { box-shadow: none; }
.psessrow > .psess h4 { display: none; }

@media (max-width: 560px) {
  .psessrow .rroom { display: none; }
}

/* ---- looking at a document without leaving the page ------------------
   Tapping "Receipt" used to put a PDF in the downloads folder and the
   attendee on a blank viewer with nowhere to go but Back — and on a
   phone, Back out of a PDF is a gamble. The document opens above the
   programme instead, with the keeping of it as a separate, deliberate
   button.                                                              */
.pdoc {
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--card); overflow: hidden; margin-bottom: 20px;
}
.pdoc-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 14px; border-bottom: 1px solid var(--border);
  background: var(--bg-alt);
}
.pdoc-bar b { margin-right: auto; font-size: 0.95rem; }
.pdoc-frame {
  display: block; width: 100%; height: min(72vh, 780px); border: 0;
  background: var(--muted);
}
.pdoc-frame p { padding: 20px; }

@media (max-width: 560px) {
  .pdoc-frame { height: 60vh; }
  .pdoc-bar b { flex: 1 0 100%; margin-bottom: 4px; }
}

/* The little mark that says "this one leaves the page". Quiet, because
   it is a reassurance and not a feature.                               */
.plink .pnew {
  font-style: normal; font-size: 0.8em; opacity: .5; margin-left: 4px;
  vertical-align: super;
}

/* ---- the live board --------------------------------------------------
   Read from the back of a room, or held in one hand while walking a
   building. Dark because it lives on a wall for eight hours, and large
   because the question it answers is asked from a distance.           */
.live-body { background: #0d0b12; color: #f4f2f7; margin: 0; }
.live-wrap { max-width: 1400px; margin: 0 auto; padding: 22px 24px 40px; }

.live-head {
  display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap;
  padding-bottom: 16px; border-bottom: 1px solid #262230; margin-bottom: 16px;
}
.live-head h1 { font-size: 1.7rem; margin: 0; line-height: 1.15; }
.live-head p { margin: 4px 0 0; color: #9d97ae; font-size: 0.9rem; }
.live-nums { display: flex; gap: 26px; margin-left: auto; text-align: right; }
.live-num b { display: block; font-size: 1.7rem; line-height: 1.1; font-variant-numeric: tabular-nums; }
.live-num span { font-size: 0.76rem; color: #9d97ae; }

/* The one thing that must never be ambiguous: is this screen alive.
   An empty board and a dead board look identical from ten feet away. */
.live-state::before {
  content: ""; display: inline-block; width: 7px; height: 7px;
  border-radius: 50%; margin-right: 6px; vertical-align: 1px;
}
.live-state.is-live::before { background: #12a05e; box-shadow: 0 0 0 0 rgba(18,160,94,.7); animation: livePulse 2s infinite; }
.live-state.is-wait::before { background: #d08700; }
@keyframes livePulse {
  70%  { box-shadow: 0 0 0 7px rgba(18, 160, 94, 0); }
  100% { box-shadow: 0 0 0 0 rgba(18, 160, 94, 0); }
}

.live-rooms { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.live-roomchip {
  display: flex; align-items: baseline; gap: 7px;
  background: #171420; border: 1px solid #262230; border-radius: 999px;
  padding: 6px 14px;
}
.live-roomchip b { font-size: 1rem; font-variant-numeric: tabular-nums; }
.live-roomchip span { font-size: 0.78rem; color: #9d97ae; }

.live-list { display: flex; flex-direction: column; }
.live-row {
  display: flex; align-items: center; gap: 16px;
  padding: 12px 6px; border-bottom: 1px solid #1d1926;
}
.live-row.is-new { animation: liveIn .55s ease; }
@keyframes liveIn {
  from { opacity: 0; transform: translateY(-8px); background: rgba(18,160,94,.14); }
  to   { opacity: 1; transform: none; background: transparent; }
}
.live-row.is-refused { opacity: .55; }
.live-row.is-refused .live-where b { color: #e07a6a; }

.live-face {
  flex: 0 0 auto; width: 46px; height: 46px; border-radius: 50%;
  background: #221d2e; display: grid; place-items: center; overflow: hidden;
  font-weight: 700; font-size: 1.1rem; color: #b9b2cc;
}
.live-face img { width: 100%; height: 100%; object-fit: cover; }

.live-who { flex: 1 1 auto; min-width: 0; }
.live-who b { display: block; font-size: 1.12rem; line-height: 1.2;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.live-who span { display: block; font-size: 0.82rem; color: #9d97ae;
                 overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.live-where { flex: 0 0 auto; text-align: right; max-width: 38%; }
.live-where b { display: block; font-size: 0.95rem; font-weight: 600;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.live-where span { font-size: 0.78rem; color: #9d97ae; font-variant-numeric: tabular-nums; }

.live-empty { padding: 60px 20px; text-align: center; color: #9d97ae; }
.live-empty p { margin: 0 0 6px; font-size: 1.05rem; }
.live-empty .small { font-size: 0.85rem; }

.live-foot { margin-top: 28px; display: flex; gap: 18px; font-size: 0.82rem; }
.live-foot a { color: #9d97ae; }

@media (max-width: 700px) {
  .live-wrap { padding: 16px 14px 30px; }
  .live-head h1 { font-size: 1.25rem; }
  .live-nums { margin-left: 0; gap: 18px; }
  .live-num b { font-size: 1.3rem; }
  .live-face { width: 38px; height: 38px; font-size: 0.95rem; }
  .live-who b { font-size: 1rem; }
  .live-where { max-width: 32%; }
}


/* ---- home hero: the product, working -----------------------------------
   Three things have stood in this column and all three were drawings
   ABOUT the software rather than the software: flyer JPGs globbed from
   real organisers' work, abstract SVG cards, and a ticket-to-tick
   diagram. Each read as filler, because each was.

   This is the two screens Evenz is actually used on at the same moment —
   an organiser watching sales on a laptop while somebody on the door
   checks people in on a phone. One seventeen-second clock runs both, so
   the scan that lands on the phone is the scan that moves the number on
   the laptop. That coincidence is the whole pitch, and it only reads if
   the two are driven by the same timeline.

   A light UI on a dark hero, because that is what the product looks like
   and the contrast is what makes it read as a screen rather than a
   decoration.                                                            */
.hero-ui {
  --run: 17s;                      /* one pass through all three screens */
  position: relative;
  padding: 6px 0 46px 42px;        /* room for the phone to overlap */
  perspective: 1400px;
}

/* --- the laptop ------------------------------------------------------ */
.hui-window {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: 0 34px 70px rgba(0,0,0,.45), 0 6px 18px rgba(0,0,0,.28);
  transform: rotateY(-9deg) rotateX(3deg);
  transform-origin: 100% 50%;
  animation: huiSettle 1s .15s cubic-bezier(.2,.8,.3,1) backwards;
}
@keyframes huiSettle {
  from { opacity: 0; transform: rotateY(-16deg) rotateX(6deg) translateY(14px); }
}

.hui-chrome {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 12px;
  background: #f1f1f4;
  border-bottom: 1px solid #e3e3e8;
}
.hui-chrome i {
  width: 9px; height: 9px; border-radius: 50%;
  background: #d5d5dc;
}
.hui-url {
  margin-left: 8px;
  flex: 1;
  font-size: 10px;
  color: #8a8a95;
  background: #fff;
  border-radius: 20px;
  padding: 3px 10px;
  border: 1px solid #e3e3e8;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* --- the three screens ------------------------------------------------ */
.hui-screens {
  position: relative;
  height: 254px;
  color: #1f1629;
}
.hui-screen {
  position: absolute; inset: 0;
  padding: 15px 16px;
  display: flex; flex-direction: column; gap: 11px;
  opacity: 0;
  animation: huiScreen var(--run) ease-in-out infinite;
}
/* Each screen owns a third of the run; the delays are what separate them. */
.hui-s2 { animation-delay: calc(var(--run) / -3 * 2); }
.hui-s3 { animation-delay: calc(var(--run) / -3); }

@keyframes huiScreen {
  0%       { opacity: 0; transform: translateY(8px); }
  3%, 30%  { opacity: 1; transform: none; }
  33%,100% { opacity: 0; transform: translateY(-8px); }
}

.hui-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.hui-head b { font-size: 13px; }
.hui-live {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10px; color: #12a05e;
  background: rgba(18,160,94,.1);
  border-radius: 20px; padding: 2px 8px;
}
.hui-live i {
  width: 6px; height: 6px; border-radius: 50%; background: #12a05e;
  animation: huiPulse 2s ease-in-out infinite;
}
@keyframes huiPulse { 50% { opacity: .3; } }

/* screen 1 — the money */
.hui-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.hui-tile {
  border: 1px solid #ececf1; border-radius: 9px; padding: 9px 10px;
  display: grid; gap: 3px;
}
.hui-tile span { font-size: 9px; color: #8a8a95; }
.hui-tile b    { font-size: 15px; font-variant-numeric: tabular-nums; }
.hui-tile-ok   { border-color: rgba(18,160,94,.32); background: rgba(18,160,94,.05); }
.hui-tile-ok b { color: #0f7a48; }

/* The one number that moves, and it moves when the phone below scores a
   hit — same clock, so it reads as cause and effect rather than two
   things happening near each other. */
.hui-count { animation: huiTick var(--run) steps(1, end) infinite; }
@keyframes huiTick {
  0%, 72%  { transform: none; }
  74%      { transform: translateY(-2px); color: #12a05e; }
  77%,100% { transform: none; }
}

.hui-chart {
  flex: 1;
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 7px; align-items: end;
  padding-top: 4px;
}
.hui-chart i {
  display: block; height: var(--h);
  background: linear-gradient(180deg, var(--primary, #c92737), #e0505e);
  border-radius: 4px 4px 2px 2px;
  transform-origin: 50% 100%;
  animation: huiGrow var(--run) ease-out infinite;
}
.hui-chart i:nth-child(1) { animation-delay: .02s; }
.hui-chart i:nth-child(2) { animation-delay: .06s; }
.hui-chart i:nth-child(3) { animation-delay: .10s; }
.hui-chart i:nth-child(4) { animation-delay: .14s; }
.hui-chart i:nth-child(5) { animation-delay: .18s; }
.hui-chart i:nth-child(6) { animation-delay: .22s; }
.hui-chart i:nth-child(7) { animation-delay: .26s; }
@keyframes huiGrow {
  0%      { transform: scaleY(.05); }
  8%, 33% { transform: scaleY(1); }
  100%    { transform: scaleY(1); }
}

/* screen 2 — what a buyer sees */
.hui-tier {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1px solid #ececf1; border-radius: 9px; padding: 9px 11px;
}
.hui-tier span { display: grid; gap: 1px; }
.hui-tier b    { font-size: 11.5px; }
.hui-tier em   { font-style: normal; font-size: 9px; color: #8a8a95; }
.hui-price     { font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
.hui-muted     { color: #b6b6bf; text-decoration: line-through; }
.hui-tier-on {
  border-color: var(--primary, #c92737);
  box-shadow: 0 0 0 2px rgba(201,39,55,.12);
}
.hui-cta {
  margin-top: auto;
  text-align: center;
  background: var(--primary, #c92737); color: #fff;
  border-radius: 8px; padding: 9px;
  font-size: 11.5px; font-weight: 600;
}

/* screen 3 — the door, from the organiser's side */
.hui-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 7px 2px;
  border-bottom: 1px solid #f2f2f6;
  font-size: 11.5px;
}
.hui-row em { font-style: normal; font-size: 9.5px; color: #8a8a95; }
.hui-row em.ok {
  color: #0f7a48; background: rgba(18,160,94,.1);
  border-radius: 20px; padding: 2px 8px;
}
/* The row that arrives while you are watching — the same person the
   phone is scanning. */
.hui-row-new { animation: huiRow var(--run) ease-out infinite; }
@keyframes huiRow {
  0%, 71%   { opacity: 0; transform: translateX(-10px); background: rgba(18,160,94,.18); }
  74%, 92%  { opacity: 1; transform: none; background: rgba(18,160,94,.18); }
  100%      { opacity: 1; transform: none; background: transparent; }
}

/* --- the phone, in front --------------------------------------------- */
.hui-phone {
  position: absolute; left: 0; bottom: 0;
  width: 118px;
  border-radius: 18px;
  padding: 5px;
  background: #15121c;
  border: 1px solid rgba(255,255,255,.16);
  box-shadow: 0 22px 44px rgba(0,0,0,.5);
  transform: rotate(-4deg);
  animation: huiPhoneIn 1s .45s cubic-bezier(.2,.8,.3,1) backwards;
}
@keyframes huiPhoneIn {
  from { opacity: 0; transform: rotate(-4deg) translateY(20px); }
}
.hui-phone-screen {
  border-radius: 14px; overflow: hidden;
  background: #0b0b12;
  display: grid;
  gap: 0;
}
.hui-cam {
  position: relative;
  aspect-ratio: 3 / 4;
  /* Stands in for a camera feed: a dim room, not a photograph of one. */
  background:
    radial-gradient(120% 90% at 50% 20%, rgba(255,255,255,.10), transparent 60%),
    linear-gradient(160deg, #232030, #14121b);
}
.hui-reticle { position: absolute; inset: 18%; }
.hui-reticle i {
  position: absolute; width: 13px; height: 13px;
  border: 2px solid rgba(255,255,255,.85);
}
.hui-reticle i:nth-child(1) { top:0; left:0;  border-right:0; border-bottom:0; border-radius:5px 0 0 0; }
.hui-reticle i:nth-child(2) { top:0; right:0; border-left:0;  border-bottom:0; border-radius:0 5px 0 0; }
.hui-reticle i:nth-child(3) { bottom:0; left:0;  border-right:0; border-top:0; border-radius:0 0 0 5px; }
.hui-reticle i:nth-child(4) { bottom:0; right:0; border-left:0;  border-top:0; border-radius:0 0 5px 0; }

/* One pass, timed to land just before the laptop's number moves. */
.hui-beam {
  position: absolute; left: 8%; right: 8%; height: 2px;
  background: linear-gradient(90deg, transparent, #12a05e, transparent);
  box-shadow: 0 0 14px 2px rgba(18,160,94,.55);
  opacity: 0;
  animation: huiBeam var(--run) linear infinite;
}
/* Timed against the laptop, not against itself.
   With a 17s run the attendees screen is up from 69.6% to 96.6%, so the
   sweep has to finish just before that or the scan appears to happen
   while somebody is still choosing a ticket — which is what the first
   version did, measured at 9.5s with the buying screen at full opacity. */
@keyframes huiBeam {
  0%, 61%  { opacity: 0; top: 18%; }
  62%      { opacity: 1; top: 18%; }
  67%      { opacity: 1; top: 78%; }
  68%,100% { opacity: 0; top: 78%; }
}

/* The result slides up over the feed, the way the real scanner answers. */
.hui-hit {
  display: flex; align-items: center; gap: 7px;
  padding: 8px 9px;
  background: #12a05e; color: #fff;
  opacity: 0;
  animation: huiHit var(--run) cubic-bezier(.2,.9,.3,1) infinite;
}
.hui-hit span { display: grid; gap: 1px; font-size: 9.5px; font-weight: 700; line-height: 1.2; }
.hui-hit em   { font-style: normal; font-weight: 400; opacity: .85; font-size: 8.5px; }
@keyframes huiHit {
  0%, 68%   { opacity: 0; transform: translateY(100%); }
  71%, 96%  { opacity: 1; transform: none; }
  100%      { opacity: 0; transform: translateY(100%); }
}

.hero-loop-said {
  margin: 18px 0 0;
  font-size: .84rem;
  color: rgba(255,255,255,.6);
  max-width: 46ch;
}

/* --- narrower ---------------------------------------------------------- */
@media (max-width: 900px) {
  /* The hero stops being two columns here. This rule lived in the block
     that was replaced when this section was rewritten, and losing it left
     the grid two-up at 375px: the copy took 151px, the art 136px, and the
     "laptop" rendered 112px wide — narrower than the phone standing in
     front of it. */
  .hero-grid { grid-template-columns: 1fr; gap: 30px; }

  .hero-ui { padding-left: 34px; padding-bottom: 40px; max-width: 470px; }

  /* The animation has to go with the transform, not just the transform.
     An animation's value beats a normal declaration however specific the
     declaration is, so `transform: none` here was silently losing to
     huiSettle's own `from` state — decoded from the computed matrix as
     rotateY(-16deg), which is why the laptop still sat 17px in from the
     copy it is supposed to line up with. */
  .hui-window { transform: none; animation: none; opacity: 1; }
  .hui-phone  { animation: none; opacity: 1; }
  .hui-screens { height: 232px; }
}

@media (max-width: 640px) {
  .hero .container { padding-top: 46px; padding-bottom: 46px; }

  /* Flush with the copy above it.
     The left padding here was reserving room for the phone to hang off
     the side, which is the right composition beside a column of text and
     the wrong one under it: measured at 375px the laptop started 40px in
     while every line of copy started at 20px, and the phone's rotated
     corner finished 5px OUTSIDE the container. Nothing is inset now, and
     the phone overlaps the laptop's own corner instead of the page's. */
  .hero-ui       { padding: 4px 0 30px; }
  .hui-screens   { height: 206px; }
  .hui-screen    { padding: 12px 13px; gap: 9px; }
  .hui-tile b    { font-size: 13px; }
  .hui-tile span { font-size: 8.5px; }
  .hui-phone     { width: 88px; left: 8px; bottom: 0; }
  .hero-loop-said{ margin-top: 12px; font-size: .78rem; }
}

/* Somebody who asked for less movement gets the dashboard, still, with
   the phone showing a completed scan. The story without the motion. */
@media (prefers-reduced-motion: reduce) {
  .hui-window, .hui-phone, .hui-screen, .hui-chart i,
  .hui-count, .hui-row-new, .hui-beam, .hui-hit, .hui-live i { animation: none; }

  .hui-window { transform: none; opacity: 1; }
  .hui-phone  { opacity: 1; transform: rotate(-4deg); }
  .hui-screen { opacity: 0; }
  .hui-s1     { opacity: 1; transform: none; }
  .hui-chart i{ transform: scaleY(1); }
  .hui-beam   { opacity: 0; }
  .hui-hit    { opacity: 1; transform: none; }
}

/* ---- showcase: three flyers off one template ---------------------------
   The claim in the copy beside this is "you design the template, each
   attendee fills it in", so the stack shows the SAME layout three times
   with different names and photographs. Three different designs would
   illustrate a template picker; this illustrates the product.

   The section lost its art when the old cards were removed, leaving the
   copy in a column and half a dark band doing nothing.                   */
.showcase-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, .92fr);
  gap: 52px;
  align-items: center;
}
.showcase-copy { max-width: 56ch; }
.showcase-art  { min-width: 0; }

.fstack {
  --fw: 190px;                      /* one flyer's width */
  position: relative;
  height: 340px;
  perspective: 1200px;
}

.fcard {
  position: absolute;
  top: 50%; left: 50%;
  width: var(--fw);
  aspect-ratio: 4 / 5;
  margin: calc(var(--fw) * -0.625) 0 0 calc(var(--fw) / -2);
  border-radius: 12px;
  padding: 14px 13px 12px;
  display: flex; flex-direction: column;
  background: linear-gradient(165deg, #2a2036 0%, #1a1523 70%);
  border: 1px solid rgba(255,255,255,.13);
  box-shadow: 0 24px 48px rgba(0,0,0,.45);
  transform-origin: 50% 100%;
  animation: fstackIn .8s cubic-bezier(.2,.8,.3,1) backwards;
}

/* Fanned, back to front. The one in front is upright and the two behind
   lean away, which is how a stack of prints actually sits. */
.fcard-1 { transform: translate3d(-58px, 14px, -90px) rotate(-9deg); opacity: .82; animation-delay: .05s; }
.fcard-2 { transform: translate3d( 58px, 22px, -60px) rotate( 8deg); opacity: .88; animation-delay: .12s; }
.fcard-3 { transform: translate3d(  0,   0,     0)    rotate(-1deg); animation-delay: .2s; z-index: 2; }

@keyframes fstackIn { from { opacity: 0; transform: translate3d(0, 26px, -120px) rotate(0deg); } }

/* Hover fans them apart, which is the one gesture somebody looking at a
   stack of three actually wants. Nothing moves on its own: this sits
   halfway down a marketing page, and motion in the corner of the eye
   while somebody is reading is a distraction rather than an invitation. */
.showcase-art:hover .fcard-1 { transform: translate3d(-96px, 10px, -60px) rotate(-13deg); opacity: 1; }
.showcase-art:hover .fcard-2 { transform: translate3d( 96px, 16px, -40px) rotate( 12deg); opacity: 1; }
.showcase-art:hover .fcard-3 { transform: translate3d(  0,  -10px,  20px) rotate(  0deg); }
.fcard { transition: transform .55s cubic-bezier(.22,.9,.3,1), opacity .4s ease; }

.fcard-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 6px;
  font-size: 7.5px; letter-spacing: .07em; text-transform: uppercase;
}
.fcard-brand { color: #fff; font-weight: 700; }
.fcard-when  { color: rgba(255,255,255,.5); white-space: nowrap; }

/* Where the attendee's photograph goes. Initials rather than a stock
   face: inventing a person for a marketing page is how the last version
   of this section ended up showing a real one. */
.fcard-photo {
  margin: 12px auto 11px;
  width: 62%; aspect-ratio: 1;
  border-radius: 50%;
  display: grid; place-items: center;
  background: linear-gradient(150deg, rgba(255,255,255,.16), rgba(255,255,255,.05));
  border: 2px solid rgba(255,255,255,.22);
  color: rgba(255,255,255,.85);
  font-size: 20px; font-weight: 700; letter-spacing: .04em;
}

.fcard-name {
  text-align: center; color: #fff;
  font-size: 12.5px; font-weight: 700; line-height: 1.15;
}
.fcard-role {
  text-align: center; margin-top: 2px;
  font-size: 8.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--primary, #c92737);
  filter: brightness(1.5);
}

.fcard-foot {
  margin-top: auto;
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding-top: 10px;
  border-top: 1px solid rgba(255,255,255,.1);
  font-size: 8px; color: rgba(255,255,255,.55);
}
.fcard-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--primary, #c92737);
}

@media (max-width: 900px) {
  .showcase-grid { grid-template-columns: 1fr; gap: 34px; }
  .showcase-copy { max-width: none; }
  .fstack { height: 300px; --fw: 168px; }
}

@media (max-width: 640px) {
  .fstack { height: 250px; --fw: 140px; }
  .fcard  { padding: 11px 10px 9px; border-radius: 10px; }
  .fcard-photo { font-size: 15px; margin: 9px auto 8px; }
  .fcard-name  { font-size: 10.5px; }
  /* Tighter fan: at 375px the wide one pushed the outer flyers past the
     edge of the screen. */
  .fcard-1 { transform: translate3d(-40px, 10px, -70px) rotate(-8deg); }
  .fcard-2 { transform: translate3d( 40px, 16px, -50px) rotate( 7deg); }
}

@media (prefers-reduced-motion: reduce) {
  .fcard { animation: none; transition: none; }
}

/* ---- site popups --------------------------------------------------------
   Five layouts, one set of parts. Each is a real shape rather than a skin
   on the same box: a top bar and a centre modal interrupt somebody to
   completely different degrees, and choosing between them is the main
   decision the person setting one up is making.

   Everything animates from a transform and an opacity, never from height
   or top, so nothing on the page moves when one appears.               */
.pop {
  position: fixed;
  z-index: 9000;
  color: #fff;
}
.pop[hidden] { display: none; }

.pop-veil {
  position: fixed; inset: 0;
  background: rgba(10, 8, 16, .55);
  opacity: 0;
  transition: opacity .25s ease;
}
.pop.is-open .pop-veil { opacity: 1; }

.pop-box {
  position: relative;
  display: flex;
  align-items: center;
  gap: 16px;
  background: var(--secondary, #1f1629);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .4);
  transition: transform .3s cubic-bezier(.2, .8, .3, 1), opacity .25s ease;
}

.pop-img { flex: 0 0 auto; }
.pop-img img {
  display: block; width: 84px; height: 84px;
  object-fit: cover; border-radius: 10px;
}

.pop-text  { flex: 1 1 auto; min-width: 0; }
.pop-head  { margin: 0 0 4px; font-size: 1.02rem; font-weight: 700; line-height: 1.25; }
.pop-body  { margin: 0; font-size: .88rem; line-height: 1.5; opacity: .85; }

.pop-acts {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
}

.pop-cta {
  display: inline-block;
  padding: 9px 16px;
  border-radius: 8px;
  background: #fff;
  color: var(--secondary, #1f1629);
  font-size: .86rem; font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.pop-cta:hover { opacity: .9; color: var(--secondary, #1f1629); }

/* A real button, sized so a thumb can hit it. */
.pop-x {
  min-width: 34px; height: 34px;
  padding: 0 10px;
  border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font-size: 1.05rem; line-height: 1;
  cursor: pointer;
}
.pop-x:hover  { background: rgba(255, 255, 255, .12); }
.pop-x:focus-visible,
.pop-cta:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* --- themes ----------------------------------------------------------- */
.pop-brand .pop-box { background: var(--primary, #c92737); }
.pop-brand .pop-cta { color: var(--primary, #c92737); }
.pop-dark  .pop-box { background: #14121b; }
.pop-dark  .pop-cta { color: #14121b; }

.pop-light .pop-box {
  background: #fff;
  color: var(--fg, #101418);
  border: 1px solid var(--border, #e6eaef);
}
.pop-light .pop-cta {
  background: var(--primary, #c92737);
  color: #fff;
}
.pop-light .pop-x { border-color: var(--border, #e6eaef); }

/* --- 1 · centre modal -------------------------------------------------- */
.pop-modal { inset: 0; display: grid; place-items: center; padding: 20px; }
.pop-modal .pop-box {
  flex-direction: column;
  align-items: flex-start;
  width: min(440px, 100%);
  padding: 26px;
  border-radius: 16px;
  transform: translateY(14px) scale(.97);
  opacity: 0;
}
.pop-modal.is-open .pop-box { transform: none; opacity: 1; }
.pop-modal .pop-img img { width: 100%; height: 150px; }
.pop-modal .pop-acts    { width: 100%; justify-content: flex-start; margin-top: 4px; }

/* --- 2 · corner card --------------------------------------------------- */
.pop-corner { right: 18px; bottom: 18px; max-width: min(370px, calc(100vw - 36px)); }
.pop-corner .pop-box {
  flex-direction: column;
  align-items: flex-start;
  padding: 18px;
  border-radius: 14px;
  transform: translateY(16px);
  opacity: 0;
}
.pop-corner.is-open .pop-box { transform: none; opacity: 1; }
.pop-corner .pop-img img { width: 100%; height: 120px; }
.pop-corner .pop-acts    { margin-top: 4px; }

/* --- 3 & 4 · bars ------------------------------------------------------ */
.pop-bar, .pop-foot { left: 0; right: 0; }
.pop-bar  { top: 0; }
.pop-foot { bottom: 0; }

.pop-bar .pop-box, .pop-foot .pop-box {
  padding: 12px 18px;
  border-radius: 0;
  opacity: 0;
}
.pop-bar  .pop-box { transform: translateY(-100%); }
.pop-foot .pop-box { transform: translateY(100%); }
.pop-bar.is-open  .pop-box,
.pop-foot.is-open .pop-box { transform: none; opacity: 1; }

.pop-bar .pop-head, .pop-foot .pop-head { font-size: .92rem; margin-bottom: 1px; }
.pop-bar .pop-body, .pop-foot .pop-body { font-size: .82rem; }
.pop-bar .pop-img img, .pop-foot .pop-img img { width: 44px; height: 44px; }

/* --- 5 · bottom sheet -------------------------------------------------- */
.pop-sheet { inset: 0; display: flex; align-items: flex-end; }
.pop-sheet .pop-box {
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  padding: 22px 20px calc(22px + env(safe-area-inset-bottom, 0px));
  border-radius: 18px 18px 0 0;
  transform: translateY(100%);
  opacity: 1;
}
.pop-sheet.is-open .pop-box { transform: none; }
.pop-sheet .pop-img img { width: 100%; height: 140px; }
.pop-sheet .pop-acts    { width: 100%; margin-top: 4px; }

/* --- phones ------------------------------------------------------------ */
@media (max-width: 560px) {
  /* A bar with a headline, body, button and close on one row becomes four
     columns of about forty pixels each. It stacks instead. */
  .pop-bar .pop-box, .pop-foot .pop-box {
    flex-wrap: wrap;
    gap: 10px;
    padding: 12px 14px;
  }
  .pop-bar .pop-text, .pop-foot .pop-text { flex: 1 1 100%; }
  .pop-bar .pop-acts, .pop-foot .pop-acts { flex: 1 1 100%; }
  .pop-bar .pop-cta,  .pop-foot .pop-cta  { flex: 1 1 auto; text-align: center; }

  .pop-corner { left: 12px; right: 12px; bottom: 12px; max-width: none; }
  .pop-modal  { padding: 14px; }
  .pop-modal .pop-box { padding: 20px; }
}

/* Somebody who asked for less movement still gets the popup — it simply
   arrives rather than travels. Removing it entirely would hide a notice
   they are entitled to read. */
.pop-calm .pop-box,
.pop-calm .pop-veil { transition: opacity .2s ease; }
.pop-calm .pop-box  { transform: none !important; }

/* The preview inside the admin editor. Same classes as the live popup, so
   what it shows is what a visitor gets — but contained in a box rather
   than fixed over the page, which is the one thing that has to differ. */
.pop-preview.pop {
  position: relative;
  inset: auto;
  display: block;
  z-index: 0;
  min-height: 90px;
  padding: 0;
  border-radius: 12px;
  overflow: hidden;
}
.pop-preview .pop-box { transform: none !important; opacity: 1 !important; }
.pop-preview.pop-modal, .pop-preview.pop-sheet { place-items: stretch; }

/* Nothing catches a click until it is actually open.
   Between `hidden` coming off and `is-open` going on there is a window,
   however small, where the box is painted at opacity 0 but still in the
   layout — and a corner card sits exactly where a page's own buttons
   often are. Belt and braces alongside the synchronous reflow in
   popup.js that closes that window. */
.pop:not(.is-open) .pop-box,
.pop:not(.is-open) .pop-veil { pointer-events: none; }
.pop.is-open .pop-box,
.pop.is-open .pop-veil { pointer-events: auto; }

/* ---- popups: glass ------------------------------------------------------
   A frosted panel rather than a painted one: the page stays faintly
   visible through it, which is what stops a popup feeling like a door
   slammed in somebody's face.

   Three things make it read as glass rather than as a translucent box —
   the blur behind it, a saturation lift so colours underneath stay alive
   instead of going grey, and a hairline top edge catching the light. The
   third is the one people leave out, and it is why theirs look flat.

   The colour underneath every panel is opaque enough on its own: a browser
   with no backdrop-filter (Firefox before 103, older Android WebViews)
   still gets a solid, readable card rather than transparent text over a
   busy page. That fallback is the reason the alpha values are as high as
   they are.                                                              */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .pop-brand .pop-box,
  .pop-dark  .pop-box,
  .pop-light .pop-box {
    -webkit-backdrop-filter: blur(18px) saturate(160%);
    backdrop-filter: blur(18px) saturate(160%);
  }

  /* Brand and dark keep their hue and lose their opacity. */
  .pop-brand .pop-box {
    background: color-mix(in srgb, var(--primary, #c92737) 78%, transparent);
    border: 1px solid rgba(255, 255, 255, .22);
  }
  .pop-dark .pop-box {
    background: rgba(20, 18, 27, .72);
    border: 1px solid rgba(255, 255, 255, .14);
  }
  .pop-light .pop-box {
    background: rgba(255, 255, 255, .74);
    border: 1px solid rgba(255, 255, 255, .6);
  }

  /* The lit edge. A one-pixel highlight along the top, brightest in the
     middle, is what a pane of glass actually does under a room light. */
  .pop-box::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 1px;
    background: linear-gradient(90deg,
      transparent,
      rgba(255, 255, 255, .55) 22%,
      rgba(255, 255, 255, .75) 50%,
      rgba(255, 255, 255, .55) 78%,
      transparent);
    pointer-events: none;
  }
  .pop-light .pop-box::before { background: linear-gradient(90deg,
      transparent, rgba(255, 255, 255, .95) 50%, transparent); }

  /* Bars span the window, so their highlight would run the whole width
     and read as a rule rather than a reflection. */
  .pop-bar .pop-box::before,
  .pop-foot .pop-box::before { opacity: .45; }

  /* The veil frosts too, so the modal sits on depth rather than on a
     flat wash. */
  .pop-modal .pop-veil,
  .pop-sheet .pop-veil {
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    background: rgba(10, 8, 16, .42);
  }

  /* The button on glass wants a little of the same treatment, or it
     reads as a sticker sitting on top of the panel. */
  .pop-x {
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    background: rgba(255, 255, 255, .1);
  }
  .pop-light .pop-x { background: rgba(0, 0, 0, .04); }
}

/* Glass is expensive to composite. Somebody who asked for less motion is
   often on a machine that would rather not, so they get the solid panel —
   which is the same design, minus the cost. */
@media (prefers-reduced-motion: reduce) {
  .pop-box { -webkit-backdrop-filter: none; backdrop-filter: none; }
  .pop-brand .pop-box { background: var(--primary, #c92737); }
  .pop-dark  .pop-box { background: #14121b; }
  .pop-light .pop-box { background: #fff; }
}


/* =====================================================================
   Ticket dock + picker  (public event page)
   =====================================================================
   A bar that follows the reader, and the panel it opens.

   The bar shows only once the Tickets card has scrolled off, which is
   the whole justification for it: while the real card is on screen a
   floating copy of its button is furniture sitting on the page.

   Both are hidden in the markup and revealed by script. With scripting
   off there is no bar and no panel, and the card's button stays what it
   always was -- a link to checkout.
   ===================================================================== */

.tdock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--card);
  border-top: 1px solid var(--border);
  /* Upward, because the thing it must not hide is above it. */
  box-shadow: 0 -6px 24px rgba(16, 20, 24, .10);
  animation: tdock-in .18s cubic-bezier(.2, .8, .3, 1);
}

@keyframes tdock-in {
  from { opacity: 0; transform: translateY(100%); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .tdock { animation: none; }
}

.tdock-inner {
  display: flex;
  align-items: center;
  gap: 16px;
  max-width: var(--container, 1120px);
  margin: 0 auto;
}

.tdock-price {
  margin: 0;
  min-width: 0;
  flex: 1 1 auto;
  line-height: 1.25;
}

.tdock-from {
  display: block;
  font-size: .74rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted-fg);
}

.tdock-price strong {
  font-size: 1.06rem;
  font-variant-numeric: tabular-nums;
}

/* The event name is the reassurance that this bar belongs to the page
   you are on, not the last one. It is the first thing to go when there
   is no room for it. */
.tdock-event {
  display: block;
  font-size: .78rem;
  color: var(--muted-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tdock-go { flex: 0 0 auto; min-height: 44px; }

/* Room to scroll past the last of the page rather than under the bar. */
body.has-tdock { padding-bottom: 84px; }

@media (max-width: 600px) {
  .tdock-event { display: none; }
  .tdock-go    { flex: 1 1 auto; text-align: center; }
  .tdock-inner { gap: 12px; }
}

/* ---------------------------------------------------------------------
   The picker
   --------------------------------------------------------------------- */

.tpick > h3 {
  margin: 0 0 4px;
  font-size: 1.02rem;
}

/* It is moved into .sheet-body, which supplies the padding and the
   scroll; inside the sheet its own heading would repeat the sheet's. */
.sheet-body > .tpick > h3 { display: none; }

.tpick-list {
  display: flex;
  flex-direction: column;
}

.tpick-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 14px;
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}

.tpick-row:last-child { border-bottom: 0; }

.tpick-main { min-width: 0; }

.tpick-name {
  display: block;
  font-weight: 650;
}

.tpick-note {
  display: block;
  margin-top: 2px;
  font-size: .82rem;
  color: var(--muted-fg);
}

.tpick-main .badge { margin-top: 4px; margin-right: 4px; }

.tpick-price {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.tpick-qty select { min-width: 4.5rem; }

.tpick-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.tpick-total {
  margin: 0;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.tpick-foot .btn { flex: 0 0 auto; min-height: 44px; }

/* A disabled Continue is the answer to "why is nothing happening" --
   it has to read as deliberately off, not as broken. */
.tpick-foot .btn:disabled {
  opacity: .55;
  cursor: not-allowed;
}

/* On a phone the sheet comes up from the bottom edge, because that is
   where the thumb and the bar that opened it both are. */
@media (max-width: 720px) {
  .tsheet {
    width: 100%;
    max-width: 100%;
    margin: auto auto 0;
    max-height: 86vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }

  @keyframes tsheet-up {
    from { opacity: 0; transform: translateY(100%); }
    to   { opacity: 1; transform: none; }
  }

  .tsheet[open] { animation: tsheet-up .2s cubic-bezier(.2, .8, .3, 1); }

  .tpick-row {
    grid-template-columns: 1fr auto;
    row-gap: 8px;
  }

  /* Price and quantity share the second line, so a long tier name gets
     the whole of the first instead of wrapping into three. */
  .tpick-main  { grid-column: 1 / -1; }
  .tpick-price { text-align: left; }
  .tpick-qty   { justify-self: end; }

  .tpick-foot {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }

  .tpick-foot .btn { width: 100%; }
  .tpick-total     { text-align: center; }
}


/* =====================================================================
   Media: an image, a video, or a pasted YouTube link
   =====================================================================
   One primitive, mounted by the popups and the banners. The rules that
   matter are about somebody else's connection, not about looks:

   - The aspect ratio is reserved before anything loads, so the page does
     not jump when it arrives. That reserve is the whole reason for the
     --media-ratio custom property.
   - A hosted player is a megabyte before it shows a frame, so the still
     is what ships and the iframe is built on request.
   - Nothing moves for somebody who asked for less motion.
   ===================================================================== */

.media {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--muted);
  aspect-ratio: var(--media-ratio, 16 / 9);
}

.media img,
.media video,
.media iframe {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
}

/* The play control covers the whole still: the target is the picture,
   which is what anyone actually aims at. */
.media-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  border: 0;
  cursor: pointer;
  color: #fff;
  /* Dark enough for white text at AA over any still, including a pale
     one, without hiding the picture underneath. */
  background: linear-gradient(180deg, rgba(10, 12, 16, .18), rgba(10, 12, 16, .52));
  transition: background .18s ease;
}

.media-play:hover,
.media-play:focus-visible { background: rgba(10, 12, 16, .58); }

.media-play-dot {
  flex: 0 0 auto;
  width: 62px;
  height: 62px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .16);
  border: 2px solid rgba(255, 255, 255, .9);
  backdrop-filter: blur(2px);
  position: relative;
}

/* A triangle in CSS rather than an asset: it is three lines, it scales,
   and it cannot 404. */
.media-play-dot::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 54%;
  transform: translate(-50%, -50%);
  border-style: solid;
  border-width: 11px 0 11px 18px;
  border-color: transparent transparent transparent #fff;
}

.media-play-text {
  font-weight: 650;
  font-size: .94rem;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .5);
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 600px) {
  .media-play-text { display: none; }
  .media-play-dot  { width: 54px; height: 54px; }
}

.media-play:hover .media-play-dot { background: rgba(255, 255, 255, .28); }

/* Once the player exists the control has done its job and must stop
   swallowing clicks meant for it. */
.media.is-playing .media-play { display: none; }

.media-fallback {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 10px 16px;
  border-radius: 999px;
  background: rgba(10, 12, 16, .72);
  color: #fff;
  font-weight: 600;
}

.media figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 22px 16px 12px;
  color: #fff;
  font-size: .86rem;
  background: linear-gradient(180deg, transparent, rgba(10, 12, 16, .74));
}

@media (prefers-reduced-motion: reduce) {
  .media-play { transition: none; }
}


/* =====================================================================
   Popup layouts
   =====================================================================
   The shape says where the popup sits; the layout says what it looks
   like inside. Everything here is additive -- a popup with no layout
   saved is `top`, which is exactly what popups looked like before.

   Only the box is restyled. The veil, the close button, the triggers and
   the counting are untouched.
   ===================================================================== */

/* ---- Picture beside the words -------------------------------------- */

.pop-l-left .pop-box,
.pop-l-right .pop-box {
  display: grid;
  grid-template-columns: minmax(0, 34%) minmax(0, 1fr);
  align-items: center;
  gap: 0;
}

.pop-l-left .pop-img,
.pop-l-right .pop-img { height: 100%; }

.pop-l-left .pop-img .media,
.pop-l-right .pop-img .media {
  height: 100%;
  border-radius: 0;
}

/* The text and the actions share the second column rather than each
   claiming a row of their own. */
.pop-l-left .pop-text,
.pop-l-right .pop-text { grid-column: 2; padding: 20px 20px 8px; }

.pop-l-left .pop-acts,
.pop-l-right .pop-acts { grid-column: 2; padding: 0 20px 20px; }

.pop-l-right .pop-img  { grid-column: 2; grid-row: 1 / span 2; }
.pop-l-right .pop-text { grid-column: 1; grid-row: 1; }
.pop-l-right .pop-acts { grid-column: 1; grid-row: 2; }

/* Beside becomes above on a narrow screen. Two columns inside a corner
   card on a phone is four words per line. */
@media (max-width: 620px) {
  .pop-l-left .pop-box,
  .pop-l-right .pop-box { display: block; }

  .pop-l-left .pop-img .media,
  .pop-l-right .pop-img .media { height: auto; aspect-ratio: 16 / 9; }
}

/* ---- Words over the picture ---------------------------------------- */

.pop-l-cover .pop-box,
.pop-l-reel .pop-box {
  position: relative;
  overflow: hidden;
}

.pop-l-cover .pop-img,
.pop-l-reel .pop-img { margin: 0; }

.pop-l-cover .pop-img .media,
.pop-l-reel .pop-img .media { border-radius: 0; }

/* Text sits on the picture, so it carries its own ground. A scrim
   rather than a flat panel: the image stays visible at the top and the
   words stay readable at the bottom, which a solid block cannot do. */
.pop-l-cover .pop-text,
.pop-l-reel .pop-text,
.pop-l-cover .pop-acts,
.pop-l-reel .pop-acts {
  position: relative;
  z-index: 2;
  color: #fff;
}

.pop-l-cover .pop-text,
.pop-l-reel .pop-text {
  margin-top: -34%;
  padding: 20px 20px 6px;
  background: linear-gradient(180deg, transparent, rgba(8, 10, 14, .80) 46%, rgba(8, 10, 14, .92));
}

.pop-l-cover .pop-acts,
.pop-l-reel .pop-acts {
  padding: 0 20px 20px;
  background: rgba(8, 10, 14, .92);
}

.pop-l-cover .pop-head,
.pop-l-reel .pop-head,
.pop-l-cover .pop-body,
.pop-l-reel .pop-body { color: #fff; }

/* The close button is over artwork now, so it needs its own contrast
   instead of borrowing the card's. */
.pop-l-cover .pop-x,
.pop-l-reel .pop-x {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 3;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(8, 10, 14, .55);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, .34);
}

.pop-l-cover .pop-x:hover,
.pop-l-reel .pop-x:hover { background: rgba(8, 10, 14, .82); }

/* ---- The reel ------------------------------------------------------- */

/* Upright, the way a phone holds a camera, and narrow enough that it
   reads as a clip rather than as a page takeover. */
.pop-l-reel .pop-box {
  max-width: 340px;
  margin-left: auto;
  margin-right: auto;
}

.pop-l-reel .pop-text { margin-top: -46%; }

/* A reel is the video. Its own play control would sit over a clip that
   is already moving. */
.pop-l-reel .media-play { display: none; }

@media (prefers-reduced-motion: reduce) {
  /* Nothing autoplayed, so the control has to come back or there is no
     way to watch it at all. */
  .pop-l-reel .media-play { display: flex; }
}


/* =====================================================================
   A hero that moves
   =====================================================================
   The video occupies the same layer as the background image, at the same
   opacity, so the headline over it is exactly as legible as it was.

   The media primitive is built to sit in a page and be clicked. Here it
   is scenery: it must fill an area of unknown height rather than hold a
   ratio, and it must not accept a click, because the copy and the search
   field are on top of it and they are what people came for.
   ===================================================================== */

.hero-bg-media { overflow: hidden; }

.hero-bg-media .media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The primitive reserves a ratio to stop the page jumping. A hero has
     its own height, so here the ratio must yield to it. */
  aspect-ratio: auto;
  border-radius: 0;
  background: none;
}

.hero-bg-media .media img,
.hero-bg-media .media video,
.hero-bg-media .media iframe {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* An iframe cannot be object-fit, so a 16:9 player in a short wide hero
   letterboxes. Scaling it past the edges and centring it crops instead,
   which is what a background should do. */
.hero-bg-media .media iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100vw;
  height: 56.25vw;
  min-height: 100%;
  min-width: 177.78vh;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* Scenery does not take clicks, and does not offer a control that the
   headline is sitting on. */
.hero-bg-media .media-play,
.hero-bg-media .media-fallback { display: none; }

.hero-bg-media .media,
.hero-bg-media .media * { pointer-events: none; }


/* =====================================================================
   Keeping the preview inside its pane
   =====================================================================
   The old preview put .pop on the pane itself, which is why the rule
   above is .pop-preview.pop -- one element wearing both. The pane now
   holds the real markup as a CHILD, so that rule stopped matching and
   .pop{position:fixed} took over: a modal preview escaped the pane,
   covered the admin screen with its veil, and had no working close
   button, because preview mode is deliberately not bound to popup.js.

   Everything here is the descendant form of the same intent. The popup
   sits in the box, at the size it will really be, and nothing about it
   is fixed to the viewport.
   ===================================================================== */

.pop-preview {
  position: relative;
  min-height: 90px;
  border-radius: 12px;
  overflow: hidden;
}

.pop-preview .pop {
  position: static;
  inset: auto;
  z-index: 0;
  display: block;
  padding: 0;
  animation: none;
}

/* The dimmer belongs to a page being covered. Nothing is being covered
   here, and at full strength it simply blacks the pane out. */
.pop-preview .pop-veil { display: none; }

/* Centring, sliding in from an edge and rising from the bottom are all
   about a viewport. In the pane they just push the box out of sight. */
.pop-preview .pop-modal,
.pop-preview .pop-corner,
.pop-preview .pop-bar,
.pop-preview .pop-foot,
.pop-preview .pop-sheet {
  position: static;
  inset: auto;
  display: block;
  padding: 0;
  transform: none;
}

.pop-preview .pop-box {
  position: static;
  margin: 0 auto;
  max-width: 100%;
  transform: none;
  opacity: 1;
  animation: none;
}

/* The close button is real markup, so it is really there -- but in a
   preview it closes nothing. Left visible because it is part of what the
   visitor will see, and made plainly inert. */
.pop-preview .pop-x { pointer-events: none; }


/* =====================================================================
   Popup layouts, dressed the way the references are
   =====================================================================
   Five reference designs, and between them only three ideas the existing
   layouts did not already express:

   1. The picture goes to the edges. Every reference with an image on top
      runs it full bleed into the card corners. None float it inside
      padding, which is what made ours read as a slide rather than as a
      piece of marketing.

   2. A split is half and half, and the artwork is a plate. The two split
      references give the art nearly half the card, inset by the card's
      own padding with its own radius, so it sits ON the card rather than
      being a column of it.

   3. The dismissal is a sentence, not a symbol. "No, thanks" under the
      button does what an x in the corner does and asks for nothing.
      dismiss_label already carries the words; they were being drawn as
      though they were still an x.
   ===================================================================== */

/* ---- 1. Full bleed on top ------------------------------------------ */

.pop-l-top .pop-box { overflow: hidden; }

.pop-l-top .pop-img {
  /* Out to the card edge, past whatever padding the box carries, without
     this rule needing to know what that padding is. */
  margin: calc(-1 * var(--pop-pad));
  margin-bottom: 0;
}

.pop-l-top .pop-img .media { border-radius: 0; }

/* ---- 2. A split is half and half ----------------------------------- */

.pop-l-left .pop-box,
.pop-l-right .pop-box {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0;
  padding: var(--pop-pad);
  align-items: stretch;
}

.pop-l-left .pop-img .media,
.pop-l-right .pop-img .media {
  border-radius: var(--radius-lg);
  height: 100%;
}

.pop-l-left .pop-text,
.pop-l-right .pop-text,
.pop-l-left .pop-acts,
.pop-l-right .pop-acts { align-self: center; }

.pop-l-left .pop-text  { padding: 8px 4px 8px 24px; }
.pop-l-left .pop-acts  { padding: 0 4px 0 24px; }
.pop-l-right .pop-text { padding: 8px 24px 8px 4px; }
.pop-l-right .pop-acts { padding: 0 24px 0 4px; }

/* Room to breathe, which is most of what the references are doing. */
.pop-l-left .pop-head,
.pop-l-right .pop-head,
.pop-l-cover .pop-head {
  font-size: 1.45rem;
  line-height: 1.2;
  margin-bottom: 8px;
}

@media (max-width: 620px) {
  .pop-l-left .pop-text,
  .pop-l-right .pop-text,
  .pop-l-left .pop-acts,
  .pop-l-right .pop-acts { padding: 16px 4px 4px; }
}

/* ---- 3. A dismissal in words --------------------------------------- */

/* The loud way out above, the quiet one under it, rather than the two
   competing side by side. */
.pop-l-top .pop-acts,
.pop-l-left .pop-acts,
.pop-l-right .pop-acts,
.pop-l-cover .pop-acts,
.pop-l-reel .pop-acts {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
}

.pop-acts .pop-cta { text-align: center; }

/* Only where the organiser wrote words. An x stays an x, and keeps the
   corner treatment the cover and reel layouts give it. */
.pop-acts .pop-x-words {
  order: 2;
  width: auto;
  height: auto;
  min-height: 0;
  padding: 6px 2px;
  border: 0;
  border-radius: 0;
  background: none;
  font-size: .86rem;
  font-weight: 500;
  color: var(--muted-fg);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pop-acts .pop-x-words:hover { color: var(--fg); }


/* =====================================================================
   The layouts, against what .pop-box actually is
   =====================================================================
   Measured in a browser instead of guessed at, and the guess was wrong
   in two ways that explain why none of it looked like the references.

   .pop-box is `display: flex; align-items: center` -- a ROW. So the
   "picture on top" layout was never stacking anything: the image and the
   words sat side by side, the image took half the card, and every
   reference showing a full-bleed image above a headline was being
   rendered as a squat two-column card. Layout has to state its own
   direction rather than inherit a row and hope.

   And --pop-pad was a variable that does not exist. Every use resolved
   to its 20px fallback while the real padding is 26px, so the negative
   margin meant to pull the picture to the card edge stopped 6px short --
   which reads as a misaligned image, not as a full bleed. It is declared
   here, once, and the box uses it.
   ===================================================================== */

.pop-box { --pop-pad: 26px; padding: var(--pop-pad); }

/* Picture over words: a column, and the picture goes to three edges. */
.pop-l-top .pop-box {
  display: block;
  overflow: hidden;
}

.pop-l-top .pop-img {
  margin: calc(-1 * var(--pop-pad)) calc(-1 * var(--pop-pad)) var(--pop-pad);
}

.pop-l-top .pop-img .media { border-radius: 0; }

.pop-l-top .pop-text,
.pop-l-top .pop-acts { width: 100%; }

/* Words over the picture, and the reel: also columns, also full bleed,
   with the text laid back over the image by the rules further up. */
.pop-l-cover .pop-box,
.pop-l-reel .pop-box {
  display: block;
  padding: 0;
}

.pop-l-cover .pop-img,
.pop-l-reel .pop-img { margin: 0; }

.pop-l-cover .pop-text,
.pop-l-reel .pop-text,
.pop-l-cover .pop-acts,
.pop-l-reel .pop-acts { padding-left: var(--pop-pad); padding-right: var(--pop-pad); }

/* Beside: a real two-column grid, half and half, overriding the row. */
.pop-l-left .pop-box,
.pop-l-right .pop-box {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
  gap: 0;
}

/* flex:0 0 auto is what kept the picture from filling its column once
   the box stopped being a row. */
.pop-l-left .pop-img,
.pop-l-right .pop-img,
.pop-l-top .pop-img,
.pop-l-cover .pop-img,
.pop-l-reel .pop-img { flex: 1 1 auto; min-width: 0; }

.pop-l-left .pop-img .media,
.pop-l-right .pop-img .media { width: 100%; height: 100%; }

@media (max-width: 620px) {
  .pop-l-left .pop-box,
  .pop-l-right .pop-box { display: block; }
}


/* =====================================================================
   Why the picture was a thumbnail
   =====================================================================
   .pop-modal .pop-img img sets height:150px. That is (0,2,1) and beats
   the primitive's own .media img at (0,1,1), so inside a popup every
   picture was 150px tall regardless of the space reserved for it -- the
   figure drew its full size in --muted and the image sat in the top-left
   corner of it. On the top layout an attempted height:auto made it
   worse: 25px, measured.

   Stated once, at a specificity that wins, for all three media kinds.
   ===================================================================== */

.pop .pop-img .media img,
.pop .pop-img .media video,
.pop .pop-img .media iframe {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---------------------------------------------------------------------
   And why the right-hand side was strung out
   ---------------------------------------------------------------------
   The words and the buttons are two separate grid items, and each was
   centring itself in a row of its own — so the heading sat near the top,
   the buttons near the bottom, and the card looked stretched rather than
   composed. They are one group: the picture spans both rows, and the
   pair centres together against it.
   --------------------------------------------------------------------- */

.pop-l-left .pop-box,
.pop-l-right .pop-box {
  grid-template-rows: min-content min-content;
  align-content: center;
}

.pop-l-left .pop-img  { grid-column: 1; grid-row: 1 / span 2; }
.pop-l-right .pop-img { grid-column: 2; grid-row: 1 / span 2; }

.pop-l-left .pop-text  { grid-column: 2; grid-row: 1; }
.pop-l-left .pop-acts  { grid-column: 2; grid-row: 2; }
.pop-l-right .pop-text { grid-column: 1; grid-row: 1; }
.pop-l-right .pop-acts { grid-column: 1; grid-row: 2; }

/* They stack as a pair now, so centring each one individually is what
   was pulling them apart. */
.pop-l-left .pop-text,
.pop-l-right .pop-text,
.pop-l-left .pop-acts,
.pop-l-right .pop-acts { align-self: start; }

.pop-l-left .pop-text,
.pop-l-right .pop-text { padding-bottom: 14px; }

/* ---------------------------------------------------------------------
   An x is not a button the width of the card
   ---------------------------------------------------------------------
   Stacking the actions put a full-width bar under the call to action
   with a single x in the middle of it, which reads as a second, stranger
   button. Only a worded dismissal belongs in that stack; a symbol goes
   back to being a small thing in the corner.
   --------------------------------------------------------------------- */

.pop-acts > .pop-x:not(.pop-x-words) {
  align-self: flex-end;
  width: 36px;
  min-width: 36px;
  height: 36px;
  padding: 0;
}

/* A worded dismissal is a link, not a bar. Stretched to the card width
   its underline ran the whole way across, which reads as a second
   button rather than as the quiet way out. */
.pop-acts > .pop-x-words { align-self: center; width: auto; min-width: 0; }


/* ---------------------------------------------------------------------
   A popup that asks for an address
   ---------------------------------------------------------------------
   Field over button, because that is the order they are used in and it
   survives a narrow card without either half becoming unusable.
   --------------------------------------------------------------------- */

.pop-capture {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.pop-capture input[type="email"] {
  width: 100%;
  min-height: 44px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--card-fg);
  font-size: 1rem;   /* 16px, or iOS zooms the page on focus */
}

.pop-capture .pop-cta { width: 100%; text-align: center; }

.pop-capture input[disabled],
.pop-capture button[disabled] { opacity: .6; cursor: default; }

/* Over artwork the field needs its own ground rather than the card's. */
.pop-l-cover .pop-capture input[type="email"],
.pop-l-reel .pop-capture input[type="email"] {
  background: rgba(255, 255, 255, .96);
  color: #14161a;
  border-color: transparent;
}


/* ---------------------------------------------------------------------
   The funnel, on "Why it did not sell".

   Bars rather than a chart on purpose: three stages, one axis, and the
   only comparison that matters is each bar against the one above it.
   A chart library would be a megabyte to draw three rectangles.

   Widths are set inline from the real numbers, so the bar is the data
   and not a decoration sized by eye.
   --------------------------------------------------------------------- */
.funnel { display: flex; flex-direction: column; gap: 14px; }

.funnel-label {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; margin-bottom: 5px;
}
.funnel-label span   { font-weight: 600; }
.funnel-label strong { font-size: 1.25rem; font-weight: 800; font-variant-numeric: tabular-nums; }

.funnel-track {
  background: var(--muted); border-radius: var(--radius-sm);
  height: 12px; overflow: hidden;
}
.funnel-bar {
  height: 100%; border-radius: var(--radius-sm);
  background: var(--primary);
  transition: width .35s ease;
}

/* The stages read as a sequence, so they darken rather than change hue:
   a different colour per stage would imply they are different kinds of
   thing rather than the same people, fewer of them. */
.funnel-looked  { opacity: .45; }
.funnel-started { opacity: .72; }
.funnel-paid    { opacity: 1; }

.funnel-rate {
  font-size: 0.8rem; color: var(--muted-fg); margin-top: 5px;
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .funnel-bar { transition: none; }
}

/* A list that is content, not navigation. */
.plain { list-style: none; padding: 0; margin: 0; }
.plain li { padding: 6px 0; border-bottom: 1px solid var(--border); }
.plain li:last-child { border-bottom: 0; }

/* ---------------------------------------------------------------------
   Sold out, and the list for when one comes back.

   A tier that ran out used to disappear from the page entirely. It is
   worth showing: it is proof the event is worth going to, and it is the
   only place a waiting list can be offered. So it renders quiet rather
   than absent -- present, plainly unavailable, not competing with the
   tiers that can still be bought.
   --------------------------------------------------------------------- */
.badge-muted {
  background: var(--muted); color: var(--muted-fg);
  border: 1px solid var(--border);
}

.sold-row {
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
  opacity: .82;
}
.sold-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* A disclosure, not an open form: most people reading a sold-out tier
   are moving on, and a permanent email field in front of them reads as
   a demand rather than an offer. */
/* --fg, not --primary. On this install --primary is #004880, which
   against the dark page ground measures 2.24:1 -- unreadable, and well
   under the 4.5:1 body text needs. Every other link on the public page
   already uses the foreground colour at 16:1; the +/- marker and the
   underline carry the affordance instead of colour. */
.wait-join > summary {
  cursor: pointer; font-size: 0.85rem; font-weight: 600;
  color: var(--fg); padding: 6px 0; list-style: none;
}
.wait-join > summary::-webkit-details-marker { display: none; }
.wait-join > summary::before { content: "+ "; font-weight: 700; }
.wait-join[open] > summary::before { content: "\2212 "; }
.wait-join > summary:hover { text-decoration: underline; }

.wait-form {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin: 6px 0 4px;
}
.wait-form input[type="email"] { flex: 2 1 12rem; min-width: 0; }
.wait-form input[name="name"]  { flex: 1 1 8rem;  min-width: 0; }
.wait-form select              { flex: 0 0 4.5rem; }
.wait-form .btn                { flex: 0 0 auto; }

/* Stacked on a phone: three fields and a button on one line becomes
   four unusable slivers. */
@media (max-width: 560px) {
  .wait-form > * { flex: 1 1 100% !important; }
}


/* ---------------------------------------------------------------------
   "Draft with AI" — the small panel under the newsletter blocks and the
   event description. A writing aid, styled like one: quiet until opened,
   one input, one button, and a note that keeps expectations honest.
   --------------------------------------------------------------------- */
.ai-draft-panel {
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm, 8px);
  padding: 12px 14px;
  margin-top: 10px;
  background: var(--bg-alt);
}

.ai-draft-row { display: flex; gap: 8px; align-items: center; }

.ai-draft-row input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 38px;
}

.ai-draft-row .btn { flex: 0 0 auto; white-space: nowrap; }

.ai-draft-panel .tiny { margin: 8px 0 0; color: var(--muted-fg); }

@media (max-width: 520px) {
  .ai-draft-row { flex-wrap: wrap; }
  .ai-draft-row input[type="text"] { flex: 1 1 100%; }
}
/* =====================================================================
   The ticket assistant
   =====================================================================
   A launcher, and the conversation in the sheet component the rest of
   the site already uses. It sits above the floating ticket bar rather
   than beside it: the bar is the thing that sells, and the assistant
   must never be what covers it.
   ===================================================================== */

.chat-launch {
  position: fixed;
  right: 18px;
  /* Clear of the ticket dock, which is 72px and owns the bottom edge. */
  bottom: 96px;
  z-index: 45;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 46px;
  padding: 0 18px 0 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--card-fg);
  font-weight: 600;
  font-size: .92rem;
  box-shadow: 0 10px 30px rgba(16, 20, 24, .16);
  cursor: pointer;
}

.chat-launch:hover { border-color: var(--primary); color: var(--primary-fg); }

.chat-launch-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--success, #1f9d55);
}

@media (max-width: 600px) {
  /* On a phone the words are the first thing to go: the bar below it is
     already competing for the same thumb. */
  .chat-launch { padding: 0; width: 46px; justify-content: center; }
  .chat-launch span:not(.chat-launch-dot) { display: none; }
}

.chat-sheet { width: min(560px, calc(100vw - 2rem)); }

.chat { display: flex; flex-direction: column; gap: 12px; }

.chat-log {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: min(48vh, 420px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.chat-line {
  margin: 0;
  max-width: 86%;
  padding: 10px 14px;
  border-radius: 16px;
  line-height: 1.45;
  /* The answers are assembled with newlines between list items, and a
     price list collapsed onto one line is unreadable. */
  white-space: pre-line;
}

.chat-assistant {
  align-self: flex-start;
  background: var(--muted);
  color: var(--fg);
  border-bottom-left-radius: 5px;
}

.chat-visitor {
  align-self: flex-end;
  background: var(--primary);
  color: #fff;
  border-bottom-right-radius: 5px;
}

.chat-prompts { display: flex; flex-wrap: wrap; gap: 8px; }

.chat-prompt {
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--card-fg);
  font-size: .86rem;
  cursor: pointer;
}

.chat-prompt:hover { border-color: var(--primary); color: var(--primary-fg); }

.chat-ask { display: flex; gap: 8px; }

.chat-ask input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 46px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--card-fg);
  /* 16px, or iOS zooms the page the moment this takes focus. */
  font-size: 1rem;
}

.chat-ask .btn { min-height: 46px; border-radius: 999px; }

/* ---- The basket, inside the conversation --------------------------- */

.chat-basket {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--card);
}

.chat-tier {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.chat-tier-name { font-weight: 600; min-width: 0; }

.chat-tier select { min-width: 4.5rem; }

.chat-go { min-height: 46px; }

/* ---------------------------------------------------------------------
   "5 Kagera tickets for these people" — the list, before anybody pays.

   Every recipient is an editable pair rather than a line of text: the
   list was read out of a sentence by a model, and correcting a mangled
   name in place is what keeps this a shortcut instead of something you
   have to undo.
   --------------------------------------------------------------------- */
.chat-order-head { font-weight: 700; margin-bottom: 8px; }

.chat-order-row {
  display: flex; gap: 6px; margin-bottom: 6px;
}
.chat-order-row input[name="attendee_name[]"]  { flex: 1 1 40%; min-width: 0; }
.chat-order-row input[name="attendee_email[]"] { flex: 1 1 60%; min-width: 0; }

/* A warning here is the assistant saying it is unsure, not an error the
   buyer caused — amber, not red, and never blocking the button. */
.chat-order-warn {
  font-size: 0.8rem; margin: 6px 0 0;
  color: var(--warning-fg, var(--fg));
}

.chat-order-foot {
  font-size: 0.75rem; color: var(--muted-fg); margin: 8px 0 0;
}

@media (max-width: 480px) {
  .chat-order-row { flex-wrap: wrap; }
  .chat-order-row input { flex: 1 1 100%; }
}

/* ---------------------------------------------------------------------
   "Everything collected" — the file index.

   Two registers on one screen, on purpose: photographs are RECOGNISED,
   so they get a grid of faces; paperwork is READ, so it stays in rows.
   The kind chips are the navigation and the count at once.
   --------------------------------------------------------------------- */
.fx-controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: end; }
.fx-controls .fx-grow { flex: 1 1 240px; }

.fx-chips { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

.fx-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px; border: 1px solid var(--border); border-radius: 999px;
  font-size: 0.82rem; color: var(--fg); text-decoration: none;
  background: var(--bg); white-space: nowrap;
}
.fx-chip b { font-weight: 700; color: var(--muted-fg); font-size: 0.76rem; }
.fx-chip:hover { border-color: var(--primary); color: var(--primary-fg); text-decoration: none; }
.fx-chip.on { border-color: var(--primary); background: var(--primary-50); color: var(--primary-fg); }
.fx-chip.on b { color: var(--primary-fg); }

.fx-spacer { flex: 1 1 auto; }

.fx-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
}

.fx-item {
  margin: 0; border: 1px solid var(--border); border-radius: 10px;
  overflow: hidden; background: var(--bg);
}
.fx-item > a { display: block; }
.fx-item img, .fx-noimg {
  width: 100%; aspect-ratio: 1; object-fit: cover; display: block;
  background: var(--bg-alt);
}
.fx-noimg { display: grid; place-items: center; color: var(--muted-fg); }

.fx-item figcaption { padding: 8px 10px 9px; display: grid; gap: 2px; }
.fx-name {
  font-size: 0.8rem; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fx-sub {
  font-size: 0.7rem; color: var(--muted-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fx-acts { display: flex; gap: 10px; font-size: 0.72rem; margin-top: 3px; }

@media (max-width: 520px) {
  .fx-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
}

/* ---------------------------------------------------------------------
   "One person" — the record header.

   The headline before the tables: who they are, their face if any
   ticket carries one, and the four numbers worth knowing before any
   detail. The photo is a circle because it is a person, not a file.
   --------------------------------------------------------------------- */
.pp-head { display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }

.pp-avatar {
  flex: 0 0 auto; width: 76px; height: 76px; border-radius: 50%;
  overflow: hidden; border: 1px solid var(--border); background: var(--bg-alt);
  display: grid; place-items: center;
}
.pp-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pp-initials {
  font-size: 1.5rem; font-weight: 800; color: var(--muted-fg);
  letter-spacing: .02em;
}

.pp-who { flex: 1 1 220px; min-width: 0; display: grid; gap: 2px; }
.pp-name {
  font-size: 1.25rem; font-weight: 800; letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pp-mail { font-size: 0.85rem; color: var(--muted-fg); }

.pp-stats {
  display: grid; gap: 10px 26px;
  grid-template-columns: repeat(auto-fit, minmax(96px, max-content));
  align-items: start;
}

@media (max-width: 640px) {
  .pp-head { gap: 14px; }
  .pp-stats { width: 100%; grid-template-columns: repeat(2, 1fr); }
}

/* The photo requirement choice, indented under its parent tick. */
.cp-mode {
  margin: 6px 0 0 28px; padding-left: 12px;
  border-left: 2px solid var(--border);
  display: grid; gap: 6px;
}

/* ---------------------------------------------------------------------
   /now — the live hub an attendee keeps open on the day, and the small
   parts the game paints into it. Phone-first: this page lives in hands.
   --------------------------------------------------------------------- */
.nw-sess { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 8px; }
.nw-sess .chip { flex: 0 0 auto; min-width: 46px; text-align: center; }

.nw-opts { display: grid; gap: 8px; grid-template-columns: repeat(2, 1fr); }
.nw-opts .nw-opt { min-height: 52px; font-weight: 600; }

.nw-bar {
  display: flex; justify-content: space-between; gap: 10px;
  padding: 7px 10px; margin-bottom: 6px;
  border: 1px solid var(--border); border-radius: 8px; font-size: 0.85rem;
}
.nw-bar.is-right { border-color: var(--ok); color: var(--ok); font-weight: 600; }

.nw-board { margin: 0; padding-left: 20px; font-size: 0.9rem; }
.nw-board li { margin-bottom: 3px; }
.nw-board b { float: right; font-variant-numeric: tabular-nums; }

@media (max-width: 420px) {
  .nw-opts { grid-template-columns: 1fr; }
}

/* The poll results bars on the organiser's dashboard. */
.pl-results { display: grid; gap: 5px; }
.pl-row { display: grid; grid-template-columns: minmax(80px, 180px) 1fr 90px;
          gap: 10px; align-items: center; font-size: 0.82rem; }
.pl-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-track { height: 10px; background: var(--bg-alt); border-radius: 999px; overflow: hidden; }
.pl-track i { display: block; height: 100%; background: var(--primary);
              border-radius: 999px; transition: width .4s ease; }
.pl-n { color: var(--muted-fg); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------------------------------------------------------------------
   The live surfaces, designed as what they are.

   /now lives in a hand at a venue; the game console lives on a
   projector. One rule governs both: the boldness is spent on the game
   — everything around it stays in the house's quiet register.

   Four game hues, defined once as tokens and used only through
   color-mix against the page's own background, so they hold in light
   and dark without a second palette.
   --------------------------------------------------------------------- */
:root {
  --game-a: #2f6fd6;   /* the house info blue   */
  --game-b: #12a05e;   /* the house ok green    */
  --game-c: #d08700;   /* the engagement amber  */
  --game-d: #b04a98;   /* the after-event plum  */
}

/* ---- the hero strip -------------------------------------------------- */
.nw-hero {
  padding: 18px 20px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius, 12px);
  background: linear-gradient(135deg,
      color-mix(in srgb, var(--primary) 10%, var(--bg)),
      var(--bg) 70%);
}
.nw-hero h1 {
  font-size: clamp(1.45rem, 5.5vw, 1.9rem);
  font-weight: 800; letter-spacing: -0.015em; line-height: 1.15;
  margin: 4px 0 4px;
}
.nw-hero-meta { font-size: 0.85rem; color: var(--muted-fg); margin: 0; }

.nw-live {
  display: inline-flex; align-items: center; gap: 7px; margin: 0;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--primary-fg);
}

.nw-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--primary); position: relative; flex: 0 0 auto;
}
.nw-dot::after {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid var(--primary); opacity: 0;
  animation: nw-pulse 2s ease-out infinite;
}
@keyframes nw-pulse {
  0%   { transform: scale(.5); opacity: .8; }
  70%  { transform: scale(1.15); opacity: 0; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .nw-dot::after { animation: none; }
}

/* ---- the game card, the one loud thing ------------------------------- */
.nw-game-card {
  border-color: color-mix(in srgb, var(--primary) 40%, var(--border));
  box-shadow: 0 6px 24px color-mix(in srgb, var(--primary) 12%, transparent);
}
.nw-game-card .card-head {
  display: flex; align-items: center; gap: 8px; color: var(--primary-fg);
}

.nw-q {
  font-size: clamp(1.05rem, 4vw, 1.3rem); font-weight: 800;
  line-height: 1.25; margin: 0 0 10px;
}

.nw-clockbar {
  height: 6px; border-radius: 999px; overflow: hidden;
  background: var(--bg-alt); margin-bottom: 6px;
}
.nw-clockbar i {
  display: block; height: 100%; border-radius: 999px;
  background: var(--primary); transition: width 1s linear;
}
.nw-clockbar.is-late i { background: var(--warning-fg, #d08700); }

/* Each answer wears its own hue — a tinted fill and a firm left bar,
   text always in the page's own ink so contrast never depends on the
   hue. The same order every question, like the game shows on screen. */
.nw-opts .nw-opt {
  min-height: 56px; font-weight: 700; font-size: 0.95rem;
  border-width: 1px; border-left-width: 5px; text-align: left;
  color: var(--fg);
}
.nw-opts .nw-opt:nth-of-type(1) {
  border-left-color: var(--game-a);
  background: color-mix(in srgb, var(--game-a) 10%, var(--bg));
}
.nw-opts .nw-opt:nth-of-type(2) {
  border-left-color: var(--game-b);
  background: color-mix(in srgb, var(--game-b) 10%, var(--bg));
}
.nw-opts .nw-opt:nth-of-type(3) {
  border-left-color: var(--game-c);
  background: color-mix(in srgb, var(--game-c) 12%, var(--bg));
}
.nw-opts .nw-opt:nth-of-type(4) {
  border-left-color: var(--game-d);
  background: color-mix(in srgb, var(--game-d) 10%, var(--bg));
}
.nw-opts .nw-opt:disabled { opacity: .55; }

/* Podium metal for the top three, on every leaderboard. */
.nw-board { list-style: none; padding-left: 0; }
.nw-board li {
  display: flex; gap: 8px; align-items: baseline;
  padding: 5px 2px; border-bottom: 1px solid var(--border);
}
.nw-board li:last-child { border-bottom: 0; }
.nw-board li::before {
  content: counter(list-item) "."; min-width: 1.6em;
  color: var(--muted-fg); font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
}
.nw-board li:nth-child(1)::before { content: "🥇"; }
.nw-board li:nth-child(2)::before { content: "🥈"; }
.nw-board li:nth-child(3)::before { content: "🥉"; }
.nw-board b { margin-left: auto; float: none; }

/* ---- the console stage: read it from the back row -------------------- */
.gc-huge {
  font-size: clamp(2.2rem, 6vw, 3.4rem); font-weight: 800;
  letter-spacing: -0.02em; margin: 0; line-height: 1.1;
}
.gc-sub { font-size: 1rem; color: var(--muted-fg); margin: 4px 0 0; }

.gc-q {
  font-size: clamp(1.5rem, 3.5vw, 2.4rem); font-weight: 800;
  line-height: 1.2; margin: 0 0 8px;
}

.gc-clock {
  font-size: clamp(1.6rem, 4vw, 2.6rem); font-weight: 800;
  color: var(--primary-fg); margin: 0 0 12px;
  font-variant-numeric: tabular-nums;
}

#gc-stage .nw-bar {
  min-height: 44px; align-items: center; font-size: 1.05rem;
  padding: 10px 16px;
}
#gc-stage .nw-bar b { font-size: 1.15rem; }

#gc-status.chip { text-transform: capitalize; }

/* The full stop at the end of a game: your rank, big enough to screenshot. */
.nw-final {
  padding: 12px 14px; margin-bottom: 10px;
  border: 1px solid color-mix(in srgb, var(--primary) 35%, var(--border));
  border-radius: 10px;
  background: color-mix(in srgb, var(--primary) 7%, var(--bg));
}
.nw-final-rank {
  font-size: 1.9rem; font-weight: 800; letter-spacing: -0.02em;
  margin: 0; line-height: 1.1;
}

/* ---------------------------------------------------------------------
   The programme's Networking tab. .pgrid was markup with no stylesheet
   behind it — everything stacked full-width in source order, and the
   reader met a long profile form before a single human being. People
   take the wide column; your own paperwork sits beside them; on a
   phone the people come first.
   --------------------------------------------------------------------- */
.pgrid {
  display: grid;
  grid-template-columns: minmax(280px, 380px) minmax(0, 1fr);
  gap: 24px; align-items: start;
}

@media (max-width: 860px) {
  .pgrid { grid-template-columns: minmax(0, 1fr); }
  /* The directory, matches and requests before your own form. */
  .pgrid > div:last-child  { order: 1; }
  .pgrid > div:first-child { order: 2; }
}

/* A request: who is asking on the left, the answer buttons on the
   right, one rule between neighbours. Also unstyled until now. */
.sv-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 0; border-bottom: 1px solid var(--border);
}
.sv-row:last-child { border-bottom: 0; }
.sv-row .who { flex: 1 1 220px; min-width: 0; font-size: 0.9rem; }
.sv-row .who b { display: block; }
.sv-row .who span { color: var(--muted-fg); font-size: 0.8rem; }

/* ---------------------------------------------------------------------
   Shakedown, 2026-08-21: classes the markup wore that no stylesheet
   ever defined. Found by crawling 1,024 pages as four identities and
   diffing every class attribute against every definition. Each rule
   below is the smallest thing that makes the markup mean what it says.
   --------------------------------------------------------------------- */

/* Utilities the markup reached for (Bootstrap and Tailwind habits). */
.items-start { align-items: flex-start; }
.items-end   { align-items: flex-end; }
.me-1        { margin-right: 4px; }
.py-5        { padding-top: 48px; padding-bottom: 48px; }
.narrow      { max-width: 720px; }
.h3 { font-size: 1.45rem; font-weight: 800; letter-spacing: -0.01em; line-height: 1.2; }
.h4 { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.005em; line-height: 1.25; }

/* Admin page heads: title and sub on the left, actions on the right. */
.page-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 16px; flex-wrap: wrap; margin-bottom: 18px;
}
.page-head h1 { margin: 0; }
.page-head .sub { margin: 2px 0 0; color: var(--muted-fg); font-size: 0.9rem; }

/* A prefix glued to an input: "localhost/evenz/" + your handle. */
.input-prefixed {
  display: flex; align-items: center; gap: 6px;
}
.input-prefixed > span { white-space: nowrap; flex: 0 0 auto; }
.input-prefixed > input { flex: 1 1 auto; min-width: 0; }

/* The pricing calculator's add-on rows. */
.calc-row {
  display: flex; justify-content: space-between; align-items: center;
  gap: 14px; padding: 9px 0; border-bottom: 1px solid var(--border);
}
.calc-row:last-child { border-bottom: 0; }
.calc-what { display: grid; gap: 2px; min-width: 0; }
.calc-take { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
.calc-take input[type="number"] { width: 72px; }

/* Home: the category name carries the row, the count sits at the end. */
.cat-name { flex: 1 1 auto; font-weight: 600; min-width: 0; }

/* The template editor's left column may shrink without spilling. */
.designer-controls { min-width: 0; }

/* The composer's toolbar glyphs, standing in for a Font Awesome that was
   never loaded on this install. */
.ico {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.1em; font-style: normal; font-size: 0.95em; line-height: 1;
}
.ico-spin { animation: ico-spin 1s linear infinite; }
@keyframes ico-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ico-spin { animation: none; } }

/* .table — worn by sixteen screens and, until the shakedown, defined by
   nobody: every one of them was rendering browser-default cells. The
   house register: quiet rules, small-caps heads, numbers right-aligned. */
.table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.table th {
  text-align: left; font-size: 0.72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted-fg); padding: 8px 10px;
  border-bottom: 2px solid var(--border); white-space: nowrap;
}
.table td { padding: 9px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover td { background: color-mix(in srgb, var(--primary) 4%, transparent); }
.table .num, .table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.table .nowrap { white-space: nowrap; }

/* Event form: the three-way choice of what ticket holders may change
   themselves. Sits under its own heading rather than a tick, so the rail
   starts at the margin. */
.tc-mode {
  margin: 8px 0 0; padding-left: 12px;
  border-left: 2px solid var(--border);
  display: grid; gap: 6px;
}

/* Everything collected: an attendee photo framed for the four shapes a
   card or badge prints. The previews are the library's own variants, so
   what is shown here is what prints. */
.fx-shapes { display: flex; gap: 4px; margin: 6px 0 2px; }
.fx-shape { width: 34px; height: 34px; object-fit: cover; background: var(--muted); border: 1px solid var(--border); }
.fx-shape-circle { border-radius: 50%; }
.fx-shape-rounded { border-radius: 6px; }
.fx-shape-portrait { width: 26px; }
.fx-state { margin-left: 4px; font-size: 0.7rem; vertical-align: middle; }
.fx-inline { display: inline; margin: 0; }
.fx-link { background: none; border: 0; padding: 0; font: inherit; color: var(--primary-fg); cursor: pointer; text-decoration: underline; }

/* Attendees: the remove drawer inside a row's manage panel. Closed by
   default; the consequences are read before the word is typed. */
.ad-remove { border-top: 1px solid var(--border); padding-top: 8px; }
.ad-remove summary { cursor: pointer; color: var(--muted-fg); }
.ad-remove[open] summary { color: var(--fg); margin-bottom: 6px; }
.ad-remove-form { max-width: 420px; }

/* Dashboard menu: sections that fold, folds inside sections, a strip
   naming the event a page is about. Headings are <summary> elements so
   every fold works without scripting; the chevron is drawn, not a glyph. */
.dash-side details { border: 0; }
.dash-side summary { list-style: none; cursor: pointer; user-select: none; }
.dash-side summary::-webkit-details-marker { display: none; }
.dash-side summary:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.dash-side .dash-group > summary h5 { margin: 18px 14px 6px 20px; }
.dash-side .dash-group:first-of-type > summary h5 { margin-top: 0; }
.dash-side .dash-group > summary h5 .cat-ic { width: 15px; height: 15px; flex: 0 0 15px; color: var(--g, var(--muted-fg)); }
.dash-side .dash-group > summary h5 span { flex: 1 1 auto; }
.dash-side .chev { width: 7px; height: 7px; flex: 0 0 7px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
                   transform: rotate(45deg); margin: -3px 2px 0 0; opacity: .55; transition: transform .15s ease; }
.dash-side details:not([open]) > summary .chev { transform: rotate(-45deg); margin-top: 0; }
.dash-side .dash-sub > summary { display: flex; align-items: center; gap: 10px; padding: 8px 20px; font-size: 0.9rem; font-weight: 600; color: var(--fg); border-left: 3px solid transparent; }
.dash-side .dash-sub > summary:hover { background: var(--muted); }
.dash-side .dash-sub > summary:hover .nav-ic { color: var(--fg); }
.dash-side .dash-sub > summary span { flex: 1 1 auto; }
.dash-side .dash-sub-links a { padding-left: 47px; font-size: 0.9rem; font-weight: 500; }
.dash-side .dash-sub-links a .nav-ic { width: 14px; height: 14px; flex: 0 0 14px; }
.dash-context { margin: 0 14px 14px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); display: grid; gap: 2px; }
.dash-context .dc-label { font-size: 0.66rem; text-transform: uppercase; letter-spacing: .09em; font-weight: 700; color: var(--muted-fg); }
.dash-context strong { font-size: 0.9rem; line-height: 1.25; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.dash-context .dc-links { font-size: 0.8rem; display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
.dash-context .dc-links a { color: var(--primary-fg); text-decoration: none; }
.dash-context .dc-links a:hover { text-decoration: underline; }
@media (max-width: 860px) {
  .dash-side .dash-group > summary h5 { margin: 12px 14px 4px 20px; padding: 4px 0; }
}

/* Event settings panels (manage page): head names the setting and its
   state, body explains it once and asks for numbers with their units,
   foot saves. The switch is the native checkbox, drawn as a switch, so
   nothing about the form changed but its face. */
.es-panel { max-width: 640px; margin-top: 18px; overflow: hidden; }
.es-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 22px; border-bottom: 1px solid var(--border); }
.es-head h3 { margin: 0; font-size: 1rem; }
.es-on .es-head { border-bottom-color: color-mix(in srgb, var(--ok) 35%, var(--border)); }
.es-body { padding: 18px 22px 20px; }
.es-switch { display: flex; align-items: flex-start; gap: 14px; margin: 0; cursor: pointer; font-weight: 400; }
.es-switch > input { appearance: none; -webkit-appearance: none; width: 40px; height: 22px; flex: 0 0 40px; margin: 2px 0 0;
                     border: 0; border-radius: 999px; background: var(--border); position: relative; cursor: pointer; transition: background .15s ease; }
.es-switch > input::after { content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
                            background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.3); transition: transform .15s ease; }
.es-switch > input:checked { background: var(--ok); }
.es-switch > input:checked::after { transform: translateX(18px); }
.es-switch > input:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.es-switch b { display: block; font-size: .95rem; line-height: 1.35; }
.es-switch .d { display: block; color: var(--muted-fg); font-size: .86rem; line-height: 1.5; margin-top: 3px; }
.es-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px 16px; margin-top: 18px; }
.es-grid-2 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.es-grid .field { margin: 0; }
.es-grid .field > label { font-size: .82rem; }
.es-unit { display: flex; align-items: stretch; border: 1px solid var(--border); border-radius: var(--radius-sm, 8px); background: var(--bg); overflow: hidden; }
.es-unit input { border: 0; border-radius: 0; background: transparent; width: 100%; min-width: 0; padding: 9px 12px; font-variant-numeric: tabular-nums; box-shadow: none; }
.es-unit input:focus { outline: none; box-shadow: none; }
.es-unit:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); }
.es-unit span { display: flex; align-items: center; padding: 0 11px; font-size: .78rem; color: var(--muted-fg); background: var(--bg-alt); border-left: 1px solid var(--border); white-space: nowrap; }
.es-dates { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin-top: 18px; }
.es-date { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; background: var(--bg-alt); }
.es-date .l { font-size: .68rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted-fg); font-weight: 700; }
.es-date .v { font-weight: 700; margin-top: 3px; font-variant-numeric: tabular-nums; }
.es-narrow { max-width: 340px; margin: 18px 0 0; }
.es-foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; padding: 12px 22px; border-top: 1px solid var(--border); background: var(--bg-alt); }
.es-foot .note { margin-right: auto; font-size: .8rem; color: var(--muted-fg); }
@media (max-width: 560px) {
  .es-foot { flex-direction: column; align-items: stretch; }
  .es-foot .note { margin: 0 0 4px; }
  .es-foot .btn { width: 100%; }
}

/* Event settings: two panels side by side where there is room, the
   schedule-mode choice, and the automatic schedule's preview table. */
.es-row { display: grid; grid-template-columns: 1fr; gap: 18px; margin-top: 18px; align-items: start; }
.es-row .es-panel { max-width: none; margin-top: 0; }
@media (min-width: 1100px) { .es-row { grid-template-columns: 1fr 1fr; } }
.es-mode { display: grid; gap: 8px; margin-top: 16px; }
.es-opt { display: flex; align-items: flex-start; gap: 10px; border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; cursor: pointer; font-weight: 400; margin: 0; }
.es-opt.on { border-color: var(--primary); background: color-mix(in srgb, var(--primary) 6%, transparent); }
.es-opt input { width: auto; flex: none; margin: 3px 0 0; }
.es-opt b { display: block; font-size: .9rem; }
.es-opt .d { display: block; color: var(--muted-fg); font-size: .82rem; line-height: 1.45; margin-top: 2px; }
.es-preview-wrap { margin-top: 18px; overflow-x: auto; }
.es-preview-lbl { font-size: .68rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted-fg); font-weight: 700; margin-bottom: 6px; }
.es-preview { width: 100%; border-collapse: collapse; font-size: .84rem; }
.es-preview th { text-align: left; font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted-fg); padding: 6px 8px; border-bottom: 1px solid var(--border); font-weight: 700; }
.es-preview td { padding: 7px 8px; border-bottom: 1px solid var(--border); font-variant-numeric: tabular-nums; white-space: nowrap; }
.es-preview tr:last-child td { border-bottom: 0; }
.es-preview .full { color: var(--muted-fg); white-space: normal; }

/* The journey: one purchase's steps as a timeline on the order page, and
   every step counted on the funnel. Phases wear a tint: before the order,
   the order itself, after it. */
.jy { list-style: none; margin: 0; padding: 0 0 0 14px; border-left: 2px solid var(--border); display: grid; gap: 10px; }
.jy li { position: relative; display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 12px; align-items: baseline; font-size: .86rem; }
.jy li::before { content: ''; position: absolute; left: -19px; top: 5px; width: 8px; height: 8px; border-radius: 50%; background: var(--muted-fg); }
.jy li.jy-before::before { background: var(--border); }
.jy li.jy-order::before { background: var(--primary); }
.jy li.jy-after::before { background: var(--ok); }
.jy-when { color: var(--muted-fg); font-variant-numeric: tabular-nums; white-space: nowrap; }
.jy-what span { color: var(--muted-fg); margin-left: 6px; }
.jy-table { width: 100%; border-collapse: collapse; font-size: .86rem; max-width: 560px; }
.jy-table th { text-align: left; font-size: .68rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted-fg); padding: 6px 8px; border-bottom: 1px solid var(--border); }
.jy-table td { padding: 6px 8px; border-bottom: 1px solid var(--border); font-variant-numeric: tabular-nums; }
.jy-table tr.jy-order td:first-child { font-weight: 600; }
.jy-table tr.jy-after td { color: var(--muted-fg); }
@media (max-width: 560px) { .jy li { grid-template-columns: 1fr; gap: 2px; } }

/* Email look: the organiser's logo, banner and colours, and the live
   preview of the shell they will wear. */
.eb-thumb { display: inline-flex; align-items: center; justify-content: center; padding: 10px 14px; border-radius: 8px; margin-bottom: 8px; }
.eb-thumb img { max-height: 44px; max-width: 220px; display: block; }
.eb-thumb-wide { display: block; padding: 0; background: var(--muted); overflow: hidden; }
.eb-thumb-wide img { max-height: none; max-width: 100%; width: 100%; }
.eb-colour { display: flex; gap: 8px; align-items: center; }
.eb-colour input[type=color] { width: 42px; height: 38px; padding: 2px; border: 1px solid var(--border); border-radius: var(--radius-sm, 8px); background: var(--bg); cursor: pointer; }
.eb-colour input[type=text] { flex: 1; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.eb-preview { background: #f4f4f7; padding: 10px; }
.eb-preview iframe { width: 100%; height: 560px; border: 0; border-radius: 8px; background: #f4f4f7; }

/* The dashboard menu, quieter: a soft ground, rows that round, the current
   page as a raised white pill, section labels in sentence case with a
   chevron, nested screens indented without icons. Overrides the rules
   above; the structure (sections, folds, badges) is unchanged. */
.dash-side { background: var(--bg-alt); border-right: 1px solid var(--border); padding: 14px 10px 22px; }
.dash-side .dash-search { margin: 0 4px 12px; }
.dash-side a { border-left: 0; border-radius: 10px; margin: 1px 0; padding: 9px 12px; color: var(--fg); font-weight: 500; }
.dash-side a:hover { background: var(--muted); }
.dash-side a.active { background: var(--card); border: 1px solid var(--border); box-shadow: var(--shadow-sm); font-weight: 600; padding: 8px 11px; }
.dash-side a.active .nav-ic { color: var(--fg); }
.dash-side .nav-ic { color: var(--muted-fg); }
.dash-side .dash-group > summary h5 { margin: 16px 10px 4px 12px; font-size: .8rem; text-transform: none; letter-spacing: 0; font-weight: 600; color: var(--muted-fg); }
.dash-side .dash-group:first-of-type > summary h5 { margin-top: 2px; }
.dash-side .dash-group > summary h5 .cat-ic { display: none; }
.dash-side .dash-group > summary h5 .chev { opacity: .7; margin-right: 4px; }
.dash-side .dash-sub > summary { border-left: 0; border-radius: 10px; margin: 1px 0; padding: 9px 12px; font-weight: 500; }
.dash-side .dash-sub > summary:hover { background: var(--muted); }
.dash-side .dash-sub-links a { padding-left: 44px; font-size: .9rem; }
.dash-side .dash-sub-links a .nav-ic { display: none; }
.dash-side .dash-sub-links a.active { padding-left: 43px; }
.dash-side .count { background: var(--fg); }
.dash-side .dash-context { margin: 0 4px 12px; background: var(--card); }
.dash-user { margin: 18px 4px 0; }
@media (max-width: 860px) { .dash-side { padding: 14px 10px 30px; } }

/* The event strip as a switcher: the current event named, every other
   one a hover (or a tap, or focus) away — upcoming first, then past. */
.dash-context { position: relative; }
.dc-switch { padding-bottom: 8px; }
.dc-toggle { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none; border: 0; padding: 0; font: inherit; color: var(--fg); cursor: pointer; }
.dc-toggle strong { flex: 1 1 auto; min-width: 0; font-size: .9rem; line-height: 1.25; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.dc-toggle .chev { width: 7px; height: 7px; flex: 0 0 7px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(45deg); margin: -3px 4px 0 0; opacity: .55; }
.dc-menu { display: none; position: absolute; left: 0; right: 0; top: calc(100% - 4px); z-index: 80; background: var(--card); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow-lg); padding: 6px; max-height: 340px; overflow-y: auto; }
.dash-context:hover .dc-menu, .dash-context:focus-within .dc-menu, .dash-context.open .dc-menu { display: block; }
.dc-filter input { width: 100%; margin: 2px 0 6px; padding: 7px 10px; font-size: .86rem; border: 1px solid var(--border); border-radius: 8px; }
.dc-group { font-size: .66rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted-fg); font-weight: 700; padding: 8px 10px 3px; }
.dc-item { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 7px 10px; border-radius: 8px; font-size: .86rem; color: var(--fg); text-decoration: none; }
.dc-item:hover { background: var(--muted); text-decoration: none; }
.dc-item.on { background: var(--primary-50); font-weight: 600; }
.dc-item .n { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dc-item .d { flex: 0 0 auto; color: var(--muted-fg); font-size: .74rem; font-variant-numeric: tabular-nums; }
.dc-item.dc-all { justify-content: center; color: var(--primary-fg); border-top: 1px solid var(--border); border-radius: 0; margin-top: 4px; padding-top: 9px; }
@media (hover: none) { .dash-context:hover .dc-menu { display: none; } .dash-context.open .dc-menu { display: block; } }

/* The bar's event chip as a switcher, beside the toggle and outside
   the strip (which scrolls, and would clip it). Opens upward. */
.qa-evwrap { position: relative; display: inline-flex; align-items: center; flex: 0 0 auto; }
.qa-evwrap .qa-ev { padding-right: 8px; }
.qa-evtoggle { border: 0; background: none; color: var(--muted-fg); cursor: pointer; padding: 6px 8px 6px 2px; font: inherit; line-height: 1; border-radius: 999px; }
.qa-evtoggle .qa-chev { display: inline-block; transform: rotate(-90deg); font-size: 1.1em; }
.qa-evwrap.open .qa-evtoggle .qa-chev { transform: rotate(90deg); }
.qa-evmenu { display: none; position: absolute; left: 0; bottom: calc(100% + 10px); z-index: 60; width: min(340px, calc(100vw - 24px)); max-height: 320px; overflow-y: auto; background: var(--card); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-lg); padding: 6px; }
.qa-evwrap.has-menu:hover .qa-evmenu, .qa-evwrap:focus-within .qa-evmenu, .qa-evwrap.open .qa-evmenu { display: block; }
.qa-evwrap + .qa-list { border-left: 1px solid var(--border); margin-left: 2px; padding-left: 6px; }
.qa[data-open="0"] .qa-evwrap { display: none; }
@media (hover: none) { .qa-evwrap.has-menu:hover .qa-evmenu { display: none; } .qa-evwrap.open .qa-evmenu { display: block; } }
/* The menu strip's own filter box and menu, in both themes. */
.dc-filter input { background: var(--input-bg); color: var(--fg); }

/* In the menu strip the list is narrow: name over date, two lines at most. */
.dash-side .dc-item { flex-direction: column; align-items: flex-start; gap: 1px; }
.dash-side .dc-item .n { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-height: 1.25; }
.dash-side .dc-item.dc-all { align-items: center; }
.dash-side .dc-links a { color: var(--info); }

/* Hover you can travel through. The menus stay in the layout (visibility,
   not display) so they can fade, close a beat after the pointer leaves,
   and bridge the gap between the chip and the menu — the bar's opens
   10px above it, and the pointer used to fall through that gap. */
.dc-menu, .qa-evmenu { display: block; visibility: hidden; opacity: 0; pointer-events: none; transition: opacity .12s ease, visibility 0s linear .28s; }
.dash-context:hover .dc-menu, .dash-context:focus-within .dc-menu, .dash-context.open .dc-menu,
.qa-evwrap.has-menu:hover .qa-evmenu, .qa-evwrap:focus-within .qa-evmenu, .qa-evwrap.open .qa-evmenu { display: block; visibility: visible; opacity: 1; pointer-events: auto; transition-delay: 0s; }
.qa-evmenu::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 14px; }
.dc-menu::before { content: ''; position: absolute; left: 0; right: 0; bottom: 100%; height: 8px; }
@media (hover: none) {
  .dash-context:hover .dc-menu, .qa-evwrap.has-menu:hover .qa-evmenu { visibility: hidden; opacity: 0; pointer-events: none; }
  .dash-context.open .dc-menu, .qa-evwrap.open .qa-evmenu { visibility: visible; opacity: 1; pointer-events: auto; }
}
/* Invite to apply: the share strip, two ways in side by side, the list. */
.sp-share { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sp-share code { font-size: .84rem; padding: 6px 10px; background: var(--bg-alt); border: 1px solid var(--border); border-radius: 8px; word-break: break-all; }
.sp-pro-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 14px; }
@media (max-width: 760px) { .sp-pro-grid { grid-template-columns: 1fr; } }
.sp-pro-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 10px; }
.sp-st { display: inline-block; font-size: .74rem; font-weight: 600; padding: 3px 9px; border-radius: 999px; background: var(--muted); color: var(--muted-fg); white-space: nowrap; }
.sp-st.sent { background: var(--primary-50); color: var(--primary-fg); }
.sp-st.on { background: var(--success-fill); color: #fff; }
.fm-share { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
.fm-share code { font-size: .8rem; padding: 2px 6px; background: var(--bg-alt); border: 1px solid var(--border); border-radius: 6px; word-break: break-all; }

/* The speaker page design card carries two choices now. */
.sp-tpl { flex-wrap: wrap; row-gap: 8px; }
.sp-tpl-lbl { font-size: .78rem; font-weight: 600; color: var(--muted-fg); margin-left: 6px; white-space: nowrap; }

/* The event's own navigation: one strip of tabs under its name. */
.ev-tabs { display: flex; gap: 2px; margin: -4px 0 18px; border-bottom: 1px solid var(--border); overflow-x: auto; scrollbar-width: none; }
.ev-tabs::-webkit-scrollbar { display: none; }
.ev-tab { display: inline-flex; align-items: center; gap: 6px; padding: 9px 12px 10px; font-size: .86rem; font-weight: 600; color: var(--muted-fg); text-decoration: none; white-space: nowrap; border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: 8px 8px 0 0; }
.ev-tab .ni { color: var(--muted-fg); }
.ev-tab:hover { color: var(--fg); background: var(--bg-alt); text-decoration: none; }
.ev-tab.on { color: var(--fg); border-bottom-color: var(--primary); }
.ev-tab.on .ni { color: var(--primary-fg); }
/* The overview: what needs you, what is next. */
.ws-two { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 820px) { .ws-two { grid-template-columns: 1fr; } }
.ws-h { font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted-fg); margin: 0 0 8px; }
.ws-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ws-list a { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 10px; color: var(--fg); text-decoration: none; }
.ws-list a:hover { background: var(--bg-alt); text-decoration: none; }
.ws-list a > span:not(.ws-ic):not(.chev):not(.ws-tick) { flex: 1 1 auto; min-width: 0; color: var(--muted-fg); font-size: .86rem; }
.ws-list b { flex: 0 0 auto; font-weight: 700; }
.ws-list .ws-ic { flex: 0 0 28px; width: 28px; height: 28px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; background: var(--primary-50); color: var(--primary-fg); }
.ws-list .chev { flex: 0 0 auto; color: var(--muted-fg); font-size: 1.2em; line-height: 1; }
.ws-steps .ws-tick { flex: 0 0 24px; width: 24px; height: 24px; border-radius: 999px; border: 1.5px solid var(--border); display: inline-flex; align-items: center; justify-content: center; font-size: .8rem; color: var(--muted-fg); }
.ws-steps li.done .ws-tick { background: var(--success-fill); border-color: var(--success-fill); color: #fff; }
.ws-steps li.done b { color: var(--muted-fg); font-weight: 600; }

/* The strip says where the event's pages went. */
.dc-note { display: block; margin-top: 6px; font-size: .72rem; line-height: 1.35; color: var(--muted-fg); }

/* The home page desk: one card per question, shaped to the job. */
.desk { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px; margin: 0 0 18px; }
.desk .ws-list a > span:not(.ws-ic):not(.chev) { white-space: normal; }

/* =====================================================================
   The design language, 2026 — one system over every page.

   Built from the UX Pilot reference, which was itself drawn on this
   file's own tokens: the same red, the same aubergine, the same
   neutrals and Manrope. So nothing here repaints; it sets the shapes
   and the rhythm — the radius every control shares, the height every
   input shares, the tracked eyebrow every label shares — and applies
   them through the classes the pages already use. Overrides sit at the
   end of the file on purpose: the rules above stay as written, these
   win by order and by one extra level of specificity where the page
   carries its own <style>.

   Tokens first, then the ground, the shell, the page head, the
   components, the phone, the dark theme.
   ===================================================================== */

:root {
  --ink: var(--secondary);                 /* the dark panel and the pill  */
  --ink-2: #2c2038;                        /* a row on the dark panel      */
  --ink-fg: #f6f4f9;
  --ink-muted: #a9a3b6;
  --tint: rgba(var(--primary-rgb), .08);   /* the brand as a wash          */
  --tint-strong: rgba(var(--primary-rgb), .14);
  --glow: 0 10px 24px rgba(var(--primary-rgb), .28);
  --ring-soft: 0 0 0 3px rgba(var(--primary-rgb), .12);
  --radius-xl: 16px;
  --control-h: 44px;
  --control-h-sm: 34px;
  --track: .1em;
}
html[data-theme="dark"] {
  --ink: #24202c;
  --ink-2: #2f2a39;
  --ink-fg: #f3f1f6;
  --ink-muted: #b3adc0;
  --glow: 0 10px 24px rgba(var(--primary-rgb), .35);
}

/* ---- type utilities ------------------------------------------------- */
.eyebrow, .dash-side h5, .stat .label, .ws-h, .dc-label, .dc-group, table.data th, .es-head .es-state,
.section-label {
  font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--track);
  color: var(--muted-fg);
}
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .86em; }
:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 4px; }

/* ---- the ground: grey page, white surfaces ---------------------------- */
body.dash-shell { background: var(--bg-alt); }
body.dash-shell .dash-main { background: var(--bg-alt); padding: 26px 32px 90px; }
body.dash-shell .dash-side { background: var(--card); border-right: 1px solid var(--border); }
body.dash-shell .site-header { background: var(--card); backdrop-filter: none; }
@media (min-width: 861px) { .dash { grid-template-columns: 256px minmax(0, 1fr); } }

/* ---- the top bar inside the dashboard: a utility bar ------------------ */
body.dash-shell .site-header .nav { display: none; }
body.dash-shell .site-header .nav-toggle { display: none; }
.dash-topsearch { flex: 0 1 420px; margin-left: 8px; }
.dash-topsearch input {
  width: 100%; height: 40px; padding: 0 14px 0 40px; font: inherit; font-size: .86rem;
  border: 1px solid var(--border); border-radius: 999px; background: var(--bg-alt); color: var(--fg);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b6880' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='7'/><path d='m20 20-3.5-3.5'/></svg>");
  background-repeat: no-repeat; background-position: 14px 50%;
}
.dash-topsearch input:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-soft); background-color: var(--card); }
.hbtn {
  position: relative; width: 38px; height: 38px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--card); color: var(--fg); display: inline-grid; place-items: center; text-decoration: none;
}
.hbtn:hover { background: var(--bg-alt); text-decoration: none; }
.hbtn .ni { width: 17px; height: 17px; }
.hbtn .dot { position: absolute; top: 7px; right: 8px; width: 8px; height: 8px; border-radius: 50%; background: var(--primary); border: 2px solid var(--card); }
body.dash-shell .theme-toggle { border-radius: 999px; width: 38px; height: 38px; }
body.dash-shell .usermenu-btn { border-radius: 999px; }
@media (max-width: 900px) { .dash-topsearch { display: none; } }

/* ---- the sidebar ------------------------------------------------------ */
.dash-side { padding: 16px 12px 22px; }
@media (min-width: 861px) { .dash-side .dash-search { display: none; } }
.dash-side .dash-job { margin: 6px 12px 16px; padding-bottom: 10px; border-bottom: 1px solid var(--border); color: var(--primary-fg); }
.dash-side .dash-group:first-of-type > summary h5 { margin-top: 0; }
.dash-side .dash-group > summary h5 {
  margin: 18px 12px 6px; font-size: .66rem; text-transform: uppercase; letter-spacing: var(--track);
  font-weight: 700; color: var(--muted-fg);
}
.dash-side .dash-group:first-of-type > summary h5 { margin-top: 6px; }
.dash-side a { border-radius: 10px; padding: 9px 12px; font-size: .9rem; font-weight: 600; color: var(--fg); margin: 1px 0; }
.dash-side a:hover { background: var(--bg-alt); }
.dash-side a.active {
  background: var(--tint); border: 0; box-shadow: none; color: var(--primary-fg); font-weight: 700; padding: 9px 12px;
}
.dash-side a.active .nav-ic { color: var(--primary-fg); }
.dash-side .dash-sub > summary { border-radius: 10px; padding: 9px 12px; font-weight: 600; font-size: .9rem; }
.dash-side .dash-sub-links a { padding-left: 42px; font-size: .86rem; }
.dash-side .dash-sub-links a.active { padding-left: 42px; }
.dash-side .count { background: var(--primary); color: #fff; font-size: .68rem; }
/* the event chip */
.dash-side .dash-context {
  margin: 0 2px 12px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-alt);
}
.dash-context .dc-label { display: none; }
.dc-toggle { gap: 10px; align-items: center; }
.dc-ini {
  flex: 0 0 36px; width: 36px; height: 36px; border-radius: 9px; background: var(--ink); color: var(--ink-fg);
  display: inline-grid; place-items: center; font-size: .72rem; font-weight: 800; letter-spacing: .04em;
}
.dc-ini.none { background: transparent; border: 1.5px dashed var(--border); color: var(--muted-fg); }
.dc-toggle .dc-words { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dc-toggle strong { font-size: .84rem; line-height: 1.25; -webkit-line-clamp: 2; }
.dc-sub { font-size: .68rem; color: var(--muted-fg); }
.dash-context .dc-links { display: flex; gap: 8px; margin-top: 8px; font-size: .78rem; font-weight: 600; }
.dash-context .dc-links a { color: var(--primary-fg); }
.dc-note { margin-top: 8px; font-size: .7rem; color: var(--muted-fg); }
/* the account block */
.dash-user { margin: 16px 2px 0; padding: 12px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg-alt); }
.dash-user .avatar { background: var(--ink); color: var(--ink-fg); }

/* ---- the page head ---------------------------------------------------- */
body.dash-shell .dash-main .dash-crumbs {
  font-size: .66rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--track);
  color: var(--muted-fg); margin: 0 0 12px;
}
body.dash-shell .dash-main .dash-crumbs a { color: var(--muted-fg); }
body.dash-shell .dash-main .dash-crumbs span[aria-current] { color: var(--fg); }
body.dash-shell .dash-main .crumb-back { text-transform: none; letter-spacing: 0; font-size: .74rem; font-weight: 600; border-radius: 999px; padding: 3px 11px; }
body.dash-shell .dash-main .crumb-sep { opacity: .45; }
.dash-head { align-items: center; margin-bottom: 20px; }
.dash-head h1 { font-size: 1.75rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.15; margin: 0 0 4px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.dash-head p { font-size: .9rem; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.status-pill {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px;
  font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; vertical-align: middle;
  background: var(--muted); color: var(--muted-strong);
}
.status-pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.status-pill.live, .status-pill.approved, .status-pill.good { background: rgba(18,160,94,.12); color: var(--ok); }
.status-pill.pending, .status-pill.warn { background: rgba(208,135,0,.12); color: var(--warning-fg); }
.status-pill.draft { background: var(--muted); color: var(--muted-strong); }
.status-pill.rejected, .status-pill.bad { background: rgba(225,29,51,.1); color: var(--danger-fg); }

/* ---- tabs ------------------------------------------------------------- */
.ev-tabs { margin: 0 0 22px; gap: 0; }
.ev-tab { padding: 10px 14px 12px; font-size: .88rem; color: var(--muted-fg); border-bottom-width: 2px; }
.ev-tab.on { color: var(--primary-fg); }
.ev-tab small { font-weight: 600; font-size: .7rem; color: var(--muted-fg); margin-left: 4px; }
.tabs, .sp-tabs { gap: 0; }
.sp-tab, .tabs a, .tabs button {
  padding: 10px 14px 12px; font-size: .88rem; font-weight: 600; color: var(--muted-fg);
  border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px; background: none; border-radius: 0; text-decoration: none;
}
.sp-tab:hover, .tabs a:hover { color: var(--fg); text-decoration: none; }
.sp-tab.on, .tabs a.active, .tabs .active { color: var(--primary-fg); border-bottom-color: var(--primary); background: none; }

/* ---- buttons ---------------------------------------------------------- */
.btn { min-height: 40px; padding: 8px 18px; border-radius: 10px; font-weight: 700; font-size: .88rem; }
.btn-sm { min-height: var(--control-h-sm); padding: 5px 13px; font-size: .8rem; border-radius: 9px; }
.btn-lg { min-height: 48px; padding: 11px 24px; font-size: .93rem; border-radius: 12px; }
.btn-primary { box-shadow: 0 1px 2px rgba(var(--primary-rgb), .25); }
.btn-primary.btn-lg { box-shadow: var(--glow); }
.btn-primary:active { transform: scale(.985); }
body.dash-shell .btn-outline { background: var(--card); border-color: var(--border); }
body.dash-shell .btn-outline:hover { background: var(--bg-alt); border-color: var(--border); color: var(--fg); }
.btn-ghost:hover { background: var(--bg-alt); }
.btn-secondary, .btn-dark { background: var(--ink); color: var(--ink-fg); }
.btn-secondary:hover, .btn-dark:hover { background: var(--ink-2); color: var(--ink-fg); }
.btn-icon {
  width: 36px; height: 36px; min-height: 36px; padding: 0; border-radius: 10px; border: 1px solid var(--border);
  background: var(--card); color: var(--muted-fg); display: inline-grid; place-items: center;
}
.btn-icon:hover { color: var(--fg); background: var(--bg-alt); }
.btn-icon .ni { width: 16px; height: 16px; }
.row-actions { gap: 6px; }
.row-actions .btn-sm { min-height: 32px; padding: 4px 11px; }

/* ---- cards, stats, the dark panel ------------------------------------- */
.card { border-radius: 12px; box-shadow: var(--shadow-sm); }
.card-head { font-size: .95rem; font-weight: 700; padding: 16px 20px; }
.card-body { padding: 20px; }
.section-title { margin: 0 0 12px; }
.section-title h2 { font-size: 1.05rem; font-weight: 800; letter-spacing: -.01em; }
.stats { gap: 14px; }
.stat { border: 1px solid var(--border); border-left: 1px solid var(--border); border-radius: 12px; padding: 18px 20px; box-shadow: var(--shadow-sm); }
.stat.accent { border-color: var(--border); }
.stat.attention { border-color: rgba(208,135,0,.45); }
.stat.good { border-color: var(--border); }
.stat .label { margin-bottom: 8px; }
.stat .value { font-size: 1.75rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; font-variant-numeric: tabular-nums;
  background: none; box-shadow: none; border: 0; padding: 0; margin: 0; }
.stat .value.sm { font-size: 1.3rem; }
.stat .sub { font-size: .8rem; color: var(--muted-fg); margin-top: 6px; }
.trend { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; border-radius: 8px; font-size: .72rem; font-weight: 700; }
.trend.up { background: rgba(18,160,94,.12); color: var(--ok); }
.trend.flat { background: var(--muted); color: var(--muted-strong); }
.trend.down { background: rgba(225,29,51,.1); color: var(--danger-fg); }
.panel-ink { background: var(--ink); color: var(--ink-fg); border-color: transparent; border-radius: 16px; }
.panel-ink .ws-h { color: var(--ink-fg); display: flex; align-items: center; gap: 8px; }
.panel-ink .ws-h .cnt { margin-left: auto; background: var(--primary); color: #fff; font-size: .66rem; padding: 2px 8px; border-radius: 999px; }
.panel-ink .ws-list a { background: var(--ink-2); color: var(--ink-fg); border-radius: 10px; margin: 0 0 6px; padding: 10px 12px; }
.panel-ink .ws-list a:hover { background: rgba(255,255,255,.12); }
.panel-ink .ws-list a > span:not(.ws-ic):not(.chev):not(.ws-tick) { color: var(--ink-muted); }
.panel-ink .ws-list .ws-ic { background: var(--tint-strong); color: #ff8a94; }
.panel-ink .ws-list .chev { color: var(--ink-muted); }
.panel-ink .muted { color: var(--ink-muted) !important; }
.ws-two { gap: 16px; }
.ws-list a { padding: 10px 12px; }
.ws-list .ws-ic { width: 34px; height: 34px; flex-basis: 34px; border-radius: 9px; }

/* ---- tables ----------------------------------------------------------- */
.table-wrap { border-radius: 12px; box-shadow: var(--shadow-sm); }
table.data { font-size: .86rem; }
table.data th { padding: 12px 16px; font-size: .66rem; letter-spacing: var(--track); background: var(--bg-alt); }
table.data td { padding: 14px 16px; }
table.data tbody tr:hover td { background: color-mix(in srgb, var(--bg-alt) 60%, transparent); }
table.data tr.row-attn td { background: rgba(208,135,0,.06); }
table.data td strong { font-weight: 700; }
table.data .tiny.muted, table.data .small.muted { color: var(--muted-fg); }
.who { display: flex; align-items: center; gap: 12px; min-width: 0; }
.dash-user .who { display: block; }
.dash-user .who strong, .dash-user .who span { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.who .avatar, .who-avatar {
  width: 36px; height: 36px; border-radius: 50%; flex: 0 0 36px; object-fit: cover;
  background: var(--tint-strong); color: var(--primary-fg); display: inline-grid; place-items: center; font-size: .78rem; font-weight: 800;
}
.who b, .who strong { display: block; font-weight: 700; line-height: 1.2; }
.who span, .who .tiny { display: block; color: var(--muted-fg); font-size: .76rem; }
.status-word { display: inline-flex; align-items: center; gap: 7px; font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--muted-strong); white-space: nowrap; }
.status-word::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.status-word.good { color: var(--ok); }
.status-word.warn { color: var(--warning-fg); }
.status-word.bad { color: var(--danger-fg); }
.status-word.info { color: var(--info); }
.table-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 12px 16px; border-top: 1px solid var(--border); font-size: .8rem; color: var(--muted-fg); background: var(--card); }
.pagination { margin-top: 18px; gap: 4px; }
.pagination a, .pagination span { min-width: 36px; height: 36px; border-radius: 10px; font-size: .84rem; }

/* ---- chips and badges ------------------------------------------------- */
.chip, .chips .chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: 999px;
  font-size: .74rem; font-weight: 700; border: 1px solid var(--border); background: var(--card); color: var(--fg); text-decoration: none; white-space: nowrap;
}
.chip:hover { background: var(--bg-alt); text-decoration: none; }
.chip.on, .chip.active { background: var(--ink); color: var(--ink-fg); border-color: var(--ink); }
.chip.warn { border-color: rgba(208,135,0,.45); color: var(--warning-fg); background: rgba(208,135,0,.08); }
.chip.tint, .chip-primary { background: var(--tint); color: var(--primary-fg); border-color: transparent; }
.chip.quiet { background: var(--muted); border-color: transparent; color: var(--muted-strong); }
.chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.badge { font-size: .66rem; letter-spacing: .06em; padding: 3px 9px; border-radius: 6px; }
.badge-xs { font-size: .62rem; padding: 2px 7px; }
.badge-primary { background: var(--tint); color: var(--primary-fg); }

/* ---- filters ---------------------------------------------------------- */
.filters {
  background: var(--card); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-sm);
  padding: 14px 16px; gap: 10px; margin-bottom: 18px; align-items: flex-end;
}
.filters .field label { font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--track); color: var(--muted-fg); margin-bottom: 6px; }
.filters input, .filters select { min-height: 40px; border-radius: 10px; background: var(--bg-alt); }
.filters input:focus, .filters select:focus { background: var(--card); }
.filters .btn { min-height: 40px; }
.filters .hint { width: 100%; margin: 2px 0 0; }
.filter-tabs { display: flex; gap: 6px; flex-wrap: wrap; }

/* ---- forms ------------------------------------------------------------ */
.field label { font-size: .8rem; font-weight: 700; margin-bottom: 6px; }
.field input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]), .field select, .field textarea,
input[type="text"], input[type="email"], input[type="search"], input[type="url"], input[type="tel"], input[type="number"],
input[type="password"], input[type="date"], input[type="time"], select, textarea {
  border: 1px solid var(--border); border-radius: 10px; background: var(--card); color: var(--fg);
  padding: 10px 14px; font: inherit; font-size: .9rem; min-height: var(--control-h);
}
textarea { min-height: 96px; }
input[type="file"] { font-size: .86rem; }
.field input:focus, .field select:focus, .field textarea:focus,
input[type="text"]:focus, input[type="email"]:focus, input[type="search"]:focus, input[type="url"]:focus, input[type="tel"]:focus,
input[type="number"]:focus, input[type="password"]:focus, input[type="date"]:focus, input[type="time"]:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--primary); box-shadow: var(--ring-soft);
}
.field .hint, .hint { font-size: .78rem; color: var(--muted-fg); }
.field-error { font-size: .78rem; color: var(--danger-fg); margin-top: 4px; font-weight: 600; }
.input-error, .field .input-error { border-color: var(--danger) !important; }
.form-section { padding: 22px 0; border-top: 1px solid var(--border); }
.form-section:first-of-type { border-top: 0; padding-top: 4px; }
.form-section h3 { font-size: 1rem; font-weight: 800; letter-spacing: -.01em; margin: 0 0 14px; }
.form-row { gap: 16px; }
.check { font-size: .9rem; }
.check input[type="checkbox"], .check input[type="radio"] { accent-color: var(--primary); width: 16px; height: 16px; }
.sticky-actions { border-radius: 12px; box-shadow: var(--shadow-lg); border: 1px solid var(--border); }
.es-panel { border-radius: 12px; }
.es-head { padding: 14px 20px; }
.es-head h3 { font-weight: 800; }
.es-body { padding: 18px 20px; }
.es-foot { padding: 12px 20px; background: var(--bg-alt); border-top: 1px solid var(--border); }
.steps { gap: 6px; margin-bottom: 18px; }
.step { border-radius: 999px; font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }
.step.on { background: var(--primary-50) !important; color: var(--primary-fg); border-color: var(--primary); }
.step.done { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, var(--border)); }
.step.done { color: var(--ok); }

/* ---- dialogs, alerts, toasts, empty states ---------------------------- */
dialog, dialog.confirm { border: 1px solid var(--border); border-radius: 16px; padding: 0; background: var(--card); color: var(--fg); box-shadow: var(--shadow-lg); max-width: 520px; width: calc(100% - 32px); }
dialog::backdrop { background: rgba(16, 20, 24, .55); backdrop-filter: blur(3px); }
dialog .card-body { padding: 22px 24px; }
dialog h3, dialog h2 { margin: 0 0 8px; font-size: 1.1rem; font-weight: 800; }
.sheet-head { padding: 16px 20px; border-bottom: 1px solid var(--border); font-weight: 800; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.sheet-body { padding: 20px; }
.sheet-close { border: 1px solid var(--border); background: var(--card); border-radius: 999px; width: 34px; height: 34px; cursor: pointer; color: var(--muted-fg); }
.alert { border-radius: 12px; padding: 12px 16px; font-size: .88rem; display: flex; gap: 10px; align-items: flex-start; }
.toast { border-radius: 12px; background: var(--ink); color: var(--ink-fg); box-shadow: var(--shadow-lg); font-weight: 600; }
.empty { padding: 48px 20px; }
.empty .icon { width: 64px; height: 64px; margin: 0 auto 14px; border-radius: 50%; background: var(--bg-alt); display: grid; place-items: center; opacity: 1; font-size: 1.6rem; }
.empty .icon .ni { width: 26px; height: 26px; color: var(--muted-fg); }
.empty h3 { font-size: 1.05rem; font-weight: 800; margin: 0 0 6px; }
.empty p { max-width: 46ch; margin: 0 auto; font-size: .9rem; }

/* ---- the quick actions bar -------------------------------------------
   Left as it was: frosted glass, the label beside its icon. The dark
   stacked version the reference draws became a black slab under every
   page once nine actions and an event chip were in it. The one thing
   added is the search button at the end, in the bar's own idiom. */
.qa-search {
  flex: 0 0 auto; width: 36px; height: 36px; margin-left: 2px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--card); color: var(--muted-fg);
  display: inline-grid; place-items: center; text-decoration: none;
}
.qa-search:hover { color: var(--primary-fg); border-color: var(--primary); text-decoration: none; }
.qa-search .ni { width: 16px; height: 16px; }

/* ---- the phone --------------------------------------------------------- */
@media (max-width: 860px) {
  body.dash-shell .dash-main { padding: 14px 14px 100px; }
  .dash-head h1 { font-size: 1.4rem; }
  .stat .value { font-size: 1.45rem; }
  .card-body { padding: 16px; }
  .filters { padding: 12px; }
  .filters .field { min-width: 0; flex: 1 1 140px; }
  table.data th, table.data td { padding: 11px 12px; }
  .qa-item { min-width: 54px; }
  .dash-side { background: var(--card); }
}

/* ---- the dark theme: the same shapes on the other ground --------------- */
html[data-theme="dark"] body.dash-shell .site-header { border-bottom-color: var(--border); }
html[data-theme="dark"] .table-wrap, html[data-theme="dark"] .card, html[data-theme="dark"] .stat, html[data-theme="dark"] .filters { box-shadow: none; }
html[data-theme="dark"] .dash-topsearch input { background-color: var(--bg-alt); }
html[data-theme="dark"] .dc-ini { background: var(--primary); color: #fff; }
html[data-theme="dark"] .status-pill.live { background: rgba(52,211,153,.14); }

/* ---- pass two: rows you can read, the phone's head, the checkout and the door ---- */
.dash-head h1 { display: block; }
.dash-head h1 .status-pill { margin-left: 10px; position: relative; top: -3px; }
@media (max-width: 600px) {
  .dash-head h1 { font-size: 1.3rem; }
  body.dash-shell .dash-main .dash-crumbs a:not(.crumb-back), body.dash-shell .dash-main .dash-crumbs .crumb-sep { display: none; }
  body.dash-shell .dash-main .dash-crumbs span[aria-current] { display: none; }
}
table.data .who strong { font-size: .9rem; }
table.data td .tiny { font-size: .76rem; }
table.data code.tiny { background: var(--bg-alt); padding: 2px 6px; border-radius: 6px; font-size: .74rem; }
table.data .chip.ok { background: rgba(18,160,94,.12); color: var(--ok); border-color: transparent; }
table.data .chip.pending { background: rgba(208,135,0,.12); color: var(--warning-fg); border-color: transparent; }
table.data .badge-success { background: rgba(18,160,94,.12); color: var(--ok); }
table.data .badge-warning { background: rgba(208,135,0,.12); color: var(--warning-fg); }
table.data .badge-danger { background: rgba(225,29,51,.1); color: var(--danger-fg); }
.badge-success { background: rgba(18,160,94,.12); color: var(--ok); }
.badge-warning { background: rgba(208,135,0,.12); color: var(--warning-fg); }
.badge-danger { background: rgba(225,29,51,.1); color: var(--danger-fg); }
/* the checkout: eyebrow steps, each attendee a card, the photograph a frame */
.checkout h1 { font-size: 1.6rem; font-weight: 800; letter-spacing: -.02em; }
.checkout .card { border-radius: 16px; }
.checkout .card-head { font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--track); color: var(--muted-fg); padding: 14px 20px 12px; border-bottom: 0; }
.checkout .card-head + .card-body { padding-top: 4px; }
.checkout .att-block { border: 1px solid var(--border); border-radius: 16px; padding: 18px 18px 12px; margin: 0 0 14px; background: var(--card); position: relative; }
.checkout .att-block legend { font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--track); color: var(--primary-fg); padding: 0 6px; }
.checkout .att-block .field input:not([type="file"]):not([type="checkbox"]):not([type="radio"]), .checkout .att-block .field select { background: var(--bg-alt); }
.checkout .att-block .field input:focus, .checkout .att-block .field select:focus { background: var(--card); }
.checkout .att-block input[type="file"] {
  display: block; width: 100%; box-sizing: border-box; padding: 28px 16px; text-align: center; cursor: pointer;
  border: 2px dashed var(--primary); border-radius: 12px; background: var(--bg-alt); color: var(--muted-fg); font-size: .86rem;
}
.checkout .att-block input[type="file"]::file-selector-button {
  display: block; margin: 0 auto 8px; padding: 9px 16px; border: 0; border-radius: 999px; background: var(--card); color: var(--primary-fg);
  font: inherit; font-weight: 700; font-size: .84rem; box-shadow: var(--shadow-sm); cursor: pointer;
}
.checkout .att-remove { border-radius: 999px; }
.checkout .sum-line { padding: 10px 0; border-bottom: 1px solid var(--border); }
.checkout .btn-primary.btn-block, .checkout .btn-primary.btn-lg { text-transform: uppercase; letter-spacing: .08em; font-size: .86rem; box-shadow: var(--glow); }
.checkout .sidebar-sticky { border-radius: 16px; }
@media (max-width: 760px) { .checkout .att-block { padding: 14px 14px 8px; } }

/* ---- pass two, continued: the pill's true height, the phone's head ---- */
@media (max-width: 600px) {
  .hbtn.help { display: none; }
  .usermenu-btn .nowrap { display: none; }
  .usermenu-btn { padding: 4px; }
  .site-header .inner { gap: 10px; }
  .header-actions { gap: 6px; }
}

/* ---- pass two, the last of it: ticket rows on a phone, the hero's scrim ---- */
@media (max-width: 600px) {
  .checkout .flex.justify-between.items-center.wrap > div:first-child { flex: 1 1 100%; min-width: 0; }
  .checkout .flex.justify-between.items-center.wrap > .text-right { flex: 1 1 auto; text-align: left; }
  .checkout .flex.justify-between.items-center.wrap > div[style*="width:96px"] { width: 120px !important; }
  .checkout .btn-primary.btn-lg { width: 100%; }
}
.event-hero::after, .hero-event::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(10, 8, 20, .78) 0%, rgba(10, 8, 20, .45) 55%, rgba(10, 8, 20, .25) 100%); }
.event-hero > *, .hero-event > * { position: relative; z-index: 1; }
.event-hero, .hero-event { position: relative; }

/* ---- pass three: the export's own components, shared -----------------
   Filter pills, the table's footer, the attention row, the meta line.
   Each restyles something the pages already render, so a page gets the
   look by being what it already was rather than by being edited. */

/* Filter pills — the roster's "All (4,820) · VIP (120) · Needs Review (12)".
   The pages call them .tab; some mark the current one .on and some
   .active, which is why both are written here. */
.tabs { gap: 6px; border-bottom: 0; }
.tab {
  padding: 7px 14px; border-radius: 999px; font-size: .8rem; font-weight: 700;
  border: 1px solid var(--border); background: var(--card); color: var(--fg); white-space: nowrap;
}
.tab:hover { background: var(--bg-alt); border-color: var(--border); color: var(--fg); text-decoration: none; }
.tab.on, .tab.active { background: var(--ink); border-color: var(--ink); color: var(--ink-fg); }
.tab.on:hover, .tab.active:hover { background: var(--ink-2); color: var(--ink-fg); }
.tab.warn { border-color: rgba(208,135,0,.5); color: var(--warning-fg); background: rgba(208,135,0,.07); }
.tab.warn.on, .tab.warn.active { background: var(--warning-fill); border-color: var(--warning-fill); color: #fff; }
.tab .count { background: transparent; color: inherit; opacity: .7; font-weight: 600; padding: 0; }

/* The table's footer: what you are looking at, and how to see more. */
.table-wrap + .pagination {
  margin: -1px 0 20px; padding: 12px 16px; gap: 4px;
  border: 1px solid var(--border); border-top: 0; border-radius: 0 0 12px 12px;
  background: var(--card); justify-content: flex-end;
}
.table-wrap:has(+ .pagination) { border-radius: 12px 12px 0 0; }
.list-note { font-size: .78rem; color: var(--muted-fg); font-weight: 600; }

/* A row that wants something done. The reference tints the whole row
   amber; the word in the cell says why. */
table.data tr.row-attn td { background: rgba(208,135,0,.07); }
table.data tr.row-attn:hover td { background: rgba(208,135,0,.11); }
table.data tr.row-attn td:first-child { box-shadow: inset 3px 0 0 var(--warning); }

/* The line under a page's title: pin, date, the facts of the event. */
.dash-head .meta { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; color: var(--muted-fg); font-size: .9rem; margin: 0; }
.dash-head .meta span { display: inline-flex; align-items: center; gap: 6px; }
.dash-head .meta .ni { width: 15px; height: 15px; color: var(--primary-fg); flex: none; }

/* ---- pass three, the last two inconsistencies ------------------------
   A secondary action in a page head was a ghost button, which on the
   grey ground reads as a sentence rather than a control. And the
   newsletter tiles put the number above its label while every other
   tile in the product does the opposite — the order is in their
   markup, so the fix is a flex order, not an edit. */
.dash-head .btn-ghost, .dash-head .btn-secondary {
  background: var(--card); color: var(--fg); border: 1px solid var(--border);
}
.dash-head .btn-ghost:hover, .dash-head .btn-secondary:hover { background: var(--bg-alt); color: var(--fg); border-color: var(--border); }
.nl-tile { display: flex; flex-direction: column; gap: 2px; }
.nl-tile .l { order: -1; font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--track); color: var(--muted-fg); }
.nl-tile .v { font-size: 1.75rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.nl-tile .n { font-size: .8rem; color: var(--muted-fg); }

/* ---- live search -----------------------------------------------------
   The panel under a search box. It is a peer of the box, not a page: it
   holds a handful of rows, says which kind each is, and gets out of the
   way. Everything it draws comes from the same finders the search page
   uses, so it can be trusted to be the truth rather than a preview. */
.ls-wrap { position: relative; display: block; }
.dash-topsearch .ls-wrap, .es-search .ls-wrap { width: 100%; }
.ls-panel {
  position: absolute; z-index: 90; top: calc(100% + 8px); left: 0; right: 0;
  min-width: 300px; max-height: min(66vh, 460px); overflow-y: auto; padding: 6px;
  background: var(--card); border: 1px solid var(--border); border-radius: 14px; box-shadow: var(--shadow-lg);
}
.ls-group {
  display: flex; align-items: center; gap: 7px; padding: 10px 10px 4px;
  font-size: .66rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--track); color: var(--muted-fg);
}
.ls-group .ni { width: 14px; height: 14px; color: var(--primary-fg); }
.ls-hit {
  display: block; padding: 8px 10px; border-radius: 10px; text-decoration: none; color: var(--fg);
}
.ls-hit:hover, .ls-hit.on { background: var(--bg-alt); text-decoration: none; }
.ls-hit.on { box-shadow: inset 0 0 0 1px var(--border); }
.ls-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ls-line b { font-weight: 700; font-size: .88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ls-meta { display: block; font-size: .76rem; color: var(--muted-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ls-all {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 4px; padding: 10px; border-top: 1px solid var(--border);
  font-size: .82rem; font-weight: 700; color: var(--primary-fg); text-decoration: none;
}
.ls-all:hover, .ls-all.on { background: var(--bg-alt); text-decoration: none; }
.ls-key {
  font-size: .66rem; font-weight: 700; color: var(--muted-fg);
  border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 6px; padding: 1px 6px;
}
.ls-none { padding: 16px 12px; }
.ls-none b { display: block; font-size: .88rem; margin-bottom: 3px; }
.ls-none span { font-size: .8rem; color: var(--muted-fg); }
/* The box says it is working, without moving anything. */
.ls-wrap.is-busy::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: -1px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--primary), transparent);
  background-size: 40% 100%; background-repeat: no-repeat;
  animation: ls-scan 1s linear infinite;
}
@keyframes ls-scan { from { background-position: -40% 0; } to { background-position: 140% 0; } }
@media (prefers-reduced-motion: reduce) { .ls-wrap.is-busy::after { animation: none; background: var(--primary); opacity: .5; } }
/* A list swapping its rows: dimmed, never collapsed — the page must not
   jump under the hand that is typing. */
[aria-busy="true"] .table-wrap, [aria-busy="true"] > .card, .is-live-loading .table-wrap { opacity: .55; transition: opacity .12s ease; }
@media (max-width: 640px) {
  .ls-panel { position: fixed; left: 8px; right: 8px; top: 64px; max-height: 70vh; }
}

/* ---- what the contrast audit measured -------------------------------
   Four things below AA across 89 screens, each fixed at its token
   rather than at the page that happened to show it.

   Destructive buttons: white on --destructive measured 4.12, and the
   token file already carries --danger at 4.75 for exactly this.
   Muted text on the muted ground: 4.21 -- which is why --muted-strong
   exists; these are the places that sit on it.
   A green word on a green tint: --ok is tuned for a card and reads 4.32
   on its own wash, so the word is carried a step deeper. */
.btn-danger { background: var(--danger); }
.btn-danger:hover { background: var(--danger); filter: brightness(.92); }
.cat-count { color: var(--muted-strong); }
.event-card .thumb span, .thumb span.muted, .qr-box .muted, .qr-box .tiny { color: var(--muted-strong); }
/* A green word on a green tint needs opposite treatment per theme: on
   white the hue must go deeper to reach AA, on near-black it must come
   up. One token, two values, so no rule has to know which theme it is
   in — the first draft hard-coded the light answer and measured 2.88
   in the dark. */
:root { --good-on-tint: #0f7746; }   /* 4.62 on the green tint, 5.60 on a card */
html[data-theme="dark"] { --good-on-tint: var(--ok); }
.status-pill.live, .status-pill.approved, .status-pill.good { color: var(--good-on-tint); }
.badge-success, table.data .badge-success { color: var(--good-on-tint); }
.status-word.good { color: var(--good-on-tint); }
/* The role switch's track is --muted, so its unchosen side needs the
   grey that was measured against that ground rather than against a card. */
.role-switch label { color: var(--muted-strong); }

/* ---- speaker pools: chips that filter, controls that assign ---------- */
.sp-chips { margin: 6px 0 2px; }
.sp-chips .chip { font-size: .7rem; padding: 3px 9px; }
.sp-assign { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0 2px; }
.sp-assign form { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.sp-assign select { min-height: 34px; font-size: .8rem; max-width: 280px; border-radius: 9px; }
.sp-pool-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.sp-pool-row:last-child { border-bottom: 0; }

/* The people who said they could speak on this session's topic. */
.sp-suggest { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; padding: 12px; border: 1px dashed var(--border); border-radius: 12px; background: var(--bg-alt); }
.sp-suggest .eyebrow { width: 100%; }
.sp-sugg { display: inline-flex; align-items: center; gap: 8px; padding: 6px 8px 6px 12px; background: var(--card); border: 1px solid var(--border); border-radius: 999px; }
.sp-sugg b { font-size: .84rem; }
/* ---------------------------------------------------------------
   A name is a way through
   Every person in a table opens onto everything on file for them.
   The link wears the row's own ink so a column of names does not
   turn into a wall of blue; it shows its nature on hover and on
   keyboard focus, where a plain colour change would not be enough.
   --------------------------------------------------------------- */
.person-link { color: inherit; text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, currentColor 30%, transparent); }
.person-link:hover { color: var(--primary); border-bottom-color: currentColor; }
.person-link:focus-visible { color: var(--primary); border-bottom-color: currentColor;
  outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 2px; }

/* One speaker's diary: when they are on, where, and the way off. */
.sp-sess { list-style: none; margin: 0 0 12px; padding: 0;
  display: flex; flex-direction: column; gap: 6px; }
.sp-sess-row { display: flex; gap: 12px; align-items: flex-start;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-alt); }
.sp-sess-when { flex: 0 0 108px; display: flex; flex-direction: column; line-height: 1.3; }
.sp-sess-when b { font-size: .8rem; }
.sp-sess-when span { font-size: .78rem; color: var(--muted-fg);
  font-variant-numeric: tabular-nums; }
.sp-sess-what { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px;
  min-width: 0; }
.sp-sess-what > a { font-weight: 600; font-size: .88rem; color: var(--fg);
  text-decoration: none; }
.sp-sess-what > a:hover { color: var(--primary); text-decoration: underline; }
.sp-sess-meta { font-size: .78rem; color: var(--muted-fg); }
.sp-sess-off { flex: 0 0 auto; }
/* The amber the audit blessed elsewhere on the sheet: a wash behind
   the warning ink, never the solid fill, which is built for white. */
.sp-clash { font-size: .76rem; color: var(--warning-fg);
  background: rgba(208,135,0,.08); border: 1px solid rgba(208,135,0,.45);
  border-radius: 999px; padding: 1px 8px; align-self: flex-start; }
.sp-sess-row.is-clash { border-color: var(--warning); }
@media (max-width: 640px) {
  .sp-sess-row { flex-wrap: wrap; }
  .sp-sess-when { flex-basis: 100%; flex-direction: row; gap: 6px; }
}

/* The brand on its own wash reads a shade thin in the dark theme —
   4.37 where 4.5 is needed — because --primary-fg is measured against
   the page, not against a tint laid over a card. Lifted per theme, the
   way --good-on-tint is, so it holds for any account's brand colour
   rather than one hex that happens to pass. */
:root { --brand-on-tint: var(--primary-fg); }
html[data-theme="dark"] { --brand-on-tint: color-mix(in srgb, var(--primary-fg) 76%, #ffffff); }
.chip.tint, .chip-primary { color: var(--brand-on-tint); }
/* The initials in a row's avatar sit on the strong wash and were
   a shade under in the dark theme for the same reason. */
.who .avatar, .who-avatar { color: var(--brand-on-tint); }

/* ---------------------------------------------------------------
   Invoice & receipt design
   A form nobody can judge without seeing the result, so the result
   sits beside it and follows what is typed. The preview sticks while
   the form scrolls; below the fold of a laptop it stacks, preview
   first, because the picture is what the page is for.
   --------------------------------------------------------------- */
.bill-wrap { display: grid; grid-template-columns: minmax(0, 1fr) minmax(380px, 42%);
  gap: 18px; align-items: start; }
.bill-side { position: sticky; top: 84px; }
.bill-frame { width: 100%; height: 74vh; min-height: 460px; border: 1px solid var(--border);
  border-radius: 8px; background: #f2f3f7; display: block; margin: 10px 0 8px; }
.bill-kinds { margin: 0 0 4px; }
.bill-kinds .tab { cursor: pointer; }
.bill-kinds .tab input { position: absolute; opacity: 0; pointer-events: none; }
.bill-colour { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.bill-colour input[type="color"] { width: 56px; height: 34px; padding: 2px; cursor: pointer;
  border: 1px solid var(--border); border-radius: 8px; background: var(--card); }
.bill-colour input[type="color"]:disabled { opacity: .45; cursor: not-allowed; }
.bill-check { display: inline-flex; align-items: center; gap: 7px; font-size: .84rem;
  color: var(--muted-fg); font-weight: 500; margin: 0; }
.bill-save { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding-bottom: 8px; }
.bill-signpost { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; }
@media (max-width: 1100px) {
  .bill-wrap { grid-template-columns: 1fr; }
  .bill-side { position: static; order: -1; }
  .bill-frame { height: 60vh; }
}

/* ---------------------------------------------------------------
   The rail is its own plane
   The sidebar sat on --card over a ground of --bg-alt, two greys
   about two per cent apart: in the light theme you had to look for
   the join, and in the dark theme there was nothing to find. A
   dashboard reads as navigation-then-work only if the navigation
   is visibly a different surface, so the ground goes deeper and the
   rail stays raised — the same relationship in both themes, rather
   than a light-theme idea that dies in the dark.

   Cards keep --card and now sit on that deeper ground too, which is
   what makes a page of cards read as a page of cards.
   --------------------------------------------------------------- */
:root {
  --shell: #f1f1f6;        /* the ground the dashboard sits on   */
  --side: #ffffff;         /* the rail, and the bar above it     */
  --side-line: #e5e4ee;
}
html[data-theme="dark"] {
  --shell: #0c0c11;
  --side: #17171f;
  --side-line: #24242e;
}

body.dash-shell { background: var(--shell); }
body.dash-shell .dash-main { background: var(--shell); }
body.dash-shell .dash-side {
  background: var(--side); border-right: 1px solid var(--side-line);
}
body.dash-shell .site-header {
  background: var(--side); border-bottom: 1px solid var(--side-line);
}
/* A raised surface needs its own ink: the rail is lighter than the
   ground it sits on, so the muted grey that measured 4.60 against the
   old sidebar measured 4.46 against this one. Lifted here rather than
   globally, because everywhere else it still passes. */
html[data-theme="dark"] body.dash-shell .dash-side,
html[data-theme="dark"] body.dash-shell .site-header {
  --muted-fg: color-mix(in srgb, var(--fg) 74%, var(--side));
}
/* Text on the muted fill takes the ink measured against it: --muted-fg
   is built for a card and lands at 4.21 on --muted. The placeholder in
   an image well was the one place still asking for the wrong one. */
.img-preview span { color: var(--muted-strong); }

/* ---------------------------------------------------------------
   Modules: what this installation uses
   A switch, not a list of features — so the on state is legible at a
   glance across the whole grid rather than needing to be read one
   checkbox at a time.
   --------------------------------------------------------------- */
.mod-intro { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 18px; flex-wrap: wrap; }
.mod-count { font-size: .84rem; color: var(--muted-fg); background: var(--bg-alt);
  border: 1px solid var(--border); border-radius: 999px; padding: 5px 14px; white-space: nowrap; }
.mod-count b { color: var(--fg); font-size: 1rem; }
.mod-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); }
.mod { display: flex; gap: 11px; align-items: flex-start; padding: 12px 14px; margin: 0;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-alt);
  cursor: pointer; transition: border-color .12s ease, background .12s ease; }
.mod:hover { border-color: var(--primary); }
.mod.is-on { background: var(--card); border-color: var(--border); }
.mod.is-core { cursor: default; opacity: .8; }
.mod.is-core:hover { border-color: var(--border); }
.mod input[type="checkbox"] { margin-top: 2px; flex: 0 0 auto; }
.mod-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mod-body b { font-size: .92rem; }
.mod-body span { font-size: .8rem; color: var(--muted-fg); line-height: 1.45; }
.mod-note { font-size: .74rem !important; color: var(--muted-strong) !important; }
.mod-save { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding-bottom: 10px; }

/* The email as it goes out now, above the boxes that change it. */
.au-now { border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-alt); padding: 12px 14px; margin-top: 4px; }
.au-now-head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-bottom: 10px; }
.au-now-head b { font-size: .95rem; display: block; margin-top: 2px; }
.au-now-body { width: 100%; height: 260px; border: 1px solid var(--border);
  border-radius: 8px; background: #fff; display: block; margin-bottom: 8px; }
@media (max-width: 640px) { .au-now-body { height: 200px; } }

/* ---------------------------------------------------------------
   What a description may now contain
   The editor and the published page have to agree: anything that
   looks one way while being written and another way once saved is a
   trap. These rules are written once and applied to both surfaces.
   --------------------------------------------------------------- */
.rt-area h2, .prose h2 { font-weight: 700; }
.rt-area h2 { font-size: 1.25rem; margin: 1.1em 0 .4em; }
.rt-area h3 { font-size: 1.08rem; margin: 1em 0 .35em; font-weight: 700; }
.rt-area p { margin: 0 0 .7em; }

/* Alignment is a class, because the sanitiser keeps classes from a
   list of four and does not keep style attributes at all. */
.rt-area .ta-left,   .prose .ta-left   { text-align: left; }
.rt-area .ta-center, .prose .ta-center { text-align: center; }
.rt-area .ta-right,  .prose .ta-right  { text-align: right; }

/* A picture or a video, and the line under it. */
.rt-figure { margin: 1.2em 0; }
.rt-figure img { max-width: 100%; height: auto; border-radius: 10px; display: block; }
.rt-figure iframe { width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0;
  border-radius: 10px; display: block; background: #000; }
.rt-figure figcaption { font-size: .82rem; color: var(--muted-fg); margin-top: 6px; }
.rt-area .rt-figure { border: 1px dashed var(--border); border-radius: 12px; padding: 8px; }
.rt-area .rt-figure figcaption { outline: none; }

/* The toolbar grew: block format, alignment, media. The active state
   is what tells somebody they are inside a quote — which is the whole
   reason people could not get out of one. */
.rt-bar button.on { background: var(--tint); border-color: var(--border);
  color: var(--brand-on-tint); font-weight: 700; }
.rt-bar.rt-busy { opacity: .6; pointer-events: none; }

/* ---------------------------------------------------------------
   Somewhere to go from an event page
   An event page was a cul-de-sac: people arrive on one from a link
   in a message, read it, and have nowhere to go but back out of the
   site. A way back to the listing at the top, and at the foot the
   events somebody here would also go to -- each saying why it is
   there, because an unexplained recommendation is just a guess with
   confidence.
   --------------------------------------------------------------- */
.ev-back { margin: 0 0 14px; font-size: .9rem; }
.ev-back a { color: var(--muted-fg); text-decoration: none; }
.ev-back a:hover { color: var(--brand-on-tint); text-decoration: underline; }

.ev-near { margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--border); }
.ev-near-head { display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; margin-bottom: 14px; }
.ev-near-head h2 { font-size: 1.1rem; margin: 0; }
.ev-near-head a { color: var(--muted-fg); text-decoration: none; }
.ev-near-head a:hover { color: var(--brand-on-tint); text-decoration: underline; }

.ev-near-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.ev-near-card { display: flex; flex-direction: column; gap: 4px;
  border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px;
  background: var(--bg-alt); text-decoration: none; color: inherit;
  transition: border-color .15s ease, background .15s ease; }
.ev-near-card:hover { border-color: var(--primary); background: var(--tint); }
.ev-near-card strong { font-size: 1rem; line-height: 1.3; }
.ev-near-when { font-size: .85rem; color: var(--muted-fg); }

/* Why this one is here, said in three words. */
.ev-near-why { align-self: flex-start; font-size: .72rem; font-weight: 700;
  letter-spacing: .03em; text-transform: uppercase; color: var(--brand-on-tint);
  background: var(--tint); border-radius: 999px; padding: 3px 9px; }

@media (max-width: 860px) { .ev-near-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .ev-near-grid { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------
   The bar with something in it
   Choosing tickets and closing the sheet left no trace on the page:
   the selection existed only inside a panel nobody could see, which
   is why a page with a chooser on it still felt like it had no cart.
   The bar now carries the basket, with a way back in to change it.
   --------------------------------------------------------------- */
.tdock-cart { display: flex; align-items: center; gap: 10px; min-width: 0; }
.tdock-cart-sum { font-size: 1.06rem; font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tdock-edit { border: 1px solid var(--border); background: transparent;
  border-radius: 999px; padding: 3px 12px; font: inherit; font-size: .78rem;
  color: var(--muted-fg); cursor: pointer; min-height: 30px; flex: 0 0 auto; }
.tdock-edit:hover { border-color: var(--primary); color: var(--brand-on-tint); }

/* The count rides on the button, where a basket is looked for. */
.tdock-count { display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 22px; margin-left: 8px; padding: 0 6px;
  border-radius: 999px; background: rgba(255, 255, 255, .22);
  font-size: .8rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.tdock.has-picks .tdock-go { font-weight: 700; }

/* The bar asked for a 44px target back when it was written, and lost
   it: the design-system .btn rule further down the file sets 40px at
   the same specificity, so the later one won. This is the one button
   on the page a thumb aims at while scrolling, so it gets its size
   back — said with two classes so it stays won. */
.tdock .tdock-go { min-height: 44px; }

/* ---------------------------------------------------------------
   The basket, on every page
   A selection used to live inside one panel on one event page and
   vanish the moment anybody navigated. It is now kept, and the
   header carries it everywhere so there is always a way back to
   finish. Hidden until there is something in it.
   --------------------------------------------------------------- */
.cart-btn { position: relative; }
.cart-btn[hidden] { display: none; }
.cart-badge {
  position: absolute; top: -4px; right: -5px;
  min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--primary); color: #fff;
  font-size: .68rem; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
  border: 2px solid var(--bg);
}

/* The basket page. One card per event, because one order is one
   event: separate payment, separate tickets, separate receipt. */
.cart-basket-head a { text-decoration: none; }
.cart-lines { width: 100%; margin-bottom: 10px; }
.cart-lines td { vertical-align: middle; padding: 10px 8px; }
.cart-lines td:first-child { padding-left: 0; }
.cart-lines tr + tr td { border-top: 1px solid var(--border); }
.cart-qty { width: 92px; }
.cart-qty input { width: 100%; text-align: center; min-height: 40px;
  font-variant-numeric: tabular-nums; }
.cart-line-total { text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; padding-right: 0 !important; }

.cart-foot { display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; padding-top: 12px;
  border-top: 1px solid var(--border); }
.cart-total strong { font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.cart-actions { display: flex; gap: 8px; }
.cart-go { margin-top: 14px; }

@media (max-width: 520px) {
  .cart-foot { flex-direction: column; align-items: stretch; }
  .cart-actions { justify-content: flex-end; }
}

/* ---------------------------------------------------------------
   The feed leads with when, then where
   The question this page exists to answer is "what can I get to,
   and when" -- and both halves of it were buried: time inside a
   dropdown of three, place nowhere at all. They are a row of chips
   now, carrying their counts, so the answer is legible before
   anybody clicks anything.
   --------------------------------------------------------------- */
.es-when, .es-where { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.es-when { padding-top: 12px; }
.es-where { padding: 8px 0 2px; }

.es-chip { display: inline-flex; align-items: center; gap: 7px;
  min-height: 34px; padding: 4px 13px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg);
  color: var(--fg); text-decoration: none;
  font-size: .84rem; font-weight: 600; white-space: nowrap;
  transition: border-color .15s ease, background .15s ease, color .15s ease; }
.es-chip:hover { border-color: var(--primary); background: var(--tint); }
.es-chip.on { background: var(--primary); border-color: var(--primary); color: #fff; }

/* The count is the whole point of the row: it says whether the window
   is worth opening before anybody opens it. */
.es-chip-n { font-size: .74rem; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--muted-fg); }
.es-chip.on .es-chip-n { color: rgba(255, 255, 255, .82); }

/* Nothing in it. Still clickable — a person may want to see for
   themselves — but it stops advertising. */
.es-chip.is-empty { opacity: .5; }
.es-chip.is-empty:hover { opacity: 1; }

.es-near { border-style: dashed; }
/* width:auto and no growing: a bare select in this codebase is a
   full-width form control, and left alone it took the whole row and
   pushed itself onto a line of its own. */
.es-place { min-height: 34px; border-radius: 999px; padding: 4px 12px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  font-size: .84rem; font-weight: 600;
  flex: 0 0 auto; width: auto; max-width: 100%; }

@media (max-width: 600px) {
  /* One line that scrolls, rather than four lines of wrapped chips
     pushing the results off the screen. */
  .es-when { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px;
    scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .es-when::-webkit-scrollbar { display: none; }
  .es-chip { flex: 0 0 auto; }
}

/* ---------------------------------------------------------------
   The filter bar, made to fit
   It had grown to four rows -- windows, place, search, refinements
   -- and measured 424px on a 375px phone: the first event card
   started at y=721 on an 812px screen, so a page for finding events
   showed no event at all until you scrolled. 279px on a 1280px
   desktop, where it pushed the first row of cards three quarters of
   the way down.
   Two lines now, and one button for the rest. The two that stay are
   the two that earn it: the search, which is why anybody opens this
   page, and the windows with their counts, which answer "what is on
   soon" without a click.
   --------------------------------------------------------------- */
.events-dock .es-search { margin-bottom: 8px; }
.es-search input[type="search"] { padding: 11px 16px; font-size: .95rem; }

/* One scrolling line rather than four wrapped rows. */
.es-line { display: flex; align-items: center; gap: 8px;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.es-line::-webkit-scrollbar { display: none; }
.es-line .es-when, .es-line .es-where { flex: 0 0 auto; flex-wrap: nowrap;
  padding: 0; overflow: visible; }
.es-line .es-chip { flex: 0 0 auto; }

/* The one button. <details> gives the open/close and the keyboard
   for free; this only has to stop it looking like a disclosure
   triangle in a form. */
.es-more { position: relative; flex: 0 0 auto; }
.es-more > summary { list-style: none; cursor: pointer; }
.es-more > summary::-webkit-details-marker { display: none; }
.es-more-btn { display: inline-flex; align-items: center; gap: 7px;
  min-height: 44px; padding: 0 16px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--card);
  font-size: .9rem; font-weight: 700; white-space: nowrap;
  transition: border-color .15s ease, background .15s ease; }
.es-more-btn:hover { border-color: var(--primary); }
.es-more[open] .es-more-btn { border-color: var(--primary); background: var(--tint); }

/* How many refinements are folded away. Without it, a filtered list
   looks like an unfiltered one that is simply missing things. */
.es-more-n { display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
  background: var(--primary); color: #fff; font-size: .72rem; font-weight: 700;
  font-variant-numeric: tabular-nums; }

.es-more-panel { position: absolute; right: 0; top: calc(100% + 8px);
  z-index: 40; width: 300px; max-width: min(300px, calc(100vw - 32px));
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--card);
  box-shadow: 0 18px 40px -20px rgb(0 0 0 / 45%); }
.es-more-row { display: flex; flex-direction: column; gap: 4px; }
.es-more-row label { font-size: .76rem; font-weight: 700; color: var(--muted-fg);
  letter-spacing: .03em; text-transform: uppercase; }
.es-more-row select { width: 100%; min-height: 40px; }
.es-more-panel .es-place { width: 100%; border-radius: 10px; }

@media (max-width: 600px) {
  /* The search box was a stacked pair -- field above a full-width
     button -- which is 108px for one control. The button goes: Enter
     submits, and with scripting on the list applies as you type. */
  .events-dock .es-search { flex-direction: row; flex-wrap: nowrap; gap: 8px; }
  .es-search .es-go { display: none; }
  .es-more-btn { padding: 0 12px; }

  /* A sheet at the bottom, where a thumb is, rather than a panel
     hanging off a button at the top of the screen. */
  .es-more-panel { position: fixed; inset: auto 10px 10px 10px;
    width: auto; max-width: none; max-height: 70vh; overflow-y: auto; }
}
