/* ============================================================================
   AIToolsay — Homepage Studio public kit (.hpx-*)
   ----------------------------------------------------------------------------
   White-first premium surface: elegant type, soft shadows, 20px radii,
   colourful icon chips, restrained motion.

   THE ONE RULE IN THIS FILE: it never hard-codes a colour, a radius, a shadow
   or a type size. Every one of those is `var(--hpx-…)`, and every one of those
   properties is a row in `hp_tokens` / `hp_typography` that an admin owns. A
   literal hex here is a colour the theme screen silently cannot change, which
   is how "100% admin editable" quietly stops being true.

   The fallbacks in each var() are there for exactly one case: the seconds
   between deploying this file and running the seeder. They are not the design.

   Dark mode is handled entirely by the compiled token block — `ThemeCompiler`
   emits the same properties again under `body.theme-dark`, so nothing below
   needs a dark twin unless it uses a colour that is NOT a token (shadows on
   coloured surfaces, mostly).

   Namespace: every selector starts `.hpx`. The page also lives inside the
   site's Bootstrap layout and beside two other card kits (`.aits-*`, `.crs-*`),
   and a bare `.card` or `.grid` here would collide with all of them.
   ========================================================================= */

/* ---------------------------------------------------------------------------
   1. Layout escape
   The public layout wraps every route in `.container` with a 9/12 + 3/12 grid
   and appends a #content-box repeating the page title. The homepage is
   full-bleed, so that chrome is neutralised here.

   Both `:has()` and the body class: Safari below 15.4 and Firefox below 121
   have no :has(), and the class is set by the kit's script as their fallback.
   ------------------------------------------------------------------------ */

body:has(.hpx) #content-box,
body.hpx-page #content-box,
body:has(.hpx) .sidebars,
body.hpx-page .sidebars,
body:has(.hpx) .page-wrapper .breadcrumb,
body.hpx-page .page-wrapper .breadcrumb,
body:has(.hpx) .page-wrapper > .page-content > #parallax,
body.hpx-page .page-wrapper > .page-content > #parallax { display: none !important; }

body:has(.hpx) .page-wrapper > .page-content > .container,
body.hpx-page .page-wrapper > .page-content > .container { max-width: 100% !important; padding: 0 !important; }

body:has(.hpx) .page-wrapper > .page-content > .container > .row,
body.hpx-page .page-wrapper > .page-content > .container > .row { margin: 0 !important; }

body:has(.hpx) .page-wrapper > .page-content > .container > .row > div,
body.hpx-page .page-wrapper > .page-content > .container > .row > div {
    flex: 0 0 100% !important; max-width: 100% !important; padding: 0 !important;
}

body:has(.hpx) .page-home,
body.hpx-page .page-home { margin: 0 !important; padding: 0 !important; }

/* The card the layout wraps content in has its own white background and
   border; on a full-bleed page that draws a box around the whole homepage. */
body:has(.hpx) .page-wrapper > .page-content > .container > .row > div > .card,
body.hpx-page .page-wrapper > .page-content > .container > .row > div > .card {
    background: transparent !important; border: 0 !important; box-shadow: none !important; margin: 0 !important;
}

body:has(.hpx) .page-wrapper > .page-content > .container > .row > div > .card > .card-body,
body.hpx-page .page-wrapper > .page-content > .container > .row > div > .card > .card-body { padding: 0 !important; }

/* Wrapper-level dark surface, so the canvas is continuous from the navbar to
   the footer rather than a dark page with light gutters. */
body.theme-dark:has(.hpx),
body.theme-dark:has(.hpx) .page-wrapper,
body.theme-dark:has(.hpx) .page-wrapper > .page-content,
body.theme-dark:has(.hpx) .page-wrapper > .page-content > .container,
body.theme-dark:has(.hpx) .page-wrapper > .page-content > .container > .row,
body.theme-dark:has(.hpx) .page-wrapper > .page-content > .container > .row > div,
body.theme-dark.hpx-page,
body.theme-dark.hpx-page .page-wrapper,
body.theme-dark.hpx-page .page-wrapper > .page-content,
body.theme-dark.hpx-page .page-wrapper > .page-content > .container,
body.theme-dark.hpx-page .page-wrapper > .page-content > .container > .row,
body.theme-dark.hpx-page .page-wrapper > .page-content > .container > .row > div {
    background: var(--hpx-bg, #0E0E11) !important;
}

/* ---------------------------------------------------------------------------
   2. Root
   ------------------------------------------------------------------------ */

.hpx {
    /* Fallbacks only — the real values arrive from hp_tokens. */
    --hpx-brand: #3680ED;          /* = site --aits-blue */
    --hpx-brand-600: #2060C0;      /* = site --aits-blue-dark */
    --hpx-brand-700: #1B4E9C;
    --hpx-brand-ink: #1B5BB5;
    --hpx-brand-soft: #EAF2FD;     /* = site --aits-blue-tint */
    --hpx-brand-line: #CFE0FA;     /* = site --aits-blue-line */
    --hpx-brand-glow: rgba(54, 128, 237, .28);
    --hpx-bg: #FFFFFF;
    --hpx-bg-2: #F7F9FC;
    --hpx-card: #FFFFFF;
    --hpx-card-2: #F9FAFC;
    --hpx-ink: #0B1220;
    --hpx-ink-2: #3D4757;
    --hpx-ink-3: #6B7686;
    --hpx-ink-invert: #FFFFFF;
    --hpx-line: #E7ECF3;
    --hpx-line-soft: #F0F3F8;
    --hpx-r-sm: 10px;
    --hpx-r-md: 14px;
    --hpx-r-lg: 20px;
    --hpx-r-xl: 26px;
    --hpx-r-pill: 999px;
    --hpx-sh-soft: 0 1px 2px rgba(16, 24, 40, .04), 0 6px 20px rgba(16, 24, 40, .05);
    --hpx-sh-lift: 0 2px 6px rgba(16, 24, 40, .06), 0 18px 44px rgba(16, 24, 40, .10);
    /* Bumped 1200 → 1600 (and wide 1360 → 1600) so the homepage's bands
       line up with the same 1600px shell every other public page runs at
       (see partials/shell-cap.blade.php + navbar/footer --container-hd
       and --aits-w tokens). On a 2560px monitor the header, content bands
       and footer all share the same edge instead of the homepage stopping
       400px narrower than the rest of the site. */
    --hpx-maxw: 1600px;
    --hpx-maxw-wide: 1600px;
    --hpx-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;

    /* Per-band, set inline by _band.blade.php. */
    --hpx-pt: 56px;
    --hpx-pb: 56px;
    --hpx-g: 20px;
    --hpx-r: 20px;
    --hpx-cd: 4;
    --hpx-ct: 2;
    --hpx-cm: 1;
    --hpx-i: 0;

    /* Motion, overridden per preset by ThemeCompiler — these are only the gap
       between deploy and the compiled block landing, so they must match the
       seeded presets or the first paint animates at a different speed. */
    --hpx-a-dur: 360ms;
    --hpx-a-delay: 0ms;
    --hpx-a-stagger: 32ms;
    --hpx-a-ease: cubic-bezier(.22, .61, .36, 1);
    --hpx-a-dist: 14px;
    --hpx-a-scale: 1;

    font-family: var(--hpx-font);
    color: var(--hpx-ink-2);
    background: var(--hpx-bg);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

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

.hpx a { color: inherit; text-decoration: none; }

/* ── Branded controls must out-rank `.hpx a { color: inherit }` ───────────
   `.hpx a` is (0,1,1); a single-class colour on an <a> (e.g. `.hpx-btn-solid
   { color:#fff }` at 0,1,0) loses to it, so a solid button that is a link would
   inherit dark ink text on its blue fill and read as a HIDDEN label. These
   re-assert the intended colour at (0,2,0). The contextual gradient/dark twins
   live further down the file and still win by source order. */
.hpx .hpx-btn-solid { color: #fff; }
.hpx .hpx-btn-ghost { color: var(--hpx-ink); }
.hpx .hpx-btn-pill  { color: var(--hpx-brand-ink); }
.hpx .hpx-btn-link  { color: var(--hpx-brand); }
.hpx .hpx-suggest-all { color: var(--hpx-brand); }
.hpx .hpx-suggest-row { color: var(--hpx-ink); }

/* Neutralise Tabler's global `a:hover { color: #c62828 }` (0,1,1) inside the
   homepage, so an unbranded link keeps its resting colour instead of turning
   red.

   The branded controls are EXCLUDED rather than out-ranked, and that exclusion
   is the whole point of this rule's shape. `.hpx a:hover` is (0,2,1) — one
   class, one type, one pseudo-class — which BEATS the (0,2,0) re-asserts above
   it. So without the `:not()`s every solid button on the page kept its white
   label at rest and lost it the instant the pointer arrived: dark body ink on a
   blue fill, i.e. the label appears to vanish on hover. It also silently broke
   the `.hpx-s-gradient` / `.hpx-s-dark` twins further down, which are (0,2,0)
   too.

   `:not(.hpx-btn)` covers all four button styles AND those contextual twins in
   one go, because every one of them carries the base class. Raising the
   branded rules instead would have meant a matching `:hover` variant for each
   of the six, plus each twin — six chances to miss one.

   The exclusions are wrapped in `:where()`, and that is not cosmetic. A bare
   `:not(.x)` CONTRIBUTES its argument's specificity, so three of them push
   this rule to (0,5,1) — high enough to out-rank the (0,3,0) `:hover` colours
   that newer controls (`.hpx-lib-more`, `.hpx-nr-btn`) declare for themselves,
   re-creating the very bug one layer up. `:where()` contributes ZERO, so the
   rule stays at (0,2,1): it still beats Tabler, it still cannot touch the six
   listed controls, and anything that declares its own hover colour at (0,3,0)
   or above wins on its own without needing to be listed here at all. That is
   why the list only has to name controls whose colour is declared at (0,2,0)
   with NO hover variant. */
.hpx a:hover:not(:where(.hpx-btn, .hpx-suggest-all, .hpx-suggest-row)),
.hpx a:focus:not(:where(.hpx-btn, .hpx-suggest-all, .hpx-suggest-row)) { color: inherit; }

.hpx img { max-width: 100%; height: auto; }
.hpx button { font: inherit; color: inherit; }

.hpx h1, .hpx h2, .hpx h3, .hpx h4 {
    margin: 0;
    color: var(--hpx-ink);
    font-weight: 700;
    letter-spacing: -.02em;
}

.hpx p { margin: 0; }
.hpx ul { margin: 0; padding: 0; list-style: none; }

.hpx :focus-visible {
    outline: 2px solid var(--hpx-brand);
    outline-offset: 3px;
    border-radius: var(--hpx-r-sm);
}

.hpx-sr {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------------------------------------------------------------------------
   3. Tint palette
   Eleven hues, each a background / ink / hairline triple, resolved from a
   `data-tone` attribute. Both twins come from hp_tokens, so dark mode is
   handled by the compiled block and never by a filter() here — a hue darkened
   by formula is how a directory gets unreadable chips at night.
   ------------------------------------------------------------------------ */

.hpx [data-tone] {
    --tone-bg: var(--hpx-t-blue-bg, #EAF2FE);
    --tone-ink: var(--hpx-t-blue-ink, #1B5BB5);
    --tone-line: var(--hpx-t-blue-line, #CFE1FB);
}

.hpx [data-tone="blue"]    { --tone-bg: var(--hpx-t-blue-bg);    --tone-ink: var(--hpx-t-blue-ink);    --tone-line: var(--hpx-t-blue-line); }
.hpx [data-tone="violet"]  { --tone-bg: var(--hpx-t-violet-bg);  --tone-ink: var(--hpx-t-violet-ink);  --tone-line: var(--hpx-t-violet-line); }
.hpx [data-tone="emerald"] { --tone-bg: var(--hpx-t-emerald-bg); --tone-ink: var(--hpx-t-emerald-ink); --tone-line: var(--hpx-t-emerald-line); }
.hpx [data-tone="amber"]   { --tone-bg: var(--hpx-t-amber-bg);   --tone-ink: var(--hpx-t-amber-ink);   --tone-line: var(--hpx-t-amber-line); }
.hpx [data-tone="rose"]    { --tone-bg: var(--hpx-t-rose-bg);    --tone-ink: var(--hpx-t-rose-ink);    --tone-line: var(--hpx-t-rose-line); }
.hpx [data-tone="cyan"]    { --tone-bg: var(--hpx-t-cyan-bg);    --tone-ink: var(--hpx-t-cyan-ink);    --tone-line: var(--hpx-t-cyan-line); }
.hpx [data-tone="indigo"]  { --tone-bg: var(--hpx-t-indigo-bg);  --tone-ink: var(--hpx-t-indigo-ink);  --tone-line: var(--hpx-t-indigo-line); }
.hpx [data-tone="orange"]  { --tone-bg: var(--hpx-t-orange-bg);  --tone-ink: var(--hpx-t-orange-ink);  --tone-line: var(--hpx-t-orange-line); }
.hpx [data-tone="teal"]    { --tone-bg: var(--hpx-t-teal-bg);    --tone-ink: var(--hpx-t-teal-ink);    --tone-line: var(--hpx-t-teal-line); }
.hpx [data-tone="pink"]    { --tone-bg: var(--hpx-t-pink-bg);    --tone-ink: var(--hpx-t-pink-ink);    --tone-line: var(--hpx-t-pink-line); }
.hpx [data-tone="slate"]   { --tone-bg: var(--hpx-t-slate-bg);   --tone-ink: var(--hpx-t-slate-ink);   --tone-line: var(--hpx-t-slate-line); }

/* ---------------------------------------------------------------------------
   4. Bands and containers
   ------------------------------------------------------------------------ */

.hpx-band {
    position: relative;
    padding-top: var(--hpx-pt);
    padding-bottom: var(--hpx-pb);
    background: var(--hpx-sec-bg, transparent);
    color: var(--hpx-sec-ink, inherit);
}

.hpx-band-bg {
    position: absolute; inset: 0;
    background-size: cover; background-position: center;
    opacity: .5; pointer-events: none;
}

/* ⚠️ THIS PADDING IS NOT A PAGE GUTTER — DO NOT REMOVE IT.
   Every .hpx-wrap renders inside `section.hpx-band.hpx-card-bordered`, so
   these 24px are the CARD'S INNER PADDING; strip them and the band's content
   sits flush against its own border. The homepage also renders inside
   `.container > .row > .col-lg-9` (it is a sidebar page), so its bands are
   NOT supposed to line up with the header brand in the first place.
   Removed on 2026-08-11 in a sitewide alignment pass and restored the same
   day — the mistake was treating a surface's inner padding as a shell gutter. */
.hpx-wrap {
    position: relative;
    width: 100%;
    max-width: var(--hpx-maxw);
    margin: 0 auto;
    padding: 0 24px;
}

.hpx-w-wide { max-width: var(--hpx-maxw-wide); }

/* ⚠️ Written `.hpx .hpx-w-full` — (0,2,0) — and not as a bare class. Three
   responsive rules further down set `.hpx-wrap, .hpx-duo { padding-left: … }`
   at (0,1,0), and at equal specificity the LATER rule wins, so a bare
   `.hpx-w-full { padding: 0 }` was silently overridden below 1024px and every
   full-bleed band came out inset by 13–20px. The ticker is the first band on
   this page to actually use the width, which is why it surfaced now.
   Same family as `.hpx a { color: inherit }` and `.hpx ul { padding: 0 }`. */
.hpx .hpx-w-full { max-width: none; padding-inline: 0; }

/* Two half bands side by side. The pairing was decided server-side; a half
   band with no partner was already downgraded to boxed. */
/* Same contract as .hpx-wrap above — this padding is the surface's inner
   inset, not a page gutter. Do not remove it. */
.hpx-duo {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 22px;
    max-width: var(--hpx-maxw);
    margin: 0 auto;
    padding: 0 24px;
    align-items: stretch;
}

.hpx-duo > .hpx-band { padding-left: 0; padding-right: 0; }
.hpx-duo .hpx-wrap { max-width: none; padding: 0; }

/* A grid item's automatic minimum size is its MIN-CONTENT, not zero, so a
   `1fr` column is only a ceiling — one non-shrinkable descendant (a card
   grid at its minimum, a nowrap heading) pushes the track wider than the
   viewport and the whole PAGE scrolls sideways. That is what these bands
   were doing on a phone: 630px columns in a 390px viewport. The stacking
   rule at 860px never helped, because `1fr` on one column has the same
   content floor as `1fr` on two. */
.hpx-duo > .hpx-band { min-width: 0; }
.hpx-duo .hpx-wrap,
.hpx-duo .hpx-panel { min-width: 0; }

/* Surface treatments ------------------------------------------------------ */

.hpx-s-tint { background: var(--hpx-sec-bg, var(--hpx-bg-2)); }

.hpx-s-card > .hpx-wrap > * {
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r);
    padding: 20px;
}

.hpx-s-gradient {
    background: linear-gradient(135deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 100%);
    color: var(--hpx-ink-invert);
}

.hpx-s-gradient h1, .hpx-s-gradient h2, .hpx-s-gradient h3 { color: var(--hpx-ink-invert); }

.hpx-s-dark {
    background: #10131A;
    color: #E9EDF5;
}

.hpx-s-dark h1, .hpx-s-dark h2, .hpx-s-dark h3 { color: #FFFFFF; }

/* The mesh glow. Two soft radial washes, no image.

   ⚠️ This is an absolutely-positioned pseudo-element, and per the painting
   order a positioned descendant paints ABOVE in-flow content. The hero got away
   with it only because `.hpx-hero` is itself `position: relative` and comes
   later in the DOM, so it out-paints the wash by accident. Every other band's
   `.hpx-wrap` is static, so the wash would have been laid OVER its cards and
   hazed them — which is why the wrap is lifted explicitly below.

   `z-index: 0` and not `-1`: a band sets no stacking context of its own, so a
   negative pseudo escapes to the page and paints underneath the band entirely.
   Same trap as the showcase card's rank chip and the hero badge's ring. */
.hpx-s-mesh::before {
    content: '';
    position: absolute; inset: 0;
    z-index: 0;
    /* Centres in PIXELS above the band edge, not in percent.
       At `6%` the centre tracked the band's own height, so on a tall band the
       dense part of the wash landed 40-odd pixels down and on a short one it
       landed in the copy. Anchored 30px above the edge with a 300px radius
       fading out at 64% (≈192px), the wash is fully transparent by ~162px —
       which is where the band subtitle starts.

       That matters for contrast, not just looks: `.hpx-sub` is `--hpx-ink-3`,
       which measures 4.41:1 on the bare page background and dropped to 4.08 at
       the densest point of the wash. Keeping the wash off the copy holds it at
       the page baseline instead of eating into it. (4.41 is itself under the
       4.5 AA floor — pre-existing, and a text-colour change to fix.) */
    background:
        radial-gradient(760px 280px at 88% -30px, var(--hpx-brand-soft), transparent 64%),
        radial-gradient(560px 260px at 6% -34px, var(--hpx-t-violet-bg, #F0EBFE), transparent 62%);
    pointer-events: none;
}

/* The arithmetic, so the next person changing these numbers knows what the
   constraint is: the wash must be transparent BEFORE the band subtitle starts.
     brand  — 280px radius × 64% = 179px from a centre 30px above the edge → 149px
     violet — 260px radius × 62% = 161px from a centre 34px above the edge → 127px
   Band subtitles begin 162px down (174px on the library band), so both clear it
   with room. Raise the radii and `.hpx-sub` starts losing contrast again. */

.hpx-s-mesh > .hpx-wrap,
.hpx-s-mesh > .hpx-duo { position: relative; z-index: 1; }

/* Patterns ---------------------------------------------------------------- */

.hpx-pat-dots::after,
.hpx-pat-grid::after {
    content: '';
    position: absolute; inset: 0;
    pointer-events: none;
    opacity: .5;
}

.hpx-pat-dots::after {
    background-image: radial-gradient(currentColor 1px, transparent 1px);
    background-size: 22px 22px;
    opacity: .06;
}

.hpx-pat-grid::after {
    background-image:
        linear-gradient(currentColor 1px, transparent 1px),
        linear-gradient(90deg, currentColor 1px, transparent 1px);
    background-size: 44px 44px;
    opacity: .04;
}

.hpx-band > .hpx-wrap { z-index: 1; }

/* ---------------------------------------------------------------------------
   5. Band heading
   ------------------------------------------------------------------------ */

.hpx-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    margin-bottom: 22px;
}

.hpx-head.is-compact { margin-bottom: 14px; }

.hpx-al-center .hpx-head { flex-direction: column; align-items: center; text-align: center; }
.hpx-al-center .hpx-head-copy { max-width: 720px; }

.hpx-eyebrow {
    display: inline-block;
    margin-bottom: 8px;
    color: var(--hpx-brand-ink);
    font-size: var(--hpx-fs-eyebrow, 12px);
    font-weight: var(--hpx-fw-eyebrow, 700);
    letter-spacing: var(--hpx-ls-eyebrow, .1em);
    text-transform: uppercase;
}

.hpx-title {
    font-size: var(--hpx-fs-h2, 30px);
    line-height: var(--hpx-lh-h2, 1.18);
    font-weight: var(--hpx-fw-h2, 750);
    letter-spacing: var(--hpx-ls-h2, -.022em);
}

.hpx-accent { color: var(--hpx-brand); }
/* No `margin-left` here any more — the band head emits a real space between
   the heading and its coloured words. A margin drew the gap but left the text
   itself reading "All freeAI Tools", which is what gets copied, announced and
   indexed; keeping both would double it. */

.hpx-sub {
    margin-top: 8px;
    max-width: 62ch;
    color: var(--hpx-ink-3);
    font-size: var(--hpx-fs-body, 15px);
    line-height: var(--hpx-lh-body, 1.66);
}

.hpx-al-center .hpx-sub { margin-left: auto; margin-right: auto; }

/* ── Showcase head ───────────────────────────────────────────────────
   Opt-in, requested by the partial (`headClass`) rather than by a band id, so
   any band can take the treatment: the eyebrow becomes a bordered pill, the
   heading steps up to display size and picks up a pair of sparkle flanks. */

.hpx-head.is-showcase { margin-bottom: 30px; }

/* A flex COLUMN, not normal flow. The heading below is `inline-block` so that
   the sparkle flanks can anchor to the width of the words rather than to the
   width of the column — but an inline-block heading sits on the same line as
   the inline-flex eyebrow pill whenever both happen to fit, which is exactly
   what a short title like "Explore Everything" does. As flex items each part
   gets its own line and still shrink-wraps. */
.hpx-head.is-showcase .hpx-head-copy {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.hpx-al-center .hpx-head.is-showcase .hpx-head-copy { align-items: center; }

/* A showcase head is three stacked lines tall, and `.hpx-head`'s default
   `align-items: flex-end` drops the button to the bottom of all three — level
   with the sub-heading rather than with the heading it belongs to. Centred
   against the block is where the eye expects it. Scoped to `hpx-al-left` so it
   cannot reach the centred variant, which stacks instead of sitting in a row. */
.hpx-al-left .hpx-head.is-showcase { align-items: center; }

.hpx-head.is-showcase .hpx-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 14px;
    padding: 7px 16px 7px 13px;
    background: var(--hpx-brand-soft);
    border: 1px solid var(--hpx-brand-line);
    border-radius: var(--hpx-r-pill);
    color: var(--hpx-brand-ink);
    font-size: 12.5px;
    letter-spacing: -.005em;
    text-transform: none;
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}

.hpx-eyebrow-spark {
    width: 14px; height: 14px;
    flex: 0 0 auto;
    color: var(--hpx-brand);
}

.hpx-head.is-showcase .hpx-title {
    /* inline-block so the box hugs the words: the sparkle flanks are anchored
       to 100% of this element, and on a block h2 that is the full column —
       they would sit at the page edges instead of beside the heading.

       Sized off the h2 token rather than a literal, so the Typography screen
       still governs it and the tablet/mobile steps come along for free. */
    position: relative;
    display: inline-block;
    font-size: calc(var(--hpx-fs-h2, 30px) * 1.42);
    line-height: 1.12;
    letter-spacing: -.03em;
}

.hpx-head.is-showcase .hpx-title .hpx-accent {
    /* Gradient rather than flat brand, matching the counters — this heading and
       the numbers below it are the two places the page raises its voice. */
    background: linear-gradient(120deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* Decorative sparkle clusters either side of the heading. Masks rather than a
   coloured data-URI so they follow the brand token in BOTH themes — a baked-in
   fill would stay light-mode blue at night.

   Centred heads only. The flanks hang 84px outside the heading on both sides;
   on a LEFT-aligned head the leading one lands outside the container and the
   trailing one runs into whatever shares the row — which on the library band
   is the "View all tools" button. A centred head is the only place they have
   room on both sides by construction. */
.hpx-al-center .hpx-head.is-showcase .hpx-title::before,
.hpx-al-center .hpx-head.is-showcase .hpx-title::after {
    content: '';
    position: absolute;
    top: 50%;
    width: 74px; height: 44px;
    background: var(--hpx-brand);
    opacity: .5;
    transform: translateY(-50%);
    pointer-events: none;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: contain;
    mask-size: contain;
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 74 44'%3E%3Cpath d='M20 4c1.6 6.2 3.4 8 9.6 9.6C23.4 15.2 21.6 17 20 23.2 18.4 17 16.6 15.2 10.4 13.6 16.6 12 18.4 10.2 20 4z'/%3E%3Cpath d='M47 20c1.1 4.3 2.4 5.6 6.7 6.7-4.3 1.1-5.6 2.4-6.7 6.7-1.1-4.3-2.4-5.6-6.7-6.7 4.3-1.1 5.6-2.4 6.7-6.7z'/%3E%3Ccircle cx='60' cy='11' r='2.6'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 74 44'%3E%3Cpath d='M20 4c1.6 6.2 3.4 8 9.6 9.6C23.4 15.2 21.6 17 20 23.2 18.4 17 16.6 15.2 10.4 13.6 16.6 12 18.4 10.2 20 4z'/%3E%3Cpath d='M47 20c1.1 4.3 2.4 5.6 6.7 6.7-4.3 1.1-5.6 2.4-6.7 6.7-1.1-4.3-2.4-5.6-6.7-6.7 4.3-1.1 5.6-2.4 6.7-6.7z'/%3E%3Ccircle cx='60' cy='11' r='2.6'/%3E%3C/svg%3E");
    animation: hpx-twinkle 4.5s var(--hpx-a-ease) infinite;
}

.hpx-al-center .hpx-head.is-showcase .hpx-title::before { inset-inline-end: calc(100% + 10px); transform: translateY(-50%) scaleX(-1); }
.hpx-al-center .hpx-head.is-showcase .hpx-title::after  { inset-inline-start: calc(100% + 10px); animation-delay: 2.2s; }

@keyframes hpx-twinkle {
    0%, 100% { opacity: .32; transform: translateY(-50%) scale(.94); }
    50%      { opacity: .62; transform: translateY(-50%) scale(1.04); }
}

/* scaleX(-1) is part of the mirrored flank's transform, so it needs its own
   keyframes or the animation drops the mirror on the first frame. */
.hpx-al-center .hpx-head.is-showcase .hpx-title::before { animation-name: hpx-twinkle-flip; }

@keyframes hpx-twinkle-flip {
    0%, 100% { opacity: .32; transform: translateY(-50%) scale(.94) scaleX(-1); }
    50%      { opacity: .62; transform: translateY(-50%) scale(1.04) scaleX(-1); }
}

.hpx-head.is-showcase .hpx-sub {
    margin-top: 12px;
    max-width: 56ch;
    font-size: 16px;
    color: var(--hpx-ink-3);
}

.hpx-band-foot,
.hpx-panel-foot { margin-top: 18px; }

.hpx-al-center .hpx-band-foot { text-align: center; }

.hpx-rule { border: 0; border-top: 1px solid var(--hpx-line); margin: 0; }

/* ---------------------------------------------------------------------------
   6. Buttons
   ------------------------------------------------------------------------ */

.hpx-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    font-size: var(--hpx-fs-button, 14px);
    font-weight: var(--hpx-fw-button, 650);
    letter-spacing: var(--hpx-ls-button, -.004em);
    border-radius: var(--hpx-r-pill);
    transition: transform .18s ease, box-shadow .22s ease, background-color .18s ease, color .18s ease, border-color .18s ease;
    white-space: nowrap;
}

.hpx-btn-link { color: var(--hpx-brand); padding: 4px 2px; }
.hpx-btn-link .hpx-btn-arrow { width: 15px; height: 15px; transition: transform .2s ease; }

.hpx-btn-solid {
    padding: 11px 20px;
    /* Site signature gradient — same 135deg blue→blue-dark as the header/footer. */
    background: linear-gradient(135deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 100%);
    color: #fff;
    box-shadow: 0 8px 20px -8px var(--hpx-brand-glow);
}

.hpx-btn-ghost {
    padding: 10px 19px;
    background: var(--hpx-card);
    color: var(--hpx-ink);
    border: 1px solid var(--hpx-line);
}

.hpx-btn-pill {
    padding: 9px 16px;
    background: var(--hpx-brand-soft);
    color: var(--hpx-brand-ink);
}

/* Buttons sitting on a gradient or dark band need their own palette — brand
   blue on brand blue is invisible. */
/* A white pill sits on a coloured band in BOTH themes, so its label needs a
   fixed dark blue (the token's dark twin is too light on white) — the same
   #2060C0 the header's white-pill CTA uses. */
.hpx-s-gradient .hpx-btn-solid,
.hpx-s-dark .hpx-btn-solid { background: #fff; color: #2060C0; box-shadow: 0 10px 26px -10px rgba(0, 0, 0, .45); }

.hpx-s-gradient .hpx-btn-ghost,
.hpx-s-dark .hpx-btn-ghost { background: rgba(255, 255, 255, .12); color: #fff; border-color: rgba(255, 255, 255, .34); }

.hpx-s-gradient .hpx-btn-link,
.hpx-s-dark .hpx-btn-link { color: #fff; }

@media (hover: hover) {
    .hpx-btn-link:hover .hpx-btn-arrow { transform: translateX(3px); }
    .hpx-btn-solid:hover { transform: translateY(-1px); box-shadow: 0 12px 26px -8px var(--hpx-brand-glow); }
    .hpx-btn-ghost:hover { border-color: var(--hpx-brand-line); color: var(--hpx-brand-ink); background: var(--hpx-brand-soft); }
    .hpx-btn-pill:hover { background: var(--hpx-brand); color: #fff; }
}

.hpx-btn:active { transform: scale(.97); }

/* ---------------------------------------------------------------------------
   7. Icon chips
   ------------------------------------------------------------------------ */

.hpx-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    background: var(--tone-bg);
    color: var(--tone-ink);
    border-radius: var(--hpx-r-md);
    transition: transform .2s ease, background-color .2s ease;
}

.hpx-ico-sm { width: 28px; height: 28px; font-size: 12px; border-radius: var(--hpx-r-sm); }
.hpx-ico-md { width: 40px; height: 40px; font-size: 15px; }
.hpx-ico-lg { width: 48px; height: 48px; font-size: 18px; }

.hpx-ico.is-plain { background: transparent; }
.hpx-ico svg { width: 55%; height: 55%; }
.hpx-ico img { width: 62%; height: 62%; object-fit: contain; }
.hpx-ico.is-shot { background: var(--hpx-card-2); padding: 6px; }
.hpx-ico.is-shot img { width: 100%; height: 100%; }
.hpx-ico-emoji { font-size: 1.15em; line-height: 1; }

/* ---------------------------------------------------------------------------
   8. Badges
   ------------------------------------------------------------------------ */

.hpx-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    background: var(--tone-bg);
    color: var(--tone-ink);
    border-radius: var(--hpx-r-pill);
    font-size: var(--hpx-fs-badge, 12px);
    font-weight: var(--hpx-fw-badge, 650);
    line-height: 1.5;
}

.hpx-badge.is-mini { padding: 2px 7px; font-size: 10.5px; }

.hpx-badge::before {
    content: '';
    width: 5px; height: 5px;
    border-radius: 50%;
    background: currentColor;
    opacity: .85;
}

/* ---------------------------------------------------------------------------
   9. Hero
   Centred column, floating orbit tiles either side, counter card underneath.

   Two specificity rules govern everything below, and breaking either one is
   what produces the "text is hidden / buttons off-brand" report:

     • `.hpx a  { color: inherit }`      (0,1,1)
     • `.hpx button { color: inherit }`  (0,1,1)

   Both out-rank a single-class colour like `.hpx-search-btn { color:#fff }`
   (0,1,0) NO MATTER the source order — so a coloured <a> or <button> in here
   silently inherits body ink and a white label disappears into a blue fill.
   Every coloured control below therefore states its colour at (0,2,0) or
   higher, written as `.hpx .hpx-thing`.

   `--hpx-glass-*` are LOCAL structural properties, not brand colours — they
   say how translucent "glass" is, not what hue it is, so they don't belong in
   hp_tokens any more than the shadows-on-coloured-surfaces already exempted
   at the top of this file do. Everything that IS a brand colour still reads
   from the real tokens, which is what makes the theme screens work.
   ------------------------------------------------------------------------ */

.hpx-hero {
    --hpx-glass-bg: rgba(255, 255, 255, .86);
    --hpx-glass-line: rgba(255, 255, 255, .9);
    --hpx-glass-blur: 20px;
    --hpx-hero-tile: 76px;
    --hpx-hero-tile-sm: 62px;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 26px 0 4px;
    text-align: center;
}

body.theme-dark .hpx-hero {
    --hpx-glass-bg: rgba(23, 26, 36, .78);
    --hpx-glass-line: rgba(255, 255, 255, .1);
}

/* No orbit means no margin to reserve for it, so the copy can breathe wider
   and the band can lose the padding that was only ever there to stop a tile
   grazing the headline. */
.hpx-hero.is-solo { padding-top: 12px; }
.hpx-hero.is-solo .hpx-hero-copy { max-width: 760px; }

.hpx-hero-copy {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
    max-width: 860px;
}

/* Badge -------------------------------------------------------------------- */

/* The first thing on the page, so it carries more chrome than its size
   suggests: a glass pill on a gradient hairline, the mark in its own tinted
   chip, and a brand glow underneath to seat it against the hero.

   No hover state. It is a <span> — nothing happens when you click it — and a
   non-control that lifts under the pointer is the same false affordance that
   was taken off the assurance strip and the why-choose cards. */
.hpx-hero-badge {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 9px;
    /* `border: 0` and a ring drawn as a pseudo-element instead, so the edge can
       be a GRADIENT while still following the pill radius — `border-image` with
       a gradient discards border-radius entirely. Padding absorbs the 1px the
       border used to occupy, so the pill is the same size as before. */
    padding: 9px 19px 9px 12px;
    border: 0;
    border-radius: var(--hpx-r-pill);
    background-color: var(--hpx-glass-bg);
    /* A fixed sheen rather than an animated shimmer: this sits above the fold
       on every visit and a permanent animation there is exactly the cost that
       the scroll-reveal was removed to avoid. */
    background-image: linear-gradient(135deg, rgba(255, 255, 255, .5) 0%, transparent 52%);
    color: var(--hpx-ink);
    font-size: 13.5px;
    font-weight: 640;
    letter-spacing: -.004em;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .75),
        0 1px 2px rgba(16, 24, 40, .04),
        0 10px 24px -14px var(--hpx-brand-glow);
    -webkit-backdrop-filter: blur(var(--hpx-glass-blur));
    backdrop-filter: blur(var(--hpx-glass-blur));
}

/* The gradient hairline. `mask-composite: exclude` cuts the padding-box out of
   the padding-box + border-box union, leaving only the 1px frame — the same
   technique the search field's focus ring uses.

   No z-index: the mask means only the 1px edge is ever painted, so although a
   positioned pseudo does paint above inline text, there is nothing of it over
   the text to see. A negative z-index would be the trap here — the badge sets
   no stacking context, so it would escape and paint under the pill entirely. */
.hpx-hero-badge::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(135deg,
        var(--hpx-brand-line) 0%,
        var(--hpx-line) 42%,
        var(--hpx-line) 58%,
        var(--hpx-t-violet-line, var(--hpx-brand-line)) 100%);
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
            mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    pointer-events: none;
}

body.theme-dark .hpx .hpx-hero-badge {
    background-image: linear-gradient(135deg, rgba(255, 255, 255, .06) 0%, transparent 52%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .07),
        0 1px 2px rgba(0, 0, 0, .4),
        0 12px 26px -14px rgba(0, 0, 0, .7);
}

/* The mark gets a chip of its own — the rounded square every other icon on this
   page now sits in, at the smallest size that still reads as one. */
.hpx-hero-badge-ico {
    display: block;
    flex: 0 0 auto;
    width: 15px; height: 15px;
    color: var(--hpx-brand-ink);
    animation: hpx-twinkle 3.2s ease-in-out infinite;
}

.hpx-hero-badge-chip {
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    width: 24px; height: 24px;
    border-radius: 8px;
    background-color: var(--hpx-brand-soft);
    box-shadow: inset 0 0 0 1px var(--hpx-brand-line);
}

/* Headline ----------------------------------------------------------------- */

.hpx-hero-title {
    width: 100%;
    margin: 22px 0 0;
    font-size: var(--hpx-fs-display, clamp(32px, 5.4vw, 62px));
    line-height: var(--hpx-lh-display, 1.08);
    font-weight: var(--hpx-fw-display, 800);
    letter-spacing: var(--hpx-ls-display, -.032em);
}

/* Each half of the headline is its own block, so the break between them is
   the editor's decision rather than whatever the container width happens to
   produce at this viewport. */
.hpx-hero-l1,
.hpx-hero-l2 { display: block; }
.hpx-hero-l2 { margin-top: .08em; }

/* Gradient text, scoped to the hero headline only. `.hpx-accent` on its own
   is a shared class every other band's heading uses, via the generic
   `.hpx-accent { color: var(--hpx-brand) }` in section 5 — those stay a flat
   brand colour; only the hero gets the treatment. `display:inline` matters:
   the accent is a phrase INSIDE line two ("All *AI Tools* in One Place"), not
   a line of its own. */
.hpx-hero-title .hpx-accent {
    display: inline;
    color: var(--hpx-brand);
    background: linear-gradient(100deg, var(--hpx-brand) 6%, var(--hpx-brand-600) 58%, var(--hpx-brand-ink) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

.hpx-hero-sub {
    width: 100%;
    margin-top: 20px;
    max-width: 60ch;
    color: var(--hpx-ink-3);
    font-size: var(--hpx-fs-lead, 18px);
    line-height: var(--hpx-lh-lead, 1.6);
}

.hpx-hero-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 26px; }

/* Buttons inside the hero get the elevated hover + ripple treatment on top of
   the shared `.hpx-btn` base; scoping to `.hpx-hero-actions` leaves every
   OTHER band's CTA — same `.hpx-btn` class — exactly as it was. */
.hpx-hero-actions .hpx-btn { position: relative; overflow: hidden; transition: transform .25s var(--hpx-a-ease), box-shadow .25s ease; }

@media (hover: hover) {
    .hpx-hero-actions .hpx-btn-solid:hover { transform: translateY(-2px); box-shadow: 0 16px 34px -10px var(--hpx-brand-glow); }
    .hpx-hero-actions .hpx-btn-ghost:hover { transform: translateY(-2px); border-color: var(--hpx-brand-line); }
}

/* Search ------------------------------------------------------------------- */

.hpx-search { position: relative; width: 100%; max-width: 780px; margin-top: 36px; }

/* BEFORE hover the field has to carry the hero on its own — it is the page's
   primary control and a flat white pill with a hairline is not that. A lit top
   edge, a contact shadow, an ambient drop and a brand-tinted bloom underneath,
   which is what makes it read as raised rather than drawn. */
.hpx-search-form {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 8px 8px 24px;
    background-color: var(--hpx-card);
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .7) 0%, rgba(255, 255, 255, 0) 46%);
    /* line-SOFT, not line. The spectrum ring sits immediately outside this
       border, so a normal-weight grey hairline lands as a dull separator
       between the white pill and a vivid edge. Kept rather than removed: if
       `mask-composite` is ever unavailable the ring cannot paint, and a field
       with no outline at all is a worse fallback than a faint one. */
    border: 1px solid var(--hpx-line-soft);
    border-radius: var(--hpx-r-pill);
    /* The last three layers are the iridescent bloom the ring casts. Layered
       COLOURED shadows rather than a blurred copy of the spectrum: a blurred
       pseudo would have to sit behind the pill, which means a negative z-index,
       which means giving `.hpx-search` a stacking context — and that would trap
       the suggest dropdown underneath the counter deck below it (z-index: 2).
       A halo is not worth breaking a dropdown for. These are static where the
       ring flows, which at this blur is indistinguishable. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .85),
        0 1px 2px rgba(16, 24, 40, .04),
        0 14px 38px -14px rgba(16, 24, 40, .20),
        0 0 22px -6px rgba(124, 77, 240, .30),
        0 6px 26px -8px rgba(236, 72, 153, .22),
        0 24px 52px -30px var(--hpx-brand-glow);
    transition: border-color .25s ease, box-shadow .25s ease, transform .25s var(--hpx-a-ease);
}

/* An 85% white top-light belongs on a light pill and blows out a dark one. */
/* The bloom runs HOTTER at night — a coloured halo that reads as a glow on
   near-black is barely a tint on white, and vice versa. */
body.theme-dark .hpx .hpx-search-form {
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .05) 0%, rgba(255, 255, 255, 0) 46%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .06),
        0 2px 6px rgba(0, 0, 0, .46),
        0 18px 44px rgba(0, 0, 0, .5),
        0 0 26px -4px rgba(124, 77, 240, .42),
        0 6px 30px -6px rgba(236, 72, 153, .30),
        0 24px 52px -30px var(--hpx-brand-glow);
}

/* ── The spectrum ring ──────────────────────────────────────────────
   A flowing multi-colour border that traces the pill. `mask-composite` cuts
   the padding-box out of the padding-box+border-box union, leaving only the
   ring itself — that is what makes a background gradient behave as a border
   gradient, which CSS has no native property for.

   This used to be a two-stop blue→violet ring shown ONLY on focus, on the
   argument that a permanent rainbow round the busiest control reads as loud.
   It is now the field's resting edge — 3px at 88% — and still has somewhere to
   go on focus, which is the only thing the resting value has to leave room
   for: 4px at full, plus the brand glow underneath it.

   The hues are the palette's own saturated values — the same ones every tint
   token on the page is mixed from — rather than a literal rainbow, so it reads
   as this site's spectrum and not a generic one. Written as hex on purpose:
   the `--hpx-t-*-ink` tokens are TEXT inks (muted in light, pale in dark) and
   would give a washed-out ring in one theme and a bleached one in the other. */
.hpx-search-form::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: inherit;
    padding: 3px;
    background-image: linear-gradient(90deg,
        #3680ED, #7C4DF0, #EC4899, #F43F5E, #F59E0B, #10B981, #06B6D4,
        #3680ED, #7C4DF0, #EC4899, #F43F5E, #F59E0B, #10B981, #06B6D4, #3680ED);
    /* The spectrum is listed TWICE across a 200% image, so shifting by exactly
       one element width lands on an identical pattern — the loop is seamless
       with no reverse and no visible seam. */
    background-size: 200% 100%;
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    opacity: .88;
    animation: hpx-ring-flow 9s linear infinite;
    transition: opacity .3s ease, padding .3s ease, inset .3s ease;
    pointer-events: none;
}

@keyframes hpx-ring-flow {
    to { background-position: 100% 50%; }
}

/* ── The glint ──────────────────────────────────────────────────────
   A specular highlight that races the ring and fades out at the far side, so
   the border reads as LIT rather than merely coloured. Its own pseudo because
   it has to run on a different clock to the spectrum underneath — one element
   can only animate `background-position` once, and a glint that crawled at the
   spectrum's 9s would read as a smear rather than a flash.

   `z-index: 1` puts it over the ring: both pseudos are positioned, and in tree
   order `::before` paints UNDER `::after`. Safe to raise — the mask carves it
   down to the 3px band OUTSIDE the field's border box, so it cannot reach the
   input or the button no matter what it sits above. */
.hpx-search-form::before {
    content: '';
    position: absolute;
    z-index: 1;
    inset: -3px;
    border-radius: inherit;
    padding: 3px;
    background-image: linear-gradient(100deg,
        transparent 40%, rgba(255, 255, 255, .95) 50%, transparent 60%);
    /* Narrower than the element, so the percentage positions in the keyframe
       behave as a straight left-to-right sweep. */
    background-size: 62% 100%;
    background-repeat: no-repeat;
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    opacity: 0;
    animation: hpx-ring-glint 4.2s var(--hpx-a-ease) infinite;
    transition: opacity .3s ease;
    pointer-events: none;
}

/* Crosses, then holds off-stage for the rest of the cycle — a glint that ran
   edge to edge without a rest would be a strobe, not a highlight. */
@keyframes hpx-ring-glint {
    0%   { background-position: -70% 50%; opacity: 0; }
    8%   { opacity: .9; }
    38%  { opacity: .9; }
    50%  { background-position: 170% 50%; opacity: 0; }
    100% { background-position: 170% 50%; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-search-form::before { animation: none; opacity: 0; }
}

/* The ring is decoration; a spectrum crawling round the hero forever is
   exactly what this setting exists to stop. It stays, it just holds still. */
@media (prefers-reduced-motion: reduce) {
    .hpx-search-form::after { animation: none; }
}

@media (hover: hover) {
    /* Full, not .8 — the resting ring is 88% now, so the old hover value would
       have DIMMED it under the pointer. */
    .hpx-search-form:hover::after { opacity: 1; }
}

.hpx-search-form:focus-within {
    border-color: transparent;
    box-shadow:
        0 0 0 5px var(--hpx-brand-glow),
        0 0 30px -4px rgba(124, 77, 240, .42),
        0 8px 34px -6px rgba(236, 72, 153, .30),
        var(--hpx-sh-lift);
    transform: translateY(-1px);
}

/* Focus takes it to full and thickens it a step, so the ring reads as the
   focus indicator rather than as the same decoration it was at rest. */
.hpx-search-form:focus-within::after {
    opacity: 1;
    inset: -4px;
    padding: 4px;
}

@media (hover: hover) {
    /* AFTER hover, but only while UNfocused: the field lifts and its bloom
       deepens, so pointing at it says "this is live" without competing with
       the focus ring. */
    .hpx-search-form:hover {
        border-color: var(--hpx-line-strong);
        transform: translateY(-1px);
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .85),
            0 1px 2px rgba(16, 24, 40, .04),
            0 18px 44px -14px rgba(16, 24, 40, .22),
            0 0 26px -5px rgba(124, 77, 240, .38),
            0 6px 30px -7px rgba(236, 72, 153, .28),
            0 26px 56px -30px var(--hpx-brand-glow);
    }

    body.theme-dark .hpx .hpx-search-form:hover {
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .06),
            0 2px 6px rgba(0, 0, 0, .5),
            0 22px 50px rgba(0, 0, 0, .55),
            0 0 30px -3px rgba(124, 77, 240, .5),
            0 6px 34px -5px rgba(236, 72, 153, .36),
            0 26px 56px -30px var(--hpx-brand-glow);
    }

    /* (0,3,0). `:focus-within` and `:hover` are BOTH (0,2,0) and the hover
       rules sit later in the file, so without this the focus glow would be
       replaced the moment the pointer crossed a focused field — the ring would
       vanish under the cursor. */
    .hpx-search-form:focus-within:hover {
        border-color: transparent;
        box-shadow:
        0 0 0 5px var(--hpx-brand-glow),
        0 0 30px -4px rgba(124, 77, 240, .42),
        0 8px 34px -6px rgba(236, 72, 153, .30),
        var(--hpx-sh-lift);
    }
}

/* ── The focus glow, restated for dark ──────────────────────────────
   `.hpx-search-form:focus-within` is (0,2,0), and every dark-mode shadow on
   this control is a `body.theme-dark …` chain at (0,3,1) or more — so in dark
   the RESTING shadow outranked the focus one and the 5px brand glow never
   painted at all. Only the gradient ring showed, which is why focus read as
   half-applied at night. That predates this pass; the hover rules above would
   have made it worse.

   Both twins live outside the hover query and AFTER it: the first ties the
   dark hover rule at (0,4,1) and wins on source order, the second is (0,5,1)
   and cannot be beaten by either. */
body.theme-dark .hpx .hpx-search-form:focus-within,
body.theme-dark .hpx .hpx-search-form:focus-within:hover {
    border-color: transparent;
    box-shadow:
        0 0 0 5px var(--hpx-brand-glow),
        0 2px 6px rgba(0, 0, 0, .46),
        0 20px 46px rgba(0, 0, 0, .52),
        0 0 34px -3px rgba(124, 77, 240, .55),
        0 8px 38px -5px rgba(236, 72, 153, .38);
}

.hpx-search-ico-wrap {
    display: flex;
    flex: 0 0 auto;
    width: 22px; height: 22px;
    /* ink-3, not ink-faint: at 22px the faint token read as a disabled glyph
       rather than as the field's own mark. */
    color: var(--hpx-ink-3);
    transition: color .25s ease, transform .25s var(--hpx-a-ease);
}

@media (hover: hover) {
    .hpx-search-form:hover .hpx-search-ico-wrap { color: var(--hpx-ink-2); }
}

.hpx-search-form:focus-within .hpx-search-ico-wrap { transform: scale(1.06); }

.hpx-search-form:focus-within .hpx-search-ico-wrap { color: var(--hpx-brand); }
.hpx-search-ico { width: 22px; height: 22px; display: block; }

.hpx-search-field { position: relative; flex: 1 1 auto; min-width: 0; display: flex; align-items: center; }

.hpx-search-field input {
    width: 100%;
    height: 56px;
    padding-right: 34px;
    border: 0;
    outline: 0;
    background: transparent;
    color: var(--hpx-ink);
    font-size: 16px;
    font-weight: 450;
}

/* `text-overflow: ellipsis` on the placeholder is the safety net, not the fix —
   the kit swaps in a shorter string when the full one does not fit (see
   `hpxFitPlaceholder`). This is what stops the remaining cases reading as a
   hard cut mid-word: at 430px the default placeholder is 388px of text in a
   243px field, and "…prompts, c" looks like a rendering fault. */
.hpx-search-field input::placeholder {
    color: var(--hpx-ink-faint);
    font-weight: 400;
    text-overflow: ellipsis;
}
.hpx-search-field input::-webkit-search-cancel-button { -webkit-appearance: none; }

.hpx .hpx-search-clear {
    position: absolute;
    right: 2px;
    display: none;
    align-items: center;
    justify-content: center;
    width: 26px; height: 26px;
    border: 0;
    border-radius: 50%;
    background: var(--hpx-bg-2);
    color: var(--hpx-ink-3);
    cursor: pointer;
    transition: background-color .2s ease, color .2s ease;
}

.hpx-search-clear svg { width: 12px; height: 12px; }
.hpx-search-field input:not(:placeholder-shown) ~ .hpx-search-clear { display: inline-flex; }

@media (hover: hover) {
    .hpx .hpx-search-clear:hover { background: var(--hpx-brand-soft); color: var(--hpx-brand-ink); }
}

/* ── The Ctrl+K hint ────────────────────────────────────────────────
   Shares the clear button's slot and swaps with it — `:placeholder-shown`
   means empty, so the hint is up exactly while there is nothing to clear.
   Gated to ≥1100px below, where the 780px field has room to spare. */
/* The wrapper carries NO surface of its own — the two caps do. One pill
   holding two words reads as a badge; separate caps read as keys, which is
   what the hint is actually describing. */
.hpx-search-kbd {
    position: absolute;
    right: 2px;
    display: none;
    align-items: center;
    gap: 4px;
    font-size: 10.5px;
    font-weight: 650;
    line-height: 1;
    letter-spacing: .01em;
    white-space: nowrap;
    /* Decoration over a live input — it must never eat a click meant for the
       field, and it is not selectable text. */
    pointer-events: none;
    user-select: none;
    transition: opacity .25s ease, transform .25s var(--hpx-a-ease);
}

/* A physical keycap: a hairline ring for the edge, a solid 1px underneath for
   the key's side wall, and a soft drop under that for the shadow it casts.
   Those three layers are the whole illusion — drop any one and it flattens
   back into a label with a border. */
.hpx-search-kbd kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 21px;
    height: 21px;
    padding: 0 6px;
    border: 0;
    border-radius: 6px;
    background: var(--hpx-card);
    color: var(--hpx-ink-2);
    font: inherit;
    box-shadow:
        inset 0 0 0 1px var(--hpx-line),
        0 1px 0 var(--hpx-line-strong),
        0 2px 3px -1px rgba(16, 24, 40, .10);
    transition: color .25s ease, box-shadow .25s ease;
}

body.theme-dark .hpx .hpx-search-kbd kbd {
    background: rgba(255, 255, 255, .07);
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, .13),
        0 1px 0 rgba(0, 0, 0, .5),
        0 2px 3px -1px rgba(0, 0, 0, .5);
}

.hpx-search-kbd-plus { color: var(--hpx-ink-faint); font-weight: 500; }

@media (hover: hover) {
    /* The caps pick up the brand when the field is hovered — a quiet "there is
       a shortcut for this" rather than a second control competing for the eye.

       `--hpx-brand-ink`, NOT #357FEC: the requested blue measures 3.89:1 on
       white, and this is 10.5px text. The ink token is the same hue two steps
       down and lands at 6.54:1. */
    .hpx-search-form:hover .hpx-search-kbd kbd {
        color: var(--hpx-brand-ink);
        box-shadow:
            inset 0 0 0 1px var(--hpx-brand-line),
            0 1px 0 var(--hpx-brand-line),
            0 2px 6px -1px var(--sc-glow);
    }
}

/* Once the caret is in the field the hint has said what it had to say, and
   sitting beside live typing it is only clutter. */
.hpx-search-form:focus-within .hpx-search-kbd {
    opacity: 0;
    transform: translateX(4px);
}

@media (min-width: 1100px) {
    .hpx-search-field input:placeholder-shown ~ .hpx-search-kbd { display: inline-flex; }

    /* Paired with the hint, and load-bearing: without it the placeholder runs
       underneath. `hpxFitPlaceholder` subtracts `paddingRight` when it measures
       the room, so widening it makes the kit swap to the SHORT placeholder a
       little sooner — text is shortened, never hidden. */
    .hpx-search-field input:placeholder-shown { padding-right: 84px; }
}

/* Hidden in the markup until the kit confirms SpeechRecognition exists — a
   mic button that does nothing on click is worse than no button at all. */
.hpx .hpx-search-voice {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 44px; height: 44px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--hpx-ink-3);
    cursor: pointer;
    transition: background-color .25s ease, color .25s ease;
}

.hpx .hpx-search-voice[hidden] { display: none; }
.hpx-search-voice svg { position: relative; z-index: 1; width: 18px; height: 18px; }

@media (hover: hover) {
    .hpx .hpx-search-voice:hover { background: var(--hpx-brand-soft); color: var(--hpx-brand-ink); }
}

.hpx-search-voice-ring {
    position: absolute; inset: 0;
    border-radius: 50%;
    border: 2px solid var(--hpx-brand);
    opacity: 0;
    transform: scale(.8);
}

.hpx .hpx-search-voice.is-listening { background: var(--hpx-brand-soft); color: var(--hpx-brand); }
.hpx-search-voice.is-listening .hpx-search-voice-ring { animation: hpx-voice-pulse 1.5s ease-out infinite; }

/* The white label on the blue fill. `.hpx button { color: inherit }` is
   (0,1,1) and beats a bare `.hpx-search-btn { color:#fff }` (0,1,0) whatever
   the source order — which is precisely how this label ended up dark ink on a
   blue gradient and read as missing. Stated at (0,2,0) it wins. */
.hpx .hpx-search-btn {
    position: relative;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex: 0 0 auto;
    height: 56px;
    padding: 0 28px;
    border: 0;
    border-radius: var(--hpx-r-pill);
    /* `--sc-fill`, the page-wide filled-control recipe, NOT a bespoke gradient.
       The old fill ran brand-600 → brand and slid toward the light end on
       hover, so the label's contrast got WORSE the moment a pointer arrived:
       white on `--hpx-brand` is 3.84:1 in light and 2.6:1 in dark, because that
       token's dark twin is lightened for use as ink. `--sc-fill` steps down to
       -600/-700 in light and lays a scrim in dark, and every other primary
       control on the page already uses it. */
    background-image: var(--sc-fill);
    /* The floor under the gradient: `background-image` alone leaves a #fff
       label on nothing anywhere the gradient does not paint — forced-colors,
       print, or an override further down. */
    background-color: var(--hpx-brand-600);
    color: #fff;
    font-size: 15px;
    font-weight: 650;
    letter-spacing: -.005em;
    white-space: nowrap;
    cursor: pointer;
    /* The inset top-light is what stops a solid fill reading as a flat
       rectangle — same highlight the icon plates across the page carry. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .3),
        inset 0 0 0 1px rgba(255, 255, 255, .1),
        0 2px 6px -2px rgba(11, 18, 32, .22),
        0 12px 28px -10px var(--sc-glow);
    transition: transform .25s var(--hpx-a-ease), box-shadow .25s ease, filter .25s ease;
}

/* The sheen that the gradient slide used to provide, as an overlay instead —
   it brightens the face without moving the fill toward a lighter stop, so the
   label keeps the same contrast hovered as at rest. */
.hpx-search-btn::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(120deg, transparent 20%, rgba(255, 255, 255, .22) 50%, transparent 80%);
    opacity: 0;
    transform: translateX(-60%);
    transition: opacity .3s ease, transform .55s var(--hpx-a-ease);
    pointer-events: none;
}

.hpx-search-btn > * { position: relative; z-index: 1; }

.hpx-search-btn-spark { width: 16px; height: 16px; flex: 0 0 auto; }
.hpx-search-btn-ico,
.hpx-search-btn-spin { width: 16px; height: 16px; display: none; }
.hpx-search-btn-spin { animation: hpx-spin .8s linear infinite; }

@media (hover: hover) {
    .hpx .hpx-search-btn:hover {
        transform: translateY(-1px);
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .34),
            inset 0 0 0 1px rgba(255, 255, 255, .14),
            0 3px 8px -2px rgba(11, 18, 32, .24),
            0 20px 38px -10px var(--sc-glow);
        filter: saturate(1.08);
    }

    .hpx-search-btn:hover::before { opacity: 1; transform: translateX(60%); }

    .hpx .hpx-search-btn:hover .hpx-search-btn-spark { animation: hpx-twinkle 1.4s ease-in-out infinite; }
}

.hpx .hpx-search-btn:active { transform: scale(.975); }

.hpx .hpx-search-btn.is-loading { pointer-events: none; }
.hpx-search-btn.is-loading span,
.hpx-search-btn.is-loading .hpx-search-btn-spark,
.hpx-search-btn.is-loading .hpx-search-btn-ico { opacity: 0; }

.hpx-search-btn.is-loading .hpx-search-btn-spin {
    display: block;
    position: absolute;
    left: 50%; top: 50%;
    margin: -8px 0 0 -8px;
}

/* Ripple — one shared class, appended by the kit to whatever was pressed.
   `currentColor` is deliberate: the same class reads as a light pulse on the
   blue search button and a dark one on a pale chip, no per-variant override. */
.hpx-ripple {
    position: absolute;
    border-radius: 50%;
    background: currentColor;
    opacity: .22;
    mix-blend-mode: overlay;
    transform: scale(0);
    pointer-events: none;
    animation: hpx-ripple-in .6s ease-out forwards;
}

/* Suggestions (inline search mode only) ------------------------------------ */

.hpx-suggest {
    position: absolute;
    z-index: 40;
    top: calc(100% + 10px);
    left: 0; right: 0;
    max-height: 420px;
    overflow-y: auto;
    padding: 8px;
    text-align: left;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r-lg);
    box-shadow: var(--hpx-sh-lift);
}

.hpx-suggest-group + .hpx-suggest-group { margin-top: 6px; border-top: 1px solid var(--hpx-line-soft); padding-top: 6px; }

.hpx-suggest-head {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 6px 10px;
    color: var(--hpx-ink-3);
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.hpx .hpx-suggest-all { margin-left: auto; color: var(--hpx-brand); text-transform: none; letter-spacing: 0; font-size: 12px; }

.hpx .hpx-suggest-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: var(--hpx-r-sm);
    color: var(--hpx-ink);
    font-size: 14px;
    transition: background-color .15s ease;
}

.hpx .hpx-suggest-row:hover { background: var(--hpx-bg-2); color: var(--hpx-ink); }
.hpx-suggest-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hpx-suggest-none { padding: 18px 12px; text-align: center; color: var(--hpx-ink-3); font-size: 14px; }

/* Popular chips ------------------------------------------------------------ */

/* The row is ONE component, not a label with some pills loose beside it: a
   recessed track holds the label, the rail and both arrows, so the group reads
   as a search affordance rather than as leftovers under the search box. That
   track is also what gives the chips something to sit ON — a white pill on a
   white hero has only its hairline to prove it is a control. */
.hpx-chipsrow {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    max-width: 100%;
    margin-top: 26px;
    padding: 8px 10px;
    border-radius: 999px;
    background: var(--hpx-bg-2);
    box-shadow: inset 0 0 0 1px var(--hpx-line-soft);
}

body.theme-dark .hpx .hpx-chipsrow {
    background: rgba(255, 255, 255, .035);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06);
}

.hpx-chips-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    padding-inline-start: 6px;
    color: var(--hpx-ink-2);
    font-size: 13px;
    font-weight: 680;
    letter-spacing: -.005em;
    white-space: nowrap;
}

/* A brand-tinted mark, the same soft-chip idea the eyebrow pills use. Small
   enough to read as punctuation on the label rather than as a sixth control. */
.hpx-chips-label-ico {
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    width: 24px; height: 24px;
    border-radius: 8px;
    background: var(--hpx-brand-soft);
    color: var(--hpx-brand-ink);
    box-shadow: inset 0 0 0 1px var(--hpx-brand-line);
}

.hpx-chips-label-ico svg { width: 13px; height: 13px; }

/* A rail, not a wrap. The reference keeps popular searches on ONE line with
   an arrow at the end, and a set an editor grows to fifteen entries must not
   silently push the counter card down three rows. */
.hpx-chips {
    display: flex;
    align-items: center;
    gap: 9px;
    flex: 1 1 auto;
    min-width: 0;
    padding: 3px 2px;
    overflow-x: auto;
    /* NO `scroll-behavior: smooth` here. It applies to direct `scrollLeft`
       assignment as well as to scrollTo(), so the nudge arrow's instant
       fallback would animate too — and when the animation is the thing being
       suppressed, that fallback stalls exactly like the call it exists to
       rescue. The kit asks for smoothness per-call instead. */
    scrollbar-width: none;
    -ms-overflow-style: none;
    scroll-snap-type: x proximity;

    /* The rail overflows by design, but a chip sliced mid-word reads as a
       layout bug rather than as "there is more". Fading the cut edge turns it
       into the affordance instead.
       Driven entirely by the data attributes `syncRail()` already publishes,
       so there is no new JavaScript and no second source of truth: the fade
       appears only on the side that actually has more to reach, and a rail
       short enough not to scroll is never dimmed at all.
       `mask` rather than a background overlay, so it works on any surface and
       needs no dark twin. */
    --hpx-fade-l: 0px;
    --hpx-fade-r: 0px;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--hpx-fade-l),
                                        #000 calc(100% - var(--hpx-fade-r)), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--hpx-fade-l),
                                #000 calc(100% - var(--hpx-fade-r)), transparent 100%);
}

.hpx-chips[data-scrollable="1"]:not([data-at-end="1"])   { --hpx-fade-r: 34px; }
.hpx-chips[data-scrollable="1"]:not([data-at-start="1"]) { --hpx-fade-l: 24px; }

.hpx-chips::-webkit-scrollbar { display: none; }

/* An <a>, so it survives JavaScript being off and can be middle-clicked —
   which means `.hpx a { color: inherit }` (0,1,1) applies and the resting
   AND hover colours both have to be re-stated above it. */
/* BEFORE hover the chip has to look finished on its own: a lit top edge and a
   contact shadow, so it reads as a raised pill rather than an outlined box.
   `isolation: isolate` keeps the sweep below the label without the label
   needing an element of its own to raise — the text here is a bare text node. */
.hpx .hpx-chip {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    padding: 9px 18px;
    background-color: var(--hpx-card);
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .55) 0%, rgba(255, 255, 255, 0) 62%);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r-pill);
    color: var(--hpx-ink-2);
    font-size: 13.5px;
    font-weight: 570;
    white-space: nowrap;
    cursor: pointer;
    scroll-snap-align: start;
    box-shadow: 0 1px 2px rgba(16, 24, 40, .05);
    transition:
        background-color .25s ease, background-image .25s ease, border-color .25s ease,
        color .25s ease, transform .25s var(--hpx-a-ease), box-shadow .25s ease;
}

/* A 55% white top-light belongs on a light pill and blows out a dark one. */
body.theme-dark .hpx .hpx-chip {
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .07) 0%, rgba(255, 255, 255, 0) 62%);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .4);
}

/* The sweep. `z-index: -1` inside the chip's own stacking context paints it
   ABOVE the chip's background and BELOW its label, which is the one place it
   can go without wrapping the text in a span. */
.hpx-chip::after {
    content: '';
    position: absolute;
    z-index: -1;
    inset-block: -40%;
    inset-inline-start: -55%;
    width: 42%;
    background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, .55) 50%, transparent 100%);
    transform: skewX(-18deg);
    opacity: 0;
    pointer-events: none;
}

@media (hover: hover) {
    /* AFTER hover the chip FILLS. A tint on a card-coloured pill barely
       registers next to six more card-coloured pills, and this is the row's
       primary action.

       `--sc-fill` is the page-wide filled-control recipe, NOT a bespoke
       gradient: white on `--hpx-brand` measures 3.84:1 in light and 2.6:1 in
       dark (the dark twin is lightened for use as ink), so the shared token
       steps down the ramp in light and lays a scrim in dark. Every other
       primary control on the page already uses it, which is what makes this
       read as branding rather than as a one-off blue. */
    .hpx .hpx-chip:hover {
        background-image: var(--sc-fill);
        /* The floor under the gradient — a #fff label on nothing anywhere the
           gradient does not paint (forced-colors, print) is a hidden label. */
        background-color: var(--hpx-brand-600);
        border-color: transparent;
        color: #fff;
        transform: translateY(-2px);
        box-shadow: 0 10px 22px -8px var(--hpx-brand-glow);
    }

    .hpx-chip:hover::after { animation: hpx-sweep .85s var(--hpx-a-ease); }
}

.hpx .hpx-chip:active { transform: translateY(0) scale(.96); }

/* Keyboard parity, and OUTSIDE the hover query on purpose: a keyboard user on
   a touch device reports `hover: none`, so a focus state written in there
   would leave them with the browser's default ring and no fill at all. */
.hpx .hpx-chip:focus-visible {
    background-image: var(--sc-fill);
    background-color: var(--hpx-brand-600);
    border-color: transparent;
    color: #fff;
    box-shadow: 0 10px 22px -8px var(--hpx-brand-glow);
    outline: 2px solid var(--hpx-brand);
    outline-offset: 2px;
}

.hpx-chip:focus-visible::after { animation: hpx-sweep .85s var(--hpx-a-ease); }

@media (prefers-reduced-motion: reduce) {
    .hpx .hpx-chip { transition: background-color .2s ease, color .2s ease; }
    .hpx .hpx-chip:hover, .hpx .hpx-chip:active { transform: none; }
    .hpx-chip:hover::after, .hpx-chip:focus-visible::after { animation: none; }
}

.hpx .hpx-chips-nav {
    display: none;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 34px; height: 34px;
    padding: 0;
    background-color: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: 50%;
    color: var(--hpx-ink-2);
    cursor: pointer;
    /* Lifted off the track at rest, like the chips beside it — the arrows sit
       on the same recessed surface and would read as holes in it otherwise. */
    box-shadow: 0 1px 2px rgba(16, 24, 40, .06);
    transition: background-color .2s ease, background-image .2s ease, border-color .2s ease,
                color .2s ease, transform .2s ease, opacity .2s ease, box-shadow .2s ease;
}

body.theme-dark .hpx .hpx-chips-nav { box-shadow: 0 1px 2px rgba(0, 0, 0, .45); }

/* The kit mirrors the rail's scroll state onto this wrapper, so an arrow is
   painted only when there is genuinely something off-screen to reach. Driven
   by an attribute rather than `:has()` on purpose — `:has()` is missing on
   Safari below 15.4 and Firefox below 121, and those browsers would lose the
   control outright instead of degrading. */
.hpx-chipsrow[data-scrollable="1"] .hpx-chips-nav { display: inline-flex; }

/* Each arrow retires at its own end of the rail. Dimmed and inert rather than
   removed: a control that disappears reflows the row by 34px on every scroll,
   and the chips would shuffle sideways under the pointer. */
.hpx-chipsrow[data-at-start="1"] .hpx-chips-prev,
.hpx-chipsrow[data-at-end="1"]   .hpx-chips-next { opacity: .3; pointer-events: none; }

.hpx-chips-nav svg { width: 15px; height: 15px; }
[dir="rtl"] .hpx-chips-nav svg { transform: scaleX(-1); }

@media (hover: hover) {
    /* Filled on hover, not tinted: these two are the only controls in the row
       and a tint on a card-coloured pill barely registers against the chips
       sitting beside it, which are card-coloured too. */
    .hpx .hpx-chips-nav:hover {
        background-image: var(--sc-fill);
        border-color: transparent;
        color: #fff;
        transform: scale(1.06);
        box-shadow: 0 8px 18px -8px var(--hpx-brand-glow);
    }
}

.hpx .hpx-chips-nav:active { transform: scale(.94); }

/* Hero orbit ---------------------------------------------------------------
   An absolute overlay spanning the whole hero, BEHIND the centred copy
   (z-index 1 here vs 2 there) so the two never compete for space: tiles
   scattered through the margins the centred column doesn't use, strung along
   two mirrored dashed arcs.

   Two-layer tiles: the OUTER <a> owns position, chrome and the hover
   transition; an INNER span owns the ambient float/parallax transform. Two
   elements fighting over `transform` on the SAME element is how a hover lift
   stops registering the moment the ambient motion animates it every frame —
   split, they compose instead of colliding. ------------------------------- */

.hpx-hero-orbit,
.hpx-hero-tiles {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    --hpx-px: 0px;
    --hpx-py: 0px;
}

/* Only the tiles take clicks — the arcs, dots and glow stay click-through so
   a visitor can never land an inert click on a decoration. */
.hpx-hero-tiles .hpx-stack-tile { pointer-events: auto; }

.hpx-orbit-arc {
    position: absolute;
    top: 2%;
    width: clamp(170px, 23%, 300px);
    height: 90%;
    color: var(--hpx-brand-line);
    opacity: .95;
}

.hpx-orbit-arc-l { left: 0; }
.hpx-orbit-arc-r { right: 0; }

.hpx-orbit-dot {
    position: absolute;
    width: 7px; height: 7px;
    border-radius: 50%;
    background: var(--hpx-brand);
    opacity: .5;
    animation: hpx-dot-pulse 4.5s ease-in-out infinite;
}

.hpx-orbit-d1 { left: 12%;  top: 10%;  animation-delay: -.2s; }
.hpx-orbit-d2 { left: 2.5%; top: 40%;  animation-delay: -1.1s; }
.hpx-orbit-d3 { left: 13%;  top: 78%;  animation-delay: -2.3s; }
.hpx-orbit-d4 { right: 12%;  top: 10%; animation-delay: -.7s; }
.hpx-orbit-d5 { right: 2.5%; top: 40%; animation-delay: -1.8s; }
.hpx-orbit-d6 { right: 13%;  top: 78%; animation-delay: -3s; }

.hpx-stack-glow {
    position: absolute;
    left: 50%; top: 50%;
    width: 42%; aspect-ratio: 1;
    transform: translate(-50%, -50%);
    background: radial-gradient(circle, var(--hpx-brand-soft) 0%, transparent 68%);
    border-radius: 50%;
    animation: hpx-glow-drift 9s ease-in-out infinite;
    pointer-events: none;
}

.hpx-stack-glow-2 {
    left: 70%; top: 24%;
    width: 26%;
    background: radial-gradient(circle, var(--hpx-t-violet-bg, #F0EBFE) 0%, transparent 70%);
    opacity: .85;
    animation-duration: 12s;
    animation-delay: -4s;
}

.hpx .hpx-stack-tile {
    position: absolute;
    display: block;
    color: var(--hpx-ink);
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: 22px;
    box-shadow: 0 2px 6px rgba(16, 24, 40, .05), 0 18px 40px -14px rgba(16, 24, 40, .22);
    z-index: 2;
    transition: box-shadow .3s ease, border-color .3s ease, transform .3s var(--hpx-a-ease);
}

body.theme-dark .hpx .hpx-stack-tile {
    box-shadow: 0 2px 6px rgba(0, 0, 0, .46), 0 18px 44px rgba(0, 0, 0, .5);
}

/* Logo tiles: a square white card carrying just the mark, the size the
   reference uses. Outer pair large, inner pair a step down — that size
   difference is what reads as depth rather than as a grid. */
.hpx-orbit-logos .hpx-stack-tile {
    width: var(--hpx-hero-tile);
    height: var(--hpx-hero-tile);
}

.hpx-orbit-logos .hpx-stack-t3,
.hpx-orbit-logos .hpx-stack-t4,
.hpx-orbit-logos .hpx-stack-t5,
.hpx-orbit-logos .hpx-stack-t6 {
    width: var(--hpx-hero-tile-sm);
    height: var(--hpx-hero-tile-sm);
    border-radius: 18px;
}

.hpx-orbit-logos .hpx-stack-tile-float {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%; height: 100%;
    animation: hpx-float 7s ease-in-out infinite;
    animation-delay: calc(var(--hpx-i) * -1.15s);
}

.hpx-orbit-logos .hpx-ico {
    width: 100%; height: 100%;
    font-size: 30px;
    color: var(--tone-ink);
    background: transparent;
}

.hpx-orbit-logos .hpx-stack-t3 .hpx-ico,
.hpx-orbit-logos .hpx-stack-t4 .hpx-ico,
.hpx-orbit-logos .hpx-stack-t5 .hpx-ico,
.hpx-orbit-logos .hpx-stack-t6 .hpx-ico { font-size: 25px; }

.hpx-orbit-logos .hpx-ico svg,
.hpx-orbit-logos .hpx-ico img { width: 52%; height: 52%; }

/* Bare marks — no card, no ring, no plate.
   The tile used to be a white rounded face with a border and a shadow, and the
   logo sat inside it as a second rounded shape. Two nested boxes around one
   mark: the artwork already carries its own silhouette, so the chrome only
   competed with it. Everything below strips the card and hands the whole tile
   box to the image.
   Scoped to the logos orbit — the `stack` visual style is still a real card
   with a number and a label in it, and must keep its face. */
.hpx .hpx-orbit-logos .hpx-stack-tile {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

body.theme-dark .hpx .hpx-orbit-logos .hpx-stack-tile { box-shadow: none; }

/* The brand ring was drawn on the card's edge; with no card there is no edge
   to ring. The featured mark now reads as featured by being the largest. */
.hpx-orbit-logos .hpx-stack-tile.is-featured::before { display: none; }

.hpx-orbit-logos .hpx-ico img {
    width: 100%; height: 100%;
    box-shadow: none;
    transition: transform .28s var(--hpx-a-ease), filter .28s ease;
}

/* The label a logo tile has no room to print. Pointer events off so it can
   never eat the tile's own click. */
/* The tile caption — always visible, on every device.
   It used to be a hover-only pill, which meant the mark was unlabelled on
   every touch device (there is no hover to reveal it) and the rule that hid
   it below 1180px was the honest admission of that. A logo nobody recognises
   with no name under it is a dead tile, so the name is now part of the
   design rather than a reward for hovering.
   Two lines: the model, then who makes it. Pointer events off so it can never
   eat the tile's own click. */
.hpx-stack-tip {
    position: absolute;
    left: 50%;
    top: calc(100% + 10px);
    z-index: 3;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    white-space: nowrap;
    text-align: center;
    font-size: 12px;
    font-weight: 650;
    letter-spacing: -.01em;
    color: var(--hpx-ink);
    transform: translateX(-50%);
    pointer-events: none;
    transition: color .22s ease, transform .22s ease;
}

.hpx-stack-tip b {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--hpx-ink-3);
    transition: color .22s ease;
}

/* Stack tiles: the older number+label treatment, still selectable from the
   band editor for anyone who prefers counters to marks. */
.hpx-orbit-stack .hpx-stack-tile { width: 152px; overflow: hidden; border-radius: var(--hpx-r-lg); }

.hpx-orbit-stack .hpx-stack-tile-float {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 13px;
    animation: hpx-float 7s ease-in-out infinite;
    animation-delay: calc(var(--hpx-i) * -1.15s);
}

.hpx-orbit-stack .hpx-ico[data-tone] {
    background: linear-gradient(135deg, var(--tone-bg) 0%, var(--tone-line) 145%);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5);
}

.hpx-stack-tile-num { font-size: 17px; font-weight: 800; letter-spacing: -.02em; color: var(--hpx-ink); }
.hpx-stack-tile-lbl { font-size: 11.5px; color: var(--hpx-ink-3); line-height: 1.35; }

/* The featured tile gets a brand-gradient ring instead of the plain border
   every other tile has — the same masked-gradient trick the search bar's
   focus ring uses, so "this one's special" speaks the same visual language
   as the rest of the band rather than inventing a new one. */
.hpx-stack-tile.is-featured::before {
    content: '';
    position: absolute;
    inset: -1.5px;
    z-index: -1;
    border-radius: inherit;
    padding: 1.5px;
    background: linear-gradient(120deg, var(--hpx-brand), var(--hpx-brand-600));
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
}

/* Positions. Percentages of the hero box, so the tiles keep their relation
   to the headline at every width instead of being pinned to a pixel column. */
.hpx-stack-t1 { left: 2%;   top: 9%; }
.hpx-stack-t2 { right: 2%;  top: 12%; }
.hpx-stack-t3 { left: 7.5%; top: 40%; }
.hpx-stack-t4 { right: 7%;  top: 38%; }
.hpx-stack-t5 { left: 1%;   top: 71%; }
.hpx-stack-t6 { right: 1%;  top: 69%; }

@media (hover: hover) {
    .hpx .hpx-stack-tile:hover {
        transform: translateY(-6px) scale(1.04);
        border-color: var(--tone-line);
        box-shadow: 0 26px 48px -16px var(--tone-line, rgba(16, 24, 40, .22));
    }

    .hpx .hpx-stack-tile:hover .hpx-ico { transform: scale(1.1) rotate(-4deg); }
    .hpx .hpx-stack-tile.is-featured:hover { box-shadow: 0 26px 48px -16px var(--hpx-brand-glow); }

    /* Logos orbit: the card is gone, so the card's hover has to go with it —
       a box-shadow on a transparent element would paint the very rectangle
       that was just removed. The mark takes the whole interaction: it lifts,
       it grows, and its shadow is a `drop-shadow` filter so it follows the
       logo's own alpha instead of boxing it. */
    .hpx .hpx-orbit-logos .hpx-stack-tile:hover {
        transform: none;
        border-color: transparent;
        box-shadow: none;
    }

    .hpx .hpx-orbit-logos .hpx-stack-tile:hover .hpx-ico {
        transform: translateY(-5px) scale(1.13);
    }

    .hpx-orbit-logos .hpx-stack-tile:hover .hpx-ico img {
        filter: drop-shadow(0 12px 20px rgba(16, 24, 40, .30));
    }

    body.theme-dark .hpx .hpx-orbit-logos .hpx-stack-tile:hover .hpx-ico img {
        filter: drop-shadow(0 12px 22px rgba(0, 0, 0, .62));
    }

    /* The caption is already on screen, so hover promotes it to brand rather
       than revealing it — the affordance without the information penalty. */
    .hpx-stack-tile:hover .hpx-stack-tip   { color: var(--hpx-brand-ink); transform: translateX(-50%) translateY(2px); }
    .hpx-stack-tile:hover .hpx-stack-tip b { color: var(--hpx-brand); }
}

/* Keyboard parity: the same promotion the mouse gets, plus a real indicator on
   the tile itself — it is a link, and it was previously indistinguishable when
   focused. */
.hpx .hpx-stack-tile:focus-visible {
    outline: none;
    border-color: var(--hpx-brand);
    box-shadow: 0 0 0 3px var(--hpx-brand-glow), 0 26px 48px -16px var(--hpx-brand-glow);
}

/* On a bare mark there is no card to ring, and a shadow would redraw the box
   the design just dropped. An outline appears only for keyboard users, so it
   costs the visual nothing while still being a real focus indicator. */
.hpx .hpx-orbit-logos .hpx-stack-tile:focus-visible {
    outline: 2px solid var(--hpx-brand);
    outline-offset: 4px;
    border-radius: 12px;
    box-shadow: none;
}

.hpx-stack-tile:focus-visible .hpx-stack-tip   { color: var(--hpx-brand-ink); }
.hpx-stack-tile:focus-visible .hpx-stack-tip b { color: var(--hpx-brand); }

/* Hero counter card --------------------------------------------------------
   The reference puts the numbers INSIDE the hero, on one card, hairline-
   divided. Rows come from hp_stats attached to this band, so the set is
   editable on the Stats screen exactly like every other counter strip. ---- */

/* The deck. A brand hairline along the top edge and a brand-tinted lift below
   it, so the card reads as the hero's own furniture rather than as a panel that
   wandered in.

   `overflow: visible`, deliberately. The old card scrolled horizontally
   (`overflow-x: auto`), and a non-visible overflow-x forces overflow-y to auto
   as well — which clipped the very lift and ring the tiles use to answer the
   pointer, and could raise a scrollbar under a row of numbers. Nothing needs to
   scroll now: the grid reflows to 3 and then 2 columns instead. */
.hpx-herostats {
    position: relative;
    z-index: 2;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 2px;
    width: 100%;
    max-width: 1120px;
    margin-top: 46px;
    padding: 12px;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: 26px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 26px 60px -30px var(--hpx-brand-glow),
        0 22px 50px -26px rgba(16, 24, 40, .26);
    overflow: visible;
}

/* The lit top edge. Inset from the corners so it reads as a highlight along the
   card rather than as a border that failed to reach them. */
.hpx-herostats::before {
    content: '';
    position: absolute;
    z-index: 1;
    inset-block-start: -1px;
    inset-inline: 14%;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: linear-gradient(90deg,
        transparent 0%, var(--hpx-brand) 32%, var(--hpx-brand-600) 68%, transparent 100%);
    opacity: .5;
    pointer-events: none;
}

body.theme-dark .hpx .hpx-herostats {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .05),
        0 2px 6px rgba(0, 0, 0, .46),
        0 26px 60px -30px var(--hpx-brand-glow),
        0 22px 50px -26px rgba(0, 0, 0, .6);
}

body.theme-dark .hpx .hpx-herostats::before { opacity: .7; }

/* ── One counter ────────────────────────────────────────────────── */

/* `isolation: isolate` gives the tile its own stacking context so the wash and
   the sweep stay under the figure without either needing a z-index race with
   the card. `overflow: hidden` is what clips both — and the kit's press ripple,
   which is appended into this element and until now escaped it. */
.hpx .hpx-hstat {
    position: relative;
    isolation: isolate;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 13px;
    padding: 13px 12px;
    color: var(--hpx-ink);
    border-radius: 17px;
    overflow: hidden;
    transition:
        transform .3s var(--hpx-a-ease),
        box-shadow .3s ease,
        background-color .24s ease;
}

/* The column hairline, on a pseudo rather than on `box-shadow`.
   It used to be `inset 1px 0 0` on the tile itself, which meant every later
   treatment — the tablet tile ring, the phone tile outline, the hover ring —
   had to fight it for the one box-shadow slot at ever-higher specificity, and
   the file carried a warning about exactly that. Moving it here frees
   box-shadow entirely for elevation, which is what it is for.

   Faded at both ends: a hairline that runs the full height of a tile cuts the
   card into six boxes, and the point is a set of six readings. */
.hpx-hstat::before {
    content: '';
    position: absolute;
    z-index: 0;
    inset-block: 24%;
    inset-inline-start: -1px;
    width: 1px;
    background: linear-gradient(180deg,
        transparent 0%, var(--hpx-line-strong) 50%, transparent 100%);
    transition: opacity .24s ease;
    pointer-events: none;
}

.hpx-herostats > :first-child::before { opacity: 0; }

/* The tone wash and the bar along the bottom edge, hidden at rest. One pseudo
   carries both: the wash is the background, the bar is an inset shadow that
   grows from nothing, so they arrive and leave together. */
.hpx-hstat::after {
    content: '';
    position: absolute;
    z-index: 0;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(180deg, var(--tone-bg) 0%, transparent 78%);
    box-shadow: inset 0 0 0 var(--tone-ink);
    opacity: 0;
    transition: opacity .28s ease, box-shadow .34s var(--hpx-a-ease);
    pointer-events: none;
}

/* The sweep. Parked off the leading edge and invisible until the pointer
   arrives; the keyframe runs it across and fades it out at the far side. */
.hpx-hstat-fx {
    position: absolute;
    z-index: 0;
    inset: 0;
    border-radius: inherit;
    overflow: hidden;
    pointer-events: none;
}

.hpx-hstat-fx::before {
    content: '';
    position: absolute;
    inset-block: -40%;
    inset-inline-start: -55%;
    width: 42%;
    background: linear-gradient(100deg,
        transparent 0%, rgba(255, 255, 255, .7) 50%, transparent 100%);
    transform: skewX(-18deg);
    opacity: 0;
}

/* White at .7 is a flashbulb on a near-black card. */
body.theme-dark .hpx .hpx-hstat-fx::before {
    background: linear-gradient(100deg,
        transparent 0%, rgba(255, 255, 255, .13) 50%, transparent 100%);
}

/* Shared by the hero counter tiles and the popular-search chips. One
   keyframe, so the two never drift apart in speed or distance. */
@keyframes hpx-sweep {
    0%   { opacity: 0; transform: translateX(0) skewX(-18deg); }
    16%  { opacity: 1; }
    100% { opacity: 0; transform: translateX(430%) skewX(-18deg); }
}

/* ── The plate ──────────────────────────────────────────────────── */

/* A SOLID tone fill with a white glyph, the house plate — the same one the
   library, category, icongrid, panel, newsroom and trust bands carry. */
.hpx-hstat .hpx-ico {
    position: relative;
    z-index: 2;
    width: 46px; height: 46px;
    font-size: 19px;
    border-radius: 13px;
    background-color: var(--tone-ink);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .26) 100%);
    color: var(--hpx-ink-invert);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        0 6px 14px -8px var(--tone-ink);
    transition: transform .3s var(--hpx-a-ease), box-shadow .3s ease;
}

.hpx-hstat .hpx-ico svg { width: 47%; height: 47%; }

/* Deepens the lightened dark twins so a white glyph clears AA. */
body.theme-dark .hpx .hpx-hstat .hpx-ico {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
        linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 6px 14px -8px rgba(0, 0, 0, .8);
}

/* ── The figure ─────────────────────────────────────────────────── */

.hpx-hstat-text {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    text-align: start;
    min-width: 0;
}

/* Fluid rather than fixed. Six figures across a 1120px card have ~150px each at
   the narrow end of this layout, and a fixed 20px "50,000+" needed more than
   that — which is why the card used to scroll. The clamp lands 16px where it is
   tight and 20px where there is room, so nothing is ever cut off to fit.

   tabular-nums for two reasons: the six figures sit on one rhythm instead of
   drifting by the width of a 1, and the count-up animation rewrites this text
   frame by frame — proportional digits make the tile twitch as it counts. */
.hpx-hstat-num {
    font-size: clamp(16px, 1.35vw, 20px);
    font-weight: 820;
    letter-spacing: -.025em;
    line-height: 1.15;
    color: var(--hpx-ink);
    font-variant-numeric: tabular-nums;
    transition: color .24s ease;
}

/* No nowrap and no ellipsis anywhere in this tile: the label is admin-editable
   and translated, so a longer string is one edit or one locale away. It wraps
   and its row grows — the grid stretches the neighbouring cells to match, so a
   wrap never leaves a ragged row and no label is ever clipped. */
.hpx-hstat-lbl {
    font-size: clamp(11.5px, .92vw, 13px);
    font-weight: 560;
    line-height: 1.35;
    color: var(--hpx-ink-3);
    text-wrap: balance;
    transition: color .24s ease;
}

.hpx-hstat-sub { font-size: 11.5px; line-height: 1.4; color: var(--hpx-ink-faint); }

/* ── Hover ──────────────────────────────────────────────────────── */

@media (hover: hover) {
    /* Every cell answers the pointer. Only two of the six carry a URL, and a
       highlight that four of them cannot do reads as a broken row rather than
       as a considered distinction. */
    .hpx-hstat:hover::after { opacity: 1; box-shadow: inset 0 -3px 0 var(--tone-ink); }
    .hpx-hstat:hover::before,
    .hpx-hstat:hover + .hpx-hstat::before { opacity: 0; }
    .hpx-hstat:hover .hpx-hstat-fx::before { animation: hpx-sweep .9s var(--hpx-a-ease); }

    .hpx-hstat:hover .hpx-ico {
        transform: scale(1.09) rotate(-5deg);
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .24),
            0 12px 22px -10px var(--tone-ink);
    }

    /* (0,5,0). The dark RESTING plate is declared at (0,4,0) — a hover shadow
       written at (0,3,0) like the one above loses to it outright, so at night
       the plate scaled under the pointer and its glow stayed put. */
    body.theme-dark .hpx .hpx-hstat:hover .hpx-ico {
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .14),
            0 12px 22px -10px rgba(0, 0, 0, .9);
    }

    /* Both stay on ink tokens, never on the tone's pale background twin — the
       figure is the content and it does not get to lose contrast to a hover. */
    .hpx .hpx-hstat:hover .hpx-hstat-num { color: var(--tone-ink); }
    .hpx .hpx-hstat:hover .hpx-hstat-lbl { color: var(--hpx-ink-2); }

    /* The LIFT and the ring are the click affordance, so they belong only to
       the cells that actually go somewhere. A plain figure that rose under the
       pointer would be promising a click it cannot honour. */
    .hpx a.hpx-hstat:hover {
        transform: translateY(-3px);
        box-shadow:
            inset 0 0 0 1px var(--tone-line),
            0 18px 30px -20px var(--tone-ink);
    }

    body.theme-dark .hpx a.hpx-hstat:hover {
        box-shadow:
            inset 0 0 0 1px var(--tone-line),
            0 18px 30px -20px rgba(0, 0, 0, .9);
    }
}

/* Keyboard parity — a linked cell reached by Tab shows what a pointer would
   show. The brand focus ring comes from the global `:focus-visible` rule and is
   drawn OUTSIDE the border box, so the tile's own `overflow: hidden` cannot
   clip it and the card's `overflow: visible` lets it through. */
.hpx a.hpx-hstat:focus-visible::after { opacity: 1; box-shadow: inset 0 -3px 0 var(--tone-ink); }
.hpx a.hpx-hstat:active { transform: translateY(-1px) scale(.985); }

@media (prefers-reduced-motion: reduce) {
    .hpx .hpx-hstat, .hpx-hstat::after, .hpx-hstat::before,
    .hpx-hstat .hpx-ico, .hpx-hstat-num, .hpx-hstat-lbl { transition: none; }

    .hpx a.hpx-hstat:hover, .hpx a.hpx-hstat:active { transform: none; }
    .hpx-hstat:hover .hpx-ico { transform: none; }
    .hpx-hstat:hover .hpx-hstat-fx::before { animation: none; }
}

/* Keyframes ---------------------------------------------------------------- */

@keyframes hpx-float {
    0%, 100% { transform: translate(var(--hpx-px, 0px), var(--hpx-py, 0px)); }
    50%      { transform: translate(var(--hpx-px, 0px), calc(var(--hpx-py, 0px) - 9px)); }
}

@keyframes hpx-glow-drift {
    0%, 100% { transform: translate(-50%, -50%) scale(1); }
    50%      { transform: translate(-50%, -50%) scale(1.08); }
}

@keyframes hpx-dot-pulse {
    0%, 100% { opacity: .28; transform: scale(.82); }
    50%      { opacity: .72; transform: scale(1.12); }
}

@keyframes hpx-twinkle {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: .55; transform: scale(.85); }
}

@keyframes hpx-voice-pulse {
    0%   { opacity: .55; transform: scale(.85); }
    100% { opacity: 0; transform: scale(1.55); }
}

@keyframes hpx-spin { to { transform: rotate(360deg); } }
@keyframes hpx-ripple-in { to { transform: scale(1); opacity: 0; } }


/* ---------------------------------------------------------------------------
   10. Stat strip
   ------------------------------------------------------------------------ */

.hpx-statbar {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 4px;
    padding: 16px 8px;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r);
    box-shadow: var(--hpx-sh-soft);
}

.hpx-stat {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 6px 14px;
    min-width: 0;
    transition: transform .2s ease;
}

.hpx-statbar.has-divider .hpx-stat + .hpx-stat { border-left: 1px solid var(--hpx-line-soft); }

.hpx-stat-text { min-width: 0; display: flex; flex-direction: column; }

.hpx-stat-num {
    font-size: var(--hpx-fs-stat-value, 23px);
    font-weight: var(--hpx-fw-stat-value, 800);
    letter-spacing: var(--hpx-ls-stat-value, -.02em);
    line-height: 1.12;
    color: var(--hpx-ink);
}

.hpx-stat-lbl {
    font-size: var(--hpx-fs-stat-label, 12.5px);
    font-weight: var(--hpx-fw-stat-label, 550);
    color: var(--hpx-ink-3);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.hpx-stat-sub { font-size: 11px; color: var(--hpx-ink-3); opacity: .8; }

@media (hover: hover) {
    a.hpx-stat:hover { transform: translateY(-2px); }
    a.hpx-stat:hover .hpx-ico { transform: scale(1.07); }
}

/* ---------------------------------------------------------------------------
   11. Category tiles — rail and grid
   The same tile in both layouts; only the container differs.

   The rail's controls are its own subsystem: nudge buttons, edge fades and a
   progress bar, all driven by the three data attributes the kit mirrors onto
   [data-hpx-railwrap] (data-scrollable / -at-start / -at-end). Nothing here
   needs JS to EXIST — JS only reports where the scroller is.

   `.hpx button { color: inherit }` and `.hpx a { color: inherit }` are both
   (0,1,1) and out-rank any single-class colour whatever the source order, so
   every coloured control below is written `.hpx .hpx-x` — (0,2,0). The nudge
   buttons are exactly the case that produces the "the arrow is the wrong
   colour / the button is off-brand" report.
   ------------------------------------------------------------------------ */

/* ── Containers ─────────────────────────────────────────────────── */

/* How far a rail's scroll box is allowed to reach OUTSIDE the band's gutter,
   so a hovered card's glow has somewhere to land. A rail is an `overflow-x:
   auto` box, which clips on BOTH axes, and its cards lift and cast a saturated
   tone shadow well past their own edges — with the 2px of side padding the
   rails used to carry, that shadow was sliced off flat down the first and last
   card and along the bottom of every one of them.

   The value is paid for out of `.hpx-wrap`'s own padding via a matching
   negative margin, so card alignment does not move. That padding is NOT a page
   gutter — see the warning on `.hpx-wrap`, it is the band card's inner inset —
   so the bleed has to stay a few px inside it or the rail runs into the band's
   own border. It therefore steps down with that padding (24/20/16/13) rather
   than being one fixed number, and every step keeps 4px in hand. */
.hpx-railwrap { position: relative; --hpx-rail-bleed: 20px; }

.hpx-catrail {
    display: grid;
    grid-auto-flow: column;
    /* 196px, not 152. The tile now carries three lines of copy under its title
       and at the old width every one of them ellipsised — "Conversational
       assistants for writing, research…" is a description that stops before it
       has described anything. */
    /* Whole tiles only — see the note on `.hpx-lib`. */
    --hpx-rail-cols: 7;
    --hpx-rail-gap: 14px;
    grid-auto-columns: calc(
        (100% - (var(--hpx-rail-cols) - 1) * var(--hpx-rail-gap)) / var(--hpx-rail-cols)
    );
    gap: var(--hpx-rail-gap);
    /* Room for the lift and the glow, taken back out of the gutter so the
       first tile still lines up with the heading above it. The tile rises 5px
       and drops a shadow ~19px past its own bottom edge; the old 12px cut it. */
    margin-inline: calc(var(--hpx-rail-bleed) * -1);
    padding: 10px var(--hpx-rail-bleed) 30px;
    overflow-x: auto;
    scroll-padding-inline: var(--hpx-rail-bleed);

    /* NOT `scroll-behavior: smooth`. The kit's nudge handler already asks for
       a smooth scroll explicitly (`scrollTo({behavior:'smooth'})`), and its
       safety net — assigning `scrollLeft` when the animation did not take —
       is silently converted into ANOTHER smooth scroll by this property, so
       the one line that exists to rescue a failed scroll cannot. Declaring it
       here buys nothing and disarms the fallback. */
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;

    /* Edge fades as a MASK, not as a gradient overlay in the band colour: the
       band's surface is admin-selectable (plain / tint / card / gradient), and
       an overlay painted in the wrong one leaves a pale smear over the tiles.
       A mask fades to transparent, so it is right on every surface. The two
       widths are 0 until the kit says the rail can move in that direction. */
    --hpx-fade-s: 0px;
    --hpx-fade-e: 0px;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--hpx-fade-s), #000 calc(100% - var(--hpx-fade-e)), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--hpx-fade-s), #000 calc(100% - var(--hpx-fade-e)), transparent 100%);
    transition: -webkit-mask-image .2s linear, mask-image .2s linear;
}

.hpx-catrail::-webkit-scrollbar { display: none; }

[data-hpx-railwrap][data-scrollable="1"][data-at-start="0"] .hpx-catrail { --hpx-fade-s: 52px; }
[data-hpx-railwrap][data-scrollable="1"][data-at-end="0"]   .hpx-catrail { --hpx-fade-e: 52px; }

.hpx-catgrid {
    display: grid;
    grid-template-columns: repeat(var(--hpx-cd), minmax(0, 1fr));
    gap: var(--hpx-g);
}

/* ── The tile ───────────────────────────────────────────────────── */

/* Built to the supplied reference: haloed icon plate, a star on the featured
   tile, name, a line of copy, a tinted count pill, a circular action button and
   a tone bar along the bottom edge.

   The count and the description are separate rows now. They used to share one
   slot in the partial — count won, subtitle was the fallback — so a tile could
   say what it is OR how much is in it, never both. */

.hpx-cat {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
    padding: 26px 16px 22px;
    overflow: hidden;
    scroll-snap-align: start;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: 20px;
    text-align: center;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 10px 22px -18px rgba(16, 24, 40, .28);
    transition: transform .24s var(--hpx-a-ease), border-color .2s ease, box-shadow .24s ease;
}

body.theme-dark .hpx .hpx-cat {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .06),
        0 1px 2px rgba(0, 0, 0, .38),
        0 10px 22px -18px rgba(0, 0, 0, .7);
}

/* Resting tone halo behind the icon — the one piece of colour a tile has at
   rest, and what makes a row read as a designed set rather than as outlines. */
.hpx-cat::before {
    content: '';
    position: absolute;
    top: -16px;
    left: 50%;
    z-index: 0;
    width: 116px; height: 116px;
    border-radius: 50%;
    background: radial-gradient(circle, var(--tone-bg) 0%, transparent 70%);
    transform: translateX(-50%);
    opacity: .9;
    transition: opacity .26s ease, transform .3s var(--hpx-a-ease);
    pointer-events: none;
}

/* Hover wash, hidden at rest: the tile already has a surface, so a resting
   tint on top of it would muddy the card rather than identify it. */
.hpx-cat::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    border-radius: inherit;
    background: linear-gradient(180deg, var(--tone-bg) 0%, transparent 72%);
    opacity: 0;
    transition: opacity .28s ease;
    pointer-events: none;
}

/* The corner controls are absolutely positioned; `position: relative` here
   would drop them into the flex column. `:where()` keeps this at (0,1,0) so it
   cannot out-rank the rules that style them — the same trap that parked the
   showcase card's rank chip above its logo. */
.hpx-cat > *:not(:where(.hpx-cat-star, .hpx-cat-bar)) { position: relative; z-index: 1; }

/* ── The star, on the featured tile ─────────────────────────────── */

.hpx .hpx-cat-star {
    position: absolute;
    inset-block-start: 14px;
    inset-inline-end: 14px;
    z-index: 2;
    display: grid;
    place-items: center;
    width: 26px; height: 26px;
    border-radius: 50%;
    background-color: var(--hpx-brand-600);
    background-image: linear-gradient(155deg, rgba(255, 255, 255, .28) 0%, rgba(255, 255, 255, 0) 62%);
    color: #fff;
    box-shadow: 0 6px 14px -6px var(--hpx-brand-glow);
}

.hpx-cat-star svg { width: 13px; height: 13px; }

/* ── The icon plate ─────────────────────────────────────────────── */

.hpx-cat-ico { display: block; margin-bottom: 16px; }

/* A SOLID tone fill with a white glyph — the same plate the library band uses
   (`.hpx-libcol-head .hpx-ico`), not the soft tinted chip this tile used to
   carry. The two rails sit a screen apart on the homepage and read as one set
   only if their icons are drawn the same way; a tinted plate next to a filled
   one looks like two different designs, which is exactly how it read.

   The sheen supplies the second gradient stop, so no per-tone "light twin"
   token has to exist. Geometry (66px / 26px glyph) is unchanged — only the
   FILL changes — so the tile's rhythm and every breakpoint override below
   still hold. */
.hpx-cat .hpx-ico {
    width: 66px; height: 66px;
    font-size: 26px;
    border-radius: 19px;
    background-color: var(--tone-ink);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .26) 100%);
    color: var(--hpx-ink-invert);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        0 10px 22px -10px var(--tone-ink);
    transition: transform .26s var(--hpx-a-ease), box-shadow .26s ease;
}

/* The base chip draws its glyph at 55% of the plate, a ratio tuned for a
   tinted background where a large silhouette still reads as light. On a solid
   fill that is a white slab; the library plate's 22/46 is the proportion the
   reference actually uses, expressed as a percentage so the 56px and 42px
   breakpoint variants scale with it instead of needing their own overrides. */
.hpx-cat .hpx-ico svg { width: 47%; height: 47%; }

/* The dark twins of `--tone-ink` are LIGHTENED so they can serve as ink on a
   dark surface, which is what makes them a poor fill under a white glyph —
   white on #8CBEFA measures 2.4:1. A scrim deepens the fill without touching
   the token, and takes every tone past 4.5:1. Same fix as the library band. */
body.theme-dark .hpx .hpx-cat .hpx-ico {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
        linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 10px 22px -10px rgba(0, 0, 0, .8);
}

/* ── Copy ───────────────────────────────────────────────────────── */

.hpx .hpx-cat-title {
    display: block;
    font-size: 16px;
    font-weight: 740;
    line-height: 1.3;
    letter-spacing: -.012em;
    color: var(--hpx-ink);
    transition: color .2s ease;
}

/* Clamped in CSS, not by a character count: the tiles are rail tracks and
   their width changes with the viewport, so the only cut that lands in the
   right place is the one the browser makes. Three lines are reserved whether
   or not the copy fills them, which is what keeps the count pills and the
   buttons below on one line across the row. */
.hpx .hpx-cat-sub {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 9px;
    min-height: calc(3 * 1.55em);
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--hpx-ink-2);
}

/* The count is evidence, so it gets a pill rather than a line of grey text —
   and the pill is the tone, which ties it to the icon above it.

   `margin-top: auto` and NOT a floor on the title: the tiles are equal-height
   tracks, so pinning the pill down aligns the row whether a title runs to one
   line or two. A min-height on the title aligns it too, but leaves a visible
   hole under every short title — which is most of them. */
.hpx .hpx-cat-count {
    display: inline-block;
    margin-top: auto;
    padding: 5px 12px;
    border-radius: var(--hpx-r-pill);
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 12px;
    font-weight: 700;
    line-height: 1.5;
    letter-spacing: -.002em;
    white-space: nowrap;
    box-shadow: inset 0 0 0 1px var(--tone-line);
    transition: background-color .2s ease, color .2s ease, box-shadow .2s ease;
}

/* A tile with no count still needs the pill's slot, or its button sits higher
   than its neighbours' and the row goes ragged. */
.hpx .hpx-cat-count + .hpx-cat-go { margin-top: 16px; }

/* ── The action ─────────────────────────────────────────────────── */

/* A ringed circle at the foot of the tile, visible at REST. It used to be a
   hover-only chip in the top corner, which meant a touch visitor — who has no
   hover — got no affordance at all that the tile went anywhere. */
.hpx .hpx-cat-go {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px; height: 40px;
    margin-top: auto;
    border-radius: 50%;
    background: transparent;
    color: var(--tone-ink);
    box-shadow: inset 0 0 0 1.5px var(--tone-line);
    transition: background-color .22s ease, background-image .22s ease, color .22s ease,
                box-shadow .22s ease, transform .26s var(--hpx-a-ease);
}

.hpx-cat-go svg { width: 15px; height: 15px; transition: transform .22s var(--hpx-a-ease); }
[dir="rtl"] .hpx-cat-go svg { transform: scaleX(-1); }

/* ── The tone bar ───────────────────────────────────────────────── */

.hpx-cat-bar {
    position: absolute;
    inset-block-end: 0;
    inset-inline: 24%;
    z-index: 2;
    height: 4px;
    border-radius: 4px 4px 0 0;
    background: linear-gradient(90deg, var(--tone-bg) 0%, var(--tone-ink) 50%, var(--tone-bg) 100%);
    transition: inset-inline .34s var(--hpx-a-ease), height .24s ease;
    pointer-events: none;
}

/* `featured` on a category row was in the schema and styled nowhere. */
.hpx .hpx-cat.is-featured {
    border-color: var(--hpx-brand-line);
    box-shadow:
        inset 0 0 0 1px var(--hpx-brand-line),
        0 1px 2px rgba(16, 24, 40, .04),
        0 12px 26px -18px var(--hpx-brand-glow);
}

/* ── Hover ──────────────────────────────────────────────────────── */

@media (hover: hover) {
    .hpx-cat:hover {
        transform: translateY(-5px);
        border-color: var(--tone-line);
        box-shadow: 0 24px 42px -26px var(--tone-ink), var(--hpx-sh-lift);
    }

    .hpx-cat:hover::after { opacity: .55; }
    .hpx-cat:hover::before { opacity: 1; transform: translateX(-50%) scale(1.12); }
    .hpx-cat:hover .hpx-ico { transform: scale(1.08) rotate(-5deg); }
    .hpx .hpx-cat:hover .hpx-cat-title { color: var(--tone-ink); }
    .hpx-cat:hover .hpx-cat-bar { inset-inline: 0; height: 5px; }

    /* The button fills with the solid tone. The dark twins of `--tone-ink` are
       LIGHTENED so they can serve as ink on a dark surface, so white on one
       measures about 1.5:1 — hence the scrim, in neutral black rather than the
       blue-black used on large surfaces, which on a 40px circle desaturates the
       hue (amber goes khaki, rose goes mauve). */
    .hpx .hpx-cat:hover .hpx-cat-go {
        background-color: var(--tone-ink);
        background-image: linear-gradient(160deg, rgba(255, 255, 255, .2) 0%, rgba(255, 255, 255, 0) 58%);
        color: var(--hpx-ink-invert);
        box-shadow: 0 10px 20px -8px var(--tone-ink);
    }

    body.theme-dark .hpx .hpx-cat:hover .hpx-cat-go {
        background-image:
            linear-gradient(0deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, .5)),
            linear-gradient(160deg, rgba(255, 255, 255, .2) 0%, rgba(255, 255, 255, 0) 58%);
        box-shadow: 0 10px 20px -10px rgba(0, 0, 0, .9);
    }

    .hpx-cat:hover .hpx-cat-go svg { transform: translateX(2px); }
    [dir="rtl"] .hpx-cat:hover .hpx-cat-go svg { transform: scaleX(-1) translateX(2px); }

    body.theme-dark .hpx .hpx-cat:hover {
        box-shadow: 0 24px 42px -26px rgba(0, 0, 0, .85);
    }
}

/* Keyboard parity — the tile is a link and a keyboard visitor gets the same
   "this one is active" state a pointer visitor does. */
.hpx .hpx-cat:focus-visible {
    outline: none;
    border-color: var(--hpx-brand);
    box-shadow: 0 0 0 3px var(--hpx-brand-line), var(--hpx-sh-lift);
}

.hpx-cat:focus-visible .hpx-cat-bar { inset-inline: 0; height: 5px; }

.hpx .hpx-cat:focus-visible .hpx-cat-go {
    background-color: var(--tone-ink);
    color: var(--hpx-ink-invert);
}

body.theme-dark .hpx .hpx-cat:focus-visible .hpx-cat-go {
    background-image: linear-gradient(0deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, .5));
}

@media (prefers-reduced-motion: reduce) {
    .hpx-cat, .hpx-cat::before, .hpx-cat::after, .hpx-cat .hpx-ico,
    .hpx-cat-go, .hpx-cat-go svg, .hpx-cat-bar { transition: none; }

    .hpx-cat:hover, .hpx .hpx-cat:focus-visible { transform: none; }
    .hpx-cat:hover .hpx-ico { transform: none; }
}

/* ── Nudge buttons ──────────────────────────────────────────────── */

.hpx .hpx-nudge {
    position: absolute;
    top: calc(50% - 7px);
    z-index: 5;
    display: grid;
    place-items: center;
    width: 38px; height: 38px;
    padding: 0;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: 50%;
    color: var(--hpx-brand-ink);
    cursor: pointer;
    box-shadow: var(--hpx-sh-soft);
    /* Hidden by opacity, not by `display`, so the button keeps its box and can
       fade rather than pop — and so the state is one attribute away from
       visible without a re-render. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-50%) scale(.9);
    transition: opacity .2s ease, transform .24s var(--hpx-a-ease),
                background-color .18s ease, color .18s ease, border-color .18s ease;
}

.hpx .hpx-nudge svg { width: 15px; height: 15px; }

.hpx .hpx-nudge.is-prev { inset-inline-start: -17px; }
.hpx .hpx-nudge.is-next { inset-inline-end: -17px; }

[dir="rtl"] .hpx .hpx-nudge svg { transform: scaleX(-1); }

/* Visible only once the kit has measured the rail and found it can move. */
[data-hpx-railwrap][data-scrollable="1"] .hpx-nudge {
    opacity: 1;
    visibility: visible;
    transform: translateY(-50%) scale(1);
}

[data-hpx-railwrap][data-at-start="1"] .hpx-nudge.is-prev,
[data-hpx-railwrap][data-at-end="1"] .hpx-nudge.is-next {
    opacity: 0;
    visibility: hidden;
    transform: translateY(-50%) scale(.9);
}

@media (hover: hover) {
    .hpx .hpx-nudge:hover {
        background: linear-gradient(135deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 100%);
        border-color: transparent;
        color: var(--hpx-ink-invert);
        box-shadow: 0 10px 22px -10px var(--hpx-brand-glow);
    }
}

.hpx .hpx-nudge:focus-visible {
    outline: none;
    border-color: var(--hpx-brand);
    box-shadow: 0 0 0 3px var(--hpx-brand-line);
}

.hpx .hpx-nudge:active { transform: translateY(-50%) scale(.94); }

/* A touch device scrolls; the arrows would only cover the first and last tile.
   `display:none` here rather than opacity — they must not be tabbable either,
   since on touch they are not an affordance at all. */
@media (hover: none) {
    .hpx .hpx-nudge { display: none; }
}

/* ── Progress bar ───────────────────────────────────────────────── */

/* A real control, not a readout. It reports where the rail is AND takes a
   click or a drag to move it, which is what a bar sitting under a scroller
   looks like it should do.

   It stays `aria-hidden`, deliberately. This is a REDUNDANT pointer affordance
   — the nudge buttons either side and the rail's own native scrolling already
   cover keyboard and assistive use — so exposing it would mean either building
   a full ARIA slider with its own key handling, or shipping a focusable
   control that does nothing on Enter. Redundant and hidden is the honest one.

   The margin is negative because the rails above now reserve ~30px of glow
   room inside their own scroll box; without it the bar would sit that far
   adrift of the cards it belongs to. */
.hpx-railbar {
    position: relative;
    height: 8px;
    width: min(260px, 55%);
    margin: -10px auto 0;
    border-radius: var(--hpx-r-pill);
    background: var(--hpx-line);
    box-shadow: inset 0 1px 2px rgba(16, 24, 40, .08);
    opacity: 0;
    /* scaleY, not height: a transform never reflows, and growing the box would
       nudge the whole band down by the difference every time a pointer crossed
       the bar. */
    transition: opacity .22s ease, transform .18s var(--hpx-a-ease), background-color .18s ease;
    /* pan-y, NOT none. `none` claims both axes, so a finger that happens to
       land on the bar — 260px wide, sitting in the middle of the page — could
       not scroll the page vertically at all. `pan-y` leaves the vertical pan
       with the browser and keeps only the horizontal gesture, which is the one
       this control actually wants. */
    touch-action: pan-y;
    cursor: pointer;
    pointer-events: auto;
}

body.theme-dark .hpx .hpx-railbar {
    background: rgba(255, 255, 255, .10);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .5);
}

/* Touch: the arrows are hidden (`@media (hover: none)`), so this is the only
   rail control on screen. It gets wider and taller to be worth aiming at, and
   sits clear of the cards rather than tucked under them. */
@media (max-width: 640px) {
    .hpx-railbar {
        height: 6px;
        width: min(180px, 62%);
        /* +2px, not the desktop -10px. On a phone the rail only pads 12px
           below the cards, so the desktop pull-up put this control's TOUCH
           TARGET 6px INSIDE the cards — a tap on the bottom edge of a card
           grabbed the pager instead of opening the card. */
        margin-top: 2px;
    }

    /* Taller again than it paints — a 6px target is not a target on a phone —
       but 12px, so the top of the target lands just clear of the cards. */
    .hpx-railbar::before { inset: -12px 0; }
}

/* A hit area taller than the 8px the bar paints. An 8px pointer target is a
   miss waiting to happen, and the extra height costs nothing visually. */
.hpx-railbar::before {
    content: '';
    position: absolute;
    inset: -10px 0;
}

[data-hpx-railwrap][data-scrollable="1"] .hpx-railbar { opacity: 1; }

/* Nothing to travel means nothing to grab — a bar that answers the pointer
   while the rail cannot move is a dead control. */
[data-hpx-railwrap]:not([data-scrollable="1"]) .hpx-railbar { pointer-events: none; }

/* Width and offset are the two fractions the kit writes onto the wrapper.
   A percentage inside calc resolves against the containing block's width, and
   a percentage may be multiplied by a unitless number — so the thumb travels
   exactly the track's leftover space.

   NO transition on either. The kit rewrites both on every scroll frame, so an
   eased thumb would permanently chase the rail instead of reporting it. */
.hpx-railbar-in {
    display: block;
    height: 100%;
    width: calc(var(--hpx-rail-v, 1) * 100%);
    margin-inline-start: calc(var(--hpx-rail-p, 0) * (100% - var(--hpx-rail-v, 1) * 100%));
    border-radius: inherit;
    background: linear-gradient(90deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .4),
        0 2px 8px -3px var(--hpx-brand-glow);
    transition: box-shadow .18s ease;
}

.hpx .hpx-railbar.is-dragging { cursor: grabbing; }

@media (hover: hover) {
    .hpx-railbar:hover,
    .hpx-railbar.is-dragging {
        transform: scaleY(1.3);
        background: var(--hpx-brand-line);
    }

    .hpx-railbar:hover .hpx-railbar-in,
    .hpx-railbar.is-dragging .hpx-railbar-in {
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .5),
            0 4px 14px -3px var(--hpx-brand-glow);
    }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-railbar { transition: opacity .22s ease; }
    .hpx-railbar:hover, .hpx-railbar.is-dragging { transform: none; }
}

/* ── The head's "View all" control ──────────────────────────────── */

/* A bare text link beside a heading reads as body copy, which is the "the
   button does not match the branding" report. As a bordered brand pill it
   reads as the control it is — and it stays a text link everywhere else,
   because this is opt-in by head class rather than a change to .hpx-btn-link. */
.hpx .hpx-head.is-railhead .hpx-btn-link {
    padding: 8px 15px;
    background: var(--hpx-brand-soft);
    border: 1px solid var(--hpx-brand-line);
    border-radius: var(--hpx-r-pill);
    color: var(--hpx-brand-ink);
}

@media (hover: hover) {
    .hpx .hpx-head.is-railhead .hpx-btn-link:hover {
        background: linear-gradient(135deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 100%);
        border-color: transparent;
        color: var(--hpx-ink-invert);
        box-shadow: 0 8px 20px -10px var(--hpx-brand-glow);
    }
}

.hpx .hpx-head.is-railhead .hpx-btn-link:focus-visible {
    outline: none;
    border-color: var(--hpx-brand);
    box-shadow: 0 0 0 3px var(--hpx-brand-line);
}

@media (prefers-reduced-motion: reduce) {
    .hpx-cat, .hpx-cat::before, .hpx-cat::after, .hpx-cat .hpx-ico, .hpx-cat-go, .hpx-nudge { transition: none; }
    .hpx-cat:hover { transform: none; }
}

/* ---------------------------------------------------------------------------
   11b. Tool library — a slider of category columns

   Metrics are lifted from the /tools directory grid so the two surfaces are
   the same component: 22px radius, 24/22/20 padding, a 46px gradient-filled
   icon with a white glyph, a dashed head rule, a 10px-radius "View all" pill
   and a left accent bar that slides into each list row on hover. What differs
   is only the container — a rail here, a wrapping grid there.

   Colours come from the tone tokens rather than /tools' hard-coded `--cc`
   palette, so this band follows the Theme screen like every other band.

   Reuses the rail subsystem above (nudge buttons, edge fades, progress bar).
   Every coloured link is written `.hpx .hpx-x` — (0,2,0). `.hpx a{color:
   inherit}` is (0,1,1) and beats any single-class colour whatever the source
   order, and this band is ~120 links.
   ------------------------------------------------------------------------ */

.hpx-lib {
    display: grid;
    grid-auto-flow: column;
    /* Wide enough for a tool name like "AI Meta Description Generator" to sit
       on one line beside its glyph, which is what keeps fourteen columns
       scanning as a table rather than as ragged paragraphs. */
    /* WHOLE CARDS ONLY. The width is derived from how many columns should be
       visible, not fixed per card: `minmax(282px, 1fr)` sized the card and let
       whatever was left over become a partial one at the edge. Dividing the
       container by a column count means N cards plus their gaps consume the
       visible width exactly, so nothing is ever half-shown. */
    --hpx-rail-cols: 5;
    --hpx-rail-gap: 22px;
    grid-auto-columns: calc(
        (100% - (var(--hpx-rail-cols) - 1) * var(--hpx-rail-gap)) / var(--hpx-rail-cols)
    );
    gap: var(--hpx-rail-gap);
    /* Room for the lift and the glow — see `--hpx-rail-bleed`. This column
       rises 5px and its hover shadow (`0 22px 44px -14px`) reaches 30px below
       and 8px either side, against the 14px/2px the rail used to allow: the
       reason the tone glow came out sliced flat under a hovered category. */
    margin-inline: calc(var(--hpx-rail-bleed) * -1);
    padding: 10px var(--hpx-rail-bleed) 32px;
    overflow-x: auto;
    /* mandatory, not proximity. Proximity only snaps when the rest position is
       ALREADY near a card edge, so a nudge or a flick that lands mid-card just
       stays there — which is the sliver at the left edge. */
    scroll-snap-type: x mandatory;
    /* The snapport starts at the padding box by default, so a card would align
       to the very edge and scroll the glow room off-screen. Insetting it by the
       bleed lines cards up with the content box instead. */
    scroll-padding-inline: var(--hpx-rail-bleed);
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;

    /* See the note on `.hpx-catrail`: a mask fades to transparent, so it is
       correct on every band surface — and `scroll-behavior: smooth` is
       deliberately NOT set, because it disarms the kit's scroll fallback. */
    --hpx-fade-s: 0px;
    --hpx-fade-e: 0px;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--hpx-fade-s), #000 calc(100% - var(--hpx-fade-e)), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--hpx-fade-s), #000 calc(100% - var(--hpx-fade-e)), transparent 100%);
    transition: -webkit-mask-image .2s linear, mask-image .2s linear;
}

.hpx-lib::-webkit-scrollbar { display: none; }

[data-hpx-railwrap][data-scrollable="1"][data-at-start="0"] .hpx-lib { --hpx-fade-s: 56px; }
[data-hpx-railwrap][data-scrollable="1"][data-at-end="0"]   .hpx-lib { --hpx-fade-e: 56px; }

/* The head sits further from the rail than a normal band head — /tools gives
   this block 40px and it needs the room, because the eyebrow, the display
   heading and the sub-heading stack into a tall unit. */
.hpx-b-library .hpx-head.is-showcase { margin-bottom: 36px; }

/* ── The column ─────────────────────────────────────────────────── */

.hpx-libcol {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 24px 22px 20px;
    scroll-snap-align: start;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: 22px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 12px 26px -20px rgba(16, 24, 40, .3);
    transition: transform .26s var(--hpx-a-ease), border-color .22s ease, box-shadow .26s ease;
}

body.theme-dark .hpx .hpx-libcol {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .06),
        0 1px 2px rgba(0, 0, 0, .4),
        0 12px 26px -20px rgba(0, 0, 0, .75);
}

/* Top edge shine bar, inset from the corners the way /tools draws it — a bar
   that runs the full width fights the 22px radius at both ends. */
.hpx-libcol::before {
    content: '';
    position: absolute;
    inset-inline: 20px;
    top: 0;
    z-index: 2;
    height: 2px;
    border-end-start-radius: 2px;
    border-end-end-radius: 2px;
    background: linear-gradient(90deg, transparent, var(--tone-ink), transparent);
    opacity: 0;
    transform: translateY(-2px);
    transition: opacity .26s ease, transform .26s ease;
}

/* Top-fade tint, hidden at rest: the column already has a surface, so a
   resting tint on top of it would muddy the card rather than identify it. */
.hpx-libcol::after {
    content: '';
    position: absolute;
    inset-inline: 0;
    top: 0;
    z-index: 0;
    height: 92px;
    border-start-start-radius: 22px;
    border-start-end-radius: 22px;
    background: linear-gradient(180deg, var(--tone-bg) 0%, transparent 100%);
    opacity: 0;
    transition: opacity .26s ease;
    pointer-events: none;
}

.hpx-libcol > * { position: relative; z-index: 1; }

.hpx .hpx-libcol.is-featured { border-color: var(--hpx-brand-line); }
.hpx-libcol.is-featured::before { opacity: 1; transform: translateY(0); }

/* ── Head ───────────────────────────────────────────────────────── */

.hpx-libcol-head {
    display: flex;
    align-items: center;
    gap: 14px;
    padding-bottom: 16px;
    margin-bottom: 18px;
    /* Dashed, not solid: it separates a heading from a list rather than
       dividing two blocks, and /tools draws it that way. */
    border-bottom: 1px dashed var(--hpx-line-strong);
}

/* A SOLID tone fill with a white glyph, not the soft tinted chip the rest of
   the homepage uses — this is the one place the /tools design differs from the
   house style, and it is what makes the reference read as it does.

   The sheen supplies the second gradient stop, so no per-tone "light twin"
   token has to exist. */
.hpx-libcol-head .hpx-ico {
    width: 46px; height: 46px;
    font-size: 19px;
    border-radius: 13px;
    background-color: var(--tone-ink);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .26) 100%);
    color: var(--hpx-ink-invert);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        0 8px 18px -8px var(--tone-ink);
    transition: transform .3s var(--hpx-a-ease), box-shadow .3s ease;
}

.hpx-libcol-head .hpx-ico svg { width: 22px; height: 22px; }

/* The dark twins of `--tone-ink` are LIGHTENED so they can serve as ink on a
   dark surface, which is exactly what makes them a poor fill under white text
   — white on #8CBEFA measures 2.4:1. A scrim deepens the fill without touching
   the token, and takes every tone past 4.5:1. Same fix as the Why-choose lead
   card. */
body.theme-dark .hpx .hpx-libcol-head .hpx-ico {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
        linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 8px 18px -8px rgba(0, 0, 0, .8);
}

.hpx-libcol-id { display: flex; flex-direction: column; gap: 5px; min-width: 0; }

.hpx .hpx-libcol-title {
    font-size: 16.8px;
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -.015em;
    color: var(--hpx-ink);
    transition: color .2s ease;
}

/* The count pill, with the ringed tone dot /tools carries. */
.hpx .hpx-libcol-count {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    align-self: flex-start;
    padding: 3px 9px 3px 6px;
    border-radius: var(--hpx-r-pill);
    background: var(--tone-bg);
    border: 1px solid var(--tone-line);
    color: var(--tone-ink);
    font-size: 11px;
    font-weight: 800;
    line-height: 1.5;
    letter-spacing: .02em;
}

.hpx .hpx-libcol-count::before {
    content: '';
    width: 6px; height: 6px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 0 3px var(--tone-bg);
}

/* ── The tool list ──────────────────────────────────────────────── */

.hpx-liblist {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 0 0 18px;
    padding: 0;
    /* The lists are uneven — SEO Tools holds six, Business Tools holds eight —
       and a rail of columns that each end where their content ends looks
       broken. The floor is eight rows' worth; `flex: 1` then lets a longer one
       push the "View all" button down evenly across the row. */
    flex: 1 1 auto;
    min-height: 268px;
}

.hpx .hpx-liblist a {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    margin-inline: -10px;
    border-radius: 10px;
    color: var(--hpx-ink-2);
    font-size: 13.5px;
    font-weight: 500;
    line-height: 1.4;
    transition: background-color .18s ease, color .18s ease;
}

/* Left accent bar, slides in on hover. */
.hpx .hpx-liblist a::before {
    content: '';
    position: absolute;
    inset-inline-start: 2px;
    top: 10px; bottom: 10px;
    width: 2px;
    border-radius: 2px;
    background: var(--tone-ink);
    opacity: 0;
    transform: scaleY(0);
    transition: opacity .18s ease, transform .18s ease;
}

.hpx-lib-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px; height: 16px;
    flex: 0 0 auto;
    line-height: 0;
    color: var(--hpx-ink-faint);
    transition: color .18s ease, transform .18s ease;
}

.hpx-lib-ico svg { display: block; width: 16px; height: 16px; }

/* One line, always. A wrapped tool name turns a tidy list into a paragraph and
   pushes the "View all" button out of line with its neighbours. */
.hpx-lib-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hpx-lib-empty {
    flex: 1 1 auto;
    min-height: 268px;
    margin: 0 0 18px;
    font-size: 13px;
    line-height: 1.6;
    color: var(--hpx-ink-3);
}

/* ── The column's own button ────────────────────────────────────── */

/* 10px radius, not a pill — /tools draws it as a rounded rectangle, and at
   (0,2,0) because it is an <a> that would otherwise inherit body ink. */
.hpx .hpx-lib-more {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    align-self: flex-start;
    margin-top: auto;
    padding: 9px 14px;
    border: 1px solid var(--tone-line);
    border-radius: 10px;
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 13px;
    font-weight: 800;
    letter-spacing: -.005em;
    transition: background-color .18s ease, background-image .18s ease, color .18s ease,
                border-color .18s ease, transform .18s ease, box-shadow .18s ease, gap .18s ease;
}

.hpx-lib-more svg { width: 14px; height: 14px; transition: transform .18s ease; }

[dir="rtl"] .hpx-lib-more svg { transform: scaleX(-1); }

@media (hover: hover) {
    .hpx-libcol:hover {
        transform: translateY(-5px);
        border-color: var(--tone-line);
        box-shadow: 0 22px 44px -14px var(--tone-ink), 0 8px 18px -8px rgba(16, 24, 40, .1);
    }

    .hpx-libcol:hover::before { opacity: 1; transform: translateY(0); }
    .hpx-libcol:hover::after { opacity: 1; }
    .hpx-libcol:hover .hpx-ico { transform: scale(1.08) rotate(-5deg); }
    .hpx .hpx-libcol:hover .hpx-libcol-title { color: var(--tone-ink); }

    body.theme-dark .hpx .hpx-libcol:hover {
        box-shadow: 0 22px 44px -14px rgba(0, 0, 0, .8), 0 8px 18px -8px rgba(0, 0, 0, .5);
    }

    .hpx .hpx-liblist a:hover {
        background: var(--tone-bg);
        color: var(--tone-ink);
    }

    .hpx .hpx-liblist a:hover::before { opacity: 1; transform: scaleY(1); }
    .hpx .hpx-liblist a:hover .hpx-lib-ico { color: var(--tone-ink); transform: scale(1.1); }

    .hpx .hpx-lib-more:hover {
        gap: 10px;
        background-color: var(--tone-ink);
        background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
        border-color: transparent;
        color: var(--hpx-ink-invert);
        transform: translateY(-2px);
        box-shadow: 0 8px 18px -8px var(--tone-ink);
    }

    /* Same lightened-token problem as the icon: white on the dark twin of
       `--tone-ink` is unreadable, so the fill takes the scrim too. */
    body.theme-dark .hpx .hpx-lib-more:hover {
        background-image:
            linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
            linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .2) 100%);
        box-shadow: 0 8px 18px -8px rgba(0, 0, 0, .8);
    }

    .hpx-lib-more:hover svg { transform: translateX(3px); }
    [dir="rtl"] .hpx-lib-more:hover svg { transform: scaleX(-1) translateX(3px); }
}

.hpx .hpx-liblist a:focus-visible,
.hpx .hpx-lib-more:focus-visible {
    outline: none;
    background-color: var(--tone-bg);
    color: var(--tone-ink);
    box-shadow: 0 0 0 3px var(--tone-line);
}

@media (max-width: 1024px) {
    .hpx-b-library .hpx-head.is-showcase { margin-bottom: 28px; }
    .hpx-lib { gap: 18px; }
}

@media (max-width: 640px) {
    /* 86% leaves the next column peeking — the swipe affordance, now that the
       arrows are gone on touch. */

    /* Phones keep the peek and PROXIMITY snap they always had. The
       column-count model below 640px replaced it with one full-width card,
       which stretched the cards, cramped the category tiles and removed the
       only cue that the rail moves — reverted on request. Everything from
       641px up still snaps to whole cards. */
    .hpx-lib {
        grid-auto-columns: minmax(272px, 86%);
        --hpx-rail-gap: 14px;
        padding-bottom: 12px;
        scroll-snap-type: x proximity;
    }

    /* The glow room the base rule reserves is only needed where a hover can
       actually happen. On a touch phone the shadow never fires, so the rail
       keeps the tighter 12px above and 20px of the fold is not spent on empty
       space; narrow a desktop window to this width instead and the room comes
       back, because otherwise the column's glow is sliced off exactly the way
       it was before this fix. */
    @media (hover: hover) {
        .hpx-lib { padding-bottom: 32px; }
    }

    .hpx-libcol { padding: 20px 18px 18px; border-radius: 18px; }
    .hpx-libcol::after { border-start-start-radius: 18px; border-start-end-radius: 18px; }
    .hpx-libcol-head { gap: 12px; padding-bottom: 14px; margin-bottom: 14px; }

    .hpx-libcol-head .hpx-ico { width: 42px; height: 42px; font-size: 17px; border-radius: 12px; }
    .hpx-libcol-head .hpx-ico svg { width: 20px; height: 20px; }

    .hpx .hpx-libcol-title { font-size: 15.5px; }
    .hpx .hpx-liblist a { font-size: 13px; padding: 7px 9px; margin-inline: -9px; }

    /* Six rows on a phone, not eight — the column is already 86% of the
       screen and the button has to stay reachable without a long scroll. */
    .hpx-liblist, .hpx-lib-empty { min-height: 208px; }

    .hpx-libcol:hover { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-libcol, .hpx-libcol::before, .hpx-libcol::after, .hpx-libcol .hpx-ico,
    .hpx-liblist a, .hpx-liblist a::before, .hpx-lib-ico, .hpx-lib-more, .hpx-lib-more svg { transition: none; }

    .hpx-libcol:hover, .hpx .hpx-lib-more:hover { transform: none; }
}

/* ---------------------------------------------------------------------------
   12. Card grids
   ------------------------------------------------------------------------ */

.hpx-grid {
    display: grid;
    grid-template-columns: repeat(var(--hpx-cd), minmax(0, 1fr));
    gap: var(--hpx-g);
}

.hpx-grid[hidden] { display: none; }

.hpx-card {
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r);
    overflow: hidden;
    transition: transform .22s var(--hpx-a-ease), box-shadow .24s ease, border-color .2s ease;
}

.hpx-card-elevated,
.hpx-card-flat .hpx-card { border-color: transparent; box-shadow: var(--hpx-sh-soft); }

.hpx-card-body { display: flex; flex-direction: column; gap: 7px; padding: 16px; flex: 1 1 auto; }

.hpx-card-tile .hpx-card-body { align-items: center; text-align: center; gap: 6px; padding: 16px 10px; }

.hpx-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.hpx-card-tile .hpx-card-top { justify-content: center; }

.hpx-card-title {
    font-size: var(--hpx-fs-card-title, 15px);
    font-weight: var(--hpx-fw-card-title, 650);
    line-height: var(--hpx-lh-card-title, 1.35);
    letter-spacing: var(--hpx-ls-card-title, -.008em);
    color: var(--hpx-ink);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.hpx-card-label { font-size: 12px; color: var(--hpx-ink-3); }

.hpx-card-desc {
    font-size: 13px;
    line-height: 1.55;
    color: var(--hpx-ink-3);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.hpx-card-meta { display: flex; align-items: center; gap: 8px; margin-top: auto; padding-top: 4px; flex-wrap: wrap; }
.hpx-card-tile .hpx-card-meta { justify-content: center; }

.hpx-rate { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 620; color: var(--hpx-t-amber-ink, #A85B08); }
.hpx-rate i { font-size: 10.5px; }

.hpx-card-date { font-size: 11.5px; color: var(--hpx-ink-3); }

.hpx-card-shot { display: block; aspect-ratio: 16 / 9; overflow: hidden; position: relative; background: var(--hpx-card-2); }
.hpx-card-shot img { width: 100%; height: 100%; object-fit: cover; transition: transform .4s var(--hpx-a-ease); }
.hpx-card-shot .hpx-badge { position: absolute; left: 10px; top: 10px; background: var(--hpx-card); }

/* The accent bar that wipes in on hover. */
.hpx-card::before {
    content: '';
    position: absolute;
    left: 0; right: 0; top: 0;
    height: 3px;
    background: var(--tone-ink);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform .3s var(--hpx-a-ease);
}

.hpx-card { position: relative; }

@media (hover: hover) {
    .hpx-card:hover { transform: translateY(-3px); border-color: var(--tone-line); box-shadow: var(--hpx-sh-lift); }
    .hpx-card:hover::before { transform: scaleX(1); }
    .hpx-card:hover .hpx-ico { transform: scale(1.07) rotate(-3deg); }
    .hpx-card:hover .hpx-card-shot img { transform: scale(1.05); }
}

/* Panels (a card grid inside its own surface) ------------------------------ */

.hpx-panel {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 20px;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r);
    box-shadow: var(--hpx-sh-soft);
}

.hpx-panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 14px; }
.hpx-panel .hpx-title { font-size: var(--hpx-fs-h3, 20px); }
.hpx-panel .hpx-grid { flex: 1 1 auto; align-content: start; }

.hpx-s-card.hpx-b-cards > .hpx-wrap > .hpx-panel { padding: 20px; }

/* Tabs -------------------------------------------------------------------- */

.hpx-tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 16px;
    padding-bottom: 2px;
    overflow-x: auto;
    scrollbar-width: none;
    border-bottom: 1px solid var(--hpx-line);
}

.hpx-tabs::-webkit-scrollbar { display: none; }

.hpx-tab {
    position: relative;
    flex: 0 0 auto;
    padding: 8px 13px 11px;
    border: 0;
    background: transparent;
    color: var(--hpx-ink-3);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: color .18s ease;
}

.hpx-tab::after {
    content: '';
    position: absolute;
    left: 10px; right: 10px; bottom: -1px;
    height: 2px;
    border-radius: 2px;
    background: var(--hpx-brand);
    transform: scaleX(0);
    transition: transform .22s var(--hpx-a-ease);
}

.hpx-tab.is-on { color: var(--hpx-brand); }
.hpx-tab.is-on::after { transform: scaleX(1); }

@media (hover: hover) {
    .hpx-tab:hover { color: var(--hpx-ink); }
}


/* ---------------------------------------------------------------------------
   12b. Showcase cards — the Trending / Models rails

   A product card in a horizontal rail behind a pill tab strip. Reuses the rail
   subsystem from §11 (nudge buttons, mask edge fades), so it adds no JavaScript
   beyond the tab-switch re-measure in the kit.

   Two rules govern everything below.

   1. COLOUR IDENTIFIES, BRAND ACTS. The tone palette dresses the things that
      say *which product this is* — the logo plate, the rank chip, the category
      pill, the hover wash. Every ACTION is brand blue on every card, because a
      row of six differently-coloured buttons reads as six different meanings
      and none of them is the site's. The previous pass gave the tinted fill to
      the buttons and reserved the brand gradient for `:first-child`, which is
      why five of six cards showed pale ink on a pale fill and read as broken.

   2. FIXED ROWS. name / category / description / facts each hold their height
      whether or not they have content, so twelve cards share one baseline. The
      ragged wrap of the old meta row is what made the rail look cluttered.

   Every coloured element is written `.hpx .hpx-x` — (0,2,0) — and every hover
   colour at (0,3,0). `.hpx a{color:inherit}` and its `:hover` twin out-rank a
   single-class colour, and the card and its button are both <a>-borne.
   ------------------------------------------------------------------------ */

/* ── The one filled-button recipe ────────────────────────────────
   Defined ONCE here and read by the card's button, the active tab pill and the
   lead rank chip, so a filled control cannot drift out of step with its
   siblings.

   It has to live on `.hpx`, not on the card: the tab strip is a sibling of the
   rail, so a definition scoped to `.hpx-card-showcase` left the active tab with
   `color: var(--hpx-ink-invert)` and no fill underneath — a white label on a
   white pill, which is exactly the hidden-text failure this pass exists to fix.

   White on `--hpx-brand` measures 3.84:1 in light and 2.6:1 in dark (the dark
   twin is LIGHTENED so it can serve as ink), so neither theme puts a white
   label straight onto the brand token: light starts a step down the ramp at
   -600, and dark lays the same scrim the library band and the why-choose lead
   card use. */
/* Anchored on #357fec, the requested brand blue, with a ramp that earns its
   label back.

   White on #357fec measures 3.89:1 — under the 4.5:1 AA needs for a 15px
   label, which is why this token used to start a step down the ramp at -600.
   So the colour leads the gradient and the ramp reaches AA by 30%: the top-left
   corner IS #357fec, and every pixel a label can sit on is 5.15:1 or better.
   Measured along the ramp — 0% 3.89, 30% 5.15, 60% 6.18, 100% 8.03.

   Changed on the token, not on the search button, because the chips sit
   directly under that button and fill from the same recipe. Two blues one
   above the other is the kind of difference nobody can explain later. */
.hpx {
    --sc-fill: linear-gradient(135deg, #357FEC 0%, #2B6BCC 30%, #1B4E9C 100%);
    /* The glow in the requested colour, so the halo round a filled control is
       literally that blue rather than the token's slightly different one. */
    --sc-glow: rgba(53, 127, 236, .40);
}

/* The scrim only deepens it — dark runs 6.77:1 at the lightest end. */
body.theme-dark .hpx {
    --sc-fill:
        linear-gradient(0deg, rgba(9, 18, 33, .34), rgba(9, 18, 33, .34)),
        linear-gradient(135deg, #357FEC 0%, #2B6BCC 34%, #1B4E9C 100%);
    --sc-glow: rgba(53, 127, 236, .52);
}

/* ── Panel head ─────────────────────────────────────────────────── */

/* A SOLID tone fill with a white glyph, the house plate — same treatment as the
   library, category and icongrid bands. The sheen supplies the second gradient
   stop, so no per-tone "light twin" token has to exist. */
.hpx-panel-ico {
    display: inline-grid;
    place-items: center;
    width: 54px; height: 54px;
    flex: 0 0 auto;
    border-radius: 15px;
    background-color: var(--tone-ink);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .26) 100%);
    color: var(--hpx-ink-invert);
    font-size: 22px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        0 10px 22px -10px var(--tone-ink);
}

/* The dark twins of `--tone-ink` are LIGHTENED so they can serve as ink on a
   dark surface, which is what makes them a poor fill under a white glyph —
   white on #8CBEFA measures 2.4:1. A scrim deepens the fill without touching
   the token. */
body.theme-dark .hpx .hpx-panel-ico {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
        linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 10px 22px -10px rgba(0, 0, 0, .8);
}

.hpx-panel-head { align-items: center; gap: 15px; }

/* With an icon the head has THREE children, and `space-between` spreads all
   three — so the title drifted 250px away from the icon it belongs to and read
   as centred. An auto end-margin on the copy absorbs the free space instead,
   keeping icon+copy as one group on the left and the button on the right. */
.hpx-panel.is-railpanel .hpx-panel-head .hpx-head-copy { margin-inline-end: auto; }

/* The band, the panel and the cards are all near-white in light mode, so
   without this the whole section reads as one flat sheet with hairlines drawn
   on it. A soft radial of the head's own hue, fading out before it reaches the
   cards, separates the three planes and is where the band gets its character:
   Trending glows blue, Models violet, entirely from `head_tone`. */
.hpx-panel.is-railpanel {
    padding: 26px 26px 22px;
    background-image: radial-gradient(110% 62% at 50% -18%, var(--tone-bg) 0%, transparent 64%);
}

/* Dark surfaces need the wash pulled back — the dark tint twins are far more
   saturated against #17171B than the light ones are against white. */
body.theme-dark .hpx .hpx-panel.is-railpanel {
    background-image: radial-gradient(110% 58% at 50% -22%, var(--tone-bg) 0%, transparent 58%);
}

.hpx-panel.is-railpanel .hpx-panel-head { margin-bottom: 18px; }
.hpx .hpx-panel.is-railpanel .hpx-title { font-size: clamp(21px, 1.4vw + 14px, 27px); }

.hpx .hpx-panel-sub {
    margin: 5px 0 0;
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--hpx-ink-2);
}

/* ── Segmented tab strip ────────────────────────────────────────── */

/* Five loose bordered pills read as five buttons; the visitor has to work out
   that they are mutually exclusive. On a recessed track with the active one
   raised out of it, the strip reads as ONE control at a glance — the shape a
   segmented filter has had since it was a physical switch.

   It also fixes a hierarchy problem: the head previously had three separate
   pill languages competing at the same size (five tab pills, the CTA, and a
   solid blue active tab), so nothing looked primary. Now the track is quiet,
   the active segment is brand, and the CTA is the one thing outside the track.

   Scoped to `.is-railpanel` so no other band changes. */
.hpx-panel.is-railpanel .hpx-tabs {
    display: inline-flex;
    /* `.hpx-panel` is a flex COLUMN, so the default `align-items: stretch`
       makes the track span the panel however inline the display value is — it
       has to opt out or the segmented shape is lost entirely. */
    align-self: flex-start;
    max-width: 100%;
    gap: 4px;
    margin-bottom: 20px;
    padding: 5px;
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r-pill);
    background: var(--hpx-bg-2);
    overflow-x: auto;
    scrollbar-width: none;
    box-shadow: inset 0 1px 2px rgba(16, 24, 40, .04);
}

/* The edge fade, applied only once the kit has MEASURED an overflow — a strip
   that fits must not have its first and last segments dimmed, and whether it
   fits depends on the viewport width, not on how many tabs there are. */
.hpx-panel.is-railpanel .hpx-tabs[data-overflow="1"] {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
}

body.theme-dark .hpx .hpx-panel.is-railpanel .hpx-tabs {
    background: rgba(0, 0, 0, .28);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .4);
}

.hpx-panel.is-railpanel .hpx-tabs::-webkit-scrollbar { display: none; }

.hpx .hpx-panel.is-railpanel .hpx-tab {
    flex: 0 0 auto;
    padding: 9px 17px;
    border: 0;
    border-radius: var(--hpx-r-pill);
    background: transparent;
    color: var(--hpx-ink-2);
    font-size: 13px;
    font-weight: 660;
    white-space: nowrap;
    transition: background-color .18s ease, background-image .18s ease, color .18s ease,
                box-shadow .2s ease;
}

.hpx .hpx-panel.is-railpanel .hpx-tab::after { display: none; }

.hpx .hpx-panel.is-railpanel .hpx-tab i { font-size: 12px; margin-inline-end: 6px; }

.hpx .hpx-panel.is-railpanel .hpx-tab.is-on {
    background-image: var(--sc-fill);
    color: var(--hpx-ink-invert);
    box-shadow: 0 6px 16px -8px var(--hpx-brand-glow), inset 0 1px 0 rgba(255, 255, 255, .18);
}

/* The inactive hover is a raised chip in the CARD colour, not a brand tint:
   against a recessed track, "lifted" is the affordance, and a brand tint here
   would read as a second selected segment. */
@media (hover: hover) {
    .hpx .hpx-panel.is-railpanel .hpx-tab:not(.is-on):hover {
        background: var(--hpx-card);
        color: var(--hpx-ink);
        box-shadow: 0 2px 6px -2px rgba(16, 24, 40, .16);
    }

    body.theme-dark .hpx .hpx-panel.is-railpanel .hpx-tab:not(.is-on):hover {
        background: var(--hpx-card-2);
        box-shadow: 0 2px 6px -2px rgba(0, 0, 0, .6);
    }
}

.hpx .hpx-panel.is-railpanel .hpx-tab:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--hpx-brand-line);
}

/* ── The head CTA ───────────────────────────────────────────────── */

/* `ghost` (white pill, neutral ink, grey hairline) is what these two bands used
   and it is the reason the band's only outbound link did not look like it
   belonged to this site at all. `brand` is tinted at rest — blue label, blue
   ring — so it is recognisably ours before anyone touches it, and fills on
   hover, which is the same rest→fill language the cards' buttons use.

   Tinted rather than solid on purpose: solid would put a second saturated blue
   pill in the same row as the active tab and blur which one is the action. */
.hpx .hpx-btn-brand {
    padding: 10px 10px 10px 19px;
    background-color: var(--hpx-brand-soft);
    color: var(--hpx-brand-ink);
    box-shadow: inset 0 0 0 1px var(--hpx-brand-line);
}

.hpx-btn-brand .hpx-btn-arrow {
    display: grid;
    place-items: center;
    width: 24px; height: 24px;
    margin-inline-start: 2px;
    border-radius: 50%;
    background: var(--hpx-brand);
    color: #fff;
    transition: background-color .2s ease, transform .22s var(--hpx-a-ease);
}

.hpx-btn-brand .hpx-btn-arrow svg { width: 13px; height: 13px; }
[dir="rtl"] .hpx-btn-brand .hpx-btn-arrow svg { transform: scaleX(-1); }

@media (hover: hover) {
    .hpx .hpx-btn-brand:hover {
        background-color: transparent;
        background-image: var(--sc-fill);
        color: var(--hpx-ink-invert);
        transform: translateY(-1px);
        box-shadow: 0 12px 24px -10px var(--hpx-brand-glow);
    }

    .hpx-btn-brand:hover .hpx-btn-arrow {
        background: rgba(255, 255, 255, .22);
        transform: translateX(2px);
    }

    [dir="rtl"] .hpx-btn-brand:hover .hpx-btn-arrow { transform: translateX(-2px); }
}

.hpx .hpx-btn-brand:focus-visible {
    outline: none;
    background-color: transparent;
    background-image: var(--sc-fill);
    color: var(--hpx-ink-invert);
    box-shadow: 0 0 0 3px var(--hpx-brand-line);
}

/* ── The rail ───────────────────────────────────────────────────── */

.hpx-cardrailwrap[hidden] { display: none; }

.hpx-cardrail {
    display: grid;
    grid-auto-flow: column;
    /* Whole cards only — see the note on `.hpx-lib`. */
    --hpx-rail-cols: 6;
    --hpx-rail-gap: 16px;
    grid-auto-columns: calc(
        (100% - (var(--hpx-rail-cols) - 1) * var(--hpx-rail-gap)) / var(--hpx-rail-cols)
    );
    gap: var(--hpx-rail-gap);
    scroll-padding-inline: var(--hpx-rail-bleed);
    /* Same treatment as the other two rails — see `--hpx-rail-bleed` — but
       24px below rather than 30. The showcase card's SATURATED glow
       (`0 26px 46px -30px`) already fitted the old 16px, so unlike the other
       two rails there was no visible slice here; this only buys back the
       ambient tail. It sits inside a panel that already pads 22px underneath,
       so matching the others exactly would have spent 14px of empty space on
       something nobody can see. The side bleed costs nothing and stays. */
    margin-inline: calc(var(--hpx-rail-bleed) * -1);
    padding: 10px var(--hpx-rail-bleed) 24px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;

    /* Mask, not an overlay — see §11. And deliberately NO
       `scroll-behavior: smooth`, which would disarm the kit's scroll fallback. */
    --hpx-fade-s: 0px;
    --hpx-fade-e: 0px;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--hpx-fade-s), #000 calc(100% - var(--hpx-fade-e)), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--hpx-fade-s), #000 calc(100% - var(--hpx-fade-e)), transparent 100%);
    transition: -webkit-mask-image .2s linear, mask-image .2s linear;
}

.hpx-cardrail::-webkit-scrollbar { display: none; }

[data-hpx-railwrap][data-scrollable="1"][data-at-start="0"] .hpx-cardrail { --hpx-fade-s: 48px; }
[data-hpx-railwrap][data-scrollable="1"][data-at-end="0"]   .hpx-cardrail { --hpx-fade-e: 48px; }

/* ── The edge fade steps aside for the pointer ──────────────────────
   The fade is a MASK on the rail, so it does not merely tint what is under it
   — it erases it, including anything a card paints outside its own box. Hover
   a card while the rail is scrolled and the mask takes a bite out of that
   card's glow with a hard vertical edge, which is the "shadow is truncated"
   report: the padding fix above gave the glow room, and then the mask deleted
   it anyway.

   So while a pointer is in the rail, the fades go to zero. Nothing is lost by
   doing that: the fade exists to say "there is more this way", and the moment
   you are actually in the rail the nudge arrows are showing and saying it
   better. `mask-image` already carries a .2s transition, so it lifts and
   returns smoothly rather than snapping.

   `:focus-within` for the keyboard equivalent — tabbing into a card that is
   half-dissolved is the same bug without a mouse.

   (0,4,0), the same as the two rules above it for each rail, so it wins on
   SOURCE ORDER — it has to stay below all six of them. */
@media (hover: hover) {
    [data-hpx-railwrap][data-scrollable="1"]:hover .hpx-catrail,
    [data-hpx-railwrap][data-scrollable="1"]:hover .hpx-lib,
    [data-hpx-railwrap][data-scrollable="1"]:hover .hpx-cardrail {
        --hpx-fade-s: 0px;
        --hpx-fade-e: 0px;
    }
}

[data-hpx-railwrap][data-scrollable="1"]:focus-within .hpx-catrail,
[data-hpx-railwrap][data-scrollable="1"]:focus-within .hpx-lib,
[data-hpx-railwrap][data-scrollable="1"]:focus-within .hpx-cardrail {
    --hpx-fade-s: 0px;
    --hpx-fade-e: 0px;
}

/* ── The card ───────────────────────────────────────────────────── */

.hpx .hpx-card-showcase {
    position: relative;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
    padding: 42px 16px 18px;
    scroll-snap-align: start;
    text-align: center;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: 20px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 10px 24px -20px rgba(16, 24, 40, .3);
    transition: transform .26s var(--hpx-a-ease), border-color .2s ease, box-shadow .26s ease;
}

body.theme-dark .hpx .hpx-card-showcase {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .06),
        0 1px 2px rgba(0, 0, 0, .4),
        0 10px 24px -20px rgba(0, 0, 0, .75);
}

/* The tone wash: a soft radial of the card's own hue, off until hover. This is
   where the colour in "colourful" mostly lives — it tints the whole card
   without touching the contrast of a single piece of text, because it sits at
   z-index 0 under content that is all z-index 1. */
.hpx-sc-wash {
    position: absolute;
    inset: 0;
    z-index: 0;
    border-radius: inherit;
    background: radial-gradient(120% 78% at 50% -12%, var(--tone-bg) 0%, transparent 70%);
    opacity: 0;
    pointer-events: none;
    transition: opacity .3s ease;
}

/* The accent bar across the top edge, drawn in the tone and scaled in from the
   centre on hover. */
.hpx-sc-edge {
    position: absolute;
    inset-block-start: 0;
    inset-inline: 14px;
    z-index: 2;
    height: 3px;
    border-radius: 0 0 3px 3px;
    background: linear-gradient(90deg, transparent, var(--tone-ink), transparent);
    opacity: .9;
    transform: scaleX(0);
    transform-origin: 50% 0;
    pointer-events: none;
    transition: transform .32s var(--hpx-a-ease);
}

/* Lift the content above the wash. The absolutely-positioned corner controls
   are excluded — `position: relative` here would drop the rank chip and the
   bookmark back into the flex column, where `align-items: center` parks them
   above the logo as two stray blobs.

   `:where()` keeps this at (0,1,0) so it cannot out-rank the (0,2,0) rules that
   style those controls; a bare `:not()` chain would sit at (0,5,0) and quietly
   win. Same lesson as the `.hpx a:hover` neutraliser at the top of the file. */
.hpx-card-showcase > *:not(:where(.hpx-sc-wash, .hpx-sc-edge, .hpx-sc-rank, .hpx-sc-save, .hpx-sc-flag)) {
    position: relative;
    z-index: 1;
}

/* Rank chip, top-left. Tone-tinted so the row reads as a ranked set; the leader
   takes the brand fill, because #1 is the claim the band is making. */
.hpx .hpx-sc-rank {
    position: absolute;
    inset-block-start: 13px;
    inset-inline-start: 13px;
    display: inline-grid;
    place-items: center;
    min-width: 24px; height: 24px;
    padding: 0 7px;
    border-radius: 9px;
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 11.5px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    box-shadow: inset 0 0 0 1px var(--tone-line);
}

.hpx .hpx-sc-rank.is-lead {
    background-image: var(--sc-fill);
    color: var(--hpx-ink-invert);
    box-shadow: 0 6px 14px -7px var(--hpx-brand-glow);
}

.hpx .hpx-sc-flag {
    position: absolute;
    inset-block-start: 13px;
    inset-inline-end: 13px;
    padding: 3px 9px;
    border-radius: var(--hpx-r-pill);
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: .04em;
    line-height: 1.5;
    text-transform: uppercase;
}

/* ── The logo plate ─────────────────────────────────────────────── */

/* A tinted plate rather than a bare mark. It gives every card the same silhouette
   whether the listing has a logo, a glyph or only an initial — a rail where
   three cards have a floating PNG and three have a coloured square is the
   single most obvious way this row looked unfinished. */
.hpx-sc-logo {
    display: grid;
    place-items: center;
    width: 66px; height: 66px;
    margin-bottom: 14px;
    border-radius: 20px;
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 30px;
    box-shadow: inset 0 0 0 1px var(--tone-line);
    transition: transform .28s var(--hpx-a-ease), box-shadow .24s ease;
}

.hpx-sc-logo img { width: 60%; height: 60%; object-fit: contain; }

/* The no-logo fallback: the listing's initial in its tone. The plate already
   supplies the shape, so the letter only has to supply the letter. */
.hpx .hpx-sc-letter {
    display: grid;
    place-items: center;
    width: 100%; height: 100%;
    color: var(--tone-ink);
    font-size: 26px;
    font-weight: 800;
    line-height: 1;
}

.hpx-sc-logo .hpx-ico { width: 100%; height: 100%; font-size: 28px; background: none; }
.hpx-sc-logo svg { width: 52%; height: 52%; }

/* ── Row 1: the name ────────────────────────────────────────────── */

.hpx .hpx-sc-name {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    max-width: 100%;
    min-height: 21px;
    font-size: 15.5px;
    font-weight: 760;
    line-height: 1.35;
    letter-spacing: -.016em;
    color: var(--hpx-ink);
    transition: color .2s ease;
}

.hpx-sc-nametext {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The shared <x-verified> badge. It carries its own colour and its own inline
   size, so this only has to stop the flex row from squashing it. */
.hpx .hpx-sc-tick { flex: 0 0 auto; }

/* ── Row 2: the category ────────────────────────────────────────── */

/* One line, ellipsised, and it holds its height when absent. The old markup put
   this in a wrapping meta row beside the rating, so a long category pushed the
   description down on some cards and not others — every card in the rail
   started its pitch at a different height. */
.hpx .hpx-sc-cat {
    display: block;
    max-width: 100%;
    margin-top: 7px;
    min-height: 17px;
    font-size: 12px;
    font-weight: 640;
    line-height: 1.4;
    color: var(--tone-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.hpx .hpx-sc-cat.is-link { cursor: pointer; transition: color .18s ease, text-decoration-color .18s ease; }

/* `text-underline-offset` rather than a border, so the underline follows the
   ellipsis instead of running the full pill width. */
@media (hover: hover) {
    .hpx .hpx-card-showcase .hpx-sc-cat.is-link:hover {
        color: var(--hpx-brand-ink);
        text-decoration: underline;
        text-underline-offset: 3px;
    }
}

.hpx .hpx-sc-cat.is-link:focus-visible {
    outline: 2px solid var(--hpx-brand);
    outline-offset: 2px;
    border-radius: 5px;
}

/* ── Row 3: the pitch ───────────────────────────────────────────── */

/* Clamped in CSS, not by Str::limit. A character count is wrong at every
   breakpoint but one; a line clamp cuts where the text actually wraps, and the
   reserved height is what keeps the buttons on one line across the rail. */
.hpx .hpx-sc-desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 10px;
    min-height: calc(2 * 1.55em);
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--hpx-ink-3);
}

/* ── Row 4: the facts ───────────────────────────────────────────── */

.hpx-sc-facts {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 12px;
}

.hpx .hpx-sc-rate {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 640;
    color: var(--hpx-ink-2);
}

.hpx .hpx-sc-rate strong { font-weight: 780; color: var(--hpx-ink); }
.hpx .hpx-sc-rate small { font-size: 11px; font-weight: 600; color: var(--hpx-ink-faint); }
.hpx .hpx-sc-rate i { font-size: 10.5px; color: var(--hpx-t-amber-ink); }
.hpx .hpx-sc-rate.is-use i { color: var(--hpx-brand); }

/* The pricing chip carries its own tone — "Free" is the same green on every
   card in the rail, which is the point of a chip. */
.hpx .hpx-sc-price {
    display: inline-flex;
    align-items: center;
    padding: 3px 9px;
    border-radius: var(--hpx-r-pill);
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 10.5px;
    font-weight: 720;
    line-height: 1.5;
    letter-spacing: .01em;
    white-space: nowrap;
    box-shadow: inset 0 0 0 1px var(--tone-line);
}

/* ── The action ─────────────────────────────────────────────────── */

/* A SOLID fill in the card's own hue, white label, on every card.

   This is the reference's colourful button row, done so the row reads as one
   set. The version it replaced put `--tone-ink` TEXT on a `--tone-bg` fill,
   which measures around 7:1 and was never a contrast failure — it read as
   washed out and unbranded, and only `:first-child` got the brand fill, so
   five of six buttons looked disabled next to the one that did not.

   Light mode needs no help: `--tone-ink` was drawn as ink-on-white, so as a
   FILL all eleven hues clear 5.1:1 against white (measured — emerald 5.47,
   amber 5.14, orange 5.20 are the tight ones; indigo reaches 8.39).

   Dark mode is the exact opposite problem and the reason a naive "just use the
   token" fails: the dark twins are LIGHTENED so they can serve as ink on a
   dark surface, so white on them measures around 1.5:1. A scrim pulls them
   back, applied uniformly rather than tuned per tone — a per-tone scrim would
   be eleven values to keep in step with eleven tokens.

   The scrim is `rgba(0,0,0,.5)` and NOT the `rgba(9,18,33,…)` the library band
   and the why-choose lead card use. Those cover large surfaces, where a
   blue-black scrim reads as part of the dark theme. On a small saturated pill
   it desaturates: amber came out khaki and rose came out mauve. Neutral black
   darkens without dragging the hue toward blue, and measures the same or
   better (amber 5.58:1 vs 5.69, rose 6.80 either way). */
.hpx .hpx-sc-go {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    width: 100%;
    margin-top: 16px;
    padding: 11px 14px;
    border-radius: 13px;
    background-color: var(--tone-ink);
    background-image: linear-gradient(160deg, rgba(255, 255, 255, .20) 0%, rgba(255, 255, 255, 0) 58%);
    color: var(--hpx-ink-invert);
    font-size: 13px;
    font-weight: 720;
    letter-spacing: -.006em;
    box-shadow: 0 8px 18px -10px var(--tone-ink);
    transition: background-image .2s ease, box-shadow .22s ease, gap .2s ease, transform .2s var(--hpx-a-ease);
}

body.theme-dark .hpx .hpx-sc-go {
    background-image:
        linear-gradient(0deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, .5)),
        linear-gradient(160deg, rgba(255, 255, 255, .20) 0%, rgba(255, 255, 255, 0) 58%);
    box-shadow: 0 8px 18px -12px rgba(0, 0, 0, .9);
}

/* `margin-top: auto` needs free space above it, which only exists once the card
   is taller than its content — the rail's tracks are equal height, so this pins
   every button to the same line. */
.hpx-card-showcase .hpx-sc-go { margin-top: auto; }
.hpx-card-showcase .hpx-sc-facts + .hpx-sc-go { margin-top: 14px; }

/* No `.is-lead` override on the button any more. With every button carrying
   its card's hue, a brand-blue one on card #1 stops reading as "the leader" and
   starts reading as "another colour" — especially since the palette cycles back
   to indigo, which is nearly brand blue, a few cards later. The lead is still
   marked, by the filled rank chip. */

.hpx-sc-golabel { white-space: nowrap; }
.hpx-sc-go svg { flex: 0 0 auto; width: 14px; height: 14px; transition: transform .2s var(--hpx-a-ease); }
[dir="rtl"] .hpx-sc-go svg { transform: scaleX(-1); }

/* ── Hover ──────────────────────────────────────────────────────── */

@media (hover: hover) {
    .hpx-card-showcase:hover {
        transform: translateY(-6px);
        border-color: var(--tone-line);
        box-shadow: 0 26px 46px -30px var(--tone-ink), var(--hpx-sh-lift);
    }

    .hpx-card-showcase:hover .hpx-sc-wash { opacity: .85; }
    .hpx-card-showcase:hover .hpx-sc-edge { transform: scaleX(1); }

    .hpx-card-showcase:hover .hpx-sc-logo {
        transform: translateY(-2px) scale(1.06);
        box-shadow: inset 0 0 0 1px var(--tone-ink);
    }

    .hpx .hpx-card-showcase:hover .hpx-sc-name { color: var(--tone-ink); }

    /* The button is already filled at rest, so hover cannot signal by filling.
       It brightens the sheen, throws a shadow in its own hue and opens the gap
       — enough to feel live without the label ever changing colour. */
    .hpx .hpx-card-showcase:hover .hpx-sc-go {
        gap: 10px;
        transform: translateY(-1px);
        background-image: linear-gradient(160deg, rgba(255, 255, 255, .34) 0%, rgba(255, 255, 255, .04) 62%);
        box-shadow: 0 14px 26px -10px var(--tone-ink);
    }

    /* (0,4,1) — the dark scrim rule below is (0,3,1) and would otherwise beat
       the (0,3,0) hover rule above, leaving dark mode with no sheen change. */
    body.theme-dark .hpx .hpx-card-showcase:hover .hpx-sc-go {
        background-image:
            linear-gradient(0deg, rgba(0, 0, 0, .44), rgba(0, 0, 0, .44)),
            linear-gradient(160deg, rgba(255, 255, 255, .30) 0%, rgba(255, 255, 255, .04) 62%);
        box-shadow: 0 14px 26px -12px rgba(0, 0, 0, .95);
    }

    .hpx-card-showcase:hover .hpx-sc-go svg { transform: translateX(3px); }
    [dir="rtl"] .hpx-card-showcase:hover .hpx-sc-go svg { transform: scaleX(-1) translateX(3px); }

    body.theme-dark .hpx .hpx-card-showcase:hover {
        box-shadow: 0 26px 46px -30px rgba(0, 0, 0, .88);
    }

    body.theme-dark .hpx-card-showcase:hover .hpx-sc-wash { opacity: 1; }
}

/* Keyboard parity: everything hover does that carries meaning, focus does too.
   The card is a link and a keyboard visitor must see the same "this one is
   active" state a pointer visitor gets. */
.hpx .hpx-card-showcase:focus-visible {
    outline: none;
    transform: translateY(-6px);
    border-color: var(--hpx-brand);
    box-shadow: 0 0 0 3px var(--hpx-brand-line), var(--hpx-sh-lift);
}

.hpx-card-showcase:focus-visible .hpx-sc-edge { transform: scaleX(1); }

.hpx .hpx-card-showcase:focus-visible .hpx-sc-go {
    transform: translateY(-1px);
    box-shadow: 0 14px 26px -10px var(--tone-ink);
}

@media (max-width: 1024px) {
    .hpx-panel.is-railpanel { padding: 22px 20px 18px; }
    .hpx-panel-ico { width: 48px; height: 48px; font-size: 19px; border-radius: 14px; }
}

@media (max-width: 640px) {
    /* 78% shows one whole card and a clear slice of the next — the swipe
       affordance, since the arrows are hidden on touch. The old 62% left the
       card too narrow for a two-line description at this type size. */
    .hpx-cardrail {
        grid-auto-columns: minmax(196px, 78%);
        --hpx-rail-gap: 12px;
        scroll-snap-type: x proximity;
    }

    .hpx-panel.is-railpanel { padding: 18px 16px 14px; }
    .hpx .hpx-card-showcase { padding: 38px 14px 16px; border-radius: 18px; }
    .hpx-sc-logo { width: 58px; height: 58px; border-radius: 18px; font-size: 26px; margin-bottom: 12px; }
    .hpx .hpx-sc-letter { font-size: 23px; }
    .hpx .hpx-sc-name { font-size: 14.5px; }
    .hpx .hpx-sc-desc { font-size: 12px; }
    .hpx .hpx-sc-go { padding: 10px 12px; font-size: 12.5px; }

    .hpx-panel-ico { width: 44px; height: 44px; font-size: 17px; }
    .hpx .hpx-panel-sub { font-size: 12.5px; }
    .hpx-panel.is-railpanel .hpx-tabs { gap: 6px; margin-bottom: 15px; }
    .hpx .hpx-panel.is-railpanel .hpx-tab { padding: 8px 14px; font-size: 12.5px; }
}

/* On a narrow screen the head has to break, and a wrapping flex row breaks it
   in the wrong place: the icon takes the first line ALONE and the title drops
   under it, so the band opens with an orphaned glyph. A two-column grid keeps
   icon and title paired and drops the CTA onto its own full-width row, which is
   also the only place a right-aligned button does not look stranded. */
@media (max-width: 560px) {
    .hpx-panel.is-railpanel .hpx-panel-head {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        align-items: center;
        gap: 12px;
    }

    .hpx-panel.is-railpanel .hpx-panel-head .hpx-head-copy { margin-inline-end: 0; }

    .hpx-panel.is-railpanel .hpx-panel-head > .hpx-btn {
        grid-column: 1 / -1;
        width: 100%;
        justify-content: center;
    }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-card-showcase, .hpx-sc-logo, .hpx-sc-go, .hpx-sc-go svg, .hpx-sc-wash, .hpx-sc-edge,
    .hpx .hpx-panel.is-railpanel .hpx-tab { transition: none; }

    .hpx-card-showcase:hover,
    .hpx .hpx-card-showcase:focus-visible,
    .hpx .hpx-panel.is-railpanel .hpx-tab:not(.is-on):hover { transform: none; }

    .hpx-card-showcase:hover .hpx-sc-logo,
    .hpx .hpx-card-showcase:hover .hpx-sc-go,
    .hpx .hpx-card-showcase:focus-visible .hpx-sc-go { transform: none; }
}

/* ── 12c. Showcase extras — save button, row layout, dots ────────────
   Colours at (0,2,0) and hover colours at (0,3,0), as everywhere else. */

/* The bookmark. A <span> with role=button rather than a nested <button>,
   because the whole card is an <a> and a button inside an anchor is invalid
   HTML that browsers re-parent — which pulled it out of the card. */
.hpx .hpx-sc-save {
    position: absolute;
    inset-block-start: 11px;
    inset-inline-end: 11px;
    z-index: 3;
    display: grid;
    place-items: center;
    width: 30px; height: 30px;
    border-radius: 10px;
    color: var(--hpx-ink-faint);
    font-size: 13px;
    cursor: pointer;
    transition: background-color .18s ease, color .18s ease, transform .18s var(--hpx-a-ease);
}

.hpx .hpx-sc-save.is-on { color: var(--hpx-brand); }

@media (hover: hover) {
    .hpx .hpx-sc-save:hover {
        background: var(--hpx-brand-soft);
        color: var(--hpx-brand-ink);
        transform: scale(1.1);
    }
}

.hpx .hpx-sc-save:focus-visible {
    outline: none;
    background: var(--hpx-brand-soft);
    color: var(--hpx-brand-ink);
    box-shadow: 0 0 0 3px var(--hpx-brand-line);
}

/* ── Row layout — logo beside the name, for the models rail ─────── */

/* Wider tracks than the portrait rail. A row card spends 50px of its width on
   the logo before the name gets any, so at the portrait width "Runway Gen-3"
   ellipsised to "Runway Ge…" — the model name is the one thing on this card
   that must never be cut. */
.hpx-cardrail.is-row { --hpx-rail-cols: 5; }

.hpx .hpx-cardrail.is-row .hpx-card-showcase {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        'logo name'
        'logo cat'
        'desc desc'
        'fact fact'
        'go   go';
    align-items: center;
    gap: 0 13px;
    padding: 20px 18px 18px;
    text-align: start;
}

.hpx-cardrail.is-row .hpx-sc-logo {
    grid-area: logo;
    width: 50px; height: 50px;
    border-radius: 15px;
    font-size: 24px;
    margin-bottom: 0;
    align-self: center;
}

.hpx-cardrail.is-row .hpx-sc-logo img { width: 66%; height: 66%; }
.hpx .hpx-cardrail.is-row .hpx-sc-letter { font-size: 21px; }

.hpx .hpx-cardrail.is-row .hpx-sc-name {
    grid-area: name;
    align-self: end;
    justify-content: flex-start;
    font-size: 14.5px;
}

.hpx .hpx-cardrail.is-row .hpx-sc-cat { grid-area: cat; align-self: start; margin-top: 2px; }
.hpx .hpx-cardrail.is-row .hpx-sc-desc { grid-area: desc; margin-top: 13px; }
.hpx-cardrail.is-row .hpx-sc-facts { grid-area: fact; justify-content: flex-start; }
.hpx .hpx-cardrail.is-row .hpx-sc-go { grid-area: go; margin-top: 14px; }

/* A card with nothing in row 2 or row 4 would leave a visible gap in a grid,
   where the flex column simply collapses it. */
.hpx .hpx-cardrail.is-row .hpx-sc-cat.is-empty,
.hpx .hpx-cardrail.is-row .hpx-sc-desc.is-empty { display: none; }

/* The rank chip and bookmark are absolutely positioned, so they sit outside the
   grid — but a row card has no top padding to hold a rank chip. */
.hpx-cardrail.is-row .hpx-sc-rank { display: none; }
.hpx .hpx-cardrail.is-row .hpx-sc-save { inset-block-start: 9px; inset-inline-end: 9px; }
.hpx-cardrail.is-row .hpx-sc-edge { inset-inline: 16px; }

/* ── Nudges outside the rail ────────────────────────────────────── */

/* The card rails are full-bleed inside a boxed band, so an arrow tucked at
   -17px overlaps the first and last card. Pushed clear of the track. */
.hpx .hpx-cardrailwrap .hpx-nudge.is-prev { inset-inline-start: -22px; }
.hpx .hpx-cardrailwrap .hpx-nudge.is-next { inset-inline-end: -22px; }

/* ── Dot pagination ─────────────────────────────────────────────── */

.hpx-dots {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin-top: 4px;
    opacity: 0;
    transition: opacity .22s ease;
    pointer-events: none;
}

[data-hpx-railwrap][data-scrollable="1"] .hpx-dots { opacity: 1; }

.hpx-dot {
    width: 7px; height: 7px;
    border-radius: 999px;
    background: var(--hpx-line-strong);
    transition: width .26s var(--hpx-a-ease), background-color .22s ease;
}

/* `data-page` is written by the kit from the rail's scroll position; the dot
   whose index matches becomes the active bar. Five is the cap — beyond that
   the strip stops reading as pagination and starts reading as noise. */
[data-hpx-railwrap][data-page="0"] .hpx-dot:nth-child(1),
[data-hpx-railwrap][data-page="1"] .hpx-dot:nth-child(2),
[data-hpx-railwrap][data-page="2"] .hpx-dot:nth-child(3),
[data-hpx-railwrap][data-page="3"] .hpx-dot:nth-child(4),
[data-hpx-railwrap][data-page="4"] .hpx-dot:nth-child(5) {
    width: 22px;
    background: var(--hpx-brand);
}

@media (max-width: 640px) {
    .hpx-cardrail.is-row { grid-auto-columns: minmax(220px, 84%); }
    .hpx .hpx-cardrail.is-row .hpx-card-showcase { padding: 18px 16px 16px; }
    .hpx-cardrail.is-row .hpx-sc-logo { width: 46px; height: 46px; border-radius: 14px; }
    .hpx .hpx-cardrail.is-row .hpx-sc-name { font-size: 14px; }
    .hpx .hpx-sc-price { padding: 3px 8px; font-size: 10px; }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-sc-save, .hpx-dot { transition: none; }
    .hpx .hpx-sc-save:hover { transform: none; }
}

/* ---------------------------------------------------------------------------
   13. Promo cards

   Three zones: a tone-washed header, the link list, one branded action at the
   foot. The header is where the colour lives, so the card reads as belonging to
   its vertical before a word is read; everything below it is content.

   What this replaced: a 128px decorative gradient blob sat between the title
   and the links. It carried no information, it pushed the links off a phone
   screen, and it is the reason the band had to become a horizontal slider on
   mobile at all. Recovering that space is what lets the cards stack.
   ------------------------------------------------------------------------ */

.hpx-promos {
    display: grid;
    grid-template-columns: repeat(var(--hpx-cd), minmax(0, 1fr));
    gap: var(--hpx-g);
}

.hpx-promo {
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r);
    /* Same three-layer recipe as the Explore tiles and the trust band: a glass
       hairline along the top inner edge, a contact shadow, and a wide soft one. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 12px 26px -20px rgba(16, 24, 40, .30);
    transition: transform .24s var(--hpx-a-ease), box-shadow .26s ease, border-color .22s ease;
}

body.theme-dark .hpx .hpx-promo {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .06),
        0 1px 2px rgba(0, 0, 0, .4),
        0 12px 26px -20px rgba(0, 0, 0, .7);
}

/* ── The header ─────────────────────────────────────────────────── */

.hpx-promo-top {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 18px 18px 16px;
    background:
        linear-gradient(180deg, var(--tone-bg) 0%, transparent 100%),
        radial-gradient(120% 130% at 100% 0%, var(--tone-bg) 0%, transparent 64%);
    border-bottom: 1px solid var(--hpx-line-soft);
}

body.theme-dark .hpx .hpx-promo-top { border-bottom-color: rgba(255, 255, 255, .06); }

/* A solid tone plate with a white glyph, the same treatment the library band's
   category icons use — a soft tint here would disappear into the tinted header
   it is sitting on. */
.hpx-promo-ico {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 46px; height: 46px;
    border-radius: 14px;
    background-color: var(--tone-ink);
    background-image: linear-gradient(155deg, rgba(255, 255, 255, .3) 0%, rgba(255, 255, 255, 0) 62%);
    color: #fff;
    font-size: 19px;
    box-shadow: 0 8px 16px -8px var(--tone-ink);
    transition: transform .28s var(--hpx-a-ease), box-shadow .26s ease;
}

/* The dark twins of `--tone-ink` are lightened so they can serve as INK on a
   dark surface, so a white glyph on one measures about 2.4:1. Same scrim the
   library band and the card buttons use.

   The tinted drop shadow goes with it: `--tone-ink` is a LIGHT colour on dark,
   so `0 8px 16px -8px var(--tone-ink)` renders as a coloured halo bleeding out
   from under the plate rather than a shadow. Same call the card itself makes
   one rule up. */
body.theme-dark .hpx .hpx-promo-ico {
    background-image:
        linear-gradient(0deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, .5)),
        linear-gradient(155deg, rgba(255, 255, 255, .3) 0%, rgba(255, 255, 255, 0) 62%);
    box-shadow: none;
}

.hpx-promo-copy { min-width: 0; flex: 1 1 auto; }

.hpx .hpx-promo-title {
    font-size: 17px;
    font-weight: 760;
    line-height: 1.25;
    letter-spacing: -.018em;
}

.hpx .hpx-promo-sub {
    margin-top: 3px;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--hpx-ink-2);
}

/* The live count. Present only when the destination actually holds rows — see
   `promoCounts()`; a card with nothing behind it prints no pill rather than a
   zero, and no card prints a number nobody can verify. */
.hpx .hpx-promo-count {
    flex: 0 0 auto;
    align-self: flex-start;
    padding: 3px 9px;
    border-radius: var(--hpx-r-pill);
    background: var(--hpx-card);
    color: var(--tone-ink);
    font-size: 11.5px;
    font-weight: 780;
    font-variant-numeric: tabular-nums;
    line-height: 1.5;
    box-shadow: inset 0 0 0 1px var(--tone-line);
}

body.theme-dark .hpx .hpx-promo-count { background: var(--hpx-card-2); }

/* ── The links ──────────────────────────────────────────────────── */

/* `.hpx .hpx-promo-links`, not `.hpx-promo-links`. §2 sets `.hpx ul { padding:
   0 }` at (0,1,1), which beats a single-class rule — so the padding written
   here had never applied at all, and the horizontal inset the links appear to
   have comes from the <a> padding instead. Same shape as the `.hpx a { color:
   inherit }` trap that hides button labels.

   The 14px floor is what guarantees a gap above the button: `margin-top: auto`
   on the button only pays out when the card has slack, and the six-link card
   has none — its button sat hard against the last row while the four-link
   cards had 90px of air. */
.hpx .hpx-promo-links {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    padding: 10px 10px 14px;
}

.hpx-promo-links a {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 9px;
    border-radius: var(--hpx-r-sm);
    transition: background-color .16s ease, transform .16s ease;
}

.hpx-promo-link-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }

.hpx .hpx-promo-link-title {
    font-size: 13px;
    font-weight: 640;
    color: var(--hpx-ink);
    line-height: 1.35;
    transition: color .18s ease;
}

.hpx .hpx-promo-link-sub {
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--hpx-ink-2);
}

/* The affordance that says these rows go somewhere. Hidden at rest on a
   pointer device and revealed on hover; permanently visible on touch, where
   there is no hover to reveal it. */
.hpx-promo-link-go {
    flex: 0 0 auto;
    width: 14px; height: 14px;
    color: var(--tone-ink);
    opacity: 0;
    transform: translateX(-4px);
    transition: opacity .18s ease, transform .2s var(--hpx-a-ease);
}

[dir="rtl"] .hpx-promo-link-go { transform: scaleX(-1) translateX(-4px); }

/* Tone bar that grows on hover, so a row reads as selected rather than merely
   tinted. */
.hpx-promo-links a::before {
    content: '';
    position: absolute;
    inset-inline-start: 0;
    top: 50%;
    width: 2px; height: 0;
    border-radius: 2px;
    background: var(--tone-ink);
    transform: translateY(-50%);
    transition: height .22s var(--hpx-a-ease);
}

/* ── The action ─────────────────────────────────────────────────── */

/* `_cta` renders `.hpx-btn .hpx-btn-brand`; §6 gives it its shape and its arrow
   chip. Here it becomes a full-width footer AND takes the card's own hue.

   Brand blue was wrong on three cards out of four: the header, the icon plate,
   the link glyphs and the hover wash are all the card's tone, and a blue button
   under a violet card reads as borrowed from somewhere else. Same rule as the
   Discover tiles — the colour identifies which section you are looking at.

   Written at (0,3,0) / (0,4,0) so it beats the (0,2,0) / (0,3,0) brand rules in
   §6 outright rather than relying on source order. The panel heads keep brand
   blue, which is correct: those belong to the page, not to one vertical. */
.hpx-promo > .hpx-btn {
    justify-content: space-between;
    /* `margin-top: auto` only adds space when the card has slack. A six-link
       card has none, so the button sat hard against the last row — the links
       list carries the floor. */
    margin: auto 14px 16px;
    padding-block: 9px;
}

.hpx .hpx-promo > .hpx-btn-brand {
    background-color: var(--tone-bg);
    color: var(--tone-ink);
    box-shadow: inset 0 0 0 1px var(--tone-line);
}

.hpx-promo > .hpx-btn-brand .hpx-btn-arrow { background: var(--tone-ink); }

/* The dark twins of `--tone-ink` are LIGHTENED so they can serve as ink on a
   dark surface, so a white glyph on one measures about 2.4:1. Neutral black
   rather than the blue-black scrim: on a 24px chip the blue-black desaturates
   the hue (amber goes khaki, rose goes mauve). */
body.theme-dark .hpx .hpx-promo > .hpx-btn-brand .hpx-btn-arrow {
    background-image: linear-gradient(0deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, .5));
}

@media (hover: hover) {
    .hpx .hpx-promo > .hpx-btn-brand:hover {
        background-color: var(--tone-ink);
        background-image: linear-gradient(160deg, rgba(255, 255, 255, .2) 0%, rgba(255, 255, 255, 0) 58%);
        color: var(--hpx-ink-invert);
        box-shadow: 0 12px 24px -10px var(--tone-ink);
    }

    body.theme-dark .hpx .hpx-promo > .hpx-btn-brand:hover {
        background-image:
            linear-gradient(0deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, .5)),
            linear-gradient(160deg, rgba(255, 255, 255, .2) 0%, rgba(255, 255, 255, 0) 58%);
        box-shadow: 0 12px 24px -12px rgba(0, 0, 0, .9);
    }

    .hpx-promo > .hpx-btn-brand:hover .hpx-btn-arrow { background: rgba(255, 255, 255, .24); }
    body.theme-dark .hpx .hpx-promo > .hpx-btn-brand:hover .hpx-btn-arrow { background-image: none; }
}

.hpx .hpx-promo > .hpx-btn-brand:focus-visible {
    background-color: var(--tone-ink);
    color: var(--hpx-ink-invert);
    box-shadow: 0 0 0 3px var(--tone-line);
}

/* ── Hover ──────────────────────────────────────────────────────── */

@media (hover: hover) {
    .hpx-promo:hover {
        transform: translateY(-4px);
        border-color: var(--tone-line);
        box-shadow: 0 26px 46px -30px var(--tone-ink), var(--hpx-sh-lift);
    }

    body.theme-dark .hpx .hpx-promo:hover { box-shadow: 0 26px 46px -30px rgba(0, 0, 0, .85); }

    .hpx-promo:hover .hpx-promo-ico {
        transform: translateY(-2px) scale(1.06) rotate(-4deg);
        box-shadow: 0 12px 22px -8px var(--tone-ink);
    }

    /* The lift stays; the coloured halo does not — see the resting rule. */
    body.theme-dark .hpx .hpx-promo:hover .hpx-promo-ico { box-shadow: none; }

    .hpx-promo-links a:hover { background: var(--tone-bg); }
    .hpx-promo-links a:hover::before { height: 62%; }
    .hpx .hpx-promo-links a:hover .hpx-promo-link-title { color: var(--tone-ink); }
    .hpx-promo-links a:hover .hpx-ico { transform: scale(1.08); }

    .hpx-promo-links a:hover .hpx-promo-link-go { opacity: 1; transform: translateX(0); }
    [dir="rtl"] .hpx-promo-links a:hover .hpx-promo-link-go { transform: scaleX(-1) translateX(0); }
}

/* No hover to reveal the arrow on touch, and the rows are still links. */
@media (hover: none) {
    .hpx-promo-link-go { opacity: .4; transform: none; }
    [dir="rtl"] .hpx-promo-link-go { transform: scaleX(-1); }
}

/* Sub-links are real links and need a focus state of their own. */
.hpx-promo-links a:focus-visible {
    outline: none;
    background: var(--tone-bg);
    box-shadow: 0 0 0 2px var(--tone-line);
}

.hpx-promo-links a:focus-visible::before { height: 62%; }
.hpx-promo-links a:focus-visible .hpx-promo-link-go { opacity: 1; transform: translateX(0); }

@media (prefers-reduced-motion: reduce) {
    .hpx-promo, .hpx-promo-ico, .hpx-promo-links a, .hpx-promo-link-go { transition: none; }
    .hpx-promo:hover, .hpx-promo:hover .hpx-promo-ico { transform: none; }
}

/* ---------------------------------------------------------------------------
   14. Icon grid
   ------------------------------------------------------------------------ */

.hpx-icongrid {
    display: grid;
    grid-template-columns: repeat(var(--hpx-cd), minmax(0, 1fr));
    gap: var(--hpx-g);
}

.hpx-igcard {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 18px 14px;
    border-radius: var(--hpx-r);
    transition: transform .24s var(--hpx-a-ease), box-shadow .24s ease, border-color .2s ease, background-color .2s ease;
}

.hpx-ig-tile .hpx-igcard {
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    overflow: hidden;
    /* Matches the promo cards and the Explore tiles — these had no resting
       shadow either, so a row of six read as outlines rather than cards. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 10px 22px -18px rgba(16, 24, 40, .28);
}

/* Tone wash, hover-only here: the tile variant already has a surface, so a
   resting tint on top of it would muddy the card rather than identify it. */
.hpx-ig-tile .hpx-igcard::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    border-radius: inherit;
    background: radial-gradient(120% 96% at 100% 0%, var(--tone-bg) 0%, transparent 62%);
    opacity: 0;
    transition: opacity .28s ease;
    pointer-events: none;
}

body.theme-dark .hpx .hpx-ig-tile .hpx-igcard {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .06),
        0 1px 2px rgba(0, 0, 0, .38),
        0 10px 22px -18px rgba(0, 0, 0, .7);
}

/* `plain` is the value-prop treatment: airy, no card at rest. It still needs a
   resting presence, which it gets from the icon rather than from a box. */
.hpx-ig-plain .hpx-igcard { background: transparent; border: 1px solid transparent; }

.hpx-icongrid.is-centred .hpx-igcard { align-items: center; text-align: center; }

/* Tone halo behind the icon — the one piece of resting colour a plain card has,
   so six value props read as a designed set rather than as loose text. */
.hpx-ig-plain .hpx-igcard::before {
    content: '';
    position: absolute;
    top: 4px;
    left: 50%;
    width: 76px; height: 76px;
    border-radius: 50%;
    background: radial-gradient(circle, var(--tone-bg) 0%, transparent 68%);
    transform: translateX(-50%);
    opacity: .85;
    transition: opacity .26s ease, transform .26s var(--hpx-a-ease);
    pointer-events: none;
}

.hpx-icongrid:not(.is-centred) .hpx-ig-plain .hpx-igcard::before,
.hpx-ig-plain .hpx-icongrid:not(.is-centred) .hpx-igcard::before { left: 30px; }

.hpx-igcard > * { position: relative; z-index: 1; }

/* Lit, dimensional icon — the same treatment the Explore tiles use, so the two
   grids read as one system. */
.hpx-igcard .hpx-ico {
    background-image: linear-gradient(155deg, rgba(255, 255, 255, .58) 0%, rgba(255, 255, 255, 0) 58%);
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, .5),
        0 4px 10px -6px var(--tone-ink);
    transition: transform .26s var(--hpx-a-ease), box-shadow .26s ease;
}

.hpx-igcard-title {
    display: inline-flex; align-items: center; gap: 7px; flex-wrap: wrap;
    font-size: 14.5px; font-weight: 680; letter-spacing: -.012em;
    color: var(--hpx-ink); line-height: 1.35;
    transition: color .2s ease;
}

.hpx-icongrid.is-centred .hpx-igcard-title { justify-content: center; }

/* ink-2, not ink-3: at 12.5px the muted tone measured 4.37:1, which FAILS AA
   outright rather than merely looking washed out. */
.hpx-igcard-sub { font-size: 12.5px; line-height: 1.55; color: var(--hpx-ink-2); }

@media (hover: hover) {
    .hpx-ig-tile a.hpx-igcard:hover {
        transform: translateY(-3px);
        border-color: var(--tone-line);
        box-shadow: 0 20px 38px -28px var(--tone-ink), var(--hpx-sh-lift);
    }

    .hpx-ig-tile a.hpx-igcard:hover::after { opacity: 1; }

    body.theme-dark .hpx .hpx-ig-tile a.hpx-igcard:hover {
        box-shadow: 0 20px 38px -28px rgba(0, 0, 0, .85);
    }

    /* NOT `a.hpx-igcard`. A value-prop card has no URL, so the partial renders
       it as a <div> — every hover rule scoped to the anchor missed all six of
       them and the row had no hover at all. */
    .hpx-ig-plain .hpx-igcard:hover {
        transform: translateY(-3px);
        background: var(--hpx-card);
        border-color: var(--tone-line);
        box-shadow: 0 18px 34px -26px var(--tone-ink), var(--hpx-sh-soft);
    }

    .hpx-ig-plain .hpx-igcard:hover::before { opacity: 1; transform: translateX(-50%) scale(1.12); }
    .hpx-igcard:hover .hpx-igcard-title { color: var(--tone-ink); }
    .hpx-igcard:hover .hpx-ico { transform: scale(1.08) rotate(-4deg); }

    /* The arrow chip used to be the showcase card's alone. Every linked card
       gets it now — the Discover-More tiles are six navigation targets and had
       nothing on them to say so. */
    .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-go { opacity: 1; transform: translateX(0) scale(1); }
}

/* No hover on touch, so the chip would never appear — but the tile IS a link.
   It rests visible instead, the same compromise the feature cards make. */
@media (hover: none) {
    .hpx-ig-tile a.hpx-igcard .hpx-igcard-go { opacity: .5; transform: none; }
}

a.hpx-igcard:focus-visible {
    outline: none;
    border-color: var(--tone-ink);
    box-shadow: 0 0 0 3px var(--tone-line);
}

a.hpx-igcard:focus-visible .hpx-igcard-go { opacity: 1; transform: translateX(0) scale(1); }

/* ── 14b. The tile card ─────────────────────────────────────────────
   Six navigation targets, built to the supplied reference: a haloed icon plate
   with scattered tone dots, a title, three lines of copy, a LABELLED action,
   and a tone bar along the bottom edge that tells the six apart at a glance.

   What it replaced was a captioned box — icon, title, one line, and nothing at
   all to say the thing was a link. */

.hpx-ig-tile .hpx-igcard {
    padding: 26px 18px 22px;
    gap: 0;
    justify-content: flex-start;
    border-radius: 20px;
}

/* The bottom tone bar. Inset and short at rest so it reads as an accent, full
   bleed on hover so the card confirms the pointer is on it. */
.hpx-igcard-bar {
    position: absolute;
    inset-block-end: 0;
    inset-inline: 22%;
    z-index: 2;
    height: 4px;
    border-radius: 4px 4px 0 0;
    background: linear-gradient(90deg, var(--tone-bg) 0%, var(--tone-ink) 50%, var(--tone-bg) 100%);
    transition: inset-inline .34s var(--hpx-a-ease), height .24s ease;
    pointer-events: none;
}

/* ── The icon and its dots ──────────────────────────────────────── */

/* A SOLID tone fill with a white glyph — the same plate the library and
   category bands use, overriding the lit-tint treatment `.hpx-igcard .hpx-ico`
   carries from §14. Scoped to `.hpx-ig-tile` on purpose: the `feature` variant
   (Why choose) is a different card with its own lead-card logic and keeps the
   tinted chip.

   The sheen supplies the second gradient stop, so no per-tone "light twin"
   token has to exist. Geometry is unchanged — only the FILL — so the 52px
   sideways-phone override below still holds. */
.hpx-ig-tile .hpx-igcard .hpx-ico {
    position: relative;
    z-index: 1;
    width: 64px; height: 64px;
    border-radius: 19px;
    font-size: 25px;
    margin-bottom: 18px;
    background-color: var(--tone-ink);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .26) 100%);
    color: var(--hpx-ink-invert);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        0 10px 22px -10px var(--tone-ink);
}

/* The base chip draws its glyph at 55% of the plate, a ratio tuned for a tinted
   background where a large silhouette still reads as light. On a solid fill
   that is a white slab; 47% is the library plate's 22/46, as a percentage so
   the 52px phone variant scales with it instead of needing its own rule. */
.hpx-ig-tile .hpx-igcard .hpx-ico svg { width: 47%; height: 47%; }

.hpx-igcard-dots {
    position: absolute;
    inset-block-start: 18px;
    inset-inline: 0;
    z-index: 0;
    height: 76px;
    pointer-events: none;
}

.hpx-igcard-dots i {
    position: absolute;
    display: block;
    border-radius: 50%;
    background: var(--tone-ink);
    transition: opacity .3s ease, transform .4s var(--hpx-a-ease);
}

/* Four fixed positions rather than random ones: the set has to look identical
   on every card or it reads as noise instead of as a motif. */
.hpx-igcard-dots i:nth-child(1) { width: 6px; height: 6px; inset-inline-start: calc(50% - 62px); top: 22px;  opacity: .28; }
.hpx-igcard-dots i:nth-child(2) { width: 4px; height: 4px; inset-inline-start: calc(50% - 48px); top: 52px;  opacity: .18; }
.hpx-igcard-dots i:nth-child(3) { width: 5px; height: 5px; inset-inline-start: calc(50% + 44px); top: 12px;  opacity: .34; }
.hpx-igcard-dots i:nth-child(4) { width: 4px; height: 4px; inset-inline-start: calc(50% + 58px); top: 44px;  opacity: .2; }

/* ── Copy ───────────────────────────────────────────────────────── */

.hpx-ig-tile .hpx-igcard-title { font-size: 17px; font-weight: 740; }

.hpx-ig-tile .hpx-igcard-sub {
    margin-top: 9px;
    font-size: 13px;
    line-height: 1.62;
    color: var(--hpx-ink-2);
}

/* ── The action ─────────────────────────────────────────────────── */

/* Tone-tinted, with the hue's own ink — the reference's treatment, and at
   roughly 6:1 in light and 8:1 in dark it is comfortably legible. It fills with
   the solid tone on hover, the same rest→fill move the tool cards make.

   THE LABEL WRAPS, and that is not cosmetic. Six cards across a 1200px band are
   175px wide, which leaves 137px of button — and "Explore Companies" plus its
   arrow needs 168px. FOUR of the six labels were being clipped on a 1440px
   desktop. No font size fixes that at this column count: shrinking the type
   only moves the failure to the next viewport down, and these labels are the
   whole point of the button ("Explore Companies" says where it goes; a bare
   arrow does not). So the label is allowed to take two lines, and every button
   reserves the height for two whether it needs them or not — otherwise a row of
   six has three tall buttons and three short ones. */
.hpx .hpx-igcard-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    width: 100%;
    /* Two lines' worth, always. Around 1280px some labels wrap and some do not,
       so without this the row is 44px/49px/44px/49px — five pixels of ragged
       that is only visible because the six sit side by side. It also clears the
       44px minimum touch target with room to spare. */
    min-height: 50px;
    margin-top: auto;
    padding: 8px 12px;
    border-radius: 12px;
    background-color: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 12.5px;
    font-weight: 720;
    line-height: 1.3;
    letter-spacing: -.004em;
    text-align: center;
    box-shadow: inset 0 0 0 1px var(--tone-line);
    transition: background-color .2s ease, background-image .2s ease, color .2s ease,
                box-shadow .22s ease, gap .2s ease;
}

/* `margin-top: auto` needs somewhere to push from, and the copy above it is
   variable-length — this is what pins six buttons to one line. */
.hpx-ig-tile .hpx-igcard-sub { margin-bottom: 18px; }

/* `balance` splits "Explore Companies" evenly rather than leaving one word
   stranded on line two. Unsupported browsers just wrap normally. */
.hpx-igcard-btnlabel { text-wrap: balance; }
.hpx-igcard-btn svg { flex: 0 0 auto; width: 13px; height: 13px; transition: transform .2s var(--hpx-a-ease); }
[dir="rtl"] .hpx-igcard-btn svg { transform: scaleX(-1); }

@media (hover: hover) {
    .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-bar { inset-inline: 0; height: 5px; }

    .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-dots i:nth-child(1) { transform: translate(-3px, -4px); opacity: .5; }
    .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-dots i:nth-child(2) { transform: translate(-5px, 3px);  opacity: .34; }
    .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-dots i:nth-child(3) { transform: translate(4px, -3px);  opacity: .56; }
    .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-dots i:nth-child(4) { transform: translate(6px, 4px);   opacity: .38; }

    .hpx .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-btn {
        gap: 11px;
        background-color: var(--tone-ink);
        background-image: linear-gradient(160deg, rgba(255, 255, 255, .2) 0%, rgba(255, 255, 255, 0) 58%);
        color: var(--hpx-ink-invert);
        box-shadow: 0 10px 20px -10px var(--tone-ink);
    }

    /* The dark twins of `--tone-ink` are LIGHTENED so they can serve as ink on a
       dark surface, so white on one measures about 1.5:1. Neutral black rather
       than the blue-black scrim used on large surfaces: on a small pill the
       blue-black desaturates the hue (amber goes khaki, rose goes mauve). */
    body.theme-dark .hpx .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-btn {
        background-image:
            linear-gradient(0deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, .5)),
            linear-gradient(160deg, rgba(255, 255, 255, .2) 0%, rgba(255, 255, 255, 0) 58%);
        box-shadow: 0 10px 20px -10px rgba(0, 0, 0, .9);
    }

    .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-btn svg { transform: translateX(3px); }
    [dir="rtl"] .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-btn svg { transform: scaleX(-1) translateX(3px); }
}

/* Keyboard parity — the card is a link and a keyboard visitor gets the same
   "this one is active" state a pointer visitor does. */
.hpx-ig-tile a.hpx-igcard:focus-visible .hpx-igcard-bar { inset-inline: 0; height: 5px; }

.hpx .hpx-ig-tile a.hpx-igcard:focus-visible .hpx-igcard-btn {
    background-color: var(--tone-ink);
    color: var(--hpx-ink-invert);
}

body.theme-dark .hpx .hpx-ig-tile a.hpx-igcard:focus-visible .hpx-igcard-btn {
    background-image: linear-gradient(0deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, .5));
}

/* `is-noflank` kept as a hook — no band uses it now that Discover More takes
   the full showcase head, flanks and all, but it is one class away for any band
   that wants the eyebrow pill on its own. */
.hpx-al-center .hpx-head.is-showcase.is-noflank .hpx-title::before,
.hpx-al-center .hpx-head.is-showcase.is-noflank .hpx-title::after { display: none; }

/* ── Phone: one column, laid out sideways ───────────────────────────
   Two columns stop working here, and not by a little. At 390px a tile is 166px
   wide, which leaves 128px of button — and "Explore Companies" plus its arrow
   needs 168px. FIVE of the six labels overflowed their button. Narrowing the
   type would only move the failure to the next phone down.

   So below 480px the card turns sideways: the plate beside the copy, the button
   full width underneath. Each card comes in around 150px instead of 300px, so
   six of them stack shorter than the two-column version they replace, and every
   label has room to be read. */
@media (max-width: 480px) {
    .hpx-icongrid.hpx-ig-tile { grid-template-columns: 1fr; gap: 10px; }

    .hpx-icongrid.is-centred.hpx-ig-tile .hpx-igcard {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        align-items: center;
        align-content: center;
        gap: 0 14px;
        padding: 16px 16px 14px;
        text-align: start;
    }

    .hpx-ig-tile .hpx-igcard .hpx-ico {
        grid-column: 1;
        grid-row: 1 / span 2;
        width: 52px; height: 52px;
        border-radius: 16px;
        font-size: 21px;
        margin-bottom: 0;
    }

    .hpx-icongrid.is-centred.hpx-ig-tile .hpx-igcard-title {
        grid-column: 2; grid-row: 1;
        justify-content: flex-start;
        font-size: 15.5px;
    }

    .hpx-ig-tile .hpx-igcard-sub {
        grid-column: 2; grid-row: 2;
        margin-top: 2px;
        margin-bottom: 0;
        font-size: 12.5px;
        line-height: 1.5;
    }

    .hpx .hpx-ig-tile .hpx-igcard-btn {
        grid-column: 1 / -1;
        grid-row: 3;
        margin-top: 14px;
        justify-content: space-between;
        padding: 10px 14px;
    }

    /* The dots are positioned against a centred plate; sideways they would land
       on the copy. */
    .hpx-igcard-dots { display: none; }

    .hpx-igcard-bar { inset-inline: 16%; }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-igcard-bar, .hpx-igcard-dots i, .hpx-igcard-btn, .hpx-igcard-btn svg { transition: none; }
    .hpx-ig-tile a.hpx-igcard:hover .hpx-igcard-dots i { transform: none; }
}

body.theme-dark .hpx .hpx-igcard .hpx-ico {
    background-image: linear-gradient(155deg, rgba(255, 255, 255, .09) 0%, rgba(255, 255, 255, 0) 58%);
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, .07),
        0 4px 10px -6px rgba(0, 0, 0, .8);
}

/* The dark twins of `--tone-ink` are LIGHTENED so they can serve as ink on a
   dark surface, which is what makes them a poor fill under a white glyph —
   white on #8CBEFA measures 2.4:1. A scrim deepens the fill without touching
   the token, and takes every tone past 4.2:1.

   (0,5,1) against the (0,4,1) rule directly above, so it wins on specificity
   rather than on source order — the feature variant is left on the tinted
   treatment either way. */
body.theme-dark .hpx .hpx-ig-tile .hpx-igcard .hpx-ico {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
        linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 10px 22px -10px rgba(0, 0, 0, .8);
}

body.theme-dark .hpx .hpx-ig-plain .hpx-igcard:hover {
    box-shadow: 0 18px 34px -26px rgba(0, 0, 0, .85);
}

/* ── 14b. `feature` — the value-prop showcase ────────────────────────
   A lead card in the brand gradient followed by tinted cards, each with a
   short tone rule under its title. Used by Why-choose.

   Every coloured rule below is written `.hpx .hpx-x`, i.e. (0,2,0). The
   stylesheet neutralises Tabler's global link colour with `.hpx a{color:
   inherit}` at (0,1,1), which out-ranks any single-class colour however late
   it appears — that is exactly how a white label on a blue card ends up
   painted in dark body ink and reads as "the text is hidden". */

.hpx-ig-feature { align-items: stretch; }

/* A card flagged `meta.mobile_only` exists to square off the two-column phone
   grid — the lead card takes a full row, so an odd number of the rest leaves a
   gap beside the last one. `display: none` above the phone breakpoint, so it is
   out of the flow entirely rather than an invisible grid cell holding a column
   open on desktop. The ≤640 block re-shows it. */
.hpx-igcard.is-mobileonly { display: none; }

.hpx-ig-feature .hpx-igcard {
    gap: 0;
    padding: 26px 20px 24px;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r);
    overflow: hidden;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 12px 26px -20px rgba(16, 24, 40, .3);
}

body.theme-dark .hpx .hpx-ig-feature .hpx-igcard {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .06),
        0 1px 2px rgba(0, 0, 0, .4),
        0 12px 26px -20px rgba(0, 0, 0, .75);
}

/* Tone wash from the top, hidden at rest. The card already has a surface, so a
   resting tint would muddy it rather than identify it. */
.hpx-ig-feature .hpx-igcard::after {
    content: '';
    position: absolute;
    inset: 0;
    /* 0, not -1: the card sets no z-index of its own, so a negative pseudo
       would escape to the band's stacking context and paint UNDER the card.
       `.hpx-igcard > *` already lifts the content to 1 above this. */
    z-index: 0;
    border-radius: inherit;
    background: radial-gradient(120% 78% at 50% 0%, var(--tone-bg) 0%, transparent 68%);
    opacity: 0;
    transition: opacity .3s ease;
    pointer-events: none;
}

/* A rounded SQUARE, not a disc — matched to the tile card's plate so the two
   icongrid bands read as one family rather than as two different components
   that happen to share a grid. Same 64/19px metrics, the same sheen, and the
   glow underneath in the plate's own hue. */
.hpx-ig-feature .hpx-ico {
    position: relative;
    z-index: 1;
    width: 64px; height: 64px;
    font-size: 25px;
    margin-bottom: 18px;
    border-radius: 19px;
}

/* A SOLID tone fill with a white glyph, matching the tile variant next door.
   `:not(.is-lead)` is load-bearing, not tidiness: the lead card sits on the
   brand gradient and carries a WHITE plate with brand ink, and the dark twin
   below would otherwise out-rank that rule at (0,6,1) vs (0,4,0) and drop a
   scrim over the white plate. Excluding the card is the only version that
   cannot go wrong in one theme while looking right in the other. */
.hpx-ig-feature .hpx-igcard:not(.is-lead) .hpx-ico {
    background-color: var(--tone-ink);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .26) 100%);
    color: var(--hpx-ink-invert);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        0 10px 22px -10px var(--tone-ink);
}

.hpx-ig-feature .hpx-igcard:not(.is-lead) .hpx-ico svg { width: 47%; height: 47%; }

/* Deepens the lightened dark twins so a white glyph clears AA. Placed here so
   it out-ranks the shared `body.theme-dark .hpx .hpx-igcard .hpx-ico` above. */
body.theme-dark .hpx .hpx-ig-feature .hpx-igcard:not(.is-lead) .hpx-ico {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
        linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 10px 22px -10px rgba(0, 0, 0, .8);
}

/* The dots are the tile card's motif, borrowed. On the lead card they sit on
   the brand gradient, where a tone-ink dot is all but invisible — white at a
   low opacity is the only version that reads on both surfaces. */
.hpx .hpx-ig-feature .is-lead .hpx-igcard-dots i { background: #fff; }

/* Tone at REST, not only on hover. The reference card carries its hue in the
   title, and these cards are no longer links — there is no hover state left to
   put it in. The lead card overrides this back to invert at (0,4,0). */
.hpx .hpx-ig-feature .hpx-igcard-title {
    display: block;
    font-size: 16px;
    font-weight: 740;
    letter-spacing: -.016em;
    color: var(--tone-ink);
    /* Two lines' worth, always. At six across, "Verified & Trusted" wraps and
       "Comprehensive" does not — without a floor the rule and the copy under
       each title land at a different height on every other card, and the row
       stops reading as a set. 2 × the inherited 1.35 line-height. */
    min-height: 2.7em;
}

/* The rule is the resting colour a feature card gets instead of a tinted
   surface, and the one element that moves on hover. */
.hpx-igcard-rule {
    display: block;
    width: 26px; height: 3px;
    margin: 11px 0 12px;
    border-radius: 999px;
    background: var(--tone-ink);
    opacity: .9;
    transition: width .3s var(--hpx-a-ease), opacity .2s ease;
}

.hpx-icongrid.is-centred .hpx-igcard-rule { margin-left: auto; margin-right: auto; }

.hpx-ig-feature .hpx-igcard-sub {
    display: block;
    font-size: 13.5px;
    line-height: 1.6;
    color: var(--hpx-ink-2);
}

/* Arrow chip — the affordance that says the card is a link. It occupies no
   layout space at rest, so a linked card and an unlinked one are the same
   height. */
.hpx-igcard-go {
    position: absolute;
    inset-block-start: 16px;
    inset-inline-end: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px; height: 26px;
    border-radius: 50%;
    background: var(--tone-bg);
    color: var(--tone-ink);
    opacity: 0;
    transform: translateX(-6px) scale(.85);
    transition: opacity .24s ease, transform .28s var(--hpx-a-ease);
    pointer-events: none;
}

.hpx-igcard-go svg { width: 13px; height: 13px; }

/* RTL: the glyph points at the reading direction, not at a fixed edge. */
[dir="rtl"] .hpx-igcard-go svg { transform: scaleX(-1); }

/* ── The lead card ───────────────────────────────────────────────── */

/* The signature button gradient, plus a scrim.

   White on `--hpx-brand` alone measures 3.84:1 in light mode and 2.6:1 in dark
   — the dark twins are LIGHTENED so they can serve as ink on a dark surface,
   which is exactly what makes them a poor fill under white text. The scrim
   deepens the fill without touching the tokens (nothing else that uses brand
   blue would want to be dragged along), and takes both themes past 4.5:1. */
.hpx .hpx-ig-feature .hpx-igcard.is-lead {
    background-color: var(--hpx-brand-700);
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .12), rgba(9, 18, 33, .12)),
        linear-gradient(135deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 62%, var(--hpx-brand-700) 100%);
    border-color: transparent;
    color: var(--hpx-ink-invert);
    box-shadow: 0 18px 40px -24px var(--hpx-brand-glow), 0 2px 4px rgba(16, 24, 40, .08);
}

body.theme-dark .hpx .hpx-ig-feature .hpx-igcard.is-lead {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .34), rgba(9, 18, 33, .34)),
        linear-gradient(135deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 62%, var(--hpx-brand-700) 100%);
}

/* Soft light pooling in the bottom corner, so the fill is not a flat slab. */
.hpx .hpx-ig-feature .hpx-igcard.is-lead::after {
    background: radial-gradient(90% 70% at 18% 108%, rgba(255, 255, 255, .28) 0%, transparent 62%);
    opacity: 1;
}

/* Both at full white. The hierarchy is carried by size and weight — dimming
   the copy to .92 was buying a little elegance with the one thing this card
   cannot spare, which is contrast against its own fill. */
.hpx .hpx-ig-feature .is-lead .hpx-igcard-title,
.hpx .hpx-ig-feature .is-lead .hpx-igcard-sub { color: var(--hpx-ink-invert); }

.hpx .hpx-ig-feature .is-lead .hpx-ico {
    background: rgba(255, 255, 255, .96);
    background-image: none;
    color: var(--hpx-brand-600);
    box-shadow: 0 8px 18px -10px rgba(0, 0, 0, .45);
}

.hpx .hpx-ig-feature .is-lead .hpx-igcard-rule {
    background: rgba(255, 255, 255, .82);
    opacity: 1;
}

.hpx .hpx-ig-feature .is-lead .hpx-igcard-go {
    background: rgba(255, 255, 255, .2);
    color: var(--hpx-ink-invert);
}

/* The corner ribbon. Absolute, so a two-word promise and a five-word one leave
   the icon at the same height on every card in the row. */
.hpx .hpx-igcard-chip {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    padding: 6px 12px 6px 14px;
    border-end-end-radius: var(--hpx-r-md);
    background: rgba(255, 255, 255, .2);
    color: var(--hpx-ink-invert);
    font-size: 10px;
    font-weight: 750;
    letter-spacing: .1em;
    text-transform: uppercase;
    line-height: 1.4;
    backdrop-filter: blur(2px);
}

/* A ribbon on a plain (non-lead) card has no gradient to sit on. */
.hpx .hpx-ig-feature .hpx-igcard:not(.is-lead) .hpx-igcard-chip {
    background: var(--tone-bg);
    color: var(--tone-ink);
}

/* The lead card is taller than its copy needs, and centring the block inside
   it is what keeps the six-card row optically level. */
.hpx-ig-feature .hpx-igcard.is-lead { justify-content: center; }

@media (hover: hover) {
    .hpx-ig-feature a.hpx-igcard:hover,
    .hpx-ig-feature a.hpx-igcard:focus-visible {
        transform: translateY(-5px);
        border-color: var(--tone-line);
        box-shadow: 0 26px 44px -28px var(--tone-ink), var(--hpx-sh-lift);
    }

    .hpx-ig-feature a.hpx-igcard:hover::after,
    .hpx-ig-feature a.hpx-igcard:focus-visible::after { opacity: 1; }

    .hpx-ig-feature a.hpx-igcard:hover .hpx-igcard-go,
    .hpx-ig-feature a.hpx-igcard:focus-visible .hpx-igcard-go {
        opacity: 1;
        transform: translateX(0) scale(1);
    }

    /* `a.` on BOTH: these two were the only hover reactions in the band not
       scoped to a link, so a card with no URL still grew a rule and tilted its
       icon under the pointer — a thing that responds like a control and then
       does nothing when clicked. */
    .hpx-ig-feature a.hpx-igcard:hover .hpx-igcard-rule { width: 46px; }
    .hpx-ig-feature a.hpx-igcard:hover .hpx-ico { transform: scale(1.07) rotate(-5deg); }

    .hpx .hpx-ig-feature a.hpx-igcard.is-lead:hover {
        border-color: transparent;
        box-shadow: 0 30px 54px -26px var(--hpx-brand-glow), 0 3px 6px rgba(16, 24, 40, .1);
    }

    /* The lead card is a gradient, not a tinted surface — a tone wash on top of
       it would only dull the brand. It gets a light sweep instead. */
    .hpx .hpx-ig-feature a.hpx-igcard.is-lead:hover::after {
        background: radial-gradient(90% 70% at 18% 108%, rgba(255, 255, 255, .38) 0%, transparent 66%);
    }

    body.theme-dark .hpx .hpx-ig-feature a.hpx-igcard:hover {
        box-shadow: 0 26px 44px -28px rgba(0, 0, 0, .88);
    }
}

/* The card is the link, so the ring belongs on the card. Written at (0,2,0)
   for the same reason the colours are. */
.hpx .hpx-ig-feature a.hpx-igcard:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--hpx-brand-line), 0 26px 44px -28px var(--tone-ink);
}

/* ---------------------------------------------------------------------------
   15. Explore grid
   ------------------------------------------------------------------------ */

.hpx-explore {
    display: grid;
    grid-template-columns: repeat(var(--hpx-cd), minmax(0, 1fr));
    gap: 14px;
}

/* The install tile is phone-only — an "add to home screen" offer on a desktop
   grid is noise, and the tile also exists to square off the two-column phone
   layout, which desktop's three or four columns do not need. The ≤640 block
   re-shows it (and keeps `[hidden]` winning over that). */
.hpx-exp.is-pwa { display: none; }

/* It is a <button>, so it needs the reset the anchors never did. `text-align`
   and `font` in particular: a button inherits neither. */
.hpx .hpx-exp.is-pwa {
    width: 100%;
    margin: 0;
    font: inherit;
    text-align: start;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.hpx-exp.is-pwa.is-busy { pointer-events: none; opacity: .7; }

.hpx-exp {
    position: relative;
    overflow: hidden;
    display: flex;
    /* stretch, not flex-start: the text column has to fill the card's height
       for the count pill's `margin-top: auto` to have any free space to
       absorb. The icon opts out below so it still sits at the top. */
    align-items: stretch;
    gap: 13px;
    padding: 17px 16px 16px;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r-md);
    /* Resting depth. The card used to sit perfectly flat and only gained a
       shadow on hover, so at rest fifteen tiles read as a table rather than as
       cards — and on touch, where hover never comes, that was the only state
       anyone ever saw. Two layers: a 1px contact shadow to seat it against the
       page, and a wide soft one for lift. */
    box-shadow:
        0 1px 2px rgba(16, 24, 40, .04),
        0 10px 22px -18px rgba(16, 24, 40, .30);
    transition: transform .26s var(--hpx-a-ease), border-color .24s ease, box-shadow .26s ease;
}

/* Tone wash — each card blooms in its OWN hue rather than all fifteen going
   brand-blue, which is what makes a fifteen-tile grid read as a map instead of
   a list. z-index 0 with the content lifted to 1: a negative z-index would put
   it behind the card's own background and it would never be seen. */
.hpx-exp::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background: radial-gradient(128% 104% at 100% 0%, var(--tone-bg) 0%, transparent 64%);
    /* Carried at a third of strength at rest, not zero. Each tile then owns its
       hue the moment the grid is painted — which is what makes fifteen cards
       scan as a colour-coded map — while hover still has somewhere to go.

       Through a CUSTOM PROPERTY, not a themed override of this rule. Dark mode
       wants a stronger rest tint, but `body.theme-dark .hpx .hpx-exp::before`
       is (0,3,2) and would outrank `.hpx-exp:hover::before` at (0,2,1) — which
       silently pinned the wash at its resting value and killed the hover
       brighten in dark mode only. Theming the variable keeps the hover rule the
       most specific thing that sets opacity. */
    opacity: var(--hpx-exp-wash, .34);
    transition: opacity .28s ease;
}

/* Hairline that wipes across the top edge in the card's tone. */
.hpx-exp::after {
    content: '';
    position: absolute;
    inset-inline: 0;
    top: 0;
    z-index: 1;
    height: 2px;
    background: linear-gradient(90deg, var(--tone-ink), transparent 82%);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform .34s var(--hpx-a-ease);
}

.hpx-exp > * { position: relative; z-index: 1; }

/* The tile was a flat swatch of --tone-bg. A 155° sheen across it plus a 1px
   inset ring gives it a lit edge and a body, which is the difference between a
   coloured square and an object. `background-image` rather than `background`
   so the tone colour underneath survives — the shorthand would wipe it. */
/* Sized to the header mega's Browse tile — 42px at radius 12 — so the two
   surfaces read as the same component. The band's generic --hpx-r-md (14px)
   is a card radius; on a 42px square it rounds too hard and the chip stops
   looking like the mega's. Icons switched from FA solids to Lucide strokes at
   the same time (see 2026_08_09_150000_explore_band_lucide_icons). */
.hpx-exp .hpx-ico {
    align-self: flex-start;
    width: 42px;
    height: 42px;
    border-radius: 12px;
    /* A SOLID tone fill with a white glyph — the house plate the library,
       category, icongrid, panel, newsroom, trust and hero-counter bands all
       carry. This chip used to be a TINT deliberately matched to the header
       mega's Browse tile; the mobile drawer moved to the solid plate at the
       same time, so the pairing now runs through the drawer rather than the
       mega. 12/42 is already the reference 13/46 radius ratio, so only the
       fill changes. */
    background-color: var(--tone-ink);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .26) 100%);
    color: var(--hpx-ink-invert);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        0 6px 14px -8px var(--tone-ink);
    transition:
        transform .3s cubic-bezier(.34, 1.56, .64, 1),
        background-color .24s ease,
        box-shadow .24s ease;
}

/* The stroke glyph needs to sit larger in the chip than a solid FA glyph did —
   .hpx-ico svg { width:55% } was tuned for a filled silhouette and leaves a
   line icon looking undersized and thin inside a 42px tile. */
.hpx-exp .hpx-ico svg { width: 22px; height: 22px; }

.hpx-exp-text { display: flex; flex-direction: column; gap: 6px; min-width: 0; flex: 1 1 auto; }

/* Only the TITLE keeps clear of the pinned arrow. The description runs the full
   width of the card, which is the difference between "500+ open / source
   projects / & templates" on three ragged lines and two clean ones. */
.hpx-exp-title {
    display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap;
    padding-inline-end: 30px;
    font-size: 14.5px; font-weight: 680; letter-spacing: -.012em;
    color: var(--hpx-ink);
    transition: color .2s ease;
}

/* ink-2, not ink-3. At 12px the muted tone measured 4.60:1 — over the AA line
   by a rounding error and reading as washed out. */
.hpx-exp-sub {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--hpx-ink-2);
}

/* The live count. A pill rather than another line of prose, because it is the
   one thing on the card that is a fact rather than a promise — and it is the
   element that replaced the invented figures the descriptions used to carry.

   `margin-top: auto` pins it to the bottom of the card, so a one-line
   description and a two-line one still line their counts up across the grid. */
.hpx .hpx-exp-count {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    align-self: flex-start;
    margin-top: auto;
    padding: 3px 9px;
    border-radius: var(--hpx-r-pill);
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 11px;
    font-weight: 600;
    line-height: 1.5;
    letter-spacing: -.002em;
    transition: background-color .22s ease, color .22s ease;
}

.hpx .hpx-exp-count strong { font-weight: 760; font-size: 11.5px; }

/* A chip, and never fully hidden. The arrow used to sit at opacity 0 until
   hover, so on every touch device — where there is no hover — the card had no
   affordance at all.

   Pinned rather than a flex item: as a sibling of the text it took 26px plus a
   13px gap out of every description's measure, and fifteen cards' worth of that
   is what made the copy wrap ragged.

   `--hpx-card-2` is an ELEVATED surface in both themes — lighter than the card
   in dark, faintly darker in light. The page background is not: using it here
   made the chip darker than the card it sits on, so in dark mode it read as a
   hole punched through the card rather than as a button. */
.hpx-exp-arrow {
    position: absolute;
    top: 16px;
    inset-inline-end: 14px;
    width: 26px; height: 26px;
    padding: 5px;
    border-radius: 999px;
    background: var(--hpx-card-2);
    border: 1px solid var(--hpx-line);
    color: var(--hpx-ink-3);
    box-shadow: 0 1px 2px rgba(16, 24, 40, .05);
    opacity: .78;
    transition: opacity .24s ease, background-color .24s ease, border-color .24s ease,
                color .24s ease, transform .26s var(--hpx-a-ease), box-shadow .24s ease;
}

@media (hover: hover) {
    .hpx-exp:hover {
        transform: translateY(-3px);
        border-color: var(--tone-line);
        box-shadow: 0 22px 40px -26px var(--tone-ink), var(--hpx-sh-lift);
    }

    .hpx-exp:hover::before { opacity: 1; }
    .hpx-exp:hover::after  { transform: scaleX(1); }

    .hpx-exp:hover .hpx-exp-title { color: var(--tone-ink); }

    /* Chip hover matched to the mega Browse tile: the same spring curve, the
       same lift-scale-rotate, and the tone ring tightening into a soft glow.
       The chip used to only scale — it changed size without ever changing
       colour, so it read as a zoom rather than as the tile waking up.

       color-mix rather than rgb(var(--t)/a): this band's tones arrive as
       finished colours from hp_tokens, not as RGB triplets. If a browser drops
       color-mix the declaration is simply ignored and the chip keeps its rest
       ring, which is a safe degradation. */
    /* The plate is already the solid tone at rest, so hover deepens the glow
       rather than mixing a new fill — the old `color-mix` blended toward the
       pale `--tone-bg`, which on a solid plate LIGHTENS it under a white glyph
       and loses contrast exactly when the pointer is on it. */
    .hpx-exp:hover .hpx-ico {
        transform: translateY(-1px) scale(1.07) rotate(-4deg);
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .24),
            0 10px 20px -9px var(--tone-ink);
    }

    body.theme-dark .hpx .hpx-exp:hover .hpx-ico {
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .14),
            0 10px 20px -9px rgba(0, 0, 0, .9);
    }

    /* Scale, not translate. The card is `overflow: hidden` so the rounded
       corners can clip the wash, and a chip that slides toward an edge it can
       be clipped by is a glitch waiting for the first narrow column. */
    .hpx-exp:hover .hpx-exp-arrow {
        opacity: 1;
        background: var(--tone-ink);
        border-color: var(--tone-ink);
        color: var(--hpx-card);
        transform: scale(1.08);
    }
}

/* Keyboard parity — these are links, and the tile carried no focus state. */
.hpx-exp:focus-visible {
    outline: none;
    border-color: var(--tone-ink);
    box-shadow: 0 0 0 3px var(--tone-line), var(--hpx-sh-lift);
}

.hpx-exp:focus-visible::before { opacity: 1; }
.hpx-exp:focus-visible::after  { transform: scaleX(1); }

.hpx-exp:active { transform: translateY(-1px) scale(.994); }

/* The wash is tuned to a light card; on the dark surface the same tint is the
   whole point, so it stays — but the lift shadow becomes plain black or it
   glows. */
/* The light-mode lift is a tone-tinted glow tuned to a white card; on the dark
   surface that same glow turns to haze, so depth comes from black instead.
   The tone still reads — it is carrying the wash, the rule, the title and the
   arrow. */
body.theme-dark .hpx .hpx-exp:hover {
    box-shadow: 0 22px 40px -26px rgba(0, 0, 0, .85);
}

/* Resting depth, dark twin. The light shadow is barely-there ink on white; on a
   dark surface it needs real black to register at all. */
body.theme-dark .hpx .hpx-exp {
    box-shadow:
        0 1px 2px rgba(0, 0, 0, .35),
        0 10px 22px -18px rgba(0, 0, 0, .7);
}

/* A 58% white sheen belongs on a light tile and blows out a dark one — the tint
   is already only ~18% alpha here, so the highlight has to drop with it.

   The RING, though, is the tone in both themes. This rule used to force a flat
   `rgba(255,255,255,.07)` edge, and at (0,4,1) it outranked the tone ring set
   on `.hpx-exp .hpx-ico` (0,2,0) — so the light theme got a tinted edge and
   dark silently fell back to a white one on all thirty chips. --tone-line
   carries real dark twins (blue rgba(96,164,245,.28), emerald
   rgba(52,211,153,.30) …), which is the same treatment the mega Browse tile
   uses in dark. The resting drop shadow goes for the same reason it went in
   light: the mega has none. */
/* Deepens the lightened dark twins so a white glyph clears AA — the same
   scrim the other seven bands use. (0,4,1), which is what it has to be: the
   `--tone-line` ring this rule used to force outranked the resting rule at
   (0,2,0), and that mismatch is documented above. */
body.theme-dark .hpx .hpx-exp .hpx-ico {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
        linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 6px 14px -8px rgba(0, 0, 0, .8);
}

body.theme-dark .hpx .hpx-exp-arrow { box-shadow: 0 1px 2px rgba(0, 0, 0, .4); }

/* The tint reads as colour on white; on near-black it needs a touch more to
   register as the same identity cue. Set on the CARD as a variable — see the
   note on `.hpx-exp::before` for why this must not target the pseudo-element
   directly. */
body.theme-dark .hpx .hpx-exp { --hpx-exp-wash: .46; }

@media (max-width: 640px) {
    .hpx-exp { padding: 14px 13px; gap: 11px; }

    /* The install tile joins the grid on phones. `[hidden]` still has to win:
       the kit only unhides it once the browser confirms an install is possible,
       and a (0,2,0) `display: flex` would otherwise out-rank the UA sheet's
       `[hidden] { display: none }` and show a tile that cannot do anything. */
    .hpx-exp.is-pwa { display: flex; }
    .hpx-exp.is-pwa[hidden] { display: none; }
    .hpx-exp-title { font-size: 14px; padding-inline-end: 26px; }
    .hpx-exp-sub { font-size: 12px; }
    .hpx-exp-arrow { top: 13px; inset-inline-end: 11px; width: 24px; height: 24px; padding: 4.5px; }
    /* No hover to come back from on touch. */
    .hpx-exp:hover { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-exp, .hpx-exp::before, .hpx-exp::after, .hpx-exp-arrow, .hpx-exp .hpx-ico { transition: none; }
    .hpx-exp:hover, .hpx-exp:active { transform: none; }
}

/* ---------------------------------------------------------------------------
   16. Trust band
   A rating panel beside a row of proof counters, with an assurance strip
   underneath.

   Both the counters and the strip items can be links, so EVERY colour below is
   written `.hpx .hpx-x` — (0,2,0). `.hpx a{color:inherit}` is (0,1,1) and beats
   any single-class rule regardless of source order; that is the trap behind
   every "the text is hidden" report on this page.
   ------------------------------------------------------------------------ */

.hpx-trust {
    position: relative;
    overflow: hidden;
    display: grid;
    grid-template-columns: minmax(0, .92fr) minmax(0, 1.62fr);
    gap: 30px;
    align-items: center;
    padding: 30px 32px;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r);
    /* Glass edge + real depth. A single soft shadow left the band reading as a
       plain box under a row of coloured icons. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 14px 34px -22px rgba(16, 24, 40, .28);
}

/* Brand wash from the trailing corner, where the numbers live. */
.hpx-trust::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background: radial-gradient(120% 130% at 100% 0%, var(--hpx-brand-soft) 0%, transparent 58%);
    pointer-events: none;
}

.hpx-trust > * { position: relative; z-index: 1; }

/* ── Left: the rating panel ─────────────────────────────────────── */

.hpx-trust-copy {
    position: relative;
    padding-inline-end: 30px;
}

/* Hairline between the claim and the evidence. `::after` on the copy rather
   than a border on the stats, so it disappears with the copy column when the
   band stacks instead of stranding a rule across the top of the numbers. */
.hpx-trust-copy::after {
    content: '';
    position: absolute;
    inset-block: 4px;
    inset-inline-end: 0;
    width: 1px;
    background: linear-gradient(180deg, transparent, var(--hpx-line) 18%, var(--hpx-line) 82%, transparent);
}

.hpx .hpx-trust-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 14px;
    padding: 6px 14px 6px 11px;
    background: var(--hpx-brand-soft);
    border: 1px solid var(--hpx-brand-line);
    border-radius: var(--hpx-r-pill);
    color: var(--hpx-brand-ink);
    font-size: 11px;
    font-weight: 750;
    letter-spacing: .09em;
    text-transform: uppercase;
    line-height: 1.5;
}

.hpx .hpx-trust-chip i { font-size: 11px; color: var(--hpx-brand); }

.hpx-trust-title {
    font-size: calc(var(--hpx-fs-h3, 20px) * 1.34);
    line-height: 1.2;
    font-weight: 760;
    letter-spacing: -.024em;
}

.hpx-trust-rate {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 14px;
    flex-wrap: wrap;
    font-size: 13px;
    color: var(--hpx-ink-3);
}

.hpx-trust-rate strong {
    color: var(--hpx-ink);
    font-size: 21px;
    font-weight: 780;
    letter-spacing: -.02em;
}

.hpx-stars { display: inline-flex; gap: 2px; color: var(--hpx-t-amber-ink, #A85B08); font-size: 15px; }
.hpx-stars.is-sm { font-size: 11.5px; }

/* ── The stack ──────────────────────────────────────────────────── */

.hpx-trust-people { margin-top: 20px; }

.hpx-faces { display: inline-flex; align-items: center; }

.hpx-face {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px; height: 40px;
    flex: 0 0 auto;
    overflow: hidden;
    border-radius: 50%;
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 14px;
    /* The ring is the card colour, not white — on the dark theme a white ring
       around every disc reads as a string of holes punched in the panel. */
    box-shadow: 0 0 0 3px var(--hpx-card), 0 4px 10px -6px rgba(16, 24, 40, .5);
    transition: transform .28s var(--hpx-a-ease), box-shadow .24s ease;
}

.hpx-face + .hpx-face { margin-inline-start: -13px; }

/* Later discs sit on top, so the stack overlaps in reading order rather than
   against it. */
.hpx-face { z-index: calc(var(--hpx-i, 0) + 1); }

.hpx-face img { width: 100%; height: 100%; object-fit: cover; }

.hpx .hpx-face.is-count {
    z-index: 20;
    background: linear-gradient(135deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 100%);
    color: var(--hpx-ink-invert);
    font-size: 11.5px;
    font-weight: 750;
    letter-spacing: -.01em;
}

.hpx-trust-join {
    display: block;
    margin-top: 11px;
    font-size: 12.5px;
    color: var(--hpx-ink-3);
}

/* ── Right: the counters ────────────────────────────────────────── */

.hpx-trust-stats {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}

.hpx-trust-stat {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    padding: 18px 12px 0;
    overflow: hidden;
    text-align: center;
    background: var(--hpx-card);
    /* brand-line rather than the neutral hairline: these cards sit on the
       band's own brand wash, and a grey border on top of it reads as dirt. */
    border: 1px solid var(--hpx-brand-line);
    border-radius: var(--hpx-r-md);
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
    transition: transform .26s var(--hpx-a-ease), box-shadow .26s ease, border-color .2s ease;
}

/* A SOLID tone fill with a white glyph, the house plate. At 44/13px this is
   already the library plate's geometry, so only the fill changes. */
.hpx-trust-stat .hpx-ico {
    width: 44px; height: 44px;
    margin-bottom: 8px;
    font-size: 18px;
    /* Rounded square, like every other icon chip on the page. */
    border-radius: 13px;
    background-color: var(--tone-ink);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .26) 100%);
    color: var(--hpx-ink-invert);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        0 8px 18px -8px var(--tone-ink);
    transition: transform .26s var(--hpx-a-ease);
}

.hpx-trust-stat .hpx-ico svg { width: 47%; height: 47%; }

.hpx-trust-num {
    font-size: 27px; font-weight: 800; letter-spacing: -.028em; line-height: 1.1;
    /* Brand gradient rather than flat blue — the one place on the page where a
       number is the message, so it earns the treatment. */
    background: linear-gradient(135deg, var(--hpx-brand) 0%, var(--hpx-brand-600) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.hpx .hpx-trust-lbl {
    font-size: 12.5px;
    font-weight: 650;
    line-height: 1.35;
    letter-spacing: -.006em;
    color: var(--hpx-ink);
    /* "Newsletter Subscribers" wraps where "Active Users" does not, which
       would step its small print — and therefore its growth line — a line
       lower than the other three. */
    min-height: 2.7em;
}

.hpx .hpx-trust-note {
    /* ink-2, not ink-3: at this size the muted tone measures 4.37:1, which
       FAILS AA outright rather than merely looking washed out. */
    margin-top: 2px;
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--hpx-ink-2);
}

/* The growth line bleeds to the card edges and owns the bottom band, so the
   card needs no bottom padding of its own. */
.hpx-spark {
    display: block;
    width: calc(100% + 26px);
    height: 42px;
    margin: 12px -13px -1px;
    color: var(--tone-ink);
    opacity: .78;
    transition: opacity .26s ease, transform .3s var(--hpx-a-ease);
}

@media (hover: hover) {
    .hpx-trust-stat:hover {
        transform: translateY(-4px);
        border-color: var(--tone-line);
        box-shadow: 0 22px 38px -26px var(--tone-ink), var(--hpx-sh-soft);
    }

    .hpx-trust-stat:hover .hpx-ico { transform: scale(1.08) rotate(-5deg); }
    .hpx-trust-stat:hover .hpx-spark { opacity: 1; transform: scaleY(1.12); transform-origin: bottom; }

    .hpx-faces:hover .hpx-face { transform: translateY(-3px); }
    .hpx-faces .hpx-face:hover { transform: translateY(-6px) scale(1.08); z-index: 21; }

    body.theme-dark .hpx .hpx-trust-stat:hover {
        box-shadow: 0 22px 38px -26px rgba(0, 0, 0, .85);
    }
}

.hpx a.hpx-trust-stat:focus-visible {
    outline: none;
    border-color: var(--tone-ink);
    box-shadow: 0 0 0 3px var(--hpx-brand-line);
}

/* ── The assurance strip ────────────────────────────────────────── */

/* The assurance strip. One instrument panel holding four claims, each of which
   is a real link — and, until this pass, nothing said so.

   Sheen over the brand tint rather than a flat wash: the panel sits directly
   under the counter card and two adjacent flat surfaces read as one block. */
.hpx-assure {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 6px;
    margin-top: 18px;
    padding: 10px;
    background-color: var(--hpx-brand-soft);
    background-image: linear-gradient(135deg, rgba(255, 255, 255, .5) 0%, transparent 58%);
    border: 1px solid var(--hpx-brand-line);
    border-radius: var(--hpx-r);
}

body.theme-dark .hpx .hpx-assure {
    background-image: linear-gradient(135deg, rgba(255, 255, 255, .05) 0%, transparent 58%);
}

.hpx-assure-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 14px;
    border-radius: 14px;
    /* The ring is declared transparent at rest so it TRANSITIONS in rather than
       appearing, and so the item never changes size when it gains one. */
    box-shadow: inset 0 0 0 1px transparent;
    transition: background-color .22s ease, box-shadow .24s ease,
                transform .22s var(--hpx-a-ease);
}

/* Hairlines between the four rather than bare gaps — one instrument panel
   instead of four floating claims. */
.hpx-assure-item + .hpx-assure-item::before {
    content: '';
    position: absolute;
    inset-inline-start: -3px;
    top: 50%;
    width: 1px; height: 28px;
    background: var(--hpx-brand-line);
    transform: translateY(-50%);
    transition: opacity .2s ease;
}

/* A rounded SQUARE, matched to the icongrid plates so every icon chip on the
   page is the same shape. */
.hpx-assure-item .hpx-ico {
    width: 36px; height: 36px;
    flex: 0 0 auto;
    font-size: 14px;
    border-radius: 11px;
    background-color: var(--hpx-card);
    color: var(--tone-ink);
    box-shadow: inset 0 0 0 1px var(--tone-line), 0 3px 8px -4px var(--tone-ink);
    transition: transform .24s var(--hpx-a-ease), background-color .22s ease, box-shadow .22s ease;
}

.hpx-assure-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.hpx .hpx-assure-title {
    font-size: 13.2px;
    font-weight: 720;
    letter-spacing: -.01em;
    line-height: 1.35;
    color: var(--hpx-ink);
    text-wrap: balance;
}

.hpx .hpx-assure-sub {
    font-size: 12px;
    line-height: 1.4;
    color: var(--hpx-ink-2);
    text-wrap: balance;
}

/* The affordance. Absolute rather than a flex child, so the copy column keeps
   the full width and a longer subtitle does not start wrapping the moment the
   chevron appears. */
.hpx-assure-go {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-end: 10px;
    display: grid;
    place-items: center;
    width: 21px; height: 21px;
    border-radius: 50%;
    background-color: var(--hpx-card);
    color: var(--hpx-ink-3);
    box-shadow: inset 0 0 0 1px var(--hpx-line);
    opacity: 0;
    transform: translate(-5px, -50%);
    transition: opacity .22s ease, transform .26s var(--hpx-a-ease),
                background-image .2s ease, color .2s ease, box-shadow .2s ease;
}

.hpx-assure-go svg { width: 11px; height: 11px; }
[dir="rtl"] .hpx-assure-go svg { transform: scaleX(-1); }

@media (hover: hover) {
    .hpx a.hpx-assure-item:hover {
        background-color: var(--hpx-card);
        box-shadow: inset 0 0 0 1px var(--hpx-brand-line), 0 12px 24px -14px var(--hpx-brand-glow);
        transform: translateY(-2px);
    }

    /* The raised tile reads as detached, so the hairlines either side of it get
       out of the way. `a.` on both: without a link there is no raised tile, and
       an inert item must not move the panel's rules under the pointer. */
    a.hpx-assure-item:hover::before,
    a.hpx-assure-item:hover + .hpx-assure-item::before { opacity: 0; }

    /* Identity stays with the ITEM's own hue — filling four plates with brand
       blue on hover would make a shield, a bolt, a phone and a globe all look
       like the same control. A tint step, not a solid fill: `--tone-ink`'s dark
       twins are lightened for use as ink and white on amber measures under 2:1.
       The chevron is the thing that goes brand, because that is the action. */
    .hpx a.hpx-assure-item:hover .hpx-ico {
        transform: scale(1.06);
        background-color: var(--tone-bg);
        box-shadow: inset 0 0 0 1px var(--tone-line), 0 6px 14px -6px var(--tone-ink);
    }

    /* The title holds `--hpx-ink` in every state. Recolouring it to the item's
       tint is what made the equivalent rows elsewhere read as washed out. */
    .hpx a.hpx-assure-item:hover .hpx-assure-go {
        opacity: 1;
        transform: translate(0, -50%);
        background-image: var(--sc-fill);
        color: #fff;
        box-shadow: inset 0 0 0 1px transparent;
    }
}

.hpx a.hpx-assure-item:active { transform: scale(.985); }

.hpx a.hpx-assure-item:focus-visible {
    outline: 3px solid var(--hpx-brand);
    outline-offset: 2px;
    background-color: var(--hpx-card);
    border-radius: 14px;
}

.hpx a.hpx-assure-item:focus-visible .hpx-assure-go {
    opacity: 1;
    transform: translate(0, -50%);
    background-image: var(--sc-fill);
    color: #fff;
}

/* ── Dark twins ─────────────────────────────────────────────────── */

/* The white glass edge and the ink shadow are tuned to a light card. */
body.theme-dark .hpx .hpx-trust {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .06),
        0 1px 2px rgba(0, 0, 0, .4),
        0 14px 34px -22px rgba(0, 0, 0, .75);
}

/* The counters sit on the band, which is already `card` — at night an
   identical fill makes them vanish into it, so they step up one surface. */
body.theme-dark .hpx .hpx-trust-stat {
    background: var(--hpx-card-2);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .35);
}

body.theme-dark .hpx .hpx-trust-stat .hpx-ico,
body.theme-dark .hpx .hpx-assure-item .hpx-ico {
    background-image: linear-gradient(155deg, rgba(255, 255, 255, .09) 0%, rgba(255, 255, 255, 0) 58%);
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, .07),
        0 4px 10px -6px rgba(0, 0, 0, .8);
}

/* Deepens the lightened dark twins so a white glyph clears AA. Split out of the
   grouped rule above rather than edited into it — the assurance rail shares
   that selector list and keeps the tinted chip. Same specificity, so this wins
   on source order alone; it must stay BELOW the group. */
body.theme-dark .hpx .hpx-trust-stat .hpx-ico {
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .45), rgba(9, 18, 33, .45)),
        linear-gradient(135deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .22) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 8px 18px -8px rgba(0, 0, 0, .8);
}

body.theme-dark .hpx .hpx-assure-item .hpx-ico { background-color: var(--hpx-card-2); }
body.theme-dark .hpx a.hpx-assure-item:hover { background: var(--hpx-card-2); }
body.theme-dark .hpx .hpx-face { box-shadow: 0 0 0 3px var(--hpx-card), 0 4px 10px -6px rgba(0, 0, 0, .8); }

/* `background-clip: text` gives no fallback if the gradient fails to paint —
   the number would vanish. Forced-colors mode is exactly that case. */
@media (forced-colors: active) {
    .hpx-trust-num { color: CanvasText; background: none; }
    .hpx-spark { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-trust-stat,
    .hpx-trust-stat .hpx-ico,
    .hpx-assure-item,
    .hpx-assure-item .hpx-ico,
    .hpx-assure-go,
    .hpx-face,
    .hpx-spark { transition: none; }

    .hpx-trust-stat:hover,
    .hpx-assure-item:hover,
    .hpx-faces:hover .hpx-face,
    .hpx-faces .hpx-face:hover { transform: none; }

    /* The chevron still has to arrive — it is the affordance, not decoration.
       It just arrives without sliding. */
    .hpx a.hpx-assure-item:hover .hpx-assure-go { transform: translate(0, -50%); }
    .hpx a.hpx-assure-item:hover .hpx-ico { transform: none; }
}


/* ---------------------------------------------------------------------------
   16b. Newsroom — the blog's latest beside the news desk

   Two panels in one band, sharing ONE shell: a card, a washed head, a body, a
   foot. They differ only by hue — the blog reads blue, the news desk violet.

   ── 2026-08-13 rebuild: the AI-model / tool detail page's card language ────
   The panels used to be flat white cards with a single pale band across the
   top. On this page that band was doing almost nothing, because the homepage
   GROUND IS #FFFFFF and so is `--hpx-card`: panel and page were the same
   colour and only a hairline separated them.

   They are now built on the recipe the three detail pages use for every card
   (atd-styles §CARD-WASH / §SPINE-BLUE / §SIDEBAR-ORBIT), so a visitor moving
   from /ai-models/{slug} to the homepage meets the same surface:

     · a soft 150deg diagonal wash, corner to corner
     · a small, saturated bloom concentrated in ONE corner
     · a hairline edge tinted in the panel's own hue
     · a gradient icon TILE as the head's anchor
     · a masked conic ORBIT ring that sweeps the border on hover

   THE LESSON THAT BLOCK PAID FOR, RESTATED: a wash is invisible unless it
   departs from the GROUND, and cranking one broad radial to compensate reads
   as haze, not as premium. What makes the reference card work is contrast
   WITHIN the card — a nearly-white field, a soft diagonal, one saturated
   corner, and a fully saturated tile as the anchor. The hue lives in the
   ACCENTS, not in the field. Every alpha below is measured against #FFFFFF
   in light and #17171B in dark, which are this page's real grounds.

   ── The empty half ────────────────────────────────────────────────────────
   The two feeds are never the same length — there are 2 posts and 5 news
   articles today — and `align-items: stretch` left the blog panel a ~200px
   hole above its foot. The grid uses `align-items: start` instead, so each
   panel is exactly as tall as its own content and NO feed configuration can
   put a void in either one. See §NR-GRID.

   ⚠️ TWO WAYS OF FILLING THAT HOLE WERE TRIED AND BOTH OVERSHOT. Anything
   that fills it has to predict the other panel's height, and a prediction
   wrong in the other direction just moves the hole across the band:
     · stretching the COVER — the covers are 1200x750 with the subject
       centred, and a 340px column stretched to 500px tall crops 57% of the
       width off and cuts the artwork in half;
     · restacking the LEAD full-width — sized for a ~600px panel, but the
       homepage shell is 1600px, so the blog column is 897px and the cover
       came out 833x468, four times the side-by-side area.
   Measure against the real 1600px shell, not the ~1200px the proportions
   suggest by eye.

   ── Specificity, because this band is nothing but links and buttons ────────
   `.hpx a { color: inherit }` is (0,1,1) and `.hpx button { color: inherit }`
   matches it, so ANY colour here is written `.hpx .hpx-x` — (0,2,0). The
   hover neutraliser `.hpx a:hover:not(:where(…))` is (0,2,1), so every hover
   colour is written `.hpx .hpx-x:hover` — (0,3,0) — which out-ranks it without
   needing to be added to that rule's exclusion list. `.hpx ul{padding:0}` is
   (0,1,1) too, which is why the list's own padding is `.hpx .hpx-nrlist`.

   ── Fills ──────────────────────────────────────────────────────────────────
   Nothing here puts a white label on `--hpx-brand`: its dark twin is LIGHTENED
   so it can serve as ink, and white on it measures 2.6:1 (3.84:1 even in
   light). Every filled control uses `--sc-fill`, the page-wide recipe that
   steps down to -600/-700 in light and lays a scrim in dark. The same trap
   applies to `--hpx-t-rose-ink`, whose dark twin is a pale pink — so the LIVE
   flags are tinted rather than solid. And it applies to `--tone-ink` itself:
   its dark twins are LIGHT (#8CBEFA / #BFA6FB), so the head tile builds its
   dark fill from the saturated triplet under a scrim, never from the token.
   ------------------------------------------------------------------------ */

/* ── §NR-TONE) The saturated triplet each panel lights itself with ─────────
   `--tone-bg/ink/line` are the admin's tokens and stay the source of every
   FLAT colour below. A wash, a bloom and a conic tail need the hue as an RGB
   TRIPLET so alpha can be varied along a gradient, and no token carries one —
   `--hpx-t-blue-bg` is already a composited pale hex. These two lines are the
   house blue and violet, byte-identical to atd-styles' own ramp, declared
   here so this block stays removable in one piece.

   rgba(var(--nr-rgb), 0) rather than the `transparent` KEYWORD at every fade
   out: a gradient fading to `transparent` interpolates through transparent
   BLACK in sRGB and leaves a grey, dirty fringe. Fading to the same triplet
   at zero alpha keeps the hue all the way out. */
.hpx .hpx-nr-blog { --nr-rgb: 54, 128, 237; }
.hpx .hpx-nr-news { --nr-rgb: 139, 92, 246; }

/* ── §NR-GRID) The band ──────────────────────────────────────────────── */

.hpx-nr {
    display: grid;
    grid-template-columns: minmax(0, 1.32fr) minmax(0, 1fr);
    gap: 24px;
    /* ⚠️ START, NOT STRETCH — this is the whole fix for the empty half.

       `stretch` forced both panels to the height of the TALLER one, and with
       2 posts against 5 news articles that left the blog panel a ~200px hole
       above its foot. Every attempt to FILL that hole has to predict how tall
       the other panel will be, and a prediction that is wrong in the other
       direction just moves the hole across the band — which is exactly what a
       full-width stacked lead did here (see §NR-RESP).

       `start` cannot be wrong in either direction: each panel is exactly as
       tall as its own content, so NO configuration of the two feeds can
       produce a void in either one. The cost is that the band's bottom edge is
       ragged when the feeds differ — which reads as two cards, because they
       ARE two cards, each with its own wash, edge and shadow.

       The panel foot keeps `margin-top: auto` and the news tail keeps its own:
       they now do nothing on the homepage, and they are what still holds the
       foot down if a host ever renders this band at a fixed height. */
    align-items: start;
}

.hpx-nr.is-single { grid-template-columns: minmax(0, 1fr); }

/* ── §NR-SHELL) The panel surface ────────────────────────────────────────
   Field, diagonal wash and bloom are three separate layers for three separate
   reasons. The wash is a background-image because it never changes; the bloom
   is a pseudo-element because it has to SWELL on hover and background layers
   cannot cross-fade; and `::before` is left free for the orbit ring.

   `isolation: isolate` is what makes `z-index: -1` mean "over the panel's own
   background, under every word on it". A positioned box with no z-index is
   NOT a stacking context, so without it the bloom escapes to an ancestor's
   and paints behind the background entirely — i.e. invisibly. */
.hpx-nrpanel {
    /* Fallback only. Both panels carry `.hpx-nr-blog` / `.hpx-nr-news` at
       (0,2,0) and override this — but an undefined triplet would make every
       rgba(var(--nr-rgb), …) below INVALID, and an invalid declaration is
       dropped silently: the wash, the edge and the ring would simply not
       exist, with nothing in the output to say why. */
    --nr-rgb: 54, 128, 237;
    position: relative;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background-color: var(--hpx-card);
    background-image:
        linear-gradient(152deg,
            rgba(var(--nr-rgb), .075) 0%,
            rgba(var(--nr-rgb), 0)   48%,
            rgba(var(--nr-rgb), .055) 100%);
    background-repeat: no-repeat;
    border: 1px solid rgba(var(--nr-rgb), .20);
    border-radius: 26px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 20px 44px -32px rgba(16, 24, 40, .36);
    transition: border-color .24s ease, box-shadow .28s ease;
}

/* The corner bloom. Hung off the top-right corner so its brightest point is
   outside the box and what lands on the card is the falloff — the reference
   card's single most recognisable cue. */
.hpx-nrpanel::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(340px 300px at calc(100% + 34px) -72px,
        rgba(var(--nr-rgb), .20) 0%,
        rgba(var(--nr-rgb), 0) 72%) no-repeat;
    opacity: .92;
    transform-origin: 100% 0;
    transition: opacity .36s ease, transform .52s cubic-bezier(.16, 1, .3, 1);
}

/* Dark twins. The ground drops to #0E0E11 and the card to #17171B, so the
   card is now LIGHTER than the page and separates on its own — the wash is
   free to carry hue rather than separation, and every alpha rises because a
   5% tint that reads over white is invisible over near-black. */
body.theme-dark .hpx .hpx-nrpanel {
    background-image:
        linear-gradient(152deg,
            rgba(var(--nr-rgb), .13) 0%,
            rgba(var(--nr-rgb), 0)  48%,
            rgba(var(--nr-rgb), .09) 100%);
    border-color: rgba(var(--nr-rgb), .30);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .05),
        0 1px 2px rgba(0, 0, 0, .42),
        0 20px 44px -32px rgba(0, 0, 0, .82);
}

body.theme-dark .hpx .hpx-nrpanel::after {
    background: radial-gradient(340px 300px at calc(100% + 34px) -72px,
        rgba(var(--nr-rgb), .17) 0%,
        rgba(var(--nr-rgb), 0) 72%) no-repeat;
}

.hpx-nrbody {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* ── §NR-HEAD) Panel head ────────────────────────────────────────────── */

.hpx-nrhead {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 22px 22px 16px;
}

/* The gradient TILE, the reference card's strongest cue. A saturated
   highlight is laid over `--tone-ink` rather than replacing it: the ink token
   is the AA-safe dark end (#1B5BB5 / #5B33C4), so the tile's darkest corner
   is guaranteed, and the highlight only has to clear 3:1 for a glyph — it
   measures 4.0:1 blue and 4.5:1 violet — the tile it copies, on the
   detail pages themselves, measures 3.3:1. */
.hpx-nrhead-ico {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px; height: 48px;
    flex: 0 0 auto;
    border-radius: 15px;
    background-color: var(--tone-ink);
    background-image:
        linear-gradient(135deg, rgba(var(--nr-rgb), .72) 0%, rgba(var(--nr-rgb), 0) 62%),
        linear-gradient(135deg, rgba(255, 255, 255, .18) 0%, rgba(255, 255, 255, 0) 100%);
    color: var(--hpx-ink-invert);
    font-size: 19px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .26),
        0 8px 20px -8px rgba(var(--nr-rgb), .58);
    transition: transform .38s cubic-bezier(.16, 1, .3, 1), box-shadow .3s ease;
}

/* ⚠️ `--tone-ink`'s dark twin is a PALE tint (#8CBEFA / #BFA6FB) — a white
   glyph on it measures under 2:1. Dark builds the fill from the saturated
   triplet under a scrim instead, which lands at 6.3:1 blue / 6.7:1 violet. */
body.theme-dark .hpx .hpx-nrhead-ico {
    background-color: rgb(var(--nr-rgb));
    background-image:
        linear-gradient(0deg, rgba(9, 18, 33, .42), rgba(9, 18, 33, .42)),
        linear-gradient(135deg, rgba(255, 255, 255, .20) 0%, rgba(255, 255, 255, 0) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        0 8px 20px -8px rgba(0, 0, 0, .8);
}

.hpx-nrhead-copy { flex: 1 1 auto; min-width: 0; }

/* ⚠️ THREE classes, and the third is load-bearing. The panel title is an
   <h3>, and custom.ltr.css:2614 carries

       body:not(.admin-shell):not(.theme-dark) h3
       { color:var(--ez-ink); font-weight:700; letter-spacing:-.015em; line-height:1.3 }

   at (0,2,2) — and, because of the `:not(.theme-dark)`, in LIGHT ONLY. At
   (0,2,0) this rule lost colour, weight, tracking AND leading to it and kept
   nothing but font-size, so the two headings rendered 700/-.015em in light and
   800/-.024em in dark: one heading, two designs, one per theme. (0,3,0) clears
   it. Measured against the rendered page, not guessed — a grep proves a rule is
   present, never that it wins.

   The two mobile steps below stay at (0,2,0): they set font-size only, which
   this global does not declare. */
.hpx .hpx-nrpanel .hpx-nrhead-title {
    font-size: 20px;
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -.024em;
    color: var(--hpx-ink);
}

.hpx .hpx-nrhead-sub {
    margin: 5px 0 0;
    font-size: 12.8px;
    line-height: 1.5;
    color: var(--hpx-ink-2);
}

/* The live flag. Tinted: `--hpx-t-rose-ink`'s dark twin is #FB9BB0 and a white
   label on it measures 1.9:1, which is precisely the hidden-text report the
   previous pass existed to answer. */
.hpx .hpx-nrlive {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    padding: 6px 13px;
    border-radius: var(--hpx-r-pill);
    background: var(--hpx-t-rose-bg);
    color: var(--hpx-t-rose-ink);
    box-shadow: inset 0 0 0 1px var(--hpx-t-rose-line);
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .1em;
    line-height: 1.5;
}

.hpx-nrlive em {
    width: 7px; height: 7px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: currentColor;
    animation: hpx-nrping 2s ease-out infinite;
}

@keyframes hpx-nrping {
    0%        { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
    70%, 100% { box-shadow: 0 0 0 6px transparent; opacity: .55; }
}

@keyframes hpx-nrpulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: .35; }
}

/* ── §NR-LEAD) The featured post ─────────────────────────────────────────
   The lead is a card ON a card, so it takes the same recipe one step quieter:
   the same diagonal, a smaller bloom, and the panel's hue in its edge. */

.hpx .hpx-nrlead {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-columns: minmax(0, .8fr) minmax(0, 1fr);
    gap: 18px;
    margin: 4px 18px 2px;
    padding: 14px;
    border: 1px solid rgba(var(--nr-rgb), .16);
    border-radius: 20px;
    background-color: var(--hpx-card);
    background-image:
        linear-gradient(152deg,
            rgba(var(--nr-rgb), .05) 0%,
            rgba(var(--nr-rgb), 0)  52%,
            rgba(var(--nr-rgb), .04) 100%);
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 6px 18px -12px rgba(16, 24, 40, .28);
    transition: transform .26s var(--hpx-a-ease), border-color .2s ease, box-shadow .26s ease;
}

.hpx .hpx-nrlead::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(220px 200px at calc(100% + 20px) -46px,
        rgba(var(--nr-rgb), .16) 0%,
        rgba(var(--nr-rgb), 0) 74%) no-repeat;
    transition: opacity .36s ease, transform .52s cubic-bezier(.16, 1, .3, 1);
    transform-origin: 100% 0;
}

body.theme-dark .hpx .hpx-nrlead {
    background-image:
        linear-gradient(152deg,
            rgba(var(--nr-rgb), .10) 0%,
            rgba(var(--nr-rgb), 0)  52%,
            rgba(var(--nr-rgb), .07) 100%);
    border-color: rgba(var(--nr-rgb), .24);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 6px 18px -12px rgba(0, 0, 0, .6);
}

body.theme-dark .hpx .hpx-nrlead::after {
    background: radial-gradient(220px 200px at calc(100% + 20px) -46px,
        rgba(var(--nr-rgb), .14) 0%,
        rgba(var(--nr-rgb), 0) 74%) no-repeat;
}

.hpx-nrlead-shot {
    position: relative;
    display: block;
    overflow: hidden;
    border-radius: 15px;
    background: var(--tone-bg);
    /* A fixed ratio, so one very tall cover cannot set the row height for a
       panel whose whole job is to look orderly. */
    aspect-ratio: 4 / 3;
}

/* aspect-ratio is well supported, but a browser without it would collapse the
   plate to nothing and the flag would float over the title. */
@supports not (aspect-ratio: 4 / 3) {
    .hpx-nrlead-shot { min-height: 190px; }
}

.hpx-nrlead-shot img {
    width: 100%; height: 100%;
    display: block;
    object-fit: cover;
    transition: transform .5s var(--hpx-a-ease);
}

.hpx-nrlead-plate {
    display: grid;
    place-items: center;
    width: 100%; height: 100%;
    color: var(--tone-ink);
    font-size: 30px;
    opacity: .5;
}

/* The cover carries two chips and it is an unknown photograph underneath, so
   the foot of it is scrimmed rather than trusted. */
.hpx-nrlead-veil {
    position: absolute;
    inset: auto 0 0;
    height: 58%;
    background: linear-gradient(to top, rgba(6, 12, 22, .74) 0%, rgba(6, 12, 22, .28) 46%, transparent 100%);
    pointer-events: none;
}

.hpx .hpx-nrlead-flag,
.hpx .hpx-nrlead-time {
    position: absolute;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 11px;
    border-radius: var(--hpx-r-pill);
    background: rgba(11, 18, 32, .74);
    color: #fff;
    font-size: 10.5px;
    font-weight: 750;
    letter-spacing: .07em;
    text-transform: uppercase;
    line-height: 1.5;
    backdrop-filter: blur(4px);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .16);
}

.hpx .hpx-nrlead-flag { inset-block-start: 11px; inset-inline-start: 11px; }
.hpx .hpx-nrlead-time { inset-block-end: 11px; inset-inline-start: 11px; letter-spacing: .04em; }

.hpx-nrlead-flag i,
.hpx-nrlead-time i { font-size: 9.5px; }

.hpx-nrlead-body {
    display: flex;
    flex-direction: column;
    gap: 9px;
    min-width: 0;
}

.hpx .hpx-nrlead-title {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: 19.5px;
    font-weight: 800;
    line-height: 1.28;
    letter-spacing: -.026em;
    color: var(--hpx-ink);
    transition: color .2s ease;
}

.hpx .hpx-nrlead-sub {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: 13.2px;
    line-height: 1.6;
    color: var(--hpx-ink-2);
}

.hpx-nrlead-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: auto;
    padding-top: 6px;
}

/* ── §NR-META) Meta row ──────────────────────────────────────────────── */

.hpx-nrmeta {
    display: flex;
    align-items: center;
    gap: 9px;
    flex-wrap: wrap;
    font-size: 11.8px;
    color: var(--hpx-ink-3);
}

.hpx-nrmeta-bit { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.hpx-nrmeta-bit i { font-size: 11px; opacity: .8; }

.hpx .hpx-nrpill {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: var(--hpx-r-pill);
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.55;
    white-space: nowrap;
}

/* The quiet variant carries the category's own colour as a DOT rather than as
   the pill's fill. `--hpx-nr-cat` is admin-set and can be any hex an editor
   picks, including a pale yellow — as a 6px dot beside neutral ink it can
   never make the label unreadable, and the taxonomy still reads at a glance. */
.hpx .hpx-nrpill.is-quiet {
    gap: 6px;
    background: var(--hpx-bg-2);
    color: var(--hpx-ink-2);
    box-shadow: inset 0 0 0 1px var(--hpx-line);
    font-weight: 660;
}

.hpx .hpx-nrpill.is-quiet::before {
    content: '';
    width: 6px; height: 6px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--hpx-nr-cat, var(--tone-ink));
}

/* ── Author ─────────────────────────────────────────────────────── */

.hpx-nrauthor { display: flex; align-items: center; gap: 10px; min-width: 0; }

.hpx-nrauthor-face {
    display: inline-grid;
    place-items: center;
    width: 34px; height: 34px;
    flex: 0 0 auto;
    overflow: hidden;
    border-radius: 50%;
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 13px;
    font-weight: 800;
    box-shadow: 0 0 0 2px var(--hpx-card), inset 0 0 0 1px var(--tone-line);
}

.hpx-nrauthor-face img { width: 100%; height: 100%; object-fit: cover; }

.hpx-nrauthor-id { display: flex; flex-direction: column; min-width: 0; }

.hpx .hpx-nrauthor-name {
    font-size: 12.8px;
    font-weight: 720;
    line-height: 1.3;
    color: var(--hpx-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.hpx .hpx-nrauthor-role {
    font-size: 11.4px;
    line-height: 1.35;
    color: var(--hpx-ink-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.hpx .hpx-nrlead-go {
    display: inline-grid;
    place-items: center;
    width: 38px; height: 38px;
    flex: 0 0 auto;
    margin-inline-start: auto;
    border-radius: 50%;
    background: var(--hpx-brand-soft);
    color: var(--hpx-brand-ink);
    box-shadow: inset 0 0 0 1px var(--hpx-brand-line);
    transition: background-color .2s ease, background-image .2s ease, color .2s ease,
                box-shadow .2s ease, transform .24s var(--hpx-a-ease);
}

.hpx-nrlead-go svg { width: 15px; height: 15px; }

/* ── §NR-ROW) The list ───────────────────────────────────────────────── */

.hpx .hpx-nrlist { list-style: none; margin: 0; padding: 10px 18px 2px; }
.hpx .hpx-nrlist.is-news { padding: 8px 16px 2px; }

.hpx-nrlist > li + li { border-top: 1px solid var(--hpx-line-soft); }

body.theme-dark .hpx .hpx-nrlist > li + li { border-top-color: rgba(255, 255, 255, .06); }

.hpx .hpx-nrrow {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px;
    margin-inline: -8px;
    border-radius: 16px;
    transition: background-color .2s ease, box-shadow .24s ease;
}

/* The accent bar. Scaled from zero rather than faded in, so it reads as the
   row being picked up rather than as something appearing next to it. It is
   also the ONE hover signal that survives forced-colors mode — see the end of
   this section. */
.hpx-nrrow::before {
    content: '';
    position: absolute;
    inset-block: 13px;
    inset-inline-start: 3px;
    width: 3px;
    border-radius: 3px;
    background: rgb(var(--nr-rgb));
    transform: scaleY(0);
    transition: transform .26s var(--hpx-a-ease);
}

.hpx-nrrow-shot {
    position: relative;
    display: grid;
    place-items: center;
    width: 84px; height: 64px;
    flex: 0 0 auto;
    overflow: hidden;
    border-radius: 13px;
    background: var(--tone-bg);
    color: var(--tone-ink);
    font-size: 16px;
    box-shadow: inset 0 0 0 1px rgba(var(--nr-rgb), .16);
}

/* Both feeds now share ONE thumbnail geometry. They used to differ by 4px in
   each axis, which is not a distinction anyone can read — it just stopped the
   two panels' rows from lining up when the band is stacked. */
.hpx-nrrow-shot.is-news { width: 84px; height: 64px; }

.hpx-nrrow-shot img {
    width: 100%; height: 100%;
    object-fit: cover;
    transition: transform .4s var(--hpx-a-ease);
}

.hpx-nrrow-body { display: flex; flex-direction: column; gap: 7px; min-width: 0; flex: 1 1 auto; }

/* The title stays `--hpx-ink` in every state, hover included. Recolouring it
   to the row's own tint is what made the hovered row read as washed out —
   blue-on-blue — and was reported as hidden text. The row signals itself with
   a tint behind it, an accent bar and a filled chevron instead. */
.hpx .hpx-nrrow-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: 14.4px;
    font-weight: 720;
    line-height: 1.42;
    letter-spacing: -.014em;
    color: var(--hpx-ink);
}

.hpx-nrrow-go {
    display: inline-grid;
    place-items: center;
    width: 32px; height: 32px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--hpx-bg-2);
    color: var(--hpx-ink-3);
    box-shadow: inset 0 0 0 1px var(--hpx-line);
    opacity: 0;
    transform: translateX(-6px) scale(.86);
    transition: opacity .22s ease, transform .26s var(--hpx-a-ease),
                background-image .2s ease, color .2s ease, box-shadow .2s ease;
}

.hpx-nrrow-go svg { width: 14px; height: 14px; }

[dir="rtl"] .hpx-nrlead-go svg,
[dir="rtl"] .hpx-nrrow-go svg,
[dir="rtl"] .hpx-nrmore svg,
[dir="rtl"] .hpx-nr-btn svg { transform: scaleX(-1); }

.hpx .hpx-nrrow-live {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border-radius: var(--hpx-r-pill);
    background: var(--hpx-t-rose-bg);
    color: var(--hpx-t-rose-ink);
    box-shadow: inset 0 0 0 1px var(--hpx-t-rose-line);
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
    line-height: 1.6;
}

.hpx .hpx-nrrow-live::before {
    content: '';
    width: 5px; height: 5px;
    border-radius: 50%;
    background: currentColor;
    animation: hpx-nrpulse 2s ease-in-out infinite;
}

/* ── §NR-TABS) Tabs ──────────────────────────────────────────────────── */

.hpx-nrtabwrap { padding: 2px 18px 0; }

/* A recessed track with raised segments — the same segmented control the card
   bands use, so the news desk stops being the one place on the page with its
   own tab language. inline-flex + a block parent is what keeps the track the
   width of its tabs rather than the width of the panel. */
.hpx-nrtabs {
    display: inline-flex;
    gap: 3px;
    max-width: 100%;
    padding: 4px;
    border-radius: var(--hpx-r-pill);
    background: var(--hpx-bg-2);
    box-shadow: inset 0 0 0 1px var(--hpx-line);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}

body.theme-dark .hpx .hpx-nrtabs {
    background: rgba(0, 0, 0, .3);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06);
}

.hpx-nrtabs::-webkit-scrollbar { display: none; }

/* Whether the strip overflows depends on the WIDTH, not on how many tabs there
   are, so it is measured by the kit (`syncTabStrip`) and the fade is drawn only
   on the side that actually has more to show. Slicing a segment with the
   track's own rounded corner and no fade is what made six tabs read as a
   rendering fault. */
.hpx-nrtabs[data-overflow="1"][data-at-start="0"][data-at-end="0"] {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);
}

.hpx-nrtabs[data-overflow="1"][data-at-start="1"][data-at-end="0"] {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent 100%);
            mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent 100%);
}

.hpx-nrtabs[data-overflow="1"][data-at-start="0"][data-at-end="1"] {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 26px);
            mask-image: linear-gradient(to right, transparent 0, #000 26px);
}

.hpx .hpx-nrtab {
    position: relative;
    flex: 0 0 auto;
    padding: 8px 14px;
    border: 0;
    border-radius: var(--hpx-r-pill);
    background: transparent;
    color: var(--hpx-ink-2);
    font-size: 12.8px;
    font-weight: 700;
    letter-spacing: -.008em;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color .18s ease, background-image .18s ease,
                color .18s ease, box-shadow .2s ease;
}

/* ⚠️ The active tab fills from `--sc-fill` — the page-wide BLUE recipe —
   even in the violet panel. It is not an oversight: white on the house violet
   (#8B5CF6) measures 3.5:1 and this label is 12.8px, so a violet fill would
   fail AA outright. The panel's hue lives in its wash, edge, tile and rows;
   the one place a white label sits on a fill, it sits on the measured one. */
.hpx .hpx-nrtab.is-on {
    background-image: var(--sc-fill);
    color: var(--hpx-ink-invert);
    box-shadow: 0 8px 18px -10px var(--hpx-brand-glow);
}

.hpx-nrnews {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

.hpx-nrnews[hidden] { display: none; }

/* ── §NR-TAIL) What closes a category panel ──────────────────────────────
   Its own archive link, then the rest of the desk under a divider.
   `margin-top: auto` on the WRAPPER, so a tab that is still short after the
   tail sinks it to the foot rather than leaving the gap below it — the void
   reads as a broken panel, a bounded gap reads as spacing. */
.hpx-nrtail { margin-top: auto; }

.hpx .hpx-nrmore {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    margin: 14px 16px 6px;
    padding: 13px 16px;
    border: 1px dashed var(--hpx-line-strong);
    border-radius: 15px;
    color: var(--hpx-ink-2);
    font-size: 12.8px;
    font-weight: 680;
    text-align: center;
    transition: background-color .18s ease, border-color .18s ease, color .18s ease;
}

.hpx-nrmore svg { width: 14px; height: 14px; flex: 0 0 auto; transition: transform .2s var(--hpx-a-ease); }

/* "More from the newsroom" — a caption with a hairline running out of it on
   both sides, so the rows below are unmistakably from other categories. */
.hpx .hpx-nrelse {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 16px 16px 0;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--hpx-ink-faint);
}

.hpx-nrelse::before,
.hpx-nrelse::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: var(--hpx-line);
}

/* Those rows are context, not the tab's own answer, so the title steps down one
   ink level and lifts back on hover. Their METRICS stay identical to a normal
   row on purpose: it is the row count that makes a category tab land at the
   same height as All, and a shorter row here would put the gap back. */
.hpx .hpx-nrlist.is-else { padding-top: 2px; }

.hpx .hpx-nrrow.is-slim .hpx-nrrow-title {
    font-weight: 680;
    color: var(--hpx-ink-2);
}

.hpx .hpx-nrrow.is-slim:hover .hpx-nrrow-title { color: var(--hpx-ink); }

/* ── §NR-FOOT) Panel feet ────────────────────────────────────────────── */

.hpx-nrfoot {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: auto;
    padding: 16px 18px 18px;
}

/* ONE button for the whole band: brand-tinted with a solid chevron chip,
   filling with `--sc-fill` on hover. Tinted rather than solid at rest on
   purpose — a saturated pill under a saturated active tab blurs which of the
   two is the action. */
.hpx .hpx-nr-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 11px 12px 11px 20px;
    border: 1px solid var(--hpx-brand-line);
    border-radius: var(--hpx-r-pill);
    background-color: var(--hpx-brand-soft);
    color: var(--hpx-brand-ink);
    font-size: 13.4px;
    font-weight: 730;
    letter-spacing: -.008em;
    line-height: 1.4;
    transition: background-color .18s ease, background-image .18s ease, color .18s ease,
                border-color .18s ease, transform .2s ease, box-shadow .22s ease;
}

.hpx-nr-chip {
    display: inline-grid;
    place-items: center;
    width: 27px; height: 27px;
    flex: 0 0 auto;
    border-radius: 50%;
    background-image: var(--sc-fill);
    color: #fff;
    transition: background-image .18s ease, background-color .18s ease, transform .2s var(--hpx-a-ease);
}

.hpx-nr-chip svg { width: 13px; height: 13px; }
.hpx-nr-btn > svg { width: 14px; height: 14px; flex: 0 0 auto; transition: transform .2s var(--hpx-a-ease); }

.hpx .hpx-nr-btn.is-wide { flex: 1 1 auto; }

/* The head's button is the secondary of the pair: neutral at rest so the
   filled foot button is unambiguously the primary, branded on hover. */
.hpx .hpx-nr-btn.is-ghost {
    flex: 0 0 auto;
    margin-inline-start: auto;
    padding: 8px 14px;
    gap: 7px;
    border-color: var(--hpx-line);
    background-color: var(--hpx-card);
    color: var(--hpx-ink-2);
    font-size: 12.5px;
    font-weight: 700;
}

.hpx .hpx-nr-iconbtn {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 46px; height: 46px;
    flex: 0 0 auto;
    border: 1px solid var(--hpx-brand-line);
    border-radius: 14px;
    background-color: var(--hpx-brand-soft);
    color: var(--hpx-brand-ink);
    font-size: 14px;
    transition: background-color .18s ease, background-image .18s ease, color .18s ease,
                border-color .18s ease, transform .2s ease, box-shadow .22s ease;
}

/* ── §NR-HOVER) Hover ────────────────────────────────────────────────── */

@media (hover: hover) {
    /* The panel answers with LIGHT only — the bloom swells from the corner it
       hangs off and the edge brightens. No lift: the reference sidebar card
       lifts because it is a 300px block of links, and a 600px editorial panel
       sliding under the pointer as you read it is not the same gesture. */
    .hpx .hpx-nrpanel:hover {
        border-color: rgba(var(--nr-rgb), .34);
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .7),
            0 2px 6px rgba(16, 24, 40, .05),
            0 26px 54px -34px rgba(var(--nr-rgb), .5);
    }

    .hpx .hpx-nrpanel:hover::after { opacity: 1; transform: scale(1.14); }

    body.theme-dark .hpx .hpx-nrpanel:hover {
        border-color: rgba(var(--nr-rgb), .46);
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .05),
            0 2px 6px rgba(0, 0, 0, .5),
            0 26px 54px -34px rgba(0, 0, 0, .9);
    }

    .hpx .hpx-nrpanel:hover .hpx-nrhead-ico {
        transform: scale(1.06) rotate(-3deg);
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .3),
            0 10px 24px -8px rgba(var(--nr-rgb), .7);
    }

    .hpx .hpx-nrlead:hover {
        transform: translateY(-3px);
        border-color: rgba(var(--nr-rgb), .34);
        box-shadow: 0 24px 44px -30px rgba(var(--nr-rgb), .55), var(--hpx-sh-soft);
    }

    .hpx .hpx-nrlead:hover::after { transform: scale(1.14); }

    body.theme-dark .hpx .hpx-nrlead:hover {
        box-shadow: 0 24px 44px -28px rgba(0, 0, 0, .9);
    }

    /* Brand ink on the CARD, never on a brand tint — 7.0:1 light, 9.4:1 dark. */
    .hpx .hpx-nrlead:hover .hpx-nrlead-title { color: var(--hpx-brand-ink); }
    .hpx-nrlead:hover .hpx-nrlead-shot img { transform: scale(1.05); }

    .hpx .hpx-nrlead:hover .hpx-nrlead-go {
        background-image: var(--sc-fill);
        color: #fff;
        box-shadow: inset 0 0 0 1px transparent, 0 10px 20px -10px var(--hpx-brand-glow);
        transform: translateX(2px);
    }

    /* The row lights in the PANEL's hue, not in brand blue — the news desk
       glows violet, the blog blue. Same principle as the detail page's cards:
       the light belongs to the surface it is lighting. */
    .hpx .hpx-nrrow:hover {
        background-color: rgba(var(--nr-rgb), .07);
        box-shadow: inset 0 0 0 1px rgba(var(--nr-rgb), .2);
    }

    body.theme-dark .hpx .hpx-nrrow:hover {
        background-color: rgba(var(--nr-rgb), .14);
        box-shadow: inset 0 0 0 1px rgba(var(--nr-rgb), .3);
    }

    .hpx-nrrow:hover::before { transform: scaleY(1); }
    .hpx-nrrow:hover .hpx-nrrow-shot img { transform: scale(1.07); }

    .hpx .hpx-nrrow:hover .hpx-nrrow-go {
        opacity: 1;
        transform: translateX(0) scale(1);
        background-image: var(--sc-fill);
        color: #fff;
        box-shadow: inset 0 0 0 1px transparent, 0 8px 18px -9px var(--hpx-brand-glow);
    }

    .hpx .hpx-nr-btn:hover,
    .hpx .hpx-nr-iconbtn:hover {
        background-image: var(--sc-fill);
        border-color: transparent;
        color: var(--hpx-ink-invert);
        transform: translateY(-2px);
        box-shadow: 0 14px 28px -14px var(--hpx-brand-glow);
    }

    /* On a filled button the chip cannot stay filled or it vanishes into it. */
    .hpx .hpx-nr-btn:hover .hpx-nr-chip {
        background-image: none;
        background-color: rgba(255, 255, 255, .24);
        transform: translateX(2px);
    }

    .hpx .hpx-nr-btn.is-ghost:hover {
        background-image: none;
        background-color: var(--hpx-brand-soft);
        border-color: var(--hpx-brand-line);
        color: var(--hpx-brand-ink);
    }

    .hpx-nr-btn.is-ghost:hover > svg { transform: translateX(3px); }
    [dir="rtl"] .hpx-nr-btn.is-ghost:hover > svg { transform: scaleX(-1) translateX(3px); }

    /* :not(.is-on) rather than source order — an active tab must not take the
       raised-chip treatment on top of its fill. */
    .hpx .hpx-nrtab:not(.is-on):hover {
        background-color: var(--hpx-card);
        color: var(--hpx-ink);
        box-shadow: var(--hpx-sh-soft);
    }

    .hpx .hpx-nrmore:hover {
        border-style: solid;
        border-color: rgba(var(--nr-rgb), .34);
        background-color: rgba(var(--nr-rgb), .07);
        color: var(--hpx-ink);
    }

    .hpx-nrmore:hover svg { transform: translateX(3px); }
    [dir="rtl"] .hpx-nrmore:hover svg { transform: scaleX(-1) translateX(3px); }
}

/* ── §NR-ORBIT) The rotating light, carried in from the detail pages ──────
   atd-styles §SIDEBAR-ORBIT, one recipe at four scales:

     panel        8.5s   the slow ambient sweep — "this panel is alive"
     lead card    4.2s   a card you can actually click
     foot button  2.8s
     icon button  2.4s   fastest, because it is the smallest target

   THE HUE IS NOT FIXED: it reads `--nr-rgb`, so the blog panel orbits blue and
   the news desk violet. That is the difference between an effect pasted onto
   two cards and a system — the light belongs to the panel it is lighting.

   Because the gradient is written once, dark mode is not a second copy of an
   11-stop conic — it is two alpha numbers, lifted because a 55%-alpha tail
   that reads over #FFFFFF disappears over #17171B.

   `rgba(var(--nr-rgb), 0)` rather than the `transparent` keyword: a conic
   fading a colour to `transparent` interpolates through transparent BLACK in
   sRGB and leaves a grey, dirty fringe on every tail. */

@property --hpx-nrspin { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@keyframes hpx-nrorbit { to { --hpx-nrspin: 360deg; } }

.hpx .hpx-nrpanel {
    --orb-hot: 255, 255, 255;   /* the specular core of the comet */
    --orb-a1: .5;               /* leading tail */
    --orb-a2: .88;              /* trailing tail */
    --orb-a3: 1;                /* core */
}

/* Over near-black the tails have to work harder or the comet reads as a single
   white dash with nothing attached to it. */
body.theme-dark .hpx .hpx-nrpanel { --orb-a1: .66; --orb-a2: 1; }

@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
    /* Two full-box mask layers — one clipped to the content box, one to the
       border box — composited with xor, leaving exactly the padding band.
       mask-clip follows border-radius, so the inner cut is a rounded rect on a
       rounded rect and the corners stay true.

       The @supports gate is load-bearing, not defensive: without mask-composite
       the two layers UNION and the conic paints as a SOLID BLOCK over the
       panel, covering every word on it. Where the mask is missing there is
       simply no ring, and the border/lift/shadow hover carries the state.

       ⚠️ inset:0, not the reference's inset:-1px. Every host here either has
       `overflow: hidden` (the panel, so it can clip its own wash) or is a pill
       whose ring must not escape — a ring hung 1px outside gets sliced off on
       all four sides and reads as a rendering fault. Sitting the ring ON the
       padding edge costs nothing visually and clips cleanly. */
    .hpx .hpx-nrpanel::before,
    .hpx .hpx-nrlead::before,
    .hpx .hpx-nr-btn::before,
    .hpx .hpx-nr-iconbtn::before {
        content: '';
        position: absolute;
        inset: 0;
        z-index: 1;
        pointer-events: none;
        border-radius: inherit;
        padding: var(--orb-t, 1.5px);
        --hpx-nrspin: 0deg;
        background: conic-gradient(from var(--hpx-nrspin),
            rgba(var(--orb-rgb, var(--nr-rgb)), 0)                 0deg,
            rgba(var(--orb-rgb, var(--nr-rgb)), var(--orb-a1))    30deg,
            rgba(var(--orb-hot), var(--orb-a3))   52deg,
            rgba(var(--orb-rgb, var(--nr-rgb)), var(--orb-a2))    74deg,
            rgba(var(--orb-rgb, var(--nr-rgb)), 0)               118deg,
            rgba(var(--orb-rgb, var(--nr-rgb)), 0)               180deg,
            rgba(var(--orb-rgb, var(--nr-rgb)), var(--orb-a1))   210deg,
            rgba(var(--orb-hot), var(--orb-a3))  232deg,
            rgba(var(--orb-rgb, var(--nr-rgb)), var(--orb-a2))   254deg,
            rgba(var(--orb-rgb, var(--nr-rgb)), 0)               298deg,
            rgba(var(--orb-rgb, var(--nr-rgb)), 0)               360deg);
        -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
                mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
        -webkit-mask-composite: xor;
                mask-composite: exclude;
        opacity: 0;
        transition: opacity .32s ease;
        animation: hpx-nrorbit var(--orb-sp, 3s) linear infinite;
        animation-play-state: paused;   /* a paused animation does not tick, so
                                           idle panels cost nothing, and
                                           re-entering RESUMES the orbit rather
                                           than snapping back to 0deg */
    }

    .hpx .hpx-nrpanel::before   { --orb-sp: 8.5s; --orb-t: 1.5px; }
    .hpx .hpx-nrlead::before    { --orb-sp: 4.2s; --orb-t: 1.5px; }
    .hpx .hpx-nr-btn::before    { --orb-sp: 2.8s; --orb-t: 1.4px; }
    .hpx .hpx-nr-iconbtn::before{ --orb-sp: 2.4s; --orb-t: 1.4px; }

    /* ⚠️ Only the FILLED controls go white. Both of these fill with `--sc-fill`
       on hover, and a blue tail on a blue button is invisible — but the head's
       ghost button does NOT fill: it hovers to `--hpx-brand-soft`, a pale tint,
       and a white comet on that is invisible for the opposite reason. It is
       excluded here and keeps the panel's own hue, which is what it is sitting
       on. `--orb-rgb` exists precisely so this can differ from `--nr-rgb`,
       which the wash, the edge and the accent bar all still read. */
    .hpx .hpx-nr-btn:not(.is-ghost)::before,
    .hpx .hpx-nr-iconbtn::before { --orb-rgb: 255, 255, 255; --orb-a1: .4; --orb-a2: .82; }

    /* Keyboard is deliberately OUTSIDE the hover query — a keyboard on a touch
       screen is a real combination, and focus deserves the same answer as the
       pointer. */
    .hpx .hpx-nrlead:focus-visible::before,
    .hpx .hpx-nr-btn:focus-visible::before,
    .hpx .hpx-nr-iconbtn:focus-visible::before { opacity: 1; animation-play-state: running; }

    @media (hover: hover) and (pointer: fine) {
        .hpx .hpx-nrpanel:hover::before,
        .hpx .hpx-nrlead:hover::before,
        .hpx .hpx-nr-btn:hover::before,
        .hpx .hpx-nr-iconbtn:hover::before { opacity: 1; animation-play-state: running; }

        /* The panel steps back the moment a control inside it claims the
           pointer, so the thing under the cursor always wins. Without :has()
           the panel simply stays lit — a degradation, not a break. */
        .hpx .hpx-nrpanel:has(.hpx-nrlead:hover)::before,
        .hpx .hpx-nrpanel:has(.hpx-nrrow:hover)::before,
        .hpx .hpx-nrpanel:has(.hpx-nr-btn:hover)::before,
        .hpx .hpx-nrpanel:has(.hpx-nr-iconbtn:hover)::before,
        .hpx .hpx-nrpanel:has(.hpx-nrmore:hover)::before,
        .hpx .hpx-nrpanel:has(.hpx-nrtab:hover)::before { opacity: .3; }
    }

    /* Touch has no hover, so the press itself lights the control — for exactly
       as long as the finger is down, which is when the feedback is wanted. */
    @media (hover: none) {
        .hpx .hpx-nrlead:active::before,
        .hpx .hpx-nr-btn:active::before,
        .hpx .hpx-nr-iconbtn:active::before { opacity: 1; animation-play-state: running; }
    }

    /* Reduced motion keeps the LIGHT and drops the TRAVEL: the ring freezes at
       135deg, which reads as a designed gradient edge rather than an arbitrary
       one. */
    @media (prefers-reduced-motion: reduce) {
        .hpx .hpx-nrpanel::before,
        .hpx .hpx-nrlead::before,
        .hpx .hpx-nr-btn::before,
        .hpx .hpx-nr-iconbtn::before { --hpx-nrspin: 135deg; animation: none !important; }
    }
}

/* ── §NR-FOCUS) Focus ────────────────────────────────────────────────── */

/* The ring is an OUTLINE, and these rules deliberately do not say
   `outline: none`.

   The band used to suppress the page's global `.hpx :focus-visible` outline and
   draw its own ring as a box-shadow instead. Three things were wrong with that:
   the replacement was `--hpx-brand-line`, a 28% wash that is right for a
   hairline and far too quiet as the only thing telling a keyboard user where
   they are; `box-shadow` is in these components' transition list, so the ring
   faded in over 260ms behind the caret; and it collided with each control's own
   resting shadow, which is what made it read as absent under measurement.

   An outline has none of those problems — it is not transitioned here, it does
   not share a property with the design, and it follows the border radius. The
   panel's `overflow: hidden` cannot clip it either: the lead and the rows both
   sit at least 10px inside the panel's padding. */
/* ⚠️ These are scoped through `.hpx-nrpanel` purely for SPECIFICITY. There is a
   site-wide rule in custom.ltr.css:3053 —

       body:not(.admin-shell):not(.theme-dark) a:focus-visible,
       body:not(.admin-shell):not(.theme-dark) button:focus-visible
       { outline:none; box-shadow:0 0 0 3px var(--ez-focus); border-radius:6px }

   — which is (0,3,2) and, being `:not(.theme-dark)`, applies in LIGHT ONLY.
   That is why a focus ring written at (0,3,0) here looked correct in dark and
   vanished in light: same stylesheet, same rule, different winner per theme.
   Three classes plus the pseudo-class puts these at (0,4,0), which clears it.

   The `border-radius: 6px` in that rule is the part that has to be undone
   rather than merely out-ranked — it squares off every pill in this band the
   instant a keyboard reaches it — so each control restates its own radius. */
.hpx .hpx-nrpanel .hpx-nrlead:focus-visible,
.hpx .hpx-nrpanel .hpx-nrrow:focus-visible,
.hpx .hpx-nrpanel .hpx-nrmore:focus-visible,
.hpx .hpx-nrpanel .hpx-nr-btn:focus-visible,
.hpx .hpx-nrpanel .hpx-nr-iconbtn:focus-visible,
.hpx .hpx-nrpanel .hpx-nrtab:focus-visible {
    outline: 3px solid var(--hpx-brand);
    outline-offset: 2px;
    box-shadow: none;
}

.hpx .hpx-nrpanel .hpx-nrlead:focus-visible,
.hpx .hpx-nrpanel .hpx-nrrow:focus-visible,
.hpx .hpx-nrpanel .hpx-nrmore:focus-visible { background-color: var(--hpx-brand-soft); }

.hpx .hpx-nrpanel .hpx-nrlead:focus-visible     { border-radius: 20px; }
.hpx .hpx-nrpanel .hpx-nrrow:focus-visible      { border-radius: 16px; }
.hpx .hpx-nrpanel .hpx-nrmore:focus-visible     { border-radius: 15px; }
.hpx .hpx-nrpanel .hpx-nr-btn:focus-visible,
.hpx .hpx-nrpanel .hpx-nrtab:focus-visible      { border-radius: var(--hpx-r-pill); }
.hpx .hpx-nrpanel .hpx-nr-iconbtn:focus-visible { border-radius: 14px; }

.hpx .hpx-nrrow:focus-visible .hpx-nrrow-go { opacity: 1; transform: none; }

/* The active tab is already brand-filled, so a brand ring 2px off it merges
   into one blue lozenge; a wider offset keeps track colour between the two. */
.hpx .hpx-nrpanel .hpx-nrtab.is-on:focus-visible { outline-offset: 3px; }

/* ── §NR-RESP) Responsive ────────────────────────────────────────────────
   Four real layouts, not one layout shedding padding:

     ≥1025px   two panels side by side; the blog lead adapts to its feed
     ≤1024px   stacked, full width — tablet portrait and landscape
     ≤640px    single column inside each panel, cover on top
     ≤380px    the head's controls wrap under the title
   -------------------------------------------------------------------- */

/* ── §NR-WIDE) The blog panel's wide layout — feature BESIDE the list ───────

   MEASURED, because two earlier attempts at this were sized by eye and both
   overshot. At the real 1600px shell the blog column is 897px and the news
   column 679px — the blog is 32% WIDER while holding 60% FEWER items. Stacked,
   its rows ran the full 897px to carry a title needing 374px: 46% of the title
   column was empty, and the panel still finished 108px short of the news desk.

   Flipping or evening the columns makes it WORSE, and the numbers are the
   reason to trust this rather than the intuition: the blog panel's height comes
   almost entirely from its cover, and the cover scales with the column, so a
   narrower blog column is a SHORTER blog panel.
       ratio 1.32 (as shipped) → 108px ragged
       ratio 1.00             → 144px
       ratio 0.76 (flipped)   → 177px
   The width therefore has to be spent INSIDE the panel, not redistributed.

   So the feature card and the post list sit side by side, the feature becoming
   a vertical card — cover on top at 16/9, copy beneath. Three things fall out:
     · the 897px is filled with content, and the rows' title column drops from
       699px (46% empty) to ~260px (full, two lines);
     · the ragged edge closes from 108px to 37px;
     · the panel height stops depending on the POST COUNT — 643px with one row
       or with five, because the feature card is the taller column either way.
       That last one is the real prize: the band no longer changes shape when
       an editor publishes.

   ≥1281px only, so it never overlaps the ≤1280px tightening block below —
   and below that width the two columns are too narrow to split. */
@media (min-width: 1281px) {
    .hpx-nr:not(.is-single) .hpx-nr-blog .hpx-nrbody {
        display: grid;
        grid-template-columns: minmax(0, 1.06fr) minmax(0, 1fr);
        gap: 18px;
        align-items: start;
        padding: 4px 18px 2px;
    }

    /* Cover on TOP now: at 434px the card is too narrow to put the cover
       beside the copy, and a vertical card is the shape a feature wants. */
    .hpx-nr:not(.is-single) .hpx-nr-blog .hpx-nrlead {
        margin: 0;
        grid-template-columns: minmax(0, 1fr);
        gap: 14px;
    }

    .hpx-nr:not(.is-single) .hpx-nr-blog .hpx-nrlead-shot { aspect-ratio: 16 / 9; }
    .hpx-nr:not(.is-single) .hpx-nr-blog .hpx-nrlist { padding: 0; }

    /* The list column is ~409px, so the row sheds 20px of thumbnail to keep
       the title on two lines rather than three. */
    .hpx-nr:not(.is-single) .hpx-nr-blog .hpx-nrrow { gap: 12px; padding: 11px 10px; }
    .hpx-nr:not(.is-single) .hpx-nr-blog .hpx-nrrow-shot { width: 64px; height: 52px; border-radius: 11px; }
    .hpx-nr:not(.is-single) .hpx-nr-blog .hpx-nrrow::before { inset-block: 11px; }
}
/* ── The short-feed lead — REMOVED 2026-08-13, and the reason is worth keeping.

   This block restacked the blog lead into a full-width hero whenever the feed
   was two rows or shorter, to fill the ~200px hole `align-items: stretch` left
   above that panel's foot. It was sized for a ~600px panel. THE HOMEPAGE SHELL
   IS 1600px: the blog column is 897px, so the lead cover came out 833px wide
   and — at 16/9 — 468px TALL, four times the area of the side-by-side cover
   and 69% of the source image's full size. That does not fill the gap, it
   overshoots it: the blog panel ends up TALLER than the news desk and the hole
   simply moves to the other panel.

   The hole is fixed at the GRID instead (see `.hpx-nr`), which cannot overshoot
   because it never has to guess a height. Measure the panel against the real
   shell before sizing anything to it — 1600px, not the ~1200px the band's
   proportions suggest by eye. */
/* Between the widest layout and the stack the columns get tight before they
   get short, so the gutters come in one step ahead of the breakpoint. */
@media (max-width: 1280px) {
    .hpx-nr { gap: 18px; }
    .hpx-nrhead { padding: 20px 18px 14px; }
    .hpx .hpx-nrlead { margin-inline: 15px; }
    .hpx .hpx-nrlist { padding-inline: 15px; }
    .hpx .hpx-nrlist.is-news { padding-inline: 13px; }
    .hpx-nrtabwrap { padding-inline: 15px; }
    .hpx-nrfoot { padding: 14px 15px 16px; }
    .hpx .hpx-nrmore { margin-inline: 13px; }
    .hpx .hpx-nrelse { margin-inline: 13px; }
    .hpx-nrrow-shot, .hpx-nrrow-shot.is-news { width: 76px; height: 60px; }
}

@media (max-width: 1024px) {
    /* Two panels of this density do not fit a tablet column; stacked, each
       gets the full width and neither has to shed content. */
    .hpx-nr { grid-template-columns: minmax(0, 1fr); gap: 18px; }

    /* At full width the lead's copy column is generous, so the cover can take
       a wider frame without squeezing the headline. */
    .hpx .hpx-nrlead { grid-template-columns: minmax(0, .62fr) minmax(0, 1fr); }

    /* Nothing is stretching to anything now — each panel is its own height —
       so the news tail's job is done by the content itself. */
    .hpx .hpx-nrlead-title { font-size: 21px; }
    .hpx .hpx-nrlead-sub { -webkit-line-clamp: 2; }
    .hpx-nrrow-shot, .hpx-nrrow-shot.is-news { width: 92px; height: 68px; }
    .hpx .hpx-nrrow-title { font-size: 15px; -webkit-line-clamp: 2; }
    .hpx .hpx-nrrow { padding: 13px 12px; }
}

/* Tablet portrait: the lead's two columns are still worth keeping, but the
   cover has to give the headline room or the title wraps every second word. */
@media (max-width: 820px) {
    .hpx .hpx-nrlead { grid-template-columns: minmax(0, .54fr) minmax(0, 1fr); gap: 16px; }
    .hpx .hpx-nrlead-title { font-size: 19px; }
    .hpx-nrrow-shot, .hpx-nrrow-shot.is-news { width: 84px; height: 64px; }
}

@media (max-width: 640px) {
    .hpx-nrpanel { border-radius: 20px; }

    .hpx-nrhead { padding: 18px 15px 14px; gap: 12px; }
    .hpx-nrhead-ico { width: 44px; height: 44px; font-size: 17px; border-radius: 14px; }
    .hpx .hpx-nrpanel .hpx-nrhead-title { font-size: 17.5px; }   /* (0,3,0) to match the base rule — a media query adds NO specificity, so at (0,2,0) this step silently lost to it */
    .hpx .hpx-nrhead-sub { font-size: 12.4px; }

    /* The cover stacks above the copy — side by side on a phone leaves the
       headline in a 40%-wide column. */
    .hpx .hpx-nrlead { grid-template-columns: minmax(0, 1fr); gap: 14px; margin: 2px 15px; padding: 12px; }
    .hpx-nrlead-shot { aspect-ratio: 16 / 10; }
    .hpx .hpx-nrlead-title { font-size: 18px; -webkit-line-clamp: 4; }
    .hpx .hpx-nrlead-sub { font-size: 12.8px; -webkit-line-clamp: 3; }

    .hpx .hpx-nrlist { padding: 6px 15px 2px; }
    .hpx .hpx-nrlist.is-news { padding: 6px 13px 2px; }

    .hpx .hpx-nrrow { gap: 12px; padding: 11px 9px; margin-inline: -7px; }
    .hpx-nrrow::before { inset-block: 11px; inset-inline-start: 1px; }
    .hpx-nrrow-shot, .hpx-nrrow-shot.is-news { width: 68px; height: 56px; border-radius: 12px; }
    .hpx .hpx-nrrow-title { font-size: 13.8px; }

    /* No hover on a phone, so the chevron never arrives and the 32px column it
       reserved is dead width taken from the headline. */
    .hpx-nrrow-go { display: none; }

    .hpx-nrtabwrap { padding: 2px 15px 0; }
    .hpx .hpx-nrtab { padding: 7px 12px; font-size: 12.4px; }

    .hpx .hpx-nrmore { margin: 12px 13px 4px; margin-top: auto; padding: 12px 14px; font-size: 12.4px; }

    .hpx-nrfoot { padding: 14px 15px 16px; }
    .hpx .hpx-nr-btn { padding: 11px 10px 11px 16px; font-size: 12.8px; gap: 8px; }
    .hpx .hpx-nr-iconbtn { width: 44px; height: 44px; }
}

/* Small phones: the head is an icon, a two-line title, and a control. Three
   things do not fit 340px of usable width, so the control takes its own row. */
@media (max-width: 380px) {
    .hpx-nrhead { flex-wrap: wrap; }
    .hpx .hpx-nr-btn.is-ghost { flex: 1 1 100%; margin-inline-start: 0; }
    .hpx .hpx-nrlive { margin-inline-start: auto; }
    .hpx .hpx-nrpanel .hpx-nrhead-title { font-size: 16.5px; }   /* (0,3,0), same reason as the 640px step */
    .hpx-nrrow-shot, .hpx-nrrow-shot.is-news { width: 60px; height: 52px; }
    .hpx-nrfoot { flex-wrap: wrap; }
    .hpx .hpx-nr-iconbtn { flex: 1 1 100%; width: auto; }
}

/* ── §NR-A11Y) Touch, reduced motion, forced colours ─────────────────── */

/* No hover on touch, so the chevron would never appear — but the row IS a link
   and needs to look like one. It rests visible instead. Above 640px, where the
   chevron still has a column of its own; below that it is removed outright. */
@media (hover: none) {
    .hpx-nrrow-go { opacity: .6; transform: none; }
    .hpx-nrlead-shot img { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .hpx-nrpanel, .hpx-nrpanel::after, .hpx-nrhead-ico,
    .hpx-nrlead, .hpx-nrlead::after, .hpx-nrrow, .hpx-nrrow::before,
    .hpx-nrrow-shot img, .hpx-nrlead-shot img,
    .hpx-nrrow-go, .hpx-nrlead-go, .hpx-nrmore, .hpx-nrmore svg,
    .hpx-nr-btn, .hpx-nr-btn > svg, .hpx-nr-chip, .hpx-nr-iconbtn, .hpx-nrtab { transition: none; }

    .hpx .hpx-nrpanel:hover::after, .hpx .hpx-nrlead:hover::after { transform: none; }
    .hpx .hpx-nrpanel:hover .hpx-nrhead-ico { transform: none; }
    .hpx .hpx-nrlead:hover, .hpx .hpx-nr-btn:hover, .hpx .hpx-nr-iconbtn:hover { transform: none; }
    .hpx .hpx-nr-btn:hover .hpx-nr-chip, .hpx .hpx-nrlead:hover .hpx-nrlead-go { transform: none; }
    .hpx-nrrow:hover .hpx-nrrow-shot img, .hpx-nrlead:hover .hpx-nrlead-shot img { transform: none; }

    .hpx-nrrow-live::before, .hpx-nrlive em { animation: none; }
}

/* A tint is a colour the OS may replace outright in forced-colors mode, taking
   the row's only hover signal with it. The accent bar is a real box and
   survives. The orbit ring does not — a conic gradient is a background image,
   and forced-colors drops it — which is why the bar was kept when the ring
   arrived rather than replaced by it. */
@media (forced-colors: active) {
    .hpx-nrrow:hover::before { transform: scaleY(1); background: Highlight; }
    .hpx-nrlive em, .hpx-nrrow-live::before { animation: none; }
    .hpx-nrpanel::before, .hpx-nrlead::before,
    .hpx-nr-btn::before, .hpx-nr-iconbtn::before { display: none; }
}

/* ---------------------------------------------------------------------------
   16c. Marquee ticker

   A looping strip of linked keywords. The reference is a saturated bar with a
   sparkle between each word; this is the same shape in the site's own blue,
   because a red band is the one colour AIToolsay's palette does not contain.

   ── Why it cannot slow the page down ──────────────────────────────────────
   `transform` is the only animated property, so the strip is promoted to its
   own compositor layer and the loop never triggers layout or paint. That
   matters here more than usual: the scroll-reveal was removed from every band
   for exactly this reason, and a decorative animation that reintroduced
   main-thread work would undo it.

   It also stops when it is not wanted — on hover, on keyboard focus, and
   entirely for `prefers-reduced-motion`.

   ── Why the track is rendered twice ───────────────────────────────────────
   The loop is `translateX(0)` → `translateX(-50%)` across BOTH copies, so when
   copy one has fully left the frame copy two is sitting exactly where copy one
   started. Any other percentage produces a visible jump. This is also why the
   copies must be identical, and why the second is aria-hidden with its links
   taken out of the tab order.
   ------------------------------------------------------------------------ */

.hpx-mq {
    position: relative;
    display: flex;
    width: 100%;
    overflow: hidden;
    padding: 0;
    /* Brand, via the page-wide filled recipe — white sits on `--sc-fill` at
       6.19:1 in light and 4.95:1 in dark. On `--hpx-brand` itself it would be
       3.84 and 2.6, because that token's dark twin is lightened for ink use. */
    background-image: var(--sc-fill);
    background-color: var(--hpx-brand-600);
    /* Full-bleed inside a boxed band: the reference runs edge to edge, and a
       ticker that stops at the container margin reads as a widget rather than
       as a band. */
    border-block: 1px solid rgba(255, 255, 255, .14);
    /* `mask` rather than a gradient overlay — the band behind it is
       admin-selectable, so an overlay in the wrong colour would smear. */
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 5%, #000 95%, transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 5%, #000 95%, transparent 100%);
}

/* The dot field the brand surfaces share, so the bar belongs to the page
   rather than looking like an embedded advert. */
.hpx-mq::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image: radial-gradient(circle, rgba(255, 255, 255, .17) 1px, transparent 1.6px);
    background-size: 15px 15px;
    opacity: .55;
    pointer-events: none;
}

.hpx-mq-track {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    gap: 0;
    /* `min-width: max-content` and NOT a width in %: the track has to be as
       wide as its content, or a long strip wraps and the loop distance stops
       matching the -50% the keyframe moves by. */
    min-width: max-content;
    padding: 13px 0;
    will-change: transform;
    animation: hpx-mq-run var(--hpx-mq-dur, 42s) linear infinite;
}

.hpx-mq[data-reverse="1"] .hpx-mq-track { animation-direction: reverse; }

@keyframes hpx-mq-run {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-100%, 0, 0); }
}

/* Each track is 100% of its own content width, and there are two of them, so
   moving one track by -100% of ITSELF is the same as moving the pair by -50%.
   Written this way the keyframe needs no knowledge of how many copies exist. */

/* RTL runs the other way round, otherwise the text scrolls into the direction
   it is read from. */
[dir="rtl"] .hpx-mq-track { animation-direction: reverse; }
[dir="rtl"] .hpx-mq[data-reverse="1"] .hpx-mq-track { animation-direction: normal; }

.hpx .hpx-mq-item {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    padding: 4px 6px;
    border-radius: 8px;
    color: #fff;
    font-size: 17px;
    font-weight: 800;
    letter-spacing: .04em;
    text-transform: uppercase;
    /* No truncation anywhere in this band: the strip is one line and as wide as
       it needs to be, so nothing is ever squeezed. */
    white-space: nowrap;
    transition: background-color .18s ease, color .18s ease, transform .2s var(--hpx-a-ease);
}

.hpx-mq-sep {
    flex: 0 0 auto;
    width: 19px; height: 19px;
    margin: 0 20px;
    color: rgba(255, 255, 255, .92);
}

.hpx-mq-sep.is-dot {
    width: 6px; height: 6px;
    margin: 0 22px;
    border-radius: 50%;
    background: rgba(255, 255, 255, .8);
}

.hpx-mq-sep.is-slash {
    display: inline-flex;
    width: auto; height: auto;
    margin: 0 18px;
    font-size: 17px;
    font-weight: 700;
    color: rgba(255, 255, 255, .55);
}

/* ── Hover ──────────────────────────────────────────────────────────
   The strip pauses so the thing under the pointer can actually be read and
   clicked — a moving link is a link you cannot hit. Pausing the whole strip
   rather than just slowing it is deliberate: a target that is still drifting is
   still a miss. */
@media (hover: hover) {
    .hpx-mq:hover .hpx-mq-track { animation-play-state: paused; }

    .hpx .hpx-mq-item:hover {
        background-color: rgba(255, 255, 255, .96);
        /* Brand ink on white, not white on white — this is the pair the rest of
           the page uses for a light chip on a brand fill. */
        color: var(--hpx-brand-700);
        transform: translateY(-1px);
    }
}

/* Keyboard users get the same courtesy: tabbing into the strip stops it, or the
   focused link slides out from under the focus ring. */
.hpx-mq:focus-within .hpx-mq-track { animation-play-state: paused; }

.hpx .hpx-mq-item:focus-visible {
    outline: 3px solid #fff;
    outline-offset: 2px;
    background-color: rgba(255, 255, 255, .96);
    color: var(--hpx-brand-700);
}

/* ── Responsive ─────────────────────────────────────────────────────
   Smaller type and tighter separators, and the loop speeds up: the same
   duration across a narrower frame looks slower, because less of the strip is
   on screen to give the eye a sense of movement. */
@media (max-width: 640px) {
    .hpx-mq-track { padding: 10px 0; animation-duration: calc(var(--hpx-mq-dur, 42s) * .68); }
    .hpx .hpx-mq-item { font-size: 14px; letter-spacing: .03em; }
    .hpx-mq-sep { width: 15px; height: 15px; margin: 0 13px; }
    .hpx-mq-sep.is-dot { margin: 0 15px; }
    .hpx-mq-sep.is-slash { margin: 0 12px; font-size: 14px; }
}

/* ── Motion off ─────────────────────────────────────────────────────
   Not `animation: none` alone — with the animation gone the second copy would
   sit there as a duplicated list. The strip becomes a normal scrollable rail
   and the duplicate is removed from the layout entirely. */
@media (prefers-reduced-motion: reduce) {
    .hpx-mq {
        overflow-x: auto;
        scrollbar-width: none;
    }

    .hpx-mq::-webkit-scrollbar { display: none; }

    .hpx-mq-track { animation: none; will-change: auto; }
    .hpx-mq-track[aria-hidden="true"] { display: none; }
}

/* A tint is a colour forced-colors mode may replace outright, taking the white
   label's only backdrop with it. */
@media (forced-colors: active) {
    .hpx-mq { background: Canvas; border-block-color: CanvasText; }
    .hpx-mq::after { display: none; }
    .hpx .hpx-mq-item { color: LinkText; }
    .hpx-mq-track { animation: none; }
    .hpx-mq-track[aria-hidden="true"] { display: none; }
}

/* ---------------------------------------------------------------------------
   17. Testimonials
   ------------------------------------------------------------------------ */

.hpx-quotes { display: grid; grid-template-columns: repeat(var(--hpx-cd), minmax(0, 1fr)); gap: var(--hpx-g); }

.hpx-quote {
    display: flex; flex-direction: column; gap: 12px;
    margin: 0;
    padding: 20px;
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r);
    transition: transform .22s var(--hpx-a-ease), box-shadow .24s ease;
}

.hpx-quote-text { margin: 0; font-size: 14px; line-height: 1.66; color: var(--hpx-ink-2); }
.hpx-quote-who { display: flex; align-items: center; gap: 10px; margin-top: auto; }

.hpx-quote-avatar { width: 38px; height: 38px; flex: 0 0 auto; border-radius: 50%; object-fit: cover; }

.hpx-quote-avatar.is-initial {
    display: grid; place-items: center;
    background: var(--hpx-brand-soft);
    color: var(--hpx-brand-ink);
    font-weight: 700; text-transform: uppercase;
}

.hpx-quote-meta { display: flex; flex-direction: column; min-width: 0; }
.hpx-quote-name { display: inline-flex; align-items: center; gap: 5px; font-size: 13.5px; font-weight: 650; color: var(--hpx-ink); }
.hpx-quote-check { color: var(--hpx-brand); font-size: 11.5px; }
.hpx-quote-role { font-size: 12px; color: var(--hpx-ink-3); }

@media (hover: hover) {
    .hpx-quote:hover { transform: translateY(-3px); box-shadow: var(--hpx-sh-lift); }
}

/* ---------------------------------------------------------------------------
   18. FAQ
   ------------------------------------------------------------------------ */

.hpx-faq { max-width: 860px; margin: 0 auto; display: flex; flex-direction: column; gap: 10px; }

.hpx-faq-item {
    background: var(--hpx-card);
    border: 1px solid var(--hpx-line);
    border-radius: var(--hpx-r-md);
    overflow: hidden;
    transition: border-color .2s ease, box-shadow .22s ease;
}

.hpx-faq-item[open] { border-color: var(--hpx-brand-line); box-shadow: var(--hpx-sh-soft); }

.hpx-faq-item summary {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 15px 18px;
    cursor: pointer;
    list-style: none;
}

.hpx-faq-item summary::-webkit-details-marker { display: none; }
.hpx-faq-ico { color: var(--hpx-brand); font-size: 13px; }

.hpx-faq-q { flex: 1 1 auto; margin: 0; font-size: 14.5px; font-weight: 620; color: var(--hpx-ink); line-height: 1.45; }

/* A CSS plus/minus rather than a chevron SVG: it animates without a second
   asset and stays legible at any zoom. */
.hpx-faq-mark { position: relative; width: 18px; height: 18px; flex: 0 0 auto; }

.hpx-faq-mark::before,
.hpx-faq-mark::after {
    content: '';
    position: absolute;
    left: 50%; top: 50%;
    background: var(--hpx-brand);
    border-radius: 2px;
    transform: translate(-50%, -50%);
    transition: transform .22s var(--hpx-a-ease), opacity .18s ease;
}

.hpx-faq-mark::before { width: 12px; height: 2px; }
.hpx-faq-mark::after  { width: 2px; height: 12px; }

.hpx-faq-item[open] .hpx-faq-mark::after { transform: translate(-50%, -50%) rotate(90deg); opacity: 0; }

.hpx-faq-a { padding: 0 18px 17px 47px; font-size: 14px; line-height: 1.72; color: var(--hpx-ink-3); }

.hpx-faq-more {
    display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap;
    margin-top: 22px;
    font-size: 13.5px; color: var(--hpx-ink-3);
}

/* ---------------------------------------------------------------------------
   19. Logos
   ------------------------------------------------------------------------ */

.hpx-logos { display: flex; align-items: center; gap: 34px; overflow-x: auto; scrollbar-width: none; padding: 6px 0; }
.hpx-logos::-webkit-scrollbar { display: none; }

.hpx-logo { flex: 0 0 auto; opacity: .55; filter: grayscale(1); transition: opacity .2s ease, filter .2s ease; }
.hpx-logo img { height: 26px; width: auto; }
.hpx-logo-word { font-size: 16px; font-weight: 700; color: var(--hpx-ink-3); }

@media (hover: hover) {
    .hpx-logo:hover { opacity: 1; filter: grayscale(0); }
}

/* ---------------------------------------------------------------------------
   20. Banners
   ------------------------------------------------------------------------ */

.hpx-banner {
    position: relative;
    background: var(--hpx-ban-bg, var(--hpx-card-2));
    color: var(--hpx-ban-ink, inherit);
    border-radius: var(--hpx-r);
    overflow: hidden;
}

.hpx-banner-label {
    display: block;
    margin-bottom: 6px;
    font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
    color: var(--hpx-ink-faint, #98A2B3);
}

.hpx-banner-x {
    position: absolute; right: 8px; top: 8px;
    z-index: 3;
    width: 26px; height: 26px;
    border: 0; border-radius: 50%;
    background: rgba(0, 0, 0, .08);
    color: inherit;
    font-size: 17px; line-height: 1;
    cursor: pointer;
}

.hpx-banner-promo { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 20px; align-items: center; padding: 22px; }
.hpx-banner-title { font-size: 19px; }
.hpx-banner-desc { margin-top: 6px; font-size: 14px; color: var(--hpx-ink-3); }
.hpx-banner-promo .hpx-btn { margin-top: 14px; }
.hpx-banner-art img { max-height: 118px; width: auto; }

/* ---------------------------------------------------------------------------
   21. Misc
   ------------------------------------------------------------------------ */

.hpx-html { font-size: 15px; line-height: 1.7; }
.hpx-html h2, .hpx-html h3 { margin: 1.2em 0 .5em; }
.hpx-html p { margin-bottom: 1em; }
.hpx-html a { color: var(--hpx-brand); text-decoration: underline; }

.hpx-empty { padding: 60px 20px; text-align: center; color: var(--hpx-ink-3); }
.hpx-empty i { font-size: 30px; color: var(--hpx-ink-faint, #98A2B3); margin-bottom: 14px; display: block; }
.hpx-empty h2 { font-size: 19px; margin-bottom: 6px; }

.hpx-preview-flag {
    position: sticky; top: 0; z-index: 60;
    display: flex; align-items: center; justify-content: center; gap: 9px;
    padding: 9px 16px;
    background: var(--hpx-t-amber-bg, #FEF4E4);
    color: var(--hpx-t-amber-ink, #A85B08);
    font-size: 12.5px; font-weight: 620;
    border-bottom: 1px solid var(--hpx-t-amber-line, #FBE3C0);
}

/* ---------------------------------------------------------------------------
   22. Motion
   Elements are hidden ONLY once the kit's script has armed them (.is-armed),
   so a visitor with JS off — or one whose IntersectionObserver never fires —
   sees the finished page instead of a blank one. This is the single most
   important rule in the file: CSS that hides content and waits for JS to
   reveal it is CSS that can hide the whole homepage.
   ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
    .hpx [data-hpx-anim].is-armed:not(.is-in) { opacity: 0; }

    .hpx [data-hpx-anim].is-armed:not(.is-in) > * {
        opacity: 0;
        transform: translateY(var(--hpx-a-dist, 18px)) scale(var(--hpx-a-scale, 1));
    }

    .hpx [data-hpx-anim].is-armed { transition: opacity var(--hpx-a-dur, 360ms) var(--hpx-a-ease); }

    .hpx [data-hpx-anim].is-armed > * {
        transition:
            opacity var(--hpx-a-dur, 360ms) var(--hpx-a-ease) var(--hpx-a-delay, 0ms),
            transform var(--hpx-a-dur, 360ms) var(--hpx-a-ease) var(--hpx-a-delay, 0ms);
    }

    .hpx [data-hpx-anim].is-in,
    .hpx [data-hpx-anim].is-in > * { opacity: 1; transform: none; }

    /* Stagger, capped at twelve steps — beyond that the last card arrives a
       second after the first and the band reads as slow rather than as
       choreographed. */
    .hpx [data-hpx-stagger] .hpx-card,
    .hpx [data-hpx-stagger] .hpx-cat,
    .hpx [data-hpx-stagger] .hpx-igcard,
    .hpx [data-hpx-stagger] .hpx-exp,
    .hpx [data-hpx-stagger] .hpx-promo,
    .hpx [data-hpx-stagger] .hpx-quote,
    .hpx [data-hpx-stagger] .hpx-stat,
    .hpx [data-hpx-stagger] .hpx-faq-item {
        /* Capped at 6, not 12. The cap is what bounds the WORST case, and the
           worst case is what the visitor experiences: the Explore band has 16
           tiles, so at a cap of 12 its last card waited 12 × stagger before it
           even began fading. Six steps still reads as a cascade — past that the
           eye stops tracking individual cards anyway — and it halves the time
           the band spends half-painted. */
        transition-delay: calc(min(var(--hpx-i, 0), 6) * var(--hpx-a-stagger, 32ms));
    }
}

/* The blanket rule has to null the TRANSFORMS too, not just the durations: an
   element that animates from translateY(18px) with only its transition
   removed sits eighteen pixels low forever. */
@media (prefers-reduced-motion: reduce) {
    .hpx *,
    .hpx *::before,
    .hpx *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }

    .hpx [data-hpx-anim],
    .hpx [data-hpx-anim] > * { opacity: 1 !important; transform: none !important; }

    /* The blanket `animation-duration:.01ms` above already neuters every
       looping animation in the hero (float, glow drift, twinkle, voice
       pulse, ripple) — this is belt-and-suspenders for the one that also
       needs its transform explicitly zeroed, since it is what parallax
       rides on. */
    .hpx-stack-tile-float { animation: none !important; transform: none !important; }
}

/* ---------------------------------------------------------------------------
   23. Responsive
   ------------------------------------------------------------------------ */

/* ── Hero ─────────────────────────────────────────────────────────────
   Three stages, and the widths were picked by where the layout actually
   breaks rather than by round numbers:

     ≤1320  the orbit pulls in and shrinks — tiles either side of a 720px
            centred column run out of margin here, not at 1024.
     ≤1180  the orbit becomes a horizontal rail under the copy. Six tiles
            scattered through margins that thin means a tile edge grazing a
            word; converting a hair early costs nothing.
     ≤980   the counter card stops being one row of six.               */

@media (max-width: 1320px) {
    .hpx-hero { --hpx-hero-tile: 66px; --hpx-hero-tile-sm: 56px; }
    .hpx-hero-copy { max-width: 620px; }
    .hpx-stack-t1 { left: 1%; }
    .hpx-stack-t2 { right: 1%; }
    .hpx-stack-t3 { left: 5%; }
    .hpx-stack-t4 { right: 5%; }
    .hpx-stack-t5 { left: 0; }
    .hpx-stack-t6 { right: 0; }
}

@media (max-width: 1180px) {
    /* The decoration layer goes entirely: arcs and dots are drawn for tiles
       at fixed percentages, and neither means anything once the tiles are in
       a rail. Hiding it beats leaving dashes pointing at nothing. */
    .hpx-hero-orbit { display: none; }

    /* Explicit order on BOTH, not just the tiles. Flex leaves same-order
       items in DOM order, and the counter card is written after the tiles —
       so ordering only the tiles would put the numbers between the copy and
       the tiles they belong beside. */
    .hpx-herostats { order: 3; }

    .hpx-hero-tiles {
        position: static;
        order: 2;
        display: flex;
        align-items: center;

        /* `width: fit-content` + auto margins rather than
           `justify-content: center`: a centred flex container that overflows
           pushes its first item past scrollLeft 0, so the first tile becomes
           unreachable. This centres while the tiles fit and falls back to a
           normal left-anchored scroller the moment they do not. */
        justify-content: flex-start;
        width: fit-content;
        max-width: 100%;
        gap: 12px;
        margin: 22px auto 0;
        padding: 6px 2px 10px;
        overflow-x: auto;
        overflow-y: visible;
        scroll-snap-type: x proximity;
        scrollbar-width: none;
        -ms-overflow-style: none;
        pointer-events: auto;
    }

    .hpx-hero-tiles::-webkit-scrollbar { display: none; }

    .hpx .hpx-stack-tile {
        position: relative;
        inset: auto;
        flex: 0 0 auto;
        scroll-snap-align: start;
    }

    /* Every tile the same size in the rail: the large/small pairing was
       depth cueing for the scattered layout and reads as inconsistency in a
       row. The caption STAYS — these are the devices with no hover, so this
       is the only place the mark's name can come from. */
    .hpx-orbit-logos .hpx-stack-tile,
    .hpx-orbit-logos .hpx-stack-t3,
    .hpx-orbit-logos .hpx-stack-t4,
    .hpx-orbit-logos .hpx-stack-t5,
    .hpx-orbit-logos .hpx-stack-t6 {
        width: 62px; height: 62px;
        border-radius: 18px;
    }

    /* The caption is absolutely positioned AND this rail is a scroller, so
       `overflow-x: auto` computes overflow-y to `auto` too and clips it
       vertically. The padding is what buys the caption room inside the
       padding box: 10px offset + two lines ≈ 44px, so 48px leaves headroom
       for a longer maker name wrapping the line height. */
    .hpx-hero-tiles { padding-bottom: 48px; }

    .hpx-stack-tip   { font-size: 11.5px; }
    .hpx-stack-tip b { font-size: 10px; }

    .hpx-stack-tile-float { animation: none; transform: none; }
}

/* ── Model tiles, phone ────────────────────────────────────────────────────
   Hidden below 640px at the user's request (2026-07-30). On a phone the four
   logos sit between the hero copy and the counter card, pushing the search
   field — the one thing the hero is for — further down the fold for four links
   that are also reachable from /ai-models and the header rail.

   `display: none` on the container, not `visibility`/`opacity`: the rail
   reserves 48px of padding-bottom for the absolutely-positioned captions, and
   anything short of removing it from flow leaves that band of empty space
   behind. Nothing else needs unwinding — the decorative orbit layer is already
   `display: none` from 1180px down. */
@media (max-width: 640px) {
    .hpx-hero-tiles { display: none; }
}

@media (max-width: 1024px) {
    .hpx-wrap, .hpx-duo { padding-left: 20px; padding-right: 20px; }

    /* The rail bleed is paid for out of the padding above — keep it inside. */
    .hpx-railwrap { --hpx-rail-bleed: 16px; }

    .hpx-grid,
    .hpx-promos,
    .hpx-icongrid,
    .hpx-catgrid,
    .hpx-explore,
    .hpx-quotes { grid-template-columns: repeat(var(--hpx-ct), minmax(0, 1fr)); }

    /* The rail's arrows hang 17px outside the wrapper, and below this width
       the wrapper is flush with the band's 20px gutter — so they would sit
       half off-screen. Tuck them inside the rail instead. */
    .hpx .hpx-nudge.is-prev { inset-inline-start: -6px; }
    .hpx .hpx-nudge.is-next { inset-inline-end: -6px; }

    /* Eight counters do not fit; the strip becomes a scroller rather than
       wrapping into a ragged second row. */
    .hpx-statbar {
        grid-auto-columns: minmax(168px, auto);
        overflow-x: auto;
        scrollbar-width: none;
    }

    .hpx-statbar::-webkit-scrollbar { display: none; }

    .hpx-trust { grid-template-columns: 1fr; text-align: center; padding: 26px 22px; }

    .hpx-trust-copy {
        display: flex;
        flex-direction: column;
        align-items: center;
        padding-inline-end: 0;
        padding-bottom: 26px;
    }

    /* The divider was a vertical rule between two columns; stacked, it has to
       become a horizontal one under the copy or it draws down the middle of
       the text. */
    .hpx-trust-copy::after {
        inset-block: auto;
        inset-inline: 10%;
        bottom: 0;
        width: auto; height: 1px;
        background: linear-gradient(90deg, transparent, var(--hpx-line) 18%, var(--hpx-line) 82%, transparent);
    }

    /* Six sparkle-flanked words plus two 74px flanks do not fit a tablet
       column, and they are pure decoration — the first thing to go. */
    .hpx-head.is-showcase .hpx-title::before,
    .hpx-head.is-showcase .hpx-title::after { display: none; }

    .hpx-assure { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 6px; }

    /* Two columns means every odd item starts a new row, and the sibling rule
       would draw at that row's left edge with nothing beside it. */
    .hpx-assure-item:nth-child(odd)::before { display: none; }
}

/* ── Tablet: two rows of three ──────────────────────────────────────
   1100, not 980. Between those two widths the old layout still packed six
   counters into one row at ~150px each, which is narrower than "50,000+" plus
   its plate — so the card fell back to scrolling and hid the last two figures
   behind an edge with nothing to say it was scrollable. Three columns is the
   honest layout for that range.

   The hairlines go entirely here. A wrapped grid needs dividers on two axes,
   which the old block drew with three nth-child rules at three specificities
   fighting over one box-shadow slot; recessed tiles say the same thing with no
   rules to keep in sync, and they leave box-shadow free for the hover ring. */
@media (max-width: 1100px) {
    .hpx-herostats {
        grid-auto-flow: row;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 10px;
        margin-top: 34px;
        padding: 10px;
    }

    .hpx-hstat::before { display: none; }

    .hpx .hpx-hstat {
        justify-content: flex-start;
        padding: 13px;
        background-color: var(--hpx-bg-2);
        box-shadow: inset 0 0 0 1px var(--hpx-line-soft);
    }

    body.theme-dark .hpx .hpx-hstat {
        background-color: rgba(255, 255, 255, .035);
        box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07);
    }

    /* The figure has a whole column to itself here, so it goes back to a fixed
       size — the viewport clamp is for the six-across row and would keep
       shrinking type that has room to spare. */
    .hpx-hstat-num { font-size: 19px; }
    .hpx-hstat-lbl { font-size: 12.5px; }
}

@media (max-width: 860px) {
    /* Paired half bands stack. Each was written to be self-contained, so
       nothing needs re-ordering here. */
    .hpx-duo { grid-template-columns: 1fr; gap: 16px; }
    .hpx-banner-promo { grid-template-columns: 1fr; }
    .hpx-banner-art { display: none; }
}

@media (max-width: 640px) {
    .hpx-wrap, .hpx-duo { padding-left: 16px; padding-right: 16px; }

    /* The rail bleed is paid for out of the padding above — keep it inside. */
    .hpx-railwrap { --hpx-rail-bleed: 12px; }

    .hpx-band { padding-top: calc(var(--hpx-pt) * .72); padding-bottom: calc(var(--hpx-pb) * .72); }

    .hpx-grid,
    .hpx-promos,
    .hpx-icongrid,
    .hpx-explore,
    .hpx-quotes { grid-template-columns: repeat(var(--hpx-cm), minmax(0, 1fr)); gap: 12px; }

    /* ── Feature cards, phone ──────────────────────────────────────────
       The lead card carries the longest copy in the row and it is the band's
       headline claim, so it takes the full width and reads as a promise
       banner; the other five pair off underneath it. */
    /* The phone-only card joins the grid here — `flex` and not `block`, because
       every other card in this variant is a flex column and a block one would
       collapse its `margin-top: auto` spacing. */
    .hpx-igcard.is-mobileonly { display: flex; }

    .hpx-ig-feature .hpx-igcard { padding: 20px 14px 18px; }
    .hpx-ig-feature .hpx-igcard.is-lead { grid-column: 1 / -1; padding: 22px 18px; }

    .hpx-ig-feature .hpx-ico { width: 50px; height: 50px; font-size: 19px; margin-bottom: 12px; }
    .hpx-ig-feature .hpx-igcard-title { font-size: 14.5px; }
    .hpx-ig-feature .hpx-igcard-sub { font-size: 12.5px; }
    .hpx-igcard-rule { margin: 9px 0 10px; }

    /* No hover on touch, so the arrow would never appear — but the card IS a
       link and needs to look like one. It rests visible instead. */
    .hpx-igcard-go { opacity: .55; transform: none; }

    /* ─── Promo cards STACK on a phone ────────────────────────────────
       They used to be a horizontal snap rail here, because stacked they ran
       1872px on a 390px screen — 512px of which was the decorative gradient
       blob that no longer exists. With that space recovered and the links in
       two columns, four stacked cards come in around a third of that, so the
       rail is not needed and every card is visible by scrolling the page the
       way the rest of it scrolls. A slider inside a vertical page is a second
       gesture to discover and a place for content to hide. */
    .hpx-promo { min-width: 0; }

    /* Two columns of links. A phone-width card is wide enough for two, and it
       is the difference between a six-link card being 340px tall and 190px. */
    /* (0,2,0) for the same reason as the desktop rule — `.hpx ul` is (0,1,1). */
    .hpx .hpx-promo-links {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
        gap: 2px 4px;
        padding: 8px 8px 12px;
    }

    .hpx-promo-links a { padding: 8px; gap: 8px; }
    .hpx-promo-top { padding: 15px 15px 14px; }
    .hpx-promo-ico { width: 42px; height: 42px; border-radius: 13px; font-size: 17px; }
    .hpx .hpx-promo-title { font-size: 16px; }
    .hpx-promo > .hpx-btn { margin: auto 12px 12px; }

    .hpx-head { flex-direction: column; align-items: flex-start; gap: 10px; }

    /* The centred head must stay centred on a phone — the rule above is for
       the default left-aligned head and would otherwise drag the showcase
       eyebrow, heading and sub-heading to the left edge. */
    .hpx-al-center .hpx-head { align-items: center; }

    /* …and the left-aligned showcase head must go back to the left edge. Its
       desktop rule centres the CTA against a tall copy block, which is right
       in a row and wrong once the head has stacked — `align-items` is the
       horizontal axis now, so it was indenting the button. (0,3,0) there, so
       it needs (0,3,0) here to be undone. */
    .hpx-al-left .hpx-head.is-showcase { align-items: flex-start; }

    .hpx-panel { padding: 16px; }

    .hpx-trust { padding: 20px 16px; }
    .hpx-trust-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
    .hpx-trust-stat { padding: 14px 8px 0; }
    .hpx-trust-stat .hpx-ico { width: 38px; height: 38px; font-size: 14px; margin-bottom: 6px; }
    .hpx-trust-num { font-size: 23px; }
    .hpx-spark { height: 34px; }

    .hpx-face { width: 34px; height: 34px; font-size: 12px; }
    .hpx-face + .hpx-face { margin-inline-start: -11px; }

    /* Stacked, the strip is a list, so the hairlines run between rows instead
       of down the middle of a two-up grid.

       `+ .hpx-assure-item` and not `:nth-child(odd)`: it excludes the first
       row for free, and it out-ranks the tablet rule above by source order at
       identical specificity — which is what puts item 3's rule back. */
    .hpx-assure { grid-template-columns: 1fr; gap: 0; padding: 6px 7px; }
    .hpx-assure-item { padding: 11px 10px; gap: 11px; }
    .hpx-assure-item .hpx-ico { width: 34px; height: 34px; border-radius: 10px; }

    .hpx-assure-item + .hpx-assure-item::before {
        display: block;
        inset-inline: 8px;
        top: 0;
        width: auto; height: 1px;
        transform: none;
    }

    /* No hover on touch, so the chevron would never appear — but the row IS a
       link and needs to look like one. It rests visible instead. */
    .hpx-assure-go { opacity: .6; transform: translate(0, -50%); }

    /* 76%, not 136px. The tile now carries three lines of copy under its title,
       and at 136px SEVEN of the nine descriptions ellipsised — a description
       that stops before it has described anything. One card and a clear slice
       of the next is the swipe affordance, and it is the same 76–78% the other
       rails on this page use. */
    .hpx-catrail {
        grid-auto-columns: minmax(220px, 76%);
        --hpx-rail-gap: 12px;
        scroll-snap-type: x proximity;
    }
    .hpx-cat { padding: 22px 16px 18px; }
    .hpx-cat-ico { margin-bottom: 13px; }
    .hpx-cat .hpx-ico { width: 56px; height: 56px; font-size: 22px; border-radius: 17px; }
    .hpx .hpx-cat-title { font-size: 15px; }
    .hpx .hpx-cat-sub { font-size: 12.5px; }
    .hpx .hpx-cat-count { padding: 4px 11px; font-size: 11.5px; }
    .hpx .hpx-cat-go { width: 38px; height: 38px; }

    /* The GRID layout stays compact — its tracks are set by cols_mobile, not by
       the rail, so they get no swipe room to trade against. */
    .hpx-catgrid .hpx-cat { padding: 16px 10px 15px; }
    .hpx-catgrid .hpx-cat .hpx-ico { width: 42px; height: 42px; font-size: 16px; border-radius: var(--hpx-r-md); }
    .hpx .hpx-catgrid .hpx-cat-title { font-size: 12.5px; }
    .hpx .hpx-catgrid .hpx-cat-sub { display: none; }

    .hpx-catgrid { grid-template-columns: repeat(var(--hpx-cm), minmax(0, 1fr)); gap: 10px; }

    .hpx-faq-a { padding-left: 18px; }

    /* ── Hero, phone ────────────────────────────────────────────────── */
    .hpx-hero { padding-top: 12px; }
    /* A shallower wash. Band heads are tighter on a phone, so the subtitle
       arrives at 136px instead of 162px and the desktop radii would reach into
       it. Same constraint as the desktop rule, re-solved for the shorter head:
         brand  — 220px × 64% = 141px, centre -30px → clear at 111px
         violet — 200px × 62% = 124px, centre -34px → clear at  90px */
    .hpx-s-mesh::before {
        background:
            radial-gradient(520px 220px at 88% -30px, var(--hpx-brand-soft), transparent 64%),
            radial-gradient(400px 200px at 6% -34px, var(--hpx-t-violet-bg, #F0EBFE), transparent 62%);
    }

    /* ── Hero copy, phone ──────────────────────────────────────────────
       Drawn for the phone rather than inherited from the desktop composition
       and shrunk. Three things the scaled-down version got wrong, in the
       order a reader meets them:

         1. The headline was one fixed size for every phone. `--hpx-fs-display`
            is NOT the clamp in the base rule — that fallback is dead; the
            compiled token in the inline <style> pins it to 34px flat below
            640. At 414px that put a 362px line in a 368px box (flush), at
            390px it broke the first line in two and the whole headline ran
            FOUR ragged lines / 150px, and at 360px the ≤380 rule's 30px
            filled the box to the pixel with nothing either side.
         2. 1.08 is a display leading, drawn for one or two SHORT lines. Two
            long ones at phone size closed into a slab.
         3. The subtitle carries a hard break the editor placed against the
            desktop measure. On a phone it stranded "productivity." alone on
            a third line. */

    /* At 12px the desktop pill's chrome — a 20px backdrop blur under a wide
       brand glow — reads as haze around a small object rather than as glass,
       so both come down. The blur is a first-paint cost above the fold as
       well; nothing else in the hero uses the variable. */
    .hpx-hero { --hpx-glass-blur: 12px; }

    .hpx .hpx-hero-badge {
        gap: 7px;
        padding: 6px 14px 6px 7px;
        font-size: 12px;
        letter-spacing: -.006em;
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .7),
            0 1px 2px rgba(16, 24, 40, .05),
            0 6px 16px -11px var(--hpx-brand-glow);
    }

    body.theme-dark .hpx .hpx-hero-badge {
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .08),
            0 1px 2px rgba(0, 0, 0, .45),
            0 8px 18px -13px rgba(0, 0, 0, .85);
    }

    .hpx-hero-badge-chip { width: 22px; height: 22px; border-radius: 7px; }
    .hpx-hero-badge-ico  { width: 13px; height: 13px; }

    /* Fluid, and sized so the LONGEST line keeps about a tenth of the box as
       breathing room at every width instead of touching the gutter at one.
       The `- 2px` takes the wrap's own padding out of the vw term so that
       ratio holds at 320 and at 640 alike, and the 34px ceiling meets the
       5.4vw desktop ramp where this query ends (34.6px at 641px) — no step
       across the breakpoint.

       Deliberately not reading `--hpx-fs-display`: the Typography screen
       sets that scale in three fixed steps (62 / 46 / 34), and one flat
       number cannot hold a 22-character line inside boxes that run from
       280px to 594px across this one query. */
    .hpx .hpx-hero-title {
        margin-top: 16px;
        font-size: clamp(22px, calc(7.4vw - 2px), 34px);
        line-height: 1.14;
        letter-spacing: -.024em;
    }

    /* The safety net for a longer edit or a longer translation: a line that
       no longer fits splits near its midpoint instead of dropping one word
       onto a line of its own. Both lines, because either can be the long
       one — today it is the first. */
    .hpx-hero-l1,
    .hpx-hero-l2 { text-wrap: balance; }
    .hpx-hero-l2 { margin-top: .1em; }

    /* The hard break goes and the subtitle re-balances as one block. The
       newline behind the <br> survives as collapsing whitespace, so the two
       sentences still meet with a space — nothing is lost here, and the copy
       stays exactly as typed at every wider viewport. */
    .hpx-hero-sub br { display: none; }

    .hpx .hpx-hero-sub {
        margin-top: 12px;
        font-size: 15px;
        line-height: 1.55;
        text-wrap: balance;
    }

    /* A wider gap than anything above it: everything to this point is read,
       everything below it is used, and that grouping is what tells a thumb
       where the band's one control begins. */
    .hpx-search { margin-top: 26px; }

    .hpx-search-form { gap: 6px; padding: 6px 6px 6px 16px; }
    .hpx-search-ico-wrap, .hpx-search-ico { width: 19px; height: 19px; }
    .hpx-search-field input { height: 48px; font-size: 15px; }

    /* The word would push the field below usable width, so the button
       becomes the magnifier it already carries. */
    .hpx .hpx-search-btn { height: 48px; padding: 0 15px; }
    .hpx-search-btn span,
    .hpx-search-btn-spark { display: none; }
    .hpx-search-btn-ico { display: block; width: 18px; height: 18px; }

    /* The chips label sits above the rail rather than stealing a third of a
       narrow row from the chips themselves. `flex-basis: 0` on the rail is
       what keeps the arrow beside it: at `auto` the basis is the chips'
       max-content width, which on a wrapping row is always wider than what
       is left, so the arrow drops to a line of its own. */
    /* The track stops being a pill once the row wraps to two lines — a 999px
       radius on a two-row block bulges at the ends and stops looking like a
       track at all. */
    .hpx-chipsrow {
        flex-wrap: wrap;
        gap: 8px 10px;
        margin-top: 20px;
        padding: 10px;
        border-radius: 20px;
    }

    /* The label takes its own line rather than stealing a third of a narrow
       row from the chips, and centres with its mark. */
    .hpx-chips-label { width: 100%; justify-content: center; padding-inline-start: 0; }
    .hpx-chips { flex: 1 1 0; padding-bottom: 2px; }

    .hpx-hero-tiles { gap: 10px; margin-top: 18px; }

    .hpx-orbit-logos .hpx-stack-tile,
    .hpx-orbit-logos .hpx-stack-t3,
    .hpx-orbit-logos .hpx-stack-t4,
    .hpx-orbit-logos .hpx-stack-t5,
    .hpx-orbit-logos .hpx-stack-t6 { width: 54px; height: 54px; border-radius: 16px; }
    .hpx-orbit-logos .hpx-ico { font-size: 22px; }

    /* ── The counter card, phone ───────────────────────────────────────
       Two columns of recessed tiles. The plate sits BESIDE the figure rather
       than above it: stacked, six cells ran the card to ~400px of a phone's
       fold for six numbers, and the hero's job is the search field underneath.
       Inline, the same six come in around half that.

       No chevrons on the two linked cells. The card is a stat display first,
       and arrows made it read as a menu; the pressed state carries the
       affordance instead. */
    .hpx-herostats {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 8px;
        margin-top: 24px;
        padding: 8px;
        border-radius: 20px;
    }

    .hpx .hpx-hstat {
        gap: 10px;
        padding: 11px 10px;
        border-radius: 14px;
    }

    .hpx-hstat .hpx-ico {
        width: 36px; height: 36px;
        flex: 0 0 auto;
        font-size: 15px;
        border-radius: 11px;
    }

    .hpx-hstat-num { font-size: 17px; line-height: 1.25; }

    /* ink-2, not ink-3: at this size the muted tone is the one that starts to
       read as disabled rather than as secondary. */
    .hpx-hstat-lbl { font-size: 11.4px; color: var(--hpx-ink-2); }

    /* The pressed state IS the hover state on a touch screen, and it is the
       only affordance the two linked cells get. `.hpx-hero .hpx-herostats >`
       puts this at (0,4,1) so it out-ranks the tablet block's resting tile
       ring, which is still in force at this width. */
    .hpx .hpx-hero .hpx-herostats > a.hpx-hstat:active {
        box-shadow: inset 0 0 0 1px var(--tone-line);
        transform: scale(.97);
    }
}

@media (max-width: 380px) {
    .hpx-wrap, .hpx-duo { padding-left: 13px; padding-right: 13px; }

    /* The rail bleed is paid for out of the padding above — keep it inside. */
    .hpx-railwrap { --hpx-rail-bleed: 9px; }

    /* No title size here any more. The fixed 30px this used to set was the
       bug it was written to fix, one breakpoint too late: at 360px it put a
       385px line into a 334px box. The ≤640 clamp already lands 24.6px at
       360 and 22px at 320, both with their margin intact. */
    .hpx .hpx-hero-badge { padding: 5px 12px 5px 6px; font-size: 11.5px; }
    .hpx-hero-badge-chip { width: 20px; height: 20px; border-radius: 6px; }
    .hpx-hero-badge-ico  { width: 12px; height: 12px; }
    .hpx .hpx-hero-sub   { margin-top: 11px; font-size: 14.5px; }

    /* The chips keep their full label — no ellipsis, no truncation. They just
       get tighter, because a clipped "Perplexity" is a search term nobody can
       read and the whole row exists to be read. */
    .hpx-chipsrow { padding: 8px; border-radius: 18px; gap: 7px 8px; }
    .hpx-chips-label { font-size: 12px; }
    .hpx-chips-label-ico { width: 21px; height: 21px; border-radius: 7px; }
    .hpx-chips-label-ico svg { width: 12px; height: 12px; }
    .hpx .hpx-chip { padding: 8px 14px; font-size: 12.8px; }
    .hpx .hpx-chips-nav { width: 30px; height: 30px; }
    .hpx-chips-nav svg { width: 14px; height: 14px; }

    .hpx-statbar { padding: 12px 4px; }

    .hpx-herostats { gap: 6px; padding: 7px; border-radius: 17px; }
    .hpx .hpx-hstat { padding: 9px 8px; gap: 8px; border-radius: 12px; }
    .hpx-hstat .hpx-ico { width: 32px; height: 32px; font-size: 13px; border-radius: 10px; }
    .hpx-hstat-num { font-size: 15.5px; }
    .hpx-hstat-lbl { font-size: 10.8px; }
}

/* ---------------------------------------------------------------------------
   23b. Rail column counts
   The single source of truth for how many cards a rail shows. Every rail
   derives its card width from this (see `.hpx-lib`), so a partial card at
   either edge is now impossible by construction rather than by tuning a
   min-width until it happens to divide evenly.

   Deliberately LAST in the file. These are all (0,1,0)/(0,2,0) and so is every
   rule they replace, which means source order is what settles it — keeping the
   ladder in one block, after everything else, is what stops a stray card width
   somewhere above silently winning. Widest first so the narrower query wins.

   The counts step down faster than the viewport does because the band is a
   bordered card inside a column, not the full window — five 280px columns need
   about 1500px of window, not 1400.
   ------------------------------------------------------------------------ */

/* 1500, not 1400. At 1440 the five-column library resolved to 261px a card,
   under the 282px the old fixed width was chosen to guarantee, so tool names
   started ellipsising on a screen with room to spare. Stepping down one column
   here puts it back to ~331px. */
@media (max-width: 1500px) {
    .hpx-lib      { --hpx-rail-cols: 4; }
    .hpx-catrail  { --hpx-rail-cols: 6; }
    .hpx-cardrail { --hpx-rail-cols: 5; }
    .hpx-cardrail.is-row { --hpx-rail-cols: 4; }
}

@media (max-width: 1180px) {
    .hpx-lib      { --hpx-rail-cols: 3; }
    .hpx-catrail  { --hpx-rail-cols: 5; }
    .hpx-cardrail { --hpx-rail-cols: 4; }
    .hpx-cardrail.is-row { --hpx-rail-cols: 3; }
}

@media (max-width: 1024px) {
    .hpx-catrail  { --hpx-rail-cols: 4; }
    .hpx-cardrail { --hpx-rail-cols: 3; }
}

@media (max-width: 860px) {
    .hpx-lib      { --hpx-rail-cols: 2; }
    .hpx-catrail  { --hpx-rail-cols: 3; }
    .hpx-cardrail { --hpx-rail-cols: 2; }
    .hpx-cardrail.is-row { --hpx-rail-cols: 2; }
}

/* The ladder STOPS at 641px. Below that each rail sets its own
   `grid-auto-columns` in the ≤640 block further up — the peek layout phones
   have always had — and those declarations come later in the file, so any
   column count written here would be dead. Leaving dead rules that silently
   lose is the exact failure this ladder was built to end, so there are none. */

/* ---------------------------------------------------------------------------
   24. Print
   ------------------------------------------------------------------------ */

@media print {
    .hpx-search, .hpx-nudge, .hpx-banner, .hpx-preview-flag { display: none !important; }
    .hpx-card, .hpx-promo, .hpx-panel { break-inside: avoid; box-shadow: none; }
    .hpx [data-hpx-anim] { opacity: 1 !important; transform: none !important; }
}

/* ---------------------------------------------------------------------------
   25. §HPXP — the detail page's card language, homepage-wide

   ── What this block is ────────────────────────────────────────────────────
   /ai-models/{slug} and /ai-directory/{slug} dress every card with four
   things (atd-styles §CARD-WASH / §SPINE-BLUE / §SIDEBAR-ORBIT):

     · a soft diagonal wash, corner to corner, in the card's OWN hue
     · a small saturated bloom concentrated in ONE corner
     · a hairline edge tinted in that same hue
     · a masked conic ORBIT ring that sweeps the border on hover

   The homepage had exactly one band on that language — the newsroom, rebuilt
   2026-08-13 (§NR-*). Every other band was a flat `--hpx-card` surface with a
   tone wash held at OPACITY ZERO until hover. Two consequences, both bad:

     1. AT REST the whole page was white cards on a white ground, separated by
        a grey hairline and nothing else. The colour system existed but was
        invisible until you moved a pointer through it.
     2. ON TOUCH — over half the traffic — hover never arrives, so the resting
        state was the ONLY state those visitors ever saw. The entire tone
        palette was desktop-only by accident.

   This block moves the wash to REST and gives hover somewhere better to go.

   ── The alphas are measured, not chosen ───────────────────────────────────
   Every number below is measured against this page's real grounds — #FFFFFF
   in light, #17171B in dark — and inherited from the newsroom pass, which
   measured them first. They are NOT atd-styles' numbers: that page sits on a
   TINTED ground (#f6f8fc) where a white card separates by itself, so its .055
   wash only has to carry hue. Here the card and the page are the same white
   and the wash has to carry hue AND separation, so light runs .075/.055 and
   dark — where the card is LIGHTER than the ground and separates on its own —
   runs .13/.09.

   ── Four numbers, one dark rule ───────────────────────────────────────────
   Wash start, wash end, edge and bloom are custom properties set once on
   `[data-tone]` and re-set once under `body.theme-dark`. Every host below
   reads them. That is the difference between a system and eleven copies: a
   dark-mode retune is four numbers in one place, not a second half of this
   file that drifts out of step with the first.

   ── Specificity, because this page is nothing but links ───────────────────
   `.hpx a { color: inherit }` is (0,1,1), so anything coloured here is written
   `.hpx .hpx-x` — (0,2,0) — and every hover colour `.hpx .hpx-x:hover`,
   (0,3,0). The same reasoning the newsroom block records at length.

   ── Where the ring lives, host by host ────────────────────────────────────
   The orbit needs a pseudo-element to itself. Three hosts had both free; on
   the other four ONE was already carrying the hover-only wash this block
   replaces — so retiring that wash is exactly what frees the slot. Nothing
   was displaced that this pass did not already supersede:

     .hpx-card-showcase   ::before ring   ::after bloom      (both were free)
     .hpx-promo           ::before ring   ::after bloom      (both were free)
     .hpx-trust-stat      ::before ring   ::after bloom      (both were free)
     .hpx-cat             ::after  ring   bloom → background (::before = halo)
     .hpx-libcol          ::after  ring   bloom → background (::before = bar)
     .hpx-exp             ::after  ring   bloom → background (::before = wash)
     .hpx-igcard          ::before ring   bloom → background (tile + feature)
     .hpx-igcard          ::after  ring   bloom → background (plain)

   Where the bloom is a BACKGROUND LAYER it cannot swell on hover — background
   layers do not cross-fade. That is a deliberate trade, not an oversight: a
   static bloom plus a moving ring still reads as one designed surface, and
   the alternative was adding a span to four blades to win an animation most
   visitors will never see because they are on a touch screen.
   ------------------------------------------------------------------------ */

/* ── §HPXP-RGB) The hue as a triplet ──────────────────────────────────────
   `--tone-bg/ink/line` stay the source of every FLAT colour on this page and
   nothing here touches them. But a wash, a bloom and a conic tail need to
   vary ALPHA along a gradient, and no tone token carries an RGB triplet —
   `--hpx-t-blue-bg` is already a composited pale hex. These eleven lines are
   that missing primitive.

   Blue is 54,128,237 — `--hpx-brand` #3680ED, byte-identical to the value
   §NR-TONE declares — so a newsroom panel and a showcase card orbit in the
   SAME blue rather than in two blues nobody can tell apart but everybody can
   feel. The rest match atd-styles' ramp so the three detail pages and this
   one share a palette exactly.

   ⚠️ The fallback on the base selector is load-bearing. An undefined triplet
   makes every `rgba(var(--tone-rgb), …)` below INVALID, and an invalid
   declaration is dropped SILENTLY — the wash, the edge and the ring would
   simply not exist, with nothing in the output to say why. Same trap §NR-SHELL
   records. */
.hpx [data-tone]           { --tone-rgb: 54, 128, 237; }
.hpx [data-tone="blue"]    { --tone-rgb: 54, 128, 237; }
.hpx [data-tone="violet"]  { --tone-rgb: 139, 92, 246; }
.hpx [data-tone="emerald"] { --tone-rgb: 16, 185, 129; }
.hpx [data-tone="amber"]   { --tone-rgb: 245, 158, 11; }
.hpx [data-tone="rose"]    { --tone-rgb: 244, 63, 94; }
.hpx [data-tone="cyan"]    { --tone-rgb: 34, 184, 217; }
.hpx [data-tone="indigo"]  { --tone-rgb: 99, 102, 241; }
.hpx [data-tone="orange"]  { --tone-rgb: 249, 115, 22; }
.hpx [data-tone="teal"]    { --tone-rgb: 20, 184, 166; }
.hpx [data-tone="pink"]    { --tone-rgb: 236, 72, 153; }
.hpx [data-tone="slate"]   { --tone-rgb: 100, 116, 139; }

/* The ramp every host reads. Four numbers in light, four in dark. */
.hpx [data-tone] {
    --hpxp-a1: .075;    /* wash, the lit corner */
    --hpxp-a2: .055;    /* wash, the far corner */
    --hpxp-edge: .20;   /* the tinted hairline */
    --hpxp-bloom: .20;  /* the corner bloom's hot end */
}

/* Dark: the ground drops to #0E0E11 and the card to #17171B, so the card is
   now LIGHTER than the page and separates on its own. The wash is free to
   carry hue rather than separation — and every alpha rises anyway, because a
   5% tint that reads over white is invisible over near-black. */
body.theme-dark .hpx [data-tone] {
    --hpxp-a1: .13;
    --hpxp-a2: .09;
    --hpxp-edge: .30;
    --hpxp-bloom: .17;
}

/* ── §HPXP-WASH) The surface ──────────────────────────────────────────────
   `background-color` and `background-image` as SEPARATE properties, never the
   `background` shorthand: the shorthand resets colour to transparent, which
   would drop `--hpx-card` out from under the wash and let the page ground
   show through every card on the site.

   The 152deg angle, the 48% dead stop and the fade to `rgba(same-hue, 0)`
   rather than the `transparent` KEYWORD all come from §NR-SHELL. That last
   one matters more than it looks: a gradient fading to `transparent`
   interpolates through transparent BLACK in sRGB and leaves a grey, dirty
   fringe down the middle of the card. */

.hpx .hpx-card-showcase,
.hpx .hpx-cat,
.hpx .hpx-libcol,
.hpx .hpx-exp,
.hpx .hpx-promo,
.hpx .hpx-trust-stat,
.hpx .hpx-ig-tile .hpx-igcard,
.hpx .hpx-ig-feature .hpx-igcard {
    background-color: var(--hpx-card);
    background-image:
        linear-gradient(152deg,
            rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
            rgba(var(--tone-rgb), 0)             48%,
            rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
    background-repeat: no-repeat;
    border-color: rgba(var(--tone-rgb), var(--hpxp-edge));
}

/* The trust card sat on `--hpx-brand-line` because the band beneath it is a
   brand wash and a grey border on that reads as dirt. The tone edge is the
   same idea one step further — it is the CARD's hue now, not the band's. */

/* ⚠️ THE ONE HOST THAT NEEDS A DARK TWIN OF ITS OWN. Every other card here
   themes itself for free, because dark mode only re-sets the four alphas and
   the wash rule never changes. The trust counters are the exception: §16's
   dark twin sets `background: var(--hpx-card-2)` — the SHORTHAND, at (0,3,1)
   — which both out-ranks the (0,2,0) wash above AND resets background-image
   to none. Without this the whole trust band silently loses its wash after
   dark, and only after dark, which is the kind of bug that ships.

   `--hpx-card-2`, not `--hpx-card`: those counters sit ON the trust band,
   which is itself `card`, so at night they step up one surface to separate
   from it. That decision is upheld here, not undone. */
body.theme-dark .hpx .hpx-trust-stat {
    background-color: var(--hpx-card-2);
    background-image:
        linear-gradient(152deg,
            rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
            rgba(var(--tone-rgb), 0)             48%,
            rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
    background-repeat: no-repeat;
}

/* ── The four hosts whose bloom is a background layer ─────────────────────
   Two images in one stack: the bloom first (it must paint OVER the wash, and
   the first layer in a background stack is the topmost), then the wash.

   The bloom's CENTRE sits OUTSIDE the box on purpose — `calc(100% + 34px)`
   across and a negative offset up — so what lands on the card is the falloff
   rather than a circle drawn on it. That is the reference card's single most
   recognisable cue, and drawing the whole circle inside the box is how it
   reads as a sticker instead of as light. */
.hpx .hpx-cat,
.hpx .hpx-libcol,
.hpx .hpx-exp,
.hpx .hpx-ig-tile .hpx-igcard,
.hpx .hpx-ig-feature .hpx-igcard {
    background-image:
        radial-gradient(220px 200px at calc(100% + 30px) -54px,
            rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
            rgba(var(--tone-rgb), 0) 72%),
        linear-gradient(152deg,
            rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
            rgba(var(--tone-rgb), 0)             48%,
            rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
}

/* The library column is the tallest card on the page — 22px radius, a full
   list inside it — so its bloom is scaled up to match. A 220px bloom on a
   560px column reads as a smudge in one corner rather than as light falling
   across the top of it. */
.hpx .hpx-libcol {
    background-image:
        radial-gradient(320px 260px at calc(100% + 34px) -66px,
            rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
            rgba(var(--tone-rgb), 0) 72%),
        linear-gradient(152deg,
            rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
            rgba(var(--tone-rgb), 0)             48%,
            rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
}

/* Explore tiles are the smallest host — 15 to a grid — so the bloom shrinks
   with them, and their `::before` keeps the resting radial it already had.
   Two washes on one 96px tile is haze; this one is tuned DOWN so the pair
   reads as a single lit corner. */
.hpx .hpx-exp {
    --hpxp-bloom: .13;
    background-image:
        radial-gradient(150px 130px at calc(100% + 20px) -36px,
            rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
            rgba(var(--tone-rgb), 0) 72%),
        linear-gradient(152deg,
            rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
            rgba(var(--tone-rgb), 0)             48%,
            rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
}
body.theme-dark .hpx .hpx-exp { --hpxp-bloom: .12; }

/* The `plain` icongrid variant is deliberately airy — transparent, no card at
   rest, its whole resting presence carried by the icon — so it is NOT in the
   wash list above. It DOES become a real card on hover, and at that moment
   the ring arrives with it, so the wash arrives too rather than leaving a lit
   border round a flat white box. (0,3,1): the rule it has to follow,
   `.hpx-ig-plain .hpx-igcard:hover`, is (0,2,1). */
@media (hover: hover) and (pointer: fine) {
    .hpx .hpx-ig-plain .hpx-igcard:hover {
        background-color: var(--hpx-card);
        background-image:
            linear-gradient(152deg,
                rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
                rgba(var(--tone-rgb), 0)             48%,
                rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
        background-repeat: no-repeat;
        border-color: rgba(var(--tone-rgb), calc(var(--hpxp-edge) + .16));
    }
}

/* ── §HPXP-BLOOM) The three hosts that get the animated bloom ─────────────
   `isolation: isolate` is what makes `z-index: -1` mean "over the card's own
   background, under every word on it". A positioned box with no z-index is
   NOT a stacking context, so without it the pseudo escapes to an ancestor's
   context and paints BEHIND the card's background — i.e. vanishes, with the
   markup and the CSS both looking perfectly correct.

   `.hpx-card-showcase` already isolates; the other two are set here. */
.hpx .hpx-promo,
.hpx .hpx-trust-stat { isolation: isolate; }

.hpx .hpx-card-showcase::after,
.hpx .hpx-promo::after,
.hpx .hpx-trust-stat::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(260px 230px at calc(100% + 32px) -60px,
        rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
        rgba(var(--tone-rgb), 0) 72%) no-repeat;
    opacity: .92;
    transform-origin: 100% 0;
    transition: opacity .36s ease, transform .52s cubic-bezier(.16, 1, .3, 1);
}

/* The promo panel is the widest card in the band, so its bloom widens too. */
.hpx .hpx-promo::after {
    background: radial-gradient(340px 280px at calc(100% + 36px) -72px,
        rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
        rgba(var(--tone-rgb), 0) 72%) no-repeat;
}

@media (hover: hover) and (pointer: fine) {
    /* The bloom SWELLS from the corner it hangs off, so the light appears to
       move rather than simply brighten. Paired with the ring on the same
       hover, that is the card's whole answer — it does not lift any further
       than the transform each band already gives it. */
    .hpx .hpx-card-showcase:hover::after,
    .hpx .hpx-promo:hover::after,
    .hpx .hpx-trust-stat:hover::after { opacity: 1; transform: scale(1.14); }
}

/* ── §HPXP-ORBIT) The rotating light ──────────────────────────────────────
   atd-styles §SIDEBAR-ORBIT, one recipe at seven scales. Speed is a statement
   about size: a big ambient panel sweeps slowly because slow reads as large,
   and a small target sweeps fast because it has less border to cross.

     promo panel      8.5s   the slow ambient sweep
     library column   7.5s
     trust stat       5.2s
     showcase card    4.2s   a card you can actually click
     icongrid card    3.6s
     category tile    3.4s
     explore tile     3.1s   the smallest host

   THE HUE IS NOT FIXED: it reads `--tone-rgb`, so a violet card orbits violet
   and an amber one amber. That is the difference between an effect pasted
   onto seven bands and a system — the light belongs to the card it is
   lighting.

   Because the gradient is written once, dark mode is not a second copy of an
   eleven-stop conic. It is two alpha numbers, lifted because a 50%-alpha tail
   that reads over #FFFFFF disappears over #17171B.

   A NEW registered property and a NEW keyframe rather than reusing the
   newsroom's: §NR-ORBIT is documented as removable in one piece, and hanging
   seven more bands off its internals would quietly make that false. */

@property --hpx-pspin { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@keyframes hpx-porbit { to { --hpx-pspin: 360deg; } }

.hpx [data-tone] {
    --orb-hot: 255, 255, 255;   /* the specular core of the comet */
    --hpxp-o1: .5;              /* leading tail */
    --hpxp-o2: .88;             /* trailing tail */
    --hpxp-o3: 1;               /* core */
}
body.theme-dark .hpx [data-tone] { --hpxp-o1: .66; --hpxp-o2: 1; }

@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
    /* Two full-box mask layers — one clipped to the content box, one to the
       border box — composited with xor, leaving exactly the padding band.
       `mask-clip` follows `border-radius`, so the inner cut is a rounded rect
       on a rounded rect and the corners stay true.

       ⚠️ THE @supports GATE IS LOAD-BEARING, NOT DEFENSIVE. Without
       mask-composite the two layers UNION and the conic paints as a SOLID
       BLOCK over the card, covering every word on it. Where the mask is
       missing there is simply no ring and the border/lift/shadow hover each
       band already has carries the state.

       ⚠️ inset: 0, not the reference's inset: -1px. Five of these seven hosts
       carry `overflow: hidden` so they can clip their own wash, and a ring
       hung 1px outside gets sliced off on all four sides — which reads as a
       rendering fault, not as a highlight. Sitting the ring ON the padding
       edge costs nothing visually and clips cleanly everywhere. */
    .hpx .hpx-card-showcase::before,
    .hpx .hpx-promo::before,
    .hpx .hpx-trust-stat::before,
    .hpx .hpx-cat::after,
    .hpx .hpx-libcol::after,
    .hpx .hpx-exp::after,
    .hpx .hpx-ig-tile .hpx-igcard::before,
    .hpx .hpx-ig-feature .hpx-igcard::before,
    .hpx .hpx-ig-plain .hpx-igcard::after {
        content: '';
        position: absolute;
        inset: 0;
        /* The four repurposed slots carried geometry from the effect they used
           to run — a 92px top strip, a 2px hairline, a scaled transform. All
           of it has to be unwound here or the ring inherits a sliver of its
           predecessor's box. */
        width: auto;
        height: auto;
        transform: none;
        z-index: 2;
        pointer-events: none;
        border-radius: inherit;
        padding: var(--hpxp-t, 1.5px);
        --hpx-pspin: 0deg;
        background: conic-gradient(from var(--hpx-pspin),
            rgba(var(--tone-rgb), 0)                 0deg,
            rgba(var(--tone-rgb), var(--hpxp-o1))   30deg,
            rgba(var(--orb-hot),  var(--hpxp-o3))   52deg,
            rgba(var(--tone-rgb), var(--hpxp-o2))   74deg,
            rgba(var(--tone-rgb), 0)               118deg,
            rgba(var(--tone-rgb), 0)               180deg,
            rgba(var(--tone-rgb), var(--hpxp-o1))  210deg,
            rgba(var(--orb-hot),  var(--hpxp-o3))  232deg,
            rgba(var(--tone-rgb), var(--hpxp-o2))  254deg,
            rgba(var(--tone-rgb), 0)               298deg,
            rgba(var(--tone-rgb), 0)               360deg);
        -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
                mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
        -webkit-mask-composite: xor;
                mask-composite: exclude;
        opacity: 0;
        transition: opacity .32s ease;
        animation: hpx-porbit var(--hpxp-sp, 4s) linear infinite;
        /* A paused animation does not tick, so a page of idle cards costs
           nothing — and re-entering RESUMES the sweep rather than snapping
           the comet back to 0deg under the pointer. */
        animation-play-state: paused;
    }

    .hpx .hpx-promo::before                    { --hpxp-sp: 8.5s; }
    .hpx .hpx-libcol::after                    { --hpxp-sp: 7.5s; }
    .hpx .hpx-trust-stat::before               { --hpxp-sp: 5.2s; }
    .hpx .hpx-card-showcase::before            { --hpxp-sp: 4.2s; }
    .hpx .hpx-ig-tile .hpx-igcard::before,
    .hpx .hpx-ig-feature .hpx-igcard::before,
    .hpx .hpx-ig-plain .hpx-igcard::after      { --hpxp-sp: 3.6s; --hpxp-t: 1.4px; }
    .hpx .hpx-cat::after                       { --hpxp-sp: 3.4s; }
    .hpx .hpx-exp::after                       { --hpxp-sp: 3.1s; --hpxp-t: 1.4px; }

    /* Keyboard is deliberately OUTSIDE the hover query. A keyboard on a touch
       screen is a real combination, and focus deserves the same answer as the
       pointer — the same call §NR-ORBIT makes. */
    .hpx .hpx-card-showcase:focus-visible::before,
    .hpx .hpx-promo:focus-within::before,
    .hpx .hpx-trust-stat:focus-visible::before,
    .hpx .hpx-cat:focus-visible::after,
    .hpx .hpx-libcol:focus-within::after,
    .hpx .hpx-exp:focus-visible::after,
    .hpx .hpx-ig-tile .hpx-igcard:focus-visible::before,
    .hpx .hpx-ig-feature .hpx-igcard:focus-visible::before,
    .hpx .hpx-ig-plain .hpx-igcard:focus-visible::after { opacity: 1; animation-play-state: running; }

    @media (hover: hover) and (pointer: fine) {
        /* (0,3,1) throughout. Four of these slots have an existing
           `:hover::after { opacity: … }` at (0,2,1) from the wash they used to
           run — `.hpx-cat` pins it at .55, which would light the comet at
           barely half strength — so every one of them has to be out-ranked,
           not merely followed. */
        .hpx .hpx-card-showcase:hover::before,
        .hpx .hpx-promo:hover::before,
        .hpx .hpx-trust-stat:hover::before,
        .hpx .hpx-cat:hover::after,
        .hpx .hpx-libcol:hover::after,
        .hpx .hpx-exp:hover::after,
        .hpx .hpx-ig-tile .hpx-igcard:hover::before,
        .hpx .hpx-ig-feature .hpx-igcard:hover::before,
        .hpx .hpx-ig-plain .hpx-igcard:hover::after { opacity: 1; animation-play-state: running; }

        /* The panel steps BACK the moment a control inside it claims the
           pointer, so the thing under the cursor always wins — identical
           choreography to the newsroom and to the detail pages' section rail,
           which is what makes the three read as one site. Without `:has()`
           the panel simply stays lit: a degradation, not a break. */
        .hpx .hpx-promo:has(a:hover)::before,
        .hpx .hpx-libcol:has(a:hover)::after { opacity: .3; }
    }
}

/* ── §HPXP-LIFT) Depth, matched across the seven hosts ────────────────────
   Each band shipped its own resting shadow and they had drifted — three
   layers here, two there, one card with none at all. One recipe: a glass
   hairline along the top inner edge, a 1px contact shadow to seat the card
   against the page, and a wide soft one for lift. The tone is carried into
   the lift shadow at low alpha, so a violet card casts a violet-tinged
   shadow rather than the same grey as its blue neighbour.

   Written on the SHADOW only. Every band keeps its own transform and its own
   border-radius — this pass is about surface, not about geometry, and
   flattening seven bands to one shape would cost the page its rhythm. */
.hpx .hpx-card-showcase,
.hpx .hpx-cat,
.hpx .hpx-libcol,
.hpx .hpx-exp,
.hpx .hpx-promo,
.hpx .hpx-trust-stat,
.hpx .hpx-ig-tile .hpx-igcard,
.hpx .hpx-ig-feature .hpx-igcard {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 14px 30px -22px rgba(var(--tone-rgb), .40),
        0 10px 24px -20px rgba(16, 24, 40, .26);
}

body.theme-dark .hpx .hpx-card-showcase,
body.theme-dark .hpx .hpx-cat,
body.theme-dark .hpx .hpx-libcol,
body.theme-dark .hpx .hpx-exp,
body.theme-dark .hpx .hpx-promo,
body.theme-dark .hpx .hpx-trust-stat,
body.theme-dark .hpx .hpx-ig-tile .hpx-igcard,
body.theme-dark .hpx .hpx-ig-feature .hpx-igcard {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .05),
        0 1px 2px rgba(0, 0, 0, .42),
        0 14px 30px -22px rgba(var(--tone-rgb), .34),
        0 10px 24px -20px rgba(0, 0, 0, .78);
}

@media (hover: hover) and (pointer: fine) {
    .hpx .hpx-card-showcase:hover,
    .hpx .hpx-cat:hover,
    .hpx .hpx-libcol:hover,
    .hpx .hpx-exp:hover,
    .hpx .hpx-promo:hover,
    .hpx .hpx-trust-stat:hover,
    .hpx .hpx-ig-tile .hpx-igcard:hover,
    .hpx .hpx-ig-feature .hpx-igcard:hover {
        border-color: rgba(var(--tone-rgb), calc(var(--hpxp-edge) + .22));
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .8),
            0 1px 2px rgba(16, 24, 40, .05),
            0 22px 44px -26px rgba(var(--tone-rgb), .52),
            0 14px 30px -22px rgba(16, 24, 40, .28);
    }

    body.theme-dark .hpx .hpx-card-showcase:hover,
    body.theme-dark .hpx .hpx-cat:hover,
    body.theme-dark .hpx .hpx-libcol:hover,
    body.theme-dark .hpx .hpx-exp:hover,
    body.theme-dark .hpx .hpx-promo:hover,
    body.theme-dark .hpx .hpx-trust-stat:hover,
    body.theme-dark .hpx .hpx-ig-tile .hpx-igcard:hover,
    body.theme-dark .hpx .hpx-ig-feature .hpx-igcard:hover {
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .07),
            0 1px 2px rgba(0, 0, 0, .45),
            0 22px 44px -26px rgba(var(--tone-rgb), .46),
            0 14px 30px -22px rgba(0, 0, 0, .82);
    }
}

/* ── §HPXP-RESP) Tablet and phone ─────────────────────────────────────────
   The bloom is sized in PIXELS, so it does not shrink with the card the way
   the wash does — a 340px bloom on a 340px desktop promo is light falling on
   a corner, and the same 340px bloom on a 320px phone card is a flood across
   the whole surface. Both bloom sizes step down, twice.

   The wash and the edge are untouched: they are proportional already, and the
   colour system has to survive the breakpoint or the phone gets the flat
   white page this pass exists to remove. */

@media (max-width: 991.98px) {
    .hpx .hpx-libcol {
        background-image:
            radial-gradient(240px 200px at calc(100% + 26px) -50px,
                rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
                rgba(var(--tone-rgb), 0) 72%),
            linear-gradient(152deg,
                rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
                rgba(var(--tone-rgb), 0)             48%,
                rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
    }
    .hpx .hpx-promo::after {
        background: radial-gradient(260px 220px at calc(100% + 28px) -56px,
            rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
            rgba(var(--tone-rgb), 0) 72%) no-repeat;
    }
}

@media (max-width: 767.98px) {
    /* One step thinner. A 1.5px ring on a 320px card is proportionally twice
       what it is on a 620px one, and at that width it stops reading as a lit
       edge and starts reading as a second border. */
    .hpx .hpx-card-showcase::before,
    .hpx .hpx-promo::before,
    .hpx .hpx-trust-stat::before,
    .hpx .hpx-cat::after,
    .hpx .hpx-libcol::after,
    .hpx .hpx-exp::after,
    .hpx .hpx-ig-tile .hpx-igcard::before,
    .hpx .hpx-ig-feature .hpx-igcard::before,
    .hpx .hpx-ig-plain .hpx-igcard::after { --hpxp-t: 1.2px; }

    .hpx .hpx-cat,
    .hpx .hpx-ig-tile .hpx-igcard,
    .hpx .hpx-ig-feature .hpx-igcard {
        background-image:
            radial-gradient(150px 140px at calc(100% + 20px) -38px,
                rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
                rgba(var(--tone-rgb), 0) 72%),
            linear-gradient(152deg,
                rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
                rgba(var(--tone-rgb), 0)             48%,
                rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
    }

    .hpx .hpx-libcol {
        background-image:
            radial-gradient(190px 170px at calc(100% + 22px) -42px,
                rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
                rgba(var(--tone-rgb), 0) 72%),
            linear-gradient(152deg,
                rgba(var(--tone-rgb), var(--hpxp-a1)) 0%,
                rgba(var(--tone-rgb), 0)             48%,
                rgba(var(--tone-rgb), var(--hpxp-a2)) 100%);
    }

    .hpx .hpx-card-showcase::after,
    .hpx .hpx-trust-stat::after {
        background: radial-gradient(170px 150px at calc(100% + 22px) -40px,
            rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
            rgba(var(--tone-rgb), 0) 72%) no-repeat;
    }
    .hpx .hpx-promo::after {
        background: radial-gradient(200px 175px at calc(100% + 24px) -46px,
            rgba(var(--tone-rgb), var(--hpxp-bloom)) 0%,
            rgba(var(--tone-rgb), 0) 72%) no-repeat;
    }
}

/* ── §HPXP-A11Y) Touch, reduced motion, forced colours ────────────────────
   On a touch screen the ring can never be triggered, and an animation that
   can never run is still a compositor layer per card. Suppressing it there is
   a battery decision, not a design one — the resting wash, the bloom and the
   tinted edge are what those visitors were getting nothing of before, and
   they all remain. */
@media (hover: none) {
    .hpx .hpx-card-showcase::before,
    .hpx .hpx-promo::before,
    .hpx .hpx-trust-stat::before,
    .hpx .hpx-cat::after,
    .hpx .hpx-libcol::after,
    .hpx .hpx-exp::after,
    .hpx .hpx-ig-tile .hpx-igcard::before,
    .hpx .hpx-ig-feature .hpx-igcard::before,
    .hpx .hpx-ig-plain .hpx-igcard::after { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
    /* The ring still APPEARS on hover and focus — it is state, and removing
       state is not what reduced motion asks for. What stops is the sweep. */
    .hpx .hpx-card-showcase::before,
    .hpx .hpx-promo::before,
    .hpx .hpx-trust-stat::before,
    .hpx .hpx-cat::after,
    .hpx .hpx-libcol::after,
    .hpx .hpx-exp::after,
    .hpx .hpx-ig-tile .hpx-igcard::before,
    .hpx .hpx-ig-feature .hpx-igcard::before,
    .hpx .hpx-ig-plain .hpx-igcard::after { animation: none !important; }

    .hpx .hpx-card-showcase::after,
    .hpx .hpx-promo::after,
    .hpx .hpx-trust-stat::after { transition: none !important; }

    .hpx .hpx-card-showcase:hover::after,
    .hpx .hpx-promo:hover::after,
    .hpx .hpx-trust-stat:hover::after { transform: none; }
}

@media (forced-colors: active) {
    /* Every layer this block adds is decorative and none of it survives a
       forced palette intact — a conic in system colours is a grey smear
       across the border. The hairline is restored as a real border so the
       cards stay separable, which is all forced colours needs from them. */
    .hpx .hpx-card-showcase::before,
    .hpx .hpx-promo::before,
    .hpx .hpx-trust-stat::before,
    .hpx .hpx-cat::after,
    .hpx .hpx-libcol::after,
    .hpx .hpx-exp::after,
    .hpx .hpx-ig-tile .hpx-igcard::before,
    .hpx .hpx-ig-feature .hpx-igcard::before,
    .hpx .hpx-ig-plain .hpx-igcard::after,
    .hpx .hpx-card-showcase::after,
    .hpx .hpx-promo::after,
    .hpx .hpx-trust-stat::after { display: none; }

    .hpx .hpx-card-showcase,
    .hpx .hpx-cat,
    .hpx .hpx-libcol,
    .hpx .hpx-exp,
    .hpx .hpx-promo,
    .hpx .hpx-trust-stat,
    .hpx .hpx-ig-tile .hpx-igcard,
    .hpx .hpx-ig-feature .hpx-igcard {
        background-image: none;
        border: 1px solid CanvasText;
        box-shadow: none;
    }
}

/* ── §HPXP-INK) The muted ink on a washed card ────────────────────────────
   MEASURED, and the measurement is the whole reason this rule exists.

   `--hpx-ink-3` (#6B7686) reads 4.60:1 on a pure white card — 0.10 above the
   4.5:1 AA floor for body text. That is not headroom, it is a rounding error.
   Solving for it: the token tolerates a tint of alpha 0.017 before it drops
   under, so ANY wash — this one at .075, or a third of it — costs AA on every
   card that carries muted text. `.hpx-sc-desc` is exactly that: the showcase
   card's two-line description, and there are 109 of them on this page.

   So the wash is not what gets dialled back. A token pinned to the minimum is
   the actual defect, and the fix is to give it real headroom on the surfaces
   this pass tints — six percent down the SAME hue, which clears 4.5:1 against
   all eleven tones (worst 4.61:1, rose) and still reads 5.09:1 on plain
   white. It stays lighter than `--hpx-ink-2` (#3D4757), so the three-step
   type hierarchy is unchanged; this is a contrast correction, not a restyle.

   SCOPED TO THE WASHED HOSTS, not to `.hpx`. Redefining the token on the card
   reaches every descendant that reads it through `var()` and nothing else —
   the FAQ answers, the quote roles, the banner copy and the logo wordmarks
   all sit on untinted ground and keep the original grey.

   DARK NEEDS NOTHING. Its twin (#9AA0AB on #17171B) starts at 6.80:1 and the
   heavier .13 wash only takes it to 5.43:1, so there is nothing to correct —
   and darkening a light-mode grey under `body.theme-dark` would be the exact
   inversion bug this file has been bitten by before. */
/* ⚠️ `body:not(.theme-dark)` — LIGHT ONLY, and this qualifier is the whole
   correctness of the rule.

   The first version set the token unconditionally. #656F7E is a DARKENED grey,
   chosen to clear AA against a near-WHITE card; inherited into dark mode it
   put a dark grey on a #17171B card and took `.hpx-sc-desc` from 6.80:1 down
   to 3.51:1 — an AA failure on all 109 showcase descriptions, created by the
   very rule written to fix an AA failure. The block's own prose warned that
   "darkening a light-mode grey under body.theme-dark would be the exact
   inversion bug this file has been bitten by before", and then did it anyway
   by omitting the guard.

   Scoping to light rather than restating the dark value means dark simply
   keeps `--hpx-ink-3` (#9AA0AB, 6.80:1 plain / 5.49:1 under the wash) with no
   second hard-coded hex to drift out of step. */
body:not(.theme-dark) .hpx .hpx-card-showcase,
body:not(.theme-dark) .hpx .hpx-cat,
body:not(.theme-dark) .hpx .hpx-libcol,
body:not(.theme-dark) .hpx .hpx-exp,
body:not(.theme-dark) .hpx .hpx-promo,
body:not(.theme-dark) .hpx .hpx-trust-stat,
body:not(.theme-dark) .hpx .hpx-ig-tile .hpx-igcard,
body:not(.theme-dark) .hpx .hpx-ig-feature .hpx-igcard { --hpx-ink-3: #656F7E; }

/* ---------------------------------------------------------------------------
   26. §HERO-ORB — the hero orbit, rebuilt so the tiles are ON the ring

   ── The defect ────────────────────────────────────────────────────────────
   The ring was TWO mirrored SVG arcs pinned to the left and right edges at
   `width: clamp(170px, 23%, 300px)`, and the logo tiles were positioned by a
   separate set of hand-tuned percentages (`.hpx-stack-t1 { left:2%; top:9% }`
   and friends). The two systems shared no geometry, so the tiles never sat on
   the path — and because one was sized in `clamp()` px and the other in per
   cent, the gap between them CHANGED with every viewport width. That is why
   it reads as mismatched rather than merely off.

   ── The fix: one ellipse, and one formula for everything on it ────────────
   The ring is now a single `<ellipse cx=50 cy=50 rx=47 ry=43>` inside a
   `viewBox="0 0 100 100"` with `preserveAspectRatio="none"`, stretched to the
   orbit box. That makes the SVG's coordinate space and the box's PERCENTAGE
   space the same space — so a point at angle θ on the ring is exactly:

       left = 50 + 47·cos θ  %        top = 50 + 43·sin θ  %

   Every tile, every node and every glint below is placed with that formula
   and nothing else. They are on the ring BY CONSTRUCTION, at every width,
   rather than by eye at one width. θ is measured the CSS way — 0° at three
   o'clock, growing clockwise, because y grows downward.

   Angles chosen so the four tiles of the live layout (ChatGPT · Claude ·
   Gemini · Grok) sit two per side, mirrored, clear of the centred copy:

       t1 left-upper   206°     t2 right-upper   334°
       t3 left-lower   154°     t4 right-lower    26°
       t5 left-foot    124°     t6 right-foot     56°

   The tile ORDER is the existing one (odd = left, even = right, filling from
   the top), so a 4-tile hero uses t1–t4 and lands symmetrically without any
   per-count special-casing.

   ── Why this block is appended rather than edited in place ────────────────
   The originals live in §9 (Hero) at ~line 1997 and are re-stated inside the
   ≤1320px media query at ~8447. Both are (0,1,0). Appending at (0,2,0) beats
   the pair from one place, and leaves §9 removable if this is ever reverted.

   ⚠️ Below 1180px `.hpx-hero-orbit` is `display:none` and the tiles become a
   flex rail — everything here is desktop-only by design, and the rail must
   keep winning, which is why the `left/top` rules below are NOT written with
   `!important` and the rail's `position:static` still applies.
   ------------------------------------------------------------------------ */

/* ── §HERO-ORB-RING) The ellipse ──────────────────────────────────────────
   The two old arc SVGs are gone from the markup; this styles their
   replacement. `inset:0` — the ring spans the orbit box exactly, which is
   what makes the percentage identity above true. */
.hpx-orbit-ring {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* ⚠️ NOT `--hpx-brand-line`. That token is #CFE0FA, a hairline tint meant
       to sit UNDER content as a card edge — at 1.4px, dashed, stretched over
       1500px of near-white hero it is essentially invisible, which is what
       made the orbit read as absent rather than subtle. The ring is drawn in
       the brand hue itself at a measured alpha instead. */
    color: rgba(54, 128, 237, .55);
    overflow: visible;
    pointer-events: none;
}

/* Two rings, two jobs. The main ring carries the tiles; the outer one is
   depth only and never has anything placed on it, so it is deliberately
   fainter and more sparsely dashed. */
.hpx-orbit-ring-main { opacity: 1; }
.hpx-orbit-ring-far  { opacity: .5; }

/* Dark: `--hpx-brand-line` is a pale blue tuned for a white page and it goes
   muddy over #0E0E11. The ring lifts to the brand hue itself at low alpha,
   which reads as a lit filament rather than a grey scratch. */
/* Dark: the same hue lifted, because a 55%-alpha blue that reads clearly over
   #FFFFFF loses most of its contrast over #0E0E11 — the same asymmetry every
   other token pair on this page is tuned for. */
body.theme-dark .hpx .hpx-orbit-ring { color: rgba(120, 180, 255, .62); }
body.theme-dark .hpx .hpx-orbit-ring-far { opacity: .6; }

/* ── §HERO-ORB-POS) Everything that sits on the ring ──────────────────────
   Each `left`/`top` pair below is `50 + 47·cos θ` / `50 + 43·sin θ` for that
   element's θ, rounded to two decimals. Do not "tidy" these numbers — they
   are the ring equation, and a rounded-off value is a tile off the path.

   `transform: translate(-50%, -50%)` centres the element ON the point rather
   than hanging it below-right of it. The tiles' inner `.hpx-stack-tile-float`
   span keeps owning the ambient float, so this transform and the animation
   never fight over the same property — the split §9 documents. */
.hpx .hpx-stack-t1,
.hpx .hpx-stack-t2,
.hpx .hpx-stack-t3,
.hpx .hpx-stack-t4,
.hpx .hpx-stack-t5,
.hpx .hpx-stack-t6 {
    right: auto;                    /* the originals used `right` on evens */
    transform: translate(-50%, -50%);
}

.hpx .hpx-stack-t1 { left: 7.76%;  top: 31.15%; }   /* θ 206° */
.hpx .hpx-stack-t2 { left: 92.24%; top: 31.15%; }   /* θ 334° */
.hpx .hpx-stack-t3 { left: 7.76%;  top: 68.85%; }   /* θ 154° */
.hpx .hpx-stack-t4 { left: 92.24%; top: 68.85%; }   /* θ  26° */
.hpx .hpx-stack-t5 { left: 23.72%; top: 85.65%; }   /* θ 124° */
.hpx .hpx-stack-t6 { left: 76.28%; top: 85.65%; }   /* θ  56° */

/* The nodes. Placed BETWEEN the tiles so the ring reads as populated rather
   than as four objects with empty wire between them. */
.hpx .hpx-orbit-dot {
    left: auto; right: auto;
    transform: translate(-50%, -50%);
    width: 6px; height: 6px;
    opacity: .55;
}

.hpx .hpx-orbit-d1 { left: 17.35%; top: 19.07%; }   /* θ 226° */
.hpx .hpx-orbit-d2 { left: 3.00%;  top: 50.00%; }   /* θ 180° */
.hpx .hpx-orbit-d3 { left: 17.35%; top: 80.93%; }   /* θ 134° */
.hpx .hpx-orbit-d4 { left: 82.65%; top: 19.07%; }   /* θ 314° */
.hpx .hpx-orbit-d5 { left: 97.00%; top: 50.00%; }   /* θ   0° */
.hpx .hpx-orbit-d6 { left: 82.65%; top: 80.93%; }   /* θ  46° */

body.theme-dark .hpx .hpx-orbit-dot { opacity: .7; }

/* ── §HERO-ORB-STARS) The field ───────────────────────────────────────────
   Painted entirely with layered radial-gradients on two elements, so ~70
   stars cost two DOM nodes instead of seventy. Each layer twinkles on its
   own phase, which is what stops a field of synchronised dots reading as a
   flashing grid.

   ⚠️ EVERY STAR IS A **CURRENTCOLOR** DOT, and the colour is set per theme
   below rather than baked into the gradients. White stars are invisible on a
   white page — the light theme needs brand-tinted specks at low alpha, and
   only dark can have true white. Baking `#fff` into 70 gradient stops is how
   a starfield ships that only exists at night.

   `background-repeat: no-repeat` on the shorthand-free `background-image`, and
   sizes in per cent, so the field stretches with the hero instead of tiling
   into a visible grid. */
.hpx-orbit-stars {
    position: absolute;
    inset: -4% -2%;
    pointer-events: none;
    color: rgba(54, 128, 237, .62);
    background-repeat: no-repeat;
    background-image:
        radial-gradient(2.1px 2.1px at 5.9% 4.1%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 10.7% 10.9%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 19.7% 3.3%, currentColor, transparent 100%),
        radial-gradient(6.8px 6.8px at 19.7% 3.3%, currentColor, transparent 70%),
        radial-gradient(1.8px 1.8px at 27.4% 5.1%, currentColor, transparent 100%),
        radial-gradient(8.1px 8.1px at 27.4% 5.1%, currentColor, transparent 70%),
        radial-gradient(1.4px 1.4px at 32.1% 6.6%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 36.5% 9.0%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 46.9% 4.0%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 65.5% 2.6%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 73.6% 9.7%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 9.5% 19.3%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 33.0% 25.0%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 36.7% 23.4%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 54.4% 23.6%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 58.1% 23.3%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 65.1% 16.1%, currentColor, transparent 100%),
        radial-gradient(2.1px 2.1px at 73.6% 26.6%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 79.2% 21.1%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 89.4% 20.4%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 94.7% 23.9%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 6.3% 30.1%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 11.0% 37.6%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 15.1% 32.9%, currentColor, transparent 100%),
        radial-gradient(1.9px 1.9px at 38.6% 34.3%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 63.4% 40.0%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 65.9% 35.0%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 77.5% 39.1%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 81.6% 38.0%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 89.4% 35.0%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 98.5% 39.7%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 13.1% 48.4%, currentColor, transparent 100%),
        radial-gradient(5.4px 5.4px at 13.1% 48.4%, currentColor, transparent 70%),
        radial-gradient(1.9px 1.9px at 17.7% 54.4%, currentColor, transparent 100%),
        radial-gradient(8.5px 8.5px at 17.7% 54.4%, currentColor, transparent 70%),
        radial-gradient(1.9px 1.9px at 33.7% 46.7%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 39.6% 54.9%, currentColor, transparent 100%),
        radial-gradient(6.8px 6.8px at 39.6% 54.9%, currentColor, transparent 70%),
        radial-gradient(1.5px 1.5px at 48.2% 48.6%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 55.8% 46.0%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 59.4% 52.7%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 66.9% 47.6%, currentColor, transparent 100%),
        radial-gradient(2.1px 2.1px at 72.6% 54.0%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 79.3% 55.3%, currentColor, transparent 100%),
        radial-gradient(2.1px 2.1px at 89.2% 55.0%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 4.3% 68.3%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 11.7% 63.3%, currentColor, transparent 100%),
        radial-gradient(2.0px 2.0px at 17.1% 61.6%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 34.0% 67.4%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 41.8% 69.7%, currentColor, transparent 100%),
        radial-gradient(8.1px 8.1px at 41.8% 69.7%, currentColor, transparent 70%),
        radial-gradient(1.8px 1.8px at 44.8% 63.4%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 52.0% 68.6%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 73.6% 62.9%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 82.4% 69.4%, currentColor, transparent 100%),
        radial-gradient(8.1px 8.1px at 82.4% 69.4%, currentColor, transparent 70%),
        radial-gradient(1.2px 1.2px at 89.0% 58.7%, currentColor, transparent 100%),
        radial-gradient(5.4px 5.4px at 89.0% 58.7%, currentColor, transparent 70%),
        radial-gradient(1.7px 1.7px at 97.4% 66.9%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 12.4% 75.4%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 19.4% 78.0%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 27.6% 76.0%, currentColor, transparent 100%),
        radial-gradient(5.0px 5.0px at 27.6% 76.0%, currentColor, transparent 70%),
        radial-gradient(1.7px 1.7px at 29.3% 75.3%, currentColor, transparent 100%),
        radial-gradient(2.0px 2.0px at 42.1% 77.0%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 51.8% 76.0%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 59.3% 76.4%, currentColor, transparent 100%),
        radial-gradient(2.0px 2.0px at 66.6% 82.1%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 77.0% 75.7%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 84.9% 82.1%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 95.6% 78.0%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 11.2% 95.6%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 20.6% 94.7%, currentColor, transparent 100%),
        radial-gradient(2.1px 2.1px at 27.7% 95.1%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 34.4% 93.1%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 40.4% 91.3%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 44.5% 89.6%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 55.8% 97.0%, currentColor, transparent 100%),
        radial-gradient(5.4px 5.4px at 55.8% 97.0%, currentColor, transparent 70%),
        radial-gradient(1.7px 1.7px at 61.1% 87.9%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 66.1% 95.9%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 72.6% 91.7%, currentColor, transparent 100%),
        radial-gradient(6.8px 6.8px at 72.6% 91.7%, currentColor, transparent 70%),
        radial-gradient(1.8px 1.8px at 91.4% 91.7%, currentColor, transparent 100%),
        radial-gradient(1.8px 1.8px at 96.1% 93.1%, currentColor, transparent 100%);
    opacity: .85;
    animation: hpx-star-twinkle 6.5s ease-in-out infinite;
}

/* The second field is offset, finer and slower, and twinkles in ANTIPHASE
   (negative delay of half the period). Two fields drifting against each other
   is what gives the impression of depth from what is really two flat layers. */
.hpx-orbit-stars-2 {
    inset: -6% -3%;
    color: rgba(139, 92, 246, .5);
    background-image:
        radial-gradient(1.7px 1.7px at 6.9% 14.7%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 14.3% 11.5%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 22.2% 12.2%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 27.3% 2.8%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 62.4% 10.7%, currentColor, transparent 100%),
        radial-gradient(0.9px 0.9px at 87.3% 3.3%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 94.8% 10.5%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 5.4% 29.2%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 12.0% 18.5%, currentColor, transparent 100%),
        radial-gradient(1.0px 1.0px at 17.3% 18.2%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 32.2% 18.5%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 39.8% 25.8%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 56.0% 31.2%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 64.4% 28.7%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 68.6% 24.8%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 78.2% 19.7%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 84.2% 31.5%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 94.1% 25.3%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 2.2% 37.0%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 15.0% 47.3%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 23.3% 46.0%, currentColor, transparent 100%),
        radial-gradient(0.9px 0.9px at 25.7% 48.7%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 44.2% 44.5%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 53.8% 48.3%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 62.8% 47.7%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 79.3% 35.5%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 85.4% 38.7%, currentColor, transparent 100%),
        radial-gradient(1.7px 1.7px at 93.1% 42.3%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 2.9% 58.8%, currentColor, transparent 100%),
        radial-gradient(1.0px 1.0px at 9.5% 60.8%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 19.5% 52.8%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 28.5% 63.3%, currentColor, transparent 100%),
        radial-gradient(1.0px 1.0px at 43.2% 57.7%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 78.3% 57.0%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 96.3% 63.5%, currentColor, transparent 100%),
        radial-gradient(1.0px 1.0px at 13.6% 81.0%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 23.7% 77.7%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 27.0% 71.7%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 46.1% 68.3%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 57.3% 72.5%, currentColor, transparent 100%),
        radial-gradient(1.0px 1.0px at 69.6% 76.3%, currentColor, transparent 100%),
        radial-gradient(0.9px 0.9px at 78.0% 78.7%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 87.7% 77.5%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 12.2% 90.5%, currentColor, transparent 100%),
        radial-gradient(1.0px 1.0px at 23.6% 87.7%, currentColor, transparent 100%),
        radial-gradient(1.2px 1.2px at 31.4% 91.2%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 38.7% 97.5%, currentColor, transparent 100%),
        radial-gradient(1.3px 1.3px at 47.2% 87.8%, currentColor, transparent 100%),
        radial-gradient(1.4px 1.4px at 51.3% 92.0%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 59.8% 91.0%, currentColor, transparent 100%),
        radial-gradient(1.6px 1.6px at 68.3% 93.7%, currentColor, transparent 100%),
        radial-gradient(1.5px 1.5px at 80.2% 96.2%, currentColor, transparent 100%),
        radial-gradient(1.1px 1.1px at 90.3% 87.2%, currentColor, transparent 100%);
    opacity: .7;
    animation: hpx-star-twinkle 9s ease-in-out infinite -4.5s;
}

/* Dark is where a starfield belongs, so it gets true white for the near layer
   and a cool blue for the far one, both a step brighter. */
body.theme-dark .hpx .hpx-orbit-stars   { color: rgba(255, 255, 255, .92); opacity: 1; }
body.theme-dark .hpx .hpx-orbit-stars-2 { color: rgba(160, 200, 255, .8);  opacity: .95; }

@keyframes hpx-star-twinkle {
    0%, 100% { opacity: .38; }
    50%      { opacity: .92; }
}

/* ── §HERO-ORB-SPARK) The five big glints ─────────────────────────────────
   A four-point star, drawn as two crossed tapered bars. Scaling a round dot
   up just gives a blurry blob; two gradient bars with a bright core stay a
   crisp vector shape at any size and cost one element each.

   Placed OFF the ring on purpose — a glint sitting exactly on the filament
   reads as another node. These sit in the open field between the ring and the
   copy, which is the space the eye reads as "background". */
.hpx-orbit-spark {
    position: absolute;
    width: var(--sp, 16px);
    aspect-ratio: 1;
    transform: translate(-50%, -50%) rotate(var(--rot, 0deg));
    background: currentColor;
    color: var(--hpx-brand);
    /* ⚠️ A RESTING opacity, not 0. The first version flashed each sparkle
       from 0 → .95 → 0 on its own long cycle, so at any given moment most of
       the eighteen were fully invisible — the field read as three or four
       stars, not seventeen. They are part of the composition, so they are
       always drawn; the animation only breathes around this value. */
    opacity: .55;
    pointer-events: none;
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.4c1.6 6.2 3.4 8 9.6 9.6C15.4 13.6 13.6 15.4 12 21.6 10.4 15.4 8.6 13.6 2.4 12 8.6 10.4 10.4 8.6 12 2.4z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.4c1.6 6.2 3.4 8 9.6 9.6C15.4 13.6 13.6 15.4 12 21.6 10.4 15.4 8.6 13.6 2.4 12 8.6 10.4 10.4 8.6 12 2.4z'/%3E%3C/svg%3E");
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-position: center;
            mask-position: center;
    animation: hpx-spark-flash var(--sp-dur, 7s) ease-in-out infinite var(--sp-delay, 0s);
}

/* ⚠️ NO ::before / ::after. The first version drew the star as two crossed
   tapered bars, which is a PLUS, not the house mark — the brand star has
   concave curves between its points and that is the whole difference between
   "a sparkle" and "a cross". One mask, one element, the real shape. */

.hpx-orbit-s1  { left:  92.9%; top:  11.7%; --sp: 17px; --rot:   1deg; --sp-delay: -4.0s; --sp-dur: 9.8s; }
.hpx-orbit-s2  { left:   9.1%; top:  12.7%; --sp: 13px; --rot:   9deg; --sp-delay: -5.4s; --sp-dur: 5.7s; }
.hpx-orbit-s3  { left:  85.8%; top:  20.0%; --sp: 26px; --rot: -14deg; --sp-delay: -2.4s; --sp-dur: 7.4s; }
.hpx-orbit-s4  { left:  77.4%; top:  23.3%; --sp: 13px; --rot: -16deg; --sp-delay: -4.2s; --sp-dur: 8.0s; }
.hpx-orbit-s5  { left:  20.8%; top:  32.8%; --sp: 22px; --rot:  -1deg; --sp-delay: -2.2s; --sp-dur: 5.5s; }
.hpx-orbit-s6  { left:   4.8%; top:  33.1%; --sp: 13px; --rot: -11deg; --sp-delay: -5.5s; --sp-dur: 9.7s; }
.hpx-orbit-s7  { left:   3.3%; top:  44.4%; --sp: 22px; --rot:  22deg; --sp-delay: -8.2s; --sp-dur: 9.3s; }
.hpx-orbit-s8  { left:  82.2%; top:  47.8%; --sp: 15px; --rot:  24deg; --sp-delay: -3.1s; --sp-dur: 5.3s; }
.hpx-orbit-s9  { left:  83.8%; top:  61.9%; --sp: 22px; --rot:  16deg; --sp-delay: -6.7s; --sp-dur: 7.7s; }
.hpx-orbit-s10 { left:  11.7%; top:  69.1%; --sp: 19px; --rot: -10deg; --sp-delay: -8.7s; --sp-dur: 5.4s; }
.hpx-orbit-s11 { left:  96.9%; top:  75.3%; --sp: 15px; --rot:   9deg; --sp-delay: -4.7s; --sp-dur: 8.7s; }
.hpx-orbit-s12 { left:   3.3%; top:  77.6%; --sp: 22px; --rot:   5deg; --sp-delay: -1.6s; --sp-dur: 9.8s; }
.hpx-orbit-s13 { left:  20.4%; top:  80.4%; --sp: 22px; --rot:  23deg; --sp-delay: -8.1s; --sp-dur: 8.0s; }
.hpx-orbit-s14 { left:  86.2%; top:  84.4%; --sp: 13px; --rot: -18deg; --sp-delay: -6.6s; --sp-dur: 5.1s; }
.hpx-orbit-s15 { left:   9.5%; top:  89.6%; --sp: 17px; --rot: -12deg; --sp-delay: -7.7s; --sp-dur: 9.8s; }
.hpx-orbit-s16 { left:  35.9%; top:  91.0%; --sp: 19px; --rot: -12deg; --sp-delay: -6.0s; --sp-dur: 8.0s; }
.hpx-orbit-s17 { left:  64.3%; top:  92.0%; --sp: 15px; --rot:  13deg; --sp-delay: -2.7s; --sp-dur: 5.9s; }
.hpx-orbit-s18 { left:  90.9%; top:  95.5%; --sp: 19px; --rot:  -2deg; --sp-delay: -3.1s; --sp-dur: 6.4s; }

/* Violet on two of them, so the field is not one hue repeated five times. */
.hpx-orbit-s3, .hpx-orbit-s7, .hpx-orbit-s10, .hpx-orbit-s13, .hpx-orbit-s16 { color: var(--hpx-t-violet-ink, #7048E8); }

body.theme-dark .hpx .hpx-orbit-spark { color: #DCEAFF; }
body.theme-dark .hpx .hpx-orbit-s3, body.theme-dark .hpx .hpx-orbit-s7, body.theme-dark .hpx .hpx-orbit-s10,
body.theme-dark .hpx .hpx-orbit-s13, body.theme-dark .hpx .hpx-orbit-s16 { color: #C9B6FF; }

/* Asymmetric: a fast bloom and a slow fade reads as a glint catching the
   light. A symmetric ease reads as a pulsing lamp. */
/* A slow breath with one brighter peak per cycle. Because every sparkle has
   its own duration AND its own negative delay, the eighteen never pulse
   together — which is the difference between a night sky and a string of
   fairy lights blinking in unison. */
@keyframes hpx-spark-flash {
    0%, 100% { opacity: .42; transform: translate(-50%, -50%) rotate(var(--rot, 0deg)) scale(.9); }
    38%      { opacity: .72; transform: translate(-50%, -50%) rotate(var(--rot, 0deg)) scale(1.02); }
    62%      { opacity: 1;   transform: translate(-50%, -50%) rotate(var(--rot, 0deg)) scale(1.12); }
    80%      { opacity: .66; transform: translate(-50%, -50%) rotate(var(--rot, 0deg)) scale(1); }
}

/* ── §HERO-ORB-RESP) Narrower desktops ────────────────────────────────────
   The ≤1320px block in §23 pulls the tiles further out (`left:1%` etc.) to
   buy the copy room. That was tuned for the OLD free-floating positions and
   now fights the ring, so it is restated here as a ring change instead: the
   ellipse itself widens and flattens, and because the tiles read the same
   equation they follow it without a second set of numbers.

   rx 48.6 / ry 41 → the four tile angles resolve to the values below. */
@media (max-width: 1320px) {
    /* ⚠️ THE SAME COORDINATES AS THE WIDE BREAKPOINT, ON PURPOSE — this block
       exists only to out-rank §23, which still pulls the tiles to `left:1%`
       and friends for the old free-floating layout. Those numbers were tuned
       against no ring at all; left alone they would drag every tile off the
       path below 1320px.

       An earlier pass widened the ellipse here (rx 48.6 / ry 41) and moved
       the tiles to match. That was WRONG for one reason: `rx`/`ry` on an
       <ellipse> are SVG GEOMETRY PROPERTIES, and while Chrome and Firefox
       accept them from CSS, Safari did not until 17.4 — and this file still
       carries :has() fallbacks for Safari below 15.4. On any browser that
       ignores them the ring would hold rx47/ry43 while the tiles moved,
       recreating the exact mismatch this section was written to remove, in
       one browser only, where nobody would think to look.

       ONE geometry at every width cannot desync. The tiles already sit at
       7.76% / 92.24%, which at 1280px puts them clear of the 620px copy
       column with room to spare, so nothing is bought by moving them. */
    .hpx .hpx-stack-t1 { left: 7.76%;  top: 31.15%; }
    .hpx .hpx-stack-t2 { left: 92.24%; top: 31.15%; }
    .hpx .hpx-stack-t3 { left: 7.76%;  top: 68.85%; }
    .hpx .hpx-stack-t4 { left: 92.24%; top: 68.85%; }
    .hpx .hpx-stack-t5 { left: 23.72%; top: 85.65%; }
    .hpx .hpx-stack-t6 { left: 76.28%; top: 85.65%; }
}

/* ── §HERO-ORB-A11Y) Reduced motion ───────────────────────────────────────
   The glints stop flashing and settle at a readable resting opacity rather
   than vanishing — they are part of the composition, not just an animation.
   The fields stop twinkling and hold their mid value. */
@media (prefers-reduced-motion: reduce) {
    .hpx-orbit-stars,
    .hpx-orbit-stars-2 { animation: none; opacity: .6; }

    .hpx-orbit-spark {
        animation: none;
        opacity: .7;
        transform: translate(-50%, -50%) rotate(var(--rot, 0deg));
    }
}

@media (forced-colors: active) {
    /* Pure decoration, and a starfield in system colours is noise on top of
       the one thing that matters here — the headline. */
    .hpx-orbit-stars,
    .hpx-orbit-stars-2,
    .hpx-orbit-spark { display: none; }
}

/* ── §HPXP-CHIP) The lead card's corner chip ──────────────────────────────
   White on a 20% white veil over the why-choose lead card measured 4.46:1 in
   dark — four hundredths under the 4.5 floor, which is a fail like any other.
   The veil goes to a dark tint instead of a light one so the chip DEEPENS
   against the card behind it; the label is untouched. */
body.theme-dark .hpx .hpx-ig-feature .hpx-igcard.is-lead .hpx-igcard-chip {
    background: rgba(6, 16, 34, .30);
}
/* ── §HPXP-ARROW) The brand button's arrow chip, in dark ──────────────────
   `--hpx-brand`'s dark twin is #60A4F5 — LIGHTENED so the token can serve as
   INK on a near-black surface. Used as a FILL under a white glyph it measures
   **2.58:1**, under the 3:1 WCAG 1.4.11 needs for a non-text graphic. This is
   the same defect `ai-contrast-fix.css` was written for: an ink token doing a
   fill's job.

   The chip appears on every "View all …" / "Browse the directory" /
   "Explore all models" button on the page, so it is the most repeated icon on
   the homepage — and the ONLY one of 770 glyphs that failed the sweep.

   The promo band's copy of this chip already carries a .5 scrim (it fills from
   `--tone-ink`, whose dark twins are paler still). That rule is (0,5,1) and
   keeps winning inside `.hpx-promo`; this one only has to cover the rest.

   .24 measured: #497DBA, 4.28:1. Chosen over the .10 that would just scrape
   3.16 — the chip is 24px with a 13px glyph inside it, and a hairline pass at
   that size still reads as washed out. It stays recognisably brand blue. */
body.theme-dark .hpx .hpx-btn-brand .hpx-btn-arrow {
    background-image: linear-gradient(0deg, rgba(0, 0, 0, .24), rgba(0, 0, 0, .24));
}
