/* StillOpen public site.
   Zero-build, same as the staff app. One stylesheet, four pages.

   One theme, deliberately. This page's job is to welcome a barber reading it at
   9pm on a phone, and until Handoff H it followed `prefers-color-scheme` -- so
   most owners met us in the dark. There is no dark block below and no
   `[data-theme]` hook: every colour is painted explicitly, once. The staff app
   and the client portal keep their theme systems; this file is the marketing
   site only.

   The ground is a warm plaster neutral rather than white or the cream every
   generated page reaches for, and the accent is a pine green -- chosen partly
   because none of the ten palettes in the showcase is green, so the chrome
   never competes with the work inside the frames. Every text pair below was
   measured against WCAG rather than eyeballed: the lowest is `dim` on the
   deeper band at 4.83, and body text runs 12.5:1 or better. */

:root {
  /* ground */
  --paper:    #F6EFE9;  /* warm plaster */
  --band:     #EDE3DA;  /* the deeper band; sections alternate */
  --card:     #FFFCF9;  /* raised, lighter than the ground -- panels float up */
  --line:     #E2D5CA;
  --line-2:   #D6C6B9;

  /* ink */
  --ink:      #2A211C;
  --dim:      #6E5F56;

  /* accents */
  --accent:   #1F5D45;
  --accent-2: #164733;   /* hover: deeper, not brighter */
  --on-accent:#FBF7F3;
  --ember:    #8E5211;   /* numerals only -- the one warm mark on the page */
  --fail:     #A33A22;
  --pass:     #216B4B;

  /* form */
  --serif: "Fraunces", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --sans:  "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono:  "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --r-sm: 8px;
  --r:    13px;
  --r-lg: 20px;
  --shadow: 0 1px 2px rgba(58, 38, 24, .05), 0 8px 24px -12px rgba(58, 38, 24, .18);
}

* { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The masthead is sticky, so an anchor jump has to clear it. */
  scroll-padding-top: 84px;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 18px/1.65 var(--sans);
  -webkit-font-smoothing: antialiased;
}
/* The rack is 100vw, which counts the scrollbar gutter on Windows; this keeps
   that sliver from becoming a horizontal scroll on the whole page. */
body { overflow-x: clip; }

/* 860px is a reading measure. The home page puts `.wrap` inside each section
   instead of around `<main>`, so the showcase can take `.wide` and run past
   the text column without the section's band breaking with it. The other three
   pages still wrap `<main>` and are unaffected. */
.wrap { max-width: 860px; margin-inline: auto; padding-inline: 24px; }
.wrap.wide { max-width: 1240px; }

/* -- masthead ------------------------------------------------------------- */

header.top {
  position: sticky; top: 0; z-index: 20;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  padding: 15px 0;
}
.top .wrap { display: flex; align-items: center; justify-content: space-between; gap: 20px; }
.brand {
  font-family: var(--serif); font-weight: 600; font-size: 23px;
  color: var(--ink); text-decoration: none; letter-spacing: -.015em;
}
.brand:hover { color: var(--accent); }
.top nav { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.top nav a { color: var(--dim); text-decoration: none; font-size: 15.5px; }
.top nav a:hover, .top nav a[aria-current="page"] { color: var(--accent); }
.top nav a.phone {
  font-family: var(--mono); font-size: 14px; color: var(--ink);
  background: var(--band); border-radius: 999px;
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 16px;
}
.top nav a.phone:hover { background: var(--line); color: var(--ink); }
footer.bottom a[href^="tel:"] { text-decoration: none; }

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

h1 {
  font-family: var(--serif); font-weight: 600; font-size: clamp(34px, 5.6vw, 52px);
  line-height: 1.08; letter-spacing: -.022em; margin: 0 0 20px; text-wrap: balance;
}
h2 {
  font-family: var(--serif); font-weight: 600; font-size: clamp(25px, 3.6vw, 32px);
  line-height: 1.18; letter-spacing: -.015em; margin: 0 0 16px; text-wrap: balance;
}
h3 { font-family: var(--serif); font-weight: 600; font-size: 19.5px; margin: 0 0 8px; }
p { margin: 0 0 18px; max-width: 66ch; }
.lede { font-size: 20.5px; line-height: 1.55; color: var(--dim); max-width: 56ch; }
a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--accent-2); }
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm);
}
strong { font-weight: 600; }

/* Bands, not rules. Alternating ground is what carries the warmth here; a
   hairline between every section made the page read as a spec sheet. */
section { padding: 84px 0; }
section.band { background: var(--band); }
.hero { padding: 76px 0 64px; }

/* -- calls to action ------------------------------------------------------ */

.cta {
  display: inline-block; background: var(--accent); color: var(--on-accent);
  text-decoration: none; font-weight: 600; font-size: 16.5px;
  padding: 14px 26px; border-radius: var(--r); border: 1px solid var(--accent);
  transition: background .15s ease, border-color .15s ease;
}
.cta:hover { background: var(--accent-2); border-color: var(--accent-2); color: var(--on-accent); }
.cta.ghost { background: transparent; color: var(--accent); border-color: var(--line-2); }
.cta.ghost:hover { background: var(--card); border-color: var(--accent); color: var(--accent); }

.actions {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: center;
  margin: 28px 0 22px; max-width: none;
}

.stat {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-weight: 600; color: var(--ember);
}

.lede-after { color: var(--dim); font-size: 16.5px; max-width: 62ch; }
.caption { color: var(--dim); font-size: 15.5px; max-width: 72ch; }

/* -- the audit specimen --------------------------------------------------- */

.audit {
  border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--card);
  box-shadow: var(--shadow);
  padding: 22px 24px; margin: 30px 0 4px; font-family: var(--mono); font-size: 14px;
}
.audit .head {
  display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--line);
  color: var(--dim); font-size: 12px; letter-spacing: .06em; text-transform: uppercase;
}
.audit ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.audit li { display: flex; gap: 12px; align-items: flex-start; line-height: 1.5; }
.mark { font-weight: 600; flex: none; width: 1.1em; }
.mark.no { color: var(--fail); }
.mark.ok { color: var(--pass); }
.audit .num { font-variant-numeric: tabular-nums; }

/* -- lists ---------------------------------------------------------------- */

.grid { display: grid; gap: 26px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.grid > div {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 24px 24px 26px; box-shadow: var(--shadow);
}
.grid p { margin: 0; font-size: 16px; color: var(--dim); }

.terms { display: grid; gap: 18px; margin: 0; padding: 0; }
.terms div {
  border-left: 3px solid var(--accent); padding: 2px 0 2px 18px; border-radius: 0 3px 3px 0;
}
.terms dt { font-weight: 600; margin-bottom: 4px; }
.terms dd { margin: 0; color: var(--dim); font-size: 16px; max-width: 62ch; }

.steps { counter-reset: s; list-style: none; margin: 0; padding: 0; display: grid; gap: 26px; }
.steps li { counter-increment: s; display: grid; grid-template-columns: 40px 1fr; gap: 18px; }
.steps li::before {
  content: counter(s); font-family: var(--mono); font-size: 14px; font-weight: 600;
  color: var(--ember); background: var(--card);
  border: 1px solid var(--line); border-radius: 50%;
  width: 36px; height: 36px; display: grid; place-items: center;
}
.steps p { margin: 0; font-size: 16px; color: var(--dim); }

/* A note to the owner. `webarm.web.launch` strips it at launch. */
.draft {
  border-left: 3px solid var(--fail); background: var(--card);
  padding: 14px 18px; border-radius: 0 var(--r) var(--r) 0;
  font-size: 15px; margin-bottom: 28px;
}

/* Same box as `.draft`, deliberately not the same class: `.draft` means "a note
   to the owner, delete at launch". This one is permanent page furniture for
   visitors with JavaScript off. */
.noscript-note {
  border-left: 3px solid var(--fail); background: var(--card);
  padding: 14px 18px; border-radius: 0 var(--r) var(--r) 0;
  font-size: 15px; margin-bottom: 28px;
}

/* -- the figures ---------------------------------------------------------- */

/* A number set this large is a claim, so the shape of the block is built to
   carry its provenance: every figure keeps the sentence its source used, and
   the caption under the list carries the study, the panel and the date. If a
   figure ever cannot have that, it does not belong in here. */
.figures {
  list-style: none; margin: 30px 0 26px; padding: 0;
  display: grid; gap: 28px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.figures li { display: grid; gap: 8px; align-content: start; }
.figures p { margin: 0; font-size: 16px; color: var(--dim); max-width: 34ch; }
.figure {
  font-family: var(--serif); font-weight: 600; font-size: 44px; line-height: 1;
  color: var(--ember);          /* numerals only -- see the token's own note */
  font-variant-numeric: tabular-nums;
}

/* -- the offer bundle ----------------------------------------------------- */

/* Deliberately the same card as `.terms`: the bundle and the terms are the same
   kind of statement -- a thing we have committed to in writing -- so they must
   not look like two different kinds of thing. */
/* Two up where there is room. Single-column it read as a second terms list,
   and the page already has one of those; the offer should not look like the
   small print. The flat rate spans, because it is the one the others hang off. */
.bundle {
  display: grid; gap: 18px; margin: 30px 0 0; padding: 0;
  grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
}
.bundle > div:last-child { grid-column: 1 / -1; }
.bundle > div {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 20px 22px;
}
.bundle dt { font-weight: 600; margin-bottom: 4px; }
.bundle dd { margin: 0; color: var(--dim); font-size: 16px; max-width: 62ch; }

/* -- the showcase --------------------------------------------------------- */

/* Ten thumbnails of generated sites. The card is deliberately quiet: the work
   inside the frame is what is being shown, so the frame carries nothing but a
   line, the trade, and the palette the generator actually chose. */

.showcase {
  display: grid; gap: 30px; margin: 34px 0 26px;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.shot { margin: 0; }
.shot img {
  display: block; width: 100%; height: auto; aspect-ratio: 880 / 1100;
  object-fit: cover; object-position: top center;
  border: 1px solid var(--line); border-radius: var(--r); background: var(--card);
  box-shadow: var(--shadow);
}
.shot figcaption {
  display: flex; flex-direction: column; gap: 5px; padding: 13px 2px 0;
}
.shot .kind { font-weight: 600; font-size: 15.5px; }
.shot .recipe {
  font-family: var(--mono); font-size: 11.5px; color: var(--dim);
  letter-spacing: .01em; line-height: 1.45;
}
.chips { display: flex; gap: 5px; margin-top: 3px; }
.chip {
  width: 18px; height: 18px; border-radius: 5px;
  border: 1px solid var(--line-2); flex: none;
}

/* -- the rotating rack ---------------------------------------------------- */

/* `rack.js` swaps the grid above for this. Everything here is additive: no
   rule below changes how the page renders before the script runs, or if it
   never does. The grid is what ships. */

.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.rack {
  --cw: clamp(200px, 68vw, 380px);   /* the card facing the room */
  /* Not --r: that is the 13px corner token, and shadowing it here turned
     every card into an ellipse the first time this was rendered. */

  /* Cylinder radius, and the one number in this block that is not a taste
     call. Ten cards at 36 degrees sit on planes tangent to the ring. Two
     adjacent planes cross at r*tan(18deg) measured along each card from its
     own centre, so they miss each other only while

         r * tan(18deg) >= --cw / 2      i.e.   --ring >= 1.5388 * --cw

     At 1.35 they did not, and preserve-3d renders the true intersection: the
     neighbour's near edge came 0.036*--cw in FRONT of the facing card and
     painted a sliver straight through its face. Measured over CDP at 1290px:
     17px on the left, 18px on the right, growing to 25px mid-turn. That is
     the owner's "the templates are clipping over each other", and no stacking
     order fixes it -- a plane really does pass through another plane.

     1.7 clears the bound by 10%, and clears the more conservative
     1/(2 sin 18deg) = 1.618 as well, leaving room for the shadow. The facing
     card does not move: it sits at z = 0 whatever the radius is, so its
     rendered size is identical before and after.
     tests/test_public.py::test_the_rack_ring_keeps_the_cards_from_intersecting
     pins the ratio, because nothing else in the suite can see it. */
  --ring: calc(var(--cw) * 1.7);     /* cylinder radius */
  position: relative;
  margin: 34px 0 26px;
  /* Full-bleed: the rack runs to the edges of the SCREEN, not the text
     column. Clipped at the 1180px wrap it sliced the neighbouring cards
     mid-face -- the owner's "pages are clipping" report. A card may only
     leave the frame at the viewport edge, and even there it fades out
     through the mask below rather than being cut. */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  overflow: clip;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 3.5%, #000 96.5%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 3.5%, #000 96.5%, transparent 100%);
}
.rack:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 8px; border-radius: var(--r-lg);
}

.rack-stage {
  --a: 0;
  position: relative;
  height: calc(var(--cw) * 1.25 + 104px);
  perspective: calc(var(--cw) * 3.2);
  touch-action: pan-y;               /* the page still scrolls vertically */
  user-select: none; -webkit-user-select: none;
  cursor: grab;
}
.rack.is-dragging .rack-stage { cursor: grabbing; }

.rack-ring {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  /* Pushed back by the radius, so the card facing the room lands on the
     viewer's plane at its natural size and the others recede from there. */
  transform: translateZ(calc(var(--ring) * -1)) rotateY(calc(var(--a) * 1deg));
}

.rack-slot {
  --th: calc((var(--a) + var(--i) * var(--step)) * 1deg);
  position: absolute; top: 0; height: 100%;
  left: 50%; width: var(--cw); margin-left: calc(var(--cw) / -2);
  display: flex; align-items: center;
  transform: rotateY(calc(var(--i) * var(--step) * 1deg)) translateZ(var(--ring));
  cursor: pointer;
}
.rack-slot.is-front { cursor: default; }
/* Round the back: past 90 degrees these already painted at zero opacity, and
   taking them out of the layout is what stops the browser fetching their
   thumbnails -- see the note in rack.js. Not applied in the still pager,
   which has its own single-card rule below. */
.rack-slot.is-far { display: none; }
.rack-slot > .shot { width: 100%; }

/* Dimming sits on the figure, not the slot: `filter` flattens the element it is
   on, and the slot is what has to stay in the 3D context. The plain declaration
   before each calc is the fallback -- a browser without cos() gets every card
   lit and unblurred, which is plainer than intended but not broken. */
.rack-slot > .shot {
  opacity: 1;
  filter: none;
  opacity: max(0, cos(var(--th)) * 1.15 - .15);
  filter: blur(calc((1 - max(0, cos(var(--th)))) * 2.4px));
}
.rack-slot > .shot img { box-shadow: 0 20px 44px -24px rgba(58, 38, 24, .55); }

/* The identity line belongs to whichever card you are actually reading. */
.rack-slot > .shot figcaption {
  opacity: 1;
  opacity: max(0, (cos(var(--th)) - .86) * 7);
}

.rack-ui {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  flex-wrap: wrap; margin-top: 20px;
}
.rack-btn {
  width: 46px; height: 46px; flex: none;
  border: 1px solid var(--line-2); border-radius: 50%;
  background: var(--card); color: var(--ink);
  font: 600 19px/1 var(--sans); cursor: pointer;
}
.rack-btn:hover { border-color: var(--accent); color: var(--accent); }
.rack-count {
  font-family: var(--mono); font-size: 13.5px; color: var(--dim);
  font-variant-numeric: tabular-nums; min-width: 4.5em;
}
.rack-hint {
  color: var(--dim); font-size: 14.5px;
  flex-basis: 100%; text-align: center; margin-top: 4px;
}

/* Reduced motion: a pager over the same DOM. One card, the same controls, and
   the 3D scene collapsed so that changing card moves nothing. */
.rack.is-still .rack-stage { perspective: none; cursor: default; }
.rack.is-still .rack-ring { transform: none; }
.rack.is-still .rack-slot {
  transform: none; visibility: hidden; pointer-events: none; cursor: default;
}
.rack.is-still .rack-slot.is-front { visibility: visible; pointer-events: auto; }
.rack.is-still .rack-slot > .shot { opacity: 1; filter: none; }
.rack.is-still .rack-slot > .shot figcaption { opacity: 1; }

/* -- the quote form ------------------------------------------------------- */

.quote {
  display: grid; gap: 18px 20px; margin-top: 30px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.quote .field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.quote .wide-field, .quote .hint, .quote .err, .quote .submit-row { grid-column: 1 / -1; }
.quote label { font-size: 15px; font-weight: 600; }
.quote .opt { font-weight: 400; color: var(--dim); font-size: 13.5px; }
.quote input, .quote textarea {
  font: inherit; font-size: 16px;  /* 16px or iOS zooms the page on focus */
  color: var(--ink); background: var(--card);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); padding: 12px 14px;
  width: 100%; min-height: 48px;
}
.quote textarea { resize: vertical; min-height: 104px; line-height: 1.55; }
.quote input:focus-visible, .quote textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);
}
.quote input::placeholder { color: var(--dim); opacity: .75; }
.quote .hint { color: var(--dim); font-size: 15px; margin: 0; }
.quote .err {
  color: var(--fail); font-size: 15.5px; margin: 0;
  border-left: 3px solid var(--fail); padding-left: 14px;
}
.quote .err a { color: var(--fail); }
.quote .submit-row { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
/* 48px to match the inputs and clear the 44px tap target; a 43px button is the
   kind of thing our own audit would flag on somebody else's site. */
.quote button.cta { cursor: pointer; font-family: inherit; min-height: 48px; }
.quote button.cta[disabled] { opacity: .6; cursor: default; }
.quote .fine { color: var(--dim); font-size: 14px; max-width: 40ch; }

/* The honeypot. Off-viewport rather than display:none, and out of the tab
   order, so a person never meets it and a DOM-scraping bot still does. */
.trap {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

.done {
  border: 1px solid var(--line); border-left: 3px solid var(--pass);
  background: var(--card); border-radius: 0 var(--r-lg) var(--r-lg) 0;
  box-shadow: var(--shadow);
  padding: 24px 26px; margin-top: 30px;
}
.done h3 { margin-bottom: 6px; }
.done p { margin: 0; color: var(--dim); }

footer.bottom {
  background: var(--band); border-top: 1px solid var(--line);
  padding: 32px 0 52px; color: var(--dim); font-size: 14.5px;
}
footer.bottom .wrap { display: flex; gap: 18px; justify-content: space-between; flex-wrap: wrap; }
footer.bottom a { color: var(--dim); }
footer.bottom a:hover { color: var(--accent); }

@media (max-width: 620px) {
  html { scroll-padding-top: 80px; }
  body { font-size: 17px; }
  section { padding: 56px 0; }
  .hero { padding: 48px 0 44px; }
  .lede { font-size: 18.5px; }
  /* `balance` optimises for equal line lengths, which left a two-word first
     line in a 342px column. `pretty` only guards the last line. */
  h1 { text-wrap: pretty; }
  header.top { padding: 10px 0; }
  .top .wrap { gap: 12px; }
  .top nav { gap: 14px; }
  .top nav a[href*="#"] { display: none; }   /* in-page anchors: scroll instead */
  .brand { font-size: 21px; }
  .showcase { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 22px; }
  .rack { --cw: clamp(190px, 72vw, 340px); }
  .actions { gap: 12px; }
  .actions .cta { flex: 1 1 100%; text-align: center; }
  .quote .submit-row { flex-direction: column; align-items: stretch; }
  .quote button.cta { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}
