/* ======================================================================
   Platform (platform.html), desktop 1440. Wireframe Platform / 1440 (126:1802); contracts
   docs/BUILD-CONTRACT-platform-r1.md and -r2.md. Loads last. Shared masters it rides on: css/workflow.css (.wf-cards, PL01
   stages), css/stats.css (PL01 outcomes), css/list-icons.css (every icon list), css/bento.css (PL07 "Data driven").
   This file holds only the page layout, the module head row (.pl-module__head), the scaled figure wrapper (.pl-fig,
   page-neutral) and the figure crops. Tokens only.

   r2 (owner 2026-10-18, pointing at Home's "Municipal review": "can we use this kind of layout? with bullet points
   under"): PL02-PL06 are now Home's H04 layout: a split head row (eyebrow + H2 left, lead right), the screen at its
   native 1280 x 720 full width under it, the icon list under the screen. Gone: r1's alternating text | figure split,
   the 480 text column, the 80px margin bleed (1168f05) and every crop written for the 906 x 510 native size.
   ====================================================================== */

.pl-secondary { color: var(--pn-color-text-secondary); }

/* ---------- sections ----------
   128 between sections (site.css). Owner 2026-10-18: the PL00 in-page nav is removed; the first section sits
   128 under the fixed 72 header (intro top at y 200), and anchors land on html's own scroll-padding. */
.pl-overview { padding-top: calc(var(--pn-header-height) + var(--pn-space-128)); }
/* owner 2026-10-18 added a hairline between sections, centred in the 128 gap; owner 2026-11-01 removed it (audit VC-035:
   "remove six section rules on Platform"). Sections separate by the 128 gap alone. */
.pl-section + .pl-section { position: relative; }

/* section intro (PL01, PL07): eyebrow, heading, lead; 760 wide */
.pl-intro { max-width: 760px; display: flex; flex-direction: column; align-items: flex-start; gap: var(--pn-space-24); }
.pl-intro .t-lead { text-wrap: pretty; }

/* ---------- PL01 Overview ---------- */
/* the four stages on the workflow master's card variant (css/workflow.css .wf-cards); the page owns the card's rule
   and padding, the 24 gap and the column count (the master has 3 columns and no column dial) */
.pl-stages {
  /* owner 2026-11-01 (audit VC-038, "follow the step rhythm Contractors now has"): the row sits one between-rhythm (192)
     below the intro, the numeral's digit tops one inside-rhythm (96) below the card top, the title 48 under the numeral
     top, the master's 48 column gap. Was 64 / 32 / 80 / 24. */
  /* owner 2026-11-01, later the same day ("make this big gap the same size as the small gap"): this row sits under the page
     intro, not after a step, so it does not take the step rhythm after all. The numeral's digit tops sit at the card top
     and the row sits 64 below the lead: the same 64 that separates the row from the stats strip under it. */
  --wf-card-num-top: 0px;
  --wf-card-text-top: var(--pn-space-48); /* with the card's 32 padding: title 80 under the digit tops, the same relation as the Contractors and Our Story rows */
  --wf-card-head-min: var(--wf-card-title-lh); /* one-word titles: reserve one line, not two */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-top: var(--pn-space-128); /* owner 2026-11-01: 128 as the medium gap on this page (lead -> stages); was 64 */
}
.pl-stage { padding-top: var(--pn-space-32); } /* owner 2026-11-01: card hairline off, like the step rule in css/workflow.css; the 32 stays so nothing shifts */
.pl-stats { margin-top: var(--pn-space-128); } /* owner 2026-11-01: 128 medium gap (stages -> stats); was 64 */

/* ---------- PL02-PL06 modules (r2): Home H04 layout ----------
   Head row on the .split 6 + 6 (eyebrow + H2 left, lead right at the full 608 column), the screen full container width
   underneath (1280 x 720, 96 below the head, as Home's .figure), the feature list 64 under the screen. */
.pl-module__title { display: flex; flex-direction: column; align-items: flex-start; gap: var(--pn-space-24); min-width: 0; }
.pl-module__lead {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--pn-space-24); min-width: 0;
  /* owner 2026-11-01: the lead's top aligns with the title, not the eyebrow: one eyebrow line (16 x 1.4 = 22.4) + the 24 gap */
  padding-top: calc(var(--pn-type-ui-eyebrow-size) * 1.4 + var(--pn-space-24));
}
.pl-module__lead .t-lead { text-wrap: pretty; }

/* PL04 privacy line: Text/Body, secondary, lock icon centred on its first line (18 x 1.6 = 28.8) */
.pl-privacy { --li-ico-top: 6.4px; font: var(--pn-type-text-body); color: var(--pn-color-text-secondary); text-wrap: pretty; }

/* ---------- the scaled figure (page-neutral): a UI recreation laid out at its native size, scaled to the figure ----------
   Put .pl-fig on a .figure.figure--ui (site.css: 96 top margin, 16:9, surface fill, card radius, hairline) and the
   recreation inside .pl-fit. js/platform.js keeps --pl-fit = figure width / native width (ResizeObserver), so it is 1 at
   1440 (the figure is the 1280 container) and narrower viewports still fit. Crops are written in native coordinates on
   a modifier (.pl-fig--lookup …). Dials on the figure: --pl-fit-native-w / --pl-fit-native-h. */
.pl-fig {
  --pl-fit-native-w: 1280px;
  --pl-fit-native-h: 720px;
}
.pl-fit {
  position: absolute; top: 0; left: 0;
  width: var(--pl-fit-native-w); height: var(--pl-fit-native-h);
  transform: scale(var(--pl-fit, 1)); transform-origin: 0 0;
  overflow: hidden; pointer-events: none;
}
/* the scroll regions take the wheel, drag and arrow keys (the recreations inside keep pointer-events off) */
.pl-fit .figure__scroll, .pl-fig--lookup .lk-panel { pointer-events: auto; }
/* a bottom fade where a crop ends inside content (lookup panel, form), the same 96 as Home's .figure--ui-scroll */
.pl-fig--lookup .pl-fit::after, .pl-fig--form .pl-fit::after {
  content: ""; position: absolute; z-index: 4; left: 0; bottom: 0; height: 96px; pointer-events: none;
  background: linear-gradient(to bottom, rgba(11, 14, 20, 0), var(--pn-color-background-canvas)); /* owner 2026-11-04: to the canvas, no hairline (site.css) */
}

/* PL02: Home's hero window, settled: no app bar (house rule), panel 460 + map 820, the panel scrolls under a 96 fade */
.pl-fig--lookup .pl-fit { --hero-fade: 96px; } /* the panel's run-out and scrollbar track clear the fade, as in the hero */
.pl-fig--lookup .hw { border-radius: 0; box-shadow: none; }
.pl-fig--lookup .hw::after { content: none; } /* the figure draws the hairline */
.pl-fig--lookup .hw__bar { display: none; }
.pl-fig--lookup .lk { height: 100%; }
.pl-fig--lookup .lk-panel:focus-visible { outline: 2px solid var(--pn-color-focus); outline-offset: -2px; }
.pl-fig--lookup .pl-fit::after { width: 452px; } /* panel 460 less its scrollbar column, like site.css's 8px gap */
/* the aerial photo ends 641 below the world's top; the window here is 720 tall (no 44 bar), so the world (photo and
   overlay together, alignment unchanged) moves down 79 so the photo reaches the bottom edge (Municipalities MU06 does
   the same for its 676) */
.pl-fig--lookup .lk-map__world { transform: translateY(79px); }
/* Home's vignette, its centre moved down with the world so it stays on the parcel (site.css: 154 170 -> 154 249) */
.pl-fig--lookup .lk-map__vignette { background: radial-gradient(ellipse 620px 440px at 154px 249px, transparent 0%, color-mix(in srgb, var(--pn-color-background-canvas) 35%, transparent) 100%); }

/* PL03: the form-validation screen at its native 1280: main column 720 + rail 400 (48 sides, 64 between); its 800
   height is cut at 720 under the fade */
.pl-fig--form .pl-fit::after { right: 0; }

/* PL04: the shared Drawing Checker at native width, no crop sideways. Its body is 800 tall (site.css .dc-body, the Home
   H03 back window), so at 720 the frame cut both columns flat and hid their 96 fades; the body takes the frame's 720
   instead, the results column still scrolls and both fades sit on the frame's bottom edge (r2 builder: the contract
   assumed 720 native) */
.pl-fig--checker .ui, .pl-fig--checker .dc-body { height: 100%; }

/* PL05 / PL06: Home H04 and Municipalities MU05 exactly: .figure--ui-scroll (96 fade, 96 + 24 run-out) with the
   scroll region inside .pl-fit; the verifier's run-out rules live in site.css (.figure--ui-scroll .ui--verifier) */

/* ---------- feature lists under each module: 2-column icon list, 64 below the module ---------- */
.pl-feats {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--pn-layout-gutter); row-gap: var(--pn-space-32);
  margin: var(--pn-space-64) 0 0; padding: 0; list-style: none;
}
.pl-feats--1 { grid-template-columns: minmax(0, 1fr); margin-top: 0; }
/* icon centred on a UI/Label L first line (18 x 1.3 = 23.4) */
.pl-feat { --li-ico-top: 3.7px; min-width: 0; }
.pl-feat__t { font: var(--pn-type-ui-label-l); color: var(--pn-color-text-primary); }
.pl-feat__b { margin-top: var(--pn-space-8); font: var(--pn-type-text-body-m); color: var(--pn-color-text-secondary); text-wrap: pretty; } /* owner 2026-11-01: fills its column like the workflow captions (a 520 cap left ~96 unused on the right) */
/* PL05 "Direct integration" qualifier. Owner 2026-11-01 ("why is the vision aligned right?"): it sat inside the fourth list
   item, which is the right column of the two-column list, so it looked right-aligned. Now its own row under the list,
   left-aligned at the content edge, one row gap (32) below. */
.pl-vision { margin-top: var(--pn-space-32); display: flex; flex-direction: column; gap: var(--pn-space-8); }
.pl-vision__k { color: var(--pn-color-text-muted); }
.pl-vision__b { font: var(--pn-type-text-body-m); color: var(--pn-color-text-muted); max-width: calc(50% - var(--pn-layout-gutter) / 2); text-wrap: pretty; } /* one list column wide (608), so it lines up with the captions above it */

/* ---------- PL04 "What we check" (131:1333) ---------- */
 /* owner 2026-11-01 ("remove this title, tighten up the drawing checker section"): the "Exactly what the check looks at" title is
   gone; the two scope cards follow the feature list directly and every gap inside the block is 64 (was 96 / 32 / 96). */
.pl-check { margin-top: var(--pn-space-64); }
.pl-check__title { max-width: 760px; text-wrap: balance; }
.pl-check__title--docs { margin-top: var(--pn-space-64); }
/* owner 2026-11-04 ("make this a table without card bg"): the two scope cards are one table. No fill, no padding; the
   two columns sit a gutter apart; eyebrow, lead and list share rows across the columns (grid-auto-flow: column with
   three rows, the cards themselves display: contents), so both lists start level under the taller lead; then one
   hairline-ruled row per item, 16 above and below the text, a closing rule under the last row. */
.pl-scope {
  --pl-scope-inset: var(--pn-space-32); /* owner 2026-11-04 ("one table without a gap in the middle"): no column gap, the rules run the full width; the right column's text is inset this much from the seam, the left lead keeps the same off its end */
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: auto auto auto; grid-auto-flow: column;
  column-gap: 0; row-gap: 0;
}
.pl-scope__card { display: contents; }
.pl-scope__card:not(.pl-scope__card--out) .pl-scope__lead { padding-right: var(--pl-scope-inset); }
.pl-scope__card--out .pl-scope__k, .pl-scope__card--out .pl-scope__lead { padding-left: var(--pl-scope-inset); }
.pl-scope__card--out .li-item { padding-left: calc(24px + var(--pl-scope-inset)); }
.pl-scope__card--out .li-ico { left: var(--pl-scope-inset); }
/* the right list is one row short: it stretches to the left list's height and a flex-filling ::after draws the
   row rule across the empty cell, so the 7/8 rule and the closing rule both run the full width */
.pl-scope__card--out .pl-scope__list { align-self: stretch; }
.pl-scope__card--out .pl-scope__list::after { content: ""; flex: 1 1 auto; border-top: 1px solid var(--pn-color-border-decorative); }
.pl-scope__k { color: var(--pn-color-text-primary); }
.pl-scope__card--out .pl-scope__k { color: var(--pn-color-text-muted); }
.pl-scope__lead { margin-top: var(--pn-space-16); font: var(--pn-type-text-body); color: var(--pn-color-text-primary); text-wrap: pretty; }
.pl-scope__card--out .pl-scope__lead { color: var(--pn-color-text-secondary); }
.pl-scope__list { margin-top: var(--pn-space-32); align-self: start; display: flex; flex-direction: column; border-bottom: 1px solid var(--pn-color-border-decorative); }
.pl-scope__list .li-item { padding-top: var(--pn-space-16); padding-bottom: var(--pn-space-16); border-top: 1px solid var(--pn-color-border-decorative); font: var(--pn-type-text-body-m); color: var(--pn-color-text-secondary); }
.pl-scope__list .li-item .li-ico { --li-ico-top: calc(var(--pn-space-16) + 4.7px); } /* the row's top padding, then the body-m first-line offset from list-icons.css */
.pl-scope__card--out .li-ico { stroke: var(--pn-color-text-muted); }
.pl-feats--docs { margin-top: var(--pn-space-32); }
.pl-footnote { margin-top: var(--pn-space-48); max-width: 560px; font: var(--pn-type-text-body-m); color: var(--pn-color-text-muted); text-wrap: pretty; }
/* owner 2026-11-01: the PL04 building-code footnote is gone; the privacy line (lock icon) sits in its slot, 48 under the check lists */
.pl-privacy--foot { margin-top: var(--pn-space-48); }

/* ---------- PL07 Data engine ---------- */
.pl-sources { margin-top: var(--pn-space-64); align-items: center; }
/* Plan/Detail (134:1356): the checker's plan sheet, inverted into the dark system as the checker preview does it */
.pl-sheet { justify-self: end; width: 544px; max-width: 100%; }
.pl-sheet__frame {
  position: relative; aspect-ratio: 544 / 408; overflow: hidden;
  background: var(--pn-color-background-surface); border-radius: var(--pn-radius-card);
  box-shadow: inset 0 0 0 1px var(--pn-color-border-decorative);
}
.pl-sheet__img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%;
  filter: invert(1) grayscale(1); mix-blend-mode: screen; opacity: 0.78;
}
/* .pl-sheet__cap removed with its figcaption (owner 2026-11-01: "remove this" on the Fig. caption) */

.pl-smarter { margin-top: var(--pn-space-96); } /* same sub-block gap as PL04's "what we check" */
.pl-smarter__head { display: flex; flex-direction: column; align-items: flex-start; gap: var(--pn-space-16); }
.pl-smarter__head .t-body { max-width: 608px; text-wrap: pretty; }

/* ---------- PL01 brand mark (docs/BUILD-CONTRACT-platform-brandmark.md, owner 2026-11-01; r2 = §6) ----------
   The logo mark, giant, behind the hero, anchored top-right and bleeding off the viewport. Position from the approved
   mock (docs/qa/wf-gap/mock-brandmark-v2-settled.png): SVG 1180 x 1118 over the PNG's 512 x 485, right -420 / top -330
   in a full-bleed wrapper that starts under the header. The paths are re-traced from assets/logo-mark.png (ring centre
   246,237 r 218; check 160,178 > 241,294 > 495,62); the group's translate(18 19) puts the ring's centre where the mock's
   sat. r2 (owner: "subtle, too fast; texture and thickness; in and out, on loop"): stroke 30 in the 512 space (the logo's
   ring is ~60 thick; 30 x 1180/512 = 69 px on screen), each path drawn twice: a quiet base stroke, then a dotted texture
   on top (the site's dot-grid idea, .bento__fig::before): 1.6-unit dots on a 9-unit pitch (3.7 px dots, 20.7 px pitch).
   The check stays the one accent. Decorative, aria-hidden. */
.pl-overview { position: relative; }
.pl-overview > :not(.pl-brandmark) { position: relative; z-index: 1; }
.pl-brandmark {
  position: absolute; z-index: 0; top: calc(-1 * var(--pn-header-height)); bottom: 0; left: 50%;
  width: 100vw; transform: translateX(-50%);
  overflow: hidden; pointer-events: none;
  /* architect 2026-11-01, from verification: the mark fades in below the header (r2's 69px band showed a hard cut and a smear in the header's
     blur when the fade started above it; it is fully clear until 150, the header's bottom, then eases in by 270) and fades out across the numeral band, so the ring never runs through card 04's caption. Wrapper y: header
     bottom = 144 (the wrapper starts one header above the section), numerals 540-640, captions from ~630. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 150px, #000 270px, #000 520px, transparent 625px);
  mask-image: linear-gradient(to bottom, transparent 150px, #000 270px, #000 520px, transparent 625px);
}
.pl-brandmark__svg { position: absolute; right: -453px; top: -330px; width: 1180px; height: 1118px; overflow: visible; }
.pl-brandmark path { fill: none; stroke-width: 30; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 0; }
/* r3 (owner: "the end of the thick line is a gradient, not just a rounded cap"): each layer is a <g> of six identical copies,
   each lagging the one before by 140 ms (below), so the moving end steps down to nothing over ~15% of the path. The colour
   sits on the <g> (inherited); the alpha sits on every copy, never the <g> (a group opacity would flatten the stack).
   Per-copy alpha a = 1 - (1 - A)^(1/6), so the six stacked copies at the hold give r2's A exactly:
   twelve copies at 70 ms (architect 2026-11-01: six at 140 ms showed stepped caps): a = 1 - (1 - A)^(1/12):
   ring base 0.55 > 0.0643, check base 0.22 > 0.0205, (owner 2026-11-01: the dot texture is gone; base strokes only). */
.pl-brandmark__ring { stroke: var(--pn-color-border-decorative); }
.pl-brandmark__check { stroke: var(--pn-color-brand-green); }
.pl-brandmark__ring .pl-brandmark__p { opacity: 0.0643; }
.pl-brandmark__check .pl-brandmark__p { opacity: 0.0205; }
/* the trace, on a slow 16 s loop (js/platform.js adds .is-running on first view): draws in 0-30% (4.8 s), holds to 55%
   (4 s), draws out from the start to 85% (4.8 s: offset 0 > -1, the line leaves the way it came in), then rests empty
   to 100% (2.4 s). The check leads; the ring runs 1.2 s behind on the same period, so they stay in phase. Base and
   texture copies share the keyframe and delay. Stroke-dashoffset only: paint, no layout. Gated on .js and on motion
   being allowed, like the hero's [data-anim] (site.css), so no script or reduced motion shows the finished mark. */
@keyframes pl-bm-trace {
  /* owner 2026-11-01 ("the duration is too long; fade out not too long after fade in"): 10 s loop, in 3.5 s, hold 0.6 s,
     out 3.5 s, empty 2.4 s (was 16 s: in 4.8, hold 4, out 4.8, empty 2.4) */
  0% { stroke-dashoffset: 1; }
  35% { stroke-dashoffset: 0; }
  41% { stroke-dashoffset: 0; }
  76% { stroke-dashoffset: -1; }
  100% { stroke-dashoffset: -1; }
}
@media (prefers-reduced-motion: no-preference) {
  .js .pl-brandmark path { stroke-dashoffset: 1; }
  .js .pl-brandmark.is-running path { animation: pl-bm-trace 10s ease-in-out infinite both;
    animation-delay: calc(var(--pl-bm-base, 0s) + var(--pl-bm-k, 0) * 70ms); }
  .js .pl-brandmark.is-running .pl-brandmark__ring { --pl-bm-base: 1.2s; }
  /* r3: copy k (0-5) of every layer trails copy 0 by k x 140 ms of travel: the tapered tail */
  .pl-brandmark__p:nth-child(2) { --pl-bm-k: 1; }
  .pl-brandmark__p:nth-child(3) { --pl-bm-k: 2; }
  .pl-brandmark__p:nth-child(4) { --pl-bm-k: 3; }
  .pl-brandmark__p:nth-child(5) { --pl-bm-k: 4; }
  .pl-brandmark__p:nth-child(6) { --pl-bm-k: 5; }
  .pl-brandmark__p:nth-child(7) { --pl-bm-k: 6; }
  .pl-brandmark__p:nth-child(8) { --pl-bm-k: 7; }
  .pl-brandmark__p:nth-child(9) { --pl-bm-k: 8; }
  .pl-brandmark__p:nth-child(10) { --pl-bm-k: 9; }
  .pl-brandmark__p:nth-child(11) { --pl-bm-k: 10; }
  .pl-brandmark__p:nth-child(12) { --pl-bm-k: 11; }
}
/* under 1440 the page margin drops to 40 (site.css) while the mark stays pinned to the viewport's right edge, so at 1280 the
   ring ran through the headline. The mark moves with the content instead: right = 100vw - 1820 keeps the ring's left
   edge 18 right of the lead's last glyph, as at 1440 (-540 at 1280). */
@media (max-width: 1439px) { .pl-brandmark__svg { right: calc(100vw - 1853px); } }

/* ======================================================================
   Responsive tiers (docs/BUILD-CONTRACT-responsive-2026-11-04.md, builder 2A). The module figures keep their native
   1280 x 720 recreations and scale whole (.pl-fig / js/platform.js); nothing inside them reflows. The brand mark is
   re-anchored to the viewport's content edges at tablet (no 100vw box, so no scrollbar-width overflow) and hidden at
   phone. Gaps step down the ladder: 128 -> the section gap, the 96 head -> figure gap -> the inside rhythm.
   ====================================================================== */

/* ---- responsive: tablet (2026-11-04) ---- */
@media (max-width: 1023px) {
  /* PL01: the two 128 medium gaps follow the section gap (96 here) */
  .pl-stages, .pl-stats { margin-top: var(--section-gap); }
  /* PL04 scope table: one column from tablet down (two 328 columns at 768 wrapped rows, so the rules fell out of step
     across the columns); the second group one inside-rhythm (48) under the first list */
  .pl-scope { --pl-scope-inset: 0px; grid-template-columns: minmax(0, 1fr); grid-template-rows: none; grid-auto-flow: row; }
  .pl-scope__card--out .pl-scope__k { margin-top: var(--pn-space-48); }
  .pl-scope__card--out .pl-scope__list::after { content: none; } /* no empty cell to rule in one column */
  /* the brand mark: wrapper spans the content box plus the page margins (= the viewport, without 100vw), the mark at
     60% of its desktop size, still top-right and bleeding off the right edge; the wrapper clips it */
  .pl-brandmark { left: calc(-1 * var(--page-margin)); right: calc(-1 * var(--page-margin)); width: auto; transform: none; }
  .pl-brandmark__svg { right: -272px; top: -198px; width: 708px; height: 671px; }
  /* modules: the screen one inside-rhythm (64) under the head, the list 64 under the screen */
  .pl-fig { margin-top: var(--pn-space-64); }
  /* the approvals recreation's title rides the heading-sub token, which steps down at this tier: pin the desktop value
     so the native 1280 screen does not reflow (it is scaled whole) */
  .pl-fit .ua-title { font: 700 26px/1.2 var(--pn-font-display); }
  /* feature lists: one column (two columns of three left a lone item, and a two-line title beside a one-line one
     staggered the bodies) */
  .pl-feats { grid-template-columns: minmax(0, 1fr); }
}

/* ---- responsive: phone (2026-11-04) ---- */
@media (max-width: 767px) {
  .pl-brandmark { display: none; }
  /* the head stacks (.split, site.css): the lead sits 16 under the title, so the eyebrow-alignment padding goes */
  .pl-module__lead { padding-top: 0; }
  .pl-fig { margin-top: var(--pn-space-48); }
  .pl-feats { margin-top: var(--pn-space-48); }
  .pl-feats--1 { margin-top: 0; }
  .pl-vision__b { max-width: none; }
  /* PL07: the plan sheet stacks under the source list, one inside-rhythm below it (the .split's 16 reads as cramped) */
  .pl-sources { row-gap: var(--pn-space-48); }
  .pl-sheet { justify-self: stretch; width: 100%; }
}
