/* ============================================================================
   Kreiso /specials — the Kreiso design system, route-scoped
   ============================================================================
   Everything lives under `.specials`. It was a separate visual system once
   ("Brat bones + Patch Kid fun"), frozen against pre-2026-08-23 tokens and
   waiting for the landing restyle. The restyle happened, so this file is the
   same design system as the rest of the site now: lilac wash, white cards, one
   lime per screen, NO BORDERS, two shadows, Archivo on its width axis.

   The scoping stays, and so does the file. Two reasons, both unchanged:

   WHY A PLAIN CSS FILE IN /public RATHER THAN A TAILWIND @theme OR A CSS MODULE
   The /specials surface is rendered TWO ways and both must look identical:
     1. /specials and /specials/{slug}  → HTML from the mvp_specials Cloud
        Function (so events publish with no rebuild + get per-event OG tags)
     2. /specials/enroll and /thanks    → static Next/React pages
   A Next-bundled stylesheet gets a hashed filename the Python function can't
   link, and Tailwind v4's `@theme` must be top-level. One stable-path
   stylesheet is the only thing both surfaces can share.

   THE FUNCTION PAGES LOAD THIS FILE AND NOTHING ELSE. No globals.css, no
   Tailwind. So the @font-face rules, the tokens and the control scale are all
   declared here as well, and `npm run check:tokens` holds them to the same
   canonical values as app/globals.css.

   CACHE NOTE: edit this file in place and ship it. The filename is NOT
   versioned and must not become versioned. firebase.json serves every other
   *.css with `max-age=31536000, immutable`, which is right for Next's
   content-hashed chunks and wrong for a hand-named file — so this one path has
   its own rule, placed after that glob so it wins:

       "/specials-theme.css"  →  "public, max-age=300"

   A change is therefore live in five minutes, and nothing else needs touching.
   `max-age`, not `no-cache`, on purpose: Firebase Hosting honours If-None-Match
   only on CACHEABLE objects, so `no-cache` here would re-download the whole
   file on every page load of /specials, /links and /email-verification,
   forever. See landing/docs/hosting.md for the measurements.

   RENAMING this file is a CROSS-DEPLOY change: Hosting serves it, but
   specials_html.py only emits the <link>, and the two deploy separately.
   Publish the new name first, repoint second, or every /specials page renders
   unstyled until landing catches up.

   MIRROR CONTRACT: design/tokens.json is canonical (see design/README.md).
   Update this file by hand when tokens change; never import across projects.
   ========================================================================= */

/* Fonts — SELF-HOSTED, deliberately, and the same three files app/globals.css
   uses. Served from our own origin rather than @import'ed from Google for two
   reasons: loading them from Google discloses every visitor's IP to a third
   party on a German-market site, and an @import is render-blocking on the paid
   conversion path.

   All three are VARIABLE. Archivo carries a WIDTH axis, which the whole type
   system rides on: 70–72% for display, 84% for titles. `block` on Archivo
   because a fallback swap on a large uppercase payoff is a visible reflow. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: block;
  src: url("/fonts/Archivo-variable.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/Inter-variable.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url("/fonts/JetBrainsMono-variable.woff2") format("woff2");
}

.specials {
  /* ── palette ──
     One hue. Lime acts, lilac belongs, the wash holds, ink is type only.
     Mirrored from design/tokens.json; `npm run check:tokens` holds it there. */
  --ink: #0e0e0e;
  --ink70: rgba(14, 14, 14, 0.7);
  --ink45: rgba(14, 14, 14, 0.45);
  --ink12: rgba(14, 14, 14, 0.12);
  --ink08: rgba(14, 14, 14, 0.08);
  --hairline-in-card: rgba(14, 14, 14, 0.07);
  --lime: #beff0a;
  --lime-deep: #a9e600;
  --lilac: #d8c4ff;
  --lilac-deep: #c6acff;
  --ground: #ece2ff;
  --well: #e4d7ff;
  --well-deep: #dfd2fa;
  --white: #ffffff;
  --ok: #2e6b33;
  --err: #c22525;

  /* ── type ──
     Three families, at most three styles on a screen. Archivo is used on its
     WIDTH axis: 70–72% for display statements, 84% for content titles. There
     is no fourth family; Baloo 2 went with the Patch Kid system. */
  --display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --display-wdth: 72%;
  --display-wdth-payoff: 70%;
  --display-tracking: -0.02em;
  --display-leading: 0.92;
  --title-wdth: 84%;
  --title-tracking: -0.012em;
  --title-leading: 1.08;

  /* ── shadows ──
     Exactly two, and nothing else casts anything. Cast sits under the lime
     pill, because lime and the wash are only 1.05 apart in luminance and need
     a soft edge as well as a hue. Lift is for the one thing that floats. */
  --shadow-cast: 0 2px 12px rgba(14, 14, 14, 0.16);
  --shadow-lift: 0 3px 14px rgba(14, 14, 14, 0.08);

  /* ── motion — one arrival, one press ── */
  --motion-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --motion-press-scale: 0.972;
  --motion-press-duration: 120ms;

  /* ── the control-dimension scale ──
     Mirrored from design/tokens.json, same values and same spellings as
     app/globals.css. It is repeated here rather than inherited because the
     function-rendered /specials pages load THIS FILE AND NOTHING ELSE — they
     never see globals.css, so a token declared only there resolves to nothing
     on half the surface this stylesheet serves.

     The heights are EXACT. They were chosen against the type, not against a
     4px rhythm, so do not snap them. The height is authoritative: set it and
     centre the content, never author a vertical padding beside it. */
  --control-xs: 32px;
  --control-sm: 38px;
  --control-md: 48px;
  --control-lg: 54px;
  --control-xl: 58px;
  --control-input: 54px;
  --control-otp: 64px;
  --control-chip: 42px;
  --control-tile: 72px;
  /* A control smaller than the touch floor keeps its visual box and carries an
     invisible hit-area extension. It does not grow. */
  --touch-min: 44px;
  --hit-inset: -6px;

  --pad-x-xs: 14px;
  --pad-x-sm: 18px;
  --pad-x-md: 24px;
  --pad-x-lg: 28px;

  /* ── shape ── */
  --radius-pill: 999px;
  --radius-lg: 20px;
  --radius-md: 16px;
  --radius-sheet: 24px;
  --radius-icon-square: 12px;
  --radius-bubble: 19px;
  --radius-tile: 16px;

  /* ── ground ──
     One ground, flat. The Patch Kid dot grid is gone: this system has no
     repeating patterns, no noise and no gradients of any kind. */
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

/* The payoff register. A booking that came through is "this is yours", so it
   floods LILAC, not lime. Lime is the action colour and there is at most one
   per screen; spending it on a whole background would leave the screen's
   actual action with nothing to be. */
.specials:has(.sp-payoff) {
  background: var(--lilac);
}

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

.specials ::selection {
  background: var(--lime);
  color: var(--ink);
}

.specials :focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ── layout ─────────────────────────────────────────────────────────────── */

.specials .sp-wrap {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 22px 96px;
}

.specials .sp-wrap.narrow {
  max-width: 620px;
}

/* Page chrome — deliberately NOT the site's <Nav>; this surface is rendered
   by a Cloud Function on half its routes and cannot mount a React component. */
.specials .sp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 22px 0 30px;
}

/* Below 48px the wordmark is chrome sitting beside other content, so it takes
   the TITLE cut. Above 48px it would be a payoff and take the display cut. */
.specials .sp-mark {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: var(--title-wdth);
  font-size: 22px;
  letter-spacing: var(--title-tracking);
  line-height: 1;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
}

.specials .sp-back {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink70);
  text-decoration: none;
}

.specials .sp-back:hover {
  opacity: 0.7;
}

.specials .sp-hero {
  position: relative;
  padding-bottom: 34px;
}

.specials .sp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 22px;
}

.specials .sp-detail {
  display: grid;
  grid-template-columns: 1.25fr 0.75fr;
  gap: 26px;
  align-items: start;
}

.specials .sp-media {
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--white);
}

/* The sticky mobile action bar. It sits on the ground rather than on a panel,
   because a second surface under one button is a slab, not a separation. */
.specials .sp-cta {
  position: sticky;
  bottom: 0;
  z-index: 20;
  display: none;
  padding: 12px 0 calc(20px + env(safe-area-inset-bottom, 0px));
  background: var(--ground);
}

.specials .sp-cta .k-btn {
  width: 100%;
}

@media (max-width: 860px) {
  .specials .sp-detail {
    grid-template-columns: 1fr;
  }

  .specials .sp-cta {
    display: block;
  }
}

@media (max-width: 640px) {
  .specials .sp-wrap {
    padding: 0 16px 72px;
  }
}

/* ── type ───────────────────────────────────────────────────────────────── */

/* Display: statements of a few words, uppercase, on the width axis. */
.specials .h1 {
  font-family: var(--display);
  font-weight: 900;
  font-stretch: var(--display-wdth-payoff);
  font-size: clamp(34px, 5vw, 60px);
  line-height: var(--display-leading);
  letter-spacing: -0.025em;
  text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 14px;
  text-wrap: balance;
}

/* Content title: sentence case, one step wider, never uppercase. */
.specials .h2 {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: var(--title-wdth);
  font-size: clamp(20px, 2.4vw, 26px);
  line-height: var(--title-leading);
  letter-spacing: var(--title-tracking);
  color: var(--ink);
  margin: 0 0 12px;
}

.specials .sub {
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink70);
  margin: 0;
}

/* Mono is documentation only: spec labels and nothing else. */
.specials .eyebrow,
.specials .lbl {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink70);
  margin: 0 0 8px;
}

.specials .mini {
  font-family: var(--sans);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink70);
}

/* ── rendered markdown ──────────────────────────────────────────────────── */

.specials .md {
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink70);
}

.specials .md > *:first-child {
  margin-top: 0;
}

.specials .md > *:last-child {
  margin-bottom: 0;
}

.specials .md h1,
.specials .md h2,
.specials .md h3 {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: var(--title-wdth);
  letter-spacing: var(--title-tracking);
  line-height: var(--title-leading);
  color: var(--ink);
  margin: 22px 0 10px;
}

.specials .md h1 { font-size: 24px; }
.specials .md h2 { font-size: 20px; }
.specials .md h3 { font-size: 17px; }

.specials .md p {
  margin: 0 0 12px;
}

.specials .md strong {
  font-weight: 650;
  color: var(--ink);
}

.specials .md em {
  font-style: italic;
}

/* No colour shift on hover. Hover is not a state in this system. */
.specials .md a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1.5px;
}

.specials .md a:hover {
  opacity: 0.7;
}

.specials .md ul,
.specials .md ol {
  margin: 0 0 12px;
  padding-left: 22px;
}

.specials .md ul { list-style: disc outside; }
.specials .md ol { list-style: decimal outside; }
.specials .md li { margin-bottom: 5px; }

/* A quote is a tinted well, not a ruled edge. */
.specials .md blockquote {
  margin: 0 0 12px;
  padding: 14px 16px;
  background: var(--well);
  border-radius: var(--radius-md);
}

.specials .md code {
  font-family: var(--mono);
  font-size: 0.9em;
  padding: 2px 6px;
  border-radius: 6px;
  background: var(--ink08);
  color: var(--ink);
}

/* ── buttons ────────────────────────────────────────────────────────────── */

/* Every action is a pill. The HEIGHT is authoritative: set it and centre the
   content, never author a vertical padding beside it. */
.specials .k-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: var(--control-md);
  padding-inline: var(--pad-x-md);
  border: none;
  border-radius: var(--radius-pill);
  background: var(--well);
  color: var(--ink);
  font-family: var(--display);
  font-weight: 800;
  font-stretch: var(--title-wdth);
  font-size: 15px;
  letter-spacing: -0.005em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--motion-press-duration) linear,
    transform var(--motion-press-duration) linear;
}

.specials .k-btn.lg {
  height: var(--control-xl);
  padding-inline: var(--pad-x-lg);
  font-size: 17px;
}

.specials .k-btn.sm {
  height: var(--control-sm);
  padding-inline: var(--pad-x-sm);
  font-size: 13.5px;
}

/* The screen's one lime. */
.specials .k-btn.primary {
  background: var(--lime);
  box-shadow: var(--shadow-cast);
}

/* "This is yours". */
.specials .k-btn.secondary {
  background: var(--lilac);
}

/* There are no outlined buttons, because there are no borders. `outline` is
   the quiet fill — the class name is kept so both renderers keep working. */
.specials .k-btn.outline,
.specials .k-btn.ghost {
  background: var(--well);
}

/* Hover is not a state here; on pointer devices it borrows the press fill
   without the scale. */
.specials .k-btn:hover:not(:disabled) { background: var(--well-deep); }
.specials .k-btn.primary:hover:not(:disabled) { background: var(--lime-deep); }
.specials .k-btn.secondary:hover:not(:disabled) { background: var(--lilac-deep); }

.specials .k-btn:active:not(:disabled) {
  transform: scale(var(--motion-press-scale));
  background: var(--well-deep);
}

.specials .k-btn.primary:active:not(:disabled) { background: var(--lime-deep); }
.specials .k-btn.secondary:active:not(:disabled) { background: var(--lilac-deep); }

/* Disabled says why in words elsewhere; here it just stops being an action. */
.specials .k-btn:disabled,
.specials .k-btn[aria-disabled="true"] {
  background: var(--well);
  color: var(--ink70);
  box-shadow: none;
  cursor: not-allowed;
  pointer-events: none;
}

/* ── fields ─────────────────────────────────────────────────────────────── */

.specials .k-fieldset {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-bottom: 18px;
}

.specials .k-label {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink70);
}

.specials .k-field {
  width: 100%;
  height: var(--control-input);
  padding: 0 16px;
  border: none;
  border-radius: var(--radius-md);
  background: var(--well);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.45;
  transition: background-color var(--motion-press-duration) linear;
}

.specials textarea.k-field {
  height: auto;
  min-height: 108px;
  padding: 14px 16px;
  resize: vertical;
}

.specials select.k-field {
  appearance: none;
  cursor: pointer;
}

.specials .k-field::placeholder {
  color: var(--ink70);
}

/* The field turns to paper when it is the one being written in, with the
   focus ring carrying the state rather than a border colour. */
.specials .k-field:focus {
  background: var(--white);
  outline: 3px solid var(--ink);
  outline-offset: 0;
}

/* No state is signalled by colour alone: the error text below says what is
   wrong, in words. */
.specials .k-field.error,
.specials .k-field.is-invalid,
.specials .k-field.is-invalid:focus {
  background: var(--white);
  outline: 3px solid var(--err);
  outline-offset: 0;
}

.specials .k-hint {
  font-family: var(--sans);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink70);
}

.specials .k-err {
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 650;
  line-height: 1.45;
  color: var(--err);
}

/* ── phone input (react-phone-number-input) ─────────────────────────────── */

.specials .k-phone .PhoneInput {
  display: flex;
  align-items: stretch;
  height: var(--control-input);
  border-radius: var(--radius-md);
  background: var(--well);
  overflow: hidden;
  transition: background-color var(--motion-press-duration) linear;
}

.specials .k-phone .PhoneInput--focus {
  background: var(--white);
  outline: 3px solid var(--ink);
}

.specials .k-phone.is-invalid .PhoneInput,
.specials .k-phone.is-invalid .PhoneInput--focus {
  background: var(--white);
  outline: 3px solid var(--err);
}

.specials .k-phone .PhoneInputInput {
  flex: 1;
  min-width: 0;
  padding: 0 14px;
  border: none;
  outline: none;
  background: transparent;
  font-family: var(--sans);
  font-size: 15px;
  color: var(--ink);
}

.specials .k-phone .PhoneInputInput::placeholder {
  color: var(--ink70);
}

.specials .k-phone .PhoneInputCountrySelectArrow {
  --PhoneInputCountrySelectArrow-color: var(--ink45);
  --PhoneInputCountrySelectArrow-opacity: 1;
  margin-left: 8px;
}

/* ── searchable country combobox ────────────────────────────────────────── */

.specials .k-combo {
  position: relative;
}

.specials .k-combo-clear {
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-xs);
  height: var(--control-xs);
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--ink45);
  font-size: 15px;
  cursor: pointer;
}

.specials .k-combo-clear:hover {
  background: var(--ink08);
}

.specials .k-combo-list {
  position: absolute;
  z-index: 30;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  padding: 6px;
  border-radius: var(--radius-md);
  background: var(--white);
  box-shadow: var(--shadow-lift);
  list-style: none;
  margin: 0;
}

.specials .k-combo-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--touch-min);
  padding: 0 12px;
  border-radius: var(--radius-icon-square);
  font-family: var(--sans);
  font-size: 15px;
  color: var(--ink);
  cursor: pointer;
}

.specials .k-combo-opt.on {
  background: var(--well);
}

/* Selected carries a dot as well as a fill: no state by colour alone. */
.specials .k-combo-opt.sel {
  background: var(--lilac);
  font-weight: 650;
}

.specials .k-combo-opt.sel::after {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink);
  margin-left: auto;
}

.specials .k-combo-empty {
  padding: 14px 12px;
  font-family: var(--sans);
  font-size: 14px;
  color: var(--ink70);
}

/* ── ticket stepper ─────────────────────────────────────────────────────── */

.specials .k-step {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px;
  border-radius: var(--radius-pill);
  background: var(--well);
}

.specials .k-step-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-min);
  height: var(--touch-min);
  border: none;
  border-radius: 50%;
  background: var(--white);
  color: var(--ink);
  font-family: var(--display);
  font-weight: 800;
  font-stretch: var(--title-wdth);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition: transform var(--motion-press-duration) linear;
}

.specials .k-step-btn:active:not(:disabled) {
  transform: scale(var(--motion-press-scale));
}

.specials .k-step-btn:disabled {
  color: var(--ink45);
  cursor: not-allowed;
}

.specials .k-step-value {
  min-width: 34px;
  text-align: center;
  font-family: var(--mono);
  font-size: 17px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.specials .k-step-total {
  font-family: var(--sans);
  font-size: 13.5px;
  font-weight: 650;
  color: var(--ink70);
  padding-right: 10px;
}

/* ── option chips ───────────────────────────────────────────────────────── */

.specials .k-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.specials .k-opt {
  display: inline-flex;
}

.specials .k-opt input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.specials .k-opt span {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: var(--control-chip);
  padding-inline: var(--pad-x-sm);
  border-radius: var(--radius-pill);
  background: var(--well);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14.5px;
  cursor: pointer;
  transition: background-color var(--motion-press-duration) linear;
}

/* Selected is a fill AND a dot. */
.specials .k-opt input:checked + span {
  background: var(--lilac);
  font-weight: 650;
}

.specials .k-opt input:checked + span::after {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink);
}

.specials .k-opt input:active:not(:disabled) + span {
  transform: scale(var(--motion-press-scale));
}

.specials .k-opt input:focus-visible + span {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

.specials .k-opt input:disabled + span {
  color: var(--ink45);
  cursor: not-allowed;
}

.specials .k-opts.is-invalid .k-opt span {
  outline: 3px solid var(--err);
  outline-offset: 2px;
}

/* ── consent row ────────────────────────────────────────────────────────── */

.specials .k-consent {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink70);
}

.specials .k-consent input[type="checkbox"] {
  appearance: none;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  margin: 1px 0 0;
  border: none;
  border-radius: 7px;
  background: var(--well);
  cursor: pointer;
}

.specials .k-consent input[type="checkbox"]:checked {
  background: var(--lilac)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230e0e0e' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E")
    center / 15px no-repeat;
}

.specials .k-consent input[type="checkbox"]:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

.specials .k-consent a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.specials .k-consent a:hover {
  opacity: 0.7;
}

.specials .k-consent.is-invalid input[type="checkbox"] {
  outline: 3px solid var(--err);
  outline-offset: 2px;
}

.specials .k-consent.is-invalid > span {
  color: var(--err);
}

.specials .k-consent + .k-btn,
.specials .k-consent + .k-err + .k-btn {
  margin-top: 16px;
}

/* ── patches (chips, stamps) ────────────────────────────────────────────── */

.specials .k-patches {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.specials .k-patch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--control-xs);
  padding-inline: var(--pad-x-xs);
  border-radius: var(--radius-pill);
  background: var(--ink08);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 650;
  white-space: nowrap;
}

.specials .k-patch.lil {
  height: 26px;
  padding-inline: 11px;
  font-size: 11.5px;
}

.specials .k-patch.on {
  background: var(--lilac);
}

/* A price is a number the reader is deciding on, so it is set in mono and
   sits on the well rather than shouting. */
.specials .k-patch.price {
  background: var(--well);
  font-family: var(--mono);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* ── event card ─────────────────────────────────────────────────────────── */

.specials .k-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--white);
  color: inherit;
  text-decoration: none;
  transition: transform var(--motion-press-duration) linear;
}

.specials a.k-card:hover {
  transform: translateY(-2px);
}

.specials .k-card:active {
  transform: scale(var(--motion-press-scale));
}

.specials .k-card .img {
  aspect-ratio: 16 / 9;
  background: var(--well) center / cover no-repeat;
}

.specials .k-card .bd {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 18px 18px 16px;
}

.specials .k-card h3 {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: var(--title-wdth);
  font-size: 18px;
  line-height: var(--title-leading);
  letter-spacing: var(--title-tracking);
  color: var(--ink);
  margin: 0;
}

.specials .k-card .d {
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink70);
  margin: 0;
}

/* The fact row: date and time in weight 650, venue below at 70% ink. */
.specials .k-card .when {
  font-family: var(--sans);
  font-size: 13.5px;
  font-weight: 650;
  color: var(--ink);
}

.specials .k-card .where {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--sans);
  font-size: 12.5px;
  color: var(--ink70);
}

.specials .k-card .where .pin {
  color: var(--ink45);
  line-height: 0;
  flex-shrink: 0;
}

.specials .k-card .where > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.specials .k-card .cfoot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 4px;
}

.specials .k-card .k-btn {
  flex-shrink: 0;
}

/* Sold out says so in words on the card; the veil is the second signal, not
   the only one. */
.specials .k-card.soldout .img {
  position: relative;
}

.specials .k-card.soldout .img::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(236, 226, 255, 0.7);
}

.specials .k-card.soldout:hover {
  transform: none;
}

/* ── row card (the fact list on an event page) ──────────────────────────── */

.specials .k-rowcard {
  border-radius: var(--radius-lg);
  background: var(--white);
  overflow: hidden;
}

.specials .k-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 13px 16px;
  font-family: var(--sans);
  font-size: 14.5px;
  color: var(--ink);
}

.specials .k-row .ic {
  flex-shrink: 0;
  color: var(--ink45);
  line-height: 0;
}

.specials .k-row .grow {
  flex: 1;
  min-width: 0;
}

.specials .k-row .val {
  font-weight: 650;
  text-align: right;
  white-space: nowrap;
}

/* The ONE sanctioned separator: a hairline BETWEEN rows of a single card.
   It is not a border around anything. */
.specials .k-div {
  height: 1px;
  background: var(--hairline-in-card);
  margin: 0 16px;
}

/* ── the note ───────────────────────────────────────────────────────────── */

/* Was a torn-paper cutout with a hard offset shadow. It is a lilac well now:
   "this is yours" is exactly what it says, and it needs no paper edge to say
   it. The class name is kept so both renderers keep working. */
.specials .torn {
  padding: 18px 20px;
  border-radius: var(--radius-md);
  background: var(--lilac);
}

.specials .torn p {
  font-family: var(--sans);
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}

.specials .torn p + p {
  margin-top: 8px;
  color: var(--ink-on-lilac-72, rgba(14, 14, 14, 0.72));
}

.specials .torn .lbl {
  color: rgba(14, 14, 14, 0.72);
}

/* ── Boris ──────────────────────────────────────────────────────────────── */

/* An author, not a mascot. He signs a judgement the system actually made; he
   is never decorative and never sits in a corner. The die-cut keyline and the
   hard ink shadow are gone with the rest of that system. */
.specials .boris-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-top: 22px;
}

.specials .boris {
  flex-shrink: 0;
  width: 96px;
  height: 96px;
  background: url("/boris-256.png") center / contain no-repeat;
}

.specials .boris.sm {
  width: 62px;
  height: 62px;
}

.specials .k-bubble {
  flex: 1;
  padding: 14px 16px;
  border-radius: var(--radius-bubble);
  background: var(--white);
  font-family: var(--sans);
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink);
}

.specials .k-bubble b {
  font-weight: 650;
}

/* ── states ─────────────────────────────────────────────────────────────── */

.specials .k-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 56px 24px;
  border-radius: var(--radius-lg);
  background: var(--white);
  text-align: center;
  font-family: var(--sans);
  font-size: 15px;
  color: var(--ink70);
}

.specials .k-empty .ic {
  width: 64px;
  height: 64px;
  background: url("/boris-256.png") center / contain no-repeat;
}

.specials .k-spin {
  display: inline-block;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: conic-gradient(from 0deg, transparent 0deg, var(--ink) 300deg);
  mask: radial-gradient(circle at center, transparent 6px, #000 7px);
  -webkit-mask: radial-gradient(circle at center, transparent 6px, #000 7px);
  animation: sp-spin 0.9s linear infinite;
}

@keyframes sp-spin {
  to { transform: rotate(360deg); }
}

/* ── footer ─────────────────────────────────────────────────────────────── */

/* A 1:1 twin of <Footer> in React. Both must show the Impressum, because it
   has to be reachable from every page, and both must offer consent withdrawal
   wherever consent could be given. */
.specials .sp-foot {
  padding: 0 22px calc(40px + env(safe-area-inset-bottom, 0px));
}

.specials .sp-foot__panel {
  max-width: 1080px;
  margin: 0 auto;
  padding: 28px 0 0;
}

.specials .sp-foot__mark {
  display: inline-block;
  font-family: var(--display);
  font-weight: 800;
  font-stretch: var(--title-wdth);
  font-size: 15px;
  letter-spacing: var(--title-tracking);
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
  margin-bottom: 14px;
}

.specials .sp-foot__links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  margin-bottom: 18px;
}

.specials .sp-foot__links a,
.specials .sp-foot__links button {
  padding: 0;
  border: none;
  background: none;
  font-family: var(--sans);
  font-size: 14px;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}

.specials .sp-foot__links a:hover,
.specials .sp-foot__links button:hover {
  opacity: 0.7;
}

.specials .sp-foot__mark:focus-visible,
.specials .sp-foot__links a:focus-visible,
.specials .sp-foot__links button:focus-visible,
.specials .sp-foot__social a:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

.specials .sp-foot__social {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
}

.specials .sp-foot__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--well);
  color: var(--ink45);
  text-decoration: none;
}

.specials .sp-foot__social a:hover {
  opacity: 0.7;
}

/* 70% ink is the lowest legal text tier, and this line used to sit below it. */
.specials .sp-foot__fine {
  font-family: var(--sans);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink70);
}

/* ── reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .specials *,
  .specials *::before,
  .specials *::after {
    animation-duration: 180ms !important;
    transition-duration: 180ms !important;
  }
}
