/* ─────────────────────────────────────────────────────────────
   Home Alt — all rules scoped to body.page-template-home-alt
   ───────────────────────────────────────────────────────────── */

body.page-template-home-alt {
    margin: 0;
    padding: 0;
    font-family: 'Barlow Condensed', sans-serif;
    background: #07030b;
    overflow-x: hidden;
}

body.page-template-home-alt *,
body.page-template-home-alt *::before,
body.page-template-home-alt *::after {
    box-sizing: border-box;
}

/* ── SECTION 1: HERO ──────────────────────────────────────────── */

body.page-template-home-alt .ha-hero {
    position: relative;
    width: 100vw;
    height: 100vh;
    background: transparent;
    overflow: visible;
}

body.page-template-home-alt .ha-orb-bg {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: calc(100vh + 800px);
    background: linear-gradient(135deg, #e8321f, #a428d4, #0d5fa3, #0a9e73, #f56000, #e8321f);
    background-size: 400% 400%;
    /* Anchors the gradient to the viewport rather than this element's own box —
       see the matching comment on .ha-intro-section/.ha-sticky-header (desktop
       media query) for why this is what makes all three read as one continuous
       sweep instead of each drifting at its own visual pace. */
    background-attachment: fixed;
    animation: colorFlow 20s ease infinite;
    z-index: 0;
    pointer-events: none;
}

body.page-template-home-alt #ha-video {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
    z-index: 1;
}

body.page-template-home-alt #ha-mask {
    display: none;
}

body.page-template-home-alt #ha-canvas {
    position: absolute;
    top: 0;
    left: 0;
    width: auto;
    height: 100vh;
    display: block !important;
    z-index: 2;
}

/* HEY THERE / MY NAME IS */
body.page-template-home-alt .ha-hero-intro {
    position: absolute;
    top: 20px;
    left: 20px;
    z-index: 10;
    display: flex;
    flex-direction: column;
    font-size: clamp(36px, 3.85vw, 90px);
    line-height: 0.8em;
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 800;
    pointer-events: none;
}

body.page-template-home-alt .ha-intro-hey,
body.page-template-home-alt .ha-intro-there {
    color: #07030b;
}

body.page-template-home-alt .ha-intro-my,
body.page-template-home-alt .ha-intro-name,
body.page-template-home-alt .ha-intro-is {
    color: #f9f5ed;
}

/* Hamburger — lives outside .ha-hero (own top-level element) so it can
   render above .ha-sticky-header regardless of the hero's own stacking
   context. position:absolute here (no positioned ancestor) resolves against
   the document like position:fixed would visually appear at page-top, so
   this matches its old "absolute inside .ha-hero" look on mobile; desktop
   switches it to true position:fixed below, since the hero itself is fixed
   there and content scrolls past underneath. */
body.page-template-home-alt .ha-hamburger {
    position: absolute;
    top: 45px;
    /* --ha-scrollbar-comp (set/cleared by setMenuOpen in template-home-alt.php)
       compensates for the nav overlay's scroll lock: locking scroll removes
       the page's own vertical scrollbar, which widens the visual viewport by
       the scrollbar's own width — this element is position:fixed on desktop
       and anchored purely via `right`, so without compensation it visibly
       jumps right by that same amount the instant the menu opens. Defaults
       to 0px (no-op) the rest of the time. */
    right: calc(55px + var(--ha-scrollbar-comp, 0px));
    display: flex;
    flex-direction: column;
    gap: 6px;
    cursor: pointer;
    z-index: 20;
}

body.page-template-home-alt .ha-hamburger span {
    display: block;
    width: 32px;
    height: 3px;
    background: #f9f5ed;
    transition: transform 0.3s ease, opacity 0.3s ease;
}

/* Morph into an X while the nav overlay is open (toggled by JS alongside
   .ha-menu.is-open below). The 3-span stack is 21px tall total (3px × 3 +
   6px gap × 2 — see the sticky-header height comment further down, which
   depends on this same 21px), so its vertical center sits 10.5px from
   either end; each outer span is 9px off that center (span 1's own center
   is 1.5px from the top, span 3's is 1.5px from the bottom — 10.5 - 1.5 =
   9), which is exactly how far each needs to translate to meet in the
   middle and form the X. The middle span just fades out, already centered. */
body.page-template-home-alt .ha-hamburger.is-open span:nth-child(1) {
    transform: translateY(9px) rotate(45deg);
}
body.page-template-home-alt .ha-hamburger.is-open span:nth-child(2) {
    opacity: 0;
}
body.page-template-home-alt .ha-hamburger.is-open span:nth-child(3) {
    transform: translateY(-9px) rotate(-45deg);
}

body.page-template-home-alt .ha-hero-spacer {
    display: none;
}

/* Drip transition — desktop only (see media query below): relies on
   .ha-hero still being position:fixed behind it to have anything worth
   revealing through the mask gaps. */
body.page-template-home-alt .ha-intro-drip {
    display: none;
}

/* Fullscreen nav overlay — dim + blur come entirely from this one element's
   own background/backdrop-filter, so whatever's actually on screen behind
   it (hero canvas, intro collage, work grid, closing statement — wherever
   the user happens to be scrolled to) shows through dimmed and softened,
   with no per-section handling needed. Sits above everything on the page
   (z-index 15, above the sticky header's 10) but below the hamburger itself
   (20), so the hamburger/X stays crisp and clickable on top of the blur
   rather than being blurred along with everything else. Opacity-only fade;
   visibility is flipped instantly on open (so it's interactive right away)
   but delayed until the fade-out finishes on close (so it doesn't vanish
   or become unclickable mid-transition) — the two states intentionally
   carry different transition rules to get that asymmetry. */
body.page-template-home-alt .ha-menu {
    position: fixed;
    inset: 0;
    z-index: 15;
    background: rgba(5, 5, 5, 0.5);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.4s ease, visibility 0s linear 0.4s;
}

body.page-template-home-alt .ha-menu.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity 0.4s ease;
}

body.page-template-home-alt .ha-menu-logo-link {
    position: absolute;
    top: 45px;
    left: 55px;
    display: block;
}

body.page-template-home-alt .ha-menu-logo {
    display: block;
    width: 110px;
    height: auto;
}

body.page-template-home-alt .ha-menu-links {
    position: absolute;
    /* Same --ha-scrollbar-comp compensation as .ha-hamburger (see its own
       comment for the full explanation) — this is anchored to .ha-menu's
       right:0 edge, which itself shifts as the scrollbar disappears/
       reappears with the scroll lock, so without this it visibly jumps too.
       Only reported as visible on CLOSE (fades out over time, so the jump
       is on screen to see) — on open it was likely happening too, just
       masked by fading in from invisible; fixed symmetrically either way. */
    right: calc(55px + var(--ha-scrollbar-comp, 0px));
    bottom: 60px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1px;
}

body.page-template-home-alt .ha-menu-links a {
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 800;
    /* 2x the original clamp(32px, 5vw, 56px) — 2026-08-08 user request to
       make each word read roughly as tall as the logo (110px wide, ~77px
       tall at that width), maybe a touch under. */
    font-size: clamp(64px, 10vw, 112px);
    /* At this size, line-height's own leading (not the 1px flex `gap` above)
       is what actually drives the visible space between stacked words — cut
       from 1.1 to 0.9 (2026-08-08 user request: at least halve the gap
       between lines). Safe to run this tight because every word here is
       all-caps with no descenders to clip. */
    line-height: 0.9;
    color: #f9f5ed;
    text-decoration: none;
    text-transform: uppercase;
    display: inline-block;
    transition: color 0.2s ease, transform 0.2s ease;
}

body.page-template-home-alt .ha-menu-links a:hover {
    color: #e6a533;
    transform: translateX(-14px);
}

/* Sticky header — hidden (slid up above the viewport) until JS adds
   .is-visible; see the desktop media query for the actual slide mechanics
   and the script block in template-home-alt.php for the scroll trigger. */
body.page-template-home-alt .ha-sticky-header {
    display: none;
}

/* ── SECTION 2: INTRO ─────────────────────────────────────────── */

body.page-template-home-alt .ha-intro-section {
    position: relative;
    background: transparent;
    /* Top/bottom cut 10% from 130px (2026-08-08 user request). */
    padding: 117px 40px;
    z-index: 1;
}

body.page-template-home-alt .ha-intro-inner {
    display: flex;
    gap: 130px;
    align-items: flex-start;
    max-width: 1400px;
    margin: 0 auto;
}

body.page-template-home-alt .ha-intro-left {
    flex: 3;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 5px;
}

body.page-template-home-alt .ha-row {
    display: flex;
    align-items: center;
    width: 100%;
    line-height: 1;
    font-size: clamp(60px, 8.5vw, 120px);
}

body.page-template-home-alt .ha-row-text {
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 800;
    font-size: clamp(60px, 8.5vw, 120px);
    line-height: 1;
    color: #f9f5ed;
    text-transform: uppercase;
    white-space: nowrap;
}

body.page-template-home-alt .ha-row-img {
    display: block;
    overflow: hidden;
    background: #e6a533;
    text-decoration: none;
    flex-shrink: 0;
    /* Matches the hero SVG letterform corners (true radius 6.01 in its 1890x1046
       viewBox — the "3.32" bezier control offsets nearby in the path data are the
       standard kappa approximation of that same radius). The hero scales
       non-uniformly with the viewport by design, so there's no single exact px
       match — 5px is representative of how it renders at typical desktop sizes. */
    border-radius: 5px;
    /* align-items:center on .ha-row centers against the font's full line-box,
       not the visible cap-height ink — user-verified in the browser that this
       nudge is needed for the sliver to read as centered against the text
       (hanging slightly below the baseline is correct/intended here). */
    margin-top: 10px;
}

body.page-template-home-alt .ha-row-img img,
body.page-template-home-alt .ha-row-img video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    /* --zoom comes from the admin dashboard's zoom slider (inline custom
       property, set in jacefolio_ha_render_sliver() — deliberately NOT an
       inline `transform`, since that would beat this rule outright and
       silently kill the hover-zoom/pan below on every sliver with a zoom
       value set). The extra *1.015 is a small permanent overscan margin —
       object-fit:cover can leave a sub-pixel gap at the container edge
       (showing its amber background through) at exactly --zoom:1; this
       hides that regardless of what the admin zoom is set to.
       Hover zoom matches .project-card-image's treatment on the portfolio
       page, plus a cursor-tracking pan (--pan-x/--pan-y set via JS on
       mousemove) layered on top so the zoomed image leans toward the cursor. */
    transform: scale(calc(var(--zoom, 1) * 1.015)) translate(var(--pan-x, 0px), var(--pan-y, 0px));
    transition: transform 0.4s ease;
}

body.page-template-home-alt .ha-row-img:hover img,
body.page-template-home-alt .ha-row-img:hover video {
    transform: scale(calc(var(--zoom, 1) * 1.08)) translate(var(--pan-x, 0px), var(--pan-y, 0px));
}

body.page-template-home-alt .ha-row-img--flex {
    flex: 1;
    flex-shrink: 1;
    height: calc(0.72em + 26px);
    /* Matches the 16px gap the --fixed and --sm slivers use on their sides.
       This sliver is the only elastic item in its row, so it just narrows
       to make room — that's expected/fine here. */
    margin-left: 16px;
}

body.page-template-home-alt .ha-row-img--fixed {
    width: 325px;
    height: calc(0.72em + 26px);
    margin-right: 16px;
}

body.page-template-home-alt .ha-row-img--sm {
    width: 240px;
    height: calc(0.72em + 26px);
    /* Was "margin: 0 16px" — that shorthand also sets margin-top:0, which
       silently overrode .ha-row-img's margin-top:10px alignment nudge and
       made this row's sliver sit visibly higher than the other two. */
    margin-left: 16px;
    margin-right: 16px;
}

body.page-template-home-alt .ha-intro-right {
    /* Was a plain flex:2 (2/5 share of .ha-intro-inner minus the 130px gap).
       Pinned to a fixed calc-based basis so it can be widened by an exact
       flat +3px without touching the gap — flex-grow:0 here means
       .ha-intro-left's flex:3 alone absorbs the remaining space, so it
       narrows by the same 3px rather than the gap changing. */
    flex: 0 0 calc(40% - 49px);
    min-width: 0;
    padding-top: 8px;
}

body.page-template-home-alt .ha-intro-right p {
    font-family: 'Open Sans', sans-serif !important;
    font-weight: 400;
    font-size: 27px;
    color: #f9f5ed;
    line-height: 1.4;
    margin: 0 0 1.2em 0;
}

body.page-template-home-alt .ha-intro-right p:last-child {
    margin-bottom: 0;
}

/* ── SECTION 3: WORK ──────────────────────────────────────────── */

body.page-template-home-alt .ha-work-section {
    background: #07030b;
    padding: 80px 60px;
    display: flex;
    align-items: flex-start;
    gap: 40px;
    position: relative;
    z-index: 2;
    /* Clips .ha-work-bg-preview's scale(1.15) overscan below — without this
       the zoomed background would visibly spill past the section's edges. */
    overflow: hidden;
}

/* Hover texture (2026-08-09 user request): whichever card is currently
   hovered gets its own thumbnail projected across the whole section
   background, zoomed in and dimmed to a faint textural tint rather than a
   real preview — JS (initWorkBgPreview in template-home-alt.php) just
   copies the hovered .ha-card-bg's background-image onto this element and
   toggles .is-active; this element owns 100% of the sizing/opacity/fade,
   so JS never has to touch those. Sits behind everything else in the
   section (z-index 0, first element in the DOM, no explicit z-index needed
   on the label/grid above it — default stacking order already puts them on
   top) since it's meant to read as ambient background texture, not
   content. background-image left unset by default (no flash-of-wrong-image
   before the first hover); opacity does all the reveal/hide work so
   swapping images while invisible is never visible either.
   background-size is `cover`, not a fixed percentage — this section can run
   far taller than any single thumbnail's own aspect ratio (many rows of
   cards stacked below it), and a fixed percentage like the original 150%
   only guarantees covering a box shaped roughly like the source image;
   anywhere taller than that, the image stopped covering and the browser's
   default background-repeat tiled it to fill the rest, which is exactly
   the hard seams/repeating edges seen live. `cover` mathematically always
   fills the box completely for any image/box combination, so tiling is
   never possible — background-repeat: no-repeat is kept anyway as a belt-
   and-suspenders default. The extra `scale(1.15)` on top of `cover` is
   what actually delivers "zoomed in," since cover alone only guarantees no
   gaps, not a zoomed-in crop. */
body.page-template-home-alt .ha-work-bg-preview {
    position: absolute;
    inset: 0;
    z-index: 0;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    /* Starts at 1x, not pre-scaled — .is-active below animates this UP to
       1.15 in step with the opacity fade (2026-08-09 user report: it "just
       flashed" in before, since scale was a hardcoded static 1.15 the whole
       time and only opacity was ever transitioning; now both animate
       together so it reads as growing in, not popping in pre-zoomed). */
    transform: scale(1);
    /* Grayscale + blur (2026-08-09 user request): at this opacity,
       full-color thumbnails were a coin flip depending on the image — some
       read as a nice tint, others clashed. Desaturating removes that
       variance entirely (every project reads the same regardless of its
       own palette), and the blur keeps whatever text/detail is in the
       source thumbnail from ever being legible enough to compete with the
       WORK label or the cards sitting on top of it — it's meant to read as
       soft ambient light, not a picture. Went 28px → 14px → 7px across
       three user follow-ups — 28px made the effect barely perceptible. */
    filter: grayscale(100%) blur(7px);
    opacity: 0;
    transition: opacity 0.5s ease, transform 0.5s ease;
    pointer-events: none;
}

body.page-template-home-alt .ha-work-bg-preview.is-active {
    opacity: 0.15;
    transform: scale(1.15);
}

body.page-template-home-alt .ha-work-label {
    /* Static (non-positioned) elements paint BELOW positioned siblings by
       default regardless of DOM order or z-index:0 — without this, the
       position:absolute .ha-work-bg-preview above (even sitting first in
       the DOM) would actually render on TOP of this label, not behind it.
       Explicit position:relative + z-index:1 puts it back in the normal
       DOM-order stacking alongside the other positioned elements here. */
    position: relative;
    z-index: 1;
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 800;
    /* Matches .ha-closing-line's font-size exactly (2026-08-08 user request).
       .ha-work-inner is flex:1 with no explicit width, so growing this label
       automatically narrows the grid to compensate — its left edge follows
       the wider label, its right edge stays exactly where it was (still
       pinned to .ha-work-section's own 60px padding), with no separate
       width/padding change needed here. */
    font-size: 15vw;
    color: #e6a533;
    writing-mode: vertical-lr;
    transform: rotate(180deg);
    flex-shrink: 0;
    line-height: 1;
    user-select: none;
}

body.page-template-home-alt .ha-work-inner {
    /* Same stacking fix and reasoning as .ha-work-label above. */
    position: relative;
    z-index: 1;
    flex: 1;
    min-width: 0;
}

body.page-template-home-alt .ha-work-grid {
    display: flex;
    flex-direction: column;
    /* +10% from 15px (2026-08-08 user request) — same bump applied
       consistently to every grid gap below, which were all 15px together. */
    gap: 16.5px;
}

body.page-template-home-alt .ha-grid-row--featured {
    display: grid;
    grid-template-columns: 3fr 2fr;
    gap: 16.5px;
    height: 500px;
}

body.page-template-home-alt .ha-grid-row--featured.ha-grid-row--reverse {
    grid-template-columns: 2fr 3fr;
}

body.page-template-home-alt .ha-card-stack {
    display: flex;
    flex-direction: column;
    gap: 16.5px;
    height: 100%;
}

body.page-template-home-alt .ha-card-stack .ha-card {
    flex: 1;
}

body.page-template-home-alt .ha-grid-row--equal {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16.5px;
    height: 250px;
}

body.page-template-home-alt .ha-card {
    position: relative;
    display: block;
    background-color: #07030b;
    text-decoration: none;
    overflow: hidden;
    /* Same corner family as .ha-row-img — see its comment for the hero-mask math. */
    border-radius: 5px;
}

/* Background image lives on its own layer (not directly on .ha-card) so it can
   be scaled/panned independently on hover without moving the gradient or text
   sitting above it. */
body.page-template-home-alt .ha-card-bg {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    z-index: 0;
    /* Same zoom + cursor-tracking-pan treatment as .ha-row-img's media — see its comment. */
    transform: scale(1) translate(var(--pan-x, 0px), var(--pan-y, 0px));
    transition: transform 0.4s ease;
}

body.page-template-home-alt .ha-card:hover .ha-card-bg {
    transform: scale(1.08) translate(var(--pan-x, 0px), var(--pan-y, 0px));
}

body.page-template-home-alt .ha-card::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.15) 60%, rgba(0, 0, 0, 0) 100%);
    z-index: 1;
}

body.page-template-home-alt .ha-card-content {
    position: absolute;
    bottom: 20px;
    left: 20px;
    z-index: 3;
}

body.page-template-home-alt .ha-card-title {
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 800;
    font-size: 18px;
    color: #f9f5ed;
    text-transform: uppercase;
    line-height: 1.1;
}

body.page-template-home-alt .ha-card-subtitle {
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 700;
    font-size: 13px;
    color: #e6a533;
    margin-top: 4px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

body.page-template-home-alt .ha-work-more {
    text-align: center;
    margin-top: 60px;
}

body.page-template-home-alt .ha-btn-outline {
    display: inline-block;
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 700;
    font-size: 16px;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: #e6a533;
    border: 2px solid #e6a533;
    background: transparent;
    padding: 16px 48px;
    text-decoration: none;
    transition: background 0.25s ease, color 0.25s ease;
}

body.page-template-home-alt .ha-btn-outline:hover {
    background: #e6a533;
    color: #07030b;
}

/* ── SECTION 4: CLOSING ───────────────────────────────────────── */

body.page-template-home-alt .ha-closing {
    position: relative;
    z-index: 2;
    background: linear-gradient(135deg, #e8321f, #a428d4, #0d5fa3, #0a9e73, #f56000, #e8321f);
    background-size: 400% 400%;
    animation: colorFlow 20s ease infinite;
    overflow: hidden;
    padding: 40px 150px 60px;
}

body.page-template-home-alt .ha-closing-line {
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 800;
    font-size: 15vw;
    line-height: 0.6;
    letter-spacing: -0.3rem;
    white-space: nowrap;
    display: block;
    margin: 0;
    padding: 20px 0 20px;
}

body.page-template-home-alt .ha-cl-dark  { color: #07030b; }
body.page-template-home-alt .ha-cl-light { color: #f9f5ed; }

/* ── SECTION 5: FOOTER ────────────────────────────────────────── */

body.page-template-home-alt .ha-footer {
    position: relative;
    z-index: 2;
    background: #07030b;
    padding: 40px 55px;
    text-align: right;
}

body.page-template-home-alt .ha-footer p {
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 700;
    font-size: 14px;
    color: #f9f5ed;
    margin: 0;
}

/* ── Pinned hero + scroll-reveal (desktop only) ─────────────────
   Hero stays fixed full-viewport; everything after it scrolls up
   over it in normal flow. The spacer holds the 1-viewport-height
   scroll distance the hero used to occupy in-flow. ───────────── */
@media (min-width: 768px) {

    body.page-template-home-alt .ha-hero-spacer {
        display: block;
        height: 100vh;
    }

    /* Drip transition between hero and intro. Sits in normal flow (own
       vertical space, not overlapping .ha-intro-section) directly above the
       intro section, full viewport width to match the hero canvas exactly
       (canvas.width = window.innerWidth in template-home-alt.php's resize()
       — a narrower strip here would visibly misalign with the mask columns
       above it). position:relative + z-index:1 (same tier as
       .ha-intro-section) is required, not decorative — a plain static
       element would paint in the in-flow-content stacking step, which is
       BELOW .ha-hero's own step (fixed + explicit z-index:0), and the hero
       would simply paint over it, hiding it entirely. The drip SVG
       (jbd-div-drip-2.svg) has its flat edge at the bottom of its viewBox
       and drips pointing up, so mask-position:bottom seats that flat edge
       flush against the top of .ha-intro-section with zero visible seam;
       the same synced-gradient treatment as .ha-orb-bg/.ha-intro-section
       (background-attachment:fixed) keeps the drips' own color in phase
       with the rest of the page's sweep rather than drifting. Where the
       mask is transparent (the gaps between drip peaks), .ha-hero — still
       fixed and still rendering at this scroll position — shows through. */
    body.page-template-home-alt .ha-intro-drip {
        display: block;
        position: relative;
        z-index: 1;
        width: 100%;
        aspect-ratio: 4390.06 / 319.39;
        -webkit-mask-image: url('../../images/jbd-div-drip-2.svg');
        mask-image: url('../../images/jbd-div-drip-2.svg');
        -webkit-mask-repeat: no-repeat;
        mask-repeat: no-repeat;
        -webkit-mask-size: 100% 100%;
        mask-size: 100% 100%;
        -webkit-mask-position: bottom;
        mask-position: bottom;
        background: linear-gradient(135deg, #e8321f, #a428d4, #0d5fa3, #0a9e73, #f56000, #e8321f);
        background-size: 400% 400%;
        background-attachment: fixed;
        animation: colorFlow 20s ease infinite;
    }

    body.page-template-home-alt .ha-hero {
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        z-index: 0;
    }

    /* No longer needs the +800px bleed tail — as a fixed child it's
       permanently behind everything, so 100vh always fully covers it. */
    body.page-template-home-alt .ha-orb-bg {
        height: 100vh;
    }

    /* Hamburger stays pinned to the viewport at all times, above both the
       hero and the sticky header (z-index 20 > header's 10). */
    body.page-template-home-alt .ha-hamburger {
        position: fixed;
    }

    /* Sticky header — height (111px) must match the JS trigger distance
       (HEADER_TRIGGER_DISTANCE in template-home-alt.php): the header is
       sized to fully cover whatever sliver of hero is still exposed at the
       moment it's triggered, so there's never a gap where the video peeks
       through mid-slide. Hidden by positioning fully above the viewport;
       JS toggles .is-visible to slide it down and back.
       111px = hamburger's bottom edge (top:45px + its own 21px rendered
       height = 66px) + another 45px, so the clear space below the
       hamburger matches the 45px above it.
       Background is the exact same gradient + colorFlow animation as
       .ha-orb-bg and .ha-intro-section, with background-attachment:fixed
       (see .ha-intro-section's comment below for why that's the part that
       actually makes them read as one continuous sweep) — this element
       exists in the DOM (just positioned off-screen) from page load, so
       its animation has been running in phase with theirs the whole time;
       sliding it into view reads as a seamless continuation rather than a
       new element appearing, exactly like hero → intro already does.
       The slide is done with `top`, not `transform`: background-attachment:fixed
       stops anchoring to the viewport (and instead anchors to the element's own
       box) on any element that has a transform applied to it — not just its
       ancestors, itself too — which was desyncing this header's gradient from
       the hero/intro's. `top` has no such side effect. */
    body.page-template-home-alt .ha-sticky-header {
        display: flex;
        align-items: center;
        position: fixed;
        top: -111px;
        left: 0;
        width: 100%;
        height: 111px;
        /* Left inset matches the hamburger's right:55px (see .ha-hamburger)
           so the logo and hamburger sit with equal clear space from their
           respective edges. */
        padding: 0 40px 0 55px;
        background: linear-gradient(135deg, #e8321f, #a428d4, #0d5fa3, #0a9e73, #f56000, #e8321f);
        background-size: 400% 400%;
        background-attachment: fixed;
        animation: colorFlow 20s ease infinite;
        z-index: 10;
        transition: top 0.5s cubic-bezier(0.22, 1, 0.36, 1);
    }

    body.page-template-home-alt .ha-sticky-header.is-visible {
        top: 0;
    }

    /* Header color-matches whatever section is currently passing beneath it:
       the animated gradient (already running on .ha-sticky-header itself,
       see above) over the hero/intro and closing sections, solid black over
       the work section so it reads as part of that section's own #07030b
       background instead of clashing with it. Implemented as a fading
       overlay rather than swapping .ha-sticky-header's own background
       outright, so the transition crossfades instead of cutting — and so
       the gradient underneath keeps running in phase the whole time,
       ready to show through the instant the overlay fades back out. Short
       duration on purpose — this is meant to read as switching right at the
       section boundary, not as a leisurely fade the user scrolls past
       mid-transition. */
    body.page-template-home-alt .ha-sticky-header-overlay {
        position: absolute;
        inset: 0;
        background: #07030b;
        opacity: 0;
        transition: opacity 0.15s ease;
        pointer-events: none;
        z-index: 0;
    }

    body.page-template-home-alt .ha-sticky-header.ha-sticky-header--black .ha-sticky-header-overlay {
        opacity: 1;
    }

    /* jbd-logo-ko.svg — the knockout/light variant, meant for dark
       backgrounds, so it reads against both the header's animated gradient
       and its solid-black overlay state. Height is 2x the hamburger's own
       rendered height (21px); width follows the SVG's own aspect ratio
       rather than being forced to match anything else. z-index keeps it
       above the header's black overlay above. */
    body.page-template-home-alt .ha-sticky-logo-link {
        position: relative;
        z-index: 1;
        display: block;
        line-height: 0;
        /* Handles the hover-out ease back to rest (see JS: mouseleave just
           removes .ha-wobble-in, and this transition picks up from wherever
           the grow animation left off) — the wobble only plays on the way
           in, never on the way out. Matches .ha-hamburger's hover transition
           exactly (0.2s ease) so both icons ease out at the same pace. */
        transition: transform 0.2s ease;
    }

    body.page-template-home-alt .ha-sticky-logo {
        height: 42px;
        width: auto;
    }

    /* The dot between the B and D is a deliberate cutout in jbd-logo-ko.svg
       (the .ha-logo-dot path itself has no fill in the source file where the
       gradient shows through) — the whole point is to let the header's own
       animated gradient background cascade through it as the page scrolls.
       That only breaks down when the header goes solid black over the work
       section (.ha-sticky-header--black, see the overlay rule above): the
       "hole" then just reads as a black dot against the black header, i.e.
       invisible. Filling it with the site's amber in that one state only —
       same 0.15s ease pace as the overlay's own black crossfade above, so
       the two read as one coordinated transition. This is the one reason
       this logo is inlined as real SVG markup rather than <img src> — an
       <img>-referenced SVG is opaque to outside CSS, so there'd be no way
       to reach this path at all otherwise. */
    body.page-template-home-alt .ha-logo-dot {
        fill: transparent;
        transition: fill 0.15s ease;
    }

    body.page-template-home-alt .ha-sticky-header.ha-sticky-header--black .ha-logo-dot {
        fill: #e6a533;
    }

    /* Nav overlay logo gets the same hover wobble as the sticky header logo
       below (wireLogoHoverWobble in template-home-alt.php handles both) —
       2026-08-08 user request. No .ha-wobble-trigger variant here, since
       that's specifically for the sticky header's "just dropped into view"
       moment, which has no equivalent for the nav overlay logo — hover only. */
    body.page-template-home-alt .ha-menu-logo-link {
        transition: transform 0.2s ease;
    }

    body.page-template-home-alt .ha-menu-logo-link.ha-wobble-in {
        animation: ha-icon-wobble-in 0.3s ease-out forwards;
    }

    /* Logo wobble — the wobble only ever plays on the way IN, never on the
       way out (JS toggles which class is applied; see playLogoAnimation in
       template-home-alt.php):
         .ha-wobble-trigger — header just dropped into view: full grow +
           wobble + settle round trip, ending back at rest.
         .ha-wobble-in — mouseenter: grows + wobbles and HOLDS there
           (forwards) for as long as the cursor stays over it. On
           mouseleave, JS just removes this class — no animation plays
           going out, the link's own `transition` above eases it back to
           rest from wherever the hold left it. */
    body.page-template-home-alt .ha-sticky-logo-link.ha-wobble-trigger {
        animation: ha-icon-wobble 0.45s ease-out;
    }

    body.page-template-home-alt .ha-sticky-logo-link.ha-wobble-in {
        animation: ha-icon-wobble-in 0.3s ease-out forwards;
    }

    @keyframes ha-icon-wobble {
        0%   { transform: scale(1) rotate(0deg); }
        35%  { transform: scale(1.15) rotate(-6deg); }
        55%  { transform: scale(1.12) rotate(4deg); }
        75%  { transform: scale(1.1) rotate(-2deg); }
        100% { transform: scale(1) rotate(0deg); }
    }

    @keyframes ha-icon-wobble-in {
        0%   { transform: scale(1) rotate(0deg); }
        40%  { transform: scale(1.15) rotate(-6deg); }
        65%  { transform: scale(1.1) rotate(4deg); }
        100% { transform: scale(1.08) rotate(0deg); }
    }

    /* Hamburger — plain grow on hover, no wobble. A simple transition (not
       an animation) handles both directions symmetrically, so there's no
       snap-back concern here the way there was with the logo's wobble. */
    body.page-template-home-alt .ha-hamburger {
        transition: transform 0.2s ease;
    }

    body.page-template-home-alt .ha-hamburger:hover {
        transform: scale(1.1);
    }

    /* The intro section can no longer rely on a transparent background
       revealing the fixed hero's gradient tail underneath it — since the
       hero is now pinned and the intro scrolls independently over it, a
       transparent intro would just show the hero's gradient sitting
       static on screen, out of sync with the scrolling intro content
       (and the hero's "HEY THERE MY NAME IS" text bleeding through).
       Instead give the intro section its own copy of the same gradient,
       running the same animation, so the two read as one continuous
       flowing background across the full scroll.
       background-attachment:fixed is what actually keeps them in visual
       sync, not just the shared animation/duration: background-size:400%
       is computed relative to each element's OWN box, and the hero
       (100vh), this section (tall, variable height), and the sticky header
       (111px) are wildly different sizes — so at the "same" animation %,
       each one's gradient image projects differently, and colors land in
       different spots (this is what "each section has its own pace"
       looked like). With attachment:fixed, background-size/position are
       instead computed relative to the viewport for all three, so the
       same animation frame always renders identically regardless of which
       element happens to be on screen at that pixel — true one-canvas
       continuity, not just matching keyframes. */
    body.page-template-home-alt .ha-intro-section {
        background: linear-gradient(135deg, #e8321f, #a428d4, #0d5fa3, #0a9e73, #f56000, #e8321f);
        background-size: 400% 400%;
        background-attachment: fixed;
        animation: colorFlow 20s ease infinite;
    }

    /* Centered-headline → two-column slide-in. Widths never change here —
       transform is the only thing moving. Same latched-boolean +
       CSS-transition pattern and pace as the work section below (transform
       0.8s cubic-bezier(0.22, 1, 0.36, 1)): JS (updateIntroState in
       template-home-alt.php) just measures a single trigger scroll position
       once and toggles the end-state transform past it; this transition does
       the actual animating. Both columns share the same transition so they
       read as one motion — the paragraph slides fully off/on screen rather
       than fading, matching the headline's own transform-only treatment. No
       hidden-by-default CSS rule on purpose, so the paragraph stays in place
       if JS never runs. */
    body.page-template-home-alt .ha-intro-left,
    body.page-template-home-alt .ha-intro-right {
        transition: transform 0.8s cubic-bezier(0.22, 1, 0.36, 1);
        will-change: transform;
    }

    /* Closing section picks up the same fixed-to-viewport gradient trick as
       .ha-orb-bg/.ha-intro-section/.ha-sticky-header above (background/size/
       animation already set in the base rule; attachment is the only piece
       that needs a desktop-only override), so its animated background
       resumes exactly in phase with the rest of the page's sweep after the
       work section's solid black gap — true continuity, not just a
       matching palette. */
    body.page-template-home-alt .ha-closing {
        background-attachment: fixed;
    }

    /* Intro headline rows — same scroll-scrubbed parallax technique and
       easing as the closing statement below (shares CLOSING_PARALLAX_EASE,
       but its own shorter INTRO_ROWS_PARALLAX_DISTANCE — see
       updateIntroRowsParallax in template-home-alt.php), so deliberately
       NO CSS transition here for the same reason as the closing lines: JS
       drives transform every frame via its own requestAnimationFrame ease,
       and a CSS transition would fight those per-frame updates. Rows
       alternate slide-in direction and converge to rest (translateX(0), i.e.
       exactly where they already sit in normal flow) at 75% of the way to
       the paragraph's own latch trigger, then hold there — locked and
       readable — for the remaining scroll distance before it fires. */
    body.page-template-home-alt .ha-row {
        will-change: transform;
    }

    /* Closing statement parallax — deliberately NO CSS transition here.
       Unlike every other reveal on this page (which latches a boolean and
       lets a CSS transition animate to rest), this one is scroll-scrubbed:
       JS (updateClosingParallax in template-home-alt.php) drives transform
       every frame via its own requestAnimationFrame-based ease toward a
       scroll-derived target, so the trailing/momentum feel comes from that
       loop, not from a CSS transition (which would fight the per-frame
       scroll updates and stutter). */
    body.page-template-home-alt .ha-closing-line {
        will-change: transform;
    }

    /* Work section reveal — same latched-transform pattern and pace as the intro
       headline/paragraph above (transform 0.8s cubic-bezier(0.22, 1, 0.36, 1)),
       but with a much shorter travel distance (see WORK_SLIDE_DISTANCE in the
       script block) so the label/grid read as already mid-arrival rather than
       sliding the full width of the viewport. No hidden-by-default CSS rule on
       purpose, same reasoning as the intro columns — JS sets the initial offset. */
    body.page-template-home-alt .ha-work-label,
    body.page-template-home-alt .ha-work-grid {
        transition: transform 0.8s cubic-bezier(0.22, 1, 0.36, 1);
        will-change: transform;
    }

}

/* ── Gradient animation ───────────────────────────────────────── */
/* Loops both X and Y around the 400%-oversized gradient canvas (an
   irregular 5-stop loop, not a symmetric box/diamond, so it reads as
   drifting/floating rather than mechanically bouncing corner to corner) —
   2026-08-08 user request. The original version only ever animated X at a
   fixed Y:50%, which meant it only ever sampled a horizontal strip through
   the gradient's vertical middle — whatever colors happened to sit in the
   canvas's upper/lower bands (this is why yellow/red/orange, which sit
   toward one side, never reached the upper-left) were simply never in that
   strip at all, no matter how long it ran. */
@keyframes colorFlow {
    0%   { background-position: 20% 20%; }
    20%  { background-position: 80% 15%; }
    40%  { background-position: 90% 75%; }
    60%  { background-position: 50% 90%; }
    80%  { background-position: 10% 70%; }
    100% { background-position: 20% 20%; }
}
