/* ProtoSandpit — the platform site.
   Carries P3MAI's branding, because this is a P3MAI product. The prototypes it
   generates carry the invented client brand instead; P3MAI appears on them only
   as a footer credit.

   copy-may-differ: every prototype has its own palette by design, so a
   stylesheet of this name is expected to differ between site/ and each folder
   under Prototypes/. They are not copies of one another and never should be. */

/* Poppins, self-hosted. Same four latin weights p3mai.com serves. The CSP is
   default-src 'self', so a Google Fonts request would be blocked outright.
   font-display: swap paints text immediately in the fallback. */
@font-face { font-family:'Poppins'; font-style:normal; font-weight:400; font-display:swap;
  src:url('../assets/fonts/poppins-400-latin.woff2') format('woff2'); }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:500; font-display:swap;
  src:url('../assets/fonts/poppins-500-latin.woff2') format('woff2'); }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:600; font-display:swap;
  src:url('../assets/fonts/poppins-600-latin.woff2') format('woff2'); }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:700; font-display:swap;
  src:url('../assets/fonts/poppins-700-latin.woff2') format('woff2'); }

:root {
  /* The sticky site header's rendered height. Anything else that sticks
     must clear it or it slides underneath -- which is how the brief rail
     lost its own heading, and the heading is the disclaimer. Asserted in
     the test suite against the real header rather than trusted here. */
  --header-h:78px;
  --primary: #0B2545;          /* P3MAI navy */
  --primary-dark: #071830;
  --accent: #C9A227;           /* P3MAI gold — WORDMARK ONLY, see .brand-ai */
  --accent-text: #856413;      /* the gold that is legible as text */
  --text: #1C2B3A;
  --muted: #5B6675;
  --bg: #F6F7F9;
  --bg-alt: #EBEDF2;
  --white: #fff;
  --line: #DDE1E8;
  --radius-sm: 8px;
  --radius: 16px;
  --radius-lg: 28px;
  --shadow-sm: 0 2px 8px rgba(11,37,69,.08);
  --shadow-md: 0 8px 24px rgba(11,37,69,.12);
}

*,*::before,*::after { box-sizing: border-box; }
body,h1,h2,h3,h4,p,ul,ol,figure { margin: 0; }
ul { padding: 0; list-style: none; }
img,svg { max-width: 100%; display: block; }
html { overflow-x: clip; }
body { overflow-x: clip; }

body {
  font-family: 'Poppins','Poppins Fallback','Segoe UI',system-ui,-apple-system,sans-serif;
  background: var(--bg); color: var(--text); line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}
.container { width:100%; max-width:1080px; margin:0 auto; padding:0 24px; }

.skip { position:absolute; left:-9999px; }
.skip:focus { left:16px; top:16px; z-index:200; padding:10px 16px; background:var(--white);
  color:var(--primary); border-radius:var(--radius); box-shadow:var(--shadow-md); }

/* ---- header: p3mai.com's treatment ---- */
/* -webkit- first: there is no WebKit engine on this machine, so a missing
   prefix is invisible here and only shows up on somebody's iPhone. */
.site-header { position:sticky; top:0; z-index:100; background:rgba(250,249,246,.92);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  box-shadow:0 1px 0 rgba(45,52,54,.06); }
.navbar { display:flex; align-items:center; gap:8px; padding:18px 24px; max-width:1080px;
  margin:0 auto; flex-wrap:wrap; }
.logo { display:flex; align-items:center; gap:10px; margin-right:auto; text-decoration:none; }
.logo img { width:40px; height:40px; }
.logo-text { font-weight:700; font-size:1.2rem; color:var(--primary); white-space:nowrap; }
/* The AI of P3MAI stays the undiluted brand gold. WCAG 2.2 SC 1.4.3 excepts text
   that is part of a logo or brand name from the contrast minimum — an automated
   audit will flag it, and it is a cited exemption, not a defect. */
.brand-ai { color: var(--accent); }
.nav-links { display:flex; gap:8px; flex-wrap:wrap; }
.nav-links a { display:inline-block; padding:8px 16px; border-radius:999px; font-weight:500;
  color:var(--text); text-decoration:none; }
.nav-links a:hover { background:var(--bg-alt); color:var(--primary); }
.nav-links a[aria-current="page"] { background:var(--primary); color:var(--white); }

/* ---- hero ---- */
.hero { background:linear-gradient(135deg,var(--primary) 0%,var(--primary-dark) 100%);
  color:var(--white); padding:84px 0 72px; }
/* METRICS-MATCHED FALLBACK (AG-146).
   Poppins is 6-9% wider than the Segoe UI that stands in while it loads, so
   every paragraph re-wraps when it arrives and the page moves. Converting the
   `ch` boxes to `em` fixed the boxes; this fixes the TEXT INSIDE them.

   Both were needed, and fixing only one made the visible total worse: the
   ch error was pushing the page one line TALLER while the width error pushed
   it two lines SHORTER, and removing the first exposed the full size of the
   second. Measured, not guessed -- body height error went 26.4px, then 52.8px,
   then to what is recorded below.

   Ratios are per weight and computed from these woff2 files against Segoe UI
   measured in the browser. They are NOT the same as WebSandpit's -- that site's
   headings are weight 600 and these are 700. Do not copy between sites. */
@font-face { font-family:'Poppins Fallback'; font-weight:400;
  src:local('Segoe UI'),local('Roboto'),local('Helvetica Neue'),local('Arial');
  size-adjust:108.21%; ascent-override:97.03%; descent-override:32.34%;
  line-gap-override:9.24%; }
@font-face { font-family:'Poppins Fallback'; font-weight:500;
  src:local('Segoe UI'),local('Roboto'),local('Helvetica Neue'),local('Arial');
  size-adjust:106.76%; ascent-override:98.35%; descent-override:32.78%;
  line-gap-override:9.37%; }
@font-face { font-family:'Poppins Fallback'; font-weight:600;
  src:local('Segoe UI'),local('Roboto'),local('Helvetica Neue'),local('Arial');
  size-adjust:108.96%; ascent-override:96.37%; descent-override:32.12%;
  line-gap-override:9.18%; }
@font-face { font-family:'Poppins Fallback'; font-weight:700;
  src:local('Segoe UI'),local('Roboto'),local('Helvetica Neue'),local('Arial');
  size-adjust:106.80%; ascent-override:98.32%; descent-override:32.77%;
  line-gap-override:9.36%; }
/* NO BOX IS MEASURED IN `ch` (AG-146, and AG-142 is why).
   `ch` is the width of the font's own `0` glyph, so a box measured in it
   CHANGES SIZE when the font swaps -- the text does not merely re-wrap, the
   container it wraps inside moves. Poppins' zero is 0.6280 em at weight 400 and
   0.6520 at 700; Segoe UI's is around 0.555. Every `ch` box here was therefore
   one width while Poppins loaded and another after it arrived.

   MEASURED HERE BEFORE CHANGING ANYTHING, because the fault is not uniform:
     .hero h1        did NOT move -- its weight is preloaded, so it arrives
                     before first paint and there is no swap to see
     .plain li       moved 26.4px, a full line
     everything else stable
   Total CLS was only 0.0006, because the element that moves sits below the
   fold and CLS counts what is in the viewport. That is not the same as
   nothing: a reader who has scrolled there watches the text jump. Fixed for
   that reason rather than for the score.

   Each width is its own conversion. 20ch at weight 700 is not the same em
   value as 20ch at 400, and neither matches WebSandpit's 11.63em -- that was
   computed from Poppins 600's zero. Do not copy these numbers between sites.

   `em` depends only on font-SIZE, which does not change when the family swaps. */
.hero h1 { font-size:clamp(2rem,5vw,3rem); line-height:1.15; max-width:13.04em; }   /* was 20ch, w700 */
.hero .lede { margin-top:18px; max-width:35.17em;   /* was 56ch, w400 */ font-size:1.12rem; color:rgba(255,255,255,.88); }
.hero .actions { margin-top:30px; display:flex; gap:12px; flex-wrap:wrap; }
.eyebrow { text-transform:uppercase; letter-spacing:.14em; font-size:.78rem; font-weight:600;
  color:var(--accent); margin-bottom:14px; }

.btn { display:inline-block; padding:13px 26px; border-radius:999px; font-weight:600;
  text-decoration:none; border:1px solid transparent; cursor:pointer; font:inherit;
  font-weight:600; }
.btn-accent { background:var(--accent); color:#231C05; }
.btn-accent:hover { filter:brightness(1.06); }
.btn-ghost { border-color:rgba(255,255,255,.55); color:var(--white); }
.btn-ghost:hover { background:rgba(255,255,255,.12); }
.section .btn-ghost { border-color:var(--primary); color:var(--primary); }
.section .btn-ghost:hover { background:var(--bg-alt); }

/* ---- sections ---- */
.section { padding:72px 0; }
.section + .section { border-top:1px solid var(--line); }
.section h2 { font-size:clamp(1.5rem,3.4vw,2.1rem); color:var(--primary); }
.section > .container > p { margin-top:16px; max-width:38.94em;   /* was 62ch, w400 */ }
.section-alt { background:var(--bg-alt); }

.steps { display:grid; gap:20px; margin-top:34px;
  grid-template-columns:repeat(auto-fit,minmax(min(240px,100%),1fr)); }
.step { background:var(--white); border:1px solid var(--line); border-radius:var(--radius);
  padding:24px; box-shadow:var(--shadow-sm); }
.step-n { display:inline-flex; align-items:center; justify-content:center; width:34px;
  height:34px; border-radius:999px; background:var(--primary); color:var(--white);
  font-weight:700; margin-bottom:12px; }
.step h3 { color:var(--primary); font-size:1.08rem; }
.step p { margin-top:8px; color:var(--muted); }

.plain li { padding:14px 0; border-top:1px solid var(--line); max-width:43.96em; }   /* was 70ch, w400 */
.plain li:first-child { border-top:0; }

/* ---- the example: show the thing, and let people click into it ----
   A page called "See an example" that shows only prose is asking the reader to
   take our word for it. These are real captures of the built site, and each one
   is a link to the live prototype -- clicking the picture of a website should
   open that website. */
.shots { display:grid; gap:26px; margin-top:34px; align-items:start;
  grid-template-columns:minmax(0,2.4fr) minmax(0,1fr); }
.shot { display:block; text-decoration:none; color:inherit; }
/* A browser frame, so a screenshot reads as a screenshot rather than as part of
   this page. The bar carries the real URL -- it is where the link goes. */
.frame { border:1px solid var(--line); border-radius:var(--radius);
  overflow:hidden; background:var(--white); box-shadow:var(--shadow-md); }
.frame-bar { display:flex; align-items:center; gap:8px; padding:9px 12px;
  background:var(--bg-alt); border-bottom:1px solid var(--line); }
.dot { width:9px; height:9px; border-radius:999px; background:#C6CBD4; flex:none; }
.frame-url { font-size:.76rem; color:var(--muted); margin-left:6px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* Long pages are capped and top-anchored: the fold is what a visitor judges,
   and a 1,900px-tall image would push everything else off the screen. */
.frame img { width:100%; height:auto; max-height:430px; object-fit:cover;
  object-position:top center; }
.frame-phone img { max-height:520px; }
.shot-cap { margin-top:10px; font-size:.92rem; color:var(--muted); }
.shot:hover .frame { box-shadow:0 12px 32px rgba(11,37,69,.2); }
.shot:hover .shot-cap { color:var(--primary); }

/* The verbatim requirements block. Same dark treatment as the brief output on
   the form, because it is the same kind of thing -- the customer's own words,
   untouched. */
.quote-block { margin-top:26px; background:#0E1B2A; color:#E6EDF3;
  border-radius:var(--radius); padding:22px; overflow-x:auto; }
.quote-block pre { font-size:.9rem; line-height:1.65; white-space:pre-wrap;
  word-break:break-word; margin:0; }

@media (max-width:820px) {
  .shots { grid-template-columns:1fr; }
  .frame img, .frame-phone img { max-height:340px; }
}

/* ---- form ----
   820px, not 640. At 640 the form sat in the middle of a 1080 container looking
   like a narrow column dropped into a wide page -- and it is the ONLY page on
   the site where the reader has work to do, so it should be the last one that
   feels cramped. The two six-card choice grids gain the most: at 640 they were
   forever one card short of a comfortable row.

   The measure is protected separately below. Widening a form widens its help
   text too, and a 100-character line of grey 15px prose is harder to read than
   a narrow form ever was. */
.form { margin-top:34px; max-width:820px; }
.field { margin-bottom:22px; }
.field label { display:block; font-weight:600; margin-bottom:6px; }
.field .why { color:var(--muted); font-size:.93rem; margin:0 0 8px;
  /* Holds the help text to a readable measure while the form itself is
     wide. Set in em, so it tracks this rule's own font-size rather than
     the root's. */
  max-width:40em; }
.field input, .field textarea, .field select {
  width:100%; padding:12px 14px; font:inherit; color:var(--text); background:var(--white);
  border:1px solid var(--line); border-radius:var(--radius-sm); }
.field textarea { min-height:110px; resize:vertical; }
/* A question that asks three things gets a box that looks like it wants three
   answers. People fill the space they are given. */
.field textarea.tall { min-height:170px; }

/* ---- choice buttons ----
   Two groups of six: what the top of the landing page DOES, and how it LOOKS. They are
   radio inputs dressed as cards, not clickable divs -- one choice, arrow-key
   navigable within the group, and announced as a set without any JavaScript.

   THE RADIO STAYS VISIBLE. Hiding it and faking the tick is the usual trick and
   it is the usual bug: the selected state then rests on colour alone, which
   WCAG 2.2 does not accept, and a page printed in greyscale becomes unreadable.
   Here the selection is carried three ways over -- the dot, a heavier border,
   and a tinted ground. */
.choice-set { border:0; padding:0; margin:0 0 22px; }
.choice-set legend { font-weight:600; padding:0; }
.choice-grid, .choice-set { display:block; }
/* `label.choice`, not `.choice`. These cards ARE labels and they sit inside a
   .field, so `.field label { display:block }` -- one class plus an element --
   outranks a bare class and quietly flattened the grid. The symptom was subtle:
   grid-template-columns applied, so the rule was obviously matching, while
   display did not. Two declarations from the same block behaving differently is
   the tell that something more specific is winning on one of them. */
label.choice {
  display:grid; grid-template-columns:auto 1fr; gap:2px 12px;
  align-items:start; padding:12px 14px; margin-bottom:8px;
  background:var(--white); border:1px solid var(--line);
  border-radius:var(--radius-sm); cursor:pointer;
}
label.choice:hover { border-color:var(--primary); }
label.choice input { grid-row:1 / span 2; margin:3px 0 0; width:18px; height:18px; accent-color:var(--primary); }
.choice-label { font-weight:600; }
.choice-hint { grid-column:2; color:var(--muted); font-size:.9rem; }
/* :has() rather than a class toggled by script -- the browser already knows
   which one is checked, and a script that has to be told is a script that can
   fall out of step with the control it is describing. */
label.choice:has(input:checked) { border-color:var(--primary); border-width:2px; background:var(--bg-alt); padding:11px 13px; }
label.choice:has(input:focus-visible) { outline:3px solid var(--primary); outline-offset:2px; }

/* The contact block. A fieldset like the choice groups so it reads as one
   thing, but plain inputs rather than cards -- there is nothing to choose. */
.contact-grid { display:grid; gap:12px; }
.contact-set .sub { display:block; }
.contact-set .sub > span { display:block; font-weight:600; margin-bottom:6px; }

@media (min-width:700px) {
  /* Two columns once there is room. Six stacked cards is a long scroll for a
     question that is meant to be a glance. */
  .choice-set { display:grid; grid-template-columns:1fr 1fr; gap:0 12px; }
  .choice-set legend, .choice-set .why { grid-column:1 / -1; }
  /* The contact fields are short, so three across rather than two. */
  .contact-set { display:block; }
  .contact-grid { grid-template-columns:1fr 1fr 1fr; }
}
/* The two markers are a PAIR and must read as one system: same position, same
   shape, different weight. `(optional)` recedes -- muted and regular, so it
   reads as permission to skip. `(essential)` is bold and in the ink colour, so
   it reads as a requirement. Douglas asked for the essential one to carry the
   same statement in bold, and the point is the contrast between them: neither
   means much without the other on the page.

   Three of the twelve are essential. Until this, they carried NO visible marker
   at all -- only a `required` attribute, which the browser reveals when you try
   to submit, which is the worst moment to learn it. */
.optional { color:var(--muted); font-weight:400; }
/* Bold alone was NOT enough, and it is worth saying why rather than quietly
   adding decoration. The label these sit inside is already `font-weight:600`,
   and the marker inherits its colour -- so `(essential)` at 700 differed from
   the question it followed by one weight step and nothing else. Measured on the
   rendered page it was indistinguishable at a glance, which is the only test
   that matters for a word whose whole job is to be noticed.

   `(optional)` works because it differs TWO ways at once: lighter AND greyer.
   So `(essential)` is given two of its own -- uppercase and letter-spaced, at
   full ink. Read down the form, the three now stand out as a different KIND of
   text rather than as slightly heavier question wording. */
.essential { color:var(--text); font-weight:700; text-transform:uppercase;
  letter-spacing:.06em; font-size:.78em; }
:focus-visible { outline:3px solid var(--primary); outline-offset:2px; }

.output { margin-top:30px; background:var(--white); border:1px solid var(--line);
  border-radius:var(--radius); padding:24px; box-shadow:var(--shadow-sm); }
.output h3 { color:var(--primary); }
.output pre { margin-top:14px; padding:16px; background:#0E1B2A; color:#E6EDF3;
  border-radius:var(--radius-sm); overflow-x:auto; font-size:.86rem; line-height:1.55;
  white-space:pre-wrap; word-break:break-word; }
.output .actions { margin-top:16px; display:flex; gap:10px; flex-wrap:wrap; }
/* The receipt: what the server actually did. Shown only when it really did it —
   an on-screen reference for a build that was never queued would be worse than
   showing nothing. */
.receipt { display:grid; grid-template-columns:auto 1fr; gap:6px 18px;
  margin:16px 0 0; padding:14px 16px; background:var(--bg-alt);
  border-radius:var(--radius-sm); font-size:.94rem; }
.receipt dt { font-weight:600; color:var(--primary); }
.receipt dd { margin:0; font-family:ui-monospace,SFMono-Regular,Consolas,monospace;
  overflow-wrap:anywhere; }
#start-note { margin-top:12px; }
/* A class, not style="margin-top:26px". The CSP is style-src 'self' with no
   'unsafe-inline', so an inline style attribute is not a tidiness question --
   the browser refuses to apply it and the layout is quietly wrong. */
.actions-spaced { margin-top:26px; }
[hidden] { display:none !important; }

/* ---- footer ---- */
.site-footer { background:var(--primary); color:rgba(255,255,255,.85); padding:40px 0; }
.site-footer a { color:var(--white); }
.footer-note { font-size:.9rem; color:rgba(255,255,255,.7); margin-top:14px; max-width:43.96em; }   /* was 70ch, w400 */

@media (max-width:640px) {
  .hero { padding:56px 0 48px; }
  .section { padding:48px 0; }
}


/* ---- Mobile navigation ---------------------------------------------------
   The header links fold into a three-bar menu below 700px, the same behaviour
   and the same breakpoint as p3mai.com. Consistency is the point: a visitor
   who has used one of these sites should not have to learn a second set of
   rules.

   The bars are currentColor, so the button takes the header's own text colour
   instead of hard-coding one per site. */
.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  padding: 8px;
  margin-left: auto;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--primary);
  z-index: 110;
}
.nav-toggle span {
  display: block;
  width: 26px;
  height: 3px;
  border-radius: 2px;
  background: currentColor;
  transition: transform 0.25s ease, opacity 0.25s ease;
}
/* Open state: the outer bars rotate into an X and the middle one fades. The
   translate has to come FIRST in the transform list -- rotating then moving
   moves along the rotated axis and the bars miss each other. */
.nav-toggle.open span:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.nav-toggle.open span:nth-child(2) { opacity: 0; }
.nav-toggle.open span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

@media (max-width: 700px) {
  .nav-toggle { display: flex; }
  [data-nav-panel] {
    position: fixed;
    top: 0;
    right: 0;
    height: 100vh;
    width: min(78%, 320px);
    background: var(--white);
    flex-direction: column;
    align-items: flex-start;
    padding: 92px 28px 40px;
    gap: 6px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
    /* Off-canvas rather than display:none, so it slides rather than appearing,
       and so the links stay in the accessibility tree in a predictable place. */
    transform: translateX(100%);
    transition: transform 0.25s ease;
    overflow-y: auto;
    z-index: 105;
  }
  [data-nav-panel].open { transform: translateX(0); }
  [data-nav-panel] li { width: 100%; }
  [data-nav-panel] li a { display: block; width: 100%; }
}

/* Someone who has asked their system for less motion gets the panel without
   the slide, and the bars without the rotation. It still works; it just does
   not move. */
@media (prefers-reduced-motion: reduce) {
  .nav-toggle span, [data-nav-panel] { transition: none; }
}

/* Honeypot: hidden by CLASS, never by an inline style.
   Formspree's own documentation suggests style="display:none" on this field,
   and this site's CSP is style-src 'self' with no 'unsafe-inline' -- so the
   browser refuses the inline style SILENTLY and the trap becomes a visible
   text input labelled "Leave this field empty". A real visitor fills it in,
   and their enquiry is discarded as spam without anyone being told.

   This rule was missing when the endpoint was first wired, and the CSP
   rehearsal caught it before a single real submission -- which is the entire
   reason the rehearsal step exists.

   Off-screen rather than display:none: a hidden input is skipped by some
   automated form-fillers, and being reachable is part of the trap. */
.hp { position: absolute; left: -9999px; }

/* ---- the brief rail, and the wireframe thumbnail inside it ----------------
   Modelled on the p3mai.com cost estimator: a sticky column beside the form
   listing what has been chosen, with one headline artefact at the top. There
   the artefact is a running total; here it is a picture of the page shape.

   STICKY, NOT FLOATING. A genuinely floating window over a form is a nuisance
   on a laptop and impossible on a phone. Sticky keeps it beside the questions
   while they are being answered and lets it fall back into the flow when there
   is no room, which is the same trade the estimator already made. */
.container-wide { max-width:1240px; }
.start-layout {
  display:grid; grid-template-columns:minmax(0, 1fr) 320px;
  gap:28px; align-items:start; }
.start-main { min-width:0; }

/* When anything scrolls itself into view -- tabbing through the form, or the
   browser jumping to a required field that failed on submit -- it stops at the
   top of the viewport, which is UNDERNEATH the sticky header. scroll-margin is
   the browser's own answer: leave this much room when scrolling me into view.
   It costs nothing when nothing is scrolling. */
.field, .choice, .choice-set, .form input, .form textarea, .form legend {
  scroll-margin-top:calc(var(--header-h) + 12px); }

.rail { position:sticky; top:calc(var(--header-h) + 18px); }
.rail-inner {
  background:var(--white); border:1px solid var(--line);
  border-radius:var(--radius-lg); box-shadow:var(--shadow-sm); overflow:hidden;
  /* Capped and scrollable so a tall rail can never push its own foot off the
     screen -- the warning about photographs lives at the bottom of it. */
  max-height:calc(100vh - var(--header-h) - 36px);
  display:flex; flex-direction:column; }
.rail-head { padding:14px 18px 12px; border-bottom:1px solid var(--line);
  background:var(--bg-alt); flex-shrink:0; }
.rail-head h2 { margin:0; font-size:.8rem; font-weight:700; letter-spacing:.07em;
  text-transform:uppercase; color:var(--primary); }

.rail-thumb { padding:16px 18px 0; flex-shrink:0; }
/* Caption and disclaimer travel TOGETHER, and together with the picture. On a
   phone this block slides into the docked strip beside the thumbnail, and the
   disclaimer is the one line that must never be the part left behind. */
.rail-say { padding:10px 18px 12px; flex-shrink:0; }
.rail-cap { margin:0; font-size:.8rem; color:var(--text); line-height:1.45; }
.rail-line { margin:4px 0 0; font-size:.72rem; color:var(--muted); }
.rail-panel { display:flex; flex-direction:column; min-height:0; }
.rail-list { margin:0; padding:0 18px 14px; overflow-y:auto; }
.rail-row { display:grid; grid-template-columns:96px minmax(0, 1fr);
  gap:10px; padding:5px 0; border-top:1px solid var(--line); }
.rail-row:first-child { border-top:0; }
.rail-row dt { margin:0; font-size:.74rem; font-weight:600; color:var(--muted);
  text-transform:uppercase; letter-spacing:.04em; padding-top:1px; }
.rail-row dd { margin:0; font-size:.82rem; color:var(--text);
  overflow-wrap:anywhere; }
/* An unanswered row is NOT greyed into invisibility. It carries a real
   sentence -- "we will invent one", "we will derive them" -- because a blank
   here is a decision we take on their behalf, and the point of showing it is
   that they can see us taking it. */
.rail-row dd.is-unset { color:var(--muted); font-style:italic; }

.rail-warn { margin:0; padding:12px 18px; font-size:.79rem; line-height:1.5;
  background:var(--bg-alt); border-top:1px solid var(--line);
  color:var(--text); flex-shrink:0; }

/* `[hidden]` first and unconditionally. A button set to display:flex below
   would otherwise beat the attribute and the control would show on desktop,
   where nothing docks and there is nothing for it to expand. */
.rail-toggle[hidden] { display:none; }
.rail-toggle {
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  width:100%; padding:12px 16px; border:0; border-top:1px solid var(--line);
  background:var(--bg-alt); color:var(--primary); font:inherit;
  font-size:.8rem; font-weight:600; cursor:pointer; text-align:left; }
.rail-toggle:focus-visible { outline:2px solid var(--accent); outline-offset:-3px; }
.rail-chev {
  width:9px; height:9px; flex-shrink:0; border-right:2px solid currentColor;
  border-bottom:2px solid currentColor; transform:rotate(45deg) translate(-2px, -2px);
  transition:transform .18s ease; }
.rail-toggle[aria-expanded="true"] .rail-chev {
  transform:rotate(-135deg) translate(-2px, -2px); }

/* ---- the wireframe itself ----
   Deliberately drab. This is the one graphic on the site that must NOT look
   like a finished page: a prototype is designed to look real, and a thumbnail
   that also looked real would be read as a promise of what arrives. Grey
   blocks, one accent, empty frames where photographs go. */
.wf { display:block; width:100%; height:auto; }
.wf-sprite { position:absolute; width:0; height:0; overflow:hidden; }
.wf-page  { fill:var(--white); }
.wf-edge  { fill:none; stroke:var(--line); stroke-width:1; }
.wf-band  { fill:#EDF0F4; }
.wf-band-alt { fill:#F4F6F8; }
.wf-band-none { fill:#F7F8FA; }
.wf-bar   { fill:#8E9AA8; }
.wf-bar-soft  { fill:#B9C2CC; }
.wf-bar-light { fill:#FFFFFF; }
.wf-dim   { opacity:.72; }
.wf-block { fill:#DDE2E8; }
/* The "blocks of colour" treatment. Three TONES, not three colours: the
   customer's brand colours are exactly what we do not know yet, and inventing
   them here would be the thumbnail making a promise the prototype has not
   earned. Tonally they still read as flat panels, which is the thing the option
   is actually describing.

   These were used in the SVG before they existed here, and an SVG fill that
   resolves to nothing falls back to BLACK -- so the treatment rendered as three
   solid black slabs. audit-wireframes.mjs now fails on any class a wireframe
   uses and the stylesheet does not define. */
.wf-block-a { fill:#6E7C8C; }
.wf-block-b { fill:#A7B2BF; }
.wf-block-c { fill:#C6CED8; }
.wf-tile  { fill:#E6EAEF; }
.wf-card  { fill:var(--white); stroke:#D2D9E1; stroke-width:1; }
.wf-field { fill:var(--white); stroke:#C3CCD6; stroke-width:1; }
.wf-accent { fill:var(--accent); }
.wf-rule  { stroke:#DDE2E8; stroke-width:1; }
.wf-hatch { stroke:#DCE2E9; stroke-width:2; }
/* An empty frame is not a placeholder for a photograph. It IS what the
   prototype will show, at the size it will show it, because we hold no
   pictures of theirs and use no stock. Dashed, so it reads as absence. */
.wf-frame, .wf-frame-sm {
  fill:#F2F4F7; stroke:#B9C2CC; stroke-width:1.5; stroke-dasharray:4 3; }
.wf-frame-hero { fill:#E4E9EF; }
.wf-frame-x { stroke:#C9D1DA; stroke-width:1; }
/* Nothing chosen yet, on either layer. Dashed for the same reason as the photo
   frames: it reads as a gap rather than as a decision. */
.wf-dash { fill:none; stroke:#C6CEd8; stroke-width:1.2; stroke-dasharray:3 3; }

/* ---- the logo sketch ----
   A separate picture on a separate canvas, because the logo question changes
   the shape of nothing. Compositing it into the page wireframe would claim a
   relationship between the two that does not exist.

   MONOCHROME, and that is a decision rather than an oversight. Colour is its
   own question one row further up the form, and the palette is derived from
   what the customer says rather than from our brand. A logo sketch in gold
   would quietly answer a question nobody has asked yet, and answer it wrongly. */
.rail-logo { padding:4px 18px 2px; }
.wf-logo { display:block; width:100%; max-width:210px; height:auto; }
.rail-logo-cap { margin:8px 0 0; font-size:.8rem; color:var(--text); line-height:1.45; }

.wf-name       { fill:#8E9AA8; }
.wf-name-soft  { fill:#B9C2CC; }
.wf-logo-mark  { fill:#6E7C8C; }
/* The cut-out inside the plain mark. Page white rather than transparent, so it
   reads as a shape removed from the mark and not as a hole onto whatever the
   rail happens to be sitting on. */
.wf-logo-cut   { fill:var(--white); }
.wf-logo-mark-s { fill:#6E7C8C; }
.wf-logo-mark-o { fill:#A7B2BF; }
.wf-logo-rib   { stroke:var(--white); stroke-width:1.4; }
/* Theirs, which we do not hold. Deliberately the same dashed language as the
   empty photograph frames, because it is the same statement: this is absent,
   and the absence is the honest thing to show. */
.wf-logo-dash  { fill:none; stroke:#B9C2CC; stroke-width:1.5; stroke-dasharray:4 3; }

@media (max-width:1100px) {
  /* ---- the docked strip -------------------------------------------------
     Below this width the two columns cannot both hold their width, so the rail
     leaves the side. The estimator's answer at this point is to let its rail
     fall into the flow, and that is what this did first -- which put it 6,218px
     into a 7,361px page. You answered all twelve questions and only then met
     the summary of what you had answered, which is the feature being absent
     rather than merely awkward.

     So on a phone it DOCKS: a strip pinned to the bottom carrying the picture,
     the caption and the disclaimer, tapping to expand the full list. The
     running-total idea the estimator sets up is exactly the mental model, and a
     bottom bar is where a running total belongs on a phone.

     `.rail-docked` IS APPLIED BY JAVASCRIPT, deliberately. Without it nothing
     docks and the rail stays in the flow as before -- so a visitor with no
     script never meets a collapsed panel with no way to open it, and the page
     degrades to something still true rather than to something broken. */
  .start-layout { grid-template-columns:minmax(0, 1fr); }
  .rail { position:static; }
  .rail-inner { max-height:none; }
  .rail-thumb { max-width:340px; }

  .rail-docked .rail {
    position:fixed; left:0; right:0; bottom:0;
    /* top:auto is NOT redundant. The desktop rule sets `top:calc(var(--header-h)
       + 18px)` for the sticky column, and that value survives the switch to
       position:fixed -- so with top AND bottom both set the strip stretched from
       96px down to the foot of the screen and docked to the TOP of the phone
       instead of the bottom. It looked deliberate. Measured, it was 96px from
       the top of an 800px viewport. */
    top:auto;
    /* Under the site header's 100. If the two ever meet, the header wins --
       it holds the way out of the page and this holds a summary. */
    z-index:90; }
  .rail-docked .rail-inner {
    display:grid; border-radius:0; border:0; border-top:1px solid var(--line);
    box-shadow:0 -6px 20px rgba(11, 37, 69, .13);
    /* 76px, up from 56. At 56 the page sketch was a smudge -- you could see
       it change but not see WHAT it changed to, which is the whole job of the
       thing. The caption keeps a 1fr column and still holds two lines at 360px,
       which is the narrowest width this estate designs for. */
    grid-template-columns:76px minmax(0, 1fr) auto;
    grid-template-areas:"thumb say toggle" "panel panel panel";
    align-items:center; column-gap:12px; padding:10px 14px 0;
    max-height:none; }
  .rail-docked .rail-head { display:none; }   /* its label moves to the toggle */
  .rail-docked .rail-thumb { grid-area:thumb; padding:0; max-width:none; }
  .rail-docked .rail-say { grid-area:say; padding:0; }
  .rail-docked .rail-cap {
    font-size:.78rem; line-height:1.35;
    /* THREE lines, not two. At two the longest captions clipped mid-phrase --
       "photograph behind the..." -- once the page sketch was widened and took
       the space back. The third line is nearly free: the sketch beside it is
       71px tall and two lines of caption plus the disclaimer only reached 66,
       so the strip grows by the difference rather than by a whole line.

       Clamped at all, rather than left to wrap, so the strip's height stays
       predictable and the padding keeping the submit button clear of it can be
       a constant rather than something JavaScript has to measure. */
    display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
    overflow:hidden; }
  .rail-docked .rail-line { font-size:.68rem; margin-top:2px; }
  .rail-docked .rail-toggle {
    grid-area:toggle; border:0; background:transparent;
    /* 44x44. With the label visually hidden only the 9px chevron is left, and
       padding alone gave a 23x25 target -- under the 24x24 WCAG 2.2 minimum,
       and far under what a thumb wants. The touch area is the button, not the
       glyph. */
    display:flex; align-items:center; justify-content:center;
    width:44px; min-width:44px; height:44px; padding:0; gap:0; }
  .rail-docked .rail-toggle-text {
    /* The label is on the button for a screen reader; on a 360px strip there is
       no room to print it, and the chevron plus the visible picture say it. */
    position:absolute; width:1px; height:1px; overflow:hidden;
    clip-path:inset(50%); white-space:nowrap; }

  .rail-docked .rail-panel {
    grid-area:panel; display:none; margin-top:10px;
    border-top:1px solid var(--line);
    max-height:min(58vh, 420px); overflow-y:auto;
    /* Cleared by the strip's own padding-bottom below. */ }
  .rail-docked.rail-open .rail-panel { display:flex; }
  .rail-docked .rail-logo { padding:12px 4px 0; }
  /* Down from 180. The logo lockup was over three times the width of the page
     sketch above it, which is backwards: the page shape is what the rail is for
     and the logo is a detail of it. 128px is still legible at arm's length --
     the five styles differ in the MARK, and the mark is the left third. */
  .rail-docked .wf-logo { max-width:128px; }
  .rail-docked .rail-list { padding:12px 4px 8px; }
  .rail-docked .rail-warn { margin:0 -14px; }

  /* Room for the strip, so the submit button and the last field are never
     underneath it. A constant works because the caption is clamped to two
     lines, which fixes the collapsed height. */
  /* The docked strip covers the bottom of the screen, so anything scrolled
     into view must clear it as well as the header. padding-bottom alone only
     protects the END of the form; this protects every field on the way down,
     which is where a scrolled-to element actually lands. */
  .rail-docked .field, .rail-docked .choice, .rail-docked .choice-set,
  .rail-docked .form input, .rail-docked .form textarea,
  .rail-docked .form legend, .rail-docked .form button {
    scroll-margin-bottom:120px; }
  .rail-docked .start-main { padding-bottom:122px; }
  .rail-docked .rail-inner { padding-bottom:10px; }
}

@media (max-width:1100px) and (prefers-reduced-motion:no-preference) {
  .rail-chev { transition:transform .18s ease; }
}

/* Belt and braces. The toggle is hidden by an attribute the script controls,
   and the script is careful -- but if its resize listener ever misses a
   crossing of this width, a desktop rail would carry a button that collapses a
   panel nothing above 1100px collapses. Hiding it in CSS as well means the
   worst a missed event can do is leave a class name lying around. */
@media (min-width:1101px) {
  .rail-toggle { display:none; }
}
