/* ═══════════════════════════════════════════════════════════════════════════
   §BANDS — homepage parity for the three listing bands   (2026-08-18)
   assets/css/ext/public-dir-bands.css
   ═══════════════════════════════════════════════════════════════════════════
   Loaded on every page that renders `.dir-hh` — the listing, category hub,
   category and tag pages of all twenty verticals.

   ── The defect ────────────────────────────────────────────────────────────
   The three bands below the results — Browse categories, "What is an … ?" and
   Latest Posts & AI News — were supposed to match the homepage. Measured side
   by side on 2026-08-18 they did not:

                            homepage .hpx-igcard      listing .dir-why-card
     card title             tone ink (#1B5BB5 …)      #111 on every card
     card background        tone corner wash          flat #fff
     card border            tone @ 20% alpha          flat grey #E7ECF3

   The same three-way gap applied to `.dir-cat-card` against `.hpx-cat`. The
   result: six cards that read as one flat row instead of the tone-coded set
   the homepage shows.

   ⚠️ THE BASE SHEET ALREADY DECLARES THE TONE COLOUR AND LOSES.
   `v2-public-directory-listing.css` carries `.v2x .dir-why-title-c { color:
   var(--dwc-ink) }` at TOP LEVEL, in no media query, twice. It still computes
   to `rgb(17,17,17)` in the browser. The override was not findable: no rule in
   any loaded sheet matches the element with `color`, none carries `!important`,
   and nothing loaded after that sheet mentions `dir-why`. Whatever is winning,
   it is not visible from the CSSOM.

   So this sheet does not try to out-argue it on equal terms. Every rule below
   is written ONE class more specific than the base rule it is fixing AND ships
   in a file that loads later, so it wins on both axes at once. That is
   deliberate belt-and-braces: fixing a cascade you cannot fully see means
   removing the ambiguity rather than betting on source order.

   ── Tokens ────────────────────────────────────────────────────────────────
   Nothing new is invented. The per-tone values already exist on the cards —
   `--dwc-ink|-bg|-line` on `.dir-why-card[data-tone]` and `--dtc-*` on
   `.dir-cat-card[data-tone]`, eight tones each. This sheet only spends them on
   the three properties the homepage spends them on. Add a tone to the base
   sheet and it flows here for free.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §BANDS-WHY) The "What is an … ?" cards ───────────────────────────────
   `:not(.is-lead)` because the lead card is a solid brand fill with white
   text — giving it a tint wash would muddy the gradient, and giving it a tone
   title would make it unreadable. It is already correct; leave it alone. */
/* ⚠️ THE SURFACE MOVED, exactly as §BANDS-CAT's did. The wash declared here
   was a corner radial in `--dwc-bg` — an OPAQUE pale hex faded to the
   `transparent` KEYWORD — plus a `--dwc-line` edge. Both are superseded by
   §WHYPRM at the foot of this file, which paints the homepage's two-layer
   `rgba(hue, …)` recipe instead and fades to `rgba(same-hue, 0)` so the
   gradient stops interpolating through transparent black. Nothing is declared
   here; this note is so the next reader does not go looking for a wash that
   used to be at this line.

   What stays below is the title colour, which is about TYPE, not surface. */

/* The title carries the tone AT REST. These cards are not links, so there is
   no hover state left to put the hue in — which is exactly the reasoning the
   homepage's own §WHY note records. */
.v2x .dir-why-card:not(.is-lead) .dir-why-title-c {
    color: var(--dwc-ink, #1B5BB5);
    /* The base sheet paints an underline-grow via a currentColor gradient on
       this element. With the title already tone-coloured that is two signals
       for one event, and the homepage drops it — so do we. */
    background-image: none;
}

/* Dark: the `-ink` tokens are tuned as INK FOR A LIGHT SURFACE. On #0E0E11 the
   darker ones (emerald #08795A, rose #B31D3C) fall under 4.5:1 as body-sized
   text. The base sheet already ships a dark twin for the title, so nothing is
   needed here for it — and the card's own dark surface is §WHYPRM's, which is
   four alpha numbers rather than a `color-mix()` of a light-mode hex. */

/* ── §BANDS-CAT) The "Browse … Categories" rail cards ─────────────────────
   ⚠️ THE SURFACE MOVED. This block used to paint the rail card's wash here,
   as a corner radial in `--dtc-bg` — a PALE COMPOSITED HEX at full opacity,
   which is not what the homepage paints and is why it had to be turned off
   again eight hours later (§CATBAND-CARD's `background-image: none`, now
   also gone). The homepage's is a SATURATED HUE AT LOW ALPHA in two layers,
   and it needs an RGB triplet no `--dtc-*` token carried.

   Both of those rules are superseded by §CATPRM at the foot of this file,
   which ports the homepage's §HPXP recipe whole. Nothing is declared here —
   one home for the value, and this note so the next reader does not go
   looking for a wash that used to be at this line.

   What stays below is the ink correction, which is about TYPE, not surface. */

/* The category name is INK AT REST and takes the tone ON HOVER — measured off
   `.hpx-cat-title`, which computes to rgb(11,18,32) on a resting homepage tile
   and only picks up `--tone-ink` under `:hover`.

   ⚠️ The 2026-08-18 pass read the hover rule as the resting one and coloured
   all ten names. Side by side against `/` that is the single loudest
   difference left in the band: ten tone-coloured headings read as a colour
   chart, and hover has nothing left to say. Corrected here rather than
   overridden later, so there is one home for the value.

   Scoped to the card so it cannot reach the SECTION heading, which is the
   gradient-accent treatment and must stay as it is. */
.v2x .dir-cat-card .dir-cat-card-nm {
    color: var(--v2-ink);
    /* The homepage's weight. `v2-public-directory-listing.css` asks for 800 in
       the base rule and 740 in §CAT; the element renders at 700 either way, so
       whatever is winning is not in that sheet. Declaring it here — later, and
       one class more specific than §CAT's `.dir-cat-card-nm` — settles it. */
    font-weight: 740;
}

/* The hover half. It has to ship in THIS file: the base sheet's own
   `.dir-cat-card:hover .dir-cat-card-nm` is (0,3,0), exactly the weight of the
   resting rule above, and this file loads later — so without a copy here the
   rest state would out-rank the hover state and the tone would never appear. */
@media (hover: hover) {
    .v2x .dir-cat-card:hover .dir-cat-card-nm { color: var(--dtc-ink, #1B5BB5); }
}

/* ⚠ `color-mix()` is Chrome 111+ / Safari 16.2+. Older browsers drop the whole
   declaration and keep the light-mode wash, which is a wrong-but-legible
   result rather than a broken one — the alternative, hand-writing eight dark
   rgba pairs per token set, is sixteen more values to keep in sync with a base
   sheet that already owns them. */

/* ── §BANDS-A11Y) Forced colours ──────────────────────────────────────────
   The washes are decoration; in a forced-colour mode they become opaque system
   fills that swallow the card. Drop them and let the border carry the edge. */
@media (forced-colors: active) {
    .v2x .dir-why-card:not(.is-lead),
    .v2x .dir-cat-card { background-image: none; }
}

/* ── §BANDS-GDX) The guides category hub, /guides/categories ──────────────
   The one page of the fifty-one audited on 2026-08-18 that the rules above
   could not reach. Every other vertical's hub builds its grid out of
   `.dir-cat-card`, so §BANDS-CAT already covers it. Guides is the exception:
   its hub cards are `.gdx-catcard`, a bespoke class carrying a richer body
   (a nested list of guides, a level pip row, an "All N in …" link) that the
   shared card has no slot for. The markup is worth keeping — the SKIN is what
   drifted, so only the skin is corrected here.

   Measured before this block, /guides/categories showed exactly the flat
   three-way gap this file was opened to close:

                            other hubs                /guides/categories
     card title             tone ink                  #0F0F12 on all 16
     card background        tone corner wash          flat #FFF, no image
     card border            tone @ 20% alpha          flat #E7EAF0
     icon plate             tone tint + tone glyph    blue on all 16

   That last row is the tell. `.gdx-catcard` ALREADY ships `data-tone`, cycled
   through all eight of `GuideVocab::CARD_TONES` — the attribute was on the
   element and nothing read it, so sixteen deliberately-toned cards rendered
   as one blue row. No new vocabulary is introduced below; the `--dtc-*` trio
   is now declared for these cards beside `.dir-cat-card`'s own, in
   `v2-public-directory-listing.css`, and this block only spends it.

   ⚠ SPECIFICITY: the base rules in `public-guides.css` are `.v2x.gdx .gdx-…`
   — two classes on one element plus a third — and that sheet loads AFTER the
   token sheet. Qualifying with `[data-tone]` puts every rule below one step
   above its base rule, and this file loads after `public-guides.css`, so the
   cascade is settled on both axes rather than on source order alone. Same
   belt-and-braces reasoning as the header of this file. */
/* ⚠️ The wash and the edge that used to be declared at this line are gone
   the same way §BANDS-CAT's are, and for the same reason — see §CATPRM at
   the foot of this file, which paints both hosts from one rule. Everything
   below is `.gdx-catcard`'s own vocabulary and has no homepage twin. */

/* The card name stays INK at rest and takes the tone on hover, the same
   correction the shared rail card gets above — `public-guides.css` already
   declares `color: var(--v2-ink)` on `.gdx-catcard-nm`, so the rest state
   needs no rule here at all, only the hover half.

   ⚠️ These cards previously carried the tone at rest, added by the same
   2026-08-18 pass and on the same misreading of the homepage tile. On a card
   whose body is a nested LIST OF GUIDE LINKS a coloured heading competes with
   the links under it, which is why the original design had it ink. */
@media (hover: hover) {
    .v2x.gdx .gdx-catcard[data-tone]:hover .gdx-catcard-nm { color: var(--dtc-ink, #1B5BB5); }
}

/* The plate reads its own card's tone instead of the hard-coded blue. Kept as
   a flat tint rather than the rail card's gradient chip: these cards are a
   READING surface with a body of links under the head, and the heavier chip
   pulls the eye off that body. */
.v2x.gdx .gdx-catcard[data-tone] .gdx-catcard-ico {
    background: var(--dtc-bg, #EAF2FD);
    color: var(--dtc-ink, #1B5BB5);
}

/* Hover took `--brand-blue` on every card, which re-flattened the row at the
   moment the tone matters most. */
.v2x.gdx .gdx-catcard[data-tone]:hover { border-color: var(--dtc-ink, #1B5BB5); }
.v2x.gdx .gdx-catcard[data-tone]:hover .gdx-catcard-arr { color: var(--dtc-ink, #1B5BB5); }

/* Dark: `--dtc-*` already resolves to the night twin for the ink (declared
   beside the light set), so the plate needs no `color-mix()` here — unlike
   §BANDS-WHY, whose tokens only have a light-surface value. The card's own
   dark surface is §CATPRM's, and is four alpha numbers rather than a second
   background rule. */
body.theme-dark .v2x.gdx .gdx-catcard[data-tone] .gdx-catcard-ico {
    background: var(--dtc-bg);
    color: var(--dtc-ink);
}

@media (forced-colors: active) {
    .v2x.gdx .gdx-catcard[data-tone] { background-image: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §CATBAND — the Browse-categories BAND, at homepage parity   (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   §BANDS-CAT above and §CAT in the three base sheets already brought the
   CARD to the homepage's `.hpx-cat` recipe — halo, solid plate, count pill,
   ringed action. The BAND AROUND IT was never converted, so on all fifty-one
   listing / hub / category / tag URLs the same tiles sat under different
   chrome:

                          homepage `.hpx-*`            listing `.dir-cat-*`
     head                 left-aligned, CTA at the     centred column with
                          right of the heading         sparkle flanks, no CTA
     rail track           whole tiles, count per       minmax(220px, 1fr),
                          breakpoint (7/6/5/4/3)       partial tile at the edge
     nudge buttons        38px, outside the rail,      44px, inset 8px OVER
                          fade out at either end       the cards, dimmed to .3
     progress bar         yes — reports and scrubs     none
     card foot bar        52% wide, 4px, tone-to-tone  full width, 3px, fading
                                                       to transparent

   Everything below is CSS-only and rides the sheet every `.dir-hh` page
   already loads, so one edit reaches all twenty verticals. The one thing CSS
   cannot invent is the progress bar's ELEMENT — that is injected by
   assets/js/ext/public-dir-catband.js, linked from the same partial.

   ── Which heads become railheads ──────────────────────────────────────────
   `.dir-cat-head` is NOT the categories head. It is the shared showcase head
   for every band in the `.dir-cat-*` namespace — "Featured GPTs", "Popular
   right now", "Acronyms & Aliases", the FAQ. Those must stay centred, exactly
   as the homepage keeps `is-showcase` centred everywhere except this one band.

   So the switch is keyed on WHAT FOLLOWS THE HEAD: a next sibling that
   contains a `.dir-cat-card`. That is true of the rail (`.dir-cat-railwrap`)
   and of every hub grid — `.dir-cat-grid`, `.dlx-catgrid`, `.apx-catgrid`,
   `.acx-…` — without this file having to list them, and false for a head
   followed by `.dir-cards` (tool cards) or an FAQ list.

   ⚠ `:has()` is Chrome 105+ / Safari 15.4+ / Firefox 121+. Where it is
   missing the head keeps the centred layout it has today and the CTA renders
   under the sub-heading — the previous design, not a broken one. Nothing else
   in this block depends on it.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §CATBAND-HEAD) The Categories head, centred showcase ─────────────────
   ⚠️ THIS BLOCK USED TO BUILD A LEFT-ALIGNED RAILHEAD, and the reversal is
   deliberate — asked and answered twice on 2026-08-18. The first answer kept
   it left because that is what the homepage's own Browse-categories band does
   (`is-railhead is-noflank`); the second asked for it to match the OTHER
   heads on its own page instead. That is the better call and it is worth
   recording why: the homepage's railhead sits in a band with ONE head, so
   there is nothing beside it to disagree with. These pages carry three heads
   in a column — Categories, Why, FAQ — and on a hub like /gpts/categories the
   PRIMARY head ("Every GPT Category") was the left-aligned one while the
   secondary band under it ("Popular right now") was centred. A page reads its
   own internal consistency long before it reads consistency with `/`.

   So the head is now the same centred showcase as its neighbours: eyebrow
   pill, sparkle flanks, gradient accent, centred sub, CTA below.

   ⚠️ The `:has(+ * .dir-cat-card)` gate STAYS, for two reasons that have
   nothing to do with alignment. It is what keeps this off the `.dir-cat-*`
   heads that are NOT category grids, and — see §HEAD-TYPE — `:has()` carries
   its argument's specificity, making this selector (0,4,0), which is what
   clears `custom.ltr.css`'s (0,2,2) `h2` rule. Drop the `:has()` and the
   heading silently loses its tracking and line-height. */
.v2x .dir-cat-head:has(+ * .dir-cat-card) {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0;
    margin-bottom: 30px;
    padding: 0;
}

/* Spacing comes from the children's own margins, not from a flex `gap` — the
   homepage's showcase head is `gap: 0` with `margin-bottom: 14px` on the
   eyebrow and `margin-top: 12px` on the sub, and a gap on top of those would
   double-count both. */
.v2x .dir-cat-head:has(+ * .dir-cat-card) > .dir-cat-eyebrow { margin-bottom: 14px; }

/* ⚠️ THE SUB IS DELIBERATELY NOT DECLARED HERE ANY MORE. This selector is
   (0,4,0) — `:has()` carries its argument's specificity — and it used to set
   `margin-top`, `max-width` and `font-size: 16px` on the sub. §HEAD-SUB later
   sets the same three values at (0,2,0) for BOTH band subs, so the pair was
   redundant everywhere except one place it mattered: §HEAD-RESP's ≤820px step
   to 15px is also (0,2,0), and this rule out-ranked it. The result was a phone
   screen showing the Categories sub at 16px and the Why sub directly under it
   at 15px. Measured at 376px on 2026-08-18; the specificity that makes the
   HEADING survive `custom.ltr.css` is the same specificity that made the sub
   ignore its own breakpoint. One home for the value, and it is §HEAD-SUB. */

/* The CTA drops below the sub. On the two-column railhead it sat in column 2
   beside the heading; centred, there is no column 2 to sit in, and stacking
   it is what the homepage does with the same button whenever its own head
   stacks. 18px is `.hpx-band-foot`'s. */
.v2x .dir-cat-head:has(+ * .dir-cat-card) > .dir-cat-cta {
    margin-top: 18px;
    align-self: center;
}

/* 30px * 1.42 = 42.6px, the homepage's showcase heading, expressed as a clamp
   so it still steps down on a narrow column. Kept at the RAILHEAD's size
   rather than the base sheet's `clamp(30px, 3.6vw, 48px)`: 48px would put
   this heading five points above the Why heading below it, which is the
   inconsistency §HEAD was opened to remove. */
.v2x .dir-cat-head:has(+ * .dir-cat-card) > .dir-cat-title {
    justify-content: center;
    font-size: clamp(26px, 2.4vw + 6px, 43px);
    line-height: 1.12;
    letter-spacing: -.03em;
}

/* §STAR's flanks are BACK. They were suppressed here for the whole life of
   the railhead because they hang 74px either side of the words and the
   leading cluster lands outside the container on a left-aligned head. On a
   centred head that constraint disappears — which is the same reason the
   homepage gates its own flanks on `.hpx-al-center`. §STAR already drops them
   below 1024px, so no rule is needed for the narrow case. */

/* ── §CATBAND-CTA) The head's "View all categories" pill ──────────────────
   The homepage's `.hpx-btn-brand`: a TINTED pill with a solid brand disc for
   the arrow, not a solid fill. Solid would put a second saturated blue pill
   on a page whose search button is already one, and blur which is the action.

   Styled unconditionally (no `:has()`) so that where `:has()` is missing the
   link is still a button rather than a bare line of blue text. */
.v2x .dir-cat-cta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    padding: 10px 10px 10px 19px;
    border-radius: 999px;
    background-color: #EAF2FD;
    color: var(--brand-blue-dark);
    font-size: 14px;
    font-weight: 650;
    letter-spacing: -.004em;
    text-decoration: none;
    white-space: nowrap;
    box-shadow: inset 0 0 0 1px #CFE0FA;
    transition: transform .18s ease, box-shadow .22s ease, background-color .18s ease, color .18s ease;
}

body.theme-dark .v2x .dir-cat-cta {
    background-color: rgba(54, 128, 237, .16);
    color: var(--brand-blue-light);
    box-shadow: inset 0 0 0 1px rgba(96, 164, 245, .28);
}

.v2x .dir-cat-cta-arr {
    display: grid;
    place-items: center;
    width: 24px; height: 24px;
    margin-inline-start: 2px;
    border-radius: 50%;
    background: var(--brand-blue);
    color: #fff;
    transition: background-color .2s ease, transform .22s var(--ease-spring);
}

.v2x .dir-cat-cta-arr svg { width: 13px; height: 13px; }
[dir="rtl"] .v2x .dir-cat-cta-arr svg { transform: scaleX(-1); }

@media (hover: hover) {
    .v2x .dir-cat-cta:hover {
        background-color: transparent;
        background-image: linear-gradient(135deg, var(--brand-blue) 0%, var(--brand-blue-dark) 100%);
        color: #fff;
        text-decoration: none;
        transform: translateY(-1px);
        box-shadow: 0 12px 24px -10px rgba(54, 128, 237, .55);
    }

    .v2x .dir-cat-cta:hover .dir-cat-cta-arr { background: rgba(255, 255, 255, .22); transform: translateX(2px); }
    [dir="rtl"] .v2x .dir-cat-cta:hover .dir-cat-cta-arr { transform: translateX(-2px); }
}

.v2x .dir-cat-cta:focus-visible {
    outline: none;
    background-color: transparent;
    background-image: linear-gradient(135deg, var(--brand-blue) 0%, var(--brand-blue-dark) 100%);
    color: #fff;
    box-shadow: 0 0 0 3px #CFE0FA;
}

.v2x .dir-cat-cta:active { transform: scale(.97); }

/* ── §CATBAND-RAIL) The track ─────────────────────────────────────────────
   WHOLE TILES, not `minmax(220px, 1fr)`. With a floor of 220px in a 1872px
   shell the last visible track was always a sliver — a card cut down the
   middle at the fade, which reads as a rendering fault rather than as "there
   is more this way". The homepage computes the track from a COLUMN COUNT so
   the row always ends on a whole tile, and the ladder at the foot of this
   block steps that count down with the viewport.

   Eight at the top, not the homepage's seven: this shell is 1920px against
   the homepage's 1600, so seven tiles here would be 255px against the
   homepage's 210. Eight lands at 221px — the same tile, not the same number.

   The bottom padding is load-bearing. A hovered card lifts 6px and drops a
   46px shadow; the rail is an `overflow-x: auto` box, which clips on BOTH
   axes, so with the 6px of padding it carried that shadow was sliced off flat
   along the bottom of every card in the row. The progress bar pulls itself
   back up into this space with a negative margin, so the band does not grow
   by the difference. */
.v2x .dir-cat-rail {
    --dir-cat-cols: 8;
    --dir-cat-gap: 14px;
    grid-auto-columns: calc(
        (100% - (var(--dir-cat-cols) - 1) * var(--dir-cat-gap)) / var(--dir-cat-cols)
    );
    gap: var(--dir-cat-gap);
    padding: 10px 6px 30px;
    scroll-padding-inline: 6px;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
}

/* 52px, matching the homepage. The 56px the base sheet uses was tuned against
   the old 220px track and now eats a visible slice of a 221px tile. */
.v2x .dir-cat-railwrap[data-scrollable="1"]:not([data-at-start="1"]) { --dir-cat-fade-l: 52px; }
.v2x .dir-cat-railwrap[data-scrollable="1"]:not([data-at-end="1"])   { --dir-cat-fade-r: 52px; }

/* ── §CATBAND-NUDGE) The arrows ───────────────────────────────────────────
   38px and OUTSIDE the track, where the homepage puts them. At 44px inset
   -8px they overlapped the first and last card — on a rail whose cards are
   links, an arrow parked on top of one is a mis-click waiting to happen.

   Hidden by `opacity` + `visibility`, not by the base sheet's `.3` dim: a
   greyed arrow at the end of the rail still looks pressable and still takes
   the click. `display` is left to the base rule so the button keeps its box
   and can fade rather than pop. */
.v2x .dir-cat-nav {
    width: 38px; height: 38px;
    background: #fff;
    border: 1px solid var(--v2-line);
    color: var(--brand-blue-dark);
    box-shadow: 0 6px 16px -8px rgba(16, 24, 40, .28);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-50%) scale(.9);
    transition: opacity .2s ease, transform .24s var(--ease-spring),
                background-color .18s ease, color .18s ease, border-color .18s ease;
}

body.theme-dark .v2x .dir-cat-nav {
    background: rgba(44,44,49,.95);
    border-color: var(--dk-line,rgba(255,255,255,.10));
    color: var(--brand-blue-light);
    box-shadow: 0 6px 16px -8px rgba(0, 0, 0, .6);
}

.v2x .dir-cat-nav svg { width: 15px; height: 15px; }
.v2x .dir-cat-nav-prev { left: -17px; right: auto; }
.v2x .dir-cat-nav-next { right: -17px; left: auto; }

.v2x .dir-cat-railwrap[data-scrollable="1"] .dir-cat-nav {
    opacity: 1;
    visibility: visible;
    transform: translateY(-50%) scale(1);
}

/* Overrides the base sheet's `opacity:.3` at the ends — later in the cascade
   and one class more specific, so it settles on both axes. */
.v2x .dir-cat-railwrap[data-at-start="1"] .dir-cat-nav-prev,
.v2x .dir-cat-railwrap[data-at-end="1"]   .dir-cat-nav-next {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-50%) scale(.9);
}

@media (hover: hover) {
    .v2x .dir-cat-nav:hover {
        background: linear-gradient(135deg, var(--brand-blue) 0%, var(--brand-blue-dark) 100%);
        border-color: transparent;
        color: #fff;
        transform: translateY(-50%) scale(1.06);
        box-shadow: 0 10px 22px -10px rgba(54, 128, 237, .55);
    }
}

.v2x .dir-cat-nav:focus-visible {
    outline: none;
    border-color: var(--brand-blue);
    box-shadow: 0 0 0 3px #CFE0FA;
}

.v2x .dir-cat-nav:active { transform: translateY(-50%) scale(.94); }

/* A touch device scrolls the rail with a finger; the arrows would only cover
   the first and last tile. `display:none` rather than opacity — on touch they
   are not an affordance at all, so they must not be tabbable either. */
@media (hover: none) {
    .v2x .dir-cat-nav { display: none; }
}

/* ── §CATBAND-BAR) The progress bar ───────────────────────────────────────
   A real control, not a readout: it reports where the rail is AND takes a
   click or a drag to move it. The element is injected by
   public-dir-catband.js, which also writes the two fractions below.

   It stays `aria-hidden`. This is a REDUNDANT pointer affordance — the arrows
   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 negative margin spends the 30px of glow room the track reserves above;
   without it the bar would sit that far adrift of the cards it belongs to. */
.v2x .dir-cat-railbar {
    position: relative;
    height: 8px;
    width: min(260px, 55%);
    margin: -10px auto 0;
    border-radius: 999px;
    background: var(--v2-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 every time a pointer crossed the bar. */
    transition: opacity .22s ease, transform .18s var(--ease-out), background-color .18s ease;
    /* pan-y, NOT none. `none` claims both axes, so a finger landing on a
       260px bar in the middle of the page could not scroll the page at all. */
    touch-action: pan-y;
    cursor: pointer;
}

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

/* 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. */
.v2x .dir-cat-railbar::before {
    content: '';
    position: absolute;
    inset: -10px 0;
}

.v2x .dir-cat-railwrap[data-scrollable="1"] .dir-cat-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. */
.v2x .dir-cat-railwrap:not([data-scrollable="1"]) .dir-cat-railbar { pointer-events: none; }

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

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

.v2x .dir-cat-railbar.is-dragging { cursor: grabbing; }

@media (hover: hover) {
    .v2x .dir-cat-railbar:hover,
    .v2x .dir-cat-railbar.is-dragging { transform: scaleY(1.3); background: #CFE0FA; }

    .v2x .dir-cat-railbar:hover .dir-cat-railbar-in,
    .v2x .dir-cat-railbar.is-dragging .dir-cat-railbar-in {
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), 0 4px 14px -3px rgba(54, 128, 237, .5);
    }
}

/* ── §CATBAND-CARD) The card details the §CAT pass left behind ────────────
   ⚠️ BOTH RULES THAT USED TO BE HERE ARE SUPERSEDED BY §CATPRM.

   1 · THE FOOT BAR was declared on `::after` at this line — 52% of the card,
   4px, tone-bg → tone-ink → tone-bg. The geometry is right and is kept; what
   changed is WHERE IT IS PAINTED. §CATPRM needs a pseudo-element for the
   orbit ring and this card has only two, `::before` already holding §CAT's
   halo, so the bar moves into the background stack where its width and
   height animate as `background-size`. See §CATPRM-BAR.

   2 · "ONE WASH, NOT TWO" was wrong, and it is the reason this band still
   read flat next to `/` after the parity pass. The homepage tile carries the
   halo AND a corner bloom AND a diagonal wash — three layers, measured on a
   resting tile at 1600px. What made the version this rule switched off look
   heavy was not that there were two washes; it was that §BANDS-CAT painted
   its corner in `--dtc-bg`, an OPAQUE pale hex, where the homepage paints
   `rgba(hue, .20)`. Turning the layer off fixed the heaviness by removing
   the colour system with it. §CATPRM restores all three at the homepage's
   own alphas. */

/* ── §CATBAND-RESP) The column ladder ─────────────────────────────────────
   Ported from the homepage's, shifted one step because this shell is 1920px
   wide against its 1600. The tile lands between 217px and 235px at every
   step, which is the homepage's 210px band. */
@media (max-width: 1700px) { .v2x .dir-cat-rail { --dir-cat-cols: 7; } }
@media (max-width: 1500px) { .v2x .dir-cat-rail { --dir-cat-cols: 6; } }
@media (max-width: 1180px) { .v2x .dir-cat-rail { --dir-cat-cols: 5; } }
@media (max-width: 1024px) { .v2x .dir-cat-rail { --dir-cat-cols: 4; } }
@media (max-width:  860px) { .v2x .dir-cat-rail { --dir-cat-cols: 3; } }

@media (max-width: 1024px) {
    .v2x .dir-cat-head:has(+ * .dir-cat-card) { margin-bottom: 24px; }
}

/* ⚠️ The 860px stacking rule that used to live here is GONE, and its absence
   is correct rather than an omission. It existed because the head was a
   two-column grid that could not fit a 43px heading and a pill on one row on
   a tablet; it dropped the CTA to row 4 and left-aligned it. The head is a
   centred single column at every width now (§CATBAND-HEAD), so the CTA is
   already stacked and already centred — re-stating `grid-area` on a flex
   child would do nothing, and `justify-self: start` would knock the button
   out of the centre it now belongs in. */

/* The ladder STOPS at 641px. Below that the rail goes back to a PEEK — one
   whole card and a clear slice of the next — because three 12.5px description
   lines in a third of a 390px viewport is not a card, it is a column of
   hyphens. Same call, and the same 76%, the homepage makes at this width.
   Declared after the ladder so any column count above is dead here. */
@media (max-width: 640px) {
    .v2x .dir-cat-rail {
        grid-auto-columns: minmax(220px, 76%);
        --dir-cat-gap: 12px;
        scroll-snap-type: x proximity;
        padding-bottom: 24px;
    }

    /* +2px, not the desktop -10px. The rail only pads 24px below the cards
       here, so the desktop pull-up would put this control's 20px-tall TOUCH
       TARGET inside them — a tap on the bottom edge of a card would grab the
       pager instead of opening the card. */
    .v2x .dir-cat-railbar {
        height: 6px;
        width: min(180px, 62%);
        margin-top: 2px;
    }

    .v2x .dir-cat-railbar::before { inset: -12px 0; }
}

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

/* The progress bar carries no information a forced-colour user can act on; in
   that mode it becomes an opaque system fill across the band. The card's own
   decoration is handled in §CATPRM-A11Y, which owns every layer it paints. */
@media (forced-colors: active) {
    .v2x .dir-cat-railbar { display: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §CATPRM — the homepage's PREMIUM card language on the listing bands
   (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   §CAT (2026-08-13) matched the tile's ANATOMY — halo, solid plate, count
   pill, ringed action, foot bar. §CATBAND (2026-08-18) matched the CHROME
   around it — railhead, whole-tile track, outside arrows, progress bar.
   Measured side by side at 2560px after both, the rows still did not read
   alike, and the whole of the remaining gap was SURFACE:

                            homepage `.hpx-cat`        listing `.dir-cat-card`
     diagonal wash          rgba(hue, .075 → .055)     none
     corner bloom           rgba(hue, .20), 220x200    none
     card edge              rgba(hue, .20)             `--dtc-line`, a pale
                                                       composited hex
     resting shadow         4 layers, tone-tinted      2 layers, neutral grey
     hover ring             masked conic, 3.4s sweep   none

   That is the homepage's §HPXP block — "the detail page's card language,
   homepage-wide" — and none of it had reached these fifty-one URLs. The
   listing tiles were white boxes with a coloured icon on them; the homepage's
   are lit surfaces. Everything below is §HPXP ported host-for-host, at its
   own measured alphas, so the two rows are the same card and not two designs
   that happen to share a layout.

   ── The pseudo-element problem, and what it cost ──────────────────────────
   §HPXP needs one pseudo for the orbit ring. `.hpx-cat` has `::before` on the
   halo and pays for the ring with `::after`, because ITS foot bar is a real
   `<span class="hpx-cat-bar">` in the blade.

   `.dir-cat-card` has no such span — its foot bar IS `::after` — so on this
   card both slots were already spent. Adding a span means editing twenty-six
   blades, which is the exact failure mode this file exists to avoid.

   So the BAR moves and the RING takes the slot. A bar is a rectangle of
   gradient at a fixed spot on the box, which is precisely what a background
   layer is; a masked conic ring cannot be anything but a pseudo. Both of the
   bar's hover changes — 52% → 100% wide, 4px → 5px tall — are one animatable
   `background-size`, so nothing is lost in the move. See §CATPRM-BAR.

   ── Why the tokens could not carry this ───────────────────────────────────
   `--dtc-bg|ink|line` are FLAT colours — pale composited hexes tuned to sit
   under a count pill. A wash, a bloom and a conic tail have to vary ALPHA
   along a gradient, and no `--dtc-*` token carries an RGB triplet to vary.
   §CATPRM-RGB is that missing primitive, and its values are §HPXP-RGB's
   verbatim so a violet tile here and a violet tile on `/` are one violet.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §CATPRM-RGB) The hue as a triplet ────────────────────────────────────
   ⚠️ THE FALLBACK ON THE BASE SELECTOR IS LOAD-BEARING. An undefined triplet
   makes every `rgba(var(--dtc-rgb), …)` below an INVALID declaration, and an
   invalid declaration is dropped SILENTLY — the wash, the bloom, the edge and
   the ring would simply not exist, with nothing in the output to say why. The
   rail cycles eight tones but the taxonomies feed `data-tone` from five
   different vocabularies, so an unlisted value reaching this attribute is a
   when, not an if. Same trap §HPXP-RGB records. */
.v2x .dir-cat-card,
.v2x.gdx .gdx-catcard                                                    { --dtc-rgb: 54, 128, 237; }
.v2x .dir-cat-card[data-tone="blue"],    .v2x.gdx .gdx-catcard[data-tone="blue"]    { --dtc-rgb: 54, 128, 237; }
.v2x .dir-cat-card[data-tone="violet"],  .v2x.gdx .gdx-catcard[data-tone="violet"]  { --dtc-rgb: 139, 92, 246; }
.v2x .dir-cat-card[data-tone="indigo"],  .v2x.gdx .gdx-catcard[data-tone="indigo"]  { --dtc-rgb: 99, 102, 241; }
.v2x .dir-cat-card[data-tone="rose"],    .v2x.gdx .gdx-catcard[data-tone="rose"]    { --dtc-rgb: 244, 63, 94; }
.v2x .dir-cat-card[data-tone="amber"],   .v2x.gdx .gdx-catcard[data-tone="amber"]   { --dtc-rgb: 245, 158, 11; }
.v2x .dir-cat-card[data-tone="emerald"], .v2x.gdx .gdx-catcard[data-tone="emerald"] { --dtc-rgb: 16, 185, 129; }
.v2x .dir-cat-card[data-tone="cyan"],    .v2x.gdx .gdx-catcard[data-tone="cyan"]    { --dtc-rgb: 34, 184, 217; }
.v2x .dir-cat-card[data-tone="orange"],  .v2x.gdx .gdx-catcard[data-tone="orange"]  { --dtc-rgb: 249, 115, 22; }
.v2x .dir-cat-card[data-tone="teal"],    .v2x.gdx .gdx-catcard[data-tone="teal"]    { --dtc-rgb: 20, 184, 166; }
.v2x .dir-cat-card[data-tone="pink"],    .v2x.gdx .gdx-catcard[data-tone="pink"]    { --dtc-rgb: 236, 72, 153; }
.v2x .dir-cat-card[data-tone="slate"],   .v2x.gdx .gdx-catcard[data-tone="slate"]   { --dtc-rgb: 100, 116, 139; }

/* The ramp every layer below reads. Four numbers in light, four in dark —
   which is the difference between a system and a second half of this file
   that drifts out of step with the first. */
.v2x .dir-cat-card,
.v2x.gdx .gdx-catcard {
    --dcp-a1: .075;     /* wash, the lit corner */
    --dcp-a2: .055;     /* wash, the far corner */
    --dcp-edge: .20;    /* the tinted hairline */
    --dcp-bloom: .20;   /* the corner bloom's hot end */
}

/* Dark: the card sits at rgba(28,31,42,.9) over a near-black page, so it is
   LIGHTER than the ground 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 .v2x .dir-cat-card,
body.theme-dark .v2x.gdx .gdx-catcard {
    --dcp-a1: .13;
    --dcp-a2: .09;
    --dcp-edge: .30;
    --dcp-bloom: .17;
}

/* ── §CATPRM-WASH) The surface ────────────────────────────────────────────
   THREE LAYERS IN ONE STACK, topmost first: the foot bar, the corner bloom,
   the diagonal wash. The halo stays where §CAT put it, on `::before`, and
   paints above all three.

   ⚠️ `background-image` ONLY — never the `background` shorthand. The base
   sheet sets the card's colour with a shorthand (`background:#fff`, and
   `rgba(28,31,42,.9)` under `body.theme-dark`); writing a shorthand here
   would reset that to transparent and let the page ground show through every
   card on all fifty-one URLs. Setting the IMAGE alone also means ONE rule
   serves both themes — dark is the four alphas above and nothing else.

   The 152deg angle, the 48% dead stop and the fade to `rgba(same-hue, 0)`
   rather than the `transparent` KEYWORD are §HPXP-WASH's, and 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.

   The bloom's CENTRE sits OUTSIDE the box on purpose — `calc(100% + 30px)`
   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. */
/* The tinted hairline, on both hosts. Split from the wash below because the
   two cards carry a DIFFERENT NUMBER OF LAYERS and a shared `background-size`
   would have to be wrong for one of them. */
.v2x .dir-cat-card,
.v2x.gdx .gdx-catcard[data-tone] { border-color: rgba(var(--dtc-rgb), var(--dcp-edge-now, var(--dcp-edge))); }

.v2x .dir-cat-card {
    background-image:
        /* 1 · the foot bar — see §CATPRM-BAR for why it is a layer */
        linear-gradient(90deg,
            var(--dtc-bg) 0%,
            var(--dtc-ink) 50%,
            var(--dtc-bg) 100%),
        /* 2 · the corner bloom */
        radial-gradient(220px 200px at calc(100% + 30px) -54px,
            rgba(var(--dtc-rgb), var(--dcp-bloom)) 0%,
            rgba(var(--dtc-rgb), 0) 72%),
        /* 3 · the diagonal wash */
        linear-gradient(152deg,
            rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dtc-rgb), 0)             48%,
            rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 50% 100%, 0 0, 0 0;
    background-size: var(--dcp-bar-w, 52%) var(--dcp-bar-h, 4px), 100% 100%, 100% 100%;
}

/* The hub card is a READING surface — a nested list of guide links under the
   head — and it is 380px tall against the rail tile's 325. It takes the wash
   and the edge, and it does NOT take the bar or the ring: a coloured tab and
   a sweeping light under a column of links compete with the links, which is
   the same call §BANDS-GDX already records for the icon plate. Two layers,
   and the bloom is scaled to the taller box the way §HPXP scales `.hpx-libcol`
   to its column. */
.v2x.gdx .gdx-catcard[data-tone] {
    background-image:
        radial-gradient(300px 250px at calc(100% + 34px) -62px,
            rgba(var(--dtc-rgb), var(--dcp-bloom)) 0%,
            rgba(var(--dtc-rgb), 0) 72%),
        linear-gradient(152deg,
            rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dtc-rgb), 0)             48%,
            rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 0 0, 0 0;
    background-size: 100% 100%, 100% 100%;
}

/* ⚠️ THE DARK TWINS ARE NOT AN OPTIMISATION, THEY ARE THE RULE.
   Every other property in this block themes itself for free, because dark is
   the four alphas in §CATPRM-RGB and the wash rule never changes. The SURFACE
   is the exception, and it is the exception on both hosts:

     body.theme-dark .v2x .dir-cat-card { background: rgba(28,31,42,.9) }
     body.theme-dark .v2x.gdx .gdx-catcard { background: … }

   Those are the base sheets', they are (0,3,1) against the (0,2,0) above, and
   they use the `background` SHORTHAND — so they out-rank the wash AND reset
   `background-image` to none in one move. Without the four rules below the
   whole colour system silently disappears after dark, and only after dark,
   which is the kind of bug that ships. Measured on 2026-08-18: the light row
   was correct and the dark row was flat, from exactly this.

   `background-color` is deliberately NOT restated — the base's shorthand is
   still the one setting it, and duplicating the surface colour here is how a
   card ends up a different grey from the band it sits in. */
body.theme-dark .v2x .dir-cat-card {
    background-image:
        linear-gradient(90deg,
            var(--dtc-bg) 0%,
            var(--dtc-ink) 50%,
            var(--dtc-bg) 100%),
        radial-gradient(220px 200px at calc(100% + 30px) -54px,
            rgba(var(--dtc-rgb), var(--dcp-bloom)) 0%,
            rgba(var(--dtc-rgb), 0) 72%),
        linear-gradient(152deg,
            rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dtc-rgb), 0)             48%,
            rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 50% 100%, 0 0, 0 0;
    background-size: var(--dcp-bar-w, 52%) var(--dcp-bar-h, 4px), 100% 100%, 100% 100%;
}

body.theme-dark .v2x.gdx .gdx-catcard[data-tone] {
    background-image:
        radial-gradient(300px 250px at calc(100% + 34px) -62px,
            rgba(var(--dtc-rgb), var(--dcp-bloom)) 0%,
            rgba(var(--dtc-rgb), 0) 72%),
        linear-gradient(152deg,
            rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dtc-rgb), 0)             48%,
            rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 0 0, 0 0;
    background-size: 100% 100%, 100% 100%;
}

/* The edge has the same problem for the same reason — the base dark rules
   set `border-color: rgba(255,255,255,.08)` alongside that shorthand. */
body.theme-dark .v2x .dir-cat-card,
body.theme-dark .v2x.gdx .gdx-catcard[data-tone] { border-color: rgba(var(--dtc-rgb), var(--dcp-edge-now, var(--dcp-edge))); }

/* ── §CATPRM-BAR) The foot bar, as a background layer ─────────────────────
   Geometry unchanged from §CATBAND-CARD: 52% of the card, 4px, running
   tone-bg → tone-ink → tone-bg so it reads as a coloured tab under the tile,
   and growing to the full width at 5px on hover.

   `background-size` is animatable and this is the whole reason the move
   costs nothing. All three layers are sized explicitly — `100% 100%` on the
   two gradients rather than `auto`, which computes identically and gives the
   interpolation an unambiguous pair to run between.

   The 20px corner radius clips the bar's ends once it reaches full width.
   That is the homepage's behaviour too: `.hpx-cat-bar` goes to `inset-inline:
   0` inside the same `overflow: hidden`.

   ⚠️ HOVER SETS TWO CUSTOM PROPERTIES, NOT `background-size`, AND THAT IS
   NOT A STYLE PREFERENCE. `background-size` has to be declared inside the
   surface rules, and those exist twice — once at (0,2,0) for light and once
   at (0,3,1) for dark, because only (0,3,1) can out-rank the base sheet's
   dark `background` shorthand. A hover rule written on the PROPERTY would be
   (0,2,1): enough to beat the light surface and NOT enough to beat the dark
   one, so the bar would grow by day and sit still by night.

   A custom property has no such fight. `--dcp-bar-w` is set nowhere in the
   surface rules, so `:hover` at (0,2,1) is simply the most specific rule
   that sets it, in both themes, and the `var()` inside `background-size`
   picks the new value up wherever that declaration happens to have come
   from. One state rule instead of four.

   ⚠️ The base sheet's `transition` on this card lists transform, border-color
   and box-shadow only, so without the line below the bar would SNAP between
   its two widths. */
.v2x .dir-cat-card {
    --dcp-bar-w: 52%;
    --dcp-bar-h: 4px;
    transition: transform .28s var(--ease-out),
                border-color .22s ease,
                box-shadow .28s ease,
                background-size .34s var(--ease-out);
}

@media (hover: hover) {
    .v2x .dir-cat-card:hover { --dcp-bar-w: 100%; --dcp-bar-h: 5px; }
}

.v2x .dir-cat-card:focus-visible { --dcp-bar-w: 100%; --dcp-bar-h: 5px; }

/* ── §CATPRM-ORBIT) The rotating light ────────────────────────────────────
   §HPXP-ORBIT at the category tile's own speed — 3.4s, the number that block
   assigns this exact host. Speed there 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.

   THE HUE IS NOT FIXED: it reads `--dtc-rgb`, so a violet card orbits violet
   and an amber one amber. That is the difference between an effect pasted
   onto a band and a system — the light belongs to the card it is lighting.

   A NEW registered property and a NEW keyframe rather than reusing the
   homepage's `--hpx-pspin`: homepage.css is not loaded on any of these URLs,
   so borrowing the name would be borrowing a definition that is not there. */

@property --dcp-spin { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@keyframes dir-cat-orbit { to { --dcp-spin: 360deg; } }

.v2x .dir-cat-card {
    --dcp-hot: 255, 255, 255;   /* the specular core of the comet */
    --dcp-o1: .5;               /* leading tail */
    --dcp-o2: .88;              /* trailing tail */
    --dcp-o3: 1;                /* core */
}

body.theme-dark .v2x .dir-cat-card { --dcp-o1: .66; --dcp-o2: 1; }

/* THE SLOT IS EMPTY UNTIL @supports SAYS OTHERWISE, and this line is the
   correctness of the block, not a tidy-up. `::after` still carries the base
   sheet's foot-bar declarations — a 3px `linear-gradient` at the bottom edge.
   The ring rule below re-boxes the pseudo to `inset: 0`, so where the ring
   itself is unsupported that stale gradient would paint FULL-CARD over every
   word on the tile. `display: none` is what makes the fallback "no ring" and
   not "a blue sheet across the card"; the bar it used to draw is a background
   layer now and does not need it. */
.v2x .dir-cat-card::after { display: none; }

@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.

       ⚠️ inset: 0, not the reference's inset: -1px. This card carries
       `overflow: hidden` so it can clip its 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.

       The three geometry resets are not decoration. This slot spent the day
       as a 3px strip pinned to the bottom edge with `left/right/height` and
       an `opacity` of .65, and every one of those has to be unwound here or
       the ring inherits a sliver of its predecessor's box. */
    .v2x .dir-cat-card::after,
    .v2x .dir-cat-card:hover::after,
    .v2x .dir-cat-card:focus-visible::after {
        display: block;
        content: '';
        position: absolute;
        inset: 0;
        width: auto;
        height: auto;
        transform: none;
        z-index: 2;
        pointer-events: none;
        border-radius: inherit;
        padding: var(--dcp-t, 1.5px);
        --dcp-spin: 0deg;
        background: conic-gradient(from var(--dcp-spin),
            rgba(var(--dtc-rgb), 0)                 0deg,
            rgba(var(--dtc-rgb), var(--dcp-o1))    30deg,
            rgba(var(--dcp-hot),  var(--dcp-o3))   52deg,
            rgba(var(--dtc-rgb), var(--dcp-o2))    74deg,
            rgba(var(--dtc-rgb), 0)               118deg,
            rgba(var(--dtc-rgb), 0)               180deg,
            rgba(var(--dtc-rgb), var(--dcp-o1))   210deg,
            rgba(var(--dcp-hot),  var(--dcp-o3))  232deg,
            rgba(var(--dtc-rgb), var(--dcp-o2))   254deg,
            rgba(var(--dtc-rgb), 0)               298deg,
            rgba(var(--dtc-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;
        transition: opacity .32s ease;
        animation: dir-cat-orbit 3.4s linear infinite;
        /* A paused animation does not tick, so a rail of ten 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;
    }

    /* Split from the block above so the two STATE rules can set it to 1
       without restating the ring. */
    .v2x .dir-cat-card::after { opacity: 0; }

    /* 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 §HPXP-ORBIT makes. */
    .v2x .dir-cat-card:focus-visible::after { opacity: 1; animation-play-state: running; }

    @media (hover: hover) {
        /* (0,3,1), which it has to be: the base sheet's own
           `.dir-cat-card:hover::after { opacity: 1; height: 5px }` is exactly
           that weight, and the `height` half would put the comet in a 5px
           letterbox along the bottom edge. Out-ranked, not merely followed. */
        .v2x .dir-cat-card:hover::after { opacity: 1; height: auto; animation-play-state: running; }
    }
}

/* ── §CATPRM-LIFT) Depth, tone-tinted ─────────────────────────────────────
   §HPXP-LIFT's recipe: a glass hairline along the top inner edge, a 1px
   contact shadow to seat the card against the page, a tone-tinted lift and a
   neutral one under it. The tone in the lift shadow is what makes a violet
   card cast a violet-tinged shadow rather than the same grey as its blue
   neighbour — at .40 alpha and a -22px spread it is not a colour anyone
   names, it is why the row looks lit.

   This REPLACES §CAT's three-layer shadow rather than adding to it: §CAT's
   was the homepage's pre-§HPXP shadow, ported faithfully from a recipe the
   homepage has since moved off. */
.v2x .dir-cat-card,
.v2x.gdx .gdx-catcard[data-tone] {
    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(--dtc-rgb), .40),
        0 10px 24px -20px rgba(16, 24, 40, .26);
}

body.theme-dark .v2x .dir-cat-card,
body.theme-dark .v2x.gdx .gdx-catcard[data-tone] {
    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(--dtc-rgb), .34),
        0 10px 24px -20px rgba(0, 0, 0, .78);
}

/* The edge steps up on both states, and like the foot bar above it does so
   through a CUSTOM PROPERTY rather than by redeclaring `border-color`. Same
   reason: `border-color` lives in the two surface rules, one of which is
   (0,3,1) so it can beat the base sheet's dark shorthand, and a state rule on
   the property itself would be (0,2,1) — enough by day, not enough by night.
   `--dcp-edge-now` is set nowhere else, so the state rule simply wins. */
@media (hover: hover) {
    .v2x .dir-cat-card:hover,
    .v2x.gdx .gdx-catcard[data-tone]:hover { --dcp-edge-now: calc(var(--dcp-edge) + .22); }
}

.v2x .dir-cat-card:focus-visible { --dcp-edge-now: calc(var(--dcp-edge) + .42); }

@media (hover: hover) {
    /* The lift roughly doubles. The card's own `translateY(-6px)` is
       untouched — this pass is about surface, and the band's rhythm is the
       base sheet's to own. */
    .v2x .dir-cat-card:hover,
    .v2x.gdx .gdx-catcard[data-tone]:hover {
        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(--dtc-rgb), .52),
            0 14px 30px -22px rgba(16, 24, 40, .28);
    }

    /* (0,3,2), because the RESTING dark shadow above is (0,3,1) and would
       otherwise out-rank the (0,2,1) hover rule it is meant to follow. */
    body.theme-dark .v2x .dir-cat-card:hover,
    body.theme-dark .v2x.gdx .gdx-catcard[data-tone]: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(--dtc-rgb), .46),
            0 14px 30px -22px rgba(0, 0, 0, .82);
    }
}

/* Focus gets the tone ring the base sheet already gives it, re-stated on top
   of the new lift so a keyboard visitor is not handed the two-layer shadow
   this block just replaced. The dark twin is here for the same specificity
   reason the hover one is. */
.v2x .dir-cat-card:focus-visible {
    outline: none;
    box-shadow:
        0 0 0 3px rgba(var(--dtc-rgb), .22),
        0 22px 44px -26px rgba(var(--dtc-rgb), .52),
        0 14px 30px -22px rgba(16, 24, 40, .28);
}

body.theme-dark .v2x .dir-cat-card:focus-visible {
    box-shadow:
        0 0 0 3px rgba(var(--dtc-rgb), .34),
        0 22px 44px -26px rgba(var(--dtc-rgb), .46),
        0 14px 30px -22px rgba(0, 0, 0, .82);
}

/* ── §CATPRM-RESP) Phone ──────────────────────────────────────────────────
   The bloom is sized in PIXELS, so it does not shrink with the card the way
   the wash does. Below 641px the rail stops stepping its column count down
   and goes to a 76% peek — a card near 280px wide — and a 220px bloom on that
   is a flood across the whole surface rather than light falling on a corner.
   Both the bloom and the ring step down once, exactly as §HPXP-RESP does at
   its own breakpoint.

   The wash and the edge are untouched: they are proportional already, and the
   colour system has to survive the breakpoint or the phone gets back the flat
   white row this block exists to remove. */
@media (max-width: 767.98px) {
    /* One step thinner. A 1.5px ring on a 280px card is proportionally twice
       what it is on a 560px one, and at that width it stops reading as a lit
       edge and starts reading as a second border. */
    .v2x .dir-cat-card::after,
    .v2x .dir-cat-card:hover::after,
    .v2x .dir-cat-card:focus-visible::after { --dcp-t: 1.2px; }

    /* Both themes, because a `background-image` written at (0,2,0) inside a
       media query still loses to the base sheet's (0,3,1) dark shorthand —
       the media query changes nothing about specificity. Same pairing as
       §CATPRM-WASH, and the reason the bloom is the only value that differs
       between the four rules. */
    .v2x .dir-cat-card,
    body.theme-dark .v2x .dir-cat-card {
        background-image:
            linear-gradient(90deg,
                var(--dtc-bg) 0%,
                var(--dtc-ink) 50%,
                var(--dtc-bg) 100%),
            radial-gradient(150px 140px at calc(100% + 20px) -38px,
                rgba(var(--dtc-rgb), var(--dcp-bloom)) 0%,
                rgba(var(--dtc-rgb), 0) 72%),
            linear-gradient(152deg,
                rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
                rgba(var(--dtc-rgb), 0)             48%,
                rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    }

    .v2x.gdx .gdx-catcard[data-tone],
    body.theme-dark .v2x.gdx .gdx-catcard[data-tone] {
        background-image:
            radial-gradient(190px 170px at calc(100% + 22px) -42px,
                rgba(var(--dtc-rgb), var(--dcp-bloom)) 0%,
                rgba(var(--dtc-rgb), 0) 72%),
            linear-gradient(152deg,
                rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
                rgba(var(--dtc-rgb), 0)             48%,
                rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    }
}

/* ── §CATPRM-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 — ten of them per rail,
   on every one of these fifty-one URLs. Suppressing it there is a battery
   decision, not a design one: the wash, the bloom, the tinted edge and the
   foot bar are what those visitors were getting nothing of before, and all
   four remain. */
@media (hover: none) {
    .v2x .dir-cat-card::after,
    .v2x .dir-cat-card:focus-visible::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, and
       the bar's grow. */
    .v2x .dir-cat-card::after,
    .v2x .dir-cat-card:hover::after,
    .v2x .dir-cat-card:focus-visible::after { animation: none !important; }

    .v2x .dir-cat-card {
        transition: transform .28s var(--ease-out),
                    border-color .22s ease,
                    box-shadow .28s ease;
    }
}

@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, and a tone wash is an opaque fill that swallows the
       card. The hairline is restored as a real border so the cards stay
       separable, which is all forced colours needs from them. */
    .v2x .dir-cat-card::after { display: none; }

    /* The dark selectors are listed for the same reason they are everywhere
       else in this block: a forced palette and a dark site preference are an
       ordinary combination, and at (0,2,0) this rule would leave the wash
       painted under it. */
    .v2x .dir-cat-card,
    .v2x.gdx .gdx-catcard[data-tone],
    body.theme-dark .v2x .dir-cat-card,
    body.theme-dark .v2x.gdx .gdx-catcard[data-tone] {
        background-image: none;
        border: 1px solid CanvasText;
        box-shadow: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §HEAD — the homepage's showcase head, on every listing band  (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   The cards were brought to homepage parity by §CATPRM. The HEADS above them
   never were. Measured against `.hpx-head.is-showcase` on `/` — the "Why
   choose AIToolsay?" band, which is the reference — the three band heads on
   these fifty-one URLs were three different designs:

                       homepage        .dir-cat-head   .dir-why-head   FAQ aside
     eyebrow size      12.5px          13px            13px            11.5px
     eyebrow case      sentence        sentence        sentence        UPPERCASE
     eyebrow tracking  -.005em         -.004em         -.004em         +.1em
     eyebrow ink       #1B5BB5         #2060C0         #2060C0         #2060C0
     eyebrow gap       8px             6px             6px             7px
     eyebrow pad-left  13px            12px            12px            12px
     title size        42.6px          43px  ✓         48px            33.6px
     title weight      700             800             800             800
     title tracking    -.03em          -.03em ✓        -.024em         -.028em
     title line-height 1.12            1.12  ✓         1.1             1.14
     accent gradient   120deg, 2 stop  100deg, 3 stop  100deg, 3 stop  none
     sub size          16px            16px  ✓         15px            15px
     sub ink           #6B7686         #4A4A5A         #4A4A5A         --aits-mut

   The Categories head is already close because §CATBAND-HEAD rebuilt it; the
   ticks above are its. This block finishes the job and applies the SAME values
   to the other two, so the three heads on a page are one design.

   ── Layout, band by band ──────────────────────────────────────────────────
     · Categories — CENTRED showcase with §STAR's sparkle flanks and the CTA
       stacked below the sub. It spent one revision as a left-aligned railhead
       copied from the homepage's version of this band; §CATBAND-HEAD records
       why that was reverted, and it comes down to a page reading its own
       internal consistency before it reads consistency with `/`.
     · Why — centred, with the same flanks. Already correct.
     · FAQ — a 350px aside rail beside the accordion, and the ONE head whose
       layout is genuinely left alone. See §HEAD-FAQ for why centring it would
       cost the band its contact card.

   §STAR already ports the flanks exactly — 74x44 masked clusters, .32 → .62
   over 4.5s, mirrored leading cluster, `calc(100% + 10px)` offset. Measured
   against the homepage's `hpx-twinkle` and identical. Nothing to do.

   ── Dark mode ─────────────────────────────────────────────────────────────
   Every rule below declares GEOMETRY AND TYPE ONLY, plus light-mode colour.
   The base sheets ship a dark twin for each eyebrow at (0,3,1) — bg
   `rgba(54,128,237,.16)`, `--brand-blue-light` ink, a `rgba(96,164,245,.28)`
   ring — which keeps winning for colour in both directions and is left alone.

   ⚠️ THE PILL'S HAIRLINE IS AN INSET SHADOW, NOT A `border`. The homepage
   uses a real border; here that would be a bug. The base dark rule overrides
   `box-shadow` but says nothing about `border`, so a `border: 1px solid
   #CFE0FA` written here would survive into dark mode and paint a pale blue
   ring on a night pill. Carrying the hairline in the shadow slot the dark
   rule already replaces is what makes one declaration serve both themes.
   Same class of trap as [dark-background-shorthand-trap] in §CATPRM-WASH.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §HEAD-PILL) The eyebrow ──────────────────────────────────────────────
   One recipe for both `.dir-*` bands. The 12.5px/-.005em/8px/13px numbers are
   `.hpx-head.is-showcase .hpx-eyebrow`'s, and `#1B5BB5` is `--hpx-brand-ink`
   — a full step darker than the `--brand-blue-dark` (#2060C0) these pills use
   today, which is the difference between a pill that reads as a label and one
   that reads as a link. */
/* ⚠️ `line-height` and the REAL BORDER are both here because the pill's
   HEIGHT is the thing a visitor actually compares, and neither is optional to
   reach it. The homepage's box is 20px of line + 14px of padding + 2px of
   border = 36px. Written with the inherited 1.5 line-height and the hairline
   in an inset shadow, the same declarations render 33px — three points short,
   which on a pill sitting directly above a heading reads as a smaller badge
   rather than as a rounding difference. Measured, not assumed.

   A real `border` rather than `box-shadow: inset`, despite the dark-leak trap
   this file records elsewhere: the base dark rule replaces `box-shadow` but
   says nothing about `border`, so the light `#CFE0FA` would survive into dark.
   The dark twin below closes that explicitly, which is the honest fix — the
   alternative, faking the 2px with extra padding, gets the height right and
   leaves the two sheets describing different boxes. */
.v2x .dir-cat-eyebrow,
.v2x .dir-why-eyebrow {
    gap: 8px;
    margin-bottom: 14px;
    padding: 7px 16px 7px 13px;
    border: 1px solid #CFE0FA;
    color: #1B5BB5;
    font-size: 12.5px;
    font-weight: 700;
    line-height: 20px;
    letter-spacing: -.005em;
    text-transform: none;
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}

/* The base dark rule carries the hairline as `box-shadow: inset 0 0 0 1px`.
   With a real border above that would be TWO hairlines at night, so this
   moves the ring back into the border and leaves the shadow as depth only. */
body.theme-dark .v2x .dir-cat-eyebrow,
body.theme-dark .v2x .dir-why-eyebrow {
    border-color: rgba(96, 164, 245, .28);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .3);
}

/* The mark is the BRAND blue, one step lighter than the words beside it —
   `.hpx-eyebrow-spark` sets its own `color` rather than inheriting the pill's
   ink, and that contrast is what stops the pill reading as a solid block. */
.v2x .dir-cat-eyebrow svg,
.v2x .dir-why-eyebrow svg {
    width: 14px; height: 14px;
    flex: 0 0 auto;
    color: var(--brand-blue);
}

body.theme-dark .v2x .dir-cat-eyebrow svg,
body.theme-dark .v2x .dir-why-eyebrow svg { color: var(--brand-blue-light); }

/* ── §HEAD-TYPE) The heading ──────────────────────────────────────────────
   The homepage sizes this as `calc(var(--hpx-fs-h2) * 1.42)` — 42.6px at the
   default h2 token — and lets the Typography screen carry the responsive
   steps. There is no `--hpx-fs-h2` on these pages, so the size is expressed
   as the clamp §CATBAND-HEAD already tuned for the Categories head, which
   lands on 43px at desktop and steps down on a narrow column. Using the SAME
   clamp on both bands is the point: the two headings on a page have to be one
   size, and `clamp(30px, 3.6vw, 48px)` put the Why heading five points above
   its neighbour.

   Weight 700, not 800. The base sheets ask for 800 in four places and the
   element renders at 700 anyway — whatever wins is not in those sheets, the
   same unfindable override §BANDS-CAT records for `.dir-why-title-c`. It is
   declared here so the value is stated where it is true rather than left as a
   number nobody can trace.

   ⚠️ THE HEAD CLASS IN THE SELECTOR IS LOAD-BEARING, AND IT IS THE ANSWER TO
   A NOTE THIS FILE HAS BEEN CARRYING SINCE THIS MORNING. §BANDS-CAT records an
   override that "was not findable … whatever is winning, it is not visible
   from the CSSOM". It is `custom.ltr.css`:

       body:not(.admin-shell):not(.theme-dark) h2 {
           color: var(--ez-ink); font-weight: 700;
           letter-spacing: -.02em; line-height: 1.2;
       }

   `:not()` takes the specificity of its argument, so that is TWO classes and
   TWO elements — (0,2,2) — and it beats any `.v2x .dir-x-title` at (0,2,0) on
   every property it names. It never showed up because it does not mention the
   class, and it is why these headings render at 700 no matter what weight the
   base sheets ask for, and why the first version of this rule set the size and
   silently lost the line-height and the tracking.

   Qualifying with the HEAD gives (0,3,0), which clears it. §CATBAND-HEAD's
   railhead rule already cleared it by accident — `:has()` carries its
   argument's specificity, so that selector is (0,4,0), which is the only
   reason the Categories heading was correct and the Why heading was not.

   Written on `.dir-cat-head` rather than only on the railhead variant so that
   the OTHER heads in the `.dir-cat-*` namespace — Featured GPTs, Popular right
   now, Acronyms & Aliases — come to the same size. Two headings on one page at
   43px and 48px is the inconsistency this block exists to remove. */
.v2x .dir-why-head .dir-why-title,
.v2x .dir-cat-head .dir-cat-title {
    font-size: clamp(26px, 2.4vw + 6px, 43px);
    font-weight: 700;
    line-height: 1.12;
    letter-spacing: -.03em;
}

/* ── §HEAD-ACCENT) The gradient word ──────────────────────────────────────
   `linear-gradient(120deg, brand 0%, brand-600 100%)` — TWO stops on a 120°
   axis. The listing sheets run a three-stop 100° gradient that returns to the
   starting hue at 100%, so the word brightens in the middle and lands back
   where it started; on a two-word accent like "AI Categories" that reads as a
   highlight sweep rather than as a single coloured phrase. The homepage's
   travels once, which is why its accent looks like ink and this one looked
   like an effect.

   `color: transparent` as well as `-webkit-text-fill-color`: the fill-color
   property is the one that works today, the standard `color` is what keeps
   working when it is retired. */
.v2x .dir-cat-title-accent,
.v2x .dir-why-title-accent {
    background: linear-gradient(120deg, var(--brand-blue) 0%, var(--brand-blue-dark) 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}

body.theme-dark .v2x .dir-cat-title-accent,
body.theme-dark .v2x .dir-why-title-accent {
    background: linear-gradient(120deg, var(--brand-blue-light) 0%, var(--brand-blue) 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}

/* ── §HEAD-SUB) The line under the heading ────────────────────────────────
   16px on 1.66 in `--hpx-ink-3` (#6B7686), capped at 56ch. The listing sub is
   15px in `--v2-muted` (#4A4A5A) at 60ch — darker, tighter and wider, so it
   competes with the heading instead of sitting under it.

   ⚠️ #6B7686 measures 4.60:1 on white — 0.10 above the AA floor. That is fine
   HERE and is not fine on a tinted card, which is the whole subject of the
   homepage's §HPXP-INK note. These subs sit on the page ground, not on a
   washed surface, so the token is used as-is; do not copy this colour onto
   anything inside a `.dir-cat-card`. */
.v2x .dir-cat-sub,
.v2x .dir-why-sub {
    margin-top: 12px;
    max-width: 56ch;
    font-size: 16px;
    line-height: 1.66;
    color: #6B7686;
}

body.theme-dark .v2x .dir-cat-sub,
body.theme-dark .v2x .dir-why-sub { color: var(--v2-muted); }

/* Both heads are flex columns with `align-items: center`, so a `max-width`
   child centres itself and the `auto` margins below are belt-and-braces for
   the case where a vertical's own sheet re-declares `align-items`. */
.v2x .dir-why-head > .dir-why-sub,
.v2x .dir-cat-head > .dir-cat-sub { margin-inline: auto; }

/* The head's own spacing. `.hpx-head.is-showcase` is 30px clear of the band
   below it; `.dir-why-head` was 36px with a 14px internal gap on top of the
   eyebrow's own margin, which double-counted the space under the pill. */
.v2x .dir-why-head {
    gap: 0;
    margin-bottom: 30px;
}

/* ── §HEAD-FAQ) The FAQ aside ─────────────────────────────────────────────
   ⚠️ SCOPED TO `.dir-faq-sect`, AND THE SCOPE IS THE POINT. `.aitsq` is the
   site's ONE FAQ component — `components/public/faq.blade.php`, rendered from
   47 blades: tool detail pages, GPT and extension show pages, the legal hub,
   the blog category page, the business sections. Restyling `.aitsq-eyebrow`
   at its own (0,1,0) would redesign all of them. `.v2x .dir-faq-sect .aitsq-…`
   is (0,3,0), reaches only the listing bands this task covers, and loads after
   `faq.css` — settled on both axes.

   LAYOUT IS UNTOUCHED. This head is a 350px sticky aside beside the accordion,
   with a contact card under it. It is not a showcase head and must not become
   one: centring a heading in a 350px column, or stepping it to 42.6px, gives
   four wrapped lines and pushes the contact card off the fold. What is
   corrected is the LANGUAGE — the pill, which at 11.5px UPPERCASE tracked
   +.1em is the one element on these pages still speaking the old design.

   The size stays at its own clamp for the same reason: 42.6px belongs to a
   full-width heading, not to a third of a column. */
/* `line-height` for the same reason the other two carry it — see §HEAD-PILL.
   No `border` needed: `faq.css` already gives this pill a real 1px one, so it
   reaches 36px on the line-height alone. */
.v2x .dir-faq-sect .aitsq-eyebrow {
    gap: 8px;
    padding: 7px 16px 7px 13px;
    border-color: #CFE0FA;
    color: #1B5BB5;
    font-size: 12.5px;
    font-weight: 700;
    line-height: 20px;
    letter-spacing: -.005em;
    text-transform: none;
}

.v2x .dir-faq-sect .aitsq-eyebrow svg { width: 14px; height: 14px; color: var(--brand-blue); }

body.theme-dark .v2x .dir-faq-sect .aitsq-eyebrow svg { color: var(--brand-blue-light); }

/* The eyebrow TEXT needs the same twin, and at the same weight: the light
   rule above is (0,3,0) and outranks faq.css's `body.theme-dark
   .aitsq-eyebrow` (0,2,1), so that twin never reached this band. #1B5BB5
   measured 2.49:1 here. */
body.theme-dark .v2x .dir-faq-sect .aitsq-eyebrow {
    color: var(--dk-accent-ink, #8CBEFA);
    border-color: var(--dk-accent-line, rgba(96,164,245,.28));
}

/* Weight and tracking join the other two heads; the SIZE does not, per above. */
.v2x .dir-faq-sect .aitsq-title {
    font-weight: 700;
    letter-spacing: -.03em;
    line-height: 1.12;
}

/* ── §HEAD-RESP) Narrow columns ───────────────────────────────────────────
   The base sheets step the sub down to 14px below 820px, at (0,2,0) inside a
   media query — which out-ranks nothing here, but lands LATER in the cascade
   than this file only if this file is not loaded, and it always is. Restated
   at the same breakpoint so the two bands stay level: 15px, the homepage's
   own phone step, rather than back to 14px. */
@media (max-width: 820px) {
    .v2x .dir-cat-sub,
    .v2x .dir-why-sub { font-size: 15px; }

    .v2x .dir-why-head { margin-bottom: 24px; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §WHYPRM — the premium card language on the "What is … ?" band  (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   §CATPRM did this for `.dir-cat-card`. This is the same port for the OTHER
   card on these pages — the six-up promise row under the Why heading, whose
   homepage twin is `.hpx-ig-feature .hpx-igcard`. Measured side by side, the
   gap was the same gap, one row down:

                          homepage `.hpx-igcard`      listing `.dir-why-card`
     diagonal wash        rgba(hue, .075 → .055)      none
     corner bloom         rgba(hue, .20), 220x200     an OPAQUE pale hex
     card edge            rgba(hue, .20)              `--dwc-line`, a pale hex
     resting shadow       4 layers, tone-tinted       3 layers, neutral grey
     hover ring           masked CONIC, 3.6s sweep    a static linear gradient
     title weight         740                         700

   ── The resting state was the whole problem ───────────────────────────────
   BOTH pseudo-elements on this card sit at `opacity: 0` until `:hover`:
   `::before` is a top radial wash in `--dwc-bg` that fades in at .9, and
   `::after` is a masked gradient ring that fades in at 1. That is precisely
   the pattern §HPXP was written to retire, and its note says why better than
   this one can:

     · at REST the band is white cards on a white ground, separated by a grey
       hairline and nothing else — the colour system exists but is invisible
       until a pointer moves through it;
     · on TOUCH, where hover never arrives, the resting state is the ONLY
       state those visitors ever see, so the entire tone palette is
       desktop-only by accident.

   So the wash moves to rest and hover gets somewhere better to go.

   ── Slot map ──────────────────────────────────────────────────────────────
   Easier than `.dir-cat-card`'s, and worth recording because it is the reason
   this block is shorter than §CATPRM:

     ::before   was the hover-only wash    → RETIRED, superseded by the
                                             resting background layers
     ::after    was a static gradient ring → becomes the ANIMATED CONIC orbit

   `::after` is already a masked ring with `padding: 1.5px` and the same
   `mask-composite` pair the orbit needs, so it is upgraded in place rather
   than rebuilt — no geometry to unwind, unlike §CATPRM-ORBIT's slot.

   ── The lead card is excluded from the surface, not from the ring ─────────
   `.is-lead` is a solid brand gradient with white text. A tone wash on top of
   it would muddy the gradient and a tone edge would be invisible, so every
   surface rule below carries `:not(.is-lead)` — the same exclusion §BANDS-WHY
   already documents. The RING is not excluded: its specular core is white,
   which is exactly what reads on a saturated card, and the homepage's own
   lead card orbits too.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §WHYPRM-RGB) The hue as a triplet ────────────────────────────────────
   `--dwc-*` are flat composited hexes for the same reason `--dtc-*` are, so
   this card needs the same missing primitive. Values are §HPXP-RGB's
   verbatim, which is also what §CATPRM-RGB uses — a violet promise card and a
   violet category tile are one violet by construction.

   The base sheet declares six tones for this card; the eleven below cost
   nothing and mean a seventh added upstream does not arrive unpainted.

   ⚠️ The fallback on the base selector is load-bearing — an undefined triplet
   makes every `rgba(var(--dwc-rgb), …)` INVALID, and an invalid declaration is
   dropped silently. Same trap §CATPRM-RGB records. */
.v2x .dir-why-card                           { --dwc-rgb: 54, 128, 237; }
.v2x .dir-why-card[data-tone="blue"]         { --dwc-rgb: 54, 128, 237; }
.v2x .dir-why-card[data-tone="violet"]       { --dwc-rgb: 139, 92, 246; }
.v2x .dir-why-card[data-tone="emerald"]      { --dwc-rgb: 16, 185, 129; }
.v2x .dir-why-card[data-tone="amber"]        { --dwc-rgb: 245, 158, 11; }
.v2x .dir-why-card[data-tone="rose"]         { --dwc-rgb: 244, 63, 94; }
.v2x .dir-why-card[data-tone="cyan"]         { --dwc-rgb: 34, 184, 217; }
.v2x .dir-why-card[data-tone="indigo"]       { --dwc-rgb: 99, 102, 241; }
.v2x .dir-why-card[data-tone="orange"]       { --dwc-rgb: 249, 115, 22; }
.v2x .dir-why-card[data-tone="teal"]         { --dwc-rgb: 20, 184, 166; }
.v2x .dir-why-card[data-tone="pink"]         { --dwc-rgb: 236, 72, 153; }
.v2x .dir-why-card[data-tone="slate"]        { --dwc-rgb: 100, 116, 139; }

/* The ramp is §CATPRM's, REUSED rather than re-declared under a second name.
   `--dcp-*` is not a `.dir-cat-card` token — it is this file's one alpha ramp,
   and two cards on the same page tinted from two ramps is how a dark-mode
   retune ends up half-applied. */
.v2x .dir-why-card {
    --dcp-a1: .075;
    --dcp-a2: .055;
    --dcp-edge: .20;
    --dcp-bloom: .20;
}

body.theme-dark .v2x .dir-why-card {
    --dcp-a1: .13;
    --dcp-a2: .09;
    --dcp-edge: .30;
    --dcp-bloom: .17;
}

/* ── §WHYPRM-WASH) The surface ────────────────────────────────────────────
   Two layers, bloom over wash, exactly as §HPXP-WASH stacks them for this
   host. `background-image` only, never the `background` shorthand — the base
   rule sets the card's colour with one, and a shorthand here would drop it.

   The bloom's centre sits OUTSIDE the box (`calc(100% + 30px)`, negative top)
   so what lands on the card is the falloff rather than a circle drawn on it.

   ⚠️ Both gradients fade to `rgba(same-hue, 0)`, NOT to `transparent`. The
   rule this replaces faded `--dwc-bg` to the `transparent` KEYWORD, which in
   sRGB interpolates through transparent BLACK and leaves a grey fringe across
   the card — visible on the pale tones, and one of the two reasons this row
   read heavier than the homepage's. The other was that its bloom was an
   OPAQUE hex where the homepage's is `rgba(hue, .20)`. */
.v2x .dir-why-card:not(.is-lead) {
    background-image:
        radial-gradient(220px 200px at calc(100% + 30px) -54px,
            rgba(var(--dwc-rgb), var(--dcp-bloom)) 0%,
            rgba(var(--dwc-rgb), 0) 72%),
        linear-gradient(152deg,
            rgba(var(--dwc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dwc-rgb), 0)             48%,
            rgba(var(--dwc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 0 0, 0 0;
    background-size: 100% 100%, 100% 100%;
    border-color: rgba(var(--dwc-rgb), var(--dcp-edge-now, var(--dcp-edge)));
}

/* ⚠️ THE DARK TWIN IS THE RULE, NOT AN OPTIMISATION — see the same note in
   §CATPRM-WASH. `body.theme-dark .v2x .dir-why-card` in the base sheet is
   (0,3,1) and sets the surface with the `background` SHORTHAND, so it both
   out-ranks the (0,3,0) above AND resets `background-image` to none. Without
   this the whole band loses its colour after dark, and only after dark. */
body.theme-dark .v2x .dir-why-card:not(.is-lead) {
    background-image:
        radial-gradient(220px 200px at calc(100% + 30px) -54px,
            rgba(var(--dwc-rgb), var(--dcp-bloom)) 0%,
            rgba(var(--dwc-rgb), 0) 72%),
        linear-gradient(152deg,
            rgba(var(--dwc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dwc-rgb), 0)             48%,
            rgba(var(--dwc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 0 0, 0 0;
    background-size: 100% 100%, 100% 100%;
    border-color: rgba(var(--dwc-rgb), var(--dcp-edge-now, var(--dcp-edge)));
}

/* The hover-only wash is retired. It is superseded by the resting layers
   above, and leaving it live would fade a SECOND tint in on top of them —
   which is the muddiness this block exists to remove, arriving on hover
   instead of at rest.

   `display: none` rather than deleting the base rule: that rule lives in
   three 206KB sheets and this file cannot edit them. */
.v2x .dir-why-card::before,
.v2x .dir-why-card:hover::before { display: none; }

/* ── §WHYPRM-ORBIT) The static ring becomes a sweep ───────────────────────
   The slot already holds a masked ring with the right mask pair and the right
   padding; only the PAINT and the animation change. 3.6s is the speed §HPXP
   assigns this exact host — slower than the category tile's 3.4s because the
   card is bigger and has more border to cross.

   `--dcp-spin` and `dir-cat-orbit` are §CATPRM-ORBIT's, reused deliberately:
   one registered property and one keyframe for both cards on the page. */
.v2x .dir-why-card {
    --dcp-hot: 255, 255, 255;
    --dcp-o1: .5;
    --dcp-o2: .88;
    --dcp-o3: 1;
}

body.theme-dark .v2x .dir-why-card { --dcp-o1: .66; --dcp-o2: 1; }

@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
    .v2x .dir-why-card::after {
        padding: var(--dcp-t, 1.4px);
        background: conic-gradient(from var(--dcp-spin),
            rgba(var(--dwc-rgb), 0)                 0deg,
            rgba(var(--dwc-rgb), var(--dcp-o1))    30deg,
            rgba(var(--dcp-hot),  var(--dcp-o3))   52deg,
            rgba(var(--dwc-rgb), var(--dcp-o2))    74deg,
            rgba(var(--dwc-rgb), 0)               118deg,
            rgba(var(--dwc-rgb), 0)               180deg,
            rgba(var(--dwc-rgb), var(--dcp-o1))   210deg,
            rgba(var(--dcp-hot),  var(--dcp-o3))  232deg,
            rgba(var(--dwc-rgb), var(--dcp-o2))   254deg,
            rgba(var(--dwc-rgb), 0)               298deg,
            rgba(var(--dwc-rgb), 0)               360deg);
        --dcp-spin: 0deg;
        animation: dir-cat-orbit 3.6s linear infinite;
        /* A paused animation does not tick, so six idle cards cost nothing —
           and re-entering RESUMES the sweep rather than snapping the comet
           back to 0deg under the pointer. */
        animation-play-state: paused;
    }

    /* Keyboard parity, deliberately outside the hover query — these cards are
       not links, so this only fires where a vertical makes one focusable. */
    .v2x .dir-why-card:focus-within::after { opacity: 1; animation-play-state: running; }

    @media (hover: hover) {
        .v2x .dir-why-card:hover::after { animation-play-state: running; }
    }
}

/* ── §WHYPRM-LIFT) Depth, tone-tinted ─────────────────────────────────────
   §HPXP-LIFT's four layers. The tone in the lift shadow is what makes a
   violet card cast a violet-tinged shadow rather than the same grey as its
   emerald neighbour; the base sheet's third layer is neutral
   `rgba(16,24,40,.22)` on all six. */
.v2x .dir-why-card:not(.is-lead) {
    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(--dwc-rgb), .40),
        0 10px 24px -20px rgba(16, 24, 40, .26);
}

body.theme-dark .v2x .dir-why-card:not(.is-lead) {
    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(--dwc-rgb), .34),
        0 10px 24px -20px rgba(0, 0, 0, .78);
}

/* ⚠️ THE EDGE STEPS UP THROUGH A CUSTOM PROPERTY, and it has to. The base
   hover rule sets `border-color: transparent` — it was written for a card
   whose hover state was a full gradient ring, where an edge underneath would
   double it. With a tone edge at rest that declaration erases the border at
   the moment the card is being pointed at. `--dcp-edge-now` is read inside
   the surface rules above, which are (0,3,0) and (0,4,1); a hover rule on
   `border-color` itself would be (0,3,1) — enough to beat the light surface,
   not the dark one. Same reasoning as §CATPRM-BAR, same fix. */
@media (hover: hover) {
    .v2x .dir-why-card:not(.is-lead):hover { --dcp-edge-now: calc(var(--dcp-edge) + .22); }

    .v2x .dir-why-card:not(.is-lead):hover {
        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(--dwc-rgb), .52),
            0 14px 30px -22px rgba(16, 24, 40, .28);
    }

    /* (0,4,2), because the RESTING dark shadow above is (0,4,1). */
    body.theme-dark .v2x .dir-why-card:not(.is-lead):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(--dwc-rgb), .46),
            0 14px 30px -22px rgba(0, 0, 0, .82);
    }
}

/* ── §WHYPRM-TYPE) The card title ─────────────────────────────────────────
   740, the homepage's. `.hpx-igcard-title` and this element are the same
   16px tone-coloured line and were two weights apart. §BANDS-WHY already owns
   this element's COLOUR; only the weight is added here, beside it in spirit
   but in this block because it is a homepage-parity number, not a fix. */
.v2x .dir-why-card .dir-why-title-c { font-weight: 740; }

/* ── §WHYPRM-RESP) Phone ──────────────────────────────────────────────────
   The bloom is sized in PIXELS so it does not shrink with the card. At this
   width the six-up row is one or two columns and a 220px bloom floods the
   whole surface instead of lighting a corner. Both themes, because a
   `background-image` at (0,3,0) inside a media query still loses to the base
   sheet's (0,3,1) dark shorthand — a media query changes no specificity. */
@media (max-width: 767.98px) {
    .v2x .dir-why-card::after { --dcp-t: 1.2px; }

    .v2x .dir-why-card:not(.is-lead),
    body.theme-dark .v2x .dir-why-card:not(.is-lead) {
        background-image:
            radial-gradient(150px 140px at calc(100% + 20px) -38px,
                rgba(var(--dwc-rgb), var(--dcp-bloom)) 0%,
                rgba(var(--dwc-rgb), 0) 72%),
            linear-gradient(152deg,
                rgba(var(--dwc-rgb), var(--dcp-a1)) 0%,
                rgba(var(--dwc-rgb), 0)             48%,
                rgba(var(--dwc-rgb), var(--dcp-a2)) 100%);
    }
}

/* ── §WHYPRM-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. The wash, the bloom and the
   tinted edge are what those visitors were getting nothing of before, and all
   three remain. */
@media (hover: none) {
    .v2x .dir-why-card::after { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
    .v2x .dir-why-card::after,
    .v2x .dir-why-card:hover::after { animation: none !important; }
}

@media (forced-colors: active) {
    .v2x .dir-why-card::after { display: none; }

    .v2x .dir-why-card:not(.is-lead),
    body.theme-dark .v2x .dir-why-card:not(.is-lead) {
        background-image: none;
        border: 1px solid CanvasText;
        box-shadow: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §PILL — the eyebrow's MARK, and the hero badge          (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   §HEAD-PILL already brought the eyebrow's box to the homepage's showcase
   recipe — 12.5px on `#1B5BB5`, `7px 16px 7px 13px`, 8px gap, a `#CFE0FA`
   hairline. Measured against `.hpx-eyebrow` on `/`, every one of those
   numbers now matches on all fifty-one URLs.

   What did NOT match is what is drawn inside it. The homepage pill carries a
   FILLED FOUR-POINT SPARKLE (`.hpx-eyebrow-spark`, `fill="currentColor"`).
   Every listing pill carries a STROKED PLUS SIGN — `M12 5v14M5 12h14` — and
   it is the same path in all forty-two of them, across all twenty verticals.
   Two pills built to identical measurements still read as two designs when
   one says "✦" and the other says "+".

   ── Why this is CSS and not a blade edit ──────────────────────────────────
   The plus is inline markup in ~30 blades. Swapping it there is thirty edits
   for one glyph, and thirty chances for the next redesign to leave one
   behind — which is exactly the failure this file exists to prevent, and
   exactly what `hh-orbit.blade.php`'s own header records happening to the
   hero arcs. A MASK is one rule, reaches every pill, and follows the brand
   token in both themes the way a baked-in fill never could. §STAR uses the
   same technique for the heading flanks, with the same encoding.

   The mark is decoration on an `aria-hidden` glyph, so moving it out of the
   DOM costs nothing an assistive reader was using.

   ⚠️ THE FAQ PILL KEEPS ITS OWN ICON. `.aitsq-eyebrow` draws a question mark
   in a circle, which is not decoration — it says what the section is, and the
   homepage has no FAQ band to match it against. Only its BOX was brought into
   the language (§HEAD-FAQ); the glyph is left alone.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §PILL-SPARK) The plus becomes a sparkle ──────────────────────────────
   `> svg` and not `svg`, so a glyph nested deeper in a future pill variant is
   not caught by accident. Hidden rather than removed: the markup still ships
   it, and a `display: none` flex item leaves the pill with exactly two
   children — the mask below and the label — which is the homepage's own
   count, so the 8px gap lands in the same place. */
.v2x .dir-cat-eyebrow > svg,
.v2x .dir-why-eyebrow > svg { display: none; }

.v2x .dir-cat-eyebrow::before,
.v2x .dir-why-eyebrow::before {
    content: '';
    width: 14px; height: 14px;
    flex: 0 0 auto;
    /* The BRAND blue, one step lighter than the words beside it — the same
       split §HEAD-PILL gives the glyph it is replacing, and what stops the
       pill reading as a solid block. */
    background-color: var(--brand-blue);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.6l1.55 5.34a3.4 3.4 0 0 0 2.31 2.31L21.4 12l-5.34 1.55a3.4 3.4 0 0 0-2.31 2.31L12 21.4l-1.55-5.34a3.4 3.4 0 0 0-2.31-2.31L2.6 12l5.34-1.55a3.4 3.4 0 0 0 2.31-2.31L12 2.6z'/%3E%3C/svg%3E") no-repeat center;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.6l1.55 5.34a3.4 3.4 0 0 0 2.31 2.31L21.4 12l-5.34 1.55a3.4 3.4 0 0 0-2.31 2.31L12 21.4l-1.55-5.34a3.4 3.4 0 0 0-2.31-2.31L2.6 12l5.34-1.55a3.4 3.4 0 0 0 2.31-2.31L12 2.6z'/%3E%3C/svg%3E") no-repeat center;
    -webkit-mask-size: contain;
            mask-size: contain;
}

body.theme-dark .v2x .dir-cat-eyebrow::before,
body.theme-dark .v2x .dir-why-eyebrow::before { background-color: var(--brand-blue-light); }

/* ⚠️ Where `mask` is unsupported the pseudo would paint a 14px SOLID BLUE
   SQUARE where the mark belongs — worse than the plus it replaced. The
   @supports gate restores the inline glyph instead, so those browsers keep
   exactly what they have today rather than gaining a bug. */
@supports not ((-webkit-mask-image: url('')) or (mask-image: url(''))) {
    .v2x .dir-cat-eyebrow > svg,
    .v2x .dir-why-eyebrow > svg { display: block; }

    .v2x .dir-cat-eyebrow::before,
    .v2x .dir-why-eyebrow::before { display: none; }
}

@media (forced-colors: active) {
    /* A mask in a forced palette is a system-coloured block. The inline glyph
       is a real vector and survives, so hand the slot back to it. */
    .v2x .dir-cat-eyebrow > svg,
    .v2x .dir-why-eyebrow > svg { display: block; }

    .v2x .dir-cat-eyebrow::before,
    .v2x .dir-why-eyebrow::before { display: none; }
}

/* ── §PILL-HERO) The hero badge ───────────────────────────────────────────
   `.dir-hh-badge` against `.hpx-hero-badge`, measured at 2560px. Same idea,
   seven numbers apart — enough that the two sit at different heights (40px
   against 42px) when a visitor moves between the homepage and a listing:

     weight     600            → 640
     padding    8px 18px 8px 10px → 9px 19px 9px 12px
     surface    #fff @ .90     → #fff @ .86
     ink        #0F0F12        → #0B1220
     chip fill  #EAF1FE        → #EAF2FD
     chip ring  #D6E4FC        → #CFE0FA
     glyph      14px, #3680ED  → 15px, #1B5BB5

   The ink and the two chip values are the interesting ones: `#0F0F12` is a
   NEUTRAL near-black and `#0B1220` is the blue-black this design uses
   everywhere else, and the chip was mixing a second pale blue into a palette
   that already has one. None of it is visible on its own; together it is why
   the badge looked borrowed rather than shared.

   ⚠️ EVERY COLOUR BELOW IS SCOPED TO LIGHT, and the first version of this
   block was not — which broke dark mode in the way this codebase keeps
   warning itself about. The base rules do NOT hard-code these values; they
   read `--v2-ink`, `--v2-accent-soft` and `--brand-blue`, tokens that already
   flip at night, and the dark twins only restate what the tokens cannot
   carry. Replacing a token with a hex therefore does not "override the light
   value" — it overrides BOTH, and `#0B1220` ink on the `rgba(28,31,42,.78)`
   surface the dark twin paints is near-black on near-black. Measured on a
   real dark load before this guard was added.

   GEOMETRY and WEIGHT stay unscoped: padding and font-weight mean the same
   thing in both themes, and they are what the 40px-against-42px difference
   was actually made of. */
.v2x .dir-hh-badge {
    padding: 9px 19px 9px 12px;
    font-weight: 640;
}

.v2x .dir-hh-badge-chip svg { width: 15px; height: 15px; }

body:not(.theme-dark) .v2x .dir-hh-badge {
    background-color: rgba(255, 255, 255, .86);
    color: #0B1220;
}

body:not(.theme-dark) .v2x .dir-hh-badge-chip {
    background-color: #EAF2FD;
    box-shadow: inset 0 0 0 1px #CFE0FA;
}

body:not(.theme-dark) .v2x .dir-hh-badge-chip svg { color: #1B5BB5; }


/* ═══════════════════════════════════════════════════════════════════════════
   §GCOL — the results grid stacks on the `no-l` / `no-r` pages too
   (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   The three base sheets set the listing grid and its two modifiers like this:

     .v2x .dir-g            { grid-template-columns: 236px minmax(0,1fr) 268px }  (0,2,0)
     .v2x .dir-g.no-l       { grid-template-columns: minmax(0,1fr) 268px }        (0,3,0)
     .v2x .dir-g.no-r       { grid-template-columns: 236px minmax(0,1fr) }        (0,3,0)
     .v2x .dir-g.no-l.no-r  { grid-template-columns: 1fr }                        (0,4,0)

   and then collapse it in a media query written against the BASE selector
   only:

     @media (max-width:1500px){ .v2x .dir-g{ grid-template-columns:1fr } }        (0,2,0)

   (0,2,0) loses to (0,3,0), so **a page carrying a modifier never stacks at
   any width.** The 236px filter rail keeps its fixed track and every pixel
   the viewport loses comes out of the results column.

   Measured at a 376px viewport on /best: `grid-template-columns` resolves to
   `236px 86px` and `.dir-main` — the entire results area — is 86px wide. The
   count label inside it renders "best-of lists found" at 39px across three
   lines. /ai-directory, which has no modifier, correctly reports a single
   340px column at the same width.

   Five verticals ship `.no-r` — /alternatives, /best, /collections, /compare,
   /use-cases — the same five whose source tables are empty and which
   therefore render no trending rail. Their category and tag pages inherit it.

   ── Why 1024px and not the base's 1500px ──────────────────────────────────
   The base stacks at 1500 because the FULL layout spends 504px on two rails
   (236 + 268) and the middle column is what is left. A `no-r` page spends
   236px on one rail, so at 1400px it still has ~1140px of results and the
   two-column layout is the better one — stacking it there would throw away
   width on exactly the pages that have the least content to show.

   1024px is where a 236px rail stops being affordable: below it the results
   column drops under ~750px and the filters are better above the grid than
   beside it. Both the base sheets and §CATBAND-RESP already treat 1024 as the
   tablet boundary, so this introduces no new number.

   `.no-l.no-r` is listed for completeness — it is already `1fr`, and naming
   it here means a future edit to the base cannot quietly reintroduce the bug
   for that combination. */
@media (max-width: 1024px) {
    .v2x .dir-g,
    .v2x .dir-g.no-l,
    .v2x .dir-g.no-r,
    .v2x .dir-g.no-l.no-r {
        grid-template-columns: 1fr;
        gap: 18px;
    }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §SHELL — the listing shell is capped on the `fullWidth` pages too
   (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   `partials/shell-cap.blade.php` is the site's ONE width dial. It pins every
   public surface to `--seo-shell-max` (1920px) with a `--seo-shell-pad`
   gutter (24px, stepping to 20/14/11) — but it does that by listing the
   BOOTSTRAP CONTAINER CLASSES:

     body.public-page .container, .container-sm, … , .container-xxl
       { max-width: var(--seo-shell-max); padding-inline: …; margin-inline: auto }

   `.container-fluid` is deliberately absent — a full-bleed page wants its own
   bands to reach the edge. And `layouts/public.blade.php:334` picks between
   the two, on a `$fullWidth` flag the page's Renders… concern sets.

   So a vertical that opts into `fullWidth` renders the ENTIRE `.v2x` listing
   shell with no cap at all. Measured at a 2546px viewport:

                         /ai-directory      /events
       .v2x               1872px @ l=337     2546px @ l=0
       .dir-g             1864px             2538px
       results column     1320px             1994px
       .dir-cat-rail      1872px             2546px
       .dir-why-grid      1872px             2546px

   Every band this file has spent the day redesigning renders 674px wider on
   those pages, with the filter rail against the left window edge and the
   cards stretched to ~2000px — while the hero's own stat deck stays capped at
   1120px, which is what makes it read as broken rather than as wide.

   THREE of the twenty verticals are affected: /events, /guides and
   /cheat-sheet. (/deals sets `fullWidth` in `RendersDealPage` too but renders
   through a path that still lands in a `.container`, so it measures 1858px
   like the rest — do not "fix" it.) Their category and tag pages inherit the
   shell.

   ── Reading the tokens, not repeating the numbers ─────────────────────────
   shell-cap's own note asks for exactly this: "Module page-shells that sit in
   a `fullWidth` layout … read these two tokens instead of hardcoding their
   own numbers. Keep it that way: one dial, every surface." So this rule reads
   `--seo-shell-max` and `--seo-shell-pad` rather than restating 1920/24 —
   raise the dial in that partial and these three pages follow.

   The fallbacks matter: the tokens are declared on `body.public-page`, and a
   listing rendered under any other body class would otherwise lose its cap
   entirely rather than fall back to the shipped width.

   ⚠️ Scoped to `.container-fluid`, which is the ONLY thing that distinguishes
   these pages — the `.v2x` shell, its modifiers and its markup are identical
   on all twenty. The seventeen capped verticals never match this selector, so
   their gutter is untouched; verified at 1858px before and after at a 1906px
   viewport. */
.container-fluid .v2x {
    max-width: var(--seo-shell-max, 1920px);
    margin-inline: auto;
    padding-inline: max(var(--seo-shell-pad, 24px), env(safe-area-inset-left));
}


/* ═══════════════════════════════════════════════════════════════════════════
   §FX — the filter rail: no inner scrollbar, and a head that fits
   (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   Two separate faults in `.dir-fx`, both only above 1500px — the media block
   at that breakpoint already unsets the first one, which is why neither shows
   on a laptop.

   ── 1 · THE PANEL SCROLLED INSIDE ITSELF ──────────────────────────────────
   The base sheet pins the rail and caps the box to the viewport:

     .v2x .dir-fx, .v2x .dir-tr { position: sticky; top: 130px; align-self: start }
     .v2x .dir-fx-box { max-height: calc(100vh - 130px); overflow: hidden }
     .v2x .dir-fx-box { overflow-y: auto }

   Measured on /ai-directory at 2546px: the box is 1141px tall around 1397px
   of content, so the last two groups sit behind a 6px scrollbar. A filter
   list is a scan-and-tick surface — putting it in a second scroller means a
   reader has to find the inner scrollport before they can reach "Categories",
   and on a trackpad the page and the panel fight for the same gesture.

   So the cap comes off and the panel renders at its full height.

   ⚠️ STICKY HAS TO GO WITH IT, and this is the part that is easy to get
   wrong. A `position: sticky` element TALLER than the viewport pins its TOP
   at `top: 130px` and never scrolls its own bottom into view — the groups
   below the fold become permanently unreachable, which is strictly worse
   than the scrollbar this block is removing. Full height and sticky are
   mutually exclusive; the panel scrolls with the page now.

   `.dir-tr` KEEPS its sticky — the trending rail is a fixed six rows and
   always fits, which is why the base rule pairs them and why this one does
   not. Do not merge the two selectors back together.

   The head's own `position: sticky; top: 0` goes too. With no scrolling
   ancestor left, its nearest scrollport is the PAGE, so it would detach and
   float over the results as the reader scrolls. The 1500px block already
   sets it static for the same reason. */
.v2x .dir-fx { position: static; }

.v2x .dir-fx-box {
    max-height: none;
    overflow: visible;
}

.v2x .dir-fx-head {
    position: static;
    border-bottom-color: var(--v2-line);
}

body.theme-dark .v2x .dir-fx-head { border-bottom-color: var(--dk-line,rgba(255,255,255,.10)); }

/* ── §FX-HEAD) "Filter Tools" against "Clear all" ─────────────────────────
   The head is a 196px flex row holding, in order: a 24px icon chip, the
   label, a count badge, and the Clear button. With a filter active that is

     24 + 8 + 78 + 8 + 20  =  138   (title)
                    + 8 + 76  =  84   (clear)
                              = 222   against 196 available

   26px short. `.dir-fx-title` carries `min-width: 0`, so it absorbs the
   deficit — and the badge inside it, despite `flex: 0 0 auto` and a 20px
   `min-width`, renders **8x8** with its digit clipped. Measured, not
   inferred: `getBoundingClientRect()` returns 8x8 while `getComputedStyle`
   still reports 20px.

   The head wraps instead. When the row does not fit, "Clear all" drops to a
   second line and everything keeps its full size; when no filter is active
   the button is already icon-only (`.is-dim` zeroes its font-size) and the
   row fits on one line as before. Nothing truncates and no affordance is
   lost — the alternative, ellipsising "Filter Tools" to protect the badge,
   trades a clipped number for a clipped word.

   `min-width: max-content` on the title is what FORCES the wrap: without it
   the title keeps shrinking and the row never reaches its wrap point. */
.v2x .dir-fx-head { flex-wrap: wrap; row-gap: 8px; }

.v2x .dir-fx-title { min-width: max-content; }

/* ── §FX-BADGE) The count badge was stuck at 40% ──────────────────────────
   ⚠️ THE SQUEEZED BADGE WAS NEVER A FLEX PROBLEM, and the first version of
   this block treated it as one. `min-width: 20px` and `flex: 0 0 auto` were
   already set, already correct, and made no difference — because the badge
   was not being SHRUNK, it was being SCALED:

     getComputedStyle → width 20px, transform matrix(0.4, 0, 0, 0.4, 0, 0)
     offsetWidth      → 20        (the layout box is right)
     getBoundingClientRect → 8    (the PAINTED box is 40% of it)

   The base rule runs `animation: dir-fx-badge-in .35s` and the keyframes are
   `0% { transform: scale(.4); opacity: 0 } → 100% { scale(1); opacity: 1 }`.
   Read back live, that animation reports `playState: "running"` with
   `currentTime: 0` — permanently pinned on its first frame, so the badge
   paints at 40% and its digit is clipped. It stays there through reloads and
   across seconds of sampling; no `content-visibility` or `contain` on any
   ancestor explains it, and the most likely cause is Livewire re-creating the
   node on each patch and restarting the animation from zero every time.

   ⚠️ `transform: none` ALONE DOES NOT FIX THIS. An active animation's
   transform sits above normal declarations in the cascade, so the base value
   loses no matter how specific it is. The animation itself has to go — which
   is also the honest call: an entry animation that has never once been seen
   to play is not an animation, it is a rendering fault with a name.

   The sizing floors stay as documentation of the box the badge is supposed to
   occupy; they cost nothing and they are what a reader will check first. */
.v2x .dir-fx-title-badge {
    animation: none;
    transform: none;
    flex: 0 0 auto;
    min-width: 20px;
    min-height: 20px;
}


/* ═══════════════════════════════════════════════════════════════════════════
   §HHGAP — breathing room between the hero and the marquee   (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   The listing hero ends flush against the marquee. Measured from the hero's
   border box to the top of `.aitsmq` at 1906px:

       /              (.hpx-hero)   56px
       /ai-directory  (.dir-hh)      0px

   Zero. `.dir-hh` carries `padding-bottom: 8px` and nothing else, so the stat
   deck sits eight pixels above a saturated blue band running the full width
   of the window — the two read as one stuck-together block rather than as a
   hero followed by a ticker.

   The homepage's 56px does not come from the marquee: every margin between
   `.hpx-hero` and `.aitsmq` computes to 0, and the space is the hero band's
   own bottom padding. So the fix belongs on the listing hero too, not on the
   shared marquee component — `x-public.marquee` renders on `/` as well, and a
   `margin-top` there would push the homepage to 112px.

   ── The phone step is the homepage's, measured not guessed ────────────────
   Sampled across widths, `/` holds 56px down to 660px and drops to 40px by
   640 — a step at the 640px breakpoint these sheets already use (§CATBAND-RESP
   and shell-cap both key on it). Mirrored here rather than rounded to one
   number, so the two pages stay level at every width.

   ⚠️ Scoped to `.v2x .dir-hh`, which is the LISTING hero only. The homepage's
   hero is `.hpx-hero` and is not touched. The hero and the marquee are
   siblings inside `.v2x` on all twenty verticals — with only the `<link>`
   tags hh-orbit injects between them — so a margin on the hero lands exactly
   where the gap is missing. On a page that renders no marquee the same margin
   simply separates the hero from whatever follows, which is the right result
   either way. */
.v2x .dir-hh { margin-bottom: 56px; }

@media (max-width: 640px) {
    .v2x .dir-hh { margin-bottom: 40px; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §NOTR — the right-hand trending rail comes off            (2026-08-18)
   ═══════════════════════════════════════════════════════════════════════════
   `.dir-tr` is the sticky right rail — "Trending Tools" / "Top Companies" /
   "Trending now", one per vertical — and it is removed from the listing,
   category and tag pages on request.

   ── Why CSS and not the blades ────────────────────────────────────────────
   TWENTY-THREE blades render `<aside class="dir-tr">`, and each would also
   need `no-r` added to its `.dir-g` so the grid drops from three columns to
   two. Forty-six edits across twenty-three files, every one of them a chance
   to leave one behind — which is the failure mode this file exists to avoid
   and the one `hh-orbit.blade.php`'s own header records happening before.
   One rule here reaches all twenty verticals plus every category and tag page
   they own, and it is reversible by deleting this block.

   ⚠️ `display: none`, deliberately, not `visibility` or a clip. It takes the
   rail out of the ACCESSIBILITY TREE as well as the layout — a screen reader
   should not still be offered a trending list nobody can see.

   ── What this does NOT do ─────────────────────────────────────────────────
   The server still BUILDS the trending collection. On the directory that is
   `DirectoryListing.php:362`, gated on `$dir['rail']['show']` from
   `DirectorySettings` — a real toggle that would also skip the query. Each
   vertical has its own settings store and its own gate, so switching all
   fifteen off is a per-vertical data change rather than one edit; this block
   is the display half only. The queries are small (limit 5) and indexed, so
   the cost is a few rows per request, not a page-weight problem — but it IS
   work being done for markup nobody sees, and worth closing separately if the
   removal is permanent.
   ═══════════════════════════════════════════════════════════════════════════ */
.v2x .dir-tr { display: none; }

/* The grid loses its third track. `.no-r` pages are already two-column and
   `.no-l.no-r` is already one, so only the base and `.no-l` need restating —
   each at the specificity of the rule it replaces, or the (0,3,0) modifier
   would keep painting a 268px column of nothing.

   ⚠️ GATED AT min-width: 1025px, AND THE GATE IS NOT OPTIONAL. §GCOL collapses
   every variant to a single column at ≤1024px, but a media query adds NO
   specificity — so these rules, sitting later in the same file at the same
   weight, would out-rank it and hand phones back the two-column layout §GCOL
   was written to fix. Restricting them to the widths where a third column
   exists at all keeps the two blocks from ever meeting. */
@media (min-width: 1025px) {
    .v2x .dir-g { grid-template-columns: 236px minmax(0, 1fr); }
    .v2x .dir-g.no-l { grid-template-columns: minmax(0, 1fr); }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §TCPRM — the homepage's premium card surface on the TOOL card (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   §CATPRM did this for `.dir-cat-card` and §WHYPRM for `.dir-why-card`. This
   is the same port for the THIRD and last card on these pages — `.dir-tc`,
   the result tile, which is the one a visitor actually spends their time on
   and the only one still wearing the pre-§HPXP surface.

   Its own header says why it was left behind:

     "No tone wash behind — the reference reads as clean white cards; tone
      only appears in rank chip / logo tile / category / button."

   That was true of the reference AS IT WAS. The homepage has since moved the
   showcase card onto §HPXP — a resting diagonal wash, a tone-tinted hairline,
   a corner bloom and a ring that sweeps on hover — so the two drifted apart
   and the listing kept the older, flatter reading. The brief is now the
   Trending AI Tools rail, so this block closes the gap.

     homepage `.hpx-card-showcase`      listing `.dir-tc`
     ─────────────────────────────      ────────────────────────────
     background-image  152deg wash      background-image  layer 2
     .hpx-sc-edge span (top accent)     background-image  layer 1
     ::after           corner bloom     ::after           corner bloom
     ::before          orbit ring       ::before          orbit ring
     border-color      tone hairline    border-color      tone hairline

   ── ONE HOST, TWENTY VERTICALS ────────────────────────────────────────────
   Every listing renders `.dir-tc`: eighteen blades across twenty verticals,
   on the index, category and tag page of each. `_card.blade.php`'s equivalent
   on this side is not one file but eighteen, which is exactly why this is CSS
   and not markup — the same reasoning §NOTR records. No blade is touched.

   ── WHY THE EDGE IS A BACKGROUND LAYER ────────────────────────────────────
   The reference draws its top accent with `<span class="hpx-sc-edge">`,
   because on that card both pseudo-elements were already spoken for. Here
   they are free and the ring and bloom take them, so the accent has nowhere
   left to live as an element — and adding a nineteenth span across eighteen
   blades is the trade §NOTR exists to refuse. It becomes a background layer
   instead and animates as `background-size`, which is animatable; §CATPRM-BAR
   made the same move for the category tile's foot bar and lost nothing.

   ── WHAT IS DELIBERATELY NOT TOUCHED ──────────────────────────────────────
   · The podium medals on ranks 1/2/3. They are a ranked directory's own
     idea, they now match the homepage rails (which gained them the same
     day), and the tone tint underneath them is what this block restores for
     ranks 4+.
   · `.dir-tc.is-featured` on /deals, which paints an amber wash at (0,3,0)
     in public-deals.css. It out-ranks the (0,2,0) wash below on purpose —
     a featured deal is meant to read amber, not as its tone-cycle colour.
     Its bloom and ring still come through, so it gains the depth without
     losing the flag.
   · Every text, pill, rating and button rule. They were ported from
     `.hpx-sc-*` when the card was built and already match.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §TCPRM-RGB) The hue as a triplet ─────────────────────────────────────
   The card cycles EIGHT tones by position — `:nth-child(8n+k)` in the base
   sheet — and carries no `data-tone`, so unlike §CATPRM there is no attribute
   to key on. The triplets below repeat that cycle exactly, taking each hue
   from the START of the slot's own `--dtc-solid` gradient, which is the
   saturated mid of the pair the slot already uses. A violet card therefore
   washes, blooms and orbits in the violet it already paints its button with,
   rather than in a second violet chosen here.

   ⚠️ THE FALLBACK ON THE BASE SELECTOR IS LOAD-BEARING. An undefined triplet
   makes every `rgba(var(--dtc-rgb), …)` below an INVALID declaration, and an
   invalid declaration is dropped SILENTLY — wash, bloom, edge and ring would
   simply not exist, with nothing in the output to say why. Same trap
   §CATPRM-RGB and §HPXP-RGB both record. The base rule below IS that
   fallback: it is `8n+1`'s value written unqualified, so slot 1 and any
   future ninth slot both resolve. */
.v2x .dir-tc                    { --dtc-rgb:  54, 128, 237; }
.v2x .dir-tc:nth-child(8n+2)    { --dtc-rgb: 139,  92, 246; }
.v2x .dir-tc:nth-child(8n+3)    { --dtc-rgb:  16, 185, 129; }
.v2x .dir-tc:nth-child(8n+4)    { --dtc-rgb: 234,  88,  12; }
.v2x .dir-tc:nth-child(8n+5)    { --dtc-rgb: 236,  72, 153; }
.v2x .dir-tc:nth-child(8n+6)    { --dtc-rgb:   6, 182, 212; }
.v2x .dir-tc:nth-child(8n+7)    { --dtc-rgb:  99, 102, 241; }
.v2x .dir-tc:nth-child(8n+8)    { --dtc-rgb: 245, 158,  11; }

/* The ramp, REUSED from §CATPRM rather than re-declared under a second name —
   the same call §WHYPRM made. Four numbers in light, four in dark, and every
   layer below reads them. A violet tile in the categories band and a violet
   card in the results grid are then lit to the same depth by construction,
   not by two sets of numbers that happen to agree today. */
.v2x .dir-tc {
    --dcp-a1: .075;     /* wash, the lit corner */
    --dcp-a2: .055;     /* wash, the far corner */
    --dcp-edge: .20;    /* the tinted hairline */
    --dcp-bloom: .20;   /* the corner bloom's hot end */

    /* The comet, §CATPRM-ORBIT's values. */
    --dcp-hot: 255, 255, 255;
    --dcp-o1: .5;
    --dcp-o2: .88;
    --dcp-o3: 1;
}

/* Dark: the card sits at rgba(28,31,42,.92) over a near-black page, so it is
   LIGHTER than the ground 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 .v2x .dir-tc {
    --dcp-a1: .13;
    --dcp-a2: .09;
    --dcp-edge: .30;
    --dcp-bloom: .17;
    --dcp-o1: .66;
    --dcp-o2: 1;
}


/* ── §TCPRM-WASH) The surface ─────────────────────────────────────────────
   TWO LAYERS, topmost first: the top accent bar, then the diagonal wash.

   ⚠️ `background-image` ONLY — never the `background` shorthand. The base
   sheet sets the card's COLOUR with a shorthand (`background:#fff`), and a
   shorthand here would reset that to transparent and let the page ground show
   through every card on all fifty-one URLs.

   The 152deg angle, the 48% dead stop and the fade to `rgba(same-hue, 0)`
   rather than the `transparent` KEYWORD are §HPXP-WASH's, and 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.

   The accent bar is 3px, runs transparent → tone → transparent so it reads as
   light rather than as a rule, and is sized to nothing across at rest —
   `0 3px`, so `background-size` has somewhere to animate FROM. §TCPRM-STATE
   grows it. `background-position: 50% 0` keeps it growing from the centre out,
   which is what `transform: scaleX()` does on the reference's span. */
.v2x .dir-tc {
    background-image:
        /* 1 · the top accent bar */
        linear-gradient(90deg,
            rgba(var(--dtc-rgb), 0) 0%,
            rgba(var(--dtc-rgb), .85) 50%,
            rgba(var(--dtc-rgb), 0) 100%),
        /* 2 · the diagonal wash */
        linear-gradient(152deg,
            rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dtc-rgb), 0)             48%,
            rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 50% 0, 0 0;
    background-size: var(--dcp-bar-w, 0) 3px, 100% 100%;
    border-color: rgba(var(--dtc-rgb), var(--dcp-edge));
    transition:
        transform .26s var(--ease-out),
        border-color .2s ease,
        box-shadow .26s ease,
        background-size .32s var(--ease-out);
}

/* ⚠️ THE DARK RESTATEMENT IS NOT OPTIONAL. `body.theme-dark .v2x .dir-tc` in
   the base sheet is (0,3,1) against the (0,2,0) above AND uses the
   `background` SHORTHAND, so it out-ranks the wash and resets
   `background-image` to none in one move. Without this rule the whole colour
   system silently disappears after dark, and only after dark — which is the
   kind of bug that ships, and did ship once already on the category rail.
   See [[dark-background-shorthand-trap]].

   `background-color` is deliberately NOT restated — the base's shorthand is
   still the one setting it, and duplicating the surface colour here is how a
   card ends up a different grey from the band it sits in. */
body.theme-dark .v2x .dir-tc {
    background-image:
        linear-gradient(90deg,
            rgba(var(--dtc-rgb), 0) 0%,
            rgba(var(--dtc-rgb), .85) 50%,
            rgba(var(--dtc-rgb), 0) 100%),
        linear-gradient(152deg,
            rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dtc-rgb), 0)             48%,
            rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 50% 0, 0 0;
    background-size: var(--dcp-bar-w, 0) 3px, 100% 100%;
}

/* The edge has the same problem for the same reason — the base dark rule sets
   `border-color: rgba(255,255,255,.08)` alongside that shorthand. */
body.theme-dark .v2x .dir-tc { border-color: rgba(var(--dtc-rgb), var(--dcp-edge)); }


/* ── §TCPRM-BLOOM) The corner light ───────────────────────────────────────
   §HPXP's `.hpx-card-showcase::after`, geometry unchanged: 260×230 centred at
   `calc(100% + 32px) -60px`.

   The centre sits OUTSIDE the box on purpose, 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.

   `z-index: -1` is the reference's own value and works for the same reason:
   `.dir-tc` already carries `isolation: isolate`, so -1 is below the card's
   content without escaping to the page behind it. `overflow: hidden` on the
   card clips the bloom to the rounded corners for free. */
.v2x .dir-tc::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(--dtc-rgb), var(--dcp-bloom)) 0%,
        rgba(var(--dtc-rgb), 0) 72%) no-repeat;
    opacity: .92;
    transform-origin: 100% 0;
    transition: opacity .36s ease, transform .52s cubic-bezier(.16, 1, .3, 1);
}


/* ── §TCPRM-ORBIT) The rotating light ────────────────────────────────────
   §HPXP-ORBIT at the showcase card's own speed — 4.2s, the number that block
   assigns this exact host. 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.

   THE HUE IS NOT FIXED: it reads `--dtc-rgb`, so a violet card orbits violet
   and an amber one amber. That is the difference between an effect pasted
   onto a grid and a system — the light belongs to the card it is lighting.

   `--dcp-spin` and `dir-cat-orbit` are §CATPRM-ORBIT's, declared once at the
   top of this file and REUSED rather than duplicated under a third name. A
   second `@property` for the same angle would be two registrations racing to
   define one thing. */
@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. Gating the whole ring
       means the fallback is "no ring", which is a card, rather than "a
       coloured sheet", which is a bug report.

       ⚠️ inset: 0, not the reference's inset: -1px. This card carries
       `overflow: hidden` so it can clip its own bloom, and a ring hung 1px
       outside gets sliced off on all four sides — which reads as a rendering
       fault, not as a highlight. Same call §CATPRM-ORBIT made. */
    .v2x .dir-tc::before {
        content: '';
        position: absolute;
        inset: 0;
        z-index: 2;
        pointer-events: none;
        border-radius: inherit;
        padding: var(--dcp-t, 1.5px);
        --dcp-spin: 0deg;
        background: conic-gradient(from var(--dcp-spin),
            rgba(var(--dtc-rgb), 0)                 0deg,
            rgba(var(--dtc-rgb), var(--dcp-o1))    30deg,
            rgba(var(--dcp-hot),  var(--dcp-o3))   52deg,
            rgba(var(--dtc-rgb), var(--dcp-o2))    74deg,
            rgba(var(--dtc-rgb), 0)               118deg,
            rgba(var(--dtc-rgb), 0)               180deg,
            rgba(var(--dtc-rgb), var(--dcp-o1))   210deg,
            rgba(var(--dcp-hot),  var(--dcp-o3))  232deg,
            rgba(var(--dtc-rgb), var(--dcp-o2))   254deg,
            rgba(var(--dtc-rgb), 0)               298deg,
            rgba(var(--dtc-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: dir-cat-orbit 4.2s linear infinite;
        /* A paused animation does not tick, so a grid of twenty-four 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;
    }

    /* 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 §HPXP-ORBIT and §CATPRM-ORBIT both make. */
    .v2x .dir-tc:focus-visible::before { opacity: 1; animation-play-state: running; }

    @media (hover: hover) {
        .v2x .dir-tc:hover::before { opacity: 1; animation-play-state: running; }
    }
}


/* ── §TCPRM-LIFT) Depth, tone-tinted ─────────────────────────────────────
   §HPXP-LIFT's recipe: a glass hairline along the top inner edge, a 1px
   contact shadow to seat the card against the page, a tone-tinted lift and a
   neutral one under it. The tone in the lift is what makes a violet card cast
   a violet-tinged shadow rather than the same grey as its blue neighbour — at
   .40 alpha and a -22px spread it is not a colour anyone names, it is why the
   grid looks lit.

   The base sheet's HOVER shadow is already tone-tinted (`var(--dtc-ink)`) and
   is left alone; it is the REST shadow that was neutral, and rest is where a
   grid of twenty-four cards spends its life. */
.v2x .dir-tc {
    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(--dtc-rgb), .40),
        0 10px 24px -20px rgba(16, 24, 40, .26);
}

body.theme-dark .v2x .dir-tc {
    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(--dtc-rgb), .34),
        0 10px 24px -20px rgba(0, 0, 0, .78);
}


/* ── §TCPRM-STATE) Hover and focus ───────────────────────────────────────
   Three things move: the accent bar grows from nothing to the card's width
   less its corner radius, the bloom swells from the corner it hangs off, and
   the edge steps up. The card's own `translateY(-6px)` is the base sheet's
   and is untouched — this pass is about surface.

   ⚠️ `border-color` IS RESTATED HERE RATHER THAN DRIVEN BY A VARIABLE. The
   base sheet's `.v2x .dir-tc:hover { border-color: var(--v2-line) }` is
   (0,3,0) against §TCPRM-WASH's (0,2,0), so it wins on hover no matter what
   `--dcp-edge` says and would drop the tone edge at exactly the moment the
   visitor is looking at the card. §CATPRM could use `--dcp-edge-now` because
   nothing there restated the property; here the property itself has to be
   out-ranked. Same fix public-deals.css already applies for its amber edge.

   `:focus-visible` is left to the base sheet, which paints a brand-blue ring
   — that is an accessibility affordance and it should not be tone-cycled into
   eight different colours. */
@media (hover: hover) {
    .v2x .dir-tc:hover {
        --dcp-bar-w: calc(100% - 40px);
        border-color: rgba(var(--dtc-rgb), calc(var(--dcp-edge) + .22));
    }

    body.theme-dark .v2x .dir-tc:hover {
        border-color: rgba(var(--dtc-rgb), calc(var(--dcp-edge) + .22));
    }

    /* The bloom SWELLS from the corner it hangs off, so the light appears to
       move rather than simply brighten. §HPXP's own values. */
    .v2x .dir-tc:hover::after { opacity: 1; transform: scale(1.14); }
}

.v2x .dir-tc:focus-visible { --dcp-bar-w: calc(100% - 40px); }


/* ── §TCPRM-LOGO) The plate joins the tone system ────────────────────────
   The plate already fills with `--dtc-bg`; only its inset ring was off. It
   was `rgba(255,255,255,.5)` — a white hairline, which on a card that is now
   washed in the tone reads as a gap between the plate and the surface rather
   than as an edge on it. `--tone-line`'s equivalent here is the same tone at
   the wash's own edge alpha, which is what every other bordered thing on the
   card uses. */
.v2x .dir-tc-logo { box-shadow: inset 0 0 0 1px rgba(var(--dtc-rgb), var(--dcp-edge)); }


/* ── §TCPRM-A11Y) Reduced motion and forced colours ──────────────────────
   The base sheet's `prefers-reduced-motion` block already names `.dir-tc` and
   kills its transform and transitions. It cannot know about the three layers
   added above, so they are stopped here: the ring does not spin, the bloom
   does not swell, and the accent bar is simply PRESENT rather than growing —
   the information the hover carried is kept, the motion is not.

   Forced colours: every layer here is decoration that the OS palette has no
   opinion about, and a conic gradient under `forced-colors` renders as a
   solid block. All of it comes off. */
@media (prefers-reduced-motion: reduce) {
    .v2x .dir-tc,
    .v2x .dir-tc::after { transition: none; }

    .v2x .dir-tc::before { animation: none; transition: none; }

    .v2x .dir-tc:hover::after,
    .v2x .dir-tc:focus-visible::after { transform: none; }

    .v2x .dir-tc:hover { --dcp-bar-w: calc(100% - 40px); }
}

@media (forced-colors: active) {
    .v2x .dir-tc::before,
    .v2x .dir-tc::after { display: none; }

    .v2x .dir-tc { background-image: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §TCCOL — a fifth column above 1400px                          (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   The base sheets set `.dir-cards` to four columns with NO upper bound, so a
   1440px laptop and a 2560px monitor both showed four — the card simply grew
   to 387px on the larger screen and the grid stopped being a grid. The
   requested ladder adds a fifth column at the top and leaves every step below
   it exactly where it was:

     ≥ 1400   5 cols   ← this block, the only change
     1181-1399  4 cols   base sheet's unqualified rule
     1025-1180  3 cols   base sheet's @media (max-width:1180px)
      821-1024  3 cols   same rule, filter rail now stacked (§GCOL)
      681- 820  2 cols   base sheet's @media (max-width:820px)
     ≤  680     1 col    base sheet's @media (max-width:680px)

   ── WHY 1400 AND NOT 1440 ─────────────────────────────────────────────────
   The brief lists 1440 as a five-column width, so the breakpoint has to sit
   at or below it. 1400 is the round number just under, and it is chosen
   rather than 1440 so that a 1440px viewport is comfortably INSIDE the tier
   rather than sitting exactly on its edge — a laptop reporting 1439 because
   of a scrollbar should not drop to four columns.

   ── THE WIDTH THIS ACTUALLY BUYS ──────────────────────────────────────────
   The results column is the viewport, capped at the 1920px shell, less its
   gutter and less the 236px filter rail and its 20px gap. With four 16px
   gaps between five cards:

     viewport   results   card
     1400        1076     202px   ← narrowest five-column card
     1440        1116     210px
     1500        1176     222px
     1600        1296     246px
     1920        1594     306px
     2560        1594     306px   (shell caps at 1920)

   202px is the number that matters, and it is not new territory: the
   four-column tier already renders a 207px card at 1181px and has since it
   shipped. Anything that truncates at 202 was already truncating at 207 — see
   §TCFIT, which is where that is dealt with, rather than here. This block
   only changes the count. */
@media (min-width: 1400px) {
    .v2x .dir-cards { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §TCFIT — nothing on the card gets cut off                     (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   Two defects, both PRE-DATING §TCCOL's fifth column — it widened the blast
   radius rather than causing them. Measured before and after, at a 232px card
   (four columns, 1300px) and a 202px card (five columns, 1400px):

                          232px / 4col      202px / 5col
     /workflows  titles     9 of 9 cut       9 of 9 cut
     /cheat-sheet titles   21 of 41 cut     32 of 41 cut
     /use-cases  titles      4 of 5 cut      5 of 5 cut
     /glossary   overflow    6 cards         8 cards

   ── 1 · THE TITLE WAS ELLIPSISED AT EVERY WIDTH ───────────────────────────
   `.dir-tc-nm a` shipped `white-space: nowrap` + `text-overflow: ellipsis`,
   so a title longer than the card was cut with no way to read it — the card
   links to the page, but a grid of "Best AI Image Gener…" is a grid you have
   to click to search.

   THIS IS NOT A NARROW-CARD PROBLEM AND NO BREAKPOINT FIXES IT. Measured on
   /cheat-sheet, the widest title lays out at 317px on one line, against a
   maximum card content width of 277px at the 1920px shell cap — so three
   titles were still being cut at 2560px, on the widest screen the site
   supports. A container query keyed on card width was written and thrown
   away for exactly this reason.

   -- RESERVE TWO LINES, ALLOW THREE ----------------------------------------
   A first pass clamped at two and justified it by dividing each vertical's
   widest single-line title by the narrowest content width (170px): worst case
   /cheat-sheet at 317px, i.e. 1.86 lines, so two looked sufficient everywhere.

   THAT ARITHMETIC WAS WRONG AND THE MEASUREMENT PROVED IT. Dividing a
   single-line width by the box width assumes text packs perfectly, and real
   wrapping is ragged — a 31-character title whose longest word is 10
   characters takes three lines in a box that "should" hold it in 1.8. Counted
   properly, by `scrollHeight > clientHeight` on the clamped box rather than by
   division, 25 of 249 titles across all twenty verticals still clipped at two
   lines, and /guides clipped 12 of 12.

   So the reserve and the cap are DIFFERENT NUMBERS:

     min-height        2 lines   what every card holds open
     -webkit-line-clamp 4 lines   the safety cap a title may grow into

   RAISING THE CAP IS FREE. It is a maximum, not a reservation — only a title
   that needs the line takes it, so a two-line title renders identically at a
   clamp of 2, 3 or 4. That is why the number is set at the point where real
   content stops clipping rather than at the smallest value that looks tidy.
   Walking it up and re-counting: clamp 2 left 25 of 249 clipped, clamp 3 left
   2 of 162 (both on /guides, at 57 and 65 characters, needing 3.9 lines in a
   170px box), clamp 4 leaves none. The cap stays rather than being removed
   altogether so a pathological title cannot push a card to twenty lines.

   The 90% of titles that fit in two lines keep the tight, aligned box. The
   10% that need a third get it instead of an ellipsis. Because the grid
   stretches every card in a row to the tallest, the only visible cost is the
   description starting one line lower on those cards — and there are exactly
   two possible start heights, not twelve, which is the thing the base sheet's
   "the four rows are FIXED" note is actually protecting against. On /guides,
   where every title runs long, all twelve grow together and the row stays
   perfectly even anyway.

   Reserving three outright was the alternative and was rejected: it spends
   21px of empty space on all 249 cards to serve 25 of them.

   `overflow-wrap: anywhere` is the backstop for a single unbroken token
   longer than the card - glossary terms and model names are full of them, and
   a clamp alone does not break a word that has nowhere to break. */
.v2x .dir-tc-nm {
    min-height: calc(2 * 1.35em);
}

.v2x .dir-tc .dir-tc-nm a {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}

/* -- 2 . THE GLOSSARY CARD PUSHED ITS OWN CHIP OFF THE EDGE ----------------
   `.glx-nmrow` is the glossary's own flex row holding the term beside its
   acronym chip, and public-glossary.css already sets `min-width: 0` on the
   row AND on the `.dir-tc-nm` inside it, with a comment saying that is "what
   lets the term truncate instead of shoving the chip off the card".

   `min-width: 0` was the right instinct and the wrong property. The row is a
   flex ITEM of `.dir-tc`, which is a flex COLUMN with `align-items: center` -
   and a centred item is sized to its own content, never stretched or shrunk
   to the container. `min-width: 0` only matters to an item being shrunk, so
   it never applied: the row laid out at its natural 263px inside a 200px
   card and hung 30px over the edge, chip and all.

   `max-width: 100%` is the constraint that was missing. It caps the row at
   the card's content box, at which point the `min-width: 0` already there
   does the job it was written for. Measured: 8 overflowing cards -> 0. */
.v2x.glx .glx-nmrow {
    max-width: 100%;
}


/* ═══════════════════════════════════════════════════════════════════════════
   §TCFOOT — the CTA sits on the floor of the card                (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   The base sheets pin the button to the bottom of the card and then, one line
   later, unpin it:

     .v2x .dir-tc-btn                 { margin-top: auto }   (0,2,0)
     .v2x .dir-tc-meta + .dir-tc-btn  { margin-top: 14px }   (0,3,0)

   The second rule exists to hold a 14px gap under the rating row, and it wins
   on specificity — so on EVERY card that has a meta row, which is nearly all
   of them, `margin-top` resolves to a fixed 14px and the button stops being
   bottom-pinned. It ends up 14px under whatever content happens to end above
   it, and the leftover height is dumped as dead space UNDERNEATH.

   Grid `align-items: stretch` still makes every card in a row the same
   height, so the effect is not ragged cards — it is ragged BUTTONS inside
   identically-sized cards, which reads as the cards being stretched. Measured
   on /company at 1440px, first row, all five cards exactly 372px tall:

     card   gap below the button
     1      19px      ← has a category line, so its content runs longest
     2      43px      ← meta row wrapped to two lines
     3      69px
     4      69px
     5      69px

   50px of drift between the first and last button in a single row.

   ── THE FIX KEEPS BOTH PROPERTIES ─────────────────────────────────────────
   The 14px is a MINIMUM SEPARATION and the `auto` is a POSITION, and they were
   fighting over one property because both were expressed as `margin-top` on
   the same element. Splitting them lets each do its job:

     · the gap moves to `margin-bottom` on the meta row, where nothing
       competes with it and it applies unconditionally;
     · `margin-top: auto` is restored on the button, at the same (0,3,0) the
       rule it replaces carried, so the free space is absorbed ABOVE the
       button instead of below it.

   When the card is full the auto margin resolves to 0 and the 14px still
   holds the two apart, so the tight case is unchanged. When there is slack —
   which is the case this fixes — the button goes to the floor and every
   button in the row lands on one line.

   ⚠️ Scoped to the button as a DIRECT SIBLING of the meta row, which is the
   grid card's structure. Three verticals wrap the button in a footer instead
   (`.evx-foot`, `.pmx-act`, `.dlx-lc-act`) for their list rows; those are not
   siblings of `.dir-tc-meta`, this selector never matches them, and their own
   `margin-top: 0` rules at (0,4,0) stay untouched.

   `.acx-cta` was a fourth wrapper until 2026-08-22 — and it was a GRID card,
   not a list row, so it was the one exception to the sentence above. It held
   /ai-courses' price beside the button; the price was removed, and rather
   than keep a flex row around a single child the wrapper went too. The
   courses grid card is therefore a plain `.dir-tc-meta` + `.dir-tc-btn` pair
   now and DEPENDS on this section for its bottom alignment. Verified after
   that change at 1278px: three rows of four, gap below every button 19px,
   spread 0px. */
.v2x .dir-tc-meta {
    margin-bottom: 14px;
}

.v2x .dir-tc-meta + .dir-tc-btn {
    margin-top: auto;
}


/* ═══════════════════════════════════════════════════════════════════════════
   §TCLOGO — the mark fills its plate                             (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   The plate is the right size; what sat inside it was not. The base sheets
   inset the image well inside the tinted square:

     .v2x .dir-tc-logo img { width: 60%; height: 60% }   66px plate -> 40px mark
     .v2x .dir-lc-logo img { width: 70%; height: 70% }   52px plate -> 36px mark

   A 40px mark centred in a 66px plate reads as a small logo floating in a
   coloured box rather than as a logo tile — there is more tint than mark. The
   brief is for the mark to REACH THE BORDER, so the image fills the plate:

     grid  66px plate -> 66px mark
     list  52px plate -> 52px mark

   A first pass stopped at 80%, on the reasoning that `border-radius: 20px`
   curves the plate away at the corners and a square mark drawn edge-to-edge
   would lose them. That was the wrong call twice over. It left ~6px of dead
   tint on every side — the gap this block was written to remove — and the
   corners are not lost anyway: the plate already carries `overflow: hidden`
   alongside its radius, so a square logo is CLIPPED TO THE SAME CURVE as the
   plate and reads as an app icon rather than as a square with its corners
   sawn off. The clipping is the effect, not a defect.

   `object-fit: contain` is untouched and is what makes 100% safe: the image
   is fitted inside the box, never cropped or stretched. A square mark fills
   the plate exactly; a wide wordmark fits to width and centres, which leaves
   air above and below it that no percentage can remove without distorting the
   logo. That is the correct outcome, not a residual gap.

   ⚠️ The tinted `--dtc-bg` plate is now only visible BEHIND A TRANSPARENT
   LOGO. A mark that ships its own opaque background — several do — covers the
   tint entirely and the tile takes the logo's own colour, with §TCPRM's 1px
   ring still drawn on top as the border. That is the intended reading: the
   ring is the tile's edge, the logo is its face.

   ── WHAT THIS DELIBERATELY DOES NOT TOUCH ─────────────────────────────────
   · `.dir-tc-mono` / `.dir-lc-mono`, the lettered fallback. A letter and a
     logo have different optical needs — a monogram at 80% of its tile reads
     as cramped where a mark reads as confident, which is why the letter sits
     at 26px in a 66px plate and stays there.
   · The per-vertical SVG glyphs (`.acx-plate svg`, `.apx-plate svg`,
     `.wfx-plate i`, `.skl-plate svg`, at a fixed 27px grid / 21px list).
     Those are ICONS standing in for a category, not brand marks, and they are
     drawn on a 24px grid — scaling them past their intended size is how a
     crisp glyph turns furry.
   · `.tplx-lc-cover img`, which is already `object-fit: cover` at 100% and
     out-ranks this at (0,3,1) — a template's cover art is a photograph
     filling the box, not a mark centred in it. */
.v2x .dir-tc-logo img { width: 100%; height: 100%; }
.v2x .dir-lc-logo img { width: 100%; height: 100%; }


/* ── §TCLOGO-RING) The border survives a full-bleed logo ──────────────────
   §TCPRM-LOGO draws the plate's edge as `box-shadow: inset 0 0 0 1px` on the
   plate itself, and the base sheet's hover swaps that inset ring to
   `--dtc-ink`. Both worked while the mark was inset — and both vanished the
   moment it went to 100%.

   INSET BOX-SHADOWS PAINT ABOVE THE BACKGROUND BUT BELOW THE CONTENT. An
   opaque logo filling the plate therefore covers the ring completely, which
   is why the tile lost its blue edge exactly when the gap was removed. The
   two are the same change: you cannot have the mark reach the border and the
   border drawn underneath the mark.

   Moving the ring to `::after` puts it back on top. A pseudo-element of a
   non-positioned parent paints AFTER that parent's children, so the ring is
   drawn over the image without needing a z-index or a stacking context — and
   `pointer-events: none` keeps it out of the way of the card's own click
   overlay.

   Both pseudo-elements on both plates were free; this takes one and leaves
   `::before` for whatever comes next. `border-radius: inherit` keeps the ring
   on the plate's own curve, including the two verticals that override the
   radius at phone widths.

   The plate's own inset shadow is deliberately LEFT IN PLACE rather than
   removed: it is identical in colour and position, it still shows through a
   transparent logo, and it is what the base sheet's hover transition is
   attached to. The pseudo is an addition, not a replacement. */
.v2x .dir-tc-logo,
.v2x .dir-lc-logo { position: relative; }

.v2x .dir-tc-logo::after,
.v2x .dir-lc-logo::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    box-shadow: inset 0 0 0 1px rgba(var(--dtc-rgb), var(--dcp-edge));
    transition: box-shadow .24s ease;
}

/* The hover step matches what the base sheet already does to the plate's own
   ring, so a full-bleed logo and a lettered one light up identically. */
@media (hover: hover) {
    .v2x .dir-tc:hover .dir-tc-logo::after,
    .v2x .dir-lc:hover .dir-lc-logo::after {
        box-shadow: inset 0 0 0 1px var(--dtc-ink);
    }
}


/* ── §TCLOGO-LCRGB) The list row needs the triplet too ────────────────────
   §TCLOGO-RING's ring came out `none` on every list row, and the cause is the
   trap §CATPRM-RGB spends a paragraph on: `rgba(var(--dtc-rgb), …)` with
   `--dtc-rgb` UNDEFINED is an invalid declaration, and an invalid declaration
   is dropped silently. No warning, no fallback, just no ring.

   §TCPRM-RGB defines the triplet on `.dir-tc` — the GRID card — because that
   is the host it was written for. A list row is `.dir-lc`, a different
   element that is never a descendant of `.dir-tc`, so it inherited nothing.

   The base sheet already gives `.dir-lc` its own eight-tone cycle at
   `v2-public-directory-listing.css:1497`, using the SAME hues in the SAME
   slot order as `.dir-tc`. These lines mirror it exactly, so a row and a card
   showing the same listing are lit by the same colour rather than by two
   cycles that happen to agree.

   `--dcp-edge` is declared here for the same reason — it too lived only on
   the grid card, and the ring reads both. */
.v2x .dir-lc                    { --dtc-rgb:  54, 128, 237; --dcp-edge: .20; }
.v2x .dir-lc:nth-child(8n+2)    { --dtc-rgb: 139,  92, 246; }
.v2x .dir-lc:nth-child(8n+3)    { --dtc-rgb:  16, 185, 129; }
.v2x .dir-lc:nth-child(8n+4)    { --dtc-rgb: 234,  88,  12; }
.v2x .dir-lc:nth-child(8n+5)    { --dtc-rgb: 236,  72, 153; }
.v2x .dir-lc:nth-child(8n+6)    { --dtc-rgb:   6, 182, 212; }
.v2x .dir-lc:nth-child(8n+7)    { --dtc-rgb:  99, 102, 241; }
.v2x .dir-lc:nth-child(8n+8)    { --dtc-rgb: 245, 158,  11; }

body.theme-dark .v2x .dir-lc    { --dcp-edge: .30; }


/* ── §TCLOGO-VS) The comparison row carries two marks, not one ────────────
   /compare's list row puts BOTH tools in the one plate — logo, a "VS" label,
   logo — sized inline in `compare-index.blade.php`:

     22px mark · 6px gap · VS label · 6px gap · 22px mark   ≈ 72px
     plus the plate's own 6px padding each side             ≈ 84px

   `.dir-lc-logo` is a 52px square with `overflow: hidden`, so 84px of content
   went into a 52px box and the second mark was CLIPPED. Measured before this
   rule: scrollWidth 68px against clientWidth 52px on four of five rows — the
   comparison was showing one and a half logos.

   ── SCOPED BY STRUCTURE, NOT BY PAGE ──────────────────────────────────────
   /compare renders `class="v2x dir"` with no modifier of its own, so there is
   no page hook to hang this on. `:has(> * + * + *)` — a plate with three or
   more children — is the two-up arrangement itself, and every other plate on
   the site holds exactly one mark, letter or glyph. That makes the selector
   describe the thing it is fixing rather than the URL it happens to appear
   on, and a future vertical that adopts the same two-up plate gets the fix
   for free. `:has()` is already load-bearing in this file — §CATBAND's
   railhead switch is one.

   `width: auto` with the square as a floor lets the plate size to its own
   content instead of guessing at 84px, which keeps it correct if the label
   ever changes from "VS" to a longer word. Height stays 52px, so the row's
   rhythm is untouched. */
.v2x .dir-lc-logo:has(> * + * + *) {
    width: auto;
    min-width: 52px;
}



/* ═══════════════════════════════════════════════════════════════════════════
   §FXTIGHT — the filter rail spends less height on nothing   (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   `.dir-g` is `align-items: start`, so the section is as tall as its TALLEST
   column — and the filter rail is routinely far taller than the results:

     /ai-models          rail 1801   results  886   ->  915px of void
     /ai-directory       rail 1781   results  886   ->  895px
     /browser-extensions rail 1812   results  923   ->  889px
     /ai-skills          rail 1903   results 1248   ->  655px
     /guides             rail 1955   results 1473   ->  482px
     /cheat-sheet        rail 1748   results 1342   ->  406px
     /ai-courses         rail 1784   results 1544   ->  240px
     /gpts /glossary /prompts                       ->    0px

   That void IS the reported gap: the cards and the pagination finish and the
   section keeps running because the filters are still going down the left.

   ── WHY THIS BLOCK IS SPACING AND NOT A CAP ───────────────────────────────
   Capping the rail with `max-height` + `overflow-y: auto` closes the gap
   completely — measured 0px on every vertical — but it puts a scrollbar
   inside the filter panel, which is explicitly not wanted, and which §FX
   removed on its own merits. So the rail has to get shorter honestly.

   The three verticals with NO void are the ones already showing enough cards
   (/glossary 30 per page, /gpts 24, /prompts 18). That is the real variable:
   a rail is ~1750-1950px and a two-row results column is ~886px. Spacing
   alone cannot bridge 900px and this block does not pretend to — it returns
   the height the rail was spending on air, and the rest is a page-size
   question answered outside CSS.

   ── WHAT IS BEING RECLAIMED ───────────────────────────────────────────────
   Nothing is hidden, no row is removed, and every control keeps its 40px
   touch target on the phone (the ≤1024 layout is untouched — §GCOL stacks the
   rail there and it is not the tall column).

     .dir-fx-group  padding  14/12 -> 10/8     x6 groups   = 48px
     .dir-fx-glbl   margin-bottom 10 -> 7      x6 labels   = 18px
     .dir-fx-chk    padding 8 -> 6, margin 1 -> 0  x28 rows = 168px

   ~234px measured, against a 1781px rail. It makes /ai-courses' 240px void
   effectively vanish and takes a bite out of the rest.

   Row padding stops at 6px rather than going lower: the checkbox mark is
   18px, so 6+18+6 = 30px is the point where the hit area stops being
   comfortable for a pointer and the list starts reading as a wall. */
@media (min-width: 1025px) {
    .v2x .dir-fx-group { padding: 10px 14px 8px; }
    .v2x .dir-fx-glbl  { margin-bottom: 7px; }
    .v2x .dir-fx-chk   { padding: 6px 10px; margin: 0; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §COUNT — the results counter, as a brand chip                 (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   "33 tools found" is the first thing the toolbar says and the one number the
   whole page is about — it changes with every filter and it is how a reader
   knows their filter did anything. It shipped as plain gradient text beside a
   muted label, which reads as a caption rather than as the page's headline
   figure.

   This gives it a chip: the number filled with the brand ramp, the label
   beside it, a halo that lights on hover and a sheen that crosses the chip
   with it. Same two spans, no blade touched — it reaches all twenty verticals
   plus every category and tag page through this file.

   ── ⚠️ THE OLD RULE COULD MAKE THE NUMBER INVISIBLE ───────────────────────
   The base sheets paint the figure with the gradient-text technique:

     .dir-tb-count-n { background: linear-gradient(...);
                       -webkit-background-clip: text;
                       -webkit-text-fill-color: transparent }

   The glyphs are transparent and only the background shows through them. Any
   rule that later sets `background` on that span — a theme override, a
   contrast layer, a future edit that does not know about the technique —
   leaves transparent text on no background, i.e. NOTHING. It is the same
   class of trap as the dark `background` shorthand one this file already
   records, and it is why the brief asks for text that cannot hide.

   So the chip does NOT reuse it. The gradient moves to the chip's own
   background and the glyphs get a real, opaque colour — and
   `-webkit-text-fill-color` is reset EXPLICITLY rather than left to inherit,
   because the base sheet already set it to `transparent` on this exact
   selector and an unset property keeps its old value. Written the long way on
   purpose: this one line is the difference between a number and a blank.

   ── `tabular-nums` IS NOT COSMETIC ────────────────────────────────────────
   The figure changes on every filter click, and proportional digits make the
   chip resize as 9 becomes 12 becomes 118 — a control that twitches beside a
   control the reader is aiming at. Tabular figures hold the width.
   ═══════════════════════════════════════════════════════════════════════════ */

.v2x .dir-tb-count {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 11px;
    /* The label may wrap on a narrow phone; it must never be clipped, so the
       group aligns to the chip rather than to a baseline it no longer shares. */
    flex-wrap: nowrap;
    min-width: 0;
}

/* The halo. A soft brand bloom behind the chip, off at rest so the toolbar
   stays calm, lit on hover. `filter: blur()` rather than a wide box-shadow so
   it reads as light rather than as a second border. */
.v2x .dir-tb-count::before {
    content: '';
    position: absolute;
    inset-block: -6px;
    inset-inline-start: -8px;
    width: 74px;
    border-radius: 18px;
    background: radial-gradient(60% 60% at 50% 50%, rgba(54, 128, 237, .38) 0%, rgba(54, 128, 237, 0) 72%);
    filter: blur(6px);
    opacity: 0;
    transition: opacity .34s ease;
    pointer-events: none;
    z-index: 0;
}

.v2x .dir-tb-count-n {
    position: relative;
    z-index: 1;
    display: inline-grid;
    place-items: center;
    min-width: 46px;
    height: 34px;
    padding: 0 12px;
    border-radius: 11px;

    /* ⚠️ BOTH LINES ARE LOAD-BEARING — see the header. The base sheet sets
       `-webkit-text-fill-color: transparent` on this selector; without the
       reset the digits stay invisible on top of the new fill. */
    -webkit-text-fill-color: #fff;
    color: #fff;

    /* ⚠️ THE RAMP IS THE MEASURED ONE, NOT THE BRAND TOKENS. This chip puts
       WHITE TEXT on a blue fill, and the brand ramp fails there:

         #60A4F5 brand-blue-light   2.58:1   ✗
         #3680ED brand-blue         3.84:1   ✗
         #2060C0 brand-blue-dark    6.01:1   ✓

       A first pass used exactly those three and shipped a 3.84:1 midpoint —
       under the 4.5:1 AA needs for 17px text, and the precise defect
       `ai-contrast-fix.css` exists to correct sitewide. Its header states the
       rule outright: brand blue "is fine for ink, borders and tints; it must
       never sit under white text."

       So the stops are that file's own `--aa-fill-1/2/3`, reused rather than
       re-invented, and re-measured here:

         #2E6FD0  4.88:1   #2762C2  5.81:1   #1B4E9E  7.98:1

       Worst point 4.88:1, clearing AA at every step of the gradient — which
       matters because the reader sees all three, not an average. The tokens
       stay in use everywhere they are correct: the halo, the glow and the
       shadows below are all brand blue, because none of them sits under text. */
    background-color: #2762C2;
    background-image: linear-gradient(135deg,
        #2E6FD0 0%,
        #2762C2 46%,
        #1B4E9E 100%);
    -webkit-background-clip: border-box;
            background-clip: border-box;

    font-size: 17px;
    font-weight: 820;
    line-height: 1;
    letter-spacing: -.018em;
    font-variant-numeric: tabular-nums;

    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .38),
        inset 0 0 0 1px rgba(255, 255, 255, .14),
        0 6px 16px -7px rgba(54, 128, 237, .62),
        0 1px 2px rgba(16, 24, 40, .10);
    overflow: hidden;
    transition: transform .26s var(--ease-out, cubic-bezier(.16,1,.3,1)),
                box-shadow .26s ease;
}

/* The sheen. A narrow diagonal band parked off the left edge that crosses the
   chip on hover — the one piece of motion in the block, and the reason the
   chip carries `overflow: hidden`. */
.v2x .dir-tb-count-n::after {
    content: '';
    position: absolute;
    inset-block: 0;
    inset-inline-start: -60%;
    width: 45%;
    background: linear-gradient(100deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 255, 255, .46) 50%,
        rgba(255, 255, 255, 0) 100%);
    transform: skewX(-18deg);
    transition: inset-inline-start .62s var(--ease-out, cubic-bezier(.16,1,.3,1));
    pointer-events: none;
}

/* ── The label ────────────────────────────────────────────────────────────
   Sentence-case 13.5px beside a filled chip read as body copy that had been
   left next to a control. The page already HAS a language for a label sitting
   above or beside a value, used twice within this same toolbar and rail:

     .dir-fx-glbl  11px / 800 / .09em / uppercase   (PRICING, CATEGORIES …)
     .dir-ts-cap   10px / 800 / .10em / uppercase   (SORT BY)

   So the counter joins it rather than introducing a third treatment. 11.5px
   is deliberately a half-step ABOVE both: this label is the sentence the
   toolbar is making, not a caption over a value, and it carries the only word
   that says what was counted.

   ── WHY UPPERCASE IS SAFE HERE AND NOT EVERYWHERE ─────────────────────────
   Tracked uppercase costs reading speed on anything long — but these labels
   are two or three words fixed by the vertical ("tools found", "browser
   extensions found"), read as a unit rather than parsed, and never sentences.
   The longest across the twenty verticals lays out ~180px, which still fits
   beside the chip on a 360px phone.

   ⚠️ `overflow-wrap: anywhere` and `white-space: normal` together are what
   keep the brief's "text should not be hidden" true when it does not fit:
   the label wraps to a second line instead of ellipsising, and a long
   unbroken word breaks rather than pushing out of the toolbar. */
.v2x .dir-tb-count-l {
    position: relative;
    z-index: 1;
    font-size: 11.5px;
    font-weight: 780;
    letter-spacing: .085em;
    text-transform: uppercase;
    color: var(--v2-muted);
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.35;
    transition: color .24s ease;
}

body.theme-dark .v2x .dir-tb-count-l { color: #A7AEBF; }

/* ── Dark ─────────────────────────────────────────────────────────────────
   The ramp is lifted and the outer glow deepened: the same chip on a #17171B
   toolbar needs a lighter fill to hold its edge and a stronger bloom to read
   as lit rather than as a flat blue rectangle. */
body.theme-dark .v2x .dir-tb-count-n {
    /* One step lighter than the light-mode ramp so the chip separates from a
       #1C1F2A toolbar, and still AA at every stop under white:
       #356FD4 4.80:1 · #2C6ACB 5.21:1 · #1F58A8 6.95:1. */
    background-color: #2C6ACB;
    background-image: linear-gradient(135deg,
        #356FD4 0%,
        #2C6ACB 48%,
        #1F58A8 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .30),
        inset 0 0 0 1px rgba(255, 255, 255, .10),
        0 8px 20px -8px rgba(54, 128, 237, .70),
        0 1px 2px rgba(0, 0, 0, .45);
}

body.theme-dark .v2x .dir-tb-count::before {
    background: radial-gradient(60% 60% at 50% 50%, rgba(96, 164, 245, .46) 0%, rgba(96, 164, 245, 0) 72%);
}

/* ── Hover ────────────────────────────────────────────────────────────────
   Scoped to the counter itself, not to the whole toolbar. The counter is a
   readout rather than a control, so lighting it up when the pointer crosses
   the toolbar on its way to the sort button would be motion the reader did
   not ask for. Crossing the number is a deliberate act. */
@media (hover: hover) {
    .v2x .dir-tb-count:hover::before { opacity: 1; }

    .v2x .dir-tb-count:hover .dir-tb-count-n {
        transform: translateY(-1px);
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .46),
            inset 0 0 0 1px rgba(255, 255, 255, .18),
            0 10px 22px -8px rgba(54, 128, 237, .72),
            0 1px 2px rgba(16, 24, 40, .10);
    }

    .v2x .dir-tb-count:hover .dir-tb-count-n::after { inset-inline-start: 115%; }
    .v2x .dir-tb-count:hover .dir-tb-count-l { color: var(--v2-ink); }

    body.theme-dark .v2x .dir-tb-count:hover .dir-tb-count-l { color: #E8EBF2; }
}

/* ── Responsive ───────────────────────────────────────────────────────────
   The chip steps down twice; the LABEL NEVER GOES. Hiding it would leave a
   bare number with nothing saying what was counted, which is precisely the
   "text should not be hidden" the brief calls out — and on a phone it is the
   only thing telling a reader the filter worked. */
@media (max-width: 1180px) {
    .v2x .dir-tb-count-n { min-width: 42px; height: 32px; font-size: 16px; border-radius: 10px; }
    .v2x .dir-tb-count   { gap: 10px; }
}

@media (max-width: 640px) {
    .v2x .dir-tb-count-n { min-width: 38px; height: 30px; padding: 0 10px; font-size: 15px; border-radius: 9px; }
    .v2x .dir-tb-count-l { font-size: 11px; letter-spacing: .075em; }
    .v2x .dir-tb-count   { gap: 9px; }
    /* The halo is sized to the desktop chip; re-fit it rather than letting it
       hang off one side. */
    .v2x .dir-tb-count::before { width: 58px; inset-inline-start: -6px; }
}

@media (max-width: 380px) {
    .v2x .dir-tb-count-n { min-width: 34px; height: 28px; padding: 0 9px; font-size: 14px; }
    .v2x .dir-tb-count-l { font-size: 10.5px; letter-spacing: .06em; }
}

/* ── A11y ─────────────────────────────────────────────────────────────────
   Reduced motion keeps the chip and its halo — they are colour, not movement
   — and stops the sheen and the lift. Forced colours drops every painted
   layer and lets the OS palette draw the number, which is the only part that
   carries information. */
@media (prefers-reduced-motion: reduce) {
    .v2x .dir-tb-count-n,
    .v2x .dir-tb-count-n::after,
    .v2x .dir-tb-count::before,
    .v2x .dir-tb-count-l { transition: none; }

    .v2x .dir-tb-count:hover .dir-tb-count-n { transform: none; }
    .v2x .dir-tb-count:hover .dir-tb-count-n::after { inset-inline-start: -60%; }
}

@media (forced-colors: active) {
    .v2x .dir-tb-count::before,
    .v2x .dir-tb-count-n::after { display: none; }

    .v2x .dir-tb-count-n {
        background: ButtonFace;
        color: ButtonText;
        -webkit-text-fill-color: ButtonText;
        border: 1px solid ButtonText;
        box-shadow: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §CATLOGO — the category tile's mark fills its plate too      (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   §TCLOGO did this for the RESULT cards. The "Browse by …" band underneath
   them was left on the old inset, so /ai-models shows OpenAI's mark at 70% of
   its tile — a 46px logo centred on a 66px plate, with 10px of the plate's
   own blue showing on every side. That ring of tone reads as a border the
   design never asked for, and it is the same gap the result cards had.

     .dir-cat-card-ico img   70%  ->  100%

   ── THIS PLATE IS NOT THE CARD'S PLATE, AND THE DIFFERENCES MATTER ────────
   `.dir-tc-logo` is a TINTED plate (pale `--dtc-bg`) carrying `overflow:
   hidden` already. `.dir-cat-card-ico` is a SOLID tone tile — measured
   `rgb(27,91,181)` with a white sheen over it — and it ships `overflow:
   visible`. So filling the image here needs two more things than it did
   there:

     · `overflow: hidden`, or a square logo at 100% keeps its corners and
       hangs them outside the plate's 19px radius. On the result cards the
       clip was already in place; here it has to be added, and it is what
       turns a square mark into a rounded tile rather than a square pasted on
       a rounded one.
     · a ring, because a solid-tone plate covered by an opaque logo loses its
       edge entirely — the tile stops reading as a tile.

   ── WHY THE RING IS ON `::after` ──────────────────────────────────────────
   Same reason as §TCLOGO-RING: an inset `box-shadow` paints above the
   background but BELOW the content, so a 100% opaque logo covers it. A
   pseudo-element of a non-positioned parent paints after that parent's
   children, so the ring lands on top without needing a z-index. Both pseudos
   on this plate were free (`content: none` on each, measured).

   The ring reads `--dtc-rgb`, which §CATPRM-RGB already sets on every
   `.dir-cat-card`. On a tile with no image the plate is ALREADY that hue, so
   the ring is invisible and costs nothing; on a tile with one it is the only
   thing drawing the edge. One rule, both cases.

   ⚠️ SVG GLYPHS ARE UNTOUCHED — the selector is `img`. The other sixteen
   companies on that band render a 31px category glyph (47% of the plate),
   which is an icon on a 24px grid standing in for a missing logo, not a brand
   mark. Scaling those to the edge is how a crisp glyph turns furry, and it is
   the same call §TCLOGO records. */
.v2x .dir-cat-card-ico {
    overflow: hidden;
}

.v2x .dir-cat-card-ico img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.v2x .dir-cat-card-ico::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    box-shadow: inset 0 0 0 1px rgba(var(--dtc-rgb, 54, 128, 237), .32);
}

/* The pseudo needs a positioned parent to sit against. The plate is
   `display: grid` with no `position` of its own, so this is the one property
   the ring adds to the plate itself — and it is safe, because nothing inside
   the plate is absolutely positioned (measured: one child per tile, an `img`
   or an `svg`, neither escaping the box). */
.v2x .dir-cat-card-ico {
    position: relative;
}


/* ═══════════════════════════════════════════════════════════════════════════
   §LCPRM — the grid card's surface, on the LIST ROW              (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   §TCPRM put the homepage's §HPXP card language on `.dir-tc`, the grid tile.
   The list row never got it, so the two view modes of the SAME page drifted
   apart: switch from Grid to List and the wash, the tinted hairline, the
   corner bloom, the sweeping ring and the tone-tinted lift all disappear and
   what is left is a flat white rectangle with a coloured chip on it.

   Measured on /ai-directory?viewMode=list before this block, first row:

     background-image  none          box-shadow  none
     border-color      #E7EAF0       isolation   auto

   Four of the five layers simply did not exist. This block is the port.

   ── ONE HOST, TWENTY VERTICALS ────────────────────────────────────────────
   `.dir-lc` is the row on every listing: twenty-one blades across the twenty
   verticals, on the index, category and tag page of each. Same reasoning
   §TCPRM and §NOTR both record — the fix is CSS because the markup is not one
   file but twenty-one. No blade is touched.

   ── WHAT IS PORTED VERBATIM, AND WHAT IS RE-AIMED ─────────────────────────
   The COLOUR SYSTEM is identical: the same eight-slot hue cycle, the same
   §CATPRM ramp, the same bloom, the same conic comet, the same lift recipe.
   A blue row and a blue card are lit by the same numbers.

   The GEOMETRY cannot be. A card is ~250×420; a row is 1608×111 — a 14:1 box
   — and three of the card's layers are aimed at a shape that does not exist
   here:

     card                         row                       why
     ────────────────────────     ───────────────────────   ─────────────────
     152deg wash (near-vert.)     100deg (near-horiz.)      see §LCPRM-WASH
     3px TOP bar, centre-out      3px LEADING RAIL          see §LCPRM-WASH
     260×230 bloom               460×260 bloom              see §LCPRM-BLOOM
     4.2s orbit                   5.6s orbit                see §LCPRM-ORBIT

   In each case the GESTURE is unchanged — an accent that grows from nothing
   on hover, light falling from the top corner, a comet crossing the edge —
   and only its aim moves to the row's own axis. That is the same trade
   §TCPRM made when it turned the reference's `<span class="hpx-sc-edge">`
   into a background layer: keep the idea, re-fit the mechanism to the host.

   ── WHAT IS DELIBERATELY NOT TOUCHED ──────────────────────────────────────
   · The rank chip's podium medals on 1/2/3, and the tone plate under 4+.
     Already tone-cycled, already matching the grid.
   · `.dir-lc-logo`'s ring. §TCLOGO-RING already draws it on both hosts from
     the same two values, and §TCLOGO-LCRGB already gave the row the
     `--dtc-rgb` / `--dcp-edge` those two rules read. This block ADDS to that
     declaration rather than repeating it — see §LCPRM-RAMP.
   · Every text, pill, rating and button rule, and the row's own
     `translateY(-2px)` on hover. This pass is about surface.
   · The per-vertical row internals (`.acx-*`, `.evx-*`, `.pmx-*`, `.dlx-*`,
     `.tplx-*`, `.glx-*`, `.wfx-*`, `.apx-*`, `.skl-*`, `.gptx-*`). Audited:
     none of them sets `background`, `border-color`, `box-shadow` or a
     pseudo-element on `.dir-lc` itself, so nothing here collides.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §LCPRM-RAMP) The ramp the layers read ────────────────────────────────
   §TCLOGO-LCRGB already declares `--dtc-rgb` (the eight-slot cycle, mirroring
   `.dir-tc`'s slot for slot) and `--dcp-edge` on this host, because
   §TCLOGO-RING's plate ring needed them. The four remaining values are added
   HERE rather than folded into that block, so the ring keeps working on its
   own if this section is ever lifted out.

   The numbers are §CATPRM's, REUSED rather than re-declared under a fifth
   name — the same call §WHYPRM and §TCPRM both made. A violet tile in the
   categories band, a violet card in the grid and a violet row in the list are
   then lit to the same depth by construction.

   ⚠️ `--dcp-edge` IS NOT RESTATED HERE. It is already `.20` light / `.30`
   dark from §TCLOGO-LCRGB, and a second declaration is two numbers racing to
   define one hairline. Everything below reads that one. */
.v2x .dir-lc {
    --dcp-a1: .075;     /* wash, the lit end */
    --dcp-a2: .055;     /* wash, the far end */
    --dcp-bloom: .20;   /* the corner bloom's hot end */

    /* The comet, §CATPRM-ORBIT's values. */
    --dcp-hot: 255, 255, 255;
    --dcp-o1: .5;
    --dcp-o2: .88;
    --dcp-o3: 1;
}

/* Dark: every alpha rises, because a 5% tint that reads over white is
   invisible over near-black. §TCPRM's own dark numbers. */
body.theme-dark .v2x .dir-lc {
    --dcp-a1: .13;
    --dcp-a2: .09;
    --dcp-bloom: .17;
    --dcp-o1: .66;
    --dcp-o2: 1;
}

/* The bloom hangs on `::after` at `z-index: -1`, which is BELOW the row's
   content but must not escape to the page behind it. `.dir-tc` gets that from
   an `isolation: isolate` it already carried; the row never had one, so this
   is the single property this block adds to the box itself.

   It is safe: the row is already `overflow: hidden`, so nothing inside it
   could reach the page anyway, and the only positioned child is
   `.dir-lc-lnk`, the full-bleed click overlay, which is inside the new
   context along with everything it overlays. */
.v2x .dir-lc { isolation: isolate; }


/* ── §LCPRM-WASH) The surface ─────────────────────────────────────────────
   TWO LAYERS, topmost first: the leading-edge accent rail, then the wash.

   ⚠️ `background-image` ONLY — never the `background` shorthand. The base
   sheet sets the row's COLOUR with one (`background:#fff`), and a shorthand
   here would reset that to transparent and let the page ground show through
   every row on all sixty URLs. See [[dark-background-shorthand-trap]].

   ── 1 · WHY 100deg AND NOT THE CARD'S 152deg ──────────────────────────────
   152deg is 28° off vertical. On a card that is a corner-to-corner diagonal.
   On a 1608x111 row it is, for all practical purposes, a VERTICAL gradient —
   and a vertical gradient on a 14:1 box is a horizontal stripe: tinted along
   the top edge, clear through the middle, tinted along the bottom. Stacked
   twelve pixels apart, twenty of those read as rules between rows, not as a
   wash on them.

   100deg is the same 10° tilt off the row's OWN long axis that 152deg is off
   the card's. It lights the end the eye enters at (rank chip, logo), clears
   through the middle where the name and description sit, and lights the end
   it leaves at (bookmark, CTA). Structurally that is the card's reading —
   lit corner, dead middle, lit far corner — turned onto the row's axis.

   The 46% dead stop, and the fade to `rgba(same-hue, 0)` rather than the
   `transparent` KEYWORD, are §HPXP-WASH's. 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 row.

   ── 2 · WHY THE ACCENT IS A LEADING RAIL AND NOT A TOP BAR ────────────────
   The card's accent is 3px tall and grows across the top, centre-out. The
   literal port is a 3px line running ~1568px across the top of the row, and
   that is not the card's effect at row scale — it is a horizontal rule, and
   in a stack of rows it reads as belonging to the gap above it rather than to
   the row it is on.

   The rail is the same accent turned 90°: 3px wide on the leading edge,
   growing from the vertical centre out. Same colour, same
   transparent -> tone -> transparent falloff so it reads as light rather than
   as a border, same `background-size` animation, same growth from nothing.
   It is also where a row's accent belongs — the leading edge is the one a
   row actually has, and it marks the start of the record.

   `0 3px` becomes `3px 0`: sized to nothing ALONG ITS OWN AXIS at rest, so
   `background-size` has somewhere to animate from. §LCPRM-STATE grows it.
   `background-position: 0 50%` keeps it growing from the centre out, which is
   what `transform: scaleY()` would do to an element. */
.v2x .dir-lc {
    background-image:
        /* 1 · the leading accent rail */
        linear-gradient(180deg,
            rgba(var(--dtc-rgb), 0) 0%,
            rgba(var(--dtc-rgb), .85) 50%,
            rgba(var(--dtc-rgb), 0) 100%),
        /* 2 · the wash, along the row's long axis */
        linear-gradient(100deg,
            rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dtc-rgb), 0)             46%,
            rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 0 50%, 0 0;
    background-size: 3px var(--dcp-bar-h, 0), 100% 100%;
    border-color: rgba(var(--dtc-rgb), var(--dcp-edge));
    transition:
        transform .2s ease,
        box-shadow .25s ease,
        border-color .2s ease,
        background-size .32s var(--ease-out);
}

/* ⚠️ THE DARK RESTATEMENT IS NOT OPTIONAL. `body.theme-dark .v2x .dir-lc` in
   the base sheet is (0,3,1) against the (0,2,0) above AND uses the
   `background` SHORTHAND, so it out-ranks the wash and resets
   `background-image` to none in one move. Without this rule the whole colour
   system silently disappears after dark, and only after dark.

   `background-color` is deliberately NOT restated — the base's shorthand is
   still the one setting it, and duplicating the surface colour here is how a
   row ends up a different grey from the band it sits in. */
body.theme-dark .v2x .dir-lc {
    background-image:
        linear-gradient(180deg,
            rgba(var(--dtc-rgb), 0) 0%,
            rgba(var(--dtc-rgb), .85) 50%,
            rgba(var(--dtc-rgb), 0) 100%),
        linear-gradient(100deg,
            rgba(var(--dtc-rgb), var(--dcp-a1)) 0%,
            rgba(var(--dtc-rgb), 0)             46%,
            rgba(var(--dtc-rgb), var(--dcp-a2)) 100%);
    background-repeat: no-repeat;
    background-position: 0 50%, 0 0;
    background-size: 3px var(--dcp-bar-h, 0), 100% 100%;
}

/* The edge has the same problem for the same reason — the base dark rule sets
   `border-color: rgba(255,255,255,.08)` alongside that shorthand. */
body.theme-dark .v2x .dir-lc { border-color: rgba(var(--dtc-rgb), var(--dcp-edge)); }

/* ── §LCPRM-RTL) The rail follows the reading direction ───────────────────
   The rail marks where the record STARTS, so in a right-to-left locale it
   belongs on the right edge. `background-position` has no logical form, so
   this is the flip, and it is the only thing in this block that moves.

   The BLOOM is deliberately NOT mirrored. It is ambient light, and it falls
   from the same corner on every surface in this design system — the category
   tile, the promise card, the grid card and the homepage showcase all light
   from the top right and none of them flip. A row that lit from the other
   corner in Arabic would be the one thing on the page out of step with the
   rest of the page. `[dir="rtl"]` styling already exists in this file at
   §CATPRM's CTA arrow, so the hook is established. */
[dir="rtl"] .v2x .dir-lc,
[dir="rtl"] body.theme-dark .v2x .dir-lc { background-position: 100% 50%, 0 0; }


/* ── §LCPRM-BLOOM) The corner light ───────────────────────────────────────
   §HPXP's `.hpx-card-showcase::after`, re-aimed. The card's 260x230 centred
   at `calc(100% + 32px) -60px` reaches 187px into a ~250px card — three
   quarters of its width. The same ellipse on a 1608px row reaches 187px into
   1608, which is a smudge in the corner rather than light on the surface.

   460x260 at `calc(100% + 44px) -48px` restores the READING at row scale: the
   falloff reaches ~287px in from the trailing edge and ~139px down past a
   111px row, so the whole action end — bookmark, CTA, the pill and rating
   beside them — sits in the light, and it fades out before the description.

   The centre stays OUTSIDE the box on purpose, so what lands on the row is
   the FALLOFF rather than a circle drawn on it. That is the reference's
   single most recognisable cue, and drawing the whole ellipse inside the box
   is how it reads as a sticker instead of as light.

   `z-index: -1` is the reference's own value and works for the same reason:
   §LCPRM-RAMP gave the row `isolation: isolate`, so -1 is below the row's
   content without escaping to the page behind it. `overflow: hidden` on the
   row clips the bloom to the rounded corners for free. */
.v2x .dir-lc::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(460px 260px at calc(100% + 44px) -48px,
        rgba(var(--dtc-rgb), var(--dcp-bloom)) 0%,
        rgba(var(--dtc-rgb), 0) 72%) no-repeat;
    opacity: .92;
    transform-origin: 100% 0;
    transition: opacity .36s ease, transform .52s cubic-bezier(.16, 1, .3, 1);
}


/* ── §LCPRM-ORBIT) The rotating light ────────────────────────────────────
   §TCPRM-ORBIT's ring, at the row's own speed.

   5.6s, not the card's 4.2s. §HPXP's rule is that speed is a statement about
   size — a big panel sweeps slowly because slow reads as large — and this is
   the largest host the system has: the row's perimeter is ~3440px against the
   card's ~1340px, so at 4.2s the comet would visibly race. It also sits at
   the slow end of the ladder the file already runs, which is the point:

     .dir-cat-card  3.4s    small category tile
     .dir-why-card  3.6s    promise card
     .dir-tc        4.2s    result card
     .dir-lc        5.6s    result ROW      <- this block

   THE HUE IS NOT FIXED: it reads `--dtc-rgb`, so a violet row orbits violet
   and an amber one amber.

   `--dcp-spin` and `dir-cat-orbit` are §CATPRM-ORBIT's, declared once at the
   top of this file and REUSED rather than duplicated under a fourth name. A
   second `@property` for the same angle would be two registrations racing to
   define one thing. */
@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.

       ⚠️ 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 row, covering every word on it. Gating the whole ring
       means the fallback is "no ring", which is a row, rather than "a
       coloured sheet", which is a bug report.

       ⚠️ inset: 0, not the reference's inset: -1px. The row carries
       `overflow: hidden` so it can clip its own bloom, and a ring hung 1px
       outside gets sliced off on all four sides. Same call §TCPRM-ORBIT and
       §CATPRM-ORBIT both made. */
    .v2x .dir-lc::before {
        content: '';
        position: absolute;
        inset: 0;
        z-index: 2;
        pointer-events: none;
        border-radius: inherit;
        padding: var(--dcp-t, 1.5px);
        --dcp-spin: 0deg;
        background: conic-gradient(from var(--dcp-spin),
            rgba(var(--dtc-rgb), 0)                 0deg,
            rgba(var(--dtc-rgb), var(--dcp-o1))    30deg,
            rgba(var(--dcp-hot),  var(--dcp-o3))   52deg,
            rgba(var(--dtc-rgb), var(--dcp-o2))    74deg,
            rgba(var(--dtc-rgb), 0)               118deg,
            rgba(var(--dtc-rgb), 0)               180deg,
            rgba(var(--dtc-rgb), var(--dcp-o1))   210deg,
            rgba(var(--dcp-hot),  var(--dcp-o3))  232deg,
            rgba(var(--dtc-rgb), var(--dcp-o2))   254deg,
            rgba(var(--dtc-rgb), 0)               298deg,
            rgba(var(--dtc-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: dir-cat-orbit 5.6s linear infinite;
        /* A paused animation does not tick, so a page of twenty idle rows
           costs nothing — and re-entering RESUMES the sweep rather than
           snapping the comet back to 0deg under the pointer. */
        animation-play-state: paused;
    }

    /* 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 §HPXP-ORBIT, §CATPRM-ORBIT and §TCPRM-ORBIT all
       make. `.dir-lc` is not itself focusable; `:focus-within` is what carries
       it here, because what the visitor tabs to is the row's name link, its
       bookmark button or its CTA. */
    .v2x .dir-lc:focus-within::before { opacity: 1; animation-play-state: running; }

    @media (hover: hover) {
        .v2x .dir-lc:hover::before { opacity: 1; animation-play-state: running; }
    }
}


/* ── §LCPRM-LIFT) Depth, tone-tinted ─────────────────────────────────────
   The row had NO resting shadow at all — measured `box-shadow: none` — so it
   sat flat on the band while the grid card floated. §HPXP-LIFT's recipe puts
   it back: a glass hairline along the top inner edge, a 1px contact shadow to
   seat the row against the page, a tone-tinted lift and a neutral one under
   it. The tone in the lift is what makes a violet row cast a violet-tinged
   shadow rather than the same grey as its blue neighbour.

   ── ONE STEP SHALLOWER THAN THE CARD ──────────────────────────────────────
   The card's `0 14px 30px -22px` is tuned for tiles separated by a 16px gap
   in both axes. Rows are stacked 12px apart in ONE axis, so each row's shadow
   falls directly onto the row beneath it — at the card's depth twenty of them
   stack into a grey haze down the column. 10px/22px/-18px is the same shape
   with the falloff pulled in enough that the gap stays clean. */
.v2x .dir-lc {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .7),
        0 1px 2px rgba(16, 24, 40, .04),
        0 10px 22px -18px rgba(var(--dtc-rgb), .34),
        0 6px 16px -14px rgba(16, 24, 40, .20);
}

body.theme-dark .v2x .dir-lc {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .05),
        0 1px 2px rgba(0, 0, 0, .42),
        0 10px 22px -18px rgba(var(--dtc-rgb), .30),
        0 6px 16px -14px rgba(0, 0, 0, .72);
}


/* ── §LCPRM-STATE) Hover and focus ───────────────────────────────────────
   Three things move: the rail grows from nothing to the row's height less its
   corner radius, the bloom swells from the corner it hangs off, and the edge
   steps up. The row's own `translateY(-2px)` is the base sheet's and is
   untouched — this pass is about surface.

   ⚠️ `border-color` IS RESTATED HERE RATHER THAN DRIVEN BY A VARIABLE. The
   base sheet's `.v2x .dir-lc:hover { border-color: transparent }` is (0,3,0)
   against §LCPRM-WASH's (0,2,0), so it wins on hover no matter what
   `--dcp-edge` says and would DROP THE EDGE ENTIRELY at exactly the moment
   the visitor is looking at the row. Same fix §TCPRM-STATE applies.

   ⚠️ THE DARK PAIR IS (0,4,1) FOR A REASON. In dark, `body.theme-dark .v2x
   .dir-lc` is (0,3,1) and out-ranks a (0,3,0) hover selector outright — so
   both the edge AND the shadow have to be restated at (0,4,1) or the row's
   entire hover state is dead after dark. The base sheet's tone-tinted hover
   shadow (`var(--dtc-shadow)`) is already in that position; this restates its
   dark equivalent so the row lifts in both themes.

   `:focus-visible` on the row's own controls is left to the base sheet, which
   paints a brand-blue ring — that is an accessibility affordance and it
   should not be tone-cycled into eight different colours. */
@media (hover: hover) {
    .v2x .dir-lc:hover {
        --dcp-bar-h: calc(100% - 28px);
        border-color: rgba(var(--dtc-rgb), calc(var(--dcp-edge) + .22));
    }

    body.theme-dark .v2x .dir-lc:hover {
        border-color: rgba(var(--dtc-rgb), calc(var(--dcp-edge) + .22));
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, .06),
            0 1px 2px rgba(0, 0, 0, .5),
            0 16px 34px -14px rgba(var(--dtc-rgb), .42),
            0 8px 20px -14px rgba(0, 0, 0, .8);
    }

    /* The bloom SWELLS from the corner it hangs off, so the light appears to
       move rather than simply brighten. §HPXP's own values. */
    .v2x .dir-lc:hover::after { opacity: 1; transform: scale(1.14); }
}

/* Tabbing through the results should light the row the same way pointing at
   it does. `:focus-within`, not `:focus-visible` — the row is not focusable
   itself; its name link, bookmark button and CTA are. */
.v2x .dir-lc:focus-within { --dcp-bar-h: calc(100% - 28px); }


/* ── §LCPRM-RESP) Phone ──────────────────────────────────────────────────
   The bloom is sized in PIXELS, so it does not shrink with the row the way
   the wash does. At 680px the base sheet compacts the row to ~72px tall and,
   below 400px, drops the description entirely — a 460px bloom on a 340px row
   is a flood across the whole surface rather than light falling on a corner.
   Both the bloom and the ring step down once, exactly as §CATPRM-RESP and
   §HPXP-RESP do at their own breakpoints.

   The wash, the rail and the edge are untouched: they are proportional
   already, and the colour system has to survive the breakpoint or the phone
   gets back the flat white row this block exists to remove. */
@media (max-width: 680px) {
    /* One step thinner. A 1.5px ring on a 340px row is proportionally twice
       what it is on a 900px one, and at that width it stops reading as a lit
       edge and starts reading as a second border. */
    .v2x .dir-lc::before,
    .v2x .dir-lc:hover::before,
    .v2x .dir-lc:focus-within::before { --dcp-t: 1.2px; }

    .v2x .dir-lc::after {
        background: radial-gradient(220px 150px at calc(100% + 24px) -34px,
            rgba(var(--dtc-rgb), var(--dcp-bloom)) 0%,
            rgba(var(--dtc-rgb), 0) 72%) no-repeat;
    }

    /* The rail's clearance is a fraction of the row, not a constant: 28px off
       a 72px row leaves a 44px stub, and off a 60px description-less row at
       400px it leaves 32px — a dash rather than a rail. */
    .v2x .dir-lc:hover,
    .v2x .dir-lc:focus-within { --dcp-bar-h: calc(100% - 18px); }
}


/* ── §LCPRM-A11Y) Touch, reduced motion, forced colours ──────────────────
   On a touch screen the ring can never be triggered by a pointer, and an
   animation that can never run is still a compositor layer per row — twenty
   per page, on every one of these sixty URLs. Suppressing it there is a
   battery decision, not a design one: the wash, the bloom, the tinted edge
   and the lift are what those visitors were getting nothing of before, and
   all four remain.

   Reduced motion: the base sheet's block already names `.dir-lc` and kills
   its transform and transitions. It cannot know about the four layers added
   above, so they are stopped here — the ring does not spin, the bloom does
   not swell, and the rail is simply PRESENT rather than growing. The
   information the hover carried is kept; the motion is not.

   Forced colours: every layer here is decoration that the OS palette has no
   opinion about, and a conic gradient under `forced-colors` renders as a
   solid block. All of it comes off, and the hairline is restored as a real
   border so the rows stay separable, which is all forced colours needs from
   them. */
@media (hover: none) {
    .v2x .dir-lc::before,
    .v2x .dir-lc:focus-within::before { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
    .v2x .dir-lc,
    .v2x .dir-lc::after { transition: none; }

    .v2x .dir-lc::before { animation: none !important; transition: none; }

    .v2x .dir-lc:hover::after,
    .v2x .dir-lc:focus-within::after { transform: none; }

    .v2x .dir-lc:hover,
    .v2x .dir-lc:focus-within { --dcp-bar-h: calc(100% - 28px); }
}

@media (forced-colors: active) {
    .v2x .dir-lc::before,
    .v2x .dir-lc::after { display: none; }

    /* The dark selector is listed for the same reason it is everywhere else
       in this block: a forced palette and a dark site preference are an
       ordinary combination, and at (0,2,0) this rule would leave the wash
       painted under it. */
    .v2x .dir-lc,
    body.theme-dark .v2x .dir-lc {
        background-image: none;
        border: 1px solid CanvasText;
        box-shadow: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §MAINFIT — the results column fits the phone                  (2026-08-19)
   ═══════════════════════════════════════════════════════════════════════════
   At ~360px the right end of every result — the bookmark and the CTA — was
   cut off the screen. Measured on /ai-directory?viewMode=list at a 360px
   viewport, before this block:

     document.documentElement.clientWidth   346
     .container                             346 @ x=0
     .v2x                                   324 @ x=11
     .dir-lab                               324 @ x=11
     .dir-g                                 316 @ x=15
     .dir-main                              376 @ x=15   <- 60px past its track
     .dir-lx / .dir-lc                      376          right edge 391 vs 346

   THIS IS THREE DEFECTS STACKED, and each earlier one HIDES the next — fix
   only the first and the clip moves rather than disappears:

     1  `.dir-main` will not shrink to its track      §MAINFIT-MIN, below
     2  the toolbar will not fit the column           §MAINFIT-TB, below
     3  two verticals give `.dir-lc-side` `width:100%`
        on a row that never wraps                     NOT IN THIS FILE

   Only the first two are shared and belong here. The third is /ai-courses and
   /deals writing a full-width, `flex:0 0 auto` side block onto a row the
   shared sheet pins to `flex-wrap:nowrap` — it squashed `.dir-lc-mid` to 0px
   (no title at all) and pushed the price and CTA out through
   `.dir-lc`'s `overflow:hidden`. Each is fixed in its own sheet, beside the
   rule that assumed a wrapping row: the 640px tier of public-ai-courses.css
   and §LCWRAP in public-deals.css. /deals is worth knowing about for another
   reason too — see §MAINFIT-MIN.

   ── SCOPE ─────────────────────────────────────────────────────────────────
   19 of the 20 verticals overflow at 360px, in BOTH view modes — the toolbar
   is shared, so this was never a list-mode bug. /cheat-sheet is the one
   exception and the reason is diagnostic rather than lucky: it ships ONE
   `.dir-tb-views` group where every other vertical ships two, so its right
   cluster is 226px instead of 350px and fits. Widest is /events at 372px.

   No vertical currently carries `.no-l` / `.no-r` — all twenty measured as
   plain `.dir-g`. It would not matter if they did: below 1024px §GCOL
   collapses every variant to `1fr`, and the fix below is on the ITEM, not on
   the track, so it is modifier-independent by construction.

   ── LOAD ORDER IS DOING THE WORK ──────────────────────────────────────────
   Every rule here is (0,2,0) — the same specificity as the base sheets' own.
   They tie, and this file is linked AFTER all three, so document order
   resolves it here. That is the same arrangement the `<link>` comments in the
   blades protect, and it is not academic: prototyping this patch from a
   `<style>` in `<head>` silently did nothing, because the base sheet's
   `<link>` sits in the BODY and therefore came later.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── §MAINFIT-MIN) 1 · the column may shrink to its track ─────────────────
   `.dir-main` is a grid item, and a grid item's default `min-width: auto`
   refuses to shrink it below its own min-content size. The `1fr` track is
   `minmax(auto, 1fr)`, so that automatic minimum becomes the TRACK's minimum
   too and the grid overflows its container instead of the column narrowing.
   Measured at 360px: `grid-template-columns` resolved to `375.656px` inside a
   316px box.

   The min-content contributions of `.dir-g`'s three children at that width:

     .dir-fx    172     the filter rail    — fits
     .dir-main  376     the results        — 60px over
     .dir-tr      0     the trending rail  — fits

   so `.dir-main` is the only one that needs this. `.dir-fx` and `.dir-tr` sit
   in FIXED tracks (236px / 268px) above 1024px and stack below it, which is
   why neither can express the fault even in principle.

   ── NOT IN A MEDIA QUERY, ON PURPOSE ──────────────────────────────────────
   `public-deals.css` §MAIN already ships exactly this rule, scoped to its own
   `.dlx-main` hook, and its comment records the desktop version of the same
   fault: "one long unbreakable string (a coupon code, a company name) push[es]
   the track past its share and squeeze[s] the rails". That is a `minmax(0,1fr)`
   middle column at full width, not a phone. The rule belongs at every width.

   /deals is therefore the one vertical that already had half of this fix —
   and it is also the page that PROVES half is not enough. Measured at 360px:
   `.dir-main` 316 (correct, thanks to `.dlx-main`) with a toolbar whose
   min-content is 377. The column stopped overflowing and the toolbar started.
   Hence part 2.

   `.dlx-main` is left in place rather than removed. It is the same element
   (`class="dir-main dlx-main"`), it is now redundant, and deleting a rule in
   another sheet to save one duplicate declaration is how a page that relied
   on it for some other reason breaks quietly. */
.v2x .dir-main { min-width: 0; }


/* ── §MAINFIT-TB) 2 · the toolbar fits the column it is given ─────────────
   What is 376px wide is not the list. Min-content inside `.dir-main` at 360px:

     .dir-tb    376     the toolbar   <- the driver
     .dir-lx    246     the results
     .dir-pg    175     the pagination

   and inside the toolbar:

     .dir-tb-left    54
     .dir-tb-right  350     `flex-wrap: nowrap` below 820px
       .dir-sortbox      170   `.dir-sort-btn` is `white-space: nowrap`
       .dir-tb-views      80   Grid | List
       .dir-tb-views      80   Pages | Infinite
       2 gaps             20
                        ---
     + 24px padding + 2px border = 376

   `.dir-tb` wraps, so its min-content is its widest ITEM, and that item is a
   350px cluster that has been told it may never break. The column's content
   box at 360px is 290px. It does not fit and no amount of shrinking makes it:
   the base already hides the toggle labels below 520px, and the sort pill's
   170px is a nowrap trigger word plus a 28px icon tile. Something has to move
   to a second line.

   ── WHY `wrap` DOES NOT COST THE 820px DESIGN ────────────────────────────
   The base chose `nowrap` deliberately, and its comment says why: "a
   full-width sort pill pushes views onto a third row and the toolbar reads as
   three floating pills instead of one control strip." That reasoning holds
   wherever the cluster FITS, and `flex-wrap: wrap` does not break a line that
   fits — it only permits one that does not. What forced the wrap
   unconditionally was the sort box's base size, `flex: 1 1 auto` resolving
   through the `width: 100%` at line 1116 of the base sheet, i.e. always the
   whole line.

   Giving it a real basis fixes that: the line breaks only when
   basis + 80 + 80 + gaps exceeds the strip. Above 520px that never happens —
   measured 384px of content box at 520 against the 372px /events needs, and
   758px at 820 — so the two-row layout is byte-for-byte what it is today. If
   a locale ever translates "Most Popular" into something long enough to break
   it, the strip now wraps instead of hanging off the page, which is the
   failure mode this whole block is about.

   `min-width: 0` from the base's own rule at line 1146 is NOT disturbed — the
   `flex` shorthand does not reset it — and it is what lets the pill shrink
   below its basis once it is on a line of its own.

   ── THE BASIS IS DECLARED TWICE ON PURPOSE ────────────────────────────────
   `170px` is /ai-directory's measured min-content and is the FALLBACK.
   `flex-basis: min-content` is the value that is actually wanted: it is 170
   here, ~192 on /events, and whatever the locale makes it elsewhere, so the
   wrap threshold is computed per vertical instead of tuned to one page. A
   browser that does not accept it drops that one declaration and keeps the
   170px, rather than dropping the whole shorthand and falling back to
   `width: 100%`, which would wrap at every width. */
@media (max-width: 820px) {
    .v2x .dir-tb-right { flex-wrap: wrap; }

    .v2x .dir-tb-sort,
    .v2x .dir-sortbox {
        flex: 1 1 170px;
        flex-basis: min-content;
    }
}

/* ── §MAINFIT-TB2) The compact tier puts the sort pill on its own row ─────
   Below 520px the cluster cannot share a line, and left to the basis above it
   breaks in the wrong place: at 360px a 170px pill and ONE 80px toggle group
   fit (260 ≤ 290) and the second group is orphaned onto a line by itself.
   Measured, and it reads exactly as badly as it sounds — a lopsided strip
   with one toggle stranded under it.

   `flex-basis: 100%` takes the choice away: the pill is a full row, and both
   toggle groups land together on the next one, held apart by the
   `justify-content: space-between` the base already sets. Grid|List left,
   Pages|Infinite right — a deliberate two-row control block rather than three
   floating pills.

   ── WHY 520 AND NOT THE 460 THE ARITHMETIC ALLOWS ────────────────────────
   The smallest breakpoint that works is ~448px — the width at which /events,
   the widest cluster at 372px, still has 372px of content box. 460 would be
   the round number above it and would leave the 460–520 band exactly as it
   is today.

   520 is chosen instead for two reasons. It is already this file's compact
   tier — the base hides `.dir-tb-views-lbl` and shrinks the sort button at
   `max-width: 520px` — so the pill going full-width lands on the SAME
   boundary as the rest of the toolbar's compact behaviour, instead of
   introducing a fourth number two tiers deep. And it leaves 72px of headroom
   at the boundary against 460's 12px, which is the difference between
   surviving a longer translation and needing this block edited again.

   The cost is that 460–520px gets the three-row toolbar rather than the
   two-row one. That band was not broken, and this is a change to it — but it
   is a layout change, not a regression: the same block, one row taller, on
   widths that are phones held sideways. */
@media (max-width: 520px) {
    .v2x .dir-tb-sort,
    .v2x .dir-sortbox { flex-basis: 100%; }
}

/* ── §CATBAND-NUDGE · narrow-window guard ─────────────────────────────────
   The arrows sit OUTSIDE the track on purpose (-17px here, -8px in the base
   sheet) so they never park on top of a card, which is a mis-click on a rail
   whose cards are links. Below 576px there is not enough shell gutter left to
   spend on that outset: measured on /events at a 320px viewport the `next`
   button reached 312px against a 306px content box, so ~6px of a real control
   sat off-screen. Pull them flush with the track at that size.

   Touch devices never see this — `@media (hover: none)` already takes the
   arrows out entirely — so this only affects a narrow desktop window, where a
   fully visible arrow beats a marginally tidier one. */
@media (max-width: 575.98px){
    .v2x .dir-cat-nav-prev{ left: 0; right: auto; }
    .v2x .dir-cat-nav-next{ right: 0; left: auto; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   §HHTILE-FILL — the orbit mark fills its tile                   (2026-08-25)
   ═══════════════════════════════════════════════════════════════════════════
   The shared hero's orbiting brand tiles, on every category and tag page and
   on all 20 vertical hubs. Twice in this pass I read them as a deliberate
   exception and left them alone — the tile is white, and a white plate with
   nothing drawn outside it needs its inset, because the ring of white IS the
   edge. That reasoning was wrong HERE for one reason I had not checked: this
   tile carries a REAL BORDER.

     border: 1px solid var(--v2-line)

   A border is drawn outside the padding box where no image can reach it, so
   the edge survives a full-bleed mark and the inset was only costing size.
   That is the same test §CTAXFILL and §HEROFILL apply on the ATD plates, and
   this tile passes it. `.dir-hh-tile` was the last brand plate on the site
   still holding its mark off the edge.

     78px tile -> 78px mark      base      (was 50px inside 14px of padding)
     66px tile -> 66px mark      <=1200px  (was 44px, padding 11px)
     54px tile -> 54px mark      <=880px   (was 36px, padding 9px)

   All three steps are `.v2x .dir-hh-tile` at (0,2,0) — two in the responsive
   block of public-dir-hh-orbit.css, one duplicated across the three base
   sheets that each declare this hero. `:has(img)` puts this at (0,2,1), which
   beats every one of them without naming a breakpoint or a sheet.

   ── NO `overflow: hidden`, AND THAT IS NOT AN OVERSIGHT ───────────────────
   Every other block in this family clips the plate so a square mark takes its
   radius. Not here: `.dir-hh-tip` — the tool's name and count — is a CHILD of
   the tile, absolutely positioned at `top: calc(100% + 8px)`, i.e. entirely
   OUTSIDE the box. Clipping the tile erases the label under every tile in the
   orbit. The radius is put on the image instead, which is all that needed it.

   ── SCOPED BY CONTENT: `:has(img)`, NOT THE BARE TILE ─────────────────────
   Half the verticals fill this tile with an inline `<svg>` category glyph
   rather than a brand logo — `category.blade.php` prints one or the other —
   and `public-tools-category.css` already sizes that glyph at 100% of the
   CONTENT box. Zeroing the padding for those would scale a 24px-grid glyph
   from 50px to 78px and turn it furry. `:has(img)` is the tile that is
   holding a real mark; everything else keeps its inset. Same carve-out
   §TCLOGO makes for the per-vertical glyphs, and the same selector §CTAXFILL
   uses for the same reason. */
.v2x .dir-hh-tile:has(img) { padding: 0; }

/* The mark takes the tile's own curve. It sits inside the 1px border, so its
   corner is a hair proud of the tile's inner one — invisible at these sizes,
   and the alternative (clipping the tile) costs the label. */
.v2x .dir-hh-tile > img { border-radius: inherit; }


/* ═══════════════════════════════════════════════════════════════════════════
   §SPARKSTILL — the search button's star stops blinking          (2026-08-25)
   ═══════════════════════════════════════════════════════════════════════════
   The sparkle on the hero's Search button ran

     .v2x .dir-hh-search-spark { animation: dir-hh-twinkle 2.6s ease-in-out infinite }
     @keyframes dir-hh-twinkle { 0%,100%{ scale(1); opacity:1 } 50%{ scale(1.14); opacity:.85 } }

   — INFINITE and NOT gated on hover, so it pulsed forever on every hero on the
   site, next to the one control a visitor is trying to type into. A thing that
   moves beside a text field competes with the caret for attention and never
   stops competing; that is the whole reason to take it off.

   UPDATED same day: the star is now GONE, not merely still. `display: none`
   stops the animation as a side effect, so this one line does both jobs and
   there is no orphaned `animation: none` left behind to puzzle over.

   The magnifier stays and is what the button now leads with. It needed no
   help here — `.dir-hh-search-btn-ico` carries no `display: none` and has
   always drawn at every width. Its twin on the `.hpx` button did, and §NOSPARK
   in homepage.css has to switch it on; see that block before assuming the two
   buttons are symmetrical.

   ── DECLARED IN THREE SHEETS, KILLED IN ONE ──────────────────────────────
   `.v2x .dir-hh-search-spark` at (0,2,0) is duplicated at line 90 of
   v2-public-directory-listing.css, shared-v2-public-best-index.css and
   v2-public-ai-model-index.css — one per hero host, the same duplication
   [[dir-hh-shared-hero]] records. This sheet loads after all three on every
   page that carries the hero, but matching their (0,2,0) would leave the
   result depending on link order, which is not a thing to depend on. Naming
   the button puts this at (0,3,0) and it wins outright.

   The `prefers-reduced-motion` block in each of those sheets already sets
   `animation: none` here; this makes that the behaviour for everyone. */
.v2x .dir-hh-search-btn .dir-hh-search-spark { display: none; }
