/* plumbbob.build — the stealth page (CI-518).
 *
 * Every value here is a design-system token or is derived from one. If you are
 * about to type a hex code, a font size or a radius into this file, stop: the
 * token is in vendor/plumbbob/tokens.css and the point of this repository is
 * that a change there reaches this page without anybody retyping it.
 *
 * The three numbers substituted at build time are geometry read out of the Logo
 * SVG (see scripts/build.mjs), not colours and not taste.
 *
 * Motion is 150ms ease on COLOUR PROPERTIES ONLY. No transforms, no fades, no
 * entrance animation. Elevation is borders, never shadows. No photography,
 * illustration, gradients, patterns or noise — that absence is the whole
 * character of the page.
 */

:root {
  /* The mark's rendered height, and the cord geometry read out of the SVG's own
   * viewBox and stroke-width. The bar above the mark is that stroke drawn at
   * this scale: stroke / viewBox height x rendered height. Derived, so re-scaling
   * the mark or re-issuing the logo carries the line with it. Never paste a px. */
  --mark-height: 320px;
  --cord-stroke: 5;
  --mark-viewbox-height: 340;
  --cord-width: calc(var(--cord-stroke) * var(--mark-height) / var(--mark-viewbox-height));

  /* Page gutter and main's vertical padding. The hang column cancels the top
   * padding with an equal negative margin so the cord meets the header hairline;
   * naming it keeps the two in step. */
  --page-gutter: 40px;
  --main-pad-y: 72px;
  --say-basis: 420px;
  --main-gap: 56px;
  --hang-pad-x: 24px;
}

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

html,
body {
  margin: 0;
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--pb-canvas);
  font-family: var(--pb-font-sans);
  font-size: var(--pb-body-size);
  line-height: var(--pb-body-lh);
  color: var(--pb-body);
  -webkit-font-smoothing: antialiased;
}

/* Never removed. 3px brand ring plus the border going to brand. */
:focus-visible {
  outline: none;
  box-shadow: var(--pb-focus-ring);
  border-color: var(--pb-brand);
  border-radius: var(--pb-radius-sm);
}

/* ---------------------------------------------------------------- header -- */

.header {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  height: var(--pb-topbar-h);
  padding: 0 var(--page-gutter);
  border-bottom: 1px solid var(--pb-border);
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}

.logo__mark {
  display: block;
  height: 26px;
  width: auto;
}

/* The Logo component's own wordmark: serif at round(height * .73), in the dark
 * placement's ink. */
.logo__word {
  font-family: var(--pb-font-serif);
  font-size: 19px;
  line-height: 1;
  color: var(--pb-ink);
}

.status {
  display: flex;
  align-items: center;
  gap: 9px;
}

.status__dot {
  width: 6px;
  height: 6px;
  border-radius: 3px;
  background: var(--pb-brand);
}

/* ------------------------------------------------------------------ type -- */

.eyebrow {
  font-family: var(--pb-font-mono);
  font-size: var(--pb-eyebrow-size);
  letter-spacing: var(--pb-eyebrow-tracking);
  font-weight: var(--pb-eyebrow-weight);
  line-height: 1;
  text-transform: uppercase;
  color: var(--pb-muted);
}

.eyebrow--faint {
  color: var(--pb-faint);
}

.micro {
  margin: 0;
  font-family: var(--pb-font-mono);
  font-size: var(--pb-micro-size);
  letter-spacing: var(--pb-micro-tracking);
  font-weight: var(--pb-micro-weight);
  line-height: 1;
  text-transform: uppercase;
}

.micro--brand {
  color: var(--pb-brand);
}

.display {
  margin: 0;
  font-family: var(--pb-font-serif);
  font-size: var(--pb-display-size);
  line-height: var(--pb-display-lh);
  font-weight: var(--pb-display-weight);
  letter-spacing: -0.01em;
  color: var(--pb-ink);
  text-wrap: pretty;
}

.body {
  margin: 0;
  max-width: 52ch;
  font-size: var(--pb-body-size);
  line-height: var(--pb-body-lh);
  color: var(--pb-body);
  text-wrap: pretty;
}

/* -------------------------------------------------------------------- main -- */

.main {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--main-gap);
  padding: var(--main-pad-y) var(--page-gutter);
}

/* Reflows by wrapping: the hang column drops under the copy rather than being
 * squeezed. Nothing here is pixel-fixed but the header and the mark. */
.say {
  flex: 1 1 var(--say-basis);
  min-width: 0;
  max-width: 620px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  margin-top: 8px;
}

/* ------------------------------------------------------------------ button -- */
/* The design system's Button, size="primary" variant="primary", as an anchor.
 * Same height, padding, radius, weight, fill and hover as the component. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  white-space: nowrap;
  font-family: var(--pb-font-sans);
  font-size: 15px;
  border-radius: var(--pb-radius);
  border: 0;
  cursor: pointer;
  text-decoration: none;
  height: var(--pb-control-h-primary);
  padding: 0 24px;
  transition:
    background var(--pb-transition),
    color var(--pb-transition);
}

.btn--primary {
  color: var(--pb-on-brand);
  background: var(--pb-brand);
  font-weight: 600;
}

/* --pb-brand-pressed lightens on dark, which is intended. */
.btn--primary:hover {
  background: var(--pb-brand-pressed);
  color: var(--pb-on-brand);
}

/* -------------------------------------------------------------- the plumb -- */

.hang {
  flex: 0 0 auto;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0 var(--hang-pad-x) 56px;
  /* Cancels main's top padding so the cord starts at the header hairline. */
  margin-top: calc(-1 * var(--main-pad-y));
}

.hang__cord {
  flex: 1 1 auto;
  min-height: 56px;
  width: var(--cord-width);
  background: var(--pb-brand);
}

/* Closes the join between the drawn bar and the SVG's own cord, which starts
 * 6 units into a 340-unit viewBox and is round-capped. */
.hang__mark {
  flex: 0 0 auto;
  margin-top: -14px;
  display: flex;
  justify-content: center;
}

.hang__mark img {
  display: block;
  height: var(--mark-height);
  width: auto;
}

/* Below this width the flex line has wrapped and the mark is under the copy, not
 * beside it. The cord would then reach for a hairline that is no longer above it,
 * and the negative top margin would drag the mark up over the paragraph. The mark
 * keeps hanging; the line stops pretending.
 *
 * **The breakpoint is computed, not chosen** — it is the exact width at which
 * these two columns stop fitting on one line, derived in scripts/build.mjs from
 * the gutters, the copy column's basis, the gap, and the mark's own width at its
 * rendered height. A round number would leave a band where the mark still sits
 * beside the copy with its cord already gone. */
@media (max-width: 754px) {
  .hang {
    align-self: center;
    margin-top: 0;
    padding-bottom: 0;
  }

  .hang__cord {
    display: none;
  }

  .hang__mark {
    margin-top: 0;
  }

  .hang__mark img {
    height: min(var(--mark-height), 46vh);
  }
}

@media (max-width: 560px) {
  :root {
    /* One step down the design system's spacing scale. */
    --page-gutter: 24px;
    --main-pad-y: 48px;
  }

  .display {
    font-size: 32px;
  }
}

/* ---------------------------------------------------------------- footer -- */

.footer {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px var(--page-gutter);
  border-top: 1px solid var(--pb-border);
}

/* ============================================================== SIGN UP ==
 *
 * plumbbob.build/signup (CI-519). Same rule as everything above it: no colour,
 * radius, control height or spacing step is typed here — every one is a
 * var(--pb-*) from the vendored design system. The handful of bare pixel values
 * that remain are the ones the design system's own components hardcode (a 15px
 * control font, a 12.5px hint, a 13.5px callout line, the Select's caret), and
 * each says which component it is transcribed from. There is no token for them
 * to reference; inventing one here would be a second place they live.
 *
 * The controls are recreations of Input, Select, Field, Callout and Button —
 * this repository has no React and no design-system runtime, so the components
 * cannot be imported. Where a value has a token, the token is used, so a change
 * in the design system still reaches this page through npm run sync.
 * ======================================================================== */

/* .field is display:flex and .card is display:flex, and `hidden` on a flex
 * element does nothing without this. The conditional sixth field, the success
 * card and the failure callout are all toggled by the attribute. */
[hidden] {
  display: none !important;
}

/* Focus, on the things this page adds.
 *
 * The global :focus-visible above gives every focusable element the 3px brand
 * ring AND turns its border brand — but it declares `border-color`, while the
 * rules below declare the `border` shorthand at the same specificity and later
 * in the file. The shorthand quietly won: the ring appeared, the border did not
 * change, and the page looked almost right. A browser caught that. This restates
 * both at a specificity the control rules cannot outrank.
 *
 * The radius is restated for the reason the global rule sets one at all: a
 * control that already has a radius keeps its own, because a border-radius that
 * changes on focus is a control that appears to resize when it is touched.
 *
 * The ring itself is never removed, from anything, in any state. */
.control:focus-visible,
.btn:focus-visible {
  border-color: var(--pb-brand);
  border-radius: var(--pb-radius);
}

.logo {
  text-decoration: none;
}

.eyebrow--link {
  text-decoration: none;
  transition: color var(--pb-transition);
}

.eyebrow--link:hover {
  color: var(--pb-ink);
}

/* ------------------------------------------------------------- the column -- */

.main--form {
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  /* The design's own figures: 56 (space-12) and 24 (space-8) are on the spacing
   * scale, 72 is not — it is the same off-scale bottom padding the home page
   * uses for main, and it is kept for fidelity rather than rounded to 56. */
  padding: var(--pb-space-12) var(--pb-space-8) 72px;
}

.column {
  width: 100%;
  max-width: 600px;
  display: flex;
  flex-direction: column;
  /* Between space-8 (24) and space-9 (32); the design's value, off the scale. */
  gap: 28px;
}

.intro {
  display: flex;
  flex-direction: column;
  gap: var(--pb-space-5);
}

.heading {
  margin: 0;
  font-family: var(--pb-font-serif);
  font-size: var(--pb-heading-size);
  line-height: var(--pb-heading-lh);
  font-weight: var(--pb-heading-weight);
  color: var(--pb-ink);
  text-wrap: pretty;
}

.page-title {
  margin: 0;
  font-family: var(--pb-font-serif);
  font-size: var(--pb-page-title-size);
  font-weight: var(--pb-page-title-weight);
  color: var(--pb-ink);
}

/* The success heading takes focus when the card is swapped, so the change is
 * announced rather than only drawn: a keyboard or screen-reader visitor was
 * standing on a Sign up button that is no longer on the page.
 *
 * It gets no ring, and this is not the ring rule being bent. That rule is about
 * INTERACTIVE elements. This is a heading with tabindex="-1" and nothing to
 * operate, and a brand-bordered box drawn the full width of the card around the
 * words "Signed up." reads as a control — which a browser showed it doing. */
.page-title:focus,
.page-title:focus-visible {
  outline: none;
  box-shadow: none;
  border-color: transparent;
}

/* The success card is this same rule, so it replaces the form at exactly the
 * same geometry — same fill, same border, same radius, same padding, same gap.
 * Nothing on the page moves when a sign-up lands. No shadow: elevation in this
 * system is a border. */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--pb-space-7);
  padding: var(--pb-space-9);
  background: var(--pb-surface);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius);
}

/* Name and email share a row and drop to one column on their own. */
.pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--pb-space-7);
}

/* ------------------------------------------------------ Field, recreated -- */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--pb-field-gap);
  min-width: 0;
}

.field__label {
  font-size: var(--pb-label-size);
  font-weight: var(--pb-label-weight);
  color: var(--pb-body);
}

/* 12.5px is Field's own hint and error size. No token exists for it. */
.field__error {
  font-size: 12.5px;
  color: var(--pb-danger);
}

.field__hint {
  font-size: 12.5px;
  color: var(--pb-muted);
}

/* ------------------------------------- Input, Select and the plain area -- */

.control {
  width: 100%;
  height: var(--pb-control-h);
  padding: 0 var(--pb-space-5);
  font-family: var(--pb-font-sans);
  /* Input's own control font size. */
  font-size: 15px;
  color: var(--pb-ink);
  /* --pb-surface-field, not --pb-surface: this is the alias Input reads, so the
   * textarea and the select match a real Input in both themes rather than
   * nearly matching. It is also why data-theme has to be on <html> — the alias
   * resolves where it is declared, and that is :root. */
  background: var(--pb-surface-field);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius);
  outline: none;
  transition:
    border-color var(--pb-transition),
    background var(--pb-transition),
    color var(--pb-transition);
}

.control::placeholder {
  color: var(--pb-text-placeholder);
  opacity: 1;
}

/* The error border survives focus. The 3px brand ring is still there — it is
 * never removed — but turning the border brand-coloured on focus would erase
 * the one thing telling the visitor which field is wrong. */
.control--invalid,
.control--invalid:focus-visible {
  border-color: var(--pb-danger);
}

.control--area {
  height: auto;
  /* Input has no vertical padding to borrow — it centres text in a fixed
   * height. 12/14 is the design's, and 14 is space-5. */
  padding: 12px var(--pb-space-5);
  line-height: var(--pb-body-lh);
  resize: vertical;
}

.select {
  position: relative;
  display: block;
}

.select .control {
  /* Room for the caret. 36 is Select's own figure and is off the spacing
   * scale; it is a control's internal geometry, not layout. */
  padding: 0 36px 0 var(--pb-space-5);
  appearance: none;
  -webkit-appearance: none;
}

.select__caret {
  position: absolute;
  right: var(--pb-space-5);
  top: 50%;
  transform: translateY(-50%);
  /* Select's own caret size. */
  font-size: 11px;
  color: var(--pb-faint);
  pointer-events: none;
}

/* -------------------------------------------------------------- the trap -- */

/* The honeypot. Off-screen rather than display:none, because a field that is
 * display:none is one a bot can cheaply learn to skip and the trap has to be
 * fillable to be a trap. Sight is only half of it: aria-hidden and inert on the
 * wrapper keep it out of the accessibility tree entirely, and tabindex="-1" in
 * the markup keeps it out of the tab order — which is also what makes the
 * aria-hidden legitimate, since aria-hidden must never contain anything
 * focusable. A screen-reader user cannot reach this field, so a screen-reader
 * user cannot be refused as a bot by it. */
.trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ------------------------------------------------- the Turnstile widget -- */

/* **The one measurement on this page that is not the design system's.**
 * Everything else here is a token; 65px is the height Cloudflare's normal
 * widget renders at, and it is a fact about somebody else's iframe rather than
 * a decision anybody made. It is a custom property so that it reads as the
 * borrowed constant it is instead of as a magic number inside a rule.
 *
 * **The room is held whether or not a widget is drawn.** signup.js renders with
 * appearance: 'interaction-only', so for an ordinary visitor nothing ever
 * appears here and this is 65px of nothing. That is the cost and it is the
 * right way round: the alternative is a form that grows by 65px at the moment
 * Cloudflare decides to ask a question, moving the Sign up button out from
 * under the pointer of the one visitor already being asked to prove something.
 * A reserved gap is invisible; a jump is not.
 *
 * min-height rather than height, so a taller challenge grows the container
 * instead of being clipped by it. */
.turnstile {
  --turnstile-height: 65px;
  display: flex;
  align-items: center;
  min-height: var(--turnstile-height);
}

/* ----------------------------------------------------- the card's footer -- */

.form__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--pb-space-6);
  margin-top: var(--pb-space-1);
  padding-top: var(--pb-space-7);
  border-top: 1px solid var(--pb-divider-soft);
}

.form__foot--plain {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* Field's hint size again — the counterpart to the promise in the intro. */
.note {
  font-size: 12.5px;
  line-height: var(--pb-body-lh);
  color: var(--pb-muted);
}

/* ------------------------------------------- Button, the other two skins -- */

/* Button size="default" — 48px and 0 22px, against .btn's size="primary". */
.btn--default {
  height: var(--pb-control-h);
  padding: 0 22px;
}

.btn--secondary {
  color: var(--pb-ink);
  background: var(--pb-surface);
  border: 1px solid var(--pb-border);
  font-weight: 500;
}

.btn--secondary:hover {
  background: var(--pb-surface-sunken);
}

.btn:disabled {
  color: var(--pb-text-disabled);
  background: var(--pb-btn-disabled-bg);
  border: 0;
  cursor: default;
}

/* ------------------------------------------------------------- Callout ---- */

/* Callout's own geometry: a 12px gap, 14x16 padding, a 3px bar at radius 2, and
 * 13.5px text. None of those four have tokens.
 *
 * The foreground is the one deliberate departure from the callout tokens.
 * --pb-callout-success-fg and --pb-callout-danger-fg are, in the vendored
 * build's DARK scope, the light theme's values — dark brown text on a dark brown
 * ground, 1.90:1 and 1.60:1 against their own tints. That is a defect in the
 * design system's token build (its own source notes it: the reconciliation asks
 * for no dark-mode change, and --pb-callout-brand-fg is correctly lifted in the
 * same file), not a decision, and it makes the one sentence a visitor most needs
 * to read unreadable. So the tone's status foreground is used instead —
 * --pb-success-fg and --pb-danger-fg, which the dark scope does lift, giving
 * 7.7:1 and 5.6:1. Still a token, still flows on sync, and it stops being a
 * departure the moment the upstream build is fixed. */
.callout {
  display: flex;
  gap: 12px;
  padding: 14px var(--pb-space-6);
  border-radius: var(--pb-radius);
  background: var(--callout-bg);
}

.callout__bar {
  flex: 0 0 3px;
  width: 3px;
  border-radius: 2px;
  background: var(--callout-bar);
}

.callout__text {
  font-size: 13.5px;
  line-height: var(--pb-body-lh);
  color: var(--callout-fg);
}

.callout--success {
  --callout-bg: var(--pb-callout-success-bg);
  --callout-bar: var(--pb-callout-success-bar);
  --callout-fg: var(--pb-success-fg);
}

.callout--danger {
  --callout-bg: var(--pb-callout-danger-bg);
  --callout-bar: var(--pb-callout-danger-bar);
  --callout-fg: var(--pb-danger-fg);
}
