/* ============================================================================
   COMPONENTS — Nocturne on this site's markup.
   ============================================================================

   Order: reset and base type, then Nocturne's own component classes, then the
   utilities, the site chrome, the checkout wizard, and the product mockups.
   Page-level composition lives in pages.css.

   SELECTOR DISCIPLINE, unchanged from the previous build: one class per rule,
   no element selectors outside the base block, no nesting deeper than a state
   or a direct child. It is what lets a page be read without holding this file
   in your head.

   NOCTURNE SIGNATURES. Three things carry the system, and none of them are
   optional decoration:
     1. Freestanding rules and table row strips FADE TO TRANSPARENT over
        --rule-fade at each end. Box outlines and in-control separators stay
        solid. Getting this wrong is what makes a dark page look merely dark.
     2. The primary action is an ACCENT OUTLINE on transparent. Never a fill.
     3. Headings stop at weight 500. Hierarchy is size and space.
   ========================================================================= */

*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.55;
  font-weight: 400;
  /* The redesign sets long-form measure in ch and leans on pretty wrapping for
     headings; both are progressive and cost nothing where unsupported. */
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  line-height: 1.12;
  letter-spacing: -0.02em;
  margin: 0 0 var(--space-2);
  text-wrap: pretty;
}
h1 { font-size: var(--step-5); line-height: 1.06; letter-spacing: -0.028em; }
h2 { font-size: var(--step-4); line-height: 1.1; }
h3 { font-size: var(--step-3); letter-spacing: -0.015em; }
h4 { font-size: var(--step-2); letter-spacing: -0.015em; }
h5 { font-size: var(--step-1); }
h6 { font-size: var(--step--1); letter-spacing: 0.08em; text-transform: uppercase; }

p { margin: 0 0 var(--space-3); text-wrap: pretty; }

a { color: var(--accent); text-decoration: none; text-underline-offset: 3px; }
a:hover { color: var(--accent-bright); text-decoration: underline; }

img, svg { display: block; max-width: 100%; }
figure { margin: 0; }
strong, b { font-weight: 500; color: var(--ink); }
code, kbd, samp, pre { font-family: var(--font-mono); }

/* Themed interaction states, never browser defaults. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
::selection { background: color-mix(in srgb, var(--accent) 30%, transparent); }

/* ---------------------------------------------------------------------------
   Rules.

   The end-fade is the signature. It is painted from a gradient rather than a
   border, because a border cannot fade — which is also why the table below
   paints its row rules as background strips on the <tr> instead of borders on
   each <td>: a per-cell border would restart the fade at every column.
   --------------------------------------------------------------------------- */
.hr {
  height: 1px;
  border: 0;
  margin: var(--space-6) 0;
  background: linear-gradient(to right,
    transparent,
    var(--color-divider) var(--rule-fade),
    var(--color-divider) calc(100% - var(--rule-fade)),
    transparent);
}

/* ============================ BUTTONS ==================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  text-decoration: none;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 14px;
  line-height: 1.2;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
  transition: background-color .16s, border-color .16s, color .16s;
}
.btn:hover { text-decoration: none; }
.btn:disabled,
.btn[aria-disabled='true'] { opacity: .45; cursor: not-allowed; }

/* An accent OUTLINE. Nocturne never floods a primary action with colour. */
.btn-primary { color: var(--accent); border-color: var(--accent); }
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
.btn-primary:active { background: color-mix(in srgb, var(--accent) 22%, transparent); }

.btn-secondary { border-color: var(--color-divider); color: var(--ink); }
.btn-secondary:hover { background: color-mix(in srgb, var(--ink) 7%, transparent); color: var(--ink); }
.btn-secondary:active { background: color-mix(in srgb, var(--ink) 14%, transparent); }

.btn-ghost { color: var(--accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--accent) 18%, transparent); }

.btn-icon { width: 36px; height: 36px; padding: 0; }
.btn-block { width: 100%; margin-top: var(--space-2); }
.btn-lg { font-size: 15px; padding: 11px 20px; }

/* ============================== TAGS ===================================== */

.tag {
  display: inline-flex;
  align-items: center;
  font-size: var(--step--1);
  letter-spacing: 0.02em;
  padding: 3px 10px;
  border-radius: calc(var(--radius-md) * 0.75);
  white-space: nowrap;
}
.tag-accent { background: var(--color-accent-800); color: var(--color-accent-100); }
.tag-neutral { background: var(--color-neutral-800); color: var(--color-neutral-100); }
.tag-outline { border: 1px solid var(--accent); color: var(--accent); }
/* The "off" state of a tier badge: present, so the row keeps its shape, but
   clearly not granted. */
.tag-muted { background: var(--surface-3); color: var(--ink-4); }

/* ============================== FORMS ==================================== */

.field { display: block; }
.field > label {
  display: block;
  font-size: 12px;
  margin-bottom: 5px;
  color: var(--ink-2);
}
.field-req { color: var(--accent); }
.field-note { font-size: var(--step--1); color: var(--ink-3); margin-top: 5px; }
.field-error {
  font-size: var(--step--1);
  color: var(--err);
  margin: 5px 0 0;
}

.input {
  width: 100%;
  min-height: 36px;
  padding: 6px 10px;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  caret-color: var(--accent);
  background: var(--surface-3);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  transition: border-color .16s;
}
.input::placeholder { color: var(--ink-4); }
.input:hover { border-color: color-mix(in srgb, var(--ink) 45%, transparent); }
.input:focus-visible { border-color: var(--accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }
.field-mono { font-family: var(--font-mono); }

.field-checkbox { position: relative; font-size: 13px; line-height: 1.5; color: var(--ink-2); }
/* The label is the whole hit area, so the box and the sentence are one target. */
.field-checkbox > label { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; }
/* Invisible but still the size and place of the box drawn over it, NOT
   collapsed to 0x0 like the toggle grid's. This one is `required`, and Chrome
   refuses to validate a control it cannot focus: a zero-size checkbox makes
   Next do nothing at all, with the reason only in the console. Left here it is
   focusable, and the validation bubble points at the box the eye is on. */
.field-checkbox > input {
  position: absolute; top: 2px; left: 0;
  width: 16px; height: 16px; margin: 0;
  opacity: 0;
}

.radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.radio input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
.radio .dot {
  width: 16px; height: 16px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--color-divider);
  transition: border-color .16s, background-color .16s;
}
.radio:hover .dot { border-color: var(--accent); }
.radio input:checked + .dot {
  border-color: var(--accent);
  background: var(--accent);
  box-shadow: inset 0 0 0 4px var(--bg);
}
.radio input:focus-visible + .dot { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ============================== CARDS ==================================== */

.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-6) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  box-shadow: var(--shadow-sm);
}
.card-kicker {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}
.card-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--step-1);
  line-height: 1.2;
  letter-spacing: -0.01em;
}
.card-body { margin: 0; font-size: 13px; line-height: 1.6; color: var(--ink-2); flex: 1; }
.card-meta { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--ink-3); }

.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* ============================== TABLES =================================== */

.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: var(--space-2);
  border-bottom: 1px solid transparent; /* holds the layout; the row paints the rule */
  font-weight: 400;
}
.table td { padding: var(--space-2); border-bottom: 1px solid transparent; }

/* Row rules as row-level background strips, so one fade spans the whole row
   rather than restarting at every cell boundary. */
.table thead tr {
  background: linear-gradient(to right,
    transparent,
    var(--color-divider) var(--rule-fade),
    var(--color-divider) calc(100% - var(--rule-fade)),
    transparent) no-repeat bottom / 100% 1px;
}
.table tbody tr {
  background: linear-gradient(to right,
    transparent,
    var(--line-faint) var(--rule-fade),
    var(--line-faint) calc(100% - var(--rule-fade)),
    transparent) no-repeat bottom / 100% 1px;
}
.table tbody tr:hover {
  background:
    linear-gradient(color-mix(in srgb, var(--ink) 4%, transparent),
                    color-mix(in srgb, var(--ink) 4%, transparent)) no-repeat 0 0 / 100% 100%,
    linear-gradient(to right,
      transparent,
      var(--line-faint) var(--rule-fade),
      var(--line-faint) calc(100% - var(--rule-fade)),
      transparent) no-repeat bottom / 100% 1px;
}
.table-num { text-align: right; font-variant-numeric: tabular-nums; }

/* ============================ UTILITIES ================================== */

.wrap { max-width: var(--wrap); margin: 0 auto; padding-inline: var(--gutter); }
.wrap-narrow { max-width: var(--wrap-narrow); margin: 0 auto; padding-inline: var(--gutter); }

/* The structural voice: a small uppercase label above a heading. It is what
   tells you which of the three pillars a section belongs to. */
.eyebrow {
  display: block;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-bright);
  margin-bottom: var(--space-4);
}
.eyebrow-sm { font-size: 10px; letter-spacing: 0.12em; color: var(--accent); }
.text-muted { color: var(--ink-2); }
.lede { font-size: var(--step-1); line-height: 1.6; color: var(--ink-2); }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 10px 16px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-md);
  border-radius: 0 0 var(--radius-md) 0;
}
.skip-link:focus { left: 0; }

/* ============================== ALERTS =================================== */

.alert {
  padding: var(--space-4) var(--space-6);
  border-radius: var(--radius-md);
  font-size: 13.5px;
  line-height: 1.6;
  background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--line-2);
  margin-bottom: var(--space-6);
}
.alert-error { background: color-mix(in srgb, var(--err) 10%, var(--surface-2)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--err) 40%, transparent); }
.alert-notice,
.alert-info { background: var(--accent-soft); box-shadow: 0 0 0 1px var(--accent-line); }
.alert-ok { background: color-mix(in srgb, var(--ok) 10%, var(--surface-2)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--ok) 40%, transparent); }
/* The DRAFT banner on the legal pages. It is loud on purpose and it disappears
   by itself when GMD_LEGAL_REVIEWED goes true — the one thing standing between
   an unreviewed privacy policy and a published one. */
.alert-draft {
  background: color-mix(in srgb, var(--warn) 12%, var(--surface-2));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--warn) 45%, transparent);
}

/* ------------------------------------------------------------------------
   Long-form pages: privacy, terms, a blog post, the 404.

   These are not composed layouts, they are documents — so they get one
   measure and a heading rhythm rather than a grid, and they read the same
   type scale as everything else.
   ------------------------------------------------------------------------ */
.page-head { padding-block: 72px 24px; }
.page-head h1 { margin-bottom: var(--space-4); }

.prose { max-width: 68ch; }
.prose h2 { font-size: var(--step-3); margin: 44px 0 var(--space-4); }
.prose h3 { font-size: var(--step-2); margin: 32px 0 var(--space-3); }
.prose h2:first-child, .prose h3:first-child { margin-top: 0; }
.prose p, .prose li { font-size: var(--step-0); line-height: 1.75; color: var(--ink-2); }
.prose ul, .prose ol { padding-left: 1.25em; margin: 0 0 var(--space-6); }
.prose li { margin-bottom: var(--space-2); }
.prose strong { color: var(--ink); }
.prose dt { color: var(--ink); margin-top: var(--space-4); }
.prose dd { margin: 0 0 var(--space-3); color: var(--ink-2); }

/* A wide table inside prose scrolls in its own box rather than pushing the
   page sideways — the body must never scroll horizontally. */
.table-scroll { overflow-x: auto; margin-bottom: var(--space-6); }

/* The 404's signpost cards. */
.card-link {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-6) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  border: 1px solid var(--line-faint);
  color: var(--ink);
  transition: border-color .16s;
}
.card-link:hover { border-color: var(--accent); text-decoration: none; color: var(--ink); }
.card-link strong { display: block; font-size: var(--step-1); font-weight: 500; }
.card-link span { font-size: 13px; line-height: 1.6; color: var(--ink-2); }

/* The environment banner. Loud on purpose — it is the only thing standing
   between "this is the test box" and someone quoting a test price. */
.env-banner {
  background: var(--accent-soft);
  color: var(--color-accent-200);
  border-bottom: 1px solid var(--accent-line);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  padding: 6px var(--gutter);
}

/* ============================== HEADER =================================== */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line-faint);
}
.header-inner {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 14px var(--gutter);
  display: flex;
  align-items: center;
  gap: 28px;
}
.logo {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-heading);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  transition: opacity .16s;
}
.logo:hover { opacity: .75; color: var(--ink); text-decoration: none; }
.logo svg { flex: none; }

.site-nav { display: flex; align-items: center; margin-right: auto; }
.site-nav-list {
  display: flex;
  gap: 4px;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-nav a {
  font-size: 12.5px;
  white-space: nowrap;
  padding: 6px 9px;
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  transition: background-color .16s, color .16s;
}
.site-nav a:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent); text-decoration: none; }
.site-nav a[aria-current='page'] { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
.nav-cta { font-size: 13px; }

/* The toggle is display:none above the breakpoint rather than absent, so the
   nav markup is identical at every width and there is one thing to test. */
.nav-toggle {
  display: none;
  width: 38px; height: 38px;
  align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  cursor: pointer;
}
.nav-toggle-bars { position: relative; display: block; width: 16px; height: 1.5px; background: var(--ink); }
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  content: ''; position: absolute; left: 0; width: 16px; height: 1.5px; background: var(--ink);
}
.nav-toggle-bars::before { top: -5px; }
.nav-toggle-bars::after { top: 5px; }

/* ============================== FOOTER =================================== */

.site-footer {
  border-top: 1px solid var(--line-faint);
  background: var(--bg-sunken);
  margin-top: auto;
}
.footer-grid {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 36px var(--gutter);
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
  align-items: center;
}
.footer-brand { font-size: 13px; color: var(--ink-3); margin-right: auto; }
.footer-grid a { font-size: 12.5px; color: var(--ink-3); }
.footer-grid a:hover { color: var(--accent); }
.footer-bottom {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 var(--gutter) 28px;
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--ink-4);
}

/* ========================== CHECKOUT WIZARD ============================== */
/* The wizard's markup is unchanged from the previous build — it is real form
   POSTs with htmx layered on top, and none of that moved. Only the paint did. */

.wiz-layout { display: grid; grid-template-columns: 1fr 336px; gap: 28px; align-items: start; }
.wiz-main { min-width: 0; position: relative; }
/* #wiz is htmx's swap target AND the panel, so the card surface lives on it —
   a wrapper would leave the border painting round content that had been
   replaced with a different height. */
#wiz {
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--line);
  overflow: hidden;
}
.wiz-side { position: sticky; top: calc(var(--header-h) + 27px); display: flex; flex-direction: column; gap: 14px; }

/* The htmx request indicator. htmx toggles .htmx-request on the element named
   by hx-indicator; includeIndicatorStyles is off in the config, so the
   transition is ours rather than injected into the document by the library. */
.wiz-busy {
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 2px;
  opacity: 0;
  background: linear-gradient(to right, transparent, var(--accent), transparent);
  transition: opacity .2s;
  pointer-events: none;
}
.wiz-busy.htmx-request { opacity: 1; }

/* The step rail is an <ol> with aria-current, not a row of divs — a screen
   reader should hear an ordered list of stages. Completed steps are links, so
   the styling has to work both with and without an <a> inside the <li>. */
.wiz-steps { border-bottom: 1px solid var(--line-faint); }
.wiz-steps ol { display: flex; list-style: none; margin: 0; padding: 0; }
.wiz-steps li {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 13px;
  text-align: center;
  color: var(--ink-4);
  border-bottom: 2px solid transparent;
  transition: color .18s, border-color .18s;
}
/* Padding goes on whichever element is the row, so a completed step's link
   covers the whole hit area rather than just its text. */
.wiz-steps li > a { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 8px; color: inherit; }
.wiz-steps li > a:hover { color: var(--accent); text-decoration: none; }
.wiz-steps li:not(:has(> a)) { padding: 14px 8px; }
.wiz-steps li.is-done { color: var(--ink-2); }
.wiz-steps li.is-current { color: var(--ink); border-bottom-color: var(--accent); }
.wiz-step-n {
  width: 20px; height: 20px; flex: none;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 10.5px;
  color: var(--ink-4);
  box-shadow: inset 0 0 0 1px var(--color-divider);
  transition: background-color .18s, color .18s, box-shadow .18s;
}
.is-done .wiz-step-n { background: var(--accent); color: var(--bg); box-shadow: none; }
.is-current .wiz-step-n { color: var(--accent); box-shadow: inset 0 0 0 1.5px var(--accent); }

.wiz-form { padding: 28px 26px 30px; }
/* The form is a stack of fields, so the gap belongs to the stack rather than to
   each template remembering a margin. 520px because a name and an email address
   are short, and a text box the width of the panel invites a paragraph. */
.wiz-form .field { max-width: 520px; }
.wiz-form .field + .field { margin-top: 18px; }
.wiz-title { font-size: var(--step-2); margin: 0 0 6px; }
.wiz-lede { font-size: 13.5px; color: var(--ink-2); margin: 0 0 26px; }
.wiz-notice {
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--ink-3);
  padding: 13px 15px;
  border-radius: var(--radius-md);
  background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--line-2);
  margin: var(--space-6) 0 0;
}
.wiz-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 30px;
  padding-top: 22px;
  border-top: 1px solid var(--line-faint);
}
.wiz-actions .btn-primary,
.wiz-next { margin-left: auto; }
/* Pushed to the far edge of its row rather than sitting against the value it
   edits, which read as part of the value: "Acme AnalyticsChange". */
.wiz-edit { margin-left: auto; font-size: 12.5px; color: var(--accent); }

/* -- plan cards (step 1's fallback list; the builder sits above them) -- */
.wiz-plans { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.wiz-plan { display: block; cursor: pointer; }
.wiz-plan input { position: absolute; opacity: 0; width: 0; height: 0; }
.wiz-plan-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  height: 100%;
  padding: 22px 20px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--line);
  transition: box-shadow .2s;
}
.wiz-plan:hover .wiz-plan-body { box-shadow: 0 0 0 1px var(--accent-line); }
.wiz-plan.is-selected .wiz-plan-body { box-shadow: 0 0 0 1.5px var(--accent); }
.wiz-plan input:focus-visible + .wiz-plan-body { outline: 2px solid var(--accent); outline-offset: 2px; }
.wiz-plan-badge {
  align-self: flex-start;
  font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 5px;
  background: var(--color-neutral-900); color: var(--ink-2);
}
.wiz-plan-name { font-size: var(--step-2); font-weight: 500; letter-spacing: -0.015em; }
.wiz-plan-price { font-size: 26px; font-weight: 500; letter-spacing: -0.02em; }
.wiz-plan-period { font-size: 12px; color: var(--ink-3); margin-left: 4px; }
.wiz-plan-blurb { font-size: 12.5px; line-height: 1.55; color: var(--ink-2); }
.wiz-plan-note { font-size: var(--step--1); color: var(--ink-3); }
.wiz-plan-features { display: flex; flex-direction: column; gap: 7px; margin-top: var(--space-2); }
.wiz-feature { font-size: 12.5px; line-height: 1.5; color: var(--ink-2); padding-left: 14px; position: relative; }
.wiz-feature::before { content: '·'; position: absolute; left: 2px; color: var(--color-accent-600); }
.wiz-feature.is-key { color: var(--ink); font-weight: 500; }

/* -- add-ons -- */
.wiz-addons { display: flex; flex-direction: column; gap: 10px; }
.wiz-addon {
  display: flex; gap: 16px; align-items: center;
  padding: 15px 16px;
  border-radius: var(--radius-md);
  background: var(--surface-2);
  border: 1px solid var(--line-faint);
  transition: background-color .16s, border-color .16s;
}
.wiz-addon.is-on,
.wiz-addon:has(input:checked) { background: color-mix(in srgb, var(--accent) 8%, transparent); border-color: var(--accent); }
.wiz-addon-main { flex: 1; min-width: 0; }
.wiz-addon-head { display: flex; align-items: center; gap: 8px; }
.wiz-addon-name { font-size: 14px; color: var(--ink); cursor: pointer; }
/* 'quantity' / 'toggle' -- which control the row carries. */
.wiz-addon-kind { font-size: 9.5px; letter-spacing: 0.08em; text-transform: uppercase; padding: 2px 7px; }
/* The whole square is the hit area, not just the 16px box inside it. */
.wiz-addon-check { display: grid; place-items: center; width: 34px; height: 30px; cursor: pointer; }
.wiz-addon-blurb { font-size: var(--step--1); line-height: 1.5; color: var(--ink-3); margin-top: 3px; max-width: 58ch; }
.wiz-addon-side { display: flex; align-items: center; gap: 12px; flex: none; }
.wiz-addon-price { font-size: 13px; color: var(--ink-2); white-space: nowrap; }
.wiz-addon-qty { width: 68px; text-align: center; }
.wiz-included { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--space-4); }

/* -- review + pay -- */
.wiz-review { display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--space-8); }
.wiz-review-row {
  display: flex; align-items: baseline; gap: var(--space-4);
  padding: 11px 4px;
  font-size: 13.5px;
  background: linear-gradient(to right,
    transparent, var(--line-faint) var(--rule-fade),
    var(--line-faint) calc(100% - var(--rule-fade)), transparent) no-repeat bottom / 100% 1px;
}
/* A <dl>, so the browser's own 40px dd indent has to go and the two halves
   have to be told to line up: without this the values started at whatever
   width the label happened to be, and "Workspace address" pushed its row out
   of column with every other one. */
.wiz-review-row > dt { flex: none; width: 150px; color: var(--ink-3); }
.wiz-review-row > dd { flex: 1; min-width: 0; margin: 0; display: flex; align-items: baseline; gap: var(--space-3); }
.wiz-paysummary { display: flex; flex-direction: column; gap: 12px; max-width: 600px; }
.wiz-paytotal {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 14px 16px;
  border-radius: var(--radius-md);
  background: var(--surface-3);
  box-shadow: 0 0 0 1px var(--line);
  font-size: 20px; font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.wiz-paytotal-period { font-size: 14px; font-weight: 400; color: var(--ink-2); }
.wiz-paynote { font-size: var(--step--1); line-height: 1.6; color: var(--ink-3); }

/* -- the rail --
   It IS the builder's recommendation card, so it is built from .rec-* in
   pages.css rather than a parallel set of .wiz-total-* rules that said the same
   thing in slightly different numbers. A column because builder_rail() emits
   the card, its note and any warnings as siblings; .wiz-side around it does the
   sticking. The card's eyebrow is an <h2> so the rail keeps a place in the
   document outline, and h2's own weight and margin are not wanted. */
.wiz-total { display: flex; flex-direction: column; gap: 14px; }
.wiz-total .rec-head > h2 { font-weight: 400; margin: 0; }

/* -- the workspace slug field -- */
.slug-input { display: flex; align-items: stretch; }
.slug-input .input { border-radius: var(--radius-md) 0 0 var(--radius-md); border-right: none; }
.slug-suffix {
  display: flex; align-items: center;
  padding: 0 11px;
  font-size: 12.5px;
  color: var(--ink-3);
  background: var(--surface-2);
  border: 1px solid var(--color-divider);
  border-left: none;
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  white-space: nowrap;
}
.slug-status { font-size: var(--step--1); margin-top: 6px; color: var(--accent); min-height: 1.2em; }
.slug-status-bad { color: var(--err); }

/* ========================== PRODUCT MOCKUPS ============================== */
/* Built in HTML from these tokens, never screenshotted. See inc/mock.php for
   why, and for the rule that every label must exist in the real app. */

.mock { margin: 0; }
.mock-inner { display: block; }

/* A mockup that is NOT a full app shell — a bare table or a run log — still has
   to read as a screen rather than as loose text on the page. mock_shell brings
   its own frame, so this paints one only when there isn't one, which is what
   the :not(:has()) is doing. Without it a mock_table sat flat on the band and
   looked like a stray table somebody forgot to style. */
.mock-inner:not(:has(.mock-shell)) {
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--line), 0 12px 30px rgba(0, 0, 0, .4);
}
/* The table needs the padding the shell's well would otherwise give it. */
.mock-inner:not(:has(.mock-shell)) > .mock-table { padding: 6px 0; }

.mock-shell {
  display: flex;
  min-height: 266px;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--line), 0 12px 30px rgba(0, 0, 0, .4);
}
.mock-rail {
  flex: none;
  width: 132px;
  background: var(--surface-inset);
  border-right: 1px solid var(--line-faint);
  padding: 11px 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.mock-brand {
  display: flex; align-items: center; gap: 5px;
  font-size: 10.5px; color: var(--ink);
  padding: 0 6px 12px;
}
.mock-brand b { font-weight: 600; }
.mock-diamond { color: var(--accent); font-size: 8px; }
.mock-navgroup {
  font-size: 8px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-4); padding: 9px 6px 3px;
}
.mock-navitem {
  font-size: 10px; padding: 4px 7px; border-radius: var(--radius-sm);
  color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mock-navitem.is-active { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent-bright); }

.mock-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.mock-topbar {
  display: flex; align-items: center;
  padding: 9px 13px;
  border-bottom: 1px solid var(--line-faint);
  background: var(--surface-chrome);
}
.mock-crumb { font-size: 10.5px; color: var(--ink-2); margin-right: auto; }
.mock-avatar {
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent-soft);
  box-shadow: 0 0 0 1px var(--accent-line);
}
.mock-well { padding: 12px 4px 4px; }

.mock-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; padding: 0 9px 12px; }
.mock-stat {
  padding: 7px 8px;
  border-radius: 6px;
  background: var(--surface-3);
  box-shadow: 0 0 0 1px var(--line-2);
  min-width: 0;
}
.mock-stat > span { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mock-stat-label { font-size: 8px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-4); }
.mock-stat-value { font-size: 11px; color: var(--ink); margin-top: 2px; }
.mock-stat-note { font-size: 8.5px; color: var(--ink-4); }

.mock-table { display: flex; flex-direction: column; font-size: 11.5px; }
.mock-tr {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 9px 12px;
  background: linear-gradient(to right,
    transparent, var(--line-faint) 24px,
    var(--line-faint) calc(100% - 24px), transparent) no-repeat bottom / 100% 1px;
}
/* flex rather than equal grid tracks, so the FIRST column gets double width.
   It is the name column in every table on this site — an extraction, a model,
   a pipeline, an audit action — and the ones beside it are a status and a
   timestamp. Equal thirds truncated "Changed schedule — dbt job …" down to
   nothing while "Mon" sat in the same width. min-width:0 is what lets a flex
   item shrink below its content and actually ellipsis. */
.mock-tr > span {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-2);
}
.mock-tr > span:first-child { flex: 2 1 0; color: var(--ink); }
.mock-th { padding: 7px 12px; }
.mock-th > span {
  font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
}

.mock-pill { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.mock-dot { width: 5px; height: 5px; border-radius: 50%; flex: none; background: currentColor; }
.mock-pill-ok { color: var(--ok); }
.mock-pill-warn { color: var(--warn); }
.mock-pill-err { color: var(--err); }
.mock-pill-running { color: var(--running); }
.mock-pill-idle { color: var(--idle); }

/* A terminal is dark on any ground. That is the app's own rule and it holds
   here too — a run log that lightened would read as a bug. */
.mock-log {
  padding: 14px 16px;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.85;
  overflow-x: auto;
  background: var(--surface-inset);
}
.mock-logline { display: block; white-space: pre; color: var(--ink-2); }
.mock-logline.is-ok { color: var(--ok); }
.mock-logline.is-warn { color: var(--warn); }
.mock-logline.is-err { color: var(--err); }

/* ========================= PIPELINE DIAGRAM ============================== */
/* COLOUR IS THE ARGUMENT: the Fetch half is the accent, the dbt half is --dbt.
   Geometry comes from inc/diagram.php, which computes edges from the same
   coordinates as the boxes so the two cannot drift. */

.diag { width: 100%; height: auto; }
.diag-rail { fill: none; stroke: var(--line); stroke-width: 2; }
.diag-flow {
  fill: none;
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 9 91;
  animation: gmd-dash 2.6s linear infinite;
}
.diag-flow-fetch { stroke: var(--accent); }
.diag-flow-dbt { stroke: var(--dbt); }
/* The class sits on the wrapping <g>; the fill and stroke belong to the <rect>
   inside it, and the sublabel takes its colour from the same kind — so a node's
   half of the pipeline is legible from its tint alone. */
.diag-node rect { stroke-width: 1.5; }
.diag-node-source rect { fill: var(--surface-3); stroke: var(--accent-line); }
.diag-node-fetch  rect { fill: var(--accent-soft); stroke: var(--accent); }
.diag-node-dbt    rect { fill: var(--dbt-soft); stroke: var(--dbt); }
.diag-node-sink   rect { fill: var(--surface-2); stroke: var(--line); }

.diag-label { font-size: 17px; fill: var(--ink); font-family: var(--font-heading); font-weight: 400; }
.diag-sub { font-size: 12px; fill: var(--ink-3); font-family: var(--font-body); }
.diag-node-fetch .diag-sub { fill: var(--color-accent-300); }
.diag-node-dbt   .diag-sub { fill: var(--dbt-text); }

@keyframes gmd-dash {
  from { stroke-dashoffset: 100; }
  to { stroke-dashoffset: 0; }
}
@keyframes gmd-pulse {
  0%, 100% { opacity: .35; }
  50% { opacity: 1; }
}
@keyframes gmd-rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

/* ============================ BREAKPOINTS ================================ */

@media (max-width: 900px) {
  :root { --gutter: 20px; }

  .nav-toggle { display: inline-flex; }
  .site-nav {
    display: none;
    position: absolute;
    left: 0; right: 0; top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--space-3) var(--gutter) var(--space-6);
    background: var(--bg);
    border-bottom: 1px solid var(--line-faint);
  }
  .site-nav.is-open { display: flex; }
  .site-nav-list { flex-direction: column; align-items: stretch; gap: 0; width: 100%; }
  .site-nav a { padding: 11px 8px; font-size: 15px; display: block; }
  .header-inner { gap: var(--space-4); position: relative; }
  .nav-toggle { order: 3; }
  .nav-cta { order: 2; }

  .wiz-plans { grid-template-columns: 1fr; }
  .mock-stats { grid-template-columns: repeat(2, 1fr); }
  .wiz-layout { grid-template-columns: 1fr; }
  /* Un-sticky before the rail is taller than the viewport, or it pins with its
     total scrolled off the bottom — the one number it exists to show. */
  .wiz-side { position: static; }
}

@media (max-width: 620px) {
  .wiz-form { padding: 20px 16px 24px; }
  /* Below this the five step labels cannot fit side by side. The numbered pips
     still show position, and the current step's own <h1> names it, so nothing
     is lost — the labels were the redundant copy. */
  .wiz-step-label { display: none; }
  .wiz-addon { flex-wrap: wrap; }
  .wiz-addon-side { width: 100%; justify-content: space-between; }
  .mock-rail { display: none; }
  .footer-brand { margin-right: 0; width: 100%; }
}
