/* shared/base_colors.css */
/* base_colors.css — the colour library: descriptive names mapped to values,
   and nothing else. Nothing outside colors.css should reference these; a
   component asks for a semantic token (--accent, --line), never a hue.

   The cornflower system from the July 2026 design workshop
   (docs/design-system.md). Neutrals are cool-biased, never flat grey. */
:root {
  --white: #ffffff;
  --gray-25: #fafbfd;
  --gray-50: #f8f9fb;
  --gray-100: #e4e6ee;
  --gray-150: #dbdee8;
  --gray-200: #d5d8e3;
  --gray-300: #a5a9b7;
  --gray-350: #9a9dab;
  --gray-400: #949aad;
  --gray-450: #767b8d;
  --gray-500: #666a76;
  --mist: #eceef3;
  --slate-600: #4d5366;
  --slate-650: #474c5e;
  --slate-700: #2f323e;
  --slate-800: #16171c;
  --slate-850: #121318;
  --slate-900: #0b0c0f;
  --ink: #1b1c22;
  --midnight: #0a1022;

  --cornflower-700: #1d4fd7;
  --cornflower-600: #2563eb;
  --cornflower-400: #669cff;
  --cornflower-300: #8ab5ff;
  --cornflower-250: #9dbfff;

  --emerald-600: #1f9d63;
  --emerald-400: #43c489;

  /* Amber is the one status hue split in two: the bright value paints
     surfaces (tints, borders, dots) and could never carry text on a light
     background; the dark golden one is for words and icons on those tints
     (AA in both themes). In dark mode one bright value is safe for both
     jobs, so --warn and --warn-ink coincide there. */
  --amber-500: #eab308;
  --amber-400: #f2a33c;
  --amber-700: #a16207;

  --rose-600: #d5484f;
  --rose-400: #ef6a71;

  --shade-soft: rgb(20 20 30 / 5%);
  --shade: rgb(20 20 30 / 7%);
  --shade-pop: rgb(20 20 30 / 8%);
  --shade-pop-far: rgb(20 20 30 / 14%);
  --night-soft: rgb(0 0 0 / 40%);
  --night: rgb(0 0 0 / 45%);
  --night-pop: rgb(0 0 0 / 50%);
  --night-pop-far: rgb(0 0 0 / 55%);
}

/* shared/colors.css */
/* colors.css — semantic tokens mapped to base_colors.css, and the one place
   light and dark are decided. `color-scheme: light dark` plus `light-dark()`
   means one declaration per token instead of a light block and a duplicate
   dark block: change a colour here and both themes follow.

   The theme toggle stamps data-theme on <html>; pinning color-scheme there
   is what makes light-dark() (and bare native widgets — the .mx matrix
   checkboxes) follow the choice rather than the OS. Because those two rules
   only set color-scheme, every token below is authored exactly once. */
:root {
  color-scheme: light dark;

  --bg: light-dark(var(--gray-50), var(--slate-900));
  --surface: light-dark(var(--white), var(--slate-800));
  --surface-2: light-dark(var(--gray-25), var(--slate-850));

  --line: light-dark(var(--gray-100), var(--slate-700));
  --line-strong: light-dark(var(--gray-200), var(--slate-650));
  --field-line: light-dark(var(--gray-150), var(--slate-600));

  --text: light-dark(var(--ink), var(--mist));
  --text-muted: light-dark(var(--gray-500), var(--gray-300));
  --text-faint: light-dark(var(--gray-450), var(--gray-400));

  /* --on-accent flips to dark ink in dark mode so primary buttons clear AA
     in both themes. */
  --accent: light-dark(var(--cornflower-600), var(--cornflower-400));
  --accent-strong: light-dark(var(--cornflower-700), var(--cornflower-300));
  --accent-ink: light-dark(var(--cornflower-700), var(--cornflower-250));
  --on-accent: light-dark(var(--white), var(--midnight));
  --accent-weak: color-mix(in srgb, var(--accent) light-dark(9%, 14%), transparent);
  --accent-weak-2: color-mix(in srgb, var(--accent) light-dark(16%, 24%), transparent);

  --ok: light-dark(var(--emerald-600), var(--emerald-400));
  --ok-weak: color-mix(in srgb, var(--ok) light-dark(11%, 16%), transparent);
  --warn: light-dark(var(--amber-500), var(--amber-400));
  --warn-ink: light-dark(var(--amber-700), var(--amber-400));
  --warn-weak: color-mix(in srgb, var(--warn) light-dark(13%, 16%), transparent);
  --danger: light-dark(var(--rose-600), var(--rose-400));
  --danger-weak: color-mix(in srgb, var(--danger) light-dark(10%, 16%), transparent);
  --neutral-pill: light-dark(var(--gray-500), var(--gray-350));

  --shadow: 0 1px 2px light-dark(var(--shade-soft), var(--night-soft)),
    0 8px 24px light-dark(var(--shade), var(--night));
  /* The one token light-dark() can't fold on its own: the far layer's blur
     and offset differ between themes, and light-dark() substitutes colours,
     not lengths. So the geometry — and only the geometry — is picked per
     scheme, and the two theme hooks below re-pick it. */
  --shadow-pop-far: 0 12px 32px var(--shade-pop-far);
  --shadow-pop: 0 4px 10px light-dark(var(--shade-pop), var(--night-pop)), var(--shadow-pop-far);

  /* Legacy aliases (buttons were restyled to the accent system; these keep
     stray rules working until the sweep finishes). */
  --accent-btn: var(--accent);
  --btn-shadow: none;
  --btn-primary-bg: var(--accent);
  --btn-primary-fg: var(--on-accent);
  --fg: var(--text);
  --muted: var(--text-muted);
  --accent-fg: var(--on-accent);
  --nav-text: var(--text-muted);
}
@media (prefers-color-scheme: dark) {
  :root { --shadow-pop-far: 0 16px 40px var(--night-pop-far); }
}
:root[data-theme="light"] {
  color-scheme: light;
  --shadow-pop-far: 0 12px 32px var(--shade-pop-far);
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --shadow-pop-far: 0 16px 40px var(--night-pop-far);
}

/* shared/type_scale.css */
/* type_scale.css — the one type scale (rem): .75 .8125 .875 1 1.125 1.375 2
   2.75. Steps are named by weight, never by use, so a component asks for a
   step rather than inventing a number. */
:root {
  --fs-3xs: .75rem;
  --fs-2xs: .8125rem;
  --fs-xs: .875rem;
  --fs-s: 1rem;
  --fs-m: 1.125rem;
  --fs-l: 1.375rem;
  --fs-xl: 2rem;
  --fs-2xl: 2.75rem;
}

/* shared/reset.css */
* { box-sizing: border-box; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* shared/page.css */
body { margin: 0; background: var(--bg); color: var(--fg);
  font: 16px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; }
main { max-width: 620px; margin: 0 auto; padding: 2.5rem 1.25rem 4rem; }

/* shared/typography.css */
h1 { font-size: 1.6rem; line-height: 1.25; margin: 0 0 .35rem; }
h2 { font-size: 1.1rem; margin: 2rem 0 .75rem; }
p { margin: .5rem 0; }
a { color: var(--accent); }
.muted { color: var(--muted); }

/* shared/meta_icon.css */
.meta-icon { fill: currentColor; height: 1em; vertical-align: -.125em; width: 1.125em; }

/* shared/row.css */
.rows { border-top: 1px solid var(--line); margin-top: 1.25rem; }
.row { display: flex; align-items: center; gap: 1rem;
  padding: .8rem 0; border-bottom: 1px solid var(--line); }
.row .grow { flex: 1; }
/* A label used as a row's main text (the order-edit quantity rows) reads as
   body text, not as the small muted form label. */
.row > label.grow { color: inherit; font-size: inherit; margin: 0; }

/* shared/page_header.css */
.page-header { align-items: baseline; display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between; margin-bottom: .35rem; }
.page-header h1 { margin-bottom: 0; }
.page-sub { margin-top: 0; }

/* shared/price.css */
.price { font-variant-numeric: tabular-nums; }

/* shared/input_number.css */
input[type=number] { width: 4.5rem; padding: .4rem .5rem; font: inherit;
  color: inherit; background: transparent; border: 1px solid var(--line); border-radius: 6px; }

/* shared/button.css */
/* Buttons (workshop reference): quiet neutral default — surface fill,
   line-strong hairline, no shadow; the primary is the one cornflower fill,
   with --on-accent text so it clears AA in both themes. */
button, a.button { margin-top: 1.25rem; padding: .38rem .8rem; font: inherit; font-size: .8125rem; font-weight: 550;
  color: var(--text); background: var(--surface); border: 1px solid var(--line-strong); border-radius: 7px;
  cursor: pointer; display: inline-flex; align-items: center; gap: .35rem;
  line-height: 1.4; text-decoration: none; transition: border-color .12s, background .1s, transform .05s; }
button:hover, a.button:hover { border-color: var(--text-faint); }
button:active, a.button:active { transform: translateY(.5px); }
button:disabled { opacity: .5; cursor: default; }

/* shared/ref_mono.css */
.ticket, body.buyer-journey .share-link a { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .95rem; }

/* shared/status_text.css */
.ok { color: var(--ok); font-weight: 600; }

/* shared/pill.css */
.pill { display: inline-block; padding: .1rem .55rem; border: 1px solid var(--line);
  border-radius: 99px; font-size: .8rem; color: var(--muted); text-decoration: none;
  white-space: nowrap; }

/* shared/qty.css */
.qty { display: flex; align-items: center; gap: .35rem; }
/* The input matches the step buttons' 2rem height — a taller box between
   two smaller squares reads as broken, not as a control. The selector
   out-specifies the journey-wide input padding (0,1,2): WebKit refuses to
   shrink an input below its padding box, so the padding must actually be
   0 here, not merely overridden on height. */
.qty input[type=number],
body.buyer-journey form:not([data-checkout]) .qty input[type=number] { box-sizing: border-box; height: 2rem; margin: 0; padding: 0 .3rem; text-align: center; }
button.step { box-sizing: border-box; margin: 0; padding: 0; width: 2rem; height: 2rem;
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  line-height: 1; border-radius: 8px; font-size: 1.1rem; }
button.step:disabled { opacity: .35; cursor: default; filter: none; }
/* A quiet round wash, matching the event page's — it was wearing the accent
   fill and competing with the page's actual primary action. No outline: the
   fill is the affordance, and a ring around every ± made the row read as a
   toolbar. Same bargain as the fields — the boundary appears only when the
   reader has asked their OS for more contrast. */
body.buyer-journey button.step { background: color-mix(in srgb, var(--fg) 6%, var(--journey-card));
  border: 0; border-radius: 999px; color: var(--fg); font-weight: 400; }
@media (prefers-contrast: more) {
  body.buyer-journey button.step { border: 1px solid var(--control-line); }
}
input.stepped { text-align: center; appearance: textfield; -moz-appearance: textfield; }
input.stepped::-webkit-outer-spin-button, input.stepped::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0; }

/* shared/button_busy.css */
button.busy { color: transparent; position: relative; pointer-events: none; }
button.busy::after { content: ""; position: absolute; inset: 0; margin: auto;
  width: 1em; height: 1em; border: 2px solid var(--text-muted);
  border-top-color: transparent; border-radius: 50%;
  animation: spin .6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* shared/button_variants.css */
button.primary, a.button.primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
button.primary:hover, a.button.primary:hover { border-color: var(--accent); filter: brightness(1.07); }
/* Destructive buttons darken the danger token so white text clears AA in both
   themes (the light --danger is a text tone, too light for a solid fill). */
button.danger, a.button.danger { background: color-mix(in srgb, var(--danger), #000 28%); color: #fff; border-color: transparent; }
button.danger:hover, a.button.danger:hover { background: color-mix(in srgb, var(--danger), #000 34%); }
button.subtle, a.button.subtle { background: transparent; border-color: var(--line-strong); color: var(--text); }
button.subtle:hover, a.button.subtle:hover { background: var(--surface-2); color: var(--text); }
button.ghost, a.button.ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
button.ghost:hover, a.button.ghost:hover { background: var(--accent-weak); color: var(--text); }
.pill.danger, a.danger { color: var(--danger); }
.pill.primary { background: var(--accent-btn); border-color: var(--accent-btn); color: var(--on-accent); }

/* shared/form_fields.css */
input[type=text], input[type=email], input[type=password], input[type=search],
input[type=date], input[type=time], input[type=datetime-local], input[type=url], input[type=tel],
textarea, select { padding: .45rem .6rem;
  font: inherit; color: inherit; background: transparent;
  border: 1px solid var(--line); border-radius: 6px; }
textarea { width: 100%; min-height: 9rem; resize: vertical; }
/* File pickers wear the same frame as every other field, with the native
   browse control restyled as a small ghost button. */
input[type=file] { background: var(--surface); border: 1px solid var(--line); border-radius: 6px;
  color: inherit; font: inherit; padding: .45rem .6rem; width: 100%; }
input[type=file]::file-selector-button { background: transparent; border: 1px solid var(--line-strong);
  border-radius: 6px; color: inherit; cursor: pointer; font: inherit; margin-right: .7rem; padding: .2rem .7rem; }
input[type=file]::file-selector-button:hover { border-color: var(--accent); }

/* shared/select.css */
/* Native <select> matches our inputs: no OS double-arrow, a --text-muted
   chevron, a --surface fill so it never reads transparent-on-canvas. Focus
   ring comes from the shared :focus-visible. This styling alone (zero JS)
   fixes the ugly native arrow; searchable-select.js layers a typeahead on
   top. A [multiple] select keeps its native list box. The chevron ships as
   two data-URIs (light/dark), swapped on the theme so it stays legible in
   both; the combobox input (.ss-input) borrows the same look. */
select:not([multiple]), .ss-combo > .ss-input {
  background-color: var(--surface); background-repeat: no-repeat;
  background-position: right .6rem center; background-size: .8rem; padding-right: 2rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2398a2b5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); }
@media (prefers-color-scheme: light) { select:not([multiple]), .ss-combo > .ss-input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235b6579' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); } }
:root[data-theme="dark"] select:not([multiple]), :root[data-theme="dark"] .ss-combo > .ss-input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2398a2b5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); }
:root[data-theme="light"] select:not([multiple]), :root[data-theme="light"] .ss-combo > .ss-input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235b6579' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); }
select:not([multiple]) { appearance: none; -webkit-appearance: none; -moz-appearance: none; cursor: pointer; }

/* shared/label.css */
label { font-size: .85rem; color: var(--muted); display: block; margin: .8rem 0 .2rem; }

/* shared/summary_disclosure.css */
details[data-summary] { margin-top: .25rem; }
details[data-summary] summary { display: flex; align-items: center; gap: .6rem;
  padding: .8rem 0; cursor: pointer; font-weight: 600; list-style: none;
  border-bottom: 1px solid var(--line); }
details[data-summary] summary::-webkit-details-marker { display: none; }
details[data-summary] summary::before { content: "▸"; color: var(--muted);
  font-size: .8em; transition: transform .15s; }
details[data-summary][open] summary::before { transform: rotate(90deg); }
details[data-summary] [data-breakdown] .row { color: var(--muted); }

/* shared/name_field.css */
.name-row { display: grid; gap: .8rem; grid-template-columns: 1fr 1fr; }
/* The ultimate name field. The input is the field; the reading unit beneath
   it annotates how the name will be read — one labelled line per detected
   part, hairline-ruled like a footnote, with the edit control opening the
   parts form. The parts live one step down behind the house ▸ disclosure
   (same bones as details[data-summary], sized as supporting text). */
.namefield-read { align-items: flex-start; display: flex; font-size: var(--bj-support, .85rem); gap: .8rem; justify-content: space-between; margin: .45rem 0 0; max-width: 28rem; }
/* Author display rules outrank the UA's [hidden] — restate it. */
.namefield-read[hidden], .namefield-details > summary[hidden] { display: none; }
.namefield-read dl { border-left: 2px solid var(--hairline, var(--line)); display: grid; gap: .1rem 1rem; grid-template-columns: max-content 1fr; margin: 0; padding: .15rem 0 .15rem .75rem; }
.namefield-read dt { color: var(--muted, var(--text-muted)); font-weight: 400; }
.namefield-read dd { margin: 0; }
button.namefield-edit, body.buyer-journey button.namefield-edit { background: none; border: 0; border-radius: 6px; box-shadow: none; color: var(--journey-link, var(--accent)); cursor: pointer; line-height: 0; margin: .1rem 0 0; min-height: 0; padding: .3rem; width: auto; }
button.namefield-edit:hover, body.buyer-journey button.namefield-edit:hover { background: var(--accent-weak, rgba(37,99,235,.09)); }
.namefield-details { margin: .45rem 0 0; }
.namefield-details > summary { align-items: center; color: var(--muted, var(--text-muted)); cursor: pointer; display: flex; font-size: var(--bj-support, .85rem); gap: .5rem; list-style: none; width: fit-content; }
.namefield-details > summary::-webkit-details-marker { display: none; }
.namefield-details > summary::before { content: "▸"; color: var(--muted, var(--text-muted)); font-size: .8em; transition: transform .15s; }
.namefield-details[open] > summary::before { transform: rotate(90deg); }
.namefield-details[open] > summary { margin-bottom: .35rem; }
.namefield-details .name-row + .name-row { margin-top: .1rem; }

/* shared/footer.css */
footer { display: flex; gap: 1rem; margin-top: 3rem; font-size: .8rem; color: var(--muted); }

/* shared/detail_list.css */
.detail-list { display: grid; grid-template-columns: max-content 1fr; gap: .35rem 1.5rem; margin: 1.5rem 0; }
.detail-list dt { color: var(--muted); }
.detail-list dd { margin: 0; }

.detail-list { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; gap: 0; margin: 1rem 0; padding: 0 1rem; }
/* Inside a .box the card chrome comes from the box — a bordered detail-list
   there reads as a double frame. */
.box > .detail-list { background: transparent; border: 0; border-radius: 0; margin: 0; padding: 0; }
.detail-list dt, .detail-list dd { border-top: 1px solid var(--line); padding: .6rem 0; }
.detail-list dd { padding-left: 1.5rem; }
.detail-list dt:first-of-type, .detail-list dd:first-of-type { border-top: 0; }
.detail-list dd { font-weight: 500; text-align: right; }

/* focus mode — edit/create views get modal manners without a modal: the
   save footer pins to the bottom of the scroll. The column keeps the
   standard width, so show ↔ edit never jumps. All CSS; a normal form POST
   underneath. (The sidebar used to fade here too — dropped, it read as a
   rendering glitch.) */

/* shared/sr_only.css */
.sr-only { clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px; }

/* shared/callout.css */
/* Callouts & alerts (from #69, re-toned to our palette). An in-flow block that
   draws the eye without stealing it: info (our blue, default), success (green),
   warning (amber), danger (red) — all our tokens, quiet tints in the same
   family as .notice. One callout vocabulary, three sizes: .notice is the
   one-line "Saved." confirmation chip, .form-error the inline field-validation
   line, <callout> the block-level alert with an icon + optional <strong> title
   + body. A danger callout reporting a live problem gets role="alert" in the
   markup; colour is never the only signal — the text (and icon) carry it too
   (WCAG 2.2 AA).
   Icon alignment (the fix over #69): the row is top-aligned and .co-ic is a
   line-box the height of the content's FIRST line with the SVG centered in it,
   so the icon's optical centre lands ON the first text line — level with the
   title when there is one, with the body line when there isn't — never floating
   centered over the whole block. */
callout { align-items: flex-start; background: color-mix(in srgb, var(--accent) 8%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--accent) 32%, var(--line)); border-radius: 11px;
  display: flex; gap: .6rem; margin: 1rem 0; padding: .85rem 1rem; }
callout > .co-ic { align-items: center; color: var(--accent); display: inline-flex; flex: none;
  height: 1.5rem; justify-content: center; width: 1.25rem; } /* height = first-line line-height */
callout > .co-ic svg { display: block; height: 1.25rem; width: 1.25rem; }
callout > .co-body { line-height: 1.5; min-width: 0; }
callout > .co-body > strong { display: block; font-weight: 600; }
callout > .co-body > p { color: var(--muted); margin: .15rem 0 0; }
callout > .co-body > p:only-child { color: inherit; margin: 0; }
/* A callout body may carry a short list (e.g. CSV rows to fix) — same
   register as its paragraphs: muted, tight. */
callout > .co-body > ul { color: var(--muted); margin: .15rem 0 0; padding-left: 1.1rem; }
callout > .co-body > ul > li { margin: .15rem 0; }
callout[tone=success] { background: color-mix(in srgb, var(--ok) 10%, var(--bg)); border-color: color-mix(in srgb, var(--ok) 34%, var(--line)); }
callout[tone=success] > .co-ic { color: var(--ok); }
callout[tone=warning] { background: color-mix(in srgb, var(--warn) 13%, var(--bg)); border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); }
callout[tone=warning] > .co-ic { color: var(--warn-ink); }
callout[tone=danger] { background: color-mix(in srgb, var(--danger) 9%, var(--bg)); border-color: color-mix(in srgb, var(--danger) 34%, var(--line)); }
callout[tone=danger] > .co-ic { color: var(--danger); }

/* shared/form_extras.css */
.full { width: 100%; }
input.short { width: 6.5rem; }

/* shared/money_input.css */
/* money-input: a price box with its currency pinned in a leading cell — an
   amount in it always reads as flat money, never a percentage. The house
   pattern for every admin price/amount input (money-field partial). The
   ring treats the whole control as one field, like the screenshot pattern. */
.money-input { border: 1px solid var(--line); border-radius: 6px; display: inline-flex; }
.money-input > .cur { align-items: center; background: var(--surface-2); border-right: 1px solid var(--line); border-radius: 5px 0 0 5px; color: var(--text-muted); display: flex; padding: 0 .6rem; }
.money-input > input { border: 0; width: 6.5rem; }
.money-input:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.money-input > input:focus-visible { outline: none; }
input.code { font-family: ui-monospace, "SF Mono", Menlo, monospace; width: 11ch; }

/* shared/check_label.css */
label.check { align-items: center; color: inherit; display: flex; font-size: 1rem; gap: .45rem; margin: .8rem 0 .2rem; }
/* field-desc: an organizer's explainer under a custom-field question —
   quieter than the label, tight to the control it introduces. Inside a
   check label it stacks under the question text, so the label, description
   and box are one hit area (the label wraps the input). */
.field-desc { color: var(--muted); display: block; font-size: .8rem; font-weight: 400; }
/* A checkbox QUESTION is a form field, not an action row — its title
   matches the sibling field labels, not the 1rem check voice. */
label.check.check-field { color: var(--muted); font-size: .85rem; }
p.field-desc { margin: -.1rem 0 .3rem; }
label .field-desc { margin: .15rem 0 0; }
label.check:has(.field-desc) { align-items: flex-start; }
label.check:has(.field-desc) > input { margin-top: .3rem; }
/* Inside the 13.5px admin shell a 1rem check label towers over body text. */
app-shell label.check { font-size: inherit; }
/* Form voice, admin: a label is a question — text-coloured and clearly
   heavier than its hint, tight to its own control (.3rem) with the breathing
   room above (1.15rem), so each field reads as one unit and the gap between
   fields is unmistakably bigger than the gap inside one. Check/switch rows
   and toggle-block heads carry values, not questions — they stay quiet. */

/* shared/form_actions.css */
/* Save sits bottom-right everywhere (Vicky, 2026-07-23): actions right-align
   and the escape hatch (Cancel, an <a>) orders before the primary. */
.form-actions { align-items: center; display: flex; flex-wrap: wrap; gap: .9rem; justify-content: flex-end; margin-top: 1.75rem; }
.form-actions button, .form-actions a.button { margin-top: 0; }
.form-actions > a { order: -1; }

/* shared/matrix_wrap.css */
/* The Team tab's capability matrix is wider than the settings panel on
   phones — it scrolls inside its own container, never the page. */
.matrix-wrap { overflow-x: auto; }
.num { font-variant-numeric: tabular-nums; text-align: right; }

/* shared/blank_art.css */
/* blank-art — the section's menu icon over a soft blob. Every colour is a
   theme token, so light and dark come free (no per-theme SVG variants). */
.blank-art { display: block; height: 132px; margin: 0 auto 1rem; position: relative; width: 165px; }
.blank-art .blob { display: block; height: 100%; width: 100%; }
.blank-art .blob .b1 { fill: var(--accent-weak); }
.blank-art .blob .b2 { fill: var(--accent-weak-2); }
.blank-art > svg:last-child { color: var(--accent); height: 52px; inset: 0; margin: auto; position: absolute; width: 52px; }
/* The art breathes — the top blob swells on a slow loop and the dots drift,
   so an empty section reads as dormant rather than dead. The offset blob
   copy keeps its SVG attribute transform (a CSS transform would clobber
   it), so only the top copy and the dots move. */
.blank-art .blob path.b1:last-of-type { animation: blob-breathe 7s ease-in-out infinite alternate; transform-box: fill-box; transform-origin: center; }
.blank-art .blob .b2 { animation: dot-drift 5s ease-in-out infinite alternate; transform-box: fill-box; transform-origin: center; }
.blank-art .blob circle.b2:nth-of-type(2) { animation-delay: -2s; animation-duration: 6.5s; }
.blank-art .blob circle.b2:nth-of-type(3) { animation-delay: -3.5s; animation-duration: 4.5s; }
.blank-art > svg:last-child { animation: icon-bob 7s ease-in-out infinite alternate; }
@keyframes blob-breathe { to { transform: scale(1.1) rotate(-4deg); } }
@keyframes dot-drift { to { transform: translate(2px, -8px); } }
@keyframes icon-bob { to { transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) { .blank-art .blob path.b1:last-of-type, .blank-art .blob .b2, .blank-art > svg:last-child { animation: none; } }

/* shared/box.css */
/* ---- Design-system component vocabulary (docs/design-system.md §Component
   vocabulary). Structural components (box, toolbar, overlay, accordion,
   master-detail, app-shell) are classes/elements; leaf components (field*,
   status-pill, badge, pagination, page-header, empty-state, page-loader) are
   partials in admin_partials.html. All use the semantic tokens. */

/* box — the universal container. A screen is a stack of "section header + card":
   the .box-head sits ABOVE its .box (a sibling just before it), so the card holds
   only its own content. */
.box { background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  margin: 1rem 0; padding: 1.1rem 1.25rem; }
.box > :first-child { margin-top: 0; }
.box > :last-child { margin-bottom: 0; }
/* box-head — a standalone section header above a card: title (fs-m) + optional
   right-aligned action on one baseline-aligned row. The larger top margin sets
   the vertical rhythm between stacked sections; the smaller bottom margin is the
   gap down to this section's own card, which sits flush beneath it. A box-head
   that opens its container (first child, e.g. a dash-card) needs no top margin. */
.box-head { align-items: baseline; display: flex; gap: 1rem; justify-content: space-between;
  margin: 1.5rem 0 .5rem; }
.box-head:first-child { margin-top: 0; }
.box-head + .box { margin-top: 0; }
.box-head h2 { font-size: var(--fs-m); margin: 0; }
/* A box-head's action is a real (compact) button — a .pill there reads as a
   status chip, not a way in (Vicky, 2026-07-23). Quiet "All X →" nav links
   stay pills. */
.box-head a.button { margin-top: 0; padding: .22rem .65rem; font-size: 12.5px; }
.box-foot { border-top: 1px solid var(--line); color: var(--text-muted); margin-top: 1rem; padding-top: .75rem; }

/* list-row — a standalone (outside master-detail) row: swatch/dot, primary
   text, meta, right value/pill; selected = accent-weak fill + accent left bar
   (no full outline ring). */

/* shared/badge.css */
/* badge — small, uppercase, tabular: LIVE, Test mode, Secret, Sold out. */
.badge { border: 1px solid var(--line); border-radius: 5px; color: var(--text-muted);
  display: inline-block; font-size: var(--fs-3xs); font-variant-numeric: tabular-nums;
  font-weight: 700; letter-spacing: .04em; padding: .05rem .4rem; text-transform: uppercase; white-space: nowrap; }
.badge-live { background: color-mix(in srgb, var(--ok) 16%, transparent); border-color: color-mix(in srgb, var(--ok) 40%, var(--line)); color: var(--ok); }
.badge-test { background: color-mix(in srgb, var(--warn) 16%, transparent); border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); color: var(--warn-ink); }
.badge-secret { color: var(--neutral-pill); }
.badge-soldout { background: color-mix(in srgb, var(--danger) 16%, transparent); border-color: color-mix(in srgb, var(--danger) 40%, var(--line)); color: var(--danger); }

/* shared/notice.css */
/* ---- Admin standard vocabulary ----
   The CSS half of admin_partials.html: one way to say back-nav, notice,
   error, form row, confirm actions, table. Alphabetical-ish by concern. */
.back-nav { margin: 0 0 .75rem; }
.back-nav a { color: var(--muted); text-decoration: none; }
.back-nav a:hover { color: inherit; text-decoration: underline; }
.notice { background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius: 8px; display: inline-block; padding: .45rem .9rem; }
.form-error { color: var(--danger); font-weight: 600; }

/* public/journey_text.css */
body.buyer-journey li ticket-row label.grow,
body.buyer-journey .row label.grow, body.buyer-journey ticket-row label.grow,
body.buyer-journey .row label, body.buyer-journey ticket-row label,
body.buyer-journey :where(li, .row, ticket-row) :where(.grow > strong, .grow > span:first-child) {
  color: var(--fg); font-size: var(--bj-body); font-weight: 500; margin-bottom: 0; }

/* public/public_brand.css */
.public-brand { font-weight: 700; margin-bottom: 2rem; }
.public-brand a { color: inherit; text-decoration: none; }

/* public/journey_tokens.css */
/* ---- Sponsor portal (portal.html) ----
   The sponsor's window front: precision minimalism. Hairlines, not borders;
   the allocation number is the one loud thing; everything else whispers.
   All colour derives from the organizer palette (--p-accent = link colour),
   so it themes per account and per scheme. */
body.buyer-journey { --p-accent: var(--journey-link, var(--accent));
  /* A divider is a fraction of a field outline, never its equal: at the same
     weight the page reads as a grid of boxes instead of a hierarchy. Derived
     from --control-line so the two can never drift apart. */
  --hairline: color-mix(in srgb, var(--control-line) 65%, var(--journey-card));
  /* A CONTROL's boundary is not decoration: WCAG 1.4.11 wants 3:1, which the
     organizer's own --line (a decorative hairline colour) never reaches.
     Computed in Go (eventDesign.ControlLine) as the LIGHTEST blend that still
     clears it against this card — a flat percentage was far too heavy on a
     near-black palette and barely enough on a mid-tone one. */
  /* Stripe's move, and the reason the line can be this quiet: depth carries
     as much of the "this is a field" signal as the border does. */
  --control-shadow: 0 1px 1px color-mix(in srgb, var(--fg) 5%, transparent),
    0 2px 5px -2px color-mix(in srgb, var(--fg) 7%, transparent);
  /* The journey's type scale. Three sizes of prose and nothing else: body,
     the supporting line under it, and the micro-caps eyebrow. Everything that
     does the same job uses the same token — that is the whole system. */
  --bj-body: 1rem; --bj-support: .875rem; --bj-micro: .75rem;
  --bj-h1: 1.6rem; --bj-h2: 1.125rem; --bj-h3: 1rem;
  /* The one number or name a page is about: an amount due, an order
     total, the holder on a ticket. One size, one weight, everywhere. */
  --bj-display: 1.35rem; --bj-display-weight: 640; --bj-display-ink: var(--journey-heading, var(--fg));
  /* Hierarchy comes from space first and rules second. */
  --bj-section: 2.25rem; --bj-after-heading: .75rem; }

/* public/journey_headings.css */
/* Headings own the space above them and give the copy below room to breathe.
   Before this the journey leaned on hairlines to separate sections, which put
   a rule the same weight as a field outline between every block. */
body.buyer-journey :where(h1, h2, h3) { margin-block: var(--bj-section) var(--bj-after-heading); }
body.buyer-journey > main > :first-child, body.buyer-journey :where(h1, h2, h3):first-child { margin-block-start: 0; }
body.buyer-journey h1 { font-size: var(--bj-h1); line-height: 1.2; }
body.buyer-journey h2 { font-size: var(--bj-h2); }
body.buyer-journey h3 { font-size: var(--bj-h3); }

/* public/journey_support.css */
/* One supporting-text recipe. An add-on's description, a bundle note, a field
   hint and a ticket's meta line are the same job and now read the same. */
body.buyer-journey :where(.field-desc, .addon-desc, .tk-meta, .hint, .bj-support),
body.buyer-journey :where(li, .row, ticket-row, .change-row, .tkt-stub, .tkt-scan) :where(.muted, small) {
  color: var(--muted); font-size: var(--bj-support); }
/* An aside offering a different route (register as a buyer, sign in) is a
   change of subject — it needs the same air a new section gets, not the
   paragraph gap it was inheriting. */
body.buyer-journey .bj-aside { margin-block-start: var(--bj-section); }
p.lead { font-size: 1.35rem; font-weight: 600; margin: .35rem 0; }

/* public/journey_divider.css */
/* One divider colour on a buyer page. .rows/.row and friends drew theirs
   straight from the organizer's --line, which lands heavier than the field
   outline; a rule that outweighs a control inverts the hierarchy. */
body.buyer-journey :where(.rows, .row, .door-types li, hr) { border-color: var(--hairline); }
body.buyer-journey hr { background: var(--hairline); border: 0; block-size: 1px; margin-block: var(--bj-section); }

/* public/journey_row_form.css */
/* Row forms keep their controls on the row. The journey-wide width:100% is
   right for a stacked form and wrong here: it broke the add-on row into three
   lines with the button orphaned at the left. */
body.buyer-journey form:where(.row, .change-row) { align-items: center; }
body.buyer-journey form:where(.row, .change-row) :where(input:not([type=checkbox]):not([type=radio]), select, textarea) {
  width: auto; }
body.buyer-journey form:where(.row, .change-row) select { flex: 1 1 9rem; min-inline-size: 7rem; }
body.buyer-journey form:where(.row, .change-row) > button[type=submit] { margin-inline-start: auto; }

/* public/journey_field.css */
/* A labelled control with its hint: one block, so the label, the control and
   the explanation travel together instead of floating as three loose lines. */
body.buyer-journey .bj-field { margin: 1.5rem 0; }
body.buyer-journey label:not(.check):not(.rowhit):not(.grow):not(.mode-body label):not(.pi-manage label):not(.portal-register label) { color: var(--fg); display: block;
  font-size: var(--bj-support); font-weight: 600; margin-bottom: .4rem; }
/* A checkbox's label is the sentence you agree to: body copy, not a field
   name. One recipe, whether or not it carries a description. */
body.buyer-journey label.check, body.buyer-journey label.check.check-field {
  color: var(--fg); font-size: var(--bj-support); font-weight: 400; }
body.buyer-journey label.check .field-desc { font-weight: 400; }
body.buyer-journey .bj-field > .hint { margin: .4rem 0 0; }

/* public/back_link.css */
/* Going back to a screen you came from is navigation, not an action: a link,
   never a button (Vicky, 2026-07-31). */
body.buyer-journey a.back-link { background: none; border: 0; color: var(--journey-link, var(--accent));
  display: inline-block; font-size: var(--bj-body); font-weight: 400; margin-top: 1.5rem;
  padding: 0; text-decoration: underline; text-underline-offset: .15em; }
body.buyer-journey a.back-link::before { content: "\2039\00a0"; text-decoration: none; }

/* public/alloc_card.css */
/* Allocation: no box — a quiet stanza between hairlines. The eyebrow
   whispers what it is; number and noun share one baseline; the covered
   types are hairline chips; used/remaining anchor the bar's two ends. */
.alloc-card { border-block: 1px solid var(--hairline); margin: 1.6rem 0 2rem; padding: 1.3rem 0 1.25rem; }
.alloc-label { color: var(--muted); font-size: .75rem; font-weight: 600; letter-spacing: .1em; margin: 0 0 .55rem; text-transform: uppercase; }
.alloc-bar { background: var(--hairline); border-radius: 99px; height: 4px; margin-top: 1.1rem; overflow: hidden; }
.alloc-bar i { background: var(--p-accent); border-radius: 99px; display: block; height: 100%; min-width: 2px; transition: width .4s ease; }
.alloc-stats { align-items: baseline; display: flex; justify-content: space-between; }
.alloc-stats div { display: flex; flex-direction: column; gap: .15rem; }
.alloc-stats strong { font-size: 1.7rem; font-weight: 640; letter-spacing: -.02em; line-height: 1; font-variant-numeric: tabular-nums; }
.alloc-stats small { color: var(--muted); font-size: .75rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; }
.alloc-mid { align-items: center; text-align: center; }
.alloc-remaining { align-items: flex-end; text-align: right; }
.alloc-remaining strong { color: var(--p-accent); }

/* public/mode_card.css */
/* Mode cards: weightless — hairline + breath of shadow; hover lifts. */
.mode-card { background: var(--journey-card, transparent); border: 1px solid var(--hairline);
  border-radius: 14px; box-shadow: 0 1px 2px rgb(0 0 0 / 3%); margin: .7rem 0;
  transition: border-color .2s, box-shadow .2s; }
.mode-card:hover, .mode-card[open] { border-color: color-mix(in srgb, var(--p-accent) 35%, transparent);
  box-shadow: 0 2px 10px rgb(0 0 0 / 6%); }
.mode-card > summary { align-items: center; cursor: pointer; display: flex; gap: .9rem; list-style: none; padding: 1rem 1.15rem; position: relative; }
.mode-card > summary::-webkit-details-marker { display: none; }
.mode-card > summary::after { border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
  content: ""; height: 7px; width: 7px; position: absolute; right: 1.25rem; top: 50%;
  transform: translateY(-65%) rotate(45deg); transition: transform .2s; }
.mode-card[open] > summary::after { transform: translateY(-35%) rotate(225deg); }
.mode-icon { color: var(--p-accent); display: flex; flex: none; opacity: .9; }
.mode-text { display: flex; flex-direction: column; gap: .12rem; padding-right: 1.75rem; }
.mode-text strong { font-weight: 600; }
.mode-text small { color: var(--muted); }
.mode-card .mode-body { border-top: 1px solid var(--hairline); padding: .4rem 1.15rem 1.15rem; }

/* Form voice: quiet labels, hairline fields that sharpen on focus. */
body.buyer-journey .mode-body label, body.buyer-journey .pi-manage label,

/* public/portal_register.css */
body.buyer-journey .portal-register label {
  color: var(--muted); display: block; font-size: var(--bj-micro); font-weight: 600;
  letter-spacing: .08em; margin: .9rem 0 .3rem; text-transform: uppercase; }
.mode-body input:where([type=text], [type=email], [type=number], [type=file]),
.pi-manage input:where([type=text], [type=email]),
.portal-register input:where([type=text], [type=email]) {
  background: transparent; border: 1px solid var(--control-line); border-radius: 9px;
  font: inherit; padding: .55rem .7rem; transition: border-color .15s; width: 100%; }
.mode-body input:focus-visible, .pi-manage input:focus-visible, .portal-register input:focus-visible {
  border-color: var(--p-accent); outline: none; }
.mode-body input[type=number] { width: 6.5rem; }
.mode-body .mono { display: inline-block; margin: .5rem 0; overflow-wrap: anywhere; }

/* public/journey_actions.css */
.mode-body .actions, .pi-manage .actions, .portal-register .actions {
  display: flex; gap: .6rem; justify-content: flex-end; margin-top: 1.15rem; }
body.buyer-journey :where(.mode-body, .pi-manage, .portal-register) button {
  border: 0; border-radius: 999px; cursor: pointer; font: inherit; font-size: .92rem;
  font-weight: 600; padding: .58rem 1.35rem; transition: filter .15s, transform .1s; }
body.buyer-journey :where(.mode-body, .pi-manage, .portal-register) button:hover { filter: brightness(1.08); }
body.buyer-journey :where(.mode-body, .pi-manage, .portal-register) button:active { transform: translateY(.5px); }
/* The portal's ghosts sit beside copy-buttons and pill-links; without this
   they inherited the generic button's radius and weight and read as a third
   kind of secondary on one page. */
body.buyer-journey :where(.mode-body, .pi-manage) button.ghost,
body.buyer-journey a.button.ghost { background: transparent; border: 1px solid var(--control-line);
  border-radius: 999px; color: var(--fg); font-size: .85rem; font-weight: 600; padding: .42rem 1rem; }

/* The shareable address: a hairline field holding the link and its copy
   button — the admin's grey .mono chip reads as a glitch out here. */
body.buyer-journey .mode-body .mono, body.buyer-journey .portal-register .mono { background: transparent; color: var(--p-accent); padding: 0; }

/* public/share_link.css */
.share-link { align-items: center; border: 1px solid var(--hairline); border-radius: 10px;
  display: flex; gap: .7rem; justify-content: space-between; margin: .55rem 0 .2rem; padding: .5rem .5rem .5rem .75rem; }
.share-link a { color: var(--p-accent); font-size: .95rem; font-family: ui-monospace, "SF Mono", Menlo, monospace;
  overflow-wrap: anywhere; text-decoration: none; }
.share-link a:hover { text-decoration: underline; }
body.buyer-journey .copy-btn { background: transparent; border: 1px solid var(--control-line); border-radius: 999px;
  color: var(--fg); cursor: pointer; flex: none; font: inherit; font-size: .85rem; font-weight: 600;
  padding: .42rem 1rem; transition: border-color .15s, color .15s; }
body.buyer-journey .copy-btn:hover { border-color: var(--p-accent); color: var(--p-accent); }
body.buyer-journey .copy-btn.done { border-color: var(--p-accent); color: var(--p-accent); }

/* Success: a slim confirmation line, not a slab. */

/* public/portal_flash.css */
.portal-flash { align-items: center; color: var(--p-accent); display: flex; font-size: .9rem;
  font-weight: 600; gap: .45rem; margin: .8rem 0 0; }
.portal-flash::before { background: var(--p-accent); border-radius: 50%; content: ""; height: 6px; width: 6px; }

/* Issued: a hairline ledger, not boxes. */

/* public/portal_issued.css */
.portal-issued { list-style: none; margin: .4rem 0 0; padding: 0; }
.portal-issued li { border-bottom: 1px solid var(--hairline); }
.portal-issued li:first-child { border-top: 1px solid var(--hairline); }
.portal-issued li.is-revoked { opacity: .5; }
/* One line per guest: name left, count right; manageable rows are the
   summary of their own edit form, cued by the mode-card chevron. */
.pi-row { align-items: baseline; display: flex; gap: 1rem; justify-content: space-between; padding: .6rem .1rem; }
.pi-name { flex: 1; font-weight: 550; overflow-wrap: anywhere; }
.pi-name em { color: var(--muted); font-size: .8rem; font-style: normal; }
.pi-count { color: var(--muted); flex: none; font-size: .84rem; font-variant-numeric: tabular-nums; }
summary.pi-row { cursor: pointer; list-style: none; }
summary.pi-row::-webkit-details-marker { display: none; }
.pi-row::after { border-right: 1.5px solid transparent; border-bottom: 1.5px solid transparent;
  content: ""; flex: none; height: .38em; transform: rotate(45deg); transition: transform .18s ease; width: .38em; align-self: center; }
summary.pi-row::after { border-color: var(--muted); }
.pi-manage[open] > summary.pi-row::after { transform: rotate(225deg); }
.pi-manage[open] { padding-bottom: 1rem; }

/* public/reg_tickets.css */
/* Registration page: ticket choices as the same weightless cards. */
.reg-tickets { border: 0; margin: 0 0 .5rem; padding: 0; }
.reg-tickets legend { font-weight: 650; margin-bottom: .45rem; }
.reg-ticket { align-items: center; border: 1px solid var(--hairline); border-radius: 12px;
  box-shadow: 0 1px 2px rgb(0 0 0 / 3%); cursor: pointer; display: flex; gap: .7rem;
  margin: .45rem 0; padding: .7rem .9rem; transition: border-color .15s, box-shadow .15s; }
.reg-ticket:hover { border-color: color-mix(in srgb, var(--p-accent) 35%, transparent); }
.reg-ticket:has(input:checked) { border-color: var(--p-accent); box-shadow: 0 0 0 1px var(--p-accent) inset; }
.reg-ticket input { accent-color: var(--p-accent); }
.reg-ticket small { color: var(--muted); }
/* Invitation variant: the name grows, the price sits flush right. */
.reg-ticket .grow { flex: 1; }
.reg-ticket .price { font-variant-numeric: tabular-nums; }
.reg-single { margin: .4rem 0 .8rem; }
@media (prefers-reduced-motion: reduce) {
  .mode-card, .mode-card > summary::after, .alloc-bar i, .reg-ticket,
  .namefield-details > summary::before,
  body.buyer-journey :where(.mode-body, .pi-manage, .portal-register) button { transition: none; }
}

/* public/lang_switch.css */
.lang-switch { font-size: .85rem; position: absolute; right: 1rem; top: 1rem; z-index: 30; }
.lang-switch > summary { background: var(--surface, #fff); border: 1px solid var(--line, #d9d9e0); border-radius: 99px; cursor: pointer; list-style: none; padding: .35rem .8rem; }
.lang-switch > summary::-webkit-details-marker { display: none; }
.lang-switch .lang-caret { opacity: .6; }
.lang-switch > nav { background: var(--surface, #fff); border: 1px solid var(--line, #d9d9e0); border-radius: .5rem; box-shadow: 0 6px 20px color-mix(in srgb, #000 25%, transparent); display: flex; flex-direction: column; margin-top: .35rem; min-width: 10rem; padding: .3rem; position: absolute; right: 0; }
.lang-switch > nav :is(a, [aria-current]) { border-radius: .3rem; color: inherit; padding: .35rem .55rem; text-decoration: none; white-space: nowrap; }
.lang-switch > nav a:hover { background: color-mix(in srgb, currentColor 8%, transparent); }
.lang-switch > nav [aria-current] { font-weight: 700; }

/* Buyer capability and checkout-adjacent pages follow the event design too.
   Root mode consumes the constrained Classic pair; the account host gets the
   richer Pro palette. The printable ticket deliberately remains independent. */

/* public/journey_form.css */
/* ---- Buyer order & ticket pages ----
   Same dialect as the sponsor portal: hairlines, micro-caps eyebrows, pill
   ghosts. The one loud thing on the ticket page is the ticket card itself. */
/* The buyer skin is single-scheme — the organizer's palette, not the OS's.
   The card's luminance decides which side it is on (eventDesign.Scheme, a
   two-value enum stamped on <body data-scheme>), so a dark-OS buyer never
   gets a light chevron on a light card, or vice versa. Both glyphs are
   static: a url() whose value carries a template action puts
   html/template's escaper into an ambiguous URL context. */
body.buyer-journey select:not([multiple]), body.buyer-journey .ss-combo > .ss-input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235b6579' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); }
body.buyer-journey[data-scheme="dark"] select:not([multiple]),
body.buyer-journey[data-scheme="dark"] .ss-combo > .ss-input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2398a2b5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); }
body.buyer-journey select { max-width: 100%; }
body.buyer-journey form:not([data-checkout]) :where(input:not([type=checkbox]):not([type=radio]):not([type=number]), select, textarea) { box-sizing: border-box; width: 100%; }
body.buyer-journey form:not([data-checkout]) :where(input:not([type=checkbox]):not([type=radio]), select, textarea) {
  background-color: transparent; border: 1px solid var(--control-line); border-radius: 8px; color: var(--fg);
  font: inherit; padding: .55rem .65rem; }
body.buyer-journey form:not([data-checkout]) select:not([multiple]) { padding-right: 2rem; }
/* Checkboxes and radios are targets, not glyphs: the browser default is 13px,
   under half the 24px WCAG 2.2 asks for. The label around them carries the
   rest of the target. */
body.buyer-journey :where(input[type=checkbox], input[type=radio]) {
  accent-color: var(--accent); block-size: 1.25rem; inline-size: 1.25rem; margin: 0; }
body.buyer-journey label.check { align-items: center; display: flex; gap: .55rem; min-block-size: 1.75rem; }
/* Every control on a buyer page, checkout included: the global input rule
   paints borders with the organizer's decorative --line, which measured
   1.23–1.48:1 against the card. WCAG 1.4.11 wants 3:1 for a control's own
   boundary, and a checkout field is not the place to make that exception. */
body.buyer-journey :where(input:not([type=checkbox]):not([type=radio]):not([type=hidden]), select, textarea) {
  border-color: var(--control-line); box-shadow: var(--control-shadow); }
body.buyer-journey form:not([data-checkout]) :where(input, select, textarea):focus-visible {
  border-color: var(--p-accent); outline: 2px solid var(--accent-weak-2); outline-offset: 1px; }
/* Primary submits: full width and properly dressed, the journey's own
   convention (the event page's checkout button styles itself). */
body.buyer-journey form:not([data-checkout]) button[type=submit] {
  background: var(--accent); border: 0; border-radius: 10px; color: var(--accent-fg);
  font-size: .95rem; font-weight: 600; justify-content: center; padding: .7rem 1.2rem; }
body.buyer-journey form:not([data-checkout]) > button[type=submit] { width: 100%; }

/* public/bj_actions.css */
.bj-eyebrow { color: var(--muted); font-size: .75rem; font-weight: 600; letter-spacing: .1em;
  margin: 0 0 .3rem; text-transform: uppercase; }
/* order-actions / ticket-actions are the two named rows a theme may style
   (docs/themes.md §The CSS contract); .bj-actions is the same shape wherever
   a buyer page needs it without being one of those two, and stays internal. */
order-actions, ticket-actions, .bj-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin: 1.1rem 0; }
ticket-actions { justify-content: center; }
body.buyer-journey order-actions a.pill-link,
body.buyer-journey ticket-actions a.pill-link,
body.buyer-journey .bj-actions a.pill-link { background: transparent; border: 1px solid var(--control-line);
  border-radius: 999px; color: var(--fg); display: inline-block; font-size: .85rem; font-weight: 600;
  padding: .42rem 1rem; text-decoration: none; transition: border-color .15s, color .15s; }
body.buyer-journey order-actions a.pill-link:hover,
body.buyer-journey ticket-actions a.pill-link:hover,
body.buyer-journey .bj-actions a.pill-link:hover { border-color: var(--p-accent); color: var(--p-accent); }
order-actions a.pill-link svg, ticket-actions a.pill-link svg,
.bj-actions a.pill-link svg { margin-right: .35rem; vertical-align: -.18em; }
/* The public error page: empty-state composition inside the journey card. */
.error-state { padding: 1rem 0; text-align: center; }
.error-state .bj-actions { justify-content: center; margin-bottom: 0; }
/* Checkout details: one soft card per attendee; legends whisper. */

/* public/checkout_attendee.css */
fieldset.co-attendee { border: 1px solid var(--hairline); border-radius: 12px; margin: 1rem 0; padding: .35rem 1rem 1.1rem; }
fieldset.co-attendee legend { color: var(--muted); font-size: .75rem; font-weight: 600; letter-spacing: .1em; padding: 0 .4rem; text-transform: uppercase; }
/* Organizer-only note on a public page: quiet, clearly not for buyers. */

/* public/journey_misc.css */
.admin-note { background: var(--accent-weak); border-radius: 10px; color: var(--fg); font-size: .85rem; margin: 1rem 0; padding: .7rem .9rem; }
body.buyer-journey footer { justify-content: center; text-align: center; }

/* public/doc_lines.css */
.doc-lines .row { border-bottom: 1px solid var(--hairline); }
.doc-refunded { opacity: .55; text-decoration: line-through; }
.doc-tax { text-align: right; }
.doc-total { font-size: 1.15rem; text-align: right; }
body.buyer-journey .doc-total { color: var(--bj-display-ink); font-size: var(--bj-display); font-weight: var(--bj-display-weight); }
@media print { body.buyer-journey { padding: 0; } body.buyer-journey > main { box-shadow: none; } order-actions, ticket-actions, .bj-actions, .lang-switch, footer { display: none; } }

/* public/email_fix.css */
.email-line { align-items: center; color: var(--muted); display: flex; flex-wrap: wrap; gap: .35rem; margin: .3rem 0 0; }
.email-fix { display: inline; }
.email-fix > summary { border: 1px solid var(--hairline); border-radius: 99px; color: var(--muted);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  height: 1.6rem; width: 1.6rem; list-style: none;
  transition: border-color .15s, color .15s; }
.email-fix form { flex-basis: 100%; }
.email-fix > summary:hover, .email-fix[open] > summary { border-color: var(--p-accent); color: var(--p-accent); }
.email-fix > summary svg { height: 12px; width: 12px; }
.email-fix > summary::-webkit-details-marker { display: none; }
.email-fix form { align-items: stretch; display: flex; flex-wrap: wrap; gap: .5rem; margin: .6rem 0 .2rem; }
.email-fix input[type=email] { flex: 1 1 0; min-width: 9rem; width: 0; }
.email-fix form button { flex: none; white-space: nowrap; }
.email-fix form label { flex-basis: 100%; margin: 0 0 -.35rem; }
/* Composite rows: field and button share one height, one baseline. */
.email-fix form :where(input, button),

/* public/change_row.css */
.change-row :where(select, button) { block-size: 2.6rem; box-sizing: border-box; margin: 0; padding-block: 0; }
.change-row { align-items: center; display: grid; gap: .6rem .8rem;
  grid-template-columns: minmax(8rem, 1fr) minmax(0, 1.4fr) 7.5rem;
  border-bottom: 1px solid var(--hairline); margin: 0; padding: .7rem 0; }
.change-row .cr-what { font-weight: 550; overflow-wrap: anywhere; }
.change-row .cr-what small { color: var(--muted); display: block; font-weight: 400; }
.change-row select { max-width: none; width: 100%; }
.change-row button[type=submit] { min-width: 0; width: 100%; }
@media (max-width: 480px) { .change-row { grid-template-columns: 1fr; } }
/* The ticket card: what you show at the door. Two halves, each with its own
   border, corner-masked at the join — real punched notches the border can't
   cross (an overflow-hidden bite left the outer border line intact). */

/* public/order_flash.css */
.order-flash { color: var(--fg); font-weight: 550; margin: .6rem 0; }
.order-flash::before { color: var(--p-accent); content: "● "; font-size: .7em; vertical-align: .15em; }
/* Door (on-site sales): the lead line is the amount being charged; the
   type picker is a quiet label/quantity ledger, same bones as .tix-list. */

/* public/door_types.css */
.door-types { list-style: none; margin: 1rem 0; padding: 0; }
.door-types li { align-items: center; border-bottom: 1px solid var(--line); display: flex;
  gap: 1rem; justify-content: space-between; padding: .55rem 0; }
.door-types li:first-child { border-top: 1px solid var(--line); }
.door-types label { flex: 1; margin: 0; }
.door-types input { margin: 0; width: 5rem; }
/* Tickets as a quiet ledger: holder (or type) left, meta right, whole row a link. */

/* public/tix_list.css */
.tix-list { list-style: none; margin: .3rem 0 0; padding: 0; }
.tix-list li { border-bottom: 1px solid var(--hairline); }
.tix-list li:first-child { border-top: 1px solid var(--hairline); }
body.buyer-journey a.tix-row { align-items: center; color: var(--fg); display: flex; gap: 1rem;
  justify-content: space-between; padding: .65rem .1rem; text-decoration: none; }
a.tix-row .tx-who { flex: 1; font-weight: 550; overflow-wrap: anywhere; }
a.tix-row .tx-who small { color: var(--muted); display: block; font-weight: 400; }
a.tix-row .tx-meta { color: var(--muted); flex: none; font-size: var(--bj-support, .875rem); }
a.tix-row::after { border-bottom: 1.5px solid var(--muted); border-right: 1.5px solid var(--muted);
  align-self: center; content: ""; flex: none; height: .38em; transform: rotate(-45deg); width: .38em; }
a.tix-row:hover .tx-who { color: var(--p-accent); }
/* Aligned self-service rows (type changes, add-ons): one grid, no drift. */
/* One aligned table: name column, select column, button column — every
   row's controls share the same widths. */

/* public/ticket_card.css */
.tkt-card { margin: 1.4rem auto; max-width: 360px; }
/* The pad is deliberately NOT the page background and NOT a customizer
   token: a loud --bg turned the credential the same colour as the surround
   (seen with a strong green palette). Derived instead — the card nudged 7%
   toward the text colour — so it reads as a quiet neutral on light and dark
   card themes alike, and the palette's text stays readable on it. */
body.buyer-journey :where(.tkt-stub, .tkt-scan) .tk-ref { font-size: .95rem; }
.tkt-stub, .tkt-scan { background: color-mix(in srgb, var(--journey-card, var(--bg)) 93%, var(--fg)); border: 1px solid var(--hairline); }
.tkt-stub { border-bottom: 0; border-radius: 18px 18px 0 0; padding: 1.15rem 1.25rem 1rem;
  -webkit-mask-image: radial-gradient(circle 10px at 0 100%, transparent 9.5px, #000 10px),
    radial-gradient(circle 10px at 100% 100%, transparent 9.5px, #000 10px);
  mask-image: radial-gradient(circle 10px at 0 100%, transparent 9.5px, #000 10px),
    radial-gradient(circle 10px at 100% 100%, transparent 9.5px, #000 10px);
  -webkit-mask-size: 51% 100%; mask-size: 51% 100%;
  -webkit-mask-position: left, right; mask-position: left, right;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
.tkt-stub .tk-event { color: var(--muted); font-size: .75rem; font-weight: 600; letter-spacing: .1em;
  margin: 0 0 .35rem; text-transform: uppercase; }
.tkt-stub .tk-holder { color: var(--bj-display-ink); font-size: var(--bj-display); font-weight: var(--bj-display-weight);
  letter-spacing: -.01em; line-height: 1.2; margin: 0; overflow-wrap: anywhere; }
.tkt-stub .tk-meta { color: var(--muted); font-size: var(--bj-support); margin: .3rem 0 0; }
.tkt-scan { border-radius: 0 0 18px 18px; border-top: 2px dashed var(--hairline);
  padding: 1.15rem 1.25rem 1.05rem; text-align: center;
  -webkit-mask-image: radial-gradient(circle 10px at 0 0, transparent 9.5px, #000 10px),
    radial-gradient(circle 10px at 100% 0, transparent 9.5px, #000 10px);
  mask-image: radial-gradient(circle 10px at 0 0, transparent 9.5px, #000 10px),
    radial-gradient(circle 10px at 100% 0, transparent 9.5px, #000 10px);
  -webkit-mask-size: 51% 100%; mask-size: 51% 100%;
  -webkit-mask-position: left, right; mask-position: left, right;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
.tkt-scan img { background: #fff; border-radius: 12px; height: auto; max-width: min(232px, 70vw); padding: 12px; }
.tkt-scan .tk-ref { color: var(--muted); font-size: .95rem; font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: .82rem; letter-spacing: .12em; margin: .5rem 0 0; }
.tkt-scan .tk-state { margin: .35rem 0 0; }

/* public/journey_shell.css */
body.buyer-journey { background: var(--bg); min-height: 100dvh; padding: 3rem 1.25rem; }
body.buyer-journey > main { background: var(--journey-card); border-radius: var(--journey-radius);
  box-shadow: 0 20px 35px -5px rgb(0 0 0 / 15%), 0 12px 15px -4px rgb(0 0 0 / 12%);
  color: var(--fg); max-width: 510px; padding: 2rem; }
body.buyer-classic > main { max-width: 760px; }
body.buyer-journey h1, body.buyer-journey h2 { color: var(--journey-heading); }
body.buyer-journey a { color: var(--journey-link); }
body.buyer-journey button { background: var(--accent); color: var(--accent-fg); }
/* A destructive journey confirm keeps its danger dress — the accent repaint
   above must not make "Cancel this quote" look like any other primary. */
body.buyer-journey button.danger,
body.buyer-journey form:not([data-checkout]) button[type=submit].danger {
  background: color-mix(in srgb, var(--danger), #000 28%); color: #fff; }
body.buyer-journey button.danger:hover,
body.buyer-journey form:not([data-checkout]) button[type=submit].danger:hover {
  background: color-mix(in srgb, var(--danger), #000 34%); }
/* A journey button opting out of the accent (e.g. the dashboard's sign-out):
   same quiet dress as the pill-link doors. */
body.buyer-journey button.subtle,
body.buyer-journey form:not([data-checkout]) button[type=submit].subtle {
  background: transparent; border: 1px solid var(--control-line); color: var(--fg); }
/* A submit inside a repeated row is a row action, not the page's action:
   the inline pill, and never full width. */
body.buyer-journey form:where(.row, .change-row):not([data-checkout]) button[type=submit].subtle,
body.buyer-journey form:where(.row, .change-row):not([data-checkout]) > button[type=submit].subtle {
  border-radius: 999px; font-size: .85rem; padding: .42rem 1rem; width: auto; }
body.buyer-journey button.subtle:hover,
body.buyer-journey form:not([data-checkout]) button[type=submit].subtle:hover {
  border-color: var(--p-accent); color: var(--p-accent); }
.journey-logo { display: block; margin: 0 0 1.5rem; max-height: 72px; max-width: 140px; object-fit: contain; }
@media (max-width: 599px) {
  body.buyer-journey { padding: 0; }
  body.buyer-journey > main { border-radius: 0; box-shadow: none; min-height: 100dvh; padding: 1.25rem; }
}

/* public/doc_head.css */
/* ---- Money documents & pay page (quotes / bank-transfer polish) ----
   doc-head: the document masthead — title block left, seller identity
   right (invoice); collapses gracefully when there's no right column. */
.doc-head { align-items: flex-start; display: flex; gap: 1rem; justify-content: space-between; }
.doc-head .doc-seller { text-align: right; }

/* public/pay_amount.css */
body.buyer-journey hr { border: 0; border-top: 1px solid var(--hairline); margin: 1.6rem 0; }
body.buyer-journey .pay-amount { color: var(--bj-display-ink); font-size: var(--bj-display); font-weight: var(--bj-display-weight); margin: .4rem 0 1rem; }
/* bj-split: a matched pair of secondary doors under the primary — equal
   halves, dressed like the journey's bordered ghosts. */

/* public/bj_split.css */
body.buyer-journey .bj-split { display: flex; gap: .6rem; margin: .9rem 0; }
body.buyer-journey .bj-split a.button { background: transparent; border: 1px solid var(--hairline);
  border-radius: 10px; color: var(--fg); flex: 1; font-size: .875rem; font-weight: 600;
  justify-content: center; margin-top: 0; padding: .55rem 1rem; text-decoration: none;
  transition: border-color .15s, color .15s; }
body.buyer-journey .bj-split a.button:hover { border-color: var(--p-accent); color: var(--p-accent); }

/* public/journey_callout.css */
/* Journey callouts whisper: the default (informational) tone drops the
   accent tint for a faint wash and hairline — the buyer card is quiet, so
   a loud box reads as an error. Warning/danger tones keep their colour. */
body.buyer-journey callout:not([tone]) { background: color-mix(in srgb, var(--fg) 4%, transparent);
  border-color: var(--hairline); border-radius: 10px; }
body.buyer-journey callout:not([tone]) > .co-body > p:only-child { color: var(--muted); }
/* Admin quote composer rows: catalogue left, labelled amounts right — one
   grid so every row's controls share the same columns (the change-row
   idea, admin-side). Labels are quiet column captions, not field labels. */

/* public/promo_note.css */
/* Promo-code feedback on the checkout details step (yste-coupons session):
   the details render prices an event-page-entered code so the buyer sees it
   working — or fixes it — before paying. */
.promo-note { font-size: .85rem; margin: .35rem 0 0; }
.promo-note.ok { color: var(--ok, #2e7d32); }
.promo-note.bad { color: var(--danger, #b3261e); }
</style>

