/* ============================================================================
   GovTheme by Doghouse — homepage.css
   The marketing landing page. terms.html and privacy.html load it too, for
   the reset and the page ground; the rest of it finds nothing to match there.

   The brand layer underneath it is css/brand.css, linked first; between the
   two, index.html loads nothing else of ours but js/install-dialog.js and
   js/homepage.js — no tokens.css, no govtheme.js. The homepage is
   not a themeable surface (see CLAUDE.md), so it has no business loading the
   live-theming token layer that govtheme.html and demo/* depend on — and those
   pages have no business being destabilised by a marketing redesign. The
   duplication below (reset, cards, section styling) is the point of the split,
   not an oversight.

   Namespace: everything is .hp-*. Nothing here may be relied on by another
   page, and nothing here may rely on another page's stylesheet.

   Built from Figma "Homepage" (file QALf7Vu6lXpgeJ3wj0aDDB, node 19:130),
   a desktop-only 1920×7224 frame. Everything below 1920 is derived here.
   ========================================================================== */

/* Self-hosted (fonts/, OFL), latin subset. One variable Hanken Grotesk file
   carries every weight; the other two are the hero cycle's borrowed faces.
   Declared here rather than in brand.css, which the kit pages load beside
   the kit's own faces. */
@font-face { font-family: 'Hanken Grotesk'; font-style: normal; font-weight: 400 900; font-display: swap; src: url(../fonts/hanken-grotesk-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 700; font-display: swap; src: url(../fonts/source-serif-4-700-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Archivo'; font-style: italic; font-weight: 800; font-display: swap; src: url(../fonts/archivo-800-italic-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

/* ═══════════════════════════════════════════════════════════════════════════
   2. Reset
   Local to the homepage. Small on purpose — it covers what this page uses.
   ═══════════════════════════════════════════════════════════════════════════ */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
/* A margin on each element, not scroll-padding on html: padding applies to the
   sticky masthead too, which never moves, so focusing in it would scroll the
   page up. css/brand.css zeroes it on the masthead and the dialog. */
* { scroll-margin-top: 120px; }

body {
  margin: 0;
  font-family: var(--hp-font);
  font-size: 18px;
  line-height: 1.4;
  letter-spacing: var(--hp-track);
  color: var(--hp-ink);
  background: var(--hp-surface);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;

  /* The dotted ground the whole comp sits on. One rule — delete it and the
     page is plain white. The values are the brand's, in css/brand.css §1; a
     bare `body` selector cannot live there, so the rule is restated on each
     surface that wears it and only the values are shared. */
  background-image: var(--hp-ground-dots);
  background-size: var(--hp-ground-grid);
  background-position: 0 0;
  /* Pinned to the viewport, so the dots hold still and the content scrolls
     over them. Must come after the `background:` shorthand above, which resets
     attachment to `scroll`. Mirrored on the showcase in css/showcase.css and
     on the preview shell in css/brand.css §8. */
  background-attachment: fixed;
}

h1, h2, h3, h4 { margin: 0; font-weight: 900; line-height: 1.04; letter-spacing: var(--hp-track); }
p, figure, figcaption, fieldset, legend { margin: 0; }
/* figure's UA margin is 40px each side — enough to burst the migration
   diagram's flex row out of the viewport if it survives. */
fieldset { border: 0; padding: 0; min-width: 0; }
legend { padding: 0; }
ul { margin: 0; padding: 0; list-style: none; }
img, svg { max-width: 100%; display: block; }
button { font: inherit; letter-spacing: inherit; color: inherit; background: none; border: 0; padding: 0; }
button.hp-arrowlink { cursor: pointer; }
a { color: inherit; }
input, select, textarea { font: inherit; letter-spacing: inherit; color: inherit; }

/* One focus treatment for everything interactive. A solid ink ring with a
   white spacer reads on the white ground, on the dark bands and on the red
   button alike, which a single-colour outline would not. */
:focus-visible {
  outline: 3px solid var(--hp-ink);
  outline-offset: 3px;
  border-radius: var(--hp-radius-sm);
}
.hp-band--dark :focus-visible,
.hp-demo :focus-visible {
  outline-color: #FFFFFF;
}

::selection { background: rgba(244, 37, 52, .18); }

/* ═══════════════════════════════════════════════════════════════════════════
   6. Hero
   ═══════════════════════════════════════════════════════════════════════════ */

/* Keyed to viewport height, not width: what has to hold is the hero fitting
   on one screen, so on a short laptop the padding gives way before the type
   does. Floors at 20px and stops growing past 150px. */
.hp-hero { position: relative; padding-block: clamp(20px, 16vh, 150px); text-align: center; }
/* The frame's hero box is 863px. This runs a little wider because line two is
   "every agency's " plus the cycling word's reserved width — and that
   reservation is the WIDEST of the three variants (Archivo italic, ~257px),
   not the ~231px Hanken one the comp measured. The extra width keeps the
   designed two-line break with headroom for platform font metrics. */
.hp-hero__inner {
  max-width: 950px;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 43px;
}

/* align-items:center would otherwise shrink the h1 to fit-content, leaving
   line two with no slack at all against its own max-content width. */
.hp-hero h1 {
  width: 100%;
  font-size: clamp(40px, 4.57vw, 87.78px);
  line-height: 1.04;
  font-weight: 900;
}
.hp-hero__lead { font-size: clamp(19px, 1.46vw, 28px); line-height: 1.4; max-width: 743px; }
.hp-hero__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; }

/* — The cycling word —
   An inline-grid stacks all three type variants in a single cell, so the box
   is as wide as the WIDEST of them and every variant is centred in it. That
   reserves the space up front: swapping Hanken for Source Serif for Archivo
   cannot move "every agency's" a pixel, and no width has to be measured in
   JS. Only state 1 is exposed to assistive tech (§HTML) so the heading reads
   once, not three times. */
.hp-cycle {
  display: inline-grid;
  position: relative;
  vertical-align: baseline;
  isolation: isolate;
}
.hp-cycle__state {
  grid-area: 1 / 1;
  justify-self: center;
  opacity: 0;
  transition: opacity .28s var(--hp-ease);
}
.hp-cycle__state.is-on { opacity: 1; }

/* First paint, and the permanent resting state — so it wears the page's own
   red at both ends of the run, and the token colours only appear mid-cycle.
   Visibility is carried solely by .is-on (set in the markup for first paint);
   an `opacity: 1` here would outrank the base rule and never fade out. */
.hp-cycle__state--1 { font-family: var(--hp-font); font-weight: 800; font-style: normal; color: var(--hp-accent-text); }
.hp-cycle__state--2 { font-family: var(--hp-font-serif); font-weight: 700; font-style: normal; color: var(--hp-token-accent); }
/* The italic's glyphs lean past their advance width, so it needs a sliver of
   padding or the "d" clips the marker. Because the grid cell sizes to the
   widest child, that padding also widens the reservation for everyone. */
.hp-cycle__state--3 { font-family: var(--hp-font-alt); font-weight: 800; font-style: italic; color: #3D5CB3; padding-right: .06em; }

/* The dashed marker around the word, with its four corner handles — a real
   layer in the Figma frame, and a fair signal that this is the configurable
   part. Drawn with outline so it takes no space and never nudges the line. */
.hp-cycle__marker {
  position: absolute;
  inset: 4% -3%;
  outline: 2px dashed var(--hp-ink);
  pointer-events: none;
}
.hp-cycle__marker i {
  position: absolute;
  width: 12px; height: 12px;
  background: var(--hp-ink);
}
.hp-cycle__marker i:nth-child(1) { left: -7px;  top: -7px; }
.hp-cycle__marker i:nth-child(2) { right: -7px; top: -7px; }
.hp-cycle__marker i:nth-child(3) { left: -7px;  bottom: -7px; }
.hp-cycle__marker i:nth-child(4) { right: -7px; bottom: -7px; }

/* ═══════════════════════════════════════════════════════════════════════════
   7. The decorative grid: red hairlines, and the four-dot glyph
   Full-bleed rules that cross the page at section seams, with a vertical run
   dropping from the intersection. Purely graphic — aria-hidden in the markup.
   ═══════════════════════════════════════════════════════════════════════════ */

/* A direct child of <main>, so it is already full-bleed — no 100vw needed.
   (100vw counts the scrollbar, which would push a horizontal scrollbar onto
   every platform that reserves gutter space for one.) */
/* The element itself is only the positioning context for the vertical run and
   the glyph; the hairline lives on ::before. That split is what keeps the
   draw-on transform (§17) off those children — a scaleX on the parent scales
   its descendants too, so the vrule would sweep in from the left along with
   the line instead of dropping from the top on its own. */
.hp-rule {
  position: relative;
  height: 1px;
  width: 100%;
}
.hp-rule::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  border-top: 1px solid var(--hp-accent);
  transform-origin: left center;
}

/* The vertical run drops from the crossing point down the outer margin. In
   the 1920 comp it lands on x=320 / x=1600 — the edges of the 1280 masthead
   column, 48px clear of the 1184 content. Below 1400px there is no margin
   left to run down and it would strike the text, so it is simply not drawn;
   the horizontal rules carry the motif on their own. */
.hp-vrule {
  display: none;
  position: absolute;
  top: 0;
  width: 0;
  pointer-events: none;
}
/* Same split as .hp-rule: the line is the pseudo-element and takes the
   scaleY, so the end caps below stay round instead of being flattened
   along with it. */
.hp-vrule::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  border-left: 1px solid var(--hp-accent);
  transform-origin: center top;
}

/* A stop at each end, so the run terminates rather than just petering out.
   Shared with the steps rail (§11), which is the same zero-width box with the
   same 1px line at x=0 — a 7px cap therefore centres at left: -3px on both. */
.hp-vrule i,
.hp-steps__rail i {
  position: absolute;
  left: -3px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--hp-accent);
}
.hp-vrule i:first-child,
.hp-steps__rail i:first-child { top: -3px; }
.hp-vrule i:last-child,
.hp-steps__rail i:last-child { bottom: -3px; }

/* The four-dot Doghouse/GovTheme glyph, sat on the crossing point. */
.hp-glyph {
  position: absolute;
  top: 50%;
  width: 50px; height: 50px;
  transform: translate(-50%, -50%);
  opacity: .25;
  pointer-events: none;
}
.hp-glyph--mid { left: 50%; }
.hp-glyph--left, .hp-glyph--right { display: none; }

/* 1400 is the first width at which the 25px glyph radius still clears the
   viewport edge once it is centred on the column edge. */
@media (min-width: 1400px) {
  .hp-vrule { display: block; }
  .hp-vrule--right { right: calc(50vw - var(--hp-maxw-header) / 2); }
  .hp-vrule--left  { left:  calc(50vw - var(--hp-maxw-header) / 2); }
  .hp-glyph--left, .hp-glyph--right { display: block; }
  .hp-glyph--right { left: auto; right: calc(50vw - var(--hp-maxw-header) / 2); transform: translate(50%, -50%); }
  .hp-glyph--left  { left: calc(50vw - var(--hp-maxw-header) / 2); }
}
.hp-glyph circle { fill: var(--hp-ink); }
.hp-glyph circle:last-child { fill: var(--hp-accent); }

/* ═══════════════════════════════════════════════════════════════════════════
   8. Cards — the shared offset-shadow treatment
   ═══════════════════════════════════════════════════════════════════════════ */

.hp-card {
  position: relative;
  background: var(--hp-surface);
  border: 1px solid var(--hp-ink);
  border-radius: var(--hp-radius);
  box-shadow: var(--hp-offset);
  display: flex;
  flex-direction: column;
}
.hp-card--feature { border-width: 2px; }

/* — Standards trio — */
.hp-standards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px 89px;
  margin-top: 78px;
}
.hp-standard { align-items: center; text-align: center; padding: 21px 30px 26px; gap: 7px; }
.hp-standard__art { width: 100%; max-width: 156px; height: 104px; }
.hp-standard__art svg { width: 100%; height: 100%; }
.hp-standard h3 { font-size: 24px; font-weight: 700; line-height: 1.04; margin-top: 6px; }
.hp-standard p { font-size: 16px; line-height: 1.2; }

/* — "Three ways to run" trio — */
.hp-ways {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px 89px;
  margin-top: 44px;
}
.hp-way { align-items: center; text-align: center; padding: 21px 30px 26px; gap: 14px; }
.hp-way__art { height: 104px; display: flex; align-items: center; justify-content: center; }
.hp-way h3 { font-size: 24px; font-weight: 700; line-height: 1.04; }
.hp-way p { font-size: 16px; line-height: 1.2; }
.hp-way__top { display: flex; flex-direction: column; align-items: center; gap: 13px; }
.hp-ways__more { margin-top: 40px; display: flex; justify-content: center; }
.hp-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: var(--hp-radius-pill);
  background: var(--hp-accent-text);
  color: #FFFFFF;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.3;
}

/* — "What's inside" trio — */
.hp-inside {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px 89px;
  margin-top: 56px;
}
.hp-inside__card {
  padding: 26px;
  gap: 16px;
  justify-content: space-between;
  min-height: 308px;
  text-decoration: none;
  transition: transform .16s var(--hp-ease), box-shadow .16s var(--hp-ease);
}
a.hp-inside__card:hover { transform: translate(-2px, -2px); box-shadow: 10px 8px 0 0 var(--hp-ink); }
/* Icon, then title, then body — 11px between the text rows and 16px under
   the icon, matching the frame. */
.hp-inside__card > div:not(.hp-inside__tags) { display: flex; flex-direction: column; gap: 11px; }
.hp-inside__icon { margin-bottom: 5px; }
.hp-inside__card h3 { font-size: 24px; font-weight: 700; line-height: 1.04; }
.hp-inside__card p { font-size: 16px; line-height: 1.2; color: var(--hp-ink-soft); }
.hp-inside__icon { width: 52px; height: 52px; }
.hp-inside__tags { display: flex; flex-wrap: wrap; gap: 8px; }
.hp-inside__tags span {
  display: inline-flex;
  align-items: center;
  padding: 4px 11px;
  border: 1px solid var(--hp-line);
  border-radius: var(--hp-radius-pill);
  background: #FFFFFF;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
}
.hp-inside__more { margin-top: 40px; display: flex; justify-content: center; }

/* ═══════════════════════════════════════════════════════════════════════════
   9. "Your brand, not ours" — copy beside the configurator scene
   ═══════════════════════════════════════════════════════════════════════════ */

.hp-brand__grid {
  display: flex;
  align-items: center;
  gap: 60px;
  padding-inline: 65px;
}
.hp-brand__copy { width: 400px; flex: none; display: flex; flex-direction: column; align-items: flex-start; gap: 28px; }
.hp-brand__copy h2 { font-size: 48px; }
.hp-brand__copy .hp-lead { max-width: none; }

/* The scene is a fixed 684×490 drawing. It scales as one piece rather than
   reflowing, because every panel inside it is specified in absolute px and
   reflowing them would just produce a different, worse drawing. */
.hp-scene-wrap {
  --s: 1;
  flex: none;
  width: calc(684px * var(--s));
  height: calc(490px * var(--s));
  position: relative;
}
.hp-scene {
  position: absolute;
  inset: 0;
  width: 684px;
  height: 490px;
  transform: scale(var(--s));
  transform-origin: top left;

  /* Recoloured on a timer by js/homepage.js to show the same layout under
     four different brand palettes. */
  --sc-primary: #82B5A0;
  --sc-solid: #618F7C;
  --sc-tint: rgba(130, 181, 160, .26);
  --sc-accent: #754C4F;
  transition: none;
}
.hp-scene__ellipse { position: absolute; left: 94px; top: 0; width: 490px; height: 490px; }

.hp-scene__panel,
.hp-scene__browser {
  position: absolute;
  top: 49px;
  background: #FFFFFF;
  border: 1px solid var(--hp-ink);
  border-radius: var(--hp-radius);
  box-shadow: 7px 6px 0 0 #000000;
  overflow: hidden;
}
.hp-scene__panel { left: 0; width: 170px; }
.hp-scene__browser { left: 237px; width: 447px; height: 372px; }

.hp-scene__head { background: var(--hp-ink); color: #FFFFFF; padding: 11px 14px; font-size: 14px; font-weight: 700; line-height: 1.3; }
.hp-scene__fields { display: flex; flex-direction: column; gap: 13px; padding: 14px 14px 16px; }
.hp-scene__field { display: flex; flex-direction: column; gap: 5px; }
.hp-scene__field > span { font-size: 11px; line-height: 1.3; color: var(--hp-ink-soft); }
.hp-scene__ctl { display: flex; align-items: center; gap: 7px; }
.hp-scene__swatch { flex: none; width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--hp-ink); }
.hp-scene__swatch[data-hp-sw='primary'] { background: var(--sc-solid); }
.hp-scene__swatch[data-hp-sw='accent'] { background: var(--sc-accent); }
.hp-scene__input {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  padding: 4px 8px;
  border: 1px solid var(--hp-line);
  border-radius: var(--hp-radius-sm);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
}
.hp-scene__input--between { justify-content: space-between; gap: 6px; }
.hp-scene__aa {
  flex: none;
  padding: 3px 6px;
  border: 1px solid var(--hp-ink);
  border-radius: var(--hp-radius-sm);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.3;
}
.hp-scene__val { font-size: 11px; font-weight: 500; line-height: 1.3; }
.hp-scene__slider { position: relative; flex: 1; height: 14px; }
.hp-scene__slider i { position: absolute; left: 0; top: 4.5px; height: 5px; border-radius: 2.5px; }
.hp-scene__slider i:nth-child(1) { width: 80px; background: var(--hp-line-soft); }
.hp-scene__slider i:nth-child(2) { width: 53px; background: var(--hp-ink); }
.hp-scene__slider i:nth-child(3) { left: 46px; top: 0; width: 14px; height: 14px; border-radius: 50%; background: #FFFFFF; border: 2px solid var(--hp-ink); }

.hp-scene__chrome { height: 26px; border-bottom: 1px solid var(--hp-ink); display: flex; align-items: center; gap: 5px; padding-inline: 10px; }
.hp-scene__chrome i { width: 3.4px; height: 3.4px; border-radius: 50%; background: var(--hp-line); }
.hp-scene__site { display: flex; flex-direction: column; }
.hp-scene__sitebar { background: var(--sc-primary); color: #FFFFFF; display: flex; align-items: center; justify-content: space-between; padding: 12px 18px; }
.hp-scene__brand { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; line-height: 1.3; }
.hp-scene__sitenav { display: flex; align-items: center; gap: 7px; font-size: 11px; line-height: 1.3; }
.hp-scene__sitehero { display: flex; align-items: center; gap: 14px; padding: 20px 18px 18px; }
.hp-scene__sitecopy { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.hp-scene__sitebadge { padding: 3px 9px; border-radius: var(--hp-radius-pill); background: var(--sc-tint); color: var(--sc-solid); font-size: 10px; font-weight: 700; line-height: 1.3; }
/* A span, not a heading — the scene is a picture of a website, and a real
   <h4> in here would put a phantom rung in the page's heading outline. */
.hp-scene__h { display: block; font-size: 20px; font-weight: 700; line-height: 1.3; }
.hp-scene__sitecopy p { font-size: 11px; line-height: 1.3; color: var(--hp-ink-soft); }
.hp-scene__sitebtns { display: flex; gap: 8px; }
.hp-scene__sitebtns span { padding: 6px 12px; border-radius: 6px; font-size: 11px; font-weight: 500; line-height: 1.3; }
.hp-scene__sitebtns span:first-child { background: var(--sc-solid); color: #FFFFFF; }
.hp-scene__sitebtns span:last-child { background: #FFFFFF; border: 1px solid #D7DCE5; }
.hp-scene__siteart { flex: none; width: 120px; height: 96px; color: var(--sc-accent); }
.hp-scene__sitebody { display: flex; flex-direction: column; gap: 12px; padding: 2px 18px 0; }
.hp-scene__strip { height: 15px; border-radius: 7.5px; background: var(--sc-tint); }
.hp-scene__cards { display: flex; gap: 10px; }
.hp-scene__cards i { flex: 1; height: 92px; border-radius: 6px; background: var(--sc-tint); }

/* Every colour-bearing part eases together when the palette swaps. */
.hp-scene.is-cycling .hp-scene__sitebar,
.hp-scene.is-cycling .hp-scene__sitebadge,
.hp-scene.is-cycling .hp-scene__sitebtns span:first-child,
.hp-scene.is-cycling .hp-scene__strip,
.hp-scene.is-cycling .hp-scene__cards i,
.hp-scene.is-cycling .hp-scene__siteart,
.hp-scene.is-cycling .hp-scene__swatch,
.hp-scene.is-cycling .hp-scene__hex {
  transition: background-color .6s var(--hp-ease), color .6s var(--hp-ease), border-color .6s var(--hp-ease);
}

/* ═══════════════════════════════════════════════════════════════════════════
   10. Demo banner
   ═══════════════════════════════════════════════════════════════════════════ */

/* Flat #211F1F ground, with img/rocket graphic.svg anchored bottom-right in
   place of the comp's nebula photograph. Flat ink means white text is a fixed
   15.5:1 everywhere in the band — there is no scrim to tune, which is the
   main reason a graphic beats the photo here. */
.hp-demo {
  position: relative;
  isolation: isolate;
  min-height: 441px;
  display: flex;
  align-items: center;
  color: #FFFFFF;
  background: var(--hp-ink);
  overflow: hidden;
}

/* The rocket artboard (845×437, inlined in index.html) is cut off on its left,
   right and bottom edges — only the top is a designed sweep — so it is treated
   as a bleed: flush to the band's bottom-right corner, with the left cut
   feathered into the ink so it doesn't read as a crop. Its own animations
   live in a <style> block inside that markup; §17 governs them.

   Unlike the drawing it replaces, this panel is opaque across its whole
   width, so there is no safe overlap with the copy at all. The width is
   whichever of three limits is smallest:
     845px             its natural size — never upscaled
     50vw + 32px       keeps a 40px gap once the wrap starts centring (>1264px)
     100vw - 600px     keeps that same gap while the wrap is gutter-pinned
   The middle two cross at exactly 1264px, so the gap holds at every width.
   Below 1100px the formula squeezes the panel under 500px, where the rocket
   stops being legible, so it is dropped and the band runs plain. */
.hp-demo__art {
  position: absolute;
  z-index: -1;
  right: 0;
  bottom: 0;
  width: min(845px, calc(50vw + 32px), calc(100vw - 600px));
  height: auto;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14%);
  mask-image: linear-gradient(90deg, transparent 0, #000 14%);
}
@media (max-width: 1100px) {
  .hp-demo__art { display: none; }
}

.hp-demo__inner { display: flex; flex-direction: column; align-items: flex-start; gap: 21px; padding-block: 64px; max-width: 520px; }
.hp-demo h2 { font-size: 48px; line-height: 1.08; }
.hp-demo p { max-width: 380px; }

/* ═══════════════════════════════════════════════════════════════════════════
   11. Steps
   ═══════════════════════════════════════════════════════════════════════════ */

.hp-steps { position: relative; margin-top: 78px; }

/* The rail down the middle, and its little cross-tick at the top. */
/* The rail runs past the bottom of the last step, out through the section's
   own padding and on through the horizontal rule below it — far enough that
   it clears the four-dot glyph sitting on that crossing (25px of it hangs
   below the rule) rather than stopping short in the whitespace above.
   No ancestor between here and <body> clips, so the overflow is safe. */
.hp-steps__rail {
  position: absolute;
  left: 50%;
  top: -34px;
  bottom: calc(-1 * (var(--hp-section-py) + 56px));
  width: 0;
  pointer-events: none;
}
/* Line on the pseudo-element, caps as children — the same split as .hp-vrule,
   for the same reason: the scaleY that draws it in would otherwise squash the
   caps and drag them down the page with it. Cap geometry is shared, in §7. */
.hp-steps__rail::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  border-left: 1px solid var(--hp-accent);
  transform-origin: center top;
}
.hp-steps__tick {
  position: absolute;
  left: 50%;
  top: -1px;
  width: 177px;
  margin-left: -88.5px;
  border-top: 1px solid var(--hp-accent);
  pointer-events: none;
}

/* Two equal tracks rather than two flex children, so the column boundary is
   fixed at the half and the rail drawn at left:50% lands on it whatever the
   copy inside happens to measure. min-width:0 stays: a grid item's automatic
   minimum is its content, so without it long words push the track wider than
   its 1fr and the halves stop being halves. */
.hp-step { display: grid; grid-template-columns: 1fr 1fr; align-items: center; padding-block: 34px; }
.hp-step:nth-child(even) > .hp-step__copy { order: 2; padding-inline: 60px; }
.hp-step:nth-child(even) > .hp-step__art { order: 1; }
.hp-step__copy { min-width: 0; display: flex; flex-direction: column; gap: 14px; padding-right: 92px; }
.hp-step__copy h3 { font-size: 34px; }
.hp-step__copy p { font-size: 18px; line-height: 1.4; color: var(--hp-ink-soft); max-width: 452px; }
.hp-step__art { min-width: 0; height: 208px; position: relative; }
.hp-step__ring { position: absolute; left: 204px; top: 12px; width: 184px; height: 184px; }
.hp-step__ring img { width: 100%; height: 100%; }
.hp-step__ring svg { position: absolute; inset: 0; width: 100%; height: 100%; }

/* ═══════════════════════════════════════════════════════════════════════════
   12. Migration band (dark)
   ═══════════════════════════════════════════════════════════════════════════ */

.hp-band--dark { background: var(--hp-ink); color: #FFFFFF; padding-block: 89px; }
.hp-band--dark .hp-wrap { max-width: calc(1050px + var(--hp-gutter) * 2); }

.hp-mig__top { display: flex; align-items: center; gap: 60px; }
.hp-mig__copy { width: 420px; flex: none; display: flex; flex-direction: column; align-items: flex-start; gap: 26px; }
.hp-mig__copy h2 { font-size: 48px; line-height: 1.08; }
/* Its own line at every width — at 420px the two clauses would otherwise
   run together on one. */
.hp-mig__kicker { display: block; font-size: 32px; font-weight: 500; line-height: 1.1; }
.hp-mig__copy p { font-size: 19px; line-height: 1.5; color: rgba(255, 255, 255, .72); }

.hp-mig__diagram { flex: 1; min-width: 0; display: flex; align-items: flex-start; gap: 16px; }
.hp-mig__stage { flex: none; width: 148px; display: flex; flex-direction: column; align-items: center; gap: 18px; }
.hp-mig__art { position: relative; width: 148px; height: 148px; }
.hp-mig__art svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.hp-mig__stage figcaption { font-size: 15px; line-height: 1.35; text-align: center; color: rgba(255, 255, 255, .8); }
.hp-mig__link { flex: none; width: 26px; height: 148px; display: flex; align-items: center; }
.hp-mig__link svg { width: 26px; height: 4px; }

/* The dotted ring around the factory glyph. Given its own element so it can
   turn without dragging the glyph with it. */
.hp-mig__spin { position: absolute; inset: 0; transform-origin: 50% 50%; }

.hp-mig__foot { margin-top: 76px; border-top: 1px solid rgba(255, 255, 255, .16); padding-top: 38px; }
.hp-mig__strip { display: flex; align-items: center; justify-content: center; gap: 67px; flex-wrap: wrap; }
.hp-mig__item { display: flex; align-items: center; gap: 18px; }
.hp-mig__chip {
  flex: none;
  width: 50px; height: 50px;
  border-radius: 25px;
  background: rgba(255, 255, 255, .07);
  display: flex; align-items: center; justify-content: center;
}
.hp-mig__item span { font-size: 17px; line-height: 1.3; color: rgba(255, 255, 255, .92); white-space: nowrap; }
.hp-mig__div { width: 1px; height: 54px; background: rgba(255, 255, 255, .14); }

/* ═══════════════════════════════════════════════════════════════════════════
   13. Final CTA
   ═══════════════════════════════════════════════════════════════════════════ */

.hp-final { padding-block: 110px 148px; }
.hp-final__inner { display: flex; flex-direction: column; align-items: center; gap: 40px; }
.hp-final__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px; }

/* ═══════════════════════════════════════════════════════════════════════════
   16. Responsive
   The comp is desktop-only at 1920. Everything below is derived: the two
   three-up grids collapse at 1000 then 700, the two side-by-side scenes stack,
   and the alternating step rail straightens into a single column.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 1360px) {
  .hp-brand__grid { padding-inline: 0; gap: 44px; }
  .hp-scene-wrap { --s: .86; }
}

@media (max-width: 1200px) {
  .hp-standards, .hp-ways, .hp-inside { gap: 32px 40px; }
  .hp-scene-wrap { --s: .74; }
  .hp-brand__copy { width: 340px; }
  .hp-mig__top { gap: 40px; }
  .hp-mig__copy { width: 360px; }
  .hp-step__copy { padding-right: 48px; }
  .hp-step:nth-child(even) > .hp-step__copy { padding-inline: 48px 0; }
  .hp-step__ring { left: auto; right: 12px; }
  .hp-step:nth-child(even) .hp-step__ring { left: 12px; right: auto; }
}

@media (max-width: 1000px) {
  .hp-hero__inner { gap: 32px; }

  .hp-standards, .hp-ways, .hp-inside { grid-template-columns: repeat(2, 1fr); }
  .hp-inside__card { min-height: 0; }

  /* Both two-column scenes stack; the drawings centre at full size. */
  .hp-brand__grid, .hp-mig__top { flex-direction: column; align-items: flex-start; }
  .hp-brand__copy, .hp-mig__copy { width: 100%; }
  .hp-scene-wrap { --s: .78; align-self: center; }
  .hp-mig__diagram { width: 100%; justify-content: flex-start; overflow-x: auto; padding-bottom: 8px; }
  .hp-mig__strip { gap: 28px; justify-content: flex-start; }
  .hp-mig__div { display: none; }

  /* Steps straighten: art above copy, one column, rail removed. */
  .hp-steps__rail, .hp-steps__tick { display: none; }
  .hp-step, .hp-step:nth-child(even) { grid-template-columns: 1fr; align-items: start; gap: 20px; }
  .hp-step > .hp-step__copy,
  .hp-step:nth-child(even) > .hp-step__copy { order: 2; padding-inline: 0; padding-right: 0; }
  .hp-step > .hp-step__art,
  .hp-step:nth-child(even) > .hp-step__art { order: 1; width: 100%; height: 184px; }
  .hp-step__ring, .hp-step:nth-child(even) .hp-step__ring { left: 0; right: auto; top: 0; }
}

@media (max-width: 760px) {
  .hp-head h2, .hp-brand__copy h2, .hp-demo h2, .hp-mig__copy h2 { font-size: clamp(30px, 7vw, 40px); }
  .hp-mig__kicker { font-size: clamp(22px, 5.4vw, 28px); }
  .hp-step__copy h3 { font-size: clamp(26px, 6vw, 30px); }

  .hp-standards, .hp-ways, .hp-inside { grid-template-columns: 1fr; gap: 28px; margin-top: 44px; }
  .hp-steps { margin-top: 44px; }

  .hp-hero__actions .hp-btn,
  .hp-final__actions .hp-btn { flex: 1 1 240px; }

  .hp-demo { min-height: 0; }
  .hp-demo__inner { padding-block: 56px; }
  .hp-demo p { max-width: none; }

  .hp-band--dark { padding-block: 64px; }
  .hp-mig__foot { margin-top: 48px; }
  .hp-mig__strip { gap: 18px 28px; }

  /* The three-stage diagram turns to run down the page rather than sideways
     — a horizontally scrolling decorative graphic is a poor trade on a
     phone. The connectors rotate with it. */
  .hp-mig__diagram { flex-direction: column; align-items: center; gap: 10px; overflow-x: visible; }
  .hp-mig__link { width: 148px; height: 30px; justify-content: center; }
  .hp-mig__link svg { transform: rotate(90deg); }

  .hp-scene-wrap { --s: .62; }
}

@media (max-width: 420px) {
  /* .44 keeps the 684px scene inside a 320px viewport (301px) with room for
     the 16px gutters. */
  .hp-scene-wrap { --s: .44; }
  .hp-mig__strip { flex-direction: column; align-items: flex-start; gap: 16px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   17. Motion
   Every animated thing on this page is finite. Nothing loops forever: the
   hero word settles after three passes, the palette cycle settles on its
   first state, the ring stops after two turns. Sustained auto-motion past
   five seconds owes the user a pause control under WCAG 2.2.2, and on a page
   selling an accessible design system that is the last flag worth carrying.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Scroll reveal. Elements start displaced and are released by an
   IntersectionObserver adding .is-in. If JS never runs, the no-js fallback in
   §18 means they simply render — content is never hidden behind script. */
.js .hp-reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .6s var(--hp-ease), transform .6s var(--hp-ease);
  transition-delay: var(--hp-delay, 0ms);
}
.js .hp-reveal.is-in { opacity: 1; transform: none; }

/* The hero arrives on load rather than on scroll — it is already on screen, so
   there is nothing for an observer to wait for. Headline, lead and buttons
   come up in that order.

   An animation rather than a .hp-reveal transition, and no JS: the .js class
   is set by an inline script in <head> (index.html), so this is in force at
   first paint and the three never flash in place before being displaced.
   `backwards` is what holds each one at the from-state through its delay. */
.js .hp-hero__inner > * { animation: hp-rise-in .7s var(--hp-ease) backwards; }
.js .hp-hero__inner > *:nth-child(1) { animation-delay: .05s; }
.js .hp-hero__inner > *:nth-child(2) { animation-delay: .18s; }
.js .hp-hero__inner > *:nth-child(3) { animation-delay: .31s; }
@keyframes hp-rise-in {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

/* The rules draw themselves in — horizontals from the left, verticals from
   the top — instead of simply appearing. */
.js .hp-rule::before { transform: scaleX(0); transition: transform 1.1s var(--hp-ease); }
.js .hp-rule.is-in::before { transform: scaleX(1); }
.js .hp-vrule::before, .js .hp-steps__rail::before { transform: scaleY(0); transition: transform 1.1s var(--hp-ease) .15s; }
.js .hp-vrule.is-in::before, .js .hp-steps__rail.is-in::before { transform: scaleY(1); }

/* The caps arrive with the end of the line they terminate: the top one as it
   sets off, the bottom one as it lands. */
.js .hp-vrule i, .js .hp-steps__rail i { opacity: 0; transition: opacity .35s var(--hp-ease); }
.js .hp-vrule.is-in i, .js .hp-steps__rail.is-in i { opacity: 1; }
.js .hp-vrule.is-in i:first-child, .js .hp-steps__rail.is-in i:first-child { transition-delay: .15s; }
.js .hp-vrule.is-in i:last-child, .js .hp-steps__rail.is-in i:last-child { transition-delay: 1.05s; }
.js .hp-steps__tick { transform: scaleX(0); transition: transform .6s var(--hp-ease); }
.js .hp-steps__tick.is-in { transform: scaleX(1); }

/* The four dots breathe individually and out of phase, offset clockwise from
   the top-left, so the glyph shimmers around itself instead of pulsing as one
   block. Each also runs at its own duration, and the four don't divide into
   each other — the phase between them keeps drifting instead of resyncing
   every few seconds into a visible pattern. The wrapper holds the resting .25
   and each dot swings its own opacity between 1 and .28 against it — the same
   effective .25 → .07 range as before, which keeps the no-JS and
   reduced-motion resting state untouched.

   This one runs indefinitely, unlike everything else on the page. See the
   caveat in §17's reduced-motion block. */
.js .hp-glyph.is-in circle { animation: hp-breathe 6.4s var(--hp-ease) infinite; }
.js .hp-glyph.is-in circle:nth-child(2) { animation-duration: 7.7s; animation-delay: .7s; }
.js .hp-glyph.is-in circle:nth-child(4) { animation-duration: 9.1s; animation-delay: 1.4s; }
.js .hp-glyph.is-in circle:nth-child(3) { animation-duration: 8.3s; animation-delay: 2.1s; }
@keyframes hp-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: .28; }
}

/* Two slow turns, landing back where it started. */
.js .hp-mig__spin.is-in { animation: hp-spin 26s linear 2; }
@keyframes hp-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* Nothing moves. Reveal targets render in place, the rules are simply
     drawn, the glyph and ring hold still, and the hero word stays on state 1
     (js/homepage.js checks the same query and never starts the cycle). */
  .js .hp-reveal,
  .js .hp-reveal.is-in { opacity: 1; transform: none; transition: none; }

  .js .hp-hero__inner > * { animation: none; }
  .js .hp-rule::before, .js .hp-rule.is-in::before,
  .js .hp-steps__tick, .js .hp-steps__tick.is-in { transform: scaleX(1); transition: none; }
  .js .hp-vrule::before, .js .hp-vrule.is-in::before,
  .js .hp-steps__rail::before, .js .hp-steps__rail.is-in::before { transform: scaleY(1); transition: none; }
  .js .hp-vrule i, .js .hp-vrule.is-in i,
  .js .hp-steps__rail i, .js .hp-steps__rail.is-in i { opacity: 1; transition: none; }
  /* The glyph's breathe is the one animation here that never ends, so this
     branch is the only thing stopping it. WCAG 2.2.2 wants a pause mechanism
     for motion that auto-starts and runs past five seconds; an OS-level
     motion preference is not formally that mechanism, so if the glyph is ever
     judged to carry information rather than decoration, it needs a real
     control or a finite iteration count again. */
  .js .hp-glyph.is-in circle, .js .hp-mig__spin.is-in { animation: none; }

  /* The demo band's rocket is the SECOND animation on this page that never
     ends (the glyph above is the first). It was inlined into index.html
     precisely so this branch can reach it — as an <img> it could not be
     stopped at all, because Chromium does not propagate the motion preference
     into an SVG-as-image document. Verified, not assumed.

     `paused`, not `animation: none`: #Rectangle_5 takes its position from its
     own 0% keyframe, so removing the animation moves it by roughly 59×297px.
     Pausing keeps the animation in effect at time zero, which is exactly the
     artwork's intended resting state.

     The same WCAG 2.2.2 caveat noted for the glyph applies here and applies
     harder — two indefinite loops now auto-start above the fold's end, and an
     OS motion preference is not the pause mechanism the criterion asks for.
     This was a deliberate call, not an oversight; a real control is the fix if
     it is ever judged to need one. */
  .hp-demo__art #Line_11,
  .hp-demo__art #Rocket_Ship,
  .hp-demo__art #Rectangle_5 { animation-play-state: paused; }

  .hp-cycle__state { transition: none; }
  .hp-btn, .hp-arrowlink svg, .hp-inside__card, .hp-modal, .hp-modal__panel { transition: none; }
  .hp-btn:hover, a.hp-inside__card:hover { transform: none; }
  .hp-scene.is-cycling * { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   18. No-JS
   The page is entirely readable and navigable without script. The only things
   lost are the hero cycle, the reveals and the install dialog — the last of
   which degrades to a link to the repository.
   ═══════════════════════════════════════════════════════════════════════════ */

html:not(.js) .hp-nav { display: flex !important; }
html:not(.js) .hp-navtoggle { display: none !important; }
