/* =========================================================================
   Sarx web theme  ·  sarx.link
   Admin dashboard · client / investor demo · (support.sarx.link reuses this)

   Derived from the Sarx iOS app icon (Canva DAHT9EAcask) and launch screen
   (Canva DAHT9KUz_-k): pure black, saturated red #D10F0F, a bold monogram "S"
   with a soft red glow, and the SARX wordmark at 0.3em tracking. The mark is set
   in Fraunces, the same face the iOS app uses.

   Drop into wwwroot/css and reference from App.razor / _Host.cshtml.
   Set data-theme="dark" (default) or data-theme="light" on <html> or any
   container. Every component reads only semantic tokens (--bg, --fg, …),
   so a light admin shell can host a dark demo panel and vice-versa.
   ========================================================================= */

/* ---------- 1. Brand palette (raw, never used directly by components) ---- */
:root {
  --sx-ink:       #0A0A0B;  /* app black */
  --sx-ink-2:     #131315;  /* raised surface on ink */
  --sx-ink-3:     #1C1C20;  /* sunken / input on ink */
  --sx-graphite:  #2B2B31;  /* rules on ink */
  --sx-slate:     #45454D;  /* strong rules on ink */
  --sx-smoke:     #8F8F98;  /* muted text on ink */
  --sx-ash:       #D2D2D6;  /* body text on ink */
  --sx-bone:      #F5F4F1;  /* headline text on ink */

  /* Day mode, taken from the app concept's own light values (app_theme/dashboard-concept.html:
     its document ground #E9E6E0, white swatch surface, #D6D2CA rules, #1A1A1C and #55555C text).
     Only --sx-paper-3 is derived — the concept has no sunken surface — and it is set one step
     below the ground so an input still reads as recessed. */
  --sx-paper:     #E9E6E0;  /* page ground */
  --sx-paper-2:   #FFFFFF;  /* cards, raised surfaces */
  --sx-paper-3:   #DFDBD3;  /* sunken / input (derived) */
  --sx-paper-4:   #D6D2CA;  /* rules */
  --sx-paper-ink: #1A1A1C;  /* primary text */
  --sx-paper-mid: #55555C;  /* secondary text */

  --sx-red:       #D10F0F;  /* launch-screen red (icon uses #CE0000) */
  --sx-red-2:     #B00C0C;  /* hover / pressed */
  --sx-ember:     #FF3B3B;  /* red lifted for small text on ink (AA) */
  --sx-oxblood:   #3A0505;  /* deep red for tinted surfaces on ink */
  --sx-blush:     #FBE6E6;  /* red tint for surfaces on paper */
  --sx-glow:      rgba(209, 15, 15, .55); /* icon's radial glow */

  --sx-moss:      #2F8A5B;  /* ok */
  --sx-amber:     #B8791A;  /* warning */

  /* ---------- 2. Type ---------- */
  /* Fraunces, not a Didone.
     Bodoni's stroke contrast is its whole character and also its failure mode: at nav-bar size
     the hairlines thin out until A, X, N, R and V stop being distinguishable from each other.
     Fraunces carries the same editorial weight with a fraction of that contrast, and it was
     already being downloaded for the app mockup — so this swap removes a font request rather
     than adding one, and the website and the iOS app now share one mark.

     No font-variation-settings anywhere. The old rules pinned "opsz" 96 — the DISPLAY cut, with
     the thinnest strokes the family has — onto a 21px wordmark, which is most of why it was
     illegible. Left alone, font-optical-sizing defaults to auto and the browser picks the optical
     size from the rendered size, which is the behaviour that was being overridden. */
  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --font-ui: "Instrument Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

  --text-xs: 0.75rem;    /* 12 */
  --text-sm: 0.8125rem;  /* 13 */
  --text-base: 0.9375rem;/* 15 */
  --text-md: 1.0625rem;  /* 17 */
  --text-lg: 1.3125rem;  /* 21 */
  --text-xl: 1.75rem;    /* 28 */
  --text-2xl: 2.5rem;    /* 40 */
  --text-3xl: 4rem;      /* 64 */
  --text-hero: clamp(4.5rem, 14vw, 11rem);

  --track-wide: 0.3em;   /* wordmark tracking, from the launch screen */

  /* ---------- 3. Space, radius, motion ---------- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px; --sp-9: 96px;

  --r-1: 3px;     /* controls */
  --r-2: 8px;     /* cards, panels */
  --r-pill: 999px;/* status badges only */

  --ease: cubic-bezier(.2,.7,.2,1);
  --dur: 160ms;

  --shell-rail: 232px;
  --shell-topbar: 56px;
  --content-max: 1280px;
  /* 52ch, not 62. The ch unit is the width of a zero, which is wider than the average letter, so
     62ch rendered about 78 characters to the line — past the 45-75 that reads comfortably. This
     lands near 68. Every prose column on the site is measured from here. */
  --measure: 52ch;
}

/* ---------- 4. Semantic tokens: dark (default) ---------- */
:root,
[data-theme="dark"] {
  color-scheme: dark;
  --bg:            var(--sx-ink);
  --bg-raised:     var(--sx-ink-2);
  --bg-sunken:     var(--sx-ink-3);
  --bg-hover:      #1F1F24;
  --fg:            var(--sx-ash);
  --fg-strong:     var(--sx-bone);
  --fg-muted:      var(--sx-smoke);
  --line:          var(--sx-graphite);
  --line-strong:   var(--sx-slate);
  --accent:        var(--sx-red);
  --accent-hover:  var(--sx-red-2);
  --accent-fg:     #FFFFFF;
  --accent-text:   var(--sx-ember);
  --accent-soft:   var(--sx-oxblood);
  --ok:            #4DB37A;
  --warn:          #D9A03A;
  --focus:         var(--sx-ember);
  --shadow-pop:    0 0 0 1px var(--line), 0 16px 40px -12px rgba(0,0,0,.8);
  --watermark:     rgba(255,255,255,.028);
  --selection:     var(--sx-oxblood);

  /* ---- The iOS app's own palette (night) ----------------------------------
     Declared here, on the theme containers, rather than on .sx-phone — and that
     placement is the whole mechanism. Custom properties inherit from the NEAREST
     ancestor that declares them, so the mockup follows whichever [data-theme] is
     closest: <html>, which the colour-mode button drives, or any container that
     declares its own. A selector like [data-theme="light"] .sx-phone could not do
     that — it would match on <html> alone and ignore anything nearer.

     Values from the app concept (app_theme/dashboard-concept.html). The app red is
     #C8102E in BOTH modes and is deliberately NOT the web red — this is a picture of
     a different product surface, and recolouring it to match the site would make the
     mockup a picture of something that does not exist. */
  --app-ink:       #0A0A0B;             /* screen ground */
  --app-panel:     #141416;             /* filled surface: fields, bubbles, zones */
  --app-line:      #26262A;             /* hairline */
  --app-bone:      #F2EFE9;             /* primary text */
  --app-ash:       #8B8B91;             /* secondary text, inactive tab glyphs */
  --app-red:       #C8102E;             /* brand red — identical in both modes */
  --app-dash:      #3A3A40;             /* the off state of a settings toggle */
  --app-bar:       rgba(10, 10, 11, .92);   /* the translucent tab bar over the feed */
  --app-shadow:    rgba(0, 0, 0, .8);   /* the device's cast shadow on the page */
  --app-glow:      rgba(200, 16, 46, .28);      /* the launch screen's bloom behind the monogram */
  --app-watermark: rgba(242, 239, 233, .030);   /* the launch screen's σάρξ, barely there */

  /* The admin rail is dark in BOTH modes — it is the spec's ink rail, and the brand scale it is
     painted from does not move. That is fine on paper, where it separates itself. On ink the page
     ground is --sx-ink too, so the rail would vanish into the page; it sits one step raised
     instead, which is the same relationship it has to paper, drawn with the dark scale. */
  --rail-bg: var(--sx-ink-2);
}

/* ---------- 5. Semantic tokens: light (admin paper) ---------- */
[data-theme="light"] {
  color-scheme: light;
  --bg:            var(--sx-paper);
  --bg-raised:     var(--sx-paper-2);
  --bg-sunken:     var(--sx-paper-3);
  --bg-hover:      #E0DCD4;
  --fg:            var(--sx-paper-ink);
  --fg-strong:     var(--sx-ink);
  --fg-muted:      var(--sx-paper-mid);
  --line:          var(--sx-paper-4);
  --line-strong:   #BEB9AF;
  --accent:        var(--sx-red);
  --accent-hover:  var(--sx-red-2);
  --accent-fg:     #FFFFFF;
  /* Not --sx-red. On the concept's warmer #E9E6E0 ground, #D10F0F measures 4.45:1 — just under
     the 4.5 AA floor for body text, and this token colours every link and inline red word. The
     pressed red clears it at 5.81:1. On the old, lighter #F1F0EE ground the bright red scraped
     past at 4.87:1, so this only became wrong when the ground moved. */
  --accent-text:   var(--sx-red-2);
  --accent-soft:   var(--sx-blush);
  --ok:            var(--sx-moss);
  --warn:          var(--sx-amber);
  --focus:         var(--sx-red);
  --shadow-pop:    0 1px 2px rgba(10,10,11,.06), 0 12px 32px -14px rgba(10,10,11,.3);
  --watermark:     rgba(10,10,11,.045);
  --selection:     var(--sx-blush);

  /* ---- The iOS app's own palette (day) ------------------------------------
     Same reasoning as the night block above. Every value is the concept's own light
     value: its #FFFFFF swatch ground, its #E9E6E0 document ground for filled
     surfaces, its #D6D2CA rules, #1A1A1C primary and #55555C secondary text.

     The red does not move. #C8102E measures 5.88:1 on white and 4.72:1 on the panel,
     so it clears AA as text in day mode without being darkened — which is what lets
     the app keep one red across both modes, as the concept specifies. */
  --app-ink:       #FFFFFF;
  --app-panel:     #E9E6E0;
  --app-line:      #D6D2CA;
  --app-bone:      #1A1A1C;
  --app-ash:       #55555C;
  --app-red:       #C8102E;
  /* Derived, not a concept literal, and the one value here that is. A switched-off toggle is a
     placeholder that should read as quiet: #3A3A40 on the night ground is 1.75:1, and the
     concept's rule colour on white is only 1.51:1, so it would go fainter in day than in night.
     #C6C0B4 is 1.81:1 — the same weight, in the concept's warm-grey family. Going to --app-ash
     instead would be 7.39:1 and make an OFF switch louder than an on one. */
  --app-dash:      #C6C0B4;
  --app-bar:       rgba(255, 255, 255, .92);
  /* A near-opaque black drop shadow reads as grime on a pale page; the device still
     needs to lift off it, so the same geometry is kept at a third of the weight. */
  --app-shadow:    rgba(10, 10, 11, .30);
  /* Both are lighter in day mode: a bloom tuned to read against black turns into a pink smear on
     white, and a watermark at the same alpha becomes a legible word rather than a texture. */
  --app-glow:      rgba(200, 16, 46, .10);
  --app-watermark: rgba(26, 26, 28, .045);

  /* Day mode: the ink rail on paper, exactly as the spec draws it. */
  --rail-bg: var(--sx-ink);
}

/* ---------- 6. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }

/* A themed container always paints its own surface, so a light admin shell
   can sit inside a dark page (or the reverse) without leaking. */
[data-theme] { background: var(--bg); color: var(--fg); }

html {
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  background: var(--bg);
  color: var(--fg);
}
body { margin: 0; min-height: 100dvh; background: var(--bg); color: var(--fg); }

::selection { background: var(--selection); color: var(--fg-strong); }

h1, h2, h3, h4 {
  margin: 0;
  color: var(--fg-strong);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
h1 { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: 400; }
h2 { font-family: var(--font-display); font-size: var(--text-xl); font-weight: 400; }
h3 { font-size: var(--text-lg); }
h4 { font-size: var(--text-md); }
p { margin: 0 0 var(--sp-4); max-width: var(--measure); }
p:last-child { margin-bottom: 0; }
small, .sx-muted { color: var(--fg-muted); font-size: var(--text-sm); }
a { color: var(--accent-text); text-decoration: none; text-underline-offset: 3px; }
a:hover { text-decoration: underline; }
strong { color: var(--fg-strong); font-weight: 600; }
hr { border: 0; border-top: 1px solid var(--line); margin: var(--sp-6) 0; }
code, kbd { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 0.875em; }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r-1); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- 7. Brand marks ---------- */
.sx-wordmark {
  font-family: var(--font-display);
  font-weight: 400;
 
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--fg-strong);
  line-height: 1;
  display: inline-block;
  padding-right: 0; /* optical: trailing tracking */
  margin-right: calc(-1 * var(--track-wide));
}
.sx-wordmark--accent { color: var(--accent-text); }
.sx-wordmark--hero { font-size: var(--text-hero); letter-spacing: 0.12em; margin-right: -0.12em; font-weight: 400; }

/* The bold "S" from the icon and launch screen */
.sx-monogram {
  font-family: var(--font-display);
  font-weight: 700;
 
  line-height: 1;
  color: var(--sx-red);
  display: inline-block;
}
.sx-monogram--hero { font-size: clamp(6rem, 18vw, 14rem); }

/* The icon's soft red radial glow. Put on a positioned parent; the glow sits behind. */
.sx-glow { position: relative; isolation: isolate; }
.sx-glow::before {
  content: ""; position: absolute; z-index: -1; inset: -30%;
  background: radial-gradient(closest-side at 50% 50%, var(--sx-glow), transparent 70%);
  pointer-events: none;
}

/* Lockup: monogram over wordmark, as on the launch screen */
.sx-lockup { display: inline-grid; justify-items: center; gap: var(--sp-3); text-align: center; }
.sx-lockup .sx-wordmark { color: var(--sx-red); margin-right: 0; }

/* Greek σάρξ watermark (faint, from the launch screen brief): position relative on the parent */
.sx-watermark { position: relative; isolation: isolate; overflow: hidden; }
.sx-watermark::before {
  content: "σάρξ";
  position: absolute; z-index: -1;
  right: -0.08em; bottom: -0.32em;
  font-family: var(--font-display);
  font-weight: 300;
 
  font-size: clamp(12rem, 34vw, 30rem);
  line-height: 1;
  color: var(--watermark);
  pointer-events: none; user-select: none;
}

/* ---------- 8. Buttons ---------- */
.sx-btn {
  --btn-bg: var(--bg-raised); --btn-fg: var(--fg-strong); --btn-line: var(--line-strong);
  display: inline-flex; align-items: center; gap: var(--sp-2);
  height: 36px; padding: 0 var(--sp-4);
  font: 500 var(--text-sm)/1 var(--font-ui);
  color: var(--btn-fg); background: var(--btn-bg);
  border: 1px solid var(--btn-line); border-radius: var(--r-1);
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.sx-btn:hover { --btn-bg: var(--bg-hover); text-decoration: none; }
.sx-btn:active { transform: translateY(1px); }
.sx-btn:disabled, .sx-btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.sx-btn--primary { --btn-bg: var(--accent); --btn-fg: var(--accent-fg); --btn-line: var(--accent); }
.sx-btn--primary:hover { --btn-bg: var(--accent-hover); --btn-line: var(--accent-hover); }
.sx-btn--ghost { --btn-bg: transparent; --btn-line: transparent; --btn-fg: var(--fg); }
.sx-btn--ghost:hover { --btn-bg: var(--bg-hover); --btn-fg: var(--fg-strong); }
.sx-btn--danger { --btn-bg: transparent; --btn-fg: var(--accent-text); --btn-line: var(--line-strong); }
.sx-btn--danger:hover { --btn-bg: var(--accent-soft); --btn-line: var(--accent); }
.sx-btn--lg { height: 44px; padding: 0 var(--sp-5); font-size: var(--text-base); }
.sx-btn--sm { height: 28px; padding: 0 var(--sp-3); font-size: var(--text-xs); }
.sx-btn--icon { width: 36px; padding: 0; justify-content: center; }

/* ---------- 9. Forms ---------- */
.sx-field { display: grid; gap: var(--sp-1); }

/* A form as a few short groups rather than one long column.
   The rhythm is one rule here instead of an inline margin repeated on every field, and the rows
   let a phone number stop being as wide as a street address — which is most of why a stack of
   eight full-width inputs reads as a wall. */
.sx-fields { display: grid; gap: var(--sp-4); }
.sx-fields + .sx-fields { margin-top: var(--sp-5); padding-top: var(--sp-5); border-top: 1px solid var(--line); }

/* The same treatment as the rail's section labels, for the same reason: a heading that differs
   from its contents only by being dimmer does not read as a heading. */
.sx-fields__legend {
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--fg-muted);
}

/* auto-fit rather than a media query, because what decides whether two fields fit side by side is
   the width of the CARD they are in, not the width of the window. This card sits in a column that
   is itself responsive. */
.sx-fields__row { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

/* A table's filter bar. NOT a grid of equal columns, which is what this was: an equal column
   stretches the submit button to the width of a select, and a button as wide as a field reads as
   a field. Here the text input takes the slack, the select is only as wide as its options need,
   and the button is its own size. */
.sx-filters { display: flex; flex-wrap: wrap; align-items: end; gap: var(--sp-4); }
.sx-filters > .sx-field { flex: 0 1 12rem; }
.sx-filters > .sx-field--grow { flex: 1 1 18rem; }
.sx-filters > .sx-btn { flex: none; }
.sx-label { font-size: var(--text-sm); font-weight: 500; color: var(--fg-strong); }
.sx-help { font-size: var(--text-xs); color: var(--fg-muted); }
.sx-error { font-size: var(--text-xs); color: var(--accent-text); }

/*
  The honeypot: a field no person sees and every bot fills.

  IN THE THEME, not in a scoped stylesheet, and that is a correction rather than a preference.
  It lived in ContactForm.razor.css and EarlySignupForm.razor.css as two identical copies, each
  commented "a property of this one form, not part of the design system". That held while there
  were two forms. The third — the venue application — used the class, got no rule, and rendered a
  labelled "Website" input in the middle of the page.

  The failure is worse than an ugly field. A honeypot a person can see is a honeypot a person
  fills in, and every form here answers a filled one with a synthetic success and stores nothing —
  so a real venue would have applied, been thanked, and vanished. Any form may now use the class
  and get the behaviour.

  Deliberately NOT `display: none` or `hidden`: some bots skip fields that are obviously not
  rendered, and the trap only works on one that looks real to a script reading the DOM. Off-screen
  and zero-opacity leaves it in the layout tree, still fillable, and still invisible to a person.
  aria-hidden and tabindex="-1" on the element itself keep it off the assistive-technology and
  keyboard paths, so nobody reaches it by accident.
*/
.sx-honeypot {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

.sx-input, .sx-select, .sx-textarea {
  width: 100%; min-height: 36px; padding: 0 var(--sp-3);
  font: 400 var(--text-base)/1.4 var(--font-ui);
  color: var(--fg-strong); background: var(--bg-sunken);
  border: 1px solid var(--line); border-radius: var(--r-1);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.sx-textarea { padding: var(--sp-2) var(--sp-3); min-height: 96px; resize: vertical; }
.sx-input::placeholder, .sx-textarea::placeholder { color: var(--fg-muted); }
.sx-input:hover, .sx-select:hover, .sx-textarea:hover { border-color: var(--line-strong); }
.sx-input:focus, .sx-select:focus, .sx-textarea:focus { outline: none; border-color: var(--focus); background: var(--bg-raised); }
.sx-input[aria-invalid="true"] { border-color: var(--accent); }
.sx-select {
  appearance: none; padding-right: 32px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 16px) 50%, calc(100% - 11px) 50%;
  background-size: 5px 5px; background-repeat: no-repeat;
}
.sx-input--search { padding-left: 34px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%238F8F98' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: 11px 50%; }

/* A file input as a drop zone.

   The native control is the whole zone, stretched over it and transparent: a click anywhere opens
   the picker and a file dropped anywhere lands in it, with no script. What the eye sees is ours —
   a thumbnail (the saved photograph, the chosen one, or an icon) and two lines of text that switch
   once a file is chosen. The switch is the .is-chosen class venue-profile.js sets — the input is a
   field of a bigger form now and is not required, so :valid cannot tell chosen from empty — and
   the script writes the file's name and a preview alongside it. The old look was the browser's own control inside .sx-input's grey
   box, which read as a broken text field. */
.sx-upload {
  position: relative;
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3);
  background: var(--bg-sunken);
  border: 1px dashed var(--line-strong); border-radius: var(--r-2);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.sx-upload:hover { border-color: var(--fg-muted); background: var(--bg-hover); }
.sx-upload:has(.sx-upload__input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
.sx-upload.is-dragover { border-color: var(--accent); background: var(--accent-soft); }
.sx-upload.is-chosen { border-style: solid; border-color: var(--accent); background: var(--bg-raised); }

.sx-upload__input {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
}
.sx-upload__input::file-selector-button { cursor: pointer; }

.sx-upload__thumb {
  flex: none;
  display: grid; place-items: center;
  width: 96px; aspect-ratio: 4 / 3;
  color: var(--fg-muted);
  background: var(--bg-raised) center / cover no-repeat;
  border: 1px solid var(--line); border-radius: var(--r-1);
}
.sx-upload__thumb svg { width: 26px; height: 26px; }
.sx-upload__thumb[style*="background-image"] svg { display: none; }

.sx-upload__text { display: grid; gap: 2px; min-width: 0; }
/* Wraps rather than truncating: on a phone "Choose a different photograph" is wider than the
   zone, and a camera's file name is one unbroken run that has to be allowed to break anywhere. */
.sx-upload__title { font-weight: 600; color: var(--fg-strong); overflow-wrap: anywhere; }
.sx-upload__sub { font-size: var(--text-sm); color: var(--fg-muted); }

.sx-upload__chosen { display: none; }
.sx-upload.is-chosen .sx-upload__idle { display: none; }
.sx-upload.is-chosen .sx-upload__chosen { display: inline; }

/* ── Password reveal ──────────────────────────────────────────────────────────────────
   The button is rendered `hidden` and form.js unhides it, so JavaScript-off pages show a
   plain password box instead of a dead control. That makes the [hidden] rule below
   load-bearing rather than decorative: an author `display` beats the UA sheet's
   `[hidden] { display: none }`, so without it the button would be visible — and useless —
   exactly in the case the markup was written to avoid. */
.sx-password { position: relative; display: block; }
.sx-password .sx-input { padding-right: 40px; }
.sx-password__reveal {
  position: absolute; top: 0; right: 0; height: 100%; width: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; margin: 0; background: none; border: 0; border-radius: var(--r-1);
  color: var(--fg-muted); cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.sx-password__reveal[hidden] { display: none; }
.sx-password__reveal:hover { color: var(--fg-strong); }
.sx-password__reveal:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.sx-password__reveal svg { width: 17px; height: 17px; display: block; }
.sx-password__reveal .sx-password__off { display: none; }
.sx-password__reveal[aria-pressed="true"] .sx-password__on { display: none; }
.sx-password__reveal[aria-pressed="true"] .sx-password__off { display: block; }

/* The counterpart to .sx-btn--lg, so an input and a button can sit on one row and agree. */
.sx-input--lg { min-height: 44px; padding: 0 var(--sp-4); font-size: var(--text-base); }

.sx-switch { position: relative; display: inline-flex; align-items: center; gap: var(--sp-2); cursor: pointer; font-size: var(--text-sm); }
.sx-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.sx-switch__track { width: 34px; height: 20px; border-radius: var(--r-pill); background: var(--bg-sunken); border: 1px solid var(--line-strong); position: relative; transition: background var(--dur) var(--ease); }
.sx-switch__track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--fg-strong); transition: transform var(--dur) var(--ease); }
.sx-switch input:checked + .sx-switch__track { background: var(--accent); border-color: var(--accent); }
.sx-switch input:checked + .sx-switch__track::after { transform: translateX(14px); background: #fff; }
.sx-switch input:focus-visible + .sx-switch__track { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ---------- 10. Surfaces ---------- */
.sx-card {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  padding: var(--sp-5);
}
.sx-card--flush { padding: 0; overflow: hidden; }
.sx-card__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.sx-card--flush .sx-card__head { margin: 0; padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--line); }

/* The gutter a flush card gives up, put back for the parts of it that are NOT a full-bleed table.
   A card goes flush so a table can reach its edges — and .sx-table pays for its own gutter in the
   cell padding, so the table looks right while everything else in the card sits hard against the
   border. Prose and forms go in a body. */
.sx-card--flush .sx-card__body { padding: var(--sp-5); }

/* Two bodies in a row are one region that happens to be conditional, not two blocks. Without this
   the shared edge is padded twice and the seam shows as a gap nobody put there. */
.sx-card--flush .sx-card__body + .sx-card__body { padding-top: 0; }

/* A table has no bottom rule on its last row, so the form under it would otherwise float. */
.sx-card--flush .sx-table + .sx-card__body { border-top: 1px solid var(--line); }

/* A card for the one action on a page that cannot be undone.

   The border carries it rather than a fill: a tinted panel reads as a notice, and the whole point
   is that this is a control. The head takes the tint so the card is identifiable while scrolling
   past it, and stays legible in both modes because --accent-soft is defined for each. */
.sx-card--danger { border-color: var(--accent); }
.sx-card--danger > .sx-card__head { background: var(--accent-soft); border-bottom-color: var(--accent); }
.sx-card--danger > .sx-card__head h2 { color: var(--accent-text); }

/* A row of facts: a narrow label column and a value that gets everything else. Used where a table
   would be dishonest — these are one record's fields, not rows of the same kind of thing. */
.sx-facts { display: grid; grid-template-columns: minmax(7rem, auto) minmax(0, 1fr); gap: var(--sp-2) var(--sp-4); margin: 0; }
.sx-facts dt { font-size: var(--text-sm); color: var(--fg-muted); }
.sx-facts dd { margin: 0; overflow-wrap: anywhere; }

/* Badges and buttons that sit beside each other on one line and wrap together when the column is
   narrow — the roles card, where the count is whatever the account happens to hold. */
.sx-chips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }

.sx-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--r-2); }
.sx-stats > .sx-stat { padding: var(--sp-5); border-left: 1px solid var(--line); }
.sx-stats > .sx-stat:first-child { border-left: 0; }
.sx-stat { display: grid; gap: var(--sp-1); align-content: start; }
.sx-stat__label { font-size: var(--text-sm); color: var(--fg-muted); }
.sx-stat__value { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: 400; color: var(--fg-strong); line-height: 1; font-variant-numeric: tabular-nums; }
.sx-stat__delta { font-size: var(--text-sm); color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.sx-stat__delta.is-up { color: var(--ok); }
.sx-stat__delta.is-down { color: var(--accent-text); }

/* ---------- 11. Status ---------- */
.sx-badge {
  display: inline-flex; align-items: center; gap: 6px;
  height: 22px; padding: 0 var(--sp-2);
  border-radius: var(--r-pill);
  font-size: var(--text-xs); font-weight: 500; line-height: 1;
  color: var(--fg); background: var(--bg-sunken); border: 1px solid var(--line);
  white-space: nowrap;
}
.sx-badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--fg-muted); }
.sx-badge--ok::before { background: var(--ok); }
.sx-badge--warn::before { background: var(--warn); }
.sx-badge--live { color: var(--accent-text); border-color: var(--accent-soft); background: var(--accent-soft); }
.sx-badge--live::before { background: var(--accent); }
.sx-badge--flag { color: var(--accent-text); }
.sx-badge--flag::before { background: var(--accent); }

.sx-count { display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--r-pill); background: var(--accent); color: var(--accent-fg); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* ---------- 12. Table ---------- */
.sx-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.sx-table th { text-align: left; font-weight: 500; color: var(--fg-muted); padding: var(--sp-3) var(--sp-5); border-bottom: 1px solid var(--line); }
.sx-table td { padding: var(--sp-3) var(--sp-5); border-bottom: 1px solid var(--line); color: var(--fg); vertical-align: middle; white-space: nowrap; }
.sx-table .is-wrap { white-space: normal; }
.sx-table tbody tr:last-child td { border-bottom: 0; }
.sx-table tbody tr:hover td { background: var(--bg-hover); }
.sx-table .is-num { text-align: right; }
.sx-table .is-strong { color: var(--fg-strong); font-weight: 500; }

/* ---------- 13. Tabs ---------- */
.sx-tabs { display: flex; gap: var(--sp-5); border-bottom: 1px solid var(--line); }
.sx-tab { appearance: none; background: none; border: 0; padding: var(--sp-3) 0; margin-bottom: -1px; font: 500 var(--text-sm)/1 var(--font-ui); color: var(--fg-muted); border-bottom: 2px solid transparent; cursor: pointer; }
.sx-tab:hover { color: var(--fg-strong); }
.sx-tab[aria-selected="true"] { color: var(--fg-strong); border-bottom-color: var(--accent); }

/* ---------- 14. App shell (admin) ---------- */
.sx-shell { display: grid; grid-template-columns: var(--shell-rail) 1fr; min-height: 100dvh; }
.sx-rail {
  position: sticky; top: 0; height: 100dvh;
  display: flex; flex-direction: column;
  padding: var(--sp-4) var(--sp-3);
  background: var(--rail-bg); color: var(--sx-ash);
  border-right: 1px solid var(--sx-graphite);
}
/* The rail's head is the admin's app bar: the mark on the left, the colour-mode button on the
   right, the same lockup the public site carries. The padding that used to sit on the wordmark
   moved here, so the two sit on one baseline and the gap below the pair is unchanged. */
.sx-rail__head { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3) var(--sp-5); }
.sx-rail__home { margin-right: auto; text-decoration: none; }
.sx-rail .sx-wordmark { color: var(--sx-bone); font-size: var(--text-md); }

/* The rail is dark in both modes, but .sx-btn--ghost draws itself from --fg, which is NOT: in day
   mode --fg is #1A1A1C and the button would be black-on-black. The rail's controls take the
   on-ink scale explicitly, the way every other thing in the rail already does.

   EVERY ghost button in the rail, not just the colour-mode one. Sign out is a .sx-btn--ghost in
   the foot and had exactly the defect this comment describes — invisible in day mode, which is the
   mode the admin opens in. Scoping the fix to one control left the other one broken. */
.sx-rail .sx-btn--ghost { --btn-fg: var(--sx-ash); --btn-line: var(--sx-graphite); }
.sx-rail .sx-btn--ghost:hover { --btn-bg: var(--sx-ink-3); --btn-fg: var(--sx-bone); }

/* A section label, and it has to look like one. These read as disabled nav items when they sit at
   the links' own size and indent and differ only by being dimmer — the eye takes "Waitlist" for
   something it cannot click rather than the heading over the two things it can. Small caps, a
   hairline above, and room to breathe say "heading" without needing the colour to carry it. */
.sx-rail__group {
  margin-top: var(--sp-4);
  padding: var(--sp-4) var(--sp-3) var(--sp-2);
  border-top: 1px solid var(--sx-graphite);
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--sx-smoke);
}
.sx-rail__link {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 8px var(--sp-3); border-radius: var(--r-1);
  font-size: var(--text-sm); color: var(--sx-ash); text-decoration: none;
}
.sx-rail__link:hover { background: var(--sx-ink-3); color: var(--sx-bone); text-decoration: none; }
.sx-rail__link[aria-current="page"] { background: var(--sx-ink-3); color: var(--sx-bone); box-shadow: inset 2px 0 0 var(--sx-red); }
.sx-rail__link svg { width: 16px; height: 16px; flex: none; opacity: .8; }
.sx-rail__link .sx-count { margin-left: auto; }
.sx-rail__foot {
  margin-top: auto;
  display: grid; gap: var(--sp-2); justify-items: start;
  padding: var(--sp-4) var(--sp-3) var(--sp-2);
  border-top: 1px solid var(--sx-graphite);
  font-size: var(--text-xs); color: var(--sx-smoke);
}

/* The signed-in address, which is a real address and can be long. The rail is 232px, so it clips
   rather than pushing the whole column wider — a name is worth showing, not worth a layout. */
.sx-rail__foot > div { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The venue section's small print, which had no rule at all — so it sat hard against whatever
   paragraph happened to end the page and read as that paragraph's last line. */
.vn-shell__terms { margin-top: var(--sp-7); }

/* The venue's own links and Sign out, as one quiet row above the page. It had no rule at all: the
   link and the form stacked, and Sign out hung under "Your venue" like a subtitle of the page. The
   links are muted rather than red so the row reads as navigation, not as a call to action. No
   max-width: signed out it is a p, and the prose measure would right-align it mid-page. */
.vn-shell__account {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  gap: var(--sp-2) var(--sp-5);
  max-width: none;
  margin: 0 0 var(--sp-6);
  font-size: var(--text-sm);
}
.vn-shell__account > a { color: var(--fg-muted); text-decoration: none; }
.vn-shell__account > a:hover,
.vn-shell__account > a[aria-current="page"] { color: var(--fg-strong); }
.vn-shell__signout { margin: 0; }

/* The venue section's single column: sign-in, join, reset, commit, the portal. This class was on
   all of them and defined nowhere, so a password field ran the full width of the window it was
   opened in. 36rem is the measure the hero lede already reads at (40ch), and a form should never
   be wider than the prose above it. Left-aligned, like everything else in the section. */
.vn-narrow { max-width: 36rem; }

.sx-main { min-width: 0; }
.sx-topbar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: var(--sp-4);
  height: var(--shell-topbar); padding: 0 var(--sp-6);
  background: var(--bg); border-bottom: 1px solid var(--line);
}
.sx-topbar .sx-input--search { max-width: 360px; }
.sx-topbar__spacer { flex: 1; }
.sx-page { max-width: var(--content-max); padding: var(--sp-6); }
.sx-page__head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-4); margin-bottom: var(--sp-6); }
.sx-page__head p { margin-top: var(--sp-1); color: var(--fg-muted); }

.sx-grid { display: grid; gap: var(--sp-4); }
.sx-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.sx-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.sx-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.sx-grid--main { grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); }

/* ---------- 15. Marketing / demo shell ---------- */
.sx-site { max-width: var(--content-max); margin: 0 auto; padding: 0 var(--sp-6); }
/*
  Sticky, like the admin .sx-topbar above. The colour-mode button lives in this bar and nowhere
  else, so on a short page like /early it is always to hand and on a long one — /investors, and
  the client section's walk-through and gallery — it used to scroll away and leave the reader with
  navigation that had no way to change mode. The button is not a per-page control; it belongs to
  the site, so the bar that carries it stays.

  No negative margin to bleed the ground to the viewport edge, though it is tempting: .sx-site's
  gutter is not the same width at every breakpoint, so cancelling it with a fixed --sp-6 overshot
  and put 16px of horizontal scroll on the whole document. It buys nothing either — the gutter is
  empty on both sides of the bar and the page below it, so nothing ever scrolls through it, and
  the bar's ground is var(--bg), which is exactly what would show there anyway.
*/
.sx-site__nav {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: var(--sp-6); height: 72px;
  background: var(--bg);
}
/*
  NO border-bottom, and that is the point. It used to have one, drawn in exactly the declaration
  the page's own section rules use — 1px solid var(--line). Because the bar is sticky and opaque,
  a section's border-bottom scrolls UP and passes under it, and for the moment before it vanishes
  it sits 20px below the bar's own border: two identical hairlines, one nailed to the viewport and
  one drifting, which reads as a rendering fault rather than as structure. The bar has an opaque
  ground; it does not need a line as well.

  What replaces it is a short dissolve, so content thins out into the bar instead of being sliced
  at an invisible edge. Positioned against the bar itself (sticky counts as positioned) and sitting
  at top:100%, so it hangs BELOW the bar and inherits the bar's stacking context — that is what
  puts it over the page. Left and right are the bar's own width, not the viewport's, for the same
  reason the note above declines the negative margin.

  The second colour stop is the awkward part and must stay as written. The idiomatic
  `linear-gradient(var(--bg), transparent)` is wrong here, because CSS `transparent` means
  transparent BLACK, not transparent paper. In dark mode the ground is already near-black so
  nothing shows; in light mode the same gradient runs from rgb(233,230,224) through a midpoint of
  rgb(174,172,168) — a dirty grey band across the top of every page, 59 channel values off the
  ground it is supposed to be dissolving into. `color-mix(..., transparent 100%)` does not fix it
  either; it resolves to transparent black too. Relative colour syntax is what actually holds the
  hue and drops only the alpha, and it re-resolves per theme on its own: rgb(10,10,11)/0 on ink,
  rgb(233,230,224)/0 on paper, measured drift 0 in both.

  A browser without relative colour syntax invalidates the whole declaration and draws no fade,
  which lands on a bar with no line and no dissolve — the plain version of this fix, and a fine
  place to fall back to. Do not add a bare-`transparent` fallback underneath: it would "work"
  everywhere and smear on paper, which is the bug this comment exists to prevent.
*/
.sx-site__nav::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%;
  height: var(--sp-5); pointer-events: none;
  background: linear-gradient(to bottom, var(--bg), rgb(from var(--bg) r g b / 0));
}
.sx-site__nav .sx-wordmark { font-size: var(--text-lg); }
/* The mark is a mark, not a text link — the site footer already declined the underline and the
   bar was the last place still taking it. */
.sx-site__nav a:hover, .sx-rail__home:hover { text-decoration: none; }
.sx-site__nav > .sx-btn:last-child { margin-left: auto; }
.sx-site__links + .sx-btn { margin-left: 0; }
.sx-site__links { display: flex; gap: var(--sp-5); margin-left: auto; font-size: var(--text-sm); }
.sx-site__actions { display: flex; align-items: center; gap: var(--sp-5); margin-left: auto; font-size: var(--text-sm); }
.sx-site__actions-link { color: var(--fg); }
.sx-site__actions-link:hover { color: var(--fg-strong); text-decoration: none; }
.sx-site__links a { color: var(--fg); }
.sx-site__links a:hover { color: var(--fg-strong); text-decoration: none; }
.sx-hero { padding: var(--sp-9) 0 var(--sp-8); border-bottom: 1px solid var(--line); }
.sx-hero__grid { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-8); align-items: end; }
.sx-hero__rule { width: 64px; height: 2px; background: var(--accent); margin: var(--sp-5) 0; }
.sx-hero__lede { font-size: var(--text-lg); line-height: 1.4; color: var(--fg); max-width: 40ch; }
.sx-hero__actions { display: flex; gap: var(--sp-3); margin-top: var(--sp-6); flex-wrap: wrap; }
/* No rule under it. The padding stays, so the spacing is unchanged — what goes is a line that
   had nothing to separate: on the legal pages it sat between the last paragraph and the Contact
   Us heading, and on the walk-through it drew one divider a short way above the contact form's
   own. Both pages end on space now, like the rest of the site. */
.sx-section { padding: var(--sp-8) 0; }

/*
  A heading belongs to the text under it. It had 32px below and 16px above — the 16 being nothing
  but the previous paragraph's own margin — so every heading sat twice as close to the paragraph
  it did NOT introduce, and the page read as a list of floating titles.

  48 above, 24 below. Sibling margins collapse to the larger of the two, so the 48 is what lands
  between a paragraph and the next heading rather than 48 + 16.
*/
.sx-section > h2 {
    margin-top: var(--sp-7);
    margin-bottom: var(--sp-5);
}

/* Nothing above the first one: the rule under the h1 already spaces it. */
.sx-section > h2:first-of-type {
    margin-top: 0;
}
.sx-feature { display: grid; gap: var(--sp-2); padding-top: var(--sp-4); border-top: 1px solid var(--line-strong); }
.sx-feature h3 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-lg); }
.sx-feature p { color: var(--fg-muted); font-size: var(--text-base); }
.sx-feature .sx-badge { justify-self: start; margin-top: var(--sp-1); }
/* Wraps. The two groups are a row where there is room and a stack where there is not — without
   this the legal links ran 7px past the right edge at 390px and put a horizontal scroll on every
   page of the site, since this footer is now on all of them. */
.sx-footer { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--sp-3) var(--sp-5); padding: var(--sp-6) 0; font-size: var(--text-sm); color: var(--fg-muted); }
.sx-footer .sx-wordmark { font-size: var(--text-sm); color: var(--fg-muted); }
/* space-between rather than margin-left:auto on this group: auto pushes it right on whatever line
   it lands on, so once wrapped the links sat right-aligned under a left-aligned wordmark.
   space-between puts them at the far end while they share a line and leaves them flush left once
   they do not. */
.sx-footer__links { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }

/* ---------- 16. Phone frame (demo) ---------- */
.sx-phone {
  width: 260px; aspect-ratio: 9 / 19.5;
  background: var(--sx-ink); color: var(--sx-bone);
  border: 6px solid #2A2A2F; border-radius: 36px;
  /* Two layers doing different jobs: the 1px spread is the chamfer on the chassis edge
     and stays dark in both modes (it is hardware), the second is the cast shadow and
     follows the page. */
  box-shadow: 0 0 0 1px #0A0A0B, 0 30px 60px -20px var(--app-shadow);
  display: grid; place-items: center; position: relative; overflow: hidden;
}
.sx-phone::before { content: ""; position: absolute; top: 10px; width: 80px; height: 22px; border-radius: 999px; background: #0A0A0B; left: 50%; transform: translateX(-50%); }
.sx-phone::after { content: ""; position: absolute; inset: -40%; background: radial-gradient(circle at 50% 50%, var(--sx-glow), transparent 55%); opacity: .6; pointer-events: none; }
.sx-phone .sx-lockup { z-index: 1; gap: var(--sp-2); }
.sx-phone .sx-monogram { font-size: 72px; }
.sx-phone .sx-wordmark { font-size: 18px; }

/* ---------- 17. Responsive ---------- */
@media (max-width: 1024px) {
  .sx-grid--4, .sx-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sx-stats > .sx-stat:nth-child(odd) { border-left: 0; }
  .sx-stats > .sx-stat:nth-child(n+3) { border-top: 1px solid var(--line); }
  .sx-grid--main { grid-template-columns: 1fr; }
}
@media (max-width: 800px) {
  .sx-shell { grid-template-columns: 1fr; }
  .sx-rail { position: static; height: auto; flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--sp-1); border-right: 0; border-bottom: 1px solid var(--sx-graphite); }
  .sx-rail .sx-wordmark { padding: var(--sp-2) var(--sp-3); }
  .sx-rail__group, .sx-rail__foot { display: none; }
  .sx-grid--3, .sx-grid--2 { grid-template-columns: 1fr; }
  .sx-page { padding: var(--sp-4); }
  .sx-page__head { flex-direction: column; align-items: flex-start; }
  .sx-site { padding: 0 var(--sp-4); }
  .sx-site__links { display: none; }
  .sx-hero { padding: var(--sp-7) 0 var(--sp-6); }
  .sx-hero__grid { grid-template-columns: 1fr; }
  .sx-hero__grid .sx-phone { display: none; }
}
@media (max-width: 560px) {
  .sx-grid--4, .sx-stats { grid-template-columns: 1fr; }
  .sx-stats > .sx-stat { border-left: 0; }
  .sx-stats > .sx-stat + .sx-stat { border-top: 1px solid var(--line); }
  .sx-topbar { padding: 0 var(--sp-4); }
}
