/* ============================================================
   eSeGeCe Home Rotation. The "hp-" design kit.
   ------------------------------------------------------------
   Loaded ONLY by the five rotating front pages (home/v1..v5),
   after css/style.css, so every rule here is additive. Nothing
   in this file may override a global component outside of an
   explicit hp- context.

   Layering model
     1. css/style.css  ... brand tokens, buttons, cards, sections
     2. css/home.css   ... this file, the "hp-" stage kit
     3. js/home.js     ... adds .hp-js to <html> and drives motion

   No-JS contract: without .hp-js on <html> every piece of content
   is fully visible and readable. Motion states are gated behind
   .hp-js, never the other way round.

   Motion budget: transform and opacity only. No animated width,
   height, top, left, box-shadow or background-position anywhere
   in a keyframe, so every animation stays on the compositor.
   ============================================================ */


/* ============================================================
   01. TOKENS
   The dark stage palette. Deep navy base, cool ink for text,
   one warm accent (inherited from the brand) and one sky blue
   that matches the syntax colours already used in .code-block.
   ============================================================ */
:root {
  /* Stage */
  --hp-navy-0:      #0A1628;   /* base of the dark stage gradient */
  --hp-navy-1:      #0F2847;   /* top of the dark stage gradient */
  --hp-navy-2:      #0C1D35;   /* card / window bodies on the stage */
  --hp-code-bg:     #0B1B2F;   /* code surfaces on the stage */

  /* Ink on the dark stage. Contrast on #0A1628:
     ink 14:1, ink-2 7.4:1, ink-3 4.6:1, so body copy is AA at worst. */
  --hp-ink:         #EAF1FA;
  --hp-ink-2:       #A6BCD6;
  --hp-ink-3:       #7F97B4;

  /* Hairlines and glass */
  --hp-line:        rgba(255,255,255,.10);
  --hp-line-2:      rgba(255,255,255,.18);
  --hp-glass:       rgba(255,255,255,.06);
  --hp-glass-2:     rgba(255,255,255,.10);

  /* Highlights */
  --hp-sky:         #7DD3FC;   /* same sky as .code-block .code-keyword */
  --hp-mint:        #86EFAC;   /* same mint as .code-block .code-string */
  --hp-glow:        rgba(0,87,184,.55);
  --hp-glow-warm:   rgba(240,164,0,.22);

  /* Shape and motion */
  --hp-radius:      16px;
  --hp-radius-sm:   10px;
  --hp-ease:        cubic-bezier(.16,1,.3,1);
  --hp-dur:         .7s;
  --hp-shadow:      0 24px 60px rgba(4,12,26,.45);
  --hp-shadow-soft: 0 12px 30px rgba(10,22,40,.10);

  /* Procedural noise, inline SVG so there is no external asset. The
     tile is emitted at 6 % and feTurbulence's own alpha averages about
     one half, so the grain lands near the 3 % the design calls for and
     the layer itself can stay at full opacity for the vignette. */
  --hp-noise-img: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.06'/%3E%3C/svg%3E");
  --hp-vignette: radial-gradient(125% 92% at 50% 36%, rgba(4,10,20,0) 52%, rgba(4,10,20,.5) 100%);
}


/* ============================================================
   02. MOTION PRIMITIVES
   Keyframes live here so every component can share them. All of
   them touch transform and opacity only.
   ============================================================ */

/* Aurora blobs. Long, irregular periods so the two blobs never
   fall into a visible rhythm. */
@keyframes hp-drift-a {
  0%   { transform: translate3d(-6%, -4%, 0) scale(1);    opacity: .85; }
  33%  { transform: translate3d(8%, 6%, 0)   scale(1.14); opacity: 1;   }
  66%  { transform: translate3d(-3%, 10%, 0) scale(.95);  opacity: .75; }
  100% { transform: translate3d(-6%, -4%, 0) scale(1);    opacity: .85; }
}
@keyframes hp-drift-b {
  0%   { transform: translate3d(5%, 8%, 0)   scale(1.05); opacity: .7;  }
  40%  { transform: translate3d(-8%, -6%, 0) scale(.92);  opacity: .95; }
  75%  { transform: translate3d(4%, -2%, 0)  scale(1.12); opacity: .6;  }
  100% { transform: translate3d(5%, 8%, 0)   scale(1.05); opacity: .7;  }
}

/* Caret blink for the terminal and the streaming chat bubble. */
@keyframes hp-blink {
  0%, 45%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* Live dot in the eyebrow. A scaling ring, not a growing shadow. */
@keyframes hp-ping {
  0%   { transform: scale(1);   opacity: .55; }
  70%  { transform: scale(2.6); opacity: 0;   }
  100% { transform: scale(2.6); opacity: 0;   }
}

/* Marquee. The track holds two identical sets, so one full set is
   exactly 50 % of the track width. */
@keyframes hp-marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* Slow float for hero visuals that opt in with .hp-float. */
@keyframes hp-float {
  0%   { transform: translate3d(0, 0, 0); }
  50%  { transform: translate3d(0, -10px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* Scroll reveal.
   Base state is fully visible, the hidden state only exists under
   .hp-js, so a page with JavaScript disabled reads normally. */
.hp-js .hp-reveal {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition:
    opacity .6s var(--hp-ease),
    transform .75s var(--hp-ease);
  will-change: opacity, transform;
}
.hp-js .hp-reveal.is-in {
  opacity: 1;
  transform: none;
}
/* Once the reveal has played, drop the compositor hint. */
.hp-js .hp-reveal.is-done { will-change: auto; }

/* Optional float, opt-in per element. */
.hp-js .hp-float { animation: hp-float 7s ease-in-out infinite; }


/* ============================================================
   03. DARK STAGE
   .hp-dark is the deep-navy stage used by heroes and feature
   bands. It owns three decorative layers, all aria-hidden:
     .hp-aurora     drifting mesh blobs
     .hp-gridlines  technical grid
     .hp-noise      film grain
   The grid and the grain are also emitted automatically as
   pseudo-elements, and suppressed with :has() when the page
   supplies the real element, so a hero never doubles them.
   ============================================================ */
.hp-dark {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background:
    radial-gradient(1200px 620px at 78% -10%, #16375F 0%, rgba(22,55,95,0) 62%),
    linear-gradient(168deg, var(--hp-navy-1) 0%, var(--hp-navy-0) 58%, #081222 100%);
  color: var(--hp-ink-2);
}

/* Content sits above every decorative layer. */
.hp-dark > *:not(.hp-aurora):not(.hp-gridlines):not(.hp-noise) {
  position: relative;
  z-index: 3;
}

/* ---------- Aurora ---------- */
.hp-aurora {
  position: absolute;
  inset: -10% -10% -10% -10%;
  z-index: 0;
  pointer-events: none;
  /* Third, static blob. Two drifting blobs plus one anchored glow
     reads as depth without a third animated layer. */
  background:
    radial-gradient(38% 42% at 68% 22%, rgba(56,189,248,.14) 0%, rgba(56,189,248,0) 70%);
}
.hp-aurora::before,
.hp-aurora::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  will-change: transform, opacity;
}
/* Cool blue mass, bottom left of the copy column. */
.hp-aurora::before {
  top: -18%;
  left: -12%;
  width: 62vw;
  height: 62vw;
  max-width: 900px;
  max-height: 900px;
  background: radial-gradient(circle at 50% 50%, var(--hp-glow) 0%, rgba(0,87,184,0) 66%);
  animation: hp-drift-a 26s ease-in-out infinite;
}
/* Warm accent mass, top right, deliberately small and low opacity
   so the stage stays engineered rather than neon. */
.hp-aurora::after {
  top: -26%;
  right: -14%;
  width: 48vw;
  height: 48vw;
  max-width: 720px;
  max-height: 720px;
  background: radial-gradient(circle at 50% 50%, var(--hp-glow-warm) 0%, rgba(240,164,0,0) 62%);
  animation: hp-drift-b 31s ease-in-out infinite;
}

/* ---------- Technical grid ----------
   The element form and the generated form carry the same paint. The
   generated one is suppressed with :has() when the page supplies a
   real .hp-gridlines element, so a stage never draws the grid twice.
   ------------------------------------------------------------ */
.hp-gridlines {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(255,255,255,.055) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255,255,255,.055) 1px, transparent 1px);
  background-size: 56px 56px;
  /* Fade the grid out towards the edges so it never fights the copy. */
  -webkit-mask-image: radial-gradient(120% 90% at 50% 12%, #000 0%, rgba(0,0,0,.35) 55%, transparent 88%);
          mask-image: radial-gradient(120% 90% at 50% 12%, #000 0%, rgba(0,0,0,.35) 55%, transparent 88%);
}
.hp-dark:not(:has(> .hp-gridlines))::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(255,255,255,.055) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255,255,255,.055) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(120% 90% at 50% 12%, #000 0%, rgba(0,0,0,.35) 55%, transparent 88%);
          mask-image: radial-gradient(120% 90% at 50% 12%, #000 0%, rgba(0,0,0,.35) 55%, transparent 88%);
}

/* ---------- Film grain plus vignette ----------
   Same suppression trick. The grain tile is a 3 % inline SVG, the
   vignette rides along as an inset shadow on the very same layer so
   the stage never needs a fourth box.
   ------------------------------------------------------------ */
.hp-noise {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: var(--hp-noise-img), var(--hp-vignette);
  background-repeat: repeat, no-repeat;
  background-size: 160px 160px, cover;
}
.hp-dark:not(:has(> .hp-noise))::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: var(--hp-noise-img), var(--hp-vignette);
  background-repeat: repeat, no-repeat;
  background-size: 160px 160px, cover;
}

/* ---------- Colour remap for nested global components ----------
   style.css hard-codes var(--text) on headings and var(--text-light)
   on secondary copy. Everything that can legally appear inside a
   dark band is remapped here so the page never renders slate-on-navy.
   ------------------------------------------------------------ */
.hp-dark h1,
.hp-dark h2,
.hp-dark h3,
.hp-dark h4,
.hp-dark h5,
.hp-dark h6,
.hp-dark .section-title,
.hp-dark .card-title,
.hp-dark strong { color: var(--hp-ink); }

.hp-dark p,
.hp-dark li,
.hp-dark .section-description,
.hp-dark .section-subtitle,
.hp-dark .card p,
.hp-dark .card-description { color: var(--hp-ink-2); }

.hp-dark .section-label { color: var(--accent); }

.hp-dark a { color: var(--hp-sky); }
.hp-dark a:hover { color: #fff; }

/* Buttons. The primary keeps the brand blue, it simply gains a glow
   so it separates from the navy. The outline flips to white. */
.hp-dark .btn-primary {
  box-shadow: 0 10px 30px rgba(0,87,184,.42);
}
.hp-dark .btn-primary:hover {
  background: #0A66D6;
  border-color: #0A66D6;
  box-shadow: 0 14px 36px rgba(10,102,214,.5);
}
.hp-dark .btn-outline {
  color: #fff;
  border-color: rgba(255,255,255,.34);
  background: transparent;
}
.hp-dark .btn-outline:hover {
  background: rgba(255,255,255,.12);
  border-color: #fff;
  color: #fff;
  box-shadow: none;
}
.hp-dark .card-link,
.hp-dark .card .card-link { color: var(--hp-sky); }

/* Generic cards dropped on the stage become glass. */
.hp-dark .card {
  background: var(--hp-glass);
  border-color: var(--hp-line);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.hp-dark .card:hover {
  border-color: rgba(125,211,252,.45);
  background: var(--hp-glass-2);
  box-shadow: 0 18px 40px rgba(3,10,22,.5);
}
.hp-dark .card-icon {
  background: rgba(125,211,252,.12);
  color: var(--hp-sky);
}

/* Testimonials on the stage. */
.hp-dark .testimonial-card {
  background: var(--hp-glass);
  border-color: var(--hp-line);
}
.hp-dark .testimonial-card .quote { color: var(--hp-ink); }
.hp-dark .testimonial-card .author { color: var(--hp-ink); }
.hp-dark .testimonial-card .role { color: var(--hp-ink-3); }

/* Protocol badges and logo items, if a variant reuses them here. */
.hp-dark .protocol-badge,
.hp-dark .logo-item,
.hp-dark .icon-item {
  border-color: var(--hp-line);
  background: rgba(255,255,255,.03);
  color: var(--hp-ink-2);
}
.hp-dark .protocol-badge:hover,
.hp-dark .logo-item:hover,
.hp-dark .icon-item:hover {
  border-color: rgba(125,211,252,.45);
  background: rgba(125,211,252,.08);
  color: var(--hp-ink);
}
.hp-dark .protocol-badge strong { color: var(--hp-ink); }
.hp-dark .protocol-badge span { color: var(--hp-ink-3); }

/* Any decorative SVG dropped straight on the stage. */
.hp-dark svg { max-width: 100%; }


/* ============================================================
   04. HERO SHELL
   <section class="hp-hero hp-dark">
     <div class="hp-aurora" aria-hidden="true"></div>
     <div class="container hp-hero-grid"> copy | visual </div>
   </section>
   ============================================================ */
/* The stage carries a language banner and a cookie bar on top of the header, so
   the hero gets roughly 900 px of screen and has to seat the eyebrow, the H1,
   the lead, both buttons and the proof row inside it. The padding, the headline
   cap and the lead measure below are all tuned against that budget. */
.hp-hero {
  padding: 72px 0 64px;
}
.hp-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: 56px;
  align-items: center;
}
/* Grid items default to min-width:auto, so a wide child (a code panel, a long
   mono string) can push a column past the viewport instead of shrinking. */
.hp-hero-grid > * { min-width: 0; }
/* Both columns hold their own stacking context, so a decorative layer
   can never paint over the copy even if a page nests it one level too
   deep, inside the container rather than beside it. */
.hp-hero-copy {
  position: relative;
  z-index: 1;
  max-width: 640px;
}

/* ---------- Eyebrow ---------- */
.hp-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 7px 16px 7px 12px;
  margin-bottom: 16px;
  border: 1px solid var(--hp-line-2);
  border-radius: 999px;
  background: var(--hp-glass);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--hp-ink);
  white-space: nowrap;
}
.hp-eyebrow .hp-dot,
.hp-dot {
  position: relative;
  display: inline-block;
  width: 8px;
  height: 8px;
  flex: 0 0 8px;
  border-radius: 50%;
  background: var(--accent);
}
.hp-dot::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--accent);
  transform-origin: 50% 50%;
}
.hp-js .hp-dot::after { animation: hp-ping 2.4s var(--hp-ease) infinite; }

/* ---------- Headline ---------- */
/* The cap comes down from 4rem so the long headlines settle on three lines
   instead of four, which is what pulls the CTA row back above the fold. */
.hp-h1 {
  font-size: clamp(2.25rem, 4.1vw, 3.4rem);
  line-height: 1.1;
  letter-spacing: -.03em;
  font-weight: 800;
  color: var(--hp-ink);
  margin-bottom: 1rem;
  text-wrap: balance;
}
/* Gradient words. The solid colour is declared first, so a browser
   without background-clip:text still shows readable accent text.

   Every stop is a light, luminous tone. Running an amber straight into a mid
   blue crosses a desaturated grey halfway, and on this navy that grey is what
   made the tail of each headline sink into the background. Routing the blend
   through a pale gold keeps the whole run bright: measured on --hp-navy-0 the
   stops land at 10.6:1, 12.4:1, 11.0:1 and 9.8:1, so the dimmest part of the
   word is still far clear of AA. */
.hp-grad {
  color: var(--accent);
  background-image: linear-gradient(96deg,
    #FFB84D 0%,
    #FFD98F 30%,
    #B6DCFF 66%,
    #8FC3FF 100%);
  -webkit-background-clip: text;
          background-clip: text;
}
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .hp-grad {
    -webkit-text-fill-color: transparent;
    color: transparent;
  }
}

/* A tighter measure and a tighter leading. The lead is a deck, not body copy,
   and every line it saves is a line the CTA row climbs. */
.hp-lead {
  font-size: 1.08rem;
  line-height: 1.62;
  color: var(--hp-ink-2);
  max-width: 520px;
  margin-bottom: 1.5rem;
}

/* ---------- CTA row ---------- */
.hp-cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 1.5rem;
}

/* Glassy button for the dark stage. */
.hp-btn-ghost {
  background: var(--hp-glass);
  color: #fff;
  border-color: var(--hp-line-2);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.hp-btn-ghost:hover {
  background: rgba(255,255,255,.14);
  border-color: rgba(255,255,255,.45);
  color: #fff;
  transform: translateY(-1px);
}
/* Same button on a light band. */
section:not(.hp-dark) .hp-btn-ghost {
  background: #fff;
  color: var(--text);
  border-color: var(--border);
}
section:not(.hp-dark) .hp-btn-ghost:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: #fff;
}

/* ---------- Proof chips ---------- */
.hp-proof {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 22px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.hp-proof li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: .875rem;
  font-weight: 500;
  color: var(--hp-ink-2);
}
.hp-proof li svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--hp-mint);
}
/* Items without an icon get a small accent tick of their own. */
.hp-proof li:not(:has(svg))::before {
  content: '';
  width: 6px;
  height: 6px;
  flex: 0 0 6px;
  border-radius: 50%;
  background: var(--accent);
}

/* ---------- Visual column ----------
   The stage is .hp-dark, which clips its overflow, and .hp-hero-grid centres
   its items. A visual column taller than the stage therefore bleeds equally
   above and below, and the top bleed is the one that hurts: the first lines of
   a terminal vanish off the top edge of the hero.

   The column is therefore a flex column that is allowed to give up height. Its
   children may shrink (min-height:0 beats the flex default of auto) and any
   scrollable body inside gives up its height first, so a visual that is too big
   scrolls its own body instead of pushing its head out of the stage.

   The cap is a backstop, not a layout constraint. It is set above the tallest
   visual any variant currently builds, measured at every common laptop height,
   so it never squeezes a hero that already fits. It only catches a runaway.
   ------------------------------------------------------------ */
.hp-hero-visual {
  position: relative;
  z-index: 1;
  min-width: 0;
  align-self: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-height: min(88vh, 760px);
}
.hp-hero-visual > * {
  min-width: 0;
  min-height: 0;          /* without this a flex child refuses to shrink */
  max-width: 100%;
}
.hp-hero-visual > svg,
.hp-hero-visual img {
  display: block;
  width: 100%;
  height: auto;
}
/* A terminal or window in the hero shrinks by scrolling its body, never by
   pushing its head off the top of the stage. */
.hp-hero-visual .hp-terminal,
.hp-hero-visual .hp-window,
.hp-hero-visual .hp-codetabs {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.hp-hero-visual .hp-terminal-body,
.hp-hero-visual .hp-window-body {
  min-height: 0;
  overflow-y: auto;
}


/* ============================================================
   05. SPOTLIGHT RIBBON
   The featured-product band that sits near the top of every
   variant. Accent rule on the left, product mark, one-line
   pitch, two links.
   ============================================================ */
.hp-spotlight {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px 28px;
  padding: 20px 26px;
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: var(--hp-radius-sm);
  background: linear-gradient(100deg, #FFF9EC 0%, var(--bg) 46%);
  box-shadow: var(--hp-shadow-soft);
}
.hp-dark .hp-spotlight {
  border-color: var(--hp-line);
  border-left-color: var(--accent);
  background: linear-gradient(100deg, rgba(240,164,0,.10) 0%, rgba(255,255,255,.04) 46%);
  box-shadow: none;
}

/* Product mark. A square initial, or whatever the variant drops in. */
.hp-spotlight-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  flex: 0 0 48px;
  border-radius: 12px;
  background: var(--primary-lt);
  color: var(--primary);
  font-weight: 800;
  font-size: 1.05rem;
}
.hp-dark .hp-spotlight-mark {
  background: rgba(125,211,252,.14);
  color: var(--hp-sky);
}
.hp-spotlight-mark svg { width: 26px; height: 26px; }

.hp-spotlight-body { flex: 1 1 320px; min-width: 0; }
.hp-spotlight-title,
.hp-spotlight strong {
  display: block;
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--text);
  line-height: 1.4;
}
.hp-dark .hp-spotlight-title,
.hp-dark .hp-spotlight strong { color: var(--hp-ink); }
.hp-spotlight-text,
.hp-spotlight p {
  margin: 2px 0 0;
  font-size: .93rem;
  color: var(--text-light);
  line-height: 1.6;
}
.hp-dark .hp-spotlight-text,
.hp-dark .hp-spotlight p { color: var(--hp-ink-2); }

.hp-spotlight-links,
.hp-spotlight-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 20px;
  margin-left: auto;
}
.hp-spotlight-links a,
.hp-spotlight-actions a {
  font-size: .9rem;
  font-weight: 600;
  white-space: nowrap;
}


/* ============================================================
   06. BENTO GRID
   12 columns on desktop, 6 on tablets, 1 on phones.
   .hp-bento-card spans 4, .is-wide spans 8, .is-tall doubles the
   row span so a card can run the full height of a pair.
   ============================================================ */
.hp-bento {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: minmax(148px, auto);
  gap: 20px;
}
.hp-bento-card {
  position: relative;
  grid-column: span 4;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 26px 24px;
  border: 1px solid var(--border);
  border-radius: var(--hp-radius);
  background: var(--bg-card);
  box-shadow: var(--hp-shadow-soft);
  overflow: hidden;
  transition:
    transform .45s var(--hp-ease),
    box-shadow .45s var(--hp-ease),
    border-color .45s var(--hp-ease);
}
.hp-bento-card.is-wide { grid-column: span 8; }
.hp-bento-card.is-tall { grid-row: span 2; }

/* Hover tint. A pseudo-element, so nothing repaints the card body. */
.hp-bento-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, rgba(0,87,184,.05), rgba(240,164,0,.04));
  opacity: 0;
  transition: opacity .45s var(--hp-ease);
  pointer-events: none;
}
.hp-bento-card:hover {
  transform: translateY(-4px);
  border-color: rgba(0,87,184,.3);
  box-shadow: 0 18px 40px rgba(0,87,184,.12);
}
.hp-bento-card:hover::before { opacity: 1; }
.hp-bento-card > * { position: relative; z-index: 1; }

.hp-bento-card h3 { font-size: 1.15rem; margin: 0; }
.hp-bento-card p {
  margin: 0;
  font-size: .93rem;
  line-height: 1.6;
  color: var(--text-light);
  flex: 1;
}
.hp-bento-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--primary-lt);
  color: var(--primary);
}
.hp-bento-icon svg { width: 22px; height: 22px; }

/* Bento on the dark stage. */
.hp-dark .hp-bento-card {
  background: var(--hp-glass);
  border-color: var(--hp-line);
  box-shadow: none;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.hp-dark .hp-bento-card:hover {
  border-color: rgba(125,211,252,.4);
  box-shadow: 0 18px 40px rgba(3,10,22,.5);
}
.hp-dark .hp-bento-card p { color: var(--hp-ink-2); }
.hp-dark .hp-bento-icon {
  background: rgba(125,211,252,.12);
  color: var(--hp-sky);
}


/* ============================================================
   07. GLASS SURFACE
   Frosted panel for the dark stage. Solid rgba fallback first,
   the blur is a progressive enhancement.
   ============================================================ */
.hp-glass {
  position: relative;
  padding: 24px;
  border: 1px solid var(--hp-line);
  border-radius: var(--hp-radius);
  background: rgba(255,255,255,.06);
  color: var(--hp-ink-2);
  box-shadow: 0 20px 50px rgba(3,10,22,.35);
}
@supports (backdrop-filter: blur(10px)) or (-webkit-backdrop-filter: blur(10px)) {
  .hp-glass {
    background: rgba(255,255,255,.055);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }
}
.hp-glass h2,
.hp-glass h3,
.hp-glass h4,
.hp-glass strong { color: var(--hp-ink); }
/* A hairline highlight along the top edge sells the glass. */
.hp-glass::before {
  content: '';
  position: absolute;
  top: 0;
  left: 12%;
  right: 12%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.35), transparent);
  pointer-events: none;
}


/* ============================================================
   08. STATS
   .hp-stat > .hp-stat-num + .hp-stat-label + .hp-stat-detail
   The number keeps tabular figures so a counting animation never
   makes the row jitter.
   ============================================================ */
.hp-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 22px;
}
.hp-stat {
  padding: 24px 22px;
  border: 1px solid var(--border);
  border-radius: var(--hp-radius);
  background: var(--bg-card);
}
.hp-dark .hp-stat {
  background: var(--hp-glass);
  border-color: var(--hp-line);
}
.hp-stat-num {
  display: block;
  font-size: clamp(2rem, 3.4vw, 2.9rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -.025em;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
.hp-dark .hp-stat-num { color: var(--hp-sky); }
.hp-stat-label {
  display: block;
  margin-top: 6px;
  font-size: .95rem;
  font-weight: 600;
  color: var(--text);
}
.hp-dark .hp-stat-label { color: var(--hp-ink); }
.hp-stat-detail {
  display: block;
  margin-top: 4px;
  font-size: .85rem;
  line-height: 1.55;
  color: var(--text-light);
}
.hp-dark .hp-stat-detail { color: var(--hp-ink-3); }

/* Counters keep tabular figures wherever they are used. */
[data-count-to] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}


/* ============================================================
   09. PILLS
   Small protocol / technology chips. Legible on both stages.
   ============================================================ */
.hp-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-alt);
  font-size: .8rem;
  font-weight: 600;
  line-height: 1.5;
  color: var(--text);
  white-space: nowrap;
  transition:
    border-color var(--transition),
    background var(--transition),
    color var(--transition);
}
a.hp-pill:hover {
  border-color: var(--primary);
  background: var(--primary-lt);
  color: var(--primary-dk);
}
.hp-pill.is-accent {
  border-color: rgba(240,164,0,.45);
  background: #FFF6E3;
  color: #8A5D00;
}
.hp-pill svg { width: 15px; height: 15px; }

.hp-dark .hp-pill {
  border-color: var(--hp-line);
  background: rgba(255,255,255,.05);
  color: var(--hp-ink-2);
}
.hp-dark a.hp-pill:hover {
  border-color: rgba(125,211,252,.5);
  background: rgba(125,211,252,.1);
  color: var(--hp-ink);
}
.hp-dark .hp-pill.is-accent {
  border-color: rgba(240,164,0,.45);
  background: rgba(240,164,0,.12);
  color: #FFCE6B;
}


/* ============================================================
   10. WINDOW CHROME
   .hp-window > .hp-window-bar (three dots + .hp-window-title)
              > .hp-window-body
   The three dots are drawn from one pseudo-element with two extra
   box-shadows, and they step aside if the page supplies its own
   dot elements.
   ============================================================ */
.hp-window {
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  background: #fff;
  box-shadow: var(--shadow-lg);
}
.hp-dark .hp-window {
  border-color: var(--hp-line);
  background: var(--hp-navy-2);
  box-shadow: var(--hp-shadow);
}

.hp-window-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 16px;
  background: #16233A;
  border-bottom: 1px solid rgba(255,255,255,.07);
}
.hp-window-bar::before {
  content: '';
  width: 10px;
  height: 10px;
  flex: 0 0 10px;
  border-radius: 50%;
  background: #FF5F57;
  box-shadow: 17px 0 0 #FEBC2E, 34px 0 0 #28C840;
  margin-right: 34px;
}
/* If the markup already carries dots, drop the generated ones. */
.hp-window-bar:has(.hp-window-dot, .hp-window-dots)::before { content: none; }
.hp-window-dot {
  width: 10px;
  height: 10px;
  flex: 0 0 10px;
  border-radius: 50%;
  background: rgba(255,255,255,.2);
}
.hp-window-title {
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .02em;
  color: #8CA3BF;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hp-window-title svg { width: 14px; height: 14px; vertical-align: -2px; }

.hp-window-body {
  padding: 22px;
  background: #fff;
  color: var(--text);
}
.hp-dark .hp-window-body {
  background: var(--hp-navy-2);
  color: var(--hp-ink-2);
}
/* A <pre> owns its own surface, so the body must not pad it away. */
.hp-window-body > pre:only-child { margin: 0; }
.hp-window-body.is-flush { padding: 0; }


/* ============================================================
   11. TERMINAL AND TYPEWRITER
   .hp-terminal[data-typewriter]
     > .hp-terminal-bar
     > .hp-terminal-body > .hp-line[data-delay]
   Without JS the lines simply read as static text. Under .hp-js
   the JS empties them and types them back in, so every line keeps
   a reserved height and the card never reflows while typing.
   ============================================================ */
.hp-terminal {
  border: 1px solid var(--hp-line);
  border-radius: 14px;
  overflow: hidden;
  background: var(--hp-code-bg);
  box-shadow: var(--hp-shadow);
}
.hp-terminal-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 16px;
  background: #0E2138;
  border-bottom: 1px solid rgba(255,255,255,.07);
  font-family: var(--font-mono);
  font-size: .78rem;
  color: #8CA3BF;
}
.hp-terminal-bar::before {
  content: '';
  width: 10px;
  height: 10px;
  flex: 0 0 10px;
  border-radius: 50%;
  background: #FF5F57;
  box-shadow: 17px 0 0 #FEBC2E, 34px 0 0 #28C840;
  margin-right: 34px;
}
.hp-terminal-bar:has(.hp-window-dot, .hp-window-dots)::before { content: none; }

.hp-terminal-body {
  padding: 20px 22px 24px;
  font-family: var(--font-mono);
  font-size: .84rem;
  line-height: 1.62;
  color: #CBD9EA;
  overflow-x: auto;
}
.hp-line {
  display: block;
  min-height: 1.62em;         /* reserve the row so typing never reflows */
  white-space: pre-wrap;
  word-break: break-word;
}
/* Line syntax helpers, usable inside .hp-line and .hp-code. */
.hp-c-key  { color: #7DD3FC; }
.hp-c-str  { color: #86EFAC; }
.hp-c-num  { color: #FDE68A; }
.hp-c-type { color: #FDE68A; }
.hp-c-com  { color: #64748B; }
.hp-c-ok   { color: #4ADE80; }
.hp-c-dim  { color: #7F97B4; }

/* Caret. It rides the line being typed, and stays on the last line
   during the pause before the loop restarts. */
.hp-line.is-typing::after,
.hp-line.is-caret::after {
  content: '';
  display: inline-block;
  width: .52em;
  height: 1.02em;
  margin-left: 2px;
  vertical-align: -.16em;
  background: var(--hp-mint);
  animation: hp-blink 1.05s steps(1, end) infinite;
}


/* ============================================================
   12. CODE TABS
   .hp-codetabs > .hp-tabbar > .hp-tab[data-tab]
                > pre.hp-code[data-panel]
   The first panel carries .is-active in the HTML, so the block is
   usable with JavaScript off. The JS adds the ARIA wiring and the
   arrow-key roving tabindex.
   ============================================================ */
/* Code is dark on both stages, exactly like .code-block in style.css,
   so the block needs no light-stage variant. Only the shadow does the
   work of lifting it off a white section. */
.hp-codetabs {
  border: 1px solid var(--hp-line);
  border-radius: 14px;
  overflow: hidden;
  background: var(--hp-code-bg);
  box-shadow: var(--hp-shadow);
}
.hp-tabbar {
  display: flex;
  gap: 2px;
  padding: 0 8px;
  background: #0E2138;
  border-bottom: 1px solid rgba(255,255,255,.08);
  overflow-x: auto;
  scrollbar-width: none;
}
.hp-tabbar::-webkit-scrollbar { display: none; }

.hp-tab {
  position: relative;
  flex: 0 0 auto;
  padding: 13px 18px;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font);
  font-size: .85rem;
  font-weight: 600;
  color: #8CA3BF;
  line-height: 1.4;
  white-space: nowrap;
  transition: color .25s var(--hp-ease);
}
.hp-tab::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 0;
  height: 2px;
  border-radius: 2px 2px 0 0;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 50% 50%;
  transition: transform .4s var(--hp-ease);
}
.hp-tab:hover { color: var(--hp-ink); }
.hp-tab.is-active { color: #fff; }
.hp-tab.is-active::after { transform: scaleX(1); }

.hp-code {
  display: none;
  margin: 0;
  padding: 22px 24px;
  overflow-x: auto;
  background: var(--hp-code-bg);
  font-family: var(--font-mono);
  font-size: .84rem;
  line-height: 1.72;
  color: #CBD9EA;
  tab-size: 2;
}
.hp-code.is-active { display: block; }
.hp-code code {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
  white-space: pre;
}
/* Reuse the site-wide code token classes inside .hp-code as well. */
.hp-code .code-keyword { color: #7DD3FC; }
.hp-code .code-string  { color: #86EFAC; }
.hp-code .code-number  { color: #FDE68A; }
.hp-code .code-type    { color: #FDE68A; }
.hp-code .code-comment { color: #64748B; }


/* ============================================================
   13. SPLIT
   Code on the left, result on the right, stacked below 992 px.
   ============================================================ */
.hp-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 32px;
  align-items: center;
}
.hp-split > * { min-width: 0; }
.hp-split.is-wide-left { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); }
.hp-split.is-wide-right { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); }
.hp-split-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-light);
}
.hp-dark .hp-split-arrow { color: var(--hp-ink-3); }


/* ============================================================
   14. STEPS
   Numbered pipeline. The connector is a single 2 px line behind
   the row, clipped by the first and last bullet.
   ============================================================ */
.hp-steps {
  position: relative;
  display: grid;
  grid-auto-flow: column;
  /* minmax(0,1fr), never plain 1fr. A long step title must not be able
     to push the row wider than its container. */
  grid-auto-columns: minmax(0, 1fr);
  gap: 24px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.hp-steps::before {
  content: '';
  position: absolute;
  top: 21px;
  left: 8%;
  right: 8%;
  height: 2px;
  background: linear-gradient(90deg, var(--border), var(--primary), var(--border));
  opacity: .55;
  z-index: 0;
}
.hp-dark .hp-steps::before {
  background: linear-gradient(90deg, rgba(255,255,255,.08), rgba(125,211,252,.55), rgba(255,255,255,.08));
}
.hp-step {
  position: relative;
  z-index: 1;
  text-align: center;
  padding: 0 6px;
}
.hp-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-bottom: 14px;
  border-radius: 50%;
  border: 2px solid var(--primary);
  background: var(--bg);
  color: var(--primary);
  font-weight: 800;
  font-size: 1rem;
}
.hp-dark .hp-step-num {
  border-color: rgba(125,211,252,.6);
  background: var(--hp-navy-0);
  color: var(--hp-sky);
}
.hp-step-title {
  display: block;
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 4px;
}
.hp-dark .hp-step-title { color: var(--hp-ink); }
.hp-step-text,
.hp-step p {
  margin: 0;
  font-size: .875rem;
  line-height: 1.6;
  color: var(--text-light);
}
.hp-dark .hp-step-text,
.hp-dark .hp-step p { color: var(--hp-ink-2); }


/* ============================================================
   15. LOGO WALL
   Text-only technology chips. No external images anywhere.
   ============================================================ */
.hp-logo-wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
}
.hp-logo-chip,
.hp-logo-wall > li,
.hp-logo-wall > span,
.hp-logo-wall > a,
.hp-logo-wall > div {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 60px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  font-size: .88rem;
  font-weight: 600;
  color: var(--text);
  text-align: center;
  list-style: none;
  transition:
    transform .35s var(--hp-ease),
    border-color .35s var(--hp-ease),
    background .35s var(--hp-ease);
}
.hp-logo-wall > a:hover,
.hp-logo-chip:hover {
  transform: translateY(-2px);
  border-color: var(--primary);
  background: var(--primary-lt);
  color: var(--primary-dk);
}
.hp-dark .hp-logo-chip,
.hp-dark .hp-logo-wall > li,
.hp-dark .hp-logo-wall > span,
.hp-dark .hp-logo-wall > a,
.hp-dark .hp-logo-wall > div {
  border-color: var(--hp-line);
  background: rgba(255,255,255,.04);
  color: var(--hp-ink-2);
}
.hp-dark .hp-logo-wall > a:hover,
.hp-dark .hp-logo-chip:hover {
  border-color: rgba(125,211,252,.45);
  background: rgba(125,211,252,.08);
  color: var(--hp-ink);
}


/* ============================================================
   16. MARQUEE
   Without JS the track is a plain wrapped row of items. The JS
   wraps the items in a .hp-marquee-set, clones the set once and
   flips the marquee into .is-live, which starts the keyframe. The
   set carries a trailing pad equal to the gap, so translating the
   track by exactly -50 % keeps the spacing seamless.
   ============================================================ */
.hp-marquee {
  --hp-marquee-gap: 12px;
  --hp-marquee-dur: 40s;
  position: relative;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
.hp-marquee-track {
  display: flex;
  flex-wrap: wrap;
  gap: var(--hp-marquee-gap);
  align-items: center;
}
.hp-marquee-track > * { flex: 0 0 auto; }

/* Live state, set by the JS once the track has been duplicated. */
.hp-marquee.is-live .hp-marquee-track {
  flex-wrap: nowrap;
  gap: 0;
  width: max-content;
  animation: hp-marquee var(--hp-marquee-dur) linear infinite;
  will-change: transform;
}
.hp-marquee-set {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--hp-marquee-gap);
  padding-right: var(--hp-marquee-gap);
}
.hp-marquee-set > * { flex: 0 0 auto; }

/* Pause for anyone reading it, with the mouse or with the keyboard. */
.hp-marquee:hover .hp-marquee-track,
.hp-marquee:focus-within .hp-marquee-track { animation-play-state: paused; }

/* Optional decorated item. Variants may also drop .hp-pill in here.
   Light stage is the default, the dark stage overrides it. */
.hp-marquee-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-alt);
  font-family: var(--font-mono);
  font-size: .82rem;
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
}
.hp-dark .hp-marquee-item {
  border-color: var(--hp-line);
  background: rgba(255,255,255,.04);
  color: var(--hp-ink-2);
}


/* ============================================================
   17. CHAT MOCK
   .hp-chat[data-chatstream] holds a short conversation. The final
   bubble carries .is-stream, its real text is in the HTML for the
   no-JS case, and the JS retypes it token by token in a loop.
   ============================================================ */
/* Light stage is the default. The dark stage overrides it below, which
   keeps the cascade one-directional and free of :not() guesswork. */
.hp-chat {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: var(--hp-radius);
  background: var(--bg-card);
  box-shadow: var(--shadow-lg);
}
.hp-chat-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 12px;
  margin-bottom: 2px;
  border-bottom: 1px solid var(--border);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-light);
}
.hp-chat-row,
.hp-chat-msg {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.hp-chat-row.is-user,
.hp-chat-msg.is-user { justify-content: flex-end; }
.hp-chat-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  border-radius: 8px;
  background: var(--primary-lt);
  color: var(--primary);
  font-size: .72rem;
  font-weight: 700;
}
.hp-chat-bubble {
  max-width: 84%;
  padding: 12px 16px;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--bg-alt);
  font-size: .92rem;
  line-height: 1.65;
  color: var(--text);
}
.hp-chat-bubble.is-user,
.is-user > .hp-chat-bubble {
  background: var(--primary-lt);
  border-color: rgba(0,87,184,.2);
  color: var(--text);
}
/* The streaming bubble reserves two lines, so the card cannot jump
   while the answer is being typed back in. */
.hp-chat-bubble.is-stream { min-height: 3.3em; }
.hp-chat-bubble code {
  font-family: var(--font-mono);
  font-size: .85em;
  color: var(--primary-dk);
}
.hp-chat-meta {
  font-size: .76rem;
  color: var(--text-light);
}
/* Streaming caret. */
.hp-chat-bubble.is-streaming::after {
  content: '';
  display: inline-block;
  width: .5em;
  height: 1em;
  margin-left: 2px;
  vertical-align: -.14em;
  background: var(--primary);
  animation: hp-blink 1.05s steps(1, end) infinite;
}

/* ---------- Chat on the dark stage ---------- */
.hp-dark .hp-chat {
  border-color: var(--hp-line);
  background: rgba(255,255,255,.05);
  box-shadow: var(--hp-shadow);
}
@supports (backdrop-filter: blur(10px)) or (-webkit-backdrop-filter: blur(10px)) {
  .hp-dark .hp-chat {
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }
}
.hp-dark .hp-chat-head {
  border-bottom-color: var(--hp-line);
  color: var(--hp-ink-3);
}
.hp-dark .hp-chat-avatar {
  background: rgba(125,211,252,.14);
  color: var(--hp-sky);
}
.hp-dark .hp-chat-bubble {
  border-color: var(--hp-line);
  background: rgba(255,255,255,.05);
  color: var(--hp-ink-2);
}
.hp-dark .hp-chat-bubble.is-user,
.hp-dark .is-user > .hp-chat-bubble {
  background: rgba(0,87,184,.32);
  border-color: rgba(125,211,252,.28);
  color: var(--hp-ink);
}
.hp-dark .hp-chat-bubble code { color: var(--hp-mint); }
.hp-dark .hp-chat-meta { color: var(--hp-ink-3); }
.hp-dark .hp-chat-bubble.is-streaming::after { background: var(--hp-sky); }


/* ============================================================
   18. QUOTE
   Testimonial card that reads correctly on either stage.
   ============================================================ */
.hp-quote {
  position: relative;
  padding: 28px 26px 24px;
  border: 1px solid var(--border);
  border-radius: var(--hp-radius);
  background: var(--bg-card);
  box-shadow: var(--hp-shadow-soft);
}
.hp-quote::before {
  content: '\201C';
  position: absolute;
  top: 6px;
  right: 18px;
  font-size: 4rem;
  line-height: 1;
  font-weight: 800;
  color: var(--primary);
  opacity: .12;
  pointer-events: none;
}
.hp-quote-text,
.hp-quote > p {
  margin: 0 0 16px;
  font-size: 1rem;
  line-height: 1.75;
  font-style: italic;
  color: var(--text);
}
.hp-quote-author,
.hp-quote cite {
  display: block;
  font-size: .92rem;
  font-weight: 700;
  font-style: normal;
  color: var(--text);
}
.hp-quote-role {
  display: block;
  font-size: .82rem;
  color: var(--text-light);
}
.hp-dark .hp-quote {
  border-color: var(--hp-line);
  background: var(--hp-glass);
  box-shadow: none;
}
.hp-dark .hp-quote::before { color: var(--hp-sky); opacity: .16; }
.hp-dark .hp-quote-text,
.hp-dark .hp-quote > p { color: var(--hp-ink); }
.hp-dark .hp-quote-author,
.hp-dark .hp-quote cite { color: var(--hp-ink); }
.hp-dark .hp-quote-role { color: var(--hp-ink-3); }


/* ============================================================
   19. PROGRESS RING
   .hp-progress-ring[data-ring-pct="86"] wraps an <svg> with a
   track circle and a value circle. The JS measures the radius,
   sets the dash pattern and animates the offset on reveal.
   ============================================================ */
.hp-progress-ring {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.hp-progress-ring > svg {
  display: block;
  transform: rotate(-90deg);   /* start the arc at twelve o'clock */
  transform-origin: 50% 50%;
  overflow: visible;
}
.hp-progress-ring circle {
  transition: stroke-dashoffset 1.4s var(--hp-ease);
}
.hp-ring-track {
  fill: none;
  stroke: var(--border);
  stroke-width: 8;
}
.hp-ring-bar {
  fill: none;
  stroke: var(--primary);
  stroke-width: 8;
  stroke-linecap: round;
}
.hp-ring-value {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 1.1rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.hp-dark .hp-ring-track { stroke: rgba(255,255,255,.12); }
.hp-dark .hp-ring-bar { stroke: var(--hp-sky); }
.hp-dark .hp-ring-value { color: var(--hp-ink); }


/* ============================================================
   20. TILT
   Pointer-driven 3D tilt with a specular glare. The JS writes the
   transform and the two glare coordinates, and only on devices
   with a fine pointer.
   ============================================================ */
.hp-tilt {
  position: relative;
  transform-style: preserve-3d;
  transition: transform .8s var(--hp-ease);
}
.hp-tilt.is-tilting {
  transition: transform .12s linear;   /* track the pointer, then ease back */
  will-change: transform;
}
.hp-tilt::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity .5s var(--hp-ease);
  background: radial-gradient(
    420px circle at var(--hp-glare-x, 50%) var(--hp-glare-y, 50%),
    rgba(255,255,255,.16) 0%,
    rgba(255,255,255,0) 55%);
}
.hp-tilt.is-tilting::after { opacity: 1; }


/* ============================================================
   21. SHUFFLE BAR AND NOTES
   The closing "see another angle" band, identical in all five
   variants, plus the small muted caption used under diagrams.
   ============================================================ */
.hp-shuffle-bar {
  padding: 40px 0;
  background: var(--bg-alt);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.hp-shuffle-bar .container {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px 24px;
  text-align: center;
}
.hp-shuffle-bar p {
  margin: 0;
  font-size: 1rem;
  color: var(--text-light);
}
.hp-shuffle-bar p strong { color: var(--text); }
.hp-shuffle {
  transition:
    transform .35s var(--hp-ease),
    background var(--transition),
    color var(--transition);
}
.hp-shuffle:hover { transform: translateY(-1px); }

.hp-note {
  font-size: .84rem;
  line-height: 1.6;
  color: var(--text-light);
  margin: 12px 0 0;
}
.hp-dark .hp-note { color: var(--hp-ink-3); }
.hp-note-center { text-align: center; }


/* ============================================================
   22. OVERFLOW CONTAINMENT
   Grid and flex items are min-width:auto by default, which means a
   child may refuse to shrink below its own content and drag its
   column past the edge of the screen. Nothing in the kit is allowed
   to do that. Every container the kit owns grants its children
   permission to shrink here, and so do style.css's own grids, but
   only where a kit component has been dropped into one, so the
   blast radius stays on these five pages.

   The one kit box measured overflowing a 390 px phone is fixed below:
   a .feature-visual holding a code panel and its caption side by
   side, min-content 586 px inside a 358 px column, +212 px.

   The site header's closed off-canvas drawer used to overflow here
   too. That was a bug in style.css, the header's backdrop-filter made
   it the containing block for the fixed drawer, and it is fixed at
   source now, so nothing about the nav belongs on this layer.
   ============================================================ */

/* ---------- Containers the kit owns ---------- */
.hp-bento > *,
.hp-stats > *,
.hp-steps > *,
.hp-logo-wall > *,
.hp-spotlight > *,
.hp-chat-row > *,
.hp-chat-msg > * { min-width: 0; }

/* ---------- Surfaces the kit owns ----------
   A surface may scroll its own contents, it may never widen the page. */
.hp-bento-card,
.hp-glass,
.hp-spotlight,
.hp-stat,
.hp-quote,
.hp-chat,
.hp-window,
.hp-window-body,
.hp-terminal,
.hp-terminal-body,
.hp-codetabs,
.hp-progress-ring {
  min-width: 0;
  max-width: 100%;
}

/* Media inside a kit component scales down instead of setting the width. */
.hp-bento-card svg,
.hp-bento-card img,
.hp-glass svg,
.hp-glass img,
.hp-spotlight svg,
.hp-stat svg,
.hp-quote svg,
.hp-chat svg,
.hp-window svg,
.hp-window img,
.hp-terminal svg,
.hp-codetabs svg,
.hp-split svg,
.hp-split img,
.hp-steps svg,
.hp-hero-visual svg,
.hp-hero-visual img { max-width: 100%; }

/* ---------- Kit components inside style.css's grids ----------
   The variants seat kit cards in .cards-grid, .stats-grid and .stats. Those
   grids belong to style.css and their items carry the same min-width:auto
   default, so the permission to shrink is granted here, scoped to the kit
   child rather than by editing the global grid. */
.cards-grid > .hp-glass,
.cards-grid > .hp-stat,
.cards-grid > .hp-bento-card,
.cards-grid > .hp-quote,
.cards-grid > .hp-chat,
.cards-grid > .hp-window,
.cards-grid > .hp-codetabs,
.cards-grid > .hp-terminal,
.stats-grid > .hp-stat,
.stats-row > .hp-stat,
.stats > .hp-stat { min-width: 0; }

/* ---------- .feature-row and .feature-visual ----------
   style.css lays .feature-row out as two 1fr columns and .feature-visual as a
   centred flex row. A variant that drops a code panel plus its caption into one
   .feature-visual gets them side by side, so that cell's min-content is the sum
   of the two, and as a grid item that cannot shrink it sets the column width for
   the whole row. Let both columns shrink, and stack the panel and its caption,
   which is where a caption belongs anyway.

   flex-wrap is the belt to the :has() braces: a browser without :has() still
   wraps the pair instead of overflowing. */
.feature-row > * { min-width: 0; }
.feature-visual {
  min-width: 0;
  flex-wrap: wrap;
}
.feature-visual:has(> .hp-codetabs),
.feature-visual:has(> .hp-terminal),
.feature-visual:has(> .hp-window),
.feature-visual:has(> .hp-note) {
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
}
.feature-visual > .hp-codetabs,
.feature-visual > .hp-terminal,
.feature-visual > .hp-window,
.feature-visual > .hp-note {
  width: 100%;
  min-width: 0;
  max-width: 100%;
}

/* ---------- Long words ----------
   A product name or an identifier is one unbreakable word. At the narrowest
   phones a single one of them in a heading is wider than its column, so it
   leaks out through the container and pushes the whole section past the screen.
   Measured on a 320 px screen: one .section-title ran 45 px over and dragged the
   document to 349 px. Headings and copy on these five pages now break the word
   rather than break the page. The rule only engages when a word genuinely will
   not fit, so nothing that already fits moves at all. */
.hp-h1,
.hp-lead,
.hp-eyebrow,
.hp-bento-card h3,
.hp-bento-card p,
.hp-stat-label,
.hp-stat-detail,
.hp-step-title,
.hp-step-text,
.hp-quote-text,
.hp-spotlight-title,
.hp-spotlight-text,
.hp-note,
.section-title,
.section-description { overflow-wrap: break-word; }

/* ---------- Korean line breaking ----------
   Korean puts spaces between its words, but a word is a run of Hangul syllables
   and the browser reads every syllable boundary inside it as a place it may
   break. At the display sizes on this page Chrome took that offer and split
   words down the middle, which reads as broken to a Korean speaker. keep-all
   holds the breaks to the spaces, so a word stays whole. Measured across the
   five Korean variants: 30 mid-word breaks at 1440 px and 83 at 390 px before
   this rule, 0 after.
   The overflow-wrap above still stands for Korean. It is the emergency fallback
   for a word wider than its column, so a long identifier still cannot push the
   page sideways on a 360 px phone. Korean alone needs this. Japanese and Chinese
   write without spaces between words and are meant to break by character, so
   they keep the normal behavior. */
:lang(ko) .hp-h1,
:lang(ko) .hp-lead,
:lang(ko) .section-title,
:lang(ko) .section-description { word-break: keep-all; }


/* ============================================================
   23. STAT CARDS IN EVERY WRAPPER
   The variants seat .hp-stat in three different parents: the kit's
   own .hp-stats, style.css's .stats-grid and .cards-grid, and now
   and then a bare .container. The card has to read the same in all
   of them: fill its cell, keep one rhythm between the number, the
   label and the detail, and never set the width of its column.
   ============================================================ */
/* No height here. A card standing on its own in a plain container has no cell to
   fill, and a blanket height:100% made it inherit the height of the whole band. */
.hp-stat {
  display: flex;
  flex-direction: column;
}
/* Only inside a grid, where the cell is real, does the card fill it. Cards in a
   row then share one height whichever of the three grids they landed in. */
.hp-stats > .hp-stat,
.stats-grid > .hp-stat,
.stats-row > .hp-stat,
.cards-grid > .hp-stat { height: 100%; }

/* A lone card dropped straight into a container is a callout rather than a
   column. It keeps its padding, but its copy keeps a readable measure instead
   of running the full width of the page. */
.container > .hp-stat .hp-stat-label,
.container > .hp-stat .hp-stat-detail { max-width: 62ch; }


/* ============================================================
   24. FOCUS VISIBILITY
   Every interactive hook in the kit gets a ring that survives on
   both stages. Mouse users never see it, keyboard users always do.
   ============================================================ */
.hp-tab:focus-visible,
.hp-shuffle:focus-visible,
.hp-btn-ghost:focus-visible,
.hp-pill:focus-visible,
.hp-bento-card a:focus-visible,
.hp-spotlight a:focus-visible,
.hp-logo-wall a:focus-visible,
.hp-code:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 6px;
}
/* Anything focusable on the dark stage. */
.hp-dark a:focus-visible,
.hp-dark button:focus-visible,
.hp-dark .btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* On the light bands the brand blue reads better than the amber. */
.hp-shuffle-bar .hp-shuffle:focus-visible {
  outline-color: var(--primary);
}
/* Code panels are scrollable, so they are focusable and must show it. */
.hp-code:focus { outline: none; }
.hp-code:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }


/* ============================================================
   25. RESPONSIVE
   Breakpoints follow the contract: 1200, 992, 768, 560. Nothing
   may overflow horizontally at 360 px.
   ============================================================ */

/* ---------- <= 1200 px ---------- */
@media (max-width: 1199px) {
  .hp-hero { padding: 64px 0 56px; }
  .hp-hero-grid { gap: 40px; }
  .hp-bento { gap: 16px; }
}

/* ---------- <= 992 px: the hero becomes one column ---------- */
@media (max-width: 991px) {
  .hp-hero { padding: 56px 0 52px; }
  .hp-hero-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 44px;
  }
  .hp-hero-copy { max-width: 720px; }
  /* One column, so the visual sits under the copy and nothing is centred
     against anything. It can be as tall as it likes, the page simply scrolls,
     and the cap that stops it bleeding out of a two-column stage is dropped. */
  .hp-hero-visual {
    display: block;
    max-width: 640px;
    max-height: none;
  }
  .hp-hero-visual .hp-terminal-body,
  .hp-hero-visual .hp-window-body { overflow-y: visible; }

  .hp-bento { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .hp-bento-card { grid-column: span 3; }
  .hp-bento-card.is-wide { grid-column: span 6; }
  .hp-bento-card.is-tall { grid-row: span 1; }

  .hp-split,
  .hp-split.is-wide-left,
  .hp-split.is-wide-right {
    grid-template-columns: minmax(0, 1fr);
    gap: 28px;
  }
  .hp-split-arrow { transform: rotate(90deg); }

  /* The pipeline wraps into a two-column grid, so the connector line
     between horizontal steps has to go. */
  .hp-steps {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px 20px;
  }
  .hp-steps::before { display: none; }

  .hp-spotlight-links,
  .hp-spotlight-actions { margin-left: 0; }
}

/* ---------- <= 768 px ---------- */
@media (max-width: 767px) {
  .hp-hero { padding: 60px 0 56px; }
  .hp-h1 { font-size: clamp(2rem, 8.2vw, 2.8rem); }
  .hp-lead { font-size: 1.06rem; }

  .hp-bento { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .hp-bento-card,
  .hp-bento-card.is-wide { grid-column: span 1; }

  .hp-spotlight {
    padding: 18px 20px;
    gap: 12px 18px;
  }
  .hp-logo-wall { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
  .hp-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; }
  .hp-chat-bubble { max-width: 92%; }
  .hp-terminal-body,
  .hp-code { font-size: .78rem; }
  .hp-window-body { padding: 18px; }
}

/* ---------- <= 560 px ---------- */
@media (max-width: 559px) {
  .hp-hero { padding: 48px 0 48px; }
  .hp-eyebrow {
    white-space: normal;
    font-size: .7rem;
    letter-spacing: .08em;
    padding: 6px 13px 6px 10px;
  }
  .hp-cta-row { gap: 10px; }
  /* style.css already stretches .btn-group children at 480 px. The hero
     CTA row is its own class, so it needs the same treatment. */
  .hp-cta-row .btn {
    width: 100%;
    justify-content: center;
  }
  .hp-proof { gap: 8px 16px; }
  .hp-proof li { font-size: .82rem; }

  .hp-steps { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .hp-step { text-align: left; display: grid; grid-template-columns: 44px 1fr; gap: 0 14px; padding: 0; }
  .hp-step-num { margin-bottom: 0; grid-row: span 2; }

  .hp-stats { grid-template-columns: minmax(0, 1fr); }
  .hp-glass,
  .hp-quote,
  .hp-chat { padding: 18px; }
  .hp-terminal-body { padding: 16px 16px 20px; }
  .hp-code { padding: 16px; }
  .hp-tab { padding: 12px 14px; font-size: .8rem; }
  .hp-shuffle-bar { padding: 32px 0; }
  .hp-shuffle-bar .container { flex-direction: column; }
  .hp-shuffle { width: 100%; justify-content: center; }
}

/* ---------- 360 px safety net ----------
   Long mono strings inside code and terminal blocks are the only
   things that can push the page wider than the viewport. They scroll
   inside their own box instead.
   ------------------------------------------------------------ */
@media (max-width: 400px) {
  .hp-h1 { font-size: 1.95rem; letter-spacing: -.02em; }
  .hp-lead { font-size: 1rem; }
  .hp-spotlight-mark { width: 40px; height: 40px; flex-basis: 40px; }
  .hp-logo-wall { grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 8px; }
  .hp-logo-chip,
  .hp-logo-wall > li,
  .hp-logo-wall > span,
  .hp-logo-wall > a,
  .hp-logo-wall > div { min-height: 52px; font-size: .8rem; padding: 10px; }
  .hp-window-bar,
  .hp-terminal-bar { padding: 10px 12px; }
}


/* ============================================================
   26. REDUCED MOTION
   style.css already clamps every animation and transition to
   0.01 ms under this query. These rules go further and remove the
   motion states themselves, so nothing is left half-played:
   the aurora sits still, the marquee returns to a wrapped row,
   reveals are simply visible, tilt is neutral and every caret
   stops blinking. js/home.js reads the same media query and skips
   the typewriter, the chat stream and the tilt entirely.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .hp-aurora,
  .hp-aurora::before,
  .hp-aurora::after,
  .hp-dot::after,
  .hp-float,
  .hp-js .hp-float,
  .hp-line.is-typing::after,
  .hp-line.is-caret::after,
  .hp-chat-bubble.is-streaming::after,
  .hp-marquee.is-live .hp-marquee-track {
    animation: none !important;
  }
  /* Keep the aurora as a still, painted gradient. */
  .hp-aurora::before { transform: translate3d(-4%, -2%, 0); opacity: .8; }
  .hp-aurora::after  { transform: translate3d(3%, 4%, 0);  opacity: .7; }

  /* Carets would otherwise freeze mid-blink and read as an artefact. */
  .hp-line.is-typing::after,
  .hp-line.is-caret::after,
  .hp-chat-bubble.is-streaming::after { display: none; }

  /* Reveals are simply content. */
  .hp-js .hp-reveal,
  .hp-js .hp-reveal.is-in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  /* The marquee is a static wrapped row again. */
  .hp-marquee.is-live .hp-marquee-track {
    flex-wrap: wrap;
    gap: var(--hp-marquee-gap);
    width: auto;
    transform: none !important;
  }
  .hp-marquee.is-live .hp-marquee-set { padding-right: 0; }
  .hp-marquee {
    -webkit-mask-image: none;
            mask-image: none;
  }

  /* No pointer tilt, no glare. */
  .hp-tilt,
  .hp-tilt.is-tilting {
    transform: none !important;
    transition: none !important;
  }
  .hp-tilt::after { opacity: 0 !important; }

  /* Rings land on their final value with no sweep. */
  .hp-progress-ring circle { transition: none !important; }

  /* Hover lift is motion too. */
  .hp-bento-card:hover,
  .hp-logo-wall > a:hover,
  .hp-logo-chip:hover,
  .hp-shuffle:hover { transform: none; }
}
