@import '_content/SkillMatch.Shared/SkillMatch.Shared.Client.f2h5ny8bqg.bundle.scp.css';

/* /Components/Pages/CariJasa.razor.rz.scp.css */
/* Scoped styles for the public catalog directory (/cari).

   Colours come from theme.css: --bg-page, --bg-white, --text-primary, --text-secondary,
   --border, --blue-primary. Those are the real token names and they flip with the theme,
   so this page follows light and dark without a second set of rules here. An earlier pass
   invented --card-bg and --border-color, which resolve to nothing: the fallbacks silently
   held the page in light colours. Check theme.css before adding a token to this file.

   .cj is the full-bleed surface, .cj-inner the readable column. The landing stylesheet
   paints body a dark marketing navy for every page on LandingLayout; this page opts out of
   that because it is a utility page, and because its rows link to the catalog pages, which
   are ordinary themed documents. */

.cj[b-bew4kst3lp] {
    background: var(--bg-page);
    min-height: 100vh;
}

/* 1120px to match the landing's container, because this page is marketing surface and the
   two should line up. It is NOT capped to --frame-content (the ~880 app column): the
   .doc-scroll marker on the root drops base.css's frosted side gutters for this page, the
   same escape the landing uses, so there is no frost left for wider content to slide under
   and get sliced by. If that marker is ever removed, this width has to come back down to
   var(--frame-content) or the first and last card in every row will be half-blurred. */
.cj-inner[b-bew4kst3lp] {
    max-width: 1120px;
    margin: 0 auto;
    padding: 1.5rem 1rem 4rem;
}

/* Back link ----------------------------------------------------------- */

/* Quiet by default: it is a way out, not the thing the page wants you to do. Sized to the
   44px touch target via padding rather than a fixed height, so the text stays on the same
   baseline grid as the heading below it. */
.cj-back[b-bew4kst3lp] {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-bottom: 0.9rem;
    padding: 0.4rem 0.6rem 0.4rem 0.35rem;
    margin-left: -0.35rem;          /* pull the icon back to the text margin */
    border-radius: 0.5rem;
    font-size: 0.85rem;
    font-weight: 500;
    text-decoration: none;
    color: var(--text-secondary);
    transition: color 0.15s ease, background-color 0.15s ease;
}

.cj-back:hover[b-bew4kst3lp] {
    color: var(--text-primary);
    background: color-mix(in srgb, var(--text-primary) 6%, transparent);
}

.cj-back:focus-visible[b-bew4kst3lp] {
    outline: 2px solid var(--blue-primary);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .cj-back[b-bew4kst3lp] {
        transition: none;
    }
}

/* Heading ------------------------------------------------------------- */

.cj-head[b-bew4kst3lp] {
    margin-bottom: 1.25rem;
}

.cj-title[b-bew4kst3lp] {
    font-size: 1.5rem;
    line-height: 1.25;
    font-weight: 700;
    margin: 0 0 0.4rem;
    color: var(--text-primary);
}

.cj-sub[b-bew4kst3lp] {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--text-secondary);
}

/* Search box ---------------------------------------------------------- */

/* Sticky so the box stays reachable while scrolling a long list. top:0 rather than a
   measured header offset: this page's layout has no fixed header to clear. */
.cj-searchbar[b-bew4kst3lp] {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 0.9rem;
    margin-bottom: 0.75rem;
    border-radius: 0.75rem;
    background: var(--bg-white);
    border: 1px solid var(--border);
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.04);
}

[b-bew4kst3lp] .cj-search-icon {
    flex: 0 0 auto;
    color: var(--text-secondary);
}

.cj-input[b-bew4kst3lp] {
    flex: 1 1 auto;
    min-width: 0;
    border: 0;
    background: transparent;
    /* 1rem keeps iOS Safari from zooming the viewport on focus, which on this page would
       throw the reader out of the list they were scanning. */
    font-size: 1rem;
    color: var(--text-primary);
}

.cj-input:focus[b-bew4kst3lp] {
    outline: none;
}

/* The visible focus ring belongs on the bar, not the bare input, so the whole control
   reads as focused. Scoped to this one control rather than a blanket ring. */
.cj-searchbar:focus-within[b-bew4kst3lp] {
    border-color: var(--blue-primary);
    box-shadow: 0 0 0 3px rgb(37 99 235 / 0.15);
}

.cj-count[b-bew4kst3lp] {
    margin: 0 0 0.6rem;
    min-height: 1.1rem;
    font-size: 0.8rem;
    color: var(--text-secondary);
}

/* The list ------------------------------------------------------------ */

.cj-list[b-bew4kst3lp] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0.6rem;
    /* auto-fill + minmax, so the column count follows the space actually available inside
       the 880px cap instead of hand-tuned breakpoints: one column on a phone, two in the
       full app column. The explicit minmax lower bound also replaces the implicit `auto`
       track, which sizes to max-content and blew the single mobile column out to 939px
       inside a 390px page, pushing the price off screen. */
    grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
}

/* Hidden by the filter script. Not display:none on .cj-item directly, so the rule stays
   one class the script toggles rather than inline styles it has to remember to undo. */
.cj-item[hidden][b-bew4kst3lp] {
    display: none;
}

/* Grid items default to min-width:auto, which is min-content, so constraining the track
   alone is not enough: the item itself has to be allowed to shrink before the ellipsis
   rules further down can ever apply. Same reason .cj-body carries min-width:0. */
.cj-item[b-bew4kst3lp],
.cj-row[b-bew4kst3lp] {
    min-width: 0;
}

.cj-row[b-bew4kst3lp] {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    padding: 0.75rem;
    border-radius: 0.75rem;
    text-decoration: none;
    background: var(--bg-white);
    border: 1px solid var(--border);
    transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.cj-row:hover[b-bew4kst3lp] {
    border-color: var(--blue-primary);
    box-shadow: 0 2px 8px rgb(0 0 0 / 0.06);
}

.cj-row:focus-visible[b-bew4kst3lp] {
    outline: 2px solid var(--blue-primary);
    outline-offset: 2px;
}

.cj-row:active[b-bew4kst3lp] {
    transform: scale(0.995);
}

@media (prefers-reduced-motion: reduce) {
    .cj-row[b-bew4kst3lp] {
        transition: none;
    }

    .cj-row:active[b-bew4kst3lp] {
        transform: none;
    }
}

/* Row contents -------------------------------------------------------- */

.cj-avatar-wrap[b-bew4kst3lp] {
    flex: 0 0 auto;
}

.cj-avatar[b-bew4kst3lp] {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 50%;
    object-fit: cover;
}

.cj-avatar-fallback[b-bew4kst3lp] {
    font-size: 0.85rem;
    font-weight: 600;
    color: #FFFFFF;
}

/* min-width:0 so the long service line can ellipsis instead of forcing the row wider
   than its container. Without it a flex child refuses to shrink below its content. */
.cj-body[b-bew4kst3lp] {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.cj-name-row[b-bew4kst3lp] {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    min-width: 0;
}

/* The name WRAPS where the two lines below truncate. It is the thing a reader is scanning
   for, and at two and three columns the cards are narrow enough that ellipsis was cutting
   real names ("Deposit Fence ..."). Grid rows stretch to their tallest card, so a wrapped
   name costs alignment nothing. overflow-wrap covers a single unbroken long name. */
.cj-name[b-bew4kst3lp] {
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.25;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

[b-bew4kst3lp] .cj-check {
    flex: 0 0 auto;
}

/* The rating is the reason a mitra is on this page at all (only 4.0+ are listed), so it sits
   on its own line rather than being folded into the service text. */
.cj-rating[b-bew4kst3lp] {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-primary);
}

.cj-rating-count[b-bew4kst3lp] {
    font-weight: 400;
    color: var(--text-secondary);
}

/* City rows carry no avatar and no price, so the body takes the full width and the chevron
   stands in for the price block as the right-hand anchor. */
.cj-city-row[b-bew4kst3lp] {
    justify-content: space-between;
}

.cj-city-row .cj-name[b-bew4kst3lp] {
    font-size: 1rem;
}

[b-bew4kst3lp] .cj-chevron {
    flex: 0 0 auto;
    color: var(--text-secondary);
}

.cj-city[b-bew4kst3lp],
.cj-services[b-bew4kst3lp] {
    font-size: 0.8rem;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cj-price[b-bew4kst3lp] {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
}

.cj-price-from[b-bew4kst3lp] {
    font-size: 0.65rem;
    color: var(--text-secondary);
}

.cj-price-value[b-bew4kst3lp] {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text-primary);
    white-space: nowrap;
}

/* Empty states -------------------------------------------------------- */

.cj-empty[b-bew4kst3lp],
.cj-empty-state[b-bew4kst3lp] {
    margin: 1.5rem 0 0;
    text-align: center;
    font-size: 0.9rem;
    color: var(--text-secondary);
}

.cj-empty[hidden][b-bew4kst3lp] {
    display: none;
}

/* Larger screens ------------------------------------------------------ */

/* Only spacing and type scale change here. The column count is handled by auto-fill above,
   so there is no breakpoint that can disagree with the 880px cap. */
@media (min-width: 40rem) {
    .cj-inner[b-bew4kst3lp] {
        padding: 2rem 1.5rem 4rem;
    }

    .cj-title[b-bew4kst3lp] {
        font-size: 1.85rem;
    }
}
/* /Components/Pages/Error.razor.rz.scp.css */
.err-page[b-eeh74bcra8] {
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: var(--bg-page);
}

.err-card[b-eeh74bcra8] {
    width: 100%;
    max-width: 420px;
    background: var(--bg-white);
    border: 1px solid var(--border);
    border-radius: 20px;
    box-shadow: var(--shadow-sm);
    padding: 32px 24px;
    text-align: center;
}

.err-brand[b-eeh74bcra8] {
    font-size: 15px;
    font-weight: 800;
    color: var(--blue-primary);
    letter-spacing: 0.3px;
    margin-bottom: 20px;
}

.err-icon[b-eeh74bcra8] {
    width: 64px;
    height: 64px;
    margin: 0 auto 16px;
    border-radius: 50%;
    background: var(--red-light);
    color: var(--red);
    display: flex;
    align-items: center;
    justify-content: center;
}

.err-title[b-eeh74bcra8] {
    font-size: 20px;
    font-weight: 700;
    color: var(--text-primary);
    margin: 0 0 8px;
}

.err-desc[b-eeh74bcra8] {
    font-size: 14px;
    color: var(--text-secondary);
    margin: 0 0 6px;
    line-height: 1.5;
}

.err-desc-en[b-eeh74bcra8] {
    font-size: 13px;
    color: var(--text-muted);
    margin: 0 0 20px;
    line-height: 1.5;
}

/* .btn on an anchor: keep the link look out of it */
.err-home[b-eeh74bcra8] {
    text-decoration: none;
}

.err-ref[b-eeh74bcra8] {
    margin: 16px 0 0;
    font-size: 11px;
    color: var(--text-muted);
    word-break: break-all;
}

.err-ref code[b-eeh74bcra8] {
    font-size: 11px;
}
/* /Components/Pages/Landing.razor.rz.scp.css */
/* ====================================================================
   Landing page — clean light enterprise design.
   Web-only (not in Shared — MAUI/WebAdmin don't see this page).

   ALWAYS LIGHT, on purpose. This page does NOT follow the app's theme
   toggle or the visitor's OS preference: it renders on the marketing
   host with no Blazor circuit, and a single fixed appearance is the one
   we can actually design, review and screenshot. Do not wire
   [data-theme="dark"] overrides in here — the app's own pages are the
   place for that.

   Every colour below comes from the token block on .landing. Add a
   token rather than a new literal, so a palette change stays one edit.
   ==================================================================== */

.landing[b-x69esb1yx8] {
    --landing-max: 1120px;
    --landing-gap: 24px;

    /* Surfaces — white page, very light grey for alternating bands. */
    --l-bg: #FFFFFF;
    --l-bg-alt: #F5F7FA;
    --l-bg-blue: #F2F7FD;   /* the agen band, so the offer reads as its own thing */
    --l-card: #FFFFFF;
    --l-border: #E3E8EF;
    --l-border-strong: #CBD5E1;
    --l-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 4px 16px rgba(15, 23, 42, 0.06);
    --l-shadow-lift: 0 4px 8px rgba(15, 23, 42, 0.06), 0 12px 28px rgba(15, 23, 42, 0.10);

    /* Text — near-black for headings, two greys below it. */
    --l-text: #0F172A;
    --l-text-2: #475569;
    /* Measured, not eyeballed: this is the lightest grey that still clears 4.5:1 on
       white. The first pass used #7A899C, which came out at 3.57:1 and failed on the
       footnotes, the footer line and the list summary — all of them small text. */
    --l-text-3: #64748B;

    /* Brand blue, plus a pale wash for tints on white. */
    --l-blue: #1565C0;
    --l-blue-deep: #0D47A1;
    --l-blue-pale: #EAF2FC;

    /* Green means "this territory is still open" — nothing else. It ALSO marks the
       "Tawarkan Jasa" provider path on this page (the earn side of the marketplace),
       which mirrors the blue "Cari Jasa" hire side. Two values on purpose: --l-green
       is for FILLS and dots, where contrast is judged against the shape, and
       --l-green-text is the darker one required whenever green carries actual words
       (the "TERBUKA" tag is 11px, and the lighter green measured 3.3:1 against white). */
    --l-green: #16A34A;
    --l-green-deep: #15803D;   /* hover/active for the green CTA — same value as -text, named for its role */
    --l-green-text: #15803D;
    --l-green-pale: #E9F7EF;

    /* Secondary text on the two blue fields. PURE white, not a translucent one.
       Measured: even at full white, text on the lightest end of the hero gradient
       only reaches 4.74:1 — there is no headroom left to spend on transparency.
       Hierarchy on blue therefore comes from size and weight, never from opacity.
       The gradients below are capped at #1B73D4 for the same reason. */
    --l-on-blue: #FFFFFF;

    min-height: 100dvh;
    /* Positioned so the absolutely-placed face dots resolve against THIS box rather
       than the initial containing block. z-index stays auto, so no new stacking
       context: .after-hero's z-2 and the navbar's z-100 still compare as before. */
    position: relative;
    background: var(--l-bg);
    color: var(--l-text-2);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;

    /* `clip`, NOT `hidden`. They look identical but differ in one decisive way:
       overflow-x: hidden turns this element into a SCROLL CONTAINER, and every
       position: sticky descendant then sticks to .landing's scrollport instead of
       the viewport. Since .landing is content-height it never scrolls, so the
       sticky navbar and the sticky story column would silently never pin — they
       would just sit where they were written. `clip` trims the same overflow
       without creating a scrollport, so sticky keeps using the viewport and the
       document keeps doing the scrolling. */
    overflow-x: clip;

    /* The hero-dimming animation lives on .hero-inner but its timeline is DECLARED
       on .after-hero — a named view-timeline is only visible to the declaring
       element's own descendants unless an ancestor hoists it into scope, which is
       exactly what this does. */
    /* --hero-swap and --story-earn were removed on 7/27 with the two crossfades they
       drove (see the HERO and story blocks). A name listed here that nothing declares
       is harmless, but it reads as a live feature — keep this list honest. */
    timeline-scope: --cover-over, --agen-fill,
                    --agen-s1, --agen-s2, --agen-s3, --agen-s4, --agen-s5,
                    /* The six section timelines the navbar's "you are here" reads. They
                       are declared on the sections, deep inside .after-hero, and consumed
                       by links in the sticky bar — a different branch of the tree
                       entirely, which is exactly what timeline-scope is for. */
                    --sec-cara, --sec-fitur, --sec-download,
                    --sec-mitra-cara, --sec-mitra-alasan,
                    /* The agen face's two numbered chapters, one link each since 7/30 —
                       --sec-agen was one name for nine screens and is gone. */
                    --sec-agen-wilayah, --sec-agen-keuntungan,
                    /* The closing band. Used for its HANDOVER BOUNDARY only — its ranges
                       cannot complete at the foot of the page, so its bar fills on the
                       document's own scroll instead. See .nav-link-cta. */
                    --sec-cta;
}

.landing-container[b-x69esb1yx8] {
    max-width: var(--landing-max);
    margin: 0 auto;
    padding: 0 var(--landing-gap);
}

/* ====== FACE SWITCHING — three audiences on one page ==================
   /welcome, /jadi-mitra and /jadi-agen were three separate pages with three
   heroes for one day. They are ONE page since 7/28, and the three buttons in
   the hero choose who it is talking to: the hero, the nav links, the body
   sections and the closing banner all change together (Susanti: "ketika user
   click button di hero, bagian content yang berubah").

   NO SCRIPT. The marketing host serves this page as static HTML with no Blazor
   circuit, so the state has to be something the browser itself can hold: three
   same-named radios as the first children of .landing, and `~` to reach
   everything after them. The agen tab strip and the phone menu already work
   this way; this is the same trick one size up.

   IT DOES ROTATE — but only before the reader has touched anything, and only
   from JavaScript (initFaceRotator in landing-motion.js). This block used to say
   "nothing rotates", which was the right answer on 7/23, when the hero swapped
   under a reader mid-scroll, and stayed right until the switcher became three
   unlabelled dots. Dots cannot say "this page has three audiences"; three heroes
   going past can. So the motion came back on strict terms, every one of them
   enforced in that function: stops dead on the first scroll, press, key or
   touch and never restarts; never runs on the two routes that preselect a face;
   never runs under reduced motion. Nothing in THIS file rotates anything — with
   no script the page is one face, changed by press, exactly as before.

   ALL THREE FACES ARE IN THE DOCUMENT — hidden, not absent. That is what makes
   the switch instant, and it is what lets /jadi-mitra and /jadi-agen stay real,
   shareable, indexable addresses: the route only decides which radio ships
   `checked` (see ResolveRoute in the component). The cost is stated plainly —
   one document now carries three audiences' markup, so anything expensive added
   to one face is paid for on all three.
   ==================================================================== */

.face-radio[b-x69esb1yx8] {
    /* Focusable but invisible. The clip-path recipe, NOT opacity:0 — that one was
       measured to drop an input out of Chromium's tab order (the same trap the
       phone menu's toggle hit; see the note on .nav-toggle). Keeping it focusable
       is what gives the switcher arrow-key navigation for free, which is what a
       segmented control should have had all along. */
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.face[b-x69esb1yx8] {
    display: none;
}

/* Both halves of each pair earn their place: the three heroes are DIRECT
   siblings of the radios, while the nav's link groups, the body blocks and the
   banner's three endings are descendants of a sibling. */
#face-market:checked ~ .face-market[b-x69esb1yx8],
#face-market:checked ~ * .face-market[b-x69esb1yx8],
#face-mitra:checked ~ .face-mitra[b-x69esb1yx8],
#face-mitra:checked ~ * .face-mitra[b-x69esb1yx8],
#face-agen:checked ~ .face-agen[b-x69esb1yx8],
#face-agen:checked ~ * .face-agen[b-x69esb1yx8] {
    display: var(--face-display, block);
}

/* What a shown face displays AS. Two exceptions declare themselves here rather
   than being special-cased in the rule above: the hero is a centred flex column,
   and the nav groups must vanish as boxes or their links would stack in one
   column inside the bar's flex row instead of sitting in it.

   The hero also has to REPEAT the hidden state. `.hero` sets display:flex further
   down this file at exactly the same specificity as `.face`, so source order handed
   it the win and all three heroes rendered at once — measured, not guessed: every
   .hero reported display:flex with only one radio checked.

   🚨 …AND SINCE 7/30 THE HEROES ARE EXEMPT FROM THE DISPLAY MECHANISM ENTIRELY. All three
   are rendered at once on purpose — stacked in one grid cell and shown or parked with
   transform/opacity/visibility, so the whole panel can be seen sliding (see THE WHOLE HERO
   SLIDES further down). That means BOTH halves below are load-bearing and neither may go:
   `display: flex` overrides the `.face { display: none }` default for heroes specifically
   (0,2,0 beats 0,1,0), and `--face-display: flex` makes the checked-face rule above resolve
   to flex rather than to its `block` fallback, which would drop the hero's centred column.
   Set one without the other and the hero either vanishes or loses its layout. */
.hero.face[b-x69esb1yx8] {
    display: flex;
    --face-display: flex;
}

/* Both are flex ROWS, so a face wrapper inside them must not be a box: the links
   would lay out as one column inside the bar, and the contact button would sit in a
   column of its own. */
.nav-links .face[b-x69esb1yx8],
.nav-actions .face[b-x69esb1yx8] { --face-display: contents; }

/* Face-aware wording INSIDE a paragraph — the download band's sub line, which is the one
   sentence that has to differ between the customer and the mitra reading of the same
   section. Without this the span would be a block and split the paragraph in two. */
.section-sub .face[b-x69esb1yx8] { --face-display: inline; }

/* ── The swap itself ──
   `display` cannot be transitioned, so the arriving face fades in on its own. It matters
   more than it sounds: landing-motion.js walks through all three faces while the reader is
   still at the top (see initFaceRotator), and a hard cut every five seconds reads as the
   page glitching rather than as it turning a page.

   🚨 IT FADES THE HERO'S CONTENT, NOT THE HERO (fixed 7/30 — Susanti: "saat hero rotate,
   ada white flash"). The fade used to sit on `.hero` itself, and that is exactly what the
   flash was: the outgoing hero is removed by `display: none` in the same frame the incoming
   one starts at `opacity: 0`, so for 420ms NO hero was painted at full strength and what
   showed through was .landing's white ground. A cross-fade is impossible here by
   construction — display:none takes the outgoing face away instantly, there is nothing left
   to fade out — so the fix is the other way round: let the arriving hero paint its
   BACKGROUND immediately, at full opacity, and fade only the column of content inside it.
   The gap disappears because there is no longer a moment with nothing behind the text.
   Do not move this back onto .hero.

   ON THE HERO ONLY. The body blocks below it swap too, but they are under the fold at the
   moment of the swap — animating them would be paying for something nobody can see, on the
   page whose whole point is painting instantly.

   It also runs on first paint, which is deliberate: the content arriving rather than
   appearing is the same 420ms either way, and a special case to suppress it would cost more
   than it saves. Guarded on reduced motion like every other animation here; without it the
   swap is the hard cut, which is correct for that reader.

   THE CONTENT FADE THAT USED TO LIVE HERE IS GONE (7/30). It faded .hero-inner while the
   hero's background cut instantly — the fix for the white flash, and correct until the whole
   panel started moving. Now that the panel itself travels, fading its contents on top of the
   travel read as two effects arguing; the slide carries the whole thing. */

/* ── THE WHOLE HERO SLIDES (Susanti, 7/30: "aku mau SELURUH HERO SLIDE") ──
   Two earlier attempts are recorded here because each one taught the constraint that shapes
   this one:

     The fade on .hero — a white flash on every swap. `display: none` removed the outgoing
     hero in the same frame the incoming one started at opacity 0, so for 420ms the page's
     white ground was what showed.
     The slide on .hero-inner — no flash, but only the words moved; the panel itself cut.

   🚨 THE RULE BEHIND BOTH: A PANEL CANNOT BE SEEN LEAVING IF IT IS ALREADY GONE. Sliding the
   whole hero means the outgoing one has to still be rendered while it travels, and that is
   why the three heroes are now stacked in one grid cell (.hero-stack in the markup) and
   switched by transform/opacity/visibility instead of display. Do not put display:none back
   on .hero: it takes the leaving panel away and there is nothing left to animate.

   WHO KNOWS WHAT, and this is the whole division of labour:
     CSS  — where a hero rests (on screen) and where it parks (one screen to the side).
     JS   — which hero is LEAVING, and which way the pair should travel. A selector cannot
            see the face that was showing a moment ago, so landing-motion.js marks the
            outgoing one `.is-leaving` and stamps data-face-dir="back" for a step back up
            the order (pelanggan, mitra, agen). Everything else is forward, INCLUDING the
            rotator's wrap from agen back to pelanggan — a loop that reversed at the seam
            would read as bouncing off the end. */
.hero-stack[b-x69esb1yx8] {
    display: grid;
    /* Every hero in the same cell. Measured before this change: all three are exactly
       100svh − 64px, so sharing a cell changes no height. */
    grid-template-areas: "hero";
    /* The clip that makes the slide possible — a panel one screen to the side must not be
       reachable, paintable or scrollable-to. .landing's own overflow-x: clip covers the
       document, this covers the stack. */
    overflow: hidden;
    /* Sticky, z-index and hero-sleep all moved here off .hero: what pins under the bar and
       is covered by .after-hero is the stack, not whichever face is on top. top is the
       navbar's height for the reason the old .hero rule gave — at 0 it crawled 64px up
       before locking and the swap opened with a lurch. */
    position: sticky;
    top: 64px;
    z-index: 0;
}

.hero.face[b-x69esb1yx8] {
    grid-area: hero;
    /* Parked one full screen to the RIGHT by default: the next face in the order is always
       the one waiting to come in from that side. */
    transform: translateX(100%);
    opacity: 0;
    visibility: hidden;
    /* visibility is what keeps a parked hero from costing anything — the market face's
       aurora blobs and their 48px blurs would otherwise stay composited behind the page,
       which is the 2026-07-21 lag report. It flips LATE (after the travel) on the way out
       and IMMEDIATELY on the way in, which is what the two transition lists below say.

       ── TIMING, and all three numbers were chosen together (Susanti, 7/30: "terlalu cepat,
          perlambat dan buat smooth") ──
       820ms, up from 480. A panel crossing a whole screen is a long way to travel, and at
       480ms it covered that distance fast enough to read as a snap rather than a glide.

       ease-IN-OUT, not the ease-out it had. Both panels start at rest and end at rest, so
       the curve should too: ease-out began at full speed, which is what "not smooth" was —
       the motion appeared already in progress on the first frame.

       OPACITY IS ALMOST INSTANT (120ms) ON PURPOSE. It is not a crossfade: two solid panels
       pass each other, and fading them across a long travel would show one THROUGH the other
       for most of the way. Opacity exists here only so a parked panel is invisible; it gets
       out of the way at the start and the slide does the rest.

       🚨 The visibility delay must equal the transform duration, and FACE_TRAVEL_MS in
       landing-motion.js must equal it too — that is the timer that releases the leaving
       panel. Three numbers, one value: change one and change all three, or a panel will be
       hidden or released mid-flight. */
    transition: transform 820ms cubic-bezier(0.65, 0, 0.35, 1),
                opacity 120ms ease,
                visibility 0s linear 820ms;
}

/* A step BACK up the order reverses both sides: the waiting faces park on the left. */
.landing[data-face-dir="back"] .hero.face[b-x69esb1yx8] {
    transform: translateX(-100%);
}

/* The one on screen. */
#face-market:checked ~ * .hero.face-market[b-x69esb1yx8],
#face-mitra:checked ~ * .hero.face-mitra[b-x69esb1yx8],
#face-agen:checked ~ * .hero.face-agen[b-x69esb1yx8] {
    transform: translateX(0);
    opacity: 1;
    visibility: visible;
    transition: transform 820ms cubic-bezier(0.65, 0, 0.35, 1),
                opacity 120ms ease,
                visibility 0s;
}

/* The one leaving, marked by JS for the length of the travel. It exits AGAINST the arrival:
   forward, the new face comes from the right and this one walks off to the left. */
.hero.face.is-leaving[b-x69esb1yx8] {
    transform: translateX(-100%);
    /* Stays fully opaque for the whole crossing — it is a panel walking off, not a ghost.
       The fade to the parked state happens after the class is released, off screen. */
    opacity: 1;
    visibility: visible;
    transition: transform 820ms cubic-bezier(0.65, 0, 0.35, 1),
                opacity 120ms ease,
                visibility 0s linear 820ms;
}

.landing[data-face-dir="back"] .hero.face.is-leaving[b-x69esb1yx8] {
    transform: translateX(100%);
}

/* Released by JS once the travel is over. The panel is off screen by then and has to be put
   back on the WAITING side so it can come in from the right place next time — INSTANTLY, and
   that is the whole point of this rule. Measured without it: after every swap the departed
   panel slid all the way back across the screen at opacity 0, a full-screen composited layer
   animating for another 820ms, on a rotator that fires every 5.2 seconds. Invisible, but this
   page carries a lag report (2026-07-21) from exactly that kind of leftover.

   `transition: none` is safe against the arrival: the checked rule is id-strength and outranks
   this one, so the panel still slides in properly when its turn comes. */
.hero.face.is-parked[b-x69esb1yx8] {
    transform: translateX(100%);
    transition: none;
}

.landing[data-face-dir="back"] .hero.face.is-parked[b-x69esb1yx8] {
    transform: translateX(-100%);
}

/* Reduced motion: no travel at all. The faces still swap — they cut, which is the correct
   answer for a reader who asked for less movement — so the transition is simply dropped. */
@media (prefers-reduced-motion: reduce) {
    .hero.face[b-x69esb1yx8],
    .hero.face.is-leaving[b-x69esb1yx8],
    #face-market:checked ~ * .hero.face-market[b-x69esb1yx8],
    #face-mitra:checked ~ * .hero.face-mitra[b-x69esb1yx8],
    #face-agen:checked ~ * .hero.face-agen[b-x69esb1yx8] {
        transition: none;
    }
}

/* ── The switcher: three dots at the foot of the hero ──
   Three attempts on 7/28, each fixing what the last one broke, and the order
   matters because two of them look reasonable on paper:

     1. THREE COPIES, one inside each hero, each tuned to its own ground. Every copy
        sat somewhere slightly different, so pressing a button moved the button
        ("switchnya membingungkan, sekarang loncat loncat").
     2. ONE copy in a white band BETWEEN the nav and the hero. Stable at last, but it
        had left the picture: "sekarang pengalihnya di luar hero" — it read as a
        second navbar rather than as the hero's own question.
     3. ONE copy, drawn ON the hero, at its foot, as three dots
        ("buat lebih simple, tiga titik saja dan letak di bagian bawah hero").

   It is written between the nav and the heroes and positioned ABSOLUTELY, so it
   belongs to the hero visually and to none of them structurally. That is the whole
   trick: something that lives inside no face cannot move when the face changes.

   The offset is 100svh minus a hair, because the hero's own min-height is
   100svh − 64px and it starts 64px down — so the first screen's bottom edge and the
   hero's bottom edge are the same line. Keep the two in step if either changes.

   <label>, not <button>: a label drives the radio with no script at all, which is
   the whole point on this host. */
.face-switch[b-x69esb1yx8] {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: calc(100vh - 62px);
    top: calc(100svh - 62px);
    /* Over the hero (z-index 0), under the cover card (2) and the navbar (100). */
    z-index: 1;
    display: flex;
    gap: 4px;
}

/* 44px of nothing around a 9px dot. The dot is what is seen; this is what is
   pressed, and a 9px tap target on a phone is not a control, it is a dare. */
.face-dot[b-x69esb1yx8] {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.face-dot[b-x69esb1yx8]::before {
    content: "";
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.42);
    transition: background 0.16s ease, transform 0.16s ease;
}

/* Hover carries the same cue focus does further down — §1 of
   docs/design-discipline.md — which here means "brighter", the only cue a 9px
   circle has room for. */
.face-dot:hover[b-x69esb1yx8]::before {
    background: rgba(255, 255, 255, 0.78);
}

/* The selected one: solid, and a third larger. Size AND opacity together, because
   opacity alone is not enough separation at this diameter — measured against the
   hero gradient, a 42%-white dot and a 100%-white dot are two steps apart on a
   field that is already light in places. */
#face-market:checked ~ .face-switch .face-dot[for="face-market"][b-x69esb1yx8]::before,
#face-mitra:checked ~ .face-switch .face-dot[for="face-mitra"][b-x69esb1yx8]::before,
#face-agen:checked ~ .face-switch .face-dot[for="face-agen"][b-x69esb1yx8]::before {
    background: #FFFFFF;
    transform: scale(1.35);
}

/* The mitra face is the one light ground of the three, so its dots invert. This is
   the ONLY thing about the control that changes with the face, and it changes
   because the ground under it does — white dots on a near-white wash would be
   invisible, which is not a style choice. */
#face-mitra:checked ~ .face-switch .face-dot[b-x69esb1yx8]::before {
    background: rgba(15, 23, 42, 0.22);
}

#face-mitra:checked ~ .face-switch .face-dot:hover[b-x69esb1yx8]::before {
    background: rgba(15, 23, 42, 0.45);
}

#face-mitra:checked ~ .face-switch .face-dot[for="face-mitra"][b-x69esb1yx8]::before {
    background: var(--l-green-text);
}

/* ── The stop control, beside the dots ──
   ABSOLUTE, NOT A FOURTH ITEM IN THE ROW, and that is the whole reason it looks like this.
   .face-switch is centred with `left: 50%` plus a half-width pull, so anything added to its
   flex row widens the group and shoves the three dots off centre. Taking the button out of
   the flow leaves the dots at the exact position they have always had, and hangs the control
   off their right edge instead.

   Its containing block is .face-switch itself, which is already absolutely positioned, so
   `left: 100%` means "just past the last dot". At 390px wide the dots span roughly 129 to 261
   and this ends near 305, well inside the screen, so it never reaches .landing's overflow clip.

   44px for the same reason .face-dot is 44px: it is the tap target, and the icon inside it is
   what is seen. */
.face-pause[b-x69esb1yx8] {
    position: absolute;
    left: 100%;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    background: none;
    color: rgba(255, 255, 255, 0.42);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color 0.16s ease;
}

/* 🚨 REQUIRED, NOT TIDINESS. The button ships with the `hidden` attribute and is revealed by
   landing-motion.js, but `hidden` is only a UA-stylesheet `display: none`, so the author
   `display: flex` above outranks it and the button would show on a page where no script ran,
   offering to stop a hero that is not moving. Any rule that gives .face-pause a display value
   needs this line beside it. */
.face-pause[hidden][b-x69esb1yx8] { display: none; }

/* Same cue the dots use, for the same reason: brighter is the only move a 14px glyph has
   room for. Section 1 of docs/design-discipline.md. */
@media (hover: hover) {
    .face-pause:hover[b-x69esb1yx8] { color: rgba(255, 255, 255, 0.85); }
}

.face-pause:focus-visible[b-x69esb1yx8] {
    outline: none;
    border-radius: 999px;
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.55);
}

/* Paused is a STATE, not a hover, so it is the one that goes solid white. A reader glancing
   at the hero should be able to tell that they stopped it. */
.face-pause[aria-pressed="true"][b-x69esb1yx8] { color: #FFFFFF; }

/* One button, two glyphs, and `aria-pressed` picks which. Pressed means "I stopped it", so
   the button then offers the way back and shows the play triangle. */
[b-x69esb1yx8] .face-pause-play { display: none; }
.face-pause[aria-pressed="true"][b-x69esb1yx8]  .face-pause-bars { display: none; }
.face-pause[aria-pressed="true"][b-x69esb1yx8]  .face-pause-play { display: block; }

/* The mitra face is the one light ground of the three, so this inverts exactly as the dots
   above it do, and for the same reason: white on a near-white wash is not visible. */
#face-mitra:checked ~ .face-switch .face-pause[b-x69esb1yx8] { color: rgba(15, 23, 42, 0.32); }

@media (hover: hover) {
    #face-mitra:checked ~ .face-switch .face-pause:hover[b-x69esb1yx8] { color: rgba(15, 23, 42, 0.62); }
}

#face-mitra:checked ~ .face-switch .face-pause[aria-pressed="true"][b-x69esb1yx8] { color: var(--l-green-text); }

#face-mitra:checked ~ .face-switch .face-pause:focus-visible[b-x69esb1yx8] {
    box-shadow: 0 0 0 3px rgba(21, 101, 192, 0.30);
}

/* ── Keyboard focus: a ring around the GROUP, not around one dot ──
   A same-name radio group is a single tab stop with a roving tabindex, so focus can
   only ever land on the option that is already selected — and that option is
   already the solid dot, whether the reader tabbed in or not. Tabbing to this
   control therefore repainted absolutely nothing; measured before this rule
   existed, not assumed.

   §2 of docs/design-discipline.md answers this exact case with "'pick one' cards
   are <button>s, never a radio group", precisely because of the single tab stop.
   That answer is unavailable here and this is the documented exception: the page
   runs with no script on the marketing host, so a <button> could not change a
   thing. Same reason the agen tab strip below is radios.

   The ring goes on the container because the group is what has focus; which option
   you are on is already said by the solid dot, and inside a radio group focus and
   selection are the same fact.

   Written from the INPUT: the radio holding focus and the label showing it are
   nowhere near each other in the tree, so `:focus-visible` on the label can never
   fire. */
#face-market:focus-visible ~ .face-switch[b-x69esb1yx8],
#face-mitra:focus-visible ~ .face-switch[b-x69esb1yx8],
#face-agen:focus-visible ~ .face-switch[b-x69esb1yx8] {
    border-radius: 999px;
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.55);
}

#face-mitra:focus-visible ~ .face-switch[b-x69esb1yx8] {
    box-shadow: 0 0 0 3px rgba(21, 101, 192, 0.30);
}

/* The bar's copy of the control needs the ring too, and needs its OWN colour: it sits on
   white, where the dots' translucent-white ring would be invisible. Both are painted at
   once — only one of the two is on screen at any scroll position, and a reader who tabs
   to the group while the hero is visible should see it on whichever they are looking at. */
#face-market:focus-visible ~ * .nav-faces[b-x69esb1yx8],
#face-mitra:focus-visible ~ * .nav-faces[b-x69esb1yx8],
#face-agen:focus-visible ~ * .nav-faces[b-x69esb1yx8] {
    box-shadow: 0 0 0 3px rgba(21, 101, 192, 0.28);
}

/* "Lihat aplikasinya" in the agen hero. It was a <label> that switched faces —
   which needed this pointer, since it only comes free on <a>/<button> — until
   7/30, when it became a real link to guest home. Harmless now; load-bearing
   again if it ever goes back to being a label. */
.face-switch-cta[b-x69esb1yx8] {
    cursor: pointer;
}

/* ====== NAVBAR ====== */
/* A solid white bar, not a translucent tint of the hero. It is sticky, so it
   travels over the blue hero AND over the white sections below; a colour keyed
   to either one would break against the other. White works over both.

   FULLY opaque, and NO backdrop-filter (Susanti, 7/23: hero felt laggy, no more
   blur). This bar was the last live blur on the page: it sits on top of the
   hero, whose faces/floats are scrubbed by scroll timelines, and a backdrop
   blur above animating content re-samples its backdrop on the main thread
   every frame — the same trap that got the float cards on 7/21. At 0.92 alpha
   the blur was doing real work (content ghosted through); solid white needs
   neither. */
.landing-nav[b-x69esb1yx8] {
    position: sticky;
    top: 0;
    z-index: 100;
    background: #FFFFFF;
    border-bottom: 1px solid var(--l-border);
}

/* The document-wide READING-PROGRESS BAR that used to sit on this bar's bottom edge was
   removed on 7/28 — see the note in Landing.razor. The "you are here" highlight on the
   links replaced it: same question, answered by name instead of by fraction.

   Its keyframes survive as bar-grow, one block down, because the fitur ride's own
   progress bar (.jack-progress) uses them and is a different thing entirely: that one
   measures a single section the reader is locked inside, where the length IS knowable. */
@keyframes bar-grow-b-x69esb1yx8 {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

.nav-inner[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 64px;
    gap: 16px;
}

.nav-brand[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    flex-shrink: 0;
}

/* ── Mark OR word, never both (Susanti, 7/30) ──
   Wide screens get the word; phones (≤480px) get the mark. The swap lives in the 480px
   block far below — search .nav-logo there before changing either of these two rules,
   because each one is half of a pair.

   The width/height stay declared while it is hidden on purpose: the img carries
   width="96" height="96" attributes, and if the 480px block ever turns it back on
   without them the bar would flash a 96px mark for one frame. */
.nav-logo[b-x69esb1yx8] {
    display: none;
    width: 32px;
    height: 32px;
    object-fit: contain;
}

.nav-wordmark[b-x69esb1yx8] {
    font-size: 18px;
    font-weight: 700;
    color: var(--l-text);
    letter-spacing: -0.3px;
}

/* She asked for a LIGHT blue ("biru muda"), and this is as light as the word can go and
   still be readable: #1B73D4 measures 4.72:1 on white, just over the 4.5:1 this file
   holds every piece of small text to (see the note on --l-text-3). It is not a new
   colour either — it is the exact value the hero gradients are capped at, and they are
   capped there for this same reason.

   Lighter was tried and rejected on the numbers, not on taste: a sky blue like #2196F3
   comes out at 3.13:1. WCAG would only forgive that for LARGE text (14pt bold = 18.66px)
   and this word is 18px bold, a third of a pixel short of the exemption. If a lighter
   blue is wanted here, the word has to get bigger or move onto a blue field first. */
.nav-wordmark-accent[b-x69esb1yx8] {
    color: #1B73D4;
}

/* 🚨 flex: 1 IS WHAT KEEPS THE SWITCHER STILL (7/30). The three faces carry a different
   NUMBER of section links — three, two, one — so this element's own width changed with
   the face, and under .nav-inner's `space-between` that moved its left edge, dragging the
   switcher pill with it. Measured before the fix: the pill's left edge sat at 472 / 474 /
   538 across the three faces, 66px of drift, and she reported it as the bar shifting when
   you navigate. Filling the space between the brand and the actions makes this element's
   left edge a fixed distance from the wordmark whatever it contains, so the pill anchored
   to that edge cannot move. min-width: 0 lets the links shrink instead of forcing the bar
   wider on the narrowest desktop widths. */
.nav-links[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 32px;
    flex: 1 1 auto;
    min-width: 0;
    /* Now that the links are pushed right, this is what keeps them off the contact
       button. Measured at 769px — the widest the phone breakpoint is NOT in force — the
       last link sat 5px from it on the mitra face, which read as the two touching. */
    padding-right: 12px;
}

/* ── The face switcher's second home: the bar (7/30) ──
   A segmented pill, always on screen, driving the same three radios the hero dots do —
   see the note in Landing.razor for why there is no state here and why it must not sit
   inside a `.face` wrapper. Deliberately quieter than the links beside it: it changes
   WHICH PAGE you are reading, and a control that shouts that would compete with the
   section links, which are the more common move. */
.nav-faces[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 3px;
    background: var(--l-bg-alt);
    border: 1px solid var(--l-border);
    border-radius: 999px;
    flex-shrink: 0;
    /* Holds the pill against the LEFT edge of .nav-links (i.e. a fixed distance from the
       wordmark — see the flex: 1 note there) and pushes the section links to the right,
       where they end up beside the contact button. The links still change width with the
       face; anchoring them to the right means what moves is their far edge, next to a
       button, rather than the whole bar shuffling. */
    margin-right: auto;
}

.nav-face[b-x69esb1yx8] {
    padding: 5px 12px;
    border-radius: 999px;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.2;
    color: var(--l-text-2);
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

/* Hover carries the same cue as .nav-link — light the word, no fill. §1 of
   docs/design-discipline.md. */
.nav-face:hover[b-x69esb1yx8] {
    color: var(--l-blue);
}

/* The selected one lifts out of the trough. Written from the INPUT, like every other
   rule that reacts to this group: the radio and the label are in different branches, so
   `:checked` on the label can never fire. */
#face-market:checked ~ * .nav-face-market[b-x69esb1yx8],
#face-mitra:checked ~ * .nav-face-mitra[b-x69esb1yx8],
#face-agen:checked ~ * .nav-face-agen[b-x69esb1yx8] {
    background: #FFFFFF;
    color: var(--l-text);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.10);
    cursor: default;
}

/* ── ONE COLOUR PER FACE (Susanti, 7/30: "warnanya juga perlu dibedakan antara satu
      halaman dan yang lain") ──
   The colour is on the SELECTED pill only. The two you are not on stay grey, because the
   question this control answers is "which one am I on" — painting all three would make it
   a legend instead of an answer.

   Two of the three already had a colour decided elsewhere on the site: blue is the hire
   side, green is the provider side (see the --l-green note in the token block — that pair
   is site-wide, not a choice made here). Agen had none: its band is a pale BLUE wash, so
   it shared the marketplace's colour, which is exactly what she is asking to separate. It
   takes #7E22CE, and that is not a new colour on this page either — it is the purple
   .sc-acc-purple already gives a fitur card, so the page's palette does not grow.
   Contrast on white is 6.4:1, which 12.5px text needs. */
/* ── THE FACE'S COLOUR, AS TOKENS (Susanti, 7/30: "saat berganti ke mitra dan agen…
      progress bar, tombol hubungi kami dan section mulai, warnanya diganti jadi tema warna
      halaman itu") ──
   The three colours above stopped at the switcher pill, so a reader on the mitra face still
   had a blue rail, a blue Hubungi Kami and a blue closing band — the page said green in one
   12px word and blue everywhere it actually painted.

   SEVERAL TOKENS, NOT ONE, because a colour cannot do every job at one value. Read this list
   before reaching for any of them — picking the wrong one is how a contrast failure gets in:

     --face-accent       FILL BEHIND NOTHING. A 2px rail, a bar in a chart, a mark. Judged
                         against the shape at 3:1, so it can be the brightest of the three.
     --face-solid        FILL WITH WHITE TEXT OR AN ICON ON IT. Always ≥4.5:1 against white,
                         which for green means #15803D and NOT #16A34A (that one is 3.3:1 —
                         the reason this token exists at all).
     --face-accent-text  THE COLOUR AS WORDS ON WHITE. 4.5:1 against the page.
     --face-accent-deep  the hover/active step under --face-solid.
     --face-accent-lite  the light end of a two-stop accent gradient.
     --face-accent-rgb   the same hue as three numbers, for rgba() shadows and hairlines.
     --face-band         the closing banner's entire background (the gold face's is a
                         multi-stop sheen, not a ramp, so this is one token and not two).
     --face-band-ink     what is legible ON that band: white on two of them, near-black on
                         gold. See the gold block for why that flip is not optional.

   Set on the RADIO'S SIBLINGS (`~ *`), which is every top-level child of .landing — the
   navbar and .after-hero — and inherited from there into the whole page. Same mechanism as
   every other face rule here, so it needs no script and survives on the static host.

   🚨 HOW FAR THIS GOES, AND WHERE IT STOPS (Susanti, 7/30, after a wider pass was built and
   then reverted): the face colour owns the CHROME — the navbar rail, Hubungi Kami, the
   closing band, the section eyebrows, the download icons, the ride's progress bar, the
   back-to-top button — plus the agen face's own claim button, which is chrome-sized even
   though it sits in the content. It does NOT own what is inside the sections. The chapter
   marks, the commission chart, the FAQ, the trust panel, the section rules and the page's
   pale washes were all turned to the face colour in one pass and all deliberately put back:
   blue is this page's resting colour, and the face colour is what speaks OVER it. Widening
   this again is a design decision that was already made once and reversed — not a tidy-up. */
.landing[b-x69esb1yx8] {
    --face-accent: var(--l-blue);
    --face-solid: var(--l-blue);
    --face-accent-text: var(--l-blue);
    --face-accent-deep: var(--l-blue-deep);
    --face-accent-lite: #4FA3E8;
    --face-accent-rgb: 21, 101, 192;
    --face-band: linear-gradient(135deg, #0D47A1 0%, #1B73D4 100%);
    /* White, and the band's light end is capped so it stays legible — see --l-on-blue. */
    --face-band-ink: #FFFFFF;
    --face-band-line: rgba(255, 255, 255, 0.3);
    --face-band-line-strong: rgba(255, 255, 255, 0.5);
    --face-band-wash: rgba(255, 255, 255, 0.08);
}

#face-mitra:checked ~ *[b-x69esb1yx8] {
    --face-accent: var(--l-green);
    /* NOT --l-green: white on #16A34A is 3.3:1. Everything that carries white goes here. */
    --face-solid: var(--l-green-text);
    --face-accent-text: var(--l-green-text);
    /* One step below --face-solid, so a solid still visibly deepens on hover. */
    --face-accent-deep: #14532D;
    --face-accent-lite: #4ADE80;
    --face-accent-rgb: 22, 163, 74;
    /* Ends on --l-green-deep, not --l-green: white on the lighter one is 3.3:1. */
    --face-band: linear-gradient(135deg, #14532D 0%, var(--l-green-deep) 100%);
}

/* ── THE AGEN FACE IS GOLD, AND THE INK FLIPS (Susanti, 7/30: purple "agak ketara", then the
      brown amber "kurang bagus, buat warna emas dengan sheen metal") ──
   🚨 THE ONE THING TO UNDERSTAND BEFORE CHANGING ANY VALUE HERE: gold that reads as gold is a
   LIGHT colour, and white text cannot sit on it. White on #D4AF37 is 1.9:1; the brightest
   gold that would hold white text is about #8A6A1F, which is the olive-brown this face just
   wore and which was rejected. So this band is the only one on the page whose ink is DARK —
   that is not a stylistic flourish, it is the only arrangement in which a genuinely gold
   surface is also readable. Do not "fix" the dark text back to white; that reverts the gold.

   THE SHEEN IS A MULTI-STOP RAMP, not a two-stop one. Metal reads as metal because its
   brightness turns over several times across the surface — light, shadow, light again —
   so the band alternates #B8912F ↔ #F2E1A8 at uneven intervals, with one soft white streak
   laid over the top on a slightly different angle. Nothing moves: a travelling glint on a
   full-width CTA would pull the eye off the button it is supposed to serve.

   EVERY STOP IS ≥4.5:1 AGAINST THE INK, darkest included (#B8912F on #2E1D05 is 5.5:1), so
   the sentence stays legible wherever the streak happens to fall at any width. That is the
   constraint that sets the range: no stop may go darker than #B8912F, and the ink may not
   go lighter than #2E1D05.

   ON WHITE the same gold cannot be used, and this is the fill/text split again, one step
   wider than green's: the 2px rail takes #A9812A (3.6:1, a mark needs 3:1) and the words
   take #8A6A1F (5.1:1). The bar's Hubungi Kami carries the metal itself — see its own rule
   below, where the dark ink returns for the same reason it does here. */
#face-agen:checked ~ *[b-x69esb1yx8] {
    --face-accent: #A9812A;
    /* White on #A9812A is only 3.6:1 — fine for a bar, not for a numeral. Anything with
       white on it therefore drops to the deep gold, which is 7.4:1. */
    --face-solid: #6B5215;
    --face-accent-text: #8A6A1F;
    --face-accent-deep: #574310;
    --face-accent-lite: #E8CE7A;
    --face-accent-rgb: 169, 129, 42;
    --face-band:
        linear-gradient(76deg, rgba(255, 255, 255, 0) 26%, rgba(255, 255, 255, 0.42) 46%, rgba(255, 255, 255, 0) 64%),
        linear-gradient(100deg,
            #B8912F 0%, #D9BE68 14%, #C6A23B 27%, #F2E1A8 44%,
            #C9A63A 57%, #E3CB7E 71%, #BC9531 85%, #D3B457 100%);
    --face-band-ink: #2E1D05;
    --face-band-line: rgba(46, 29, 5, 0.42);
    --face-band-line-strong: rgba(46, 29, 5, 0.7);
    --face-band-wash: rgba(46, 29, 5, 0.09);
}

/* The selected pill, now reading the token instead of naming the colour a second time. */
#face-market:checked ~ * .nav-face-market[b-x69esb1yx8],
#face-mitra:checked  ~ * .nav-face-mitra[b-x69esb1yx8],
#face-agen:checked   ~ * .nav-face-agen[b-x69esb1yx8] { color: var(--face-accent-text); }

.nav-link[b-x69esb1yx8] {
    position: relative;
    font-size: 14px;
    font-weight: 500;
    color: var(--l-text-2);
    text-decoration: none;
    transition: color 0.15s;
    /* Room for the "you are here" rail below. On the BASE rule, not on the active
       state: padding that appeared only when a link lit up would shove its neighbours
       sideways every time the reader crossed a section boundary. */
    padding-bottom: 5px;
}

/* The rail. TWO CUSTOM PROPERTIES, both written by initNavRail() in landing-motion.js:
   --rail is 0→1, how far through this section the reader is; --rail-on is 0 or 1, whether
   this is the section they are in. Defaults are 0/0, so with no script the links are simply
   neutral — the same thing that happened before wherever scroll-driven animation was
   unsupported, so nothing regresses.

   A PSEUDO-ELEMENT SCALED ON X, not `background-size` on the link (which is what it was
   until 7/29): background-size cannot be handed to the compositor, so three of these growing
   on scroll forced a style recalc and a repaint on the main thread every frame for the whole
   length of the page. scaleX and opacity both move an already-rasterised layer. Layout is
   untouched either way — neither property reflows. */
.nav-link[b-x69esb1yx8]::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    /* The FILL token, not the text one: this is a 2px shape, judged at 3:1. */
    background: var(--face-accent);
    transform: scaleX(var(--rail, 0));
    transform-origin: left center;
    opacity: var(--rail-on, 0);
    pointer-events: none;
    /* Only the fade is eased. The FILL must not be: it is a readout of scroll position, and a
       transition on it would make the bar chase the reader instead of tracking them. */
    transition: opacity 0.18s ease;
}

.nav-link:hover[b-x69esb1yx8],
.nav-link:focus-visible[b-x69esb1yx8] {
    outline: none;
    /* Moves with the rail: a green rail under a link that turns blue when touched would be
       two colours for one link. The TEXT token here — 14px words need 4.5:1. */
    color: var(--face-accent-text);
}

/* ── "You are here": the bar follows the reading position ────────────────────────
   Each link lights and grows its underline while the section it points at is the one
   on screen. It is the answer to a plain question a long scrolling page keeps asking —
   how far in am I — and the reading-progress bar above only answers it as a fraction.

   NO SCRIPT, and no scroll listener: each section declares a named view-timeline,
   .landing hoists the names into scope, and the link runs a scroll-driven animation
   over them. Exactly what the agen page's chapter list has done since 7/24; this is
   that mechanism moved into the shared bar. Without scroll-driven animation (Safari,
   Firefox) the links simply stay neutral and every one of them still works — which is
   why this is not gated on reduced motion either: nothing moves except a 2px rail
   growing, and a reader who asked for less motion still deserves to know where they
   are.

   THE RANGE AND THE KEYFRAMES ARE ONE DECISION. `entry 0% exit 100%` spans the entire
   visible life of a section — from its first pixel on screen to its last — so adjacent
   sections overlap heavily at every boundary. The keyframes put the blue in the middle
   80% and hand back to neutral at both ends, which turns that overlap into a clean
   pass: the outgoing link fades out as the incoming one fades in, and there is never a
   moment with two links lit or none. Same trick, same numbers, as the agent question
   rail (.agen-quests). */
@supports (animation-timeline: view()) {
    /* IDs, not classes: #cara-kerja and #cara-mitra are both <section class="story">
       and nothing but the id tells them apart. */
    #cara-kerja[b-x69esb1yx8]   { view-timeline: --sec-cara block; }
    #download[b-x69esb1yx8]     { view-timeline: --sec-download block; }
    #cara-mitra[b-x69esb1yx8]   { view-timeline: --sec-mitra-cara block; }

    /* The agen face's two chapters, not the whole section (7/30). `#agen` spans nine
       screens, so one timeline on it lit one link for the entire face — the bar said
       "you are somewhere in the agen page", which the switcher already said. */
    #agen-wilayah[b-x69esb1yx8]    { view-timeline: --sec-agen-wilayah block; }
    #agen-keuntungan[b-x69esb1yx8] { view-timeline: --sec-agen-keuntungan block; }

    /* The closing band needs a timeline again — not to measure its own progress (its ranges
       cannot complete, see .nav-link-cta) but to mark ONE BOUNDARY: the moment it takes over
       from the section above. That is all the handover mechanism asks of it. */
    #mulai[b-x69esb1yx8]        { view-timeline: --sec-cta block; }
    /* #alasan-mitra is NOT here. Since 7/30 that section is the second .showcase and it
       has to declare --jack for its own ride as well; an id rule would outrank the class
       that declares both and replace the list. See .showcase.showcase-mitra below. */

    /* The fitur section is the one exception, and it is written as `.showcase` — NOT
       `#features` — on purpose. That section ALSO declares --jack for the ride, in the
       FITUR LOCK block at the end of this file, and `view-timeline` is a shorthand, so
       one rule's declaration replaces the other's entirely rather than adding to it.
       The lock re-declares BOTH names; for that to win it has to outrank this one, and
       a class can be outranked by a later class while an ID cannot.

       This was shipped as `#features` for one build and it broke the ride outright:
       measured, getComputedStyle(.showcase).viewTimelineName came back "--sec-fitur"
       alone, --jack resolved to nothing, and the track sat frozen at translateX(-1208)
       through the whole 340vh. Leave this as a class. */
    .showcase[b-x69esb1yx8]     { view-timeline: --sec-fitur block; }

    /* 🚨 THE SECOND RIDE (7/30). The mitra face's "Kenapa lewat Tepatjasa?" is now a
       .showcase too, so the rule above would hand it --sec-fitur and light the WRONG
       nav link — two elements declaring one hoisted name is also ambiguous, which would
       break the marketplace link as well. DOUBLED CLASS so it outranks both the rule
       above and the FITUR LOCK's own `.showcase` rule at the end of the file, wherever
       either one moves to; and it declares BOTH names in one shorthand for the reason
       spelled out above.

       --jack is deliberately NOT in .landing's timeline-scope list: it stays a plain
       descendant lookup, so each ride's own .sc-track and .jack-progress resolve to the
       nearest .showcase ancestor and the two rides cannot see each other's. */
    .showcase.showcase-mitra[b-x69esb1yx8] { view-timeline: --jack block, --sec-mitra-alasan block; }

    /* ══ THE RAIL IS DRIVEN BY SCRIPT NOW — and the reason is a hard limit, not taste ═══════
       Four pure-CSS designs stood here on 7/30 and every one of them traded a gap for an
       overlap. The cause is that a view timeline can only describe a section through its own
       phases, and the phrase this feature needs — "from this section's top edge reaching the
       top of the viewport until the NEXT section's does" — is only expressible that way for
       sections in the MIDDLE of the page. `exit 100%` is unreachable for the last sections
       (the reader runs out of document first; the closing band's top never reaches the
       viewport top at all), so their windows had to come off a document-percentage clock,
       and a boundary written on one clock never lands on a boundary written on the other.
       Mixing them is what produced "sebelumnya udah selesai lama tapi berikutnya belum
       mulai" AND "yang lama belum selesai yang baru sudah mulai" at the same time.

       Section boundaries are trivial to compute directly, so initNavRail() in
       landing-motion.js does that and writes two custom properties per link. The CSS keeps
       only the shape. What is lost without script is the rail itself — which is exactly what
       was lost without scroll-driven animation support before, so no browser is worse off. ══ */

/* ── Why there is no scroll-driven rail machinery left here (7/30) ──
   Five designs stood in this spot in one day: a single plateau, a wide plateau, four
   length-matched plateaus derived from where a section owns the viewport midpoint, a shared
   `entry 88%` boundary event, and finally one window per section spanning
   `contain … exit 100%`. Each fixed the previous one's fault and introduced the other:
   a gap between rails, or two rails lit, or a bar that appeared already 30–40% full because
   its fill ran on a different range from its visibility.

   The limit is real and worth writing down: **a view timeline can only describe a section
   through its own phases, and "until the NEXT section's top edge reaches the top of the
   viewport" is only expressible that way for sections in the middle of the page.** For the
   last sections `exit 100%` is never reached — the reader runs out of document — so those
   windows have to come off a document-percentage clock, and a boundary written on one clock
   never coincides with one written on the other. That mismatch IS the gap-or-overlap.

   Boundaries are trivial to compute directly, so initNavRail() in landing-motion.js does it
   and writes --rail-on and --rail per link. Everything below is just the shape. */

.nav-actions[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* ── The phone menu (2026-07-28) ──
   The bar hid .nav-links below 768px and put nothing in their place, so on a phone the
   navigation was simply absent — and once the agen band moved off /welcome earlier the
   same day, a phone reader on that page had NO route to /jadi-agen at all. Reported as
   "top bar punya navigasi sama sekali tidak terlihat".

   No script: the marketing host serves this page as static HTML, so the open/closed
   state is a checkbox, the same no-JS technique the agen tabs use. landing-motion.js
   adds one nicety on top (closing the panel after an in-page link is tapped) and the
   menu works without it.

   Both parts are display:none above the breakpoint — the bar there is unchanged. */
.nav-toggle[b-x69esb1yx8],
.nav-burger[b-x69esb1yx8] {
    display: none;
}

@media (max-width: 768px) {
    /* Focusable but invisible. display:none or visibility:hidden would take the only
       keyboard way in, and `opacity: 0` — tried first — turned out to drop it from the
       tab order in Chromium as well: measured, the first Tab from a fresh load skipped
       straight past it to the contact button. This is the clip-path recipe the rest of
       this file already uses for visually-hidden-but-present text. */
    .nav-toggle[b-x69esb1yx8] {
        display: block;
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        border: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .nav-burger[b-x69esb1yx8] {
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: 5px;
        width: 40px;
        height: 40px;
        padding: 0 9px;
        margin-left: -4px;
        border-radius: 50%;
        cursor: pointer;
        flex-shrink: 0;
    }

    .nav-burger span[b-x69esb1yx8] {
        display: block;
        height: 2px;
        border-radius: 2px;
        background: var(--l-text-2);
        transition: transform 0.18s ease, opacity 0.18s ease, background 0.15s;
    }

    /* Hover and focus carry the IDENTICAL cue, per §1 of docs/design-discipline.md:
       the plain-icon-button row — light the icon, no ring, no fill. */
    .nav-burger:hover span[b-x69esb1yx8],
    .nav-toggle:focus-visible ~ .nav-burger span[b-x69esb1yx8] {
        background: var(--l-blue);
    }

    /* Open: the outer bars cross, the middle one goes. 7px is half the 14px the two
       outer bars sit apart (2px bar + 5px gap each side). */
    .nav-toggle:checked ~ .nav-burger span:nth-child(1)[b-x69esb1yx8] { transform: translateY(7px) rotate(45deg); }
    .nav-toggle:checked ~ .nav-burger span:nth-child(2)[b-x69esb1yx8] { opacity: 0; }
    .nav-toggle:checked ~ .nav-burger span:nth-child(3)[b-x69esb1yx8] { transform: translateY(-7px) rotate(-45deg); }

    /* The panel. Anchored to .landing-nav (position: sticky is a positioned ancestor),
       full-bleed under the bar, and it pushes nothing — the page beneath does not move
       when the menu opens. */
    .nav-toggle:checked ~ .nav-links[b-x69esb1yx8] {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        position: absolute;
        top: 64px;
        left: 0;
        right: 0;
        padding: 4px 24px 12px;
        background: #FFFFFF;
        border-bottom: 1px solid var(--l-border);
        box-shadow: 0 14px 30px rgba(15, 23, 42, 0.12);
    }

    .nav-toggle:checked ~ .nav-links .nav-link[b-x69esb1yx8] {
        padding: 14px 2px;
        font-size: 15px;
        border-top: 1px solid var(--l-border);
        /* No "you are here" rail in the dropdown. In the bar it sits 5px under a word;
           in a 14px-padded full-width row it would land against the next row's divider
           and read as a second border. The colour half of the cue still applies, which
           is the half that carries the meaning. */
        background-image: none;
    }

    .nav-toggle:checked ~ .nav-links .nav-link:first-child[b-x69esb1yx8] {
        border-top: none;
    }

    /* ── The switcher inside the phone panel (7/30) ──
       It is the FIRST CHILD of .nav-links, so it lands at the top of the dropdown with no
       extra rule about where it goes — only about how wide it is. Full width and the three
       pills share it equally, because at 390px three fixed-width pills left a ragged gap
       on the right that read as a missing fourth option.

       margin-right: auto is undone here: it centres the bar's pill on desktop and would
       squash this one against the left edge of the panel. */
    .nav-toggle:checked ~ .nav-links .nav-faces[b-x69esb1yx8] {
        margin: 4px 0 8px;
        width: 100%;
    }

    .nav-toggle:checked ~ .nav-links .nav-face[b-x69esb1yx8] {
        flex: 1;
        text-align: center;
        padding: 8px 4px;
        font-size: 13px;
    }

    /* The first LINK now sits under the switcher rather than at the top of the panel, so
       it keeps the divider that .nav-link:first-child above removes — otherwise the
       switcher and the first section link ran together as one block. The rule above still
       wins for the switcher's own case because .nav-faces is not a .nav-link. */
    .nav-toggle:checked ~ .nav-links .face:first-of-type .nav-link:first-child[b-x69esb1yx8] {
        border-top: 1px solid var(--l-border);
    }
}

/* ====== BUTTONS ====== */
/* Two families, decided by what the button SITS ON:
     - on white  → .btn-solid (blue fill) and .btn-ghost (grey outline)
     - on blue   → .btn-white / .btn-outline (white fill / white outline)
   The blue hero and the closing banner are the only blue surfaces, and both
   already opt into the white pair. A white .btn-solid would be invisible on
   this page's white background, which is why the default flipped to blue. */
.btn-ghost[b-x69esb1yx8] {
    background: none;
    border: 1px solid var(--l-border-strong);
    padding: 8px 16px;
    font-size: 14px;
    font-weight: 600;
    color: var(--l-text);
    cursor: pointer;
    border-radius: var(--radius-xs);
    transition: background 0.15s, border-color 0.15s, color 0.15s;
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

.btn-ghost:focus-visible[b-x69esb1yx8] {
    outline: none;
    background: var(--l-blue-pale);
    border-color: var(--l-blue);
    color: var(--l-blue);
}

@media (hover: hover) {
    .btn-ghost:hover[b-x69esb1yx8] {
        background: var(--l-blue-pale);
        border-color: var(--l-blue);
        color: var(--l-blue);
    }
}

.btn-solid[b-x69esb1yx8] {
    background: var(--l-blue);
    color: #fff;
    border: none;
    padding: 10px 20px;
    font-size: 14px;
    font-weight: 600;
    border-radius: var(--radius-xs);
    cursor: pointer;
    transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    box-shadow: 0 1px 2px rgba(21, 101, 192, 0.24);
}

/* Hover and focus share one cue (design-discipline §1): deepen + lift. */
.btn-solid:focus-visible[b-x69esb1yx8] {
    outline: none;
    background: var(--l-blue-deep);
    box-shadow: 0 6px 18px rgba(21, 101, 192, 0.32);
    transform: translateY(-1px);
}

@media (hover: hover) {
    .btn-solid:hover[b-x69esb1yx8] {
        background: var(--l-blue-deep);
        box-shadow: 0 6px 18px rgba(21, 101, 192, 0.32);
        transform: translateY(-1px);
    }
}

.btn-solid:active[b-x69esb1yx8] {
    transform: none;
}

/* ── The bar's Hubungi Kami, in the face's colour (7/30) ──
   ONLY this .btn-solid, not the class: .btn-solid is also the hero CTA and the closing
   band's buttons, each of which answers to its own section, and repainting all of them
   from one token would have swept up buttons nobody asked about.

   Two classes deep (0,2,0) on every rule so it outranks the plain .btn-solid states above
   — including the ones inside `@media (hover: hover)`, which would otherwise keep putting
   the blue back on hover. Same shape, same lift, same timing; the hue is the only change. */
/* --face-solid, not --face-accent: the label is white. */
.btn-solid.nav-contact[b-x69esb1yx8],
.btn-solid.agen-claim[b-x69esb1yx8] {
    background: var(--face-solid);
    box-shadow: 0 1px 2px rgba(var(--face-accent-rgb), 0.24);
}

.btn-solid.nav-contact:focus-visible[b-x69esb1yx8],
.btn-solid.agen-claim:focus-visible[b-x69esb1yx8] {
    background: var(--face-accent-deep);
    box-shadow: 0 6px 18px rgba(var(--face-accent-rgb), 0.32);
}

@media (hover: hover) {
    .btn-solid.nav-contact:hover[b-x69esb1yx8],
    .btn-solid.agen-claim:hover[b-x69esb1yx8] {
        background: var(--face-accent-deep);
        box-shadow: 0 6px 18px rgba(var(--face-accent-rgb), 0.32);
    }
}

/* ── The gold face's button is the only METAL one, and it inverts (7/30) ──
   Same reasoning as the closing band, at button size: a flat fill of a gold light enough to
   look like gold cannot hold white text, so this one is a vertical ramp with dark ink on it.
   The ramp is what makes it metal — light at the top edge, shadow at the bottom, the way a
   convex piece of metal takes a room's light — and the inset white hairline is the lit edge
   itself. Both are static; nothing here animates.

   The ink stays legible on the DARKEST stop (#2E1D05 on #B8912F is 5.5:1), which is the only
   contrast that has to be checked on a gradient of this direction, since the label crosses
   the whole height.

   .agen-claim — "Daftar Jadi Agen" in the wilayah result bar — is here too, and it is the
   reason this rule matters most: it is the face's primary conversion button, it had no
   colour rule of its own, so it simply inherited .btn-solid's blue and stayed blue while
   everything around it turned. */
#face-agen:checked ~ * .btn-solid.nav-contact[b-x69esb1yx8],
#face-agen:checked ~ * .btn-solid.agen-claim[b-x69esb1yx8] {
    background: linear-gradient(180deg, #E8CE7A 0%, #D4B551 46%, #B8912F 100%);
    color: #2E1D05;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.62),
                0 1px 2px rgba(107, 82, 21, 0.38);
}

#face-agen:checked ~ * .btn-solid.nav-contact:focus-visible[b-x69esb1yx8],
#face-agen:checked ~ * .btn-solid.agen-claim:focus-visible[b-x69esb1yx8] {
    background: linear-gradient(180deg, #DEC069 0%, #C5A340 46%, #A9812A 100%);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5),
                0 6px 18px rgba(107, 82, 21, 0.42);
}

@media (hover: hover) {
    #face-agen:checked ~ * .btn-solid.nav-contact:hover[b-x69esb1yx8],
    #face-agen:checked ~ * .btn-solid.agen-claim:hover[b-x69esb1yx8] {
        background: linear-gradient(180deg, #DEC069 0%, #C5A340 46%, #A9812A 100%);
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5),
                    0 6px 18px rgba(107, 82, 21, 0.42);
    }
}

/* ── The shared chrome is METAL on this face too, not merely gold (Susanti, 7/30: "bagian
      chrome bersama juga mau dikasih effect metallic, bukan cuma diwarnai keemasan") ──
   Metal is a brightness that turns over across a surface, so each of these gets a ramp
   sized to the surface it is on, and the lit edge where there is an edge to light. What it
   is NOT is a filter applied to everything gold: two of the five chrome pieces below are
   deliberately left flat, and the reason is at the bottom of this block. */

/* The back-to-top disc. A round button is the one place a diagonal ramp reads unmistakably
   as a machined surface, so this one carries the strongest version — and its chevron flips
   to dark ink for the same reason every other gold surface's does. #2E1D05 on the darkest
   stop is 4.5:1, and the chevron is a mark needing 3:1.

   TWO SELECTORS, and the first one is the one that works: .landing-top is a DIRECT CHILD of
   .landing, i.e. a sibling of the radios, so `~ *` (a descendant of a sibling) never reaches
   it. The token-driven version before this looked fine only because `~ *` sets the token ON
   the sibling itself. The descendant form is kept for the day this button moves inside a
   wrapper. */
#face-agen:checked ~ .landing-top[b-x69esb1yx8],
#face-agen:checked ~ * .landing-top[b-x69esb1yx8] {
    background: linear-gradient(158deg, #E8CE7A 0%, #C9A63A 38%, #A9812A 66%, #D4B551 100%);
    color: #2E1D05;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6),
                0 8px 24px rgba(107, 82, 21, 0.40);
}

@media (hover: hover) {
    #face-agen:checked ~ .landing-top:hover[b-x69esb1yx8],
    #face-agen:checked ~ * .landing-top:hover[b-x69esb1yx8] {
        background: linear-gradient(158deg, #DEC069 0%, #B8912F 38%, #8A6A1F 66%, #C5A340 100%);
    }
}

/* The navbar's reading rail — the bar this whole colour job started from. Five stops along
   its LENGTH, because on a 2px mark a two-stop ramp is just a gradient.
   It is painted by `transform: scaleX(--rail)`, so the sheen compresses into the bar's
   current width and stretches out as the reader advances. That is not a side effect being
   tolerated: it is the same thing the ride's progress bar has always done with its own
   gradient, and on a bar that fills it reads as the polish running along a lengthening
   strip. If it ever needs to hold still instead, the fix is background-size, NOT removing
   the transform — the transform is what keeps this bar off the main thread. */
#face-agen:checked ~ * .nav-link[b-x69esb1yx8]::after {
    background: linear-gradient(90deg, #8A6A1F 0%, #C9A63A 24%, #A9812A 48%, #E8CE7A 74%, #A9812A 100%);
}

/* The switcher's own name in the bar — metal on TEXT, which is a different problem: the ramp
   is clipped to the glyphs, so every stop has to clear 4.5:1 on white BY ITSELF. That caps
   the range at #6B5215 ↔ #8A6A1F (7.4:1 and 5.1:1), a narrow band by design — enough to
   catch a shift across a word, not enough to thin the letters anywhere.
   @supports is not decoration: `color: transparent` without background-clip is INVISIBLE
   TEXT, so the transparency and the clip must arrive together or not at all. */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
    #face-agen:checked ~ * .nav-face-agen[b-x69esb1yx8] {
        background: linear-gradient(96deg, #6B5215 0%, #8A6A1F 32%, #6B5215 58%, #8A6A1F 84%, #6B5215 100%);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
    }
}

/* 🚨 THE REST OF THE SHARED CHROME HAS NO METAL BECAUSE IT IS NOT ON THIS FACE. Measured,
   not assumed: of the five shared chrome pieces, the gold face renders exactly two — this
   disc and the footer links. The section eyebrows, the ride's progress bar and the download
   band's platform icons belong to the marketplace and mitra faces and never appear here, so
   metal rules for them would be dead CSS that reads as live. Two were written and deleted
   again for that reason; do not re-add them without checking the face still lacks them.

   The footer links stay flat: 13px, and they only turn gold on hover, where a clipped ramp
   flashing in for a moment reads as a rendering fault rather than as metal. Were the
   platform icons ever to arrive here they would stay flat too — they are inline SVG painted
   with `stroke: currentColor`, CSS cannot put a gradient on a stroke, and a sheen on a 2px
   stroke is invisible even when the markup is changed to allow one. */

.btn-outline[b-x69esb1yx8] {
    background: none;
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.35);
    padding: 10px 20px;
    font-size: 14px;
    font-weight: 600;
    border-radius: var(--radius-xs);
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
}

.btn-outline:focus-visible[b-x69esb1yx8] {
    outline: none;
    background: rgba(255, 255, 255, 0.12);
    border-color: rgba(255, 255, 255, 0.6);
}

@media (hover: hover) {
    .btn-outline:hover[b-x69esb1yx8] {
        background: rgba(255, 255, 255, 0.12);
        border-color: rgba(255, 255, 255, 0.6);
    }
}

.btn-lg[b-x69esb1yx8] {
    padding: 14px 28px;
    font-size: 16px;
    border-radius: var(--radius-sm);
}

/* ====== HERO ====== */
/* The page's one big colour field. Kept brand blue rather than turned white:
   an all-white page has nothing to look at, and this gives the white sections
   below something to be a relief from. Brightened from the old near-black
   gradient so it reads as inviting rather than heavy. */
.hero[b-x69esb1yx8] {
    padding: 0 0 80px;
    text-align: center;
    /* THREE BACKGROUND LAYERS ON ONE ELEMENT, and that is the entire point (7/29).
       The halftone used to be its own absolutely-positioned <div class="hero-grid">
       carrying a `mask-image` to fade out low down — and a mask forces that div to be
       composited as a separate layer, hero-wide, on every frame of the scroll. It was
       deleted for that, and Susanti asked for the dots back but static ("buat tetap ada
       tapi statis"). This is what static means here: the dots now live in the hero's OWN
       background stack, so they are painted once into a layer that already existed and
       cost nothing again.

       Layers paint top-first. The 180deg wash sits ABOVE the dots and does the job the
       mask used to do — it fades in from 40% down and dissolves them toward the bottom.
       It ends on #1B73D4, which is what the 165deg base is already showing down there,
       so it hides the dots without flattening the gradient (the worst mismatch, at the
       bottom-left corner of a wide screen, is about 7/255 of blue — not visible).

       .hero-mitra and .hero-agen replace this whole `background` wholesale, which is how
       they already worked; the dots do not reach them. */
    background:
        linear-gradient(180deg, transparent 40%, #1B73D4 100%),
        radial-gradient(rgba(255, 255, 255, 0.09) 1px, transparent 1px) 0 0 / 26px 26px,
        linear-gradient(165deg, #0D47A1 0%, #1565C0 50%, #1B73D4 100%);
    color: #fff;
    /* STICKY, not relative: the hero locks in place and the rest of the page
       (.after-hero) slides up OVER it — the reader scrolls the content, not the
       backdrop. Its containing block is .landing (the whole page), so once pinned
       it simply stays put behind everything; z-index 0 keeps it under the z-raised
       .after-hero and the z-100 navbar. Sticky works here because .landing is
       overflow-x: CLIP, not hidden — see the note on .landing.

       top is 64px — the navbar's height — NOT 0: the hero's natural position is
       already 64px down, below the nav, so with top:0 it crawled those 64px up
       before locking and the hero swap opened with a visible lurch instead of
       being pinned from the first scrolled pixel (Susanti's bug report, 7/23).
       At 64px it pins exactly where it already sits, and its min-height of
       100svh−64px lands its bottom precisely on the viewport's bottom edge.

       🚨 THE STICKY MOVED TO .hero-stack ON 7/30, and this note stays because the reasoning
       above is still the reasoning for the value — it just belongs to the wrapper now. Three
       stacked panels cannot each be sticky; the box that pins is the one they share. Putting
       `position: sticky` back on .hero would give each parked face its own pinning context
       and the stack would come apart. */
    overflow: hidden;
    /* Fill the first screen (minus the 64px navbar) so the cover-over moment reads
       as a full-page curtain rather than a short band sliding a short band.
       svh, with a vh fallback: on phones 100vh includes the collapsed URL bar, and
       a hero taller than the visible screen would put the CTAs below the fold.
       THE FACE DOTS READ THIS: .face-switch is parked at 100svh − 62px, i.e. just
       inside this element's bottom edge. Change one of the two and change both. */
    min-height: calc(100vh - 64px);
    min-height: calc(100svh - 64px);
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/* The agen face wears the Semanggi aerial — the same photograph chapter 02's
   cover draws, stored at this exact full-screen shape so the one-slot-one-shape
   rule in CREDITS.md holds. The scrim is NEUTRAL dark, not brand blue (Susanti,
   7/23: "coba tanpa tint biru") — the photo keeps its own colour, the same
   treatment ch02's cover gets, just a touch heavier because the full headline
   stands on it. A float-card set was tried first and rejected: she wanted the
   photograph. Painted once; every scrub on this page only touches children. */
/* Plain `scroll` attachment. `fixed` was tried on 7/28 to line this crop up with the panel's
   blurred copy and REMOVED the same day — it cannot work, see the note over .hero-frame (the
   blurred copy itself is gone since 7/30). All it left behind was a viewport-repaint cost per
   scroll frame. */
.hero-agen[b-x69esb1yx8] {
    background:
        linear-gradient(180deg, rgba(15, 20, 26, 0.55) 0%, rgba(15, 20, 26, 0.42) 45%, rgba(15, 20, 26, 0.58) 100%),
        url('../../../images/marketing/ch-sebelum.jpg') center / cover no-repeat;
}

/* The decorative circles are tuned for the flat gradient; over a photograph
   they read as lens flare. The aurora and dot grid are simply not rendered on
   this face (markup branch), and these two are its pseudo-element siblings.

   DOUBLED CLASS ON PURPOSE (.hero.hero-agen, fixed 2026-07-28). Written as
   `.hero-agen::before` this rule had EXACTLY the same specificity as the
   `.hero::before` it was meant to cancel — and it sits ABOVE that rule in the
   file, so source order handed the win to the base rule and the circles kept
   drawing over the photograph for five days. Measured, not assumed:
   getComputedStyle(hero,'::before').content came back "" on every face. The
   extra class outranks the base rule wherever either one moves to.

   WHERE THE CIRCLES COME FROM: not this file. `.hero` is also the auth pages'
   class and SkillMatch.Shared.Client/wwwroot/css/auth.css hangs a 180px and a 120px
   translucent white circle off `.hero::before/::after`; both stylesheets load on
   the app host. That is why cancelling is all this page can do. */
.hero.hero-agen[b-x69esb1yx8]::before,
.hero.hero-agen[b-x69esb1yx8]::after {
    content: none;
}

/* ====== HERO — MITRA ======
   The third face, and deliberately not a recolour of the first (Susanti, 7/28: "tidak
   mau mirip dengan hero welcome"). What separates them is SHAPE before colour:

     /welcome     dark blue gradient · centred column · glass cards floating behind
     /jadi-mitra  light green wash   · split, copy left / panel right
     /jadi-agen   photograph + dark scrim · centred column in a glass panel

   Everything below is the cost of turning a hero written for white-on-dark into a
   dark-on-light one: the section's own `color:#fff`, the headline, the badge, the
   trust strip and the CTA all assume a dark ground and each needs its light
   counterpart. Kept together in one block so the face can be read — or removed — as
   a single decision. */
.hero-mitra[b-x69esb1yx8] {
    background: linear-gradient(170deg, #FFFFFF 0%, var(--l-green-pale) 52%, #DCF0E4 100%);
    color: var(--l-text);
    text-align: left;
}

/* Both decorative circles are blue-on-blue washes; on the light ground they surface
   as grey smudges. The aurora and dot grid are not rendered on this face at all.
   Doubled class for the same cascade reason as .hero.hero-agen above — see there. */
.hero.hero-mitra[b-x69esb1yx8]::before,
.hero.hero-mitra[b-x69esb1yx8]::after {
    content: none;
}

.hero-mitra .hero-inner[b-x69esb1yx8] {
    /* Wider than the centred faces' 720px — this one has two columns to seat. */
    max-width: var(--landing-max);
    display: grid;
    grid-template-columns: 1.05fr 0.95fr;
    align-items: center;
    gap: 56px;
}

.hero-mitra .hero-title[b-x69esb1yx8] {
    color: var(--l-text);
}

/* The white-to-pale-blue wash is invisible on a light ground; the accent half of the
   headline takes the provider green instead — flat, not a gradient, because a gradient
   on dark text reads as a rendering fault rather than as emphasis. */
.hero-mitra .hero-gradient[b-x69esb1yx8] {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: var(--l-green-text);
}

.hero-mitra .hero-sub[b-x69esb1yx8] {
    color: var(--l-text-2);
    margin-left: 0;
    max-width: 480px;
}

/* Was a translucent-white pill for a dark ground. Here it is the same shape as the
   page's own green tag (.path-tag-green) — same family as the band on /welcome. */
.hero-mitra .hero-badge[b-x69esb1yx8] {
    background: var(--l-green-pale);
    border-color: rgba(22, 163, 74, 0.28);
    color: var(--l-green-text);
}

.hero-mitra .hero-cta[b-x69esb1yx8] {
    justify-content: flex-start;
    margin-bottom: 36px;
}

/* .btn-white is a white fill for a dark hero — on this one it would be invisible.
   The CTA takes the filled green the rest of the mitra strand uses. */
.hero-mitra .hero-cta .btn-solid[b-x69esb1yx8] {
    background: var(--l-green);
    color: #FFFFFF;
    box-shadow: 0 2px 12px rgba(22, 163, 74, 0.3);
}

@media (hover: hover) {
    .hero-mitra .hero-cta .btn-solid:hover[b-x69esb1yx8] {
        background: var(--l-green-deep);
        box-shadow: 0 8px 22px rgba(22, 163, 74, 0.34);
    }
}

.hero-mitra .hero-cta .btn-solid:focus-visible[b-x69esb1yx8] {
    outline: none;
    background: var(--l-green-deep);
    box-shadow: 0 8px 22px rgba(22, 163, 74, 0.34);
}

/* A plain list on this face, not the glass strip: three lines under the CTA read as
   the small print of an offer, which is what they are. */
.hero-mitra .hero-trust[b-x69esb1yx8] {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 10px;
    padding: 0;
    background: none;
    border: none;
    box-shadow: none;
}

.hero-mitra .hero-trust-item[b-x69esb1yx8] {
    color: var(--l-text-2);
}

.hero-mitra .hero-trust-item[b-x69esb1yx8]  svg {
    color: var(--l-green);
    opacity: 1;
}

/* ── The right column ── */
.hero-mitra-art[b-x69esb1yx8] {
    display: flex;
    flex-direction: column;
    gap: 16px;
    align-items: flex-end;
}

.hm-card[b-x69esb1yx8] {
    width: 100%;
    max-width: 320px;
    padding: 18px 20px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius);
    box-shadow: 0 10px 30px rgba(15, 23, 42, 0.10);
    text-align: left;
}

/* The second card steps out from under the first — two cards squared off in a
   column read as a form, not as a glimpse of an app. */
.hm-card-review[b-x69esb1yx8] {
    max-width: 260px;
    margin-right: 40px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.hm-card-head[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
}

.hm-chip[b-x69esb1yx8] {
    padding: 4px 11px;
    border-radius: 999px;
    background: var(--l-green-pale);
    color: var(--l-green-text);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.hm-meta[b-x69esb1yx8],
.hm-mini-label[b-x69esb1yx8] {
    font-size: 11px;
    font-weight: 600;
    color: var(--l-text-3);
}

.hm-card-title[b-x69esb1yx8] {
    font-size: 15px;
    font-weight: 700;
    color: var(--l-text);
    margin-bottom: 14px;
}

.hm-card-row[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--l-border);
}

.hm-avatar[b-x69esb1yx8] {
    width: 28px;
    height: 28px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--l-green-pale);
    color: var(--l-green-text);
    font-size: 11px;
    font-weight: 700;
}

.hm-name[b-x69esb1yx8] {
    flex: 1;
    font-size: 13px;
    font-weight: 600;
    color: var(--l-text);
}

.hm-rating[b-x69esb1yx8] {
    font-size: 12px;
    font-weight: 700;
    color: #B45309;
}

.hm-card-foot[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-top: 14px;
}

.hm-price[b-x69esb1yx8] {
    font-size: 17px;
    font-weight: 800;
    color: var(--l-green-text);
}

.hm-tag[b-x69esb1yx8] {
    padding: 4px 10px;
    border-radius: 999px;
    background: var(--l-bg-alt);
    color: var(--l-text-3);
    font-size: 11px;
    font-weight: 600;
}

.hm-mini-stars[b-x69esb1yx8] {
    font-size: 15px;
    letter-spacing: 2px;
    color: #F59E0B;
}

.hm-mini-text[b-x69esb1yx8] {
    font-size: 13px;
    font-style: italic;
    color: var(--l-text-2);
}

/* One column below the width where two would squeeze the headline. The panel keeps
   its place BELOW the copy — a reader on a phone should reach the button without
   scrolling past a picture first. */
@media (max-width: 900px) {
    .hero-mitra .hero-inner[b-x69esb1yx8] {
        grid-template-columns: 1fr;
        gap: 36px;
    }

    .hero-mitra-art[b-x69esb1yx8] {
        align-items: stretch;
    }

    .hm-card[b-x69esb1yx8],
    .hm-card-review[b-x69esb1yx8] {
        max-width: none;
        margin-right: 0;
    }
}

/* ── What the mitra hero drops when the screen is too short to hold it ──
   🚨 THE CONSTRAINT IS HEIGHT, NOT WIDTH, and keying it on width is what broke. The rule
   here used to read `@media (max-width: 480px)`, sized against one device (390×844), on the
   reasoning that "the two-card stack is intact from 481px up, where there is room for it".
   There is room for it at 481px up only if the WINDOW IS ALSO TALL. Susanti, 8/27: "kartu
   penilaian masuk hanya terlihat title nya kalau di layar yang lebih sempit".

   WHY ANYTHING THAT OVERFLOWS IS LOST FOR GOOD, rather than merely below the fold: .hero-stack
   is `position: sticky` and .after-hero slides up OVER it, so the hero's bottom edge is
   covered before it can ever be scrolled to. Measured 8/27 at 640×720: the review card sat at
   y 769 to 871 and reported 0 visible pixels at every scroll offset from 0 to 400. It is not
   reachable, it is gone.

   THE BUDGET, and every one of these was measured at 640×720 (656px of hero to spend):

     pelanggan  622  fits
     mitra      878  222px over
     agen       492  fits

   The mitra face is the only one that does not fit, the same finding the 390x844 note
   recorded, at a different size. And because all three heroes share ONE grid cell
   (see .hero-stack), the mitra overflow sets the stack height for the other two as well,
   which is what pushes the face dots off the bottom of the screen and onto the cards.

   WHAT GOES, AND IN WHICH ORDER. Unchanged reasoning from the rule this replaces: the art is
   decoration (aria-hidden, "illustrative placeholders"), the trust list directly above makes
   the same claims in words, and a reader should reach the button without scrolling past a
   picture. So the review card goes first (the earnings card is the offer, the review is a
   flourish on top of it), and the whole panel goes only when even one card will not fit.

   THE TWO NUMBERS. Stacked, the panel needs ~880-900px with both cards and ~760-780px with
   one, against a budget of `vh - 64`. 1040 and 900 are those two figures plus real headroom,
   deliberately NOT the exact measurements: the old rule cleared 390×844 by 2px, and 2px is a
   coincidence rather than a margin, and it is why any phone with slightly taller browser chrome
   reported this bug again. Note what `vh` means to a real handset: 100svh excludes the URL
   bar, so a 390×844 phone hands this hero well under 780px in practice. */
@media (max-width: 900px) and (max-height: 1040px) {
    .hero-mitra .hm-card-review[b-x69esb1yx8] {
        display: none;
    }
}

@media (max-width: 900px) and (max-height: 900px) {
    /* display:none, not visibility: it is a grid item, so this takes its 36px row gap with
       it. Measured: 223px recovered, which is the card plus the gap. */
    .hero-mitra-art[b-x69esb1yx8] {
        display: none;
    }
}

.hero[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    top: 0;
    right: -80px;
    width: 400px;
    height: 400px;
    border-radius: 50%;
    background: rgba(33, 150, 243, 0.08);
    pointer-events: none;
}

.hero[b-x69esb1yx8]::after {
    content: '';
    position: absolute;
    bottom: -60px;
    left: -100px;
    width: 300px;
    height: 300px;
    border-radius: 50%;
    background: rgba(21, 101, 192, 0.06);
    pointer-events: none;
}

    /* The navbar is no longer nested here, so this padding is the hero's own top
       breathing room rather than clearance for a bar sitting on top of it. */
    .hero-inner[b-x69esb1yx8] {
        max-width: 720px;
        padding-top: 76px;
        position: relative;
        z-index: 2;
    }

    /* ── Aurora field ──
       Three soft radial blobs drifting slowly across the hero. This is the "eye candy"
       the brand brief asked for, and it is the technique Stripe and Linear use to make
       a flat gradient feel three-dimensional. The blobs sit BEHIND the content
       (z-index:1 to .hero-inner's 2) and are clipped by .hero's overflow:hidden, so
       they never leak into the sections below.

       The animation is registered ONLY where the reader did not ask for reduced motion
       — otherwise the blobs sit frozen at the static positions declared below, which
       still gives the hero the same depth without the movement. */
    .hero-aurora[b-x69esb1yx8] {
        position: absolute;
        inset: 0;
        z-index: 1;
        pointer-events: none;
        overflow: hidden;
    }

    /* NO filter: blur() here — the softness comes from the gradient itself.
       The blobs used to pair a radial gradient with blur(48px), but a gradient
       that eases to transparent through a mid-stop looks the same soft glow and
       the graphics engine gets it for free, while a real blur is a texture it
       must compute (2026-07-21, Susanti: use a fade instead of blur — correct).
       The boxes grew ~15% because the blur used to smear the glow past the box
       edge; the gradient has to reach that far on its own. */
    .aurora-blob[b-x69esb1yx8] {
        position: absolute;
        border-radius: 50%;
        opacity: 0.55;
        will-change: transform;
    }

    /* Three blobs, three hues, three corners — so the field is uneven by design
       (two blobs in the same place is a single bigger blob, not a field). */
    .aurora-blob-a[b-x69esb1yx8] {
        top: -12%;
        left: 10%;
        width: 530px;
        height: 530px;
        background: radial-gradient(circle, rgba(79, 163, 232, 0.5), rgba(79, 163, 232, 0.22) 42%, transparent 72%);
    }

    .aurora-blob-b[b-x69esb1yx8] {
        top: 28%;
        right: -8%;
        width: 600px;
        height: 600px;
        background: radial-gradient(circle, rgba(124, 198, 255, 0.38), rgba(124, 198, 255, 0.17) 42%, transparent 72%);
    }

    .aurora-blob-c[b-x69esb1yx8] {
        bottom: -20%;
        left: 38%;
        width: 480px;
        height: 480px;
        background: radial-gradient(circle, rgba(13, 71, 161, 0.42), rgba(13, 71, 161, 0.19) 42%, transparent 72%);
    }

    /* Same two guards as the reveal rules above, for the same reason: a reader who
       asked for less motion gets the static field, and the field is still pleasant
       frozen. The drift is slow on purpose — a fast aurora is a distraction. */
    @media (prefers-reduced-motion: no-preference) {
        .aurora-blob-a[b-x69esb1yx8] { animation: aurora-drift-a-b-x69esb1yx8 18s ease-in-out infinite alternate; }
        .aurora-blob-b[b-x69esb1yx8] { animation: aurora-drift-b-b-x69esb1yx8 22s ease-in-out infinite alternate; }
        .aurora-blob-c[b-x69esb1yx8] { animation: aurora-drift-c-b-x69esb1yx8 26s ease-in-out infinite alternate; }
    }

    /* Translate ONLY — no scale(). Scaling a layer that carries a 48px blur makes
       the compositor re-rasterize the blurred texture as the scale changes, and
       three of those re-blurring at once was a steady drain on the hero while
       anything else (like scroll) was happening. A pure translation moves the
       once-rasterized texture for free; the drift reads the same. */
    @keyframes aurora-drift-a-b-x69esb1yx8 {
        from { transform: translate(0, 0); }
        to   { transform: translate(60px, 30px); }
    }

    @keyframes aurora-drift-b-b-x69esb1yx8 {
        from { transform: translate(0, 0); }
        to   { transform: translate(-50px, 40px); }
    }

    @keyframes aurora-drift-c-b-x69esb1yx8 {
        from { transform: translate(0, 0); }
        to   { transform: translate(30px, -40px); }
    }

    /* ── Dot grid: no longer an ELEMENT (7/29) ──
       The 26px halftone is still on this face; it moved into `.hero`'s own background
       stack, where the note explaining it lives. What went is the <div> and its
       `mask-image`, which is what made it a separately composited layer across the whole
       hero. Do not reintroduce it as an overlay element. */

/* ── Floating product mock-cards ──
   The "eye candy" half of the hero. Two cards tilted at opposite angles drift slowly
   behind the trust strip, visualising the two doors the CTAs open (hire-blue on the
   left, earn-green on the right) plus a small rating chip. They are pure CSS
   glassmorphism: a translucent fill, a 1px light border, and a backdrop blur — the
   same recipe Apple and Stripe use for product floats.

   They sit OUTSIDE .hero-inner (which is centred and only 720px wide) so they can
   reach the full viewport width and peek in from both edges. They are clipped by
   .hero's overflow:hidden, so they never leak into the next section. They are also
   hidden on narrow screens (see the responsive block) — on a phone they would
   collide with the centred text and read as clutter rather than depth. */
.hero-floats[b-x69esb1yx8] {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
}

/* The float cards need a viewport wide enough that the centred hero column leaves
   real space at both edges. Measured 2026-07-25 (Playwright, per-width overlap test):
   the job/earn cards still cross the headline and sub at 1100/1200/1280 and only clear
   the text at ~1366px. The old 900px cutoff therefore left every 900–1365px screen —
   most laptops — showing cards straight through the copy. Hide until 1400px (a safety
   margin past 1366) so the cards appear only where they have real room. */
@media (max-width: 1400px) {
    .hero-floats[b-x69esb1yx8] {
        display: none;
    }
}

/* WHITE PRODUCT CARDS, NOT GLASS (Susanti, 7/29: "mungkin kita tidak perlu pakai glass
   effect disini, cukup kartu putih?"). They were glass for their whole life — painted
   from 7/21, a real baked blur earlier the same day — and the glass was working against
   what they are for. Their job is to show a reader what the app looks like, and the
   app's job cards are white cards on a light ground. Frosted blue said "decoration";
   white says "this is the screen the button opens".

   It also settles what the glass kept raising: this hero now has ONE glass surface, the
   trust strip, instead of three competing ones.

   THE VOCABULARY IS .hm-card's, deliberately. The mitra hero already floats white
   product cards over a coloured ground for exactly this purpose, and two faces showing
   the same product should show it in the same clothes — same fill, border, radius, the
   same divider under the name row. Only the accent moves: blue here, green there,
   following each face's strand.

   NO opacity. The old 0.92 was how glass sat back into the gradient; on white it just
   mixes blue into the card and turns it grey. What keeps these from shouting is the
   shadow and the tilt, not transparency. */
.float-card[b-x69esb1yx8] {
    position: absolute;
    width: 268px;
    padding: 18px 20px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius);
    /* Deeper and bluer than .hm-card's shadow: that one falls on a near-white ground,
       this one has to read as lifted off a saturated blue. */
    box-shadow: 0 20px 46px rgba(6, 32, 78, 0.34);
    color: var(--l-text);
    text-align: left;
}

.float-card-job[b-x69esb1yx8] {
    top: 22%;
    left: 4%;
    transform: rotate(-4deg);
}

/* .float-card-earn — the third card, the one that sat at top:36% right:4% — has no
   markup any more; it went when the hero stopped selling to two audiences at once.
   Its rule is kept because the slot is real: if a third card ever returns to this hero,
   right-hand-upper is where it goes. (Its bob went with the other two on 7/29.) */
.float-card-earn[b-x69esb1yx8] {
    top: 36%;
    right: 4%;
    transform: rotate(3.5deg);
}

/* RIGHT-HAND CARD, MIDDLE — not the bottom corner. It was `bottom: 7%` until 7/28
   (Susanti: "jangan terlalu bawah, agak janggal"), and janggal is the right word for
   what that produced: the earn card above it had already been deleted, so the right
   half of the hero was empty from the navbar down to a card wedged 58px off the floor,
   below even the trust strip. It read as something that had slipped rather than as
   something placed. 50% puts it opposite the job card's 22%, a diagonal across the
   centred column. */
.float-card-small[b-x69esb1yx8] {
    top: 50%;
    right: 4%;
    width: 210px;
    padding: 14px 16px;
    transform: rotate(-2deg);
    display: flex;
    flex-direction: column;
    gap: 3px;
}

/* 🚨 THE CARDS DO NOT BOB ANY MORE (7/29, Susanti, on a lag report). They used to sway
   6–8.5s vertically, staggered so they never moved in lockstep. The tilt stays — it is
   a static `rotate()` on each card's own rule and it is what makes them read as
   floating; only the movement is gone.

   The reasoning is the measurement, not a guess. Traced across the three faces over the
   cover-over climb: mitra is genuinely the cheapest, but agen — which she also calls
   smooth — measures no cheaper than this face. The difference between them is not cost,
   it is that MOTION IS WHAT MAKES A DROPPED FRAME VISIBLE. Agen's hero is a still
   photograph, so frames it drops are invisible; this hero had five objects moving during
   the climb (3 drifting aurora blobs + these 2 cards), and every dropped frame showed up
   as a judder in one of them. Removing the sway removes the tell, not the cost.

   The three drifting blobs are still moving — if the stutter is still visible, they are
   next. */

.float-card-head[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
}

.float-chip[b-x69esb1yx8] {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

/* .hm-chip is the same chip on the mitra face and pairs --l-green-pale with
   --l-green-text. This page has no pale-blue token, so the literal is that pair's blue
   half; --l-blue is the text colour it is built to sit under. */
.float-chip-blue[b-x69esb1yx8] {
    background: #E8F1FC;
    color: var(--l-blue);
}

/* Unused since the earn card left (see .float-card-earn), kept with the slot. */
.float-chip-green[b-x69esb1yx8] {
    background: var(--l-green-pale);
    color: var(--l-green-text);
}

.float-card-meta[b-x69esb1yx8] {
    font-size: 11px;
    color: var(--l-text-3);
    font-weight: 500;
}

.float-card-title[b-x69esb1yx8] {
    font-size: 15px;
    font-weight: 700;
    color: var(--l-text);
    margin-bottom: 10px;
    line-height: 1.3;
}

/* The divider is .hm-card-row's, and it is what makes these read as the same product
   as the mitra hero's cards rather than as a differently-shaped mock-up. */
.float-card-row[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 9px;
    padding-bottom: 12px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--l-border);
}

.float-avatar[b-x69esb1yx8] {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    color: #fff;
    flex-shrink: 0;
}

.float-avatar-a[b-x69esb1yx8] { background: linear-gradient(135deg, #1565C0, #4FA3E8); }
.float-avatar-b[b-x69esb1yx8] { background: linear-gradient(135deg, #16A34A, #4ADE80); }

.float-card-name[b-x69esb1yx8] {
    flex: 1;
    font-size: 12px;
    font-weight: 600;
    color: var(--l-text);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Amber on white, the same value .hm-rating uses — the pale #FDE68A it wore was tuned
   to glow on dark blue and is unreadable on a white card. */
.float-rating[b-x69esb1yx8] {
    font-size: 11px;
    font-weight: 700;
    color: #B45309;
}

.float-card-foot[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* Blue where .hm-price is green — same role, each face's own strand colour. */
.float-price[b-x69esb1yx8] {
    font-size: 14px;
    font-weight: 800;
    color: var(--l-blue);
    letter-spacing: -0.01em;
}

.float-tag[b-x69esb1yx8] {
    font-size: 10px;
    font-weight: 600;
    padding: 3px 8px;
    border-radius: 999px;
    background: var(--l-bg-alt);
    color: var(--l-text-3);
}

/* Unused since the earn card left, kept with the slot. */
.float-tag-green[b-x69esb1yx8] {
    background: var(--l-green-pale);
    color: var(--l-green-text);
}

.float-mini-label[b-x69esb1yx8] {
    font-size: 10px;
    font-weight: 600;
    color: var(--l-text-3);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.float-mini-stars[b-x69esb1yx8] {
    font-size: 13px;
    color: #F59E0B;
    letter-spacing: 1px;
}

.float-mini-text[b-x69esb1yx8] {
    font-size: 11px;
    color: var(--l-text-2);
    font-style: italic;
}

.hero-badge[b-x69esb1yx8] {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 16px;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: var(--l-on-blue);
    font-size: 13px;
    font-weight: 600;
    border-radius: 999px;
    margin-bottom: 24px;
    letter-spacing: 0.02em;
}

/* A live "we are here" pulse next to the badge text. Pulses only where motion is
   welcome; otherwise it is a plain dot, which still reads as "active".

   🚨 TWO PSEUDO-ELEMENTS, AND THE ANIMATION TOUCHES ONLY transform + opacity. This was
   an expanding `box-shadow` spread until 7/29, which is the textbook way to write this
   ring and also a repaint on EVERY frame, forever, for as long as the hero is on screen
   — box-shadow is not a compositable property, so it drags the main thread even when
   nothing is scrolling. Traced and A/B'd alternately (3 runs each way): it was costing
   about 9% of BeginMainFrame and of UpdateLifecycle. Modest, but it was buying nothing.

   PAINT ORDER IS THE WHOLE TRICK: ::before is the ring and ::after is the dot, so the
   dot covers the ring's centre the way the element's own background used to cover the
   shadow's. Swap them and the ring washes over the dot as it fades. */
.hero-badge-dot[b-x69esb1yx8] {
    position: relative;
    width: 8px;
    height: 8px;
    flex-shrink: 0;
}

/* The ring. scale(3.5) reproduces the old 10px spread: 8px + 2×10px = 28px, 28/8 = 3.5. */
.hero-badge-dot[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: rgba(74, 222, 128, 0.7);
    opacity: 0;
    pointer-events: none;
}

/* The dot itself, painted over the ring's centre. */
.hero-badge-dot[b-x69esb1yx8]::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: #4ADE80;
}

@media (prefers-reduced-motion: no-preference) {
    .hero-badge-dot[b-x69esb1yx8]::before {
        animation: hero-badge-pulse-b-x69esb1yx8 2.4s ease-out infinite;
    }
}

@keyframes hero-badge-pulse-b-x69esb1yx8 {
    0%   { transform: scale(1);   opacity: 0.7; }
    70%  { transform: scale(3.5); opacity: 0; }
    100% { transform: scale(3.5); opacity: 0; }
}

.hero-title[b-x69esb1yx8] {
    font-size: clamp(32px, 5vw, 56px);
    font-weight: 800;
    line-height: 1.12;
    letter-spacing: -0.035em;
    margin: 0 0 20px;
    color: #fff;
    /* The hard <br> splits the sentence at its comma, which is right on a wide screen.
       On anything narrower the second half wraps again and drops its last word onto a
       line of its own — measured at 834px: "datang." sat alone under a full line. Balance
       shares the words evenly across however many lines the width forces, so there is no
       one-word last line at any size. Browsers without it just wrap normally, which is
       what happens today. */
    text-wrap: balance;
}

.hero-gradient[b-x69esb1yx8] {
    background: linear-gradient(135deg, #C9E4FF 0%, #E6F2FF 50%, #FFFFFF 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}

/* THE HERO SWAP IS GONE (2026-07-27) — the whole block, on purpose, recorded here
   because it was a real feature for four days and someone will wonder where it went.
   Susanti's spec (7/23) was one pinned stage for two audiences: load showed the
   CLIENT pitch, the first stretch of scrolling crossfaded the text to the MITRA
   pitch, then the page moved on. It needed no new pin (the hero is already sticky
   for the whole page) — only scroll DISTANCE, supplied by an empty .hero-spacer
   between the hero and .after-hero, whose view-timeline drove both faces.

   It answered a real problem: two first-time audiences, one headline. That problem
   is now answered by giving each audience a PAGE (7/27), so .hero-swap, .hero-face*,
   .hero-spacer, --hero-swap and the two hero-face keyframes all went at once. What is
   left is .hero-headline, a plain block wrapper with no styling of its own — the
   markup keeps a wrapper so the hero's badge/title/sub stay one unit. */

/* --l-on-blue, not a fainter white: the old 0.6 was tuned against a near-black
   gradient. On the brighter blue this hero now uses, 0.6 white falls close to the
   background and the sentence goes soft. */
.hero-sub[b-x69esb1yx8] {
    font-size: clamp(16px, 2.5vw, 20px);
    line-height: 1.65;
    color: #FFFFFF;
    margin: 0 auto 28px;
    max-width: 560px;
}

/* Susanti 7/25: a panel wrapping the WHOLE hero block — badge, title, paragraph and buttons
   — so all of it reads cleanly over the busy aerial photo, not just the paragraph.

   ── PLAIN TRANSPARENCY, NOT GLASS (Susanti, 7/30: "effect kaca di hero agen terlalu
      mencolok… ganti jadi transparan biasa dan jangan terlalu gelap, cukup frame konten
      dengan ringan, agar terkesan luas") ──
   Until now this carried a baked, blurred copy of the photograph masked in from the top,
   under a tint running 0.30 → 0.62. Both are gone, and the class is called -frame rather
   than -glass because there is no glass left to name. What holds the content together now
   is a hairline and a wash light enough to see the aerial through at every height: the
   panel says "the words start here" and stops there. The photograph is the reason this
   face exists, and it is now visible across the whole panel instead of behind a frosting.

   🚨 THE WASH CANNOT GO TO ZERO, however light it looks. White text is standing on a
   PHOTOGRAPH, and this panel is the second half of what makes that legible — the first is
   the hero's own scrim (180deg, 0.55 → 0.42 → 0.58, on .hero-agen). It still deepens
   downward for the reason it always did: the top holds a 56px headline that survives
   anything, the bottom holds 16px sub-copy and two buttons that do not. Before lightening
   the bottom stop again, look at the sub-paragraph over the BRIGHTEST part of the aerial,
   not over an average one.

   🚨 DO NOT REACH FOR backdrop-filter TO BRING THE GLASS BACK. It samples what is behind
   the element every frame; this panel sits inside a hero that the cover-over timeline
   scrubs on every scroll frame, and an animated ancestor of a backdrop-filter re-samples
   on the main thread — that is precisely what made the hero stutter on 2026-07-21. The
   blur that just left was a baked static layer to dodge that cost, and even so it could
   never line up with the hero's crop: `background-attachment: fixed` is the one mechanism
   that would have, and it silently does nothing here because .hero-inner carries a
   transform (hero-recede) and a transformed ancestor becomes the containing block for
   fixed backgrounds. Computed style still reports `fixed` — the only way to catch it is to
   look at the screen. */
.hero-frame[b-x69esb1yx8] {
    position: relative;
    /* Keeps the z-index:-1 wash inside this box; without it the wash drops behind the
       hero's own background and disappears. */
    isolation: isolate;
    overflow: hidden;
    max-width: 760px;
    margin: 0 auto;
    padding: clamp(28px, 5vw, 44px) clamp(22px, 6vw, 52px);
    /* The frame itself, and the only edge the panel has now — a touch brighter than the
       0.10 it wore when a whole blurred slab was drawing the shape for it. */
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 24px;
}

/* The wash. One layer where there were two, and light enough at the top that the aerial
   reads straight through the panel's shoulders. */
.hero-frame[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    /* The bottom two stops are MEASURED, not chosen by eye. Sampling the aerial's actual
       pixels under the sub-paragraph, compositing the hero scrim and this wash over them,
       the brightest pixel in that band gave white text 4.35:1 at 0.18/0.30 — just under the
       4.5:1 that 20px regular text needs. 0.22/0.36 puts the worst case at 4.6:1 while the
       shoulders stay at 0.10 and the photograph still reads through the whole panel. */
    background: linear-gradient(180deg,
        rgba(8, 16, 32, 0.10) 0%,
        rgba(8, 16, 32, 0.22) 45%,
        rgba(8, 16, 32, 0.36) 100%);
}

/* 🚨 PHONES NEED MORE WASH, AND NOT BECAUSE THE PANEL CHANGED — THE PHOTOGRAPH DID.
   `cover` on a 390×844 box crops the aerial far tighter than on 1440×836, so a different
   and much brighter part of it lands behind the sub-paragraph. Measured on the real pixels:
   the desktop values leave the 16px sub-copy at 4.1:1 here, against the same 4.5:1 it clears
   at 4.7:1 on desktop. Nothing about the panel is wrong at that width; the ground under it
   is. Re-measure at BOTH widths after touching either set — one is not evidence for the
   other, which is exactly how this was nearly shipped failing. */
@media (max-width: 768px) {
    .hero-frame[b-x69esb1yx8]::before {
        background: linear-gradient(180deg,
            rgba(8, 16, 32, 0.14) 0%,
            rgba(8, 16, 32, 0.30) 45%,
            rgba(8, 16, 32, 0.44) 100%);
    }
}

.hero-frame .hero-cta[b-x69esb1yx8] { margin-bottom: 0; }

/* "Cek wilayah Anda" glides to chapter 01's "01 · Wilayah" label (smooth, via
   landing-agent.js), framing the whole chapter — label, title, picker, facts, portal.
   The sticky hero's bottom sits at #agen's top, so aiming at #agen stopped with the hero
   still covering the top strip ("tanggung"); aiming at the picker overshot the title
   ("over"). This offset clears the sticky nav and lands just past the hero. */
#agen[b-x69esb1yx8] { scroll-margin-top: 84px; }
.agen-ch-wilayah .agen-mark[b-x69esb1yx8] { scroll-margin-top: 118px; }

.hero-cta[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 56px;
}

/* Replaced the old .hero-stats metric strip — see the comment in Landing.razor.
   Deliberately NOT styled to look like figures: no big numerals, no dividers
   between "values". Three plain claims with a check, which is what they are. */
.hero-trust[b-x69esb1yx8] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 12px 28px;
    padding: 18px 32px;
    /* A PAINTED wash, and it must stay painted. A baked `filter: blur()` frost was
       tried here on 7/29 and removed the same day: "baked" only stays baked while
       nothing SCALES it, and this strip lives in .hero-inner, which runs `hero-recede`
       — `transform: translateY(-36px) scale(0.95)` driven by the cover-over timeline,
       i.e. a new scale factor on every scroll frame. The compositor re-rasterises a
       blurred texture each time the scale changes, so inside this subtree a baked blur
       behaves like a live one. The aurora blobs above carry the same rule in their own
       note ("Translate ONLY — no scale()"); this is that rule applied to a descendant
       instead of to the animated element itself. */
    background: linear-gradient(150deg,
        rgba(255, 255, 255, 0.20),
        rgba(255, 255, 255, 0.10) 60%,
        rgba(255, 255, 255, 0.14));
    border: 1px solid rgba(255, 255, 255, 0.26);
    border-radius: var(--radius);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24);
}

.hero-trust-item[b-x69esb1yx8] {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    font-size: 14px;
    font-weight: 500;
    color: var(--l-on-blue);
}

.hero-trust-item[b-x69esb1yx8]  svg {
    flex-shrink: 0;
    opacity: 0.9;
}

/* ====== AFTER-HERO — the cover card ======
   Everything below the hero lives in this wrapper. It is the thing the reader
   actually scrolls: the hero behind it is pinned (see .hero), and this slides up
   over it like a card being drawn over a poster. */
.after-hero[b-x69esb1yx8] {
    /* Positioned + raised, or it would paint UNDER the pinned (positioned) hero —
       painting order puts non-positioned siblings below positioned ones regardless
       of document order. */
    position: relative;
    z-index: 2;
    /* Its own ground, so the hero never shows through the gaps between sections. */
    background: var(--l-bg);
    border-radius: 28px 28px 0 0;
    box-shadow: 0 -18px 50px rgba(8, 47, 110, 0.18);
    /* NO overflow on this wrapper — not even clip. It briefly carried
       overflow:clip to round the first section's corners, and that wrapped the
       ENTIRE scrolling page in a rounded clip layer, which knocked scrolling off
       the compositor fast path — the whole page went laggy (reported 2026-07-21).
       The rounding is now on .story's own top corners instead; the radius here
       only shapes the shadow. */
    /* The timeline the ornament fades read. "entry" of this element = the cover card
       climbing its first viewport-height — exactly the covering moment. */
    view-timeline: --cover-over block;
}

/* 🚨 THE SLIDE-UP'S SPEED CANNOT BE CHANGED, AND IT IS NOT AN ANIMATION (asked twice,
   7/30: "the slide up speed follow the scroll speed, this is wrong… make the mouse
   scroll decide the end point"). Nothing animates this card. The hero is `position:
   sticky` and this element is simply scrolled over it, so its edge sits at
   `documentTop − scrollY` and moves exactly 1px per scrolled px. There is no ratio to
   tune.

   THE ONE LEVER WOULD BE A SCROLL-DRIVEN `translate` ON THIS ELEMENT, HOLDING IT BACK —
   and it must not be used. Scroll timelines measure LAYOUT positions, not transformed
   ones, so translating the element that contains every section on the page would leave
   all their `view-timeline` reveals firing tens of vh away from where the reader sees
   them. It also cannot be done without a cost: slowing the crossing by a third means a
   third more scrolling before the content arrives, and the alternative — lagging then
   catching up inside the same range — buys the slowdown with a lurch at the end
   (worked through: lag 20vh peaking mid-crossing gives 0.64× then 1.44×).

   What DID help her jitter was removing the second scrubbed motion, not slowing this
   one. See the note below where hero-recede used to be applied.

   Both guards as usual: no support → the ornaments simply stay lit behind the card
   (sticky itself needs no @supports), and reduced motion → no scrub. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        /* 🚨 `hero-recede` IS GONE (7/30). It dimmed the hero content to 0.25 and lifted
           it 36px, scrubbed 1:1 against the scroll, at the same time as the cover card
           climbed. Susanti: the slide-up reads jittery because it is bound to scroll
           speed. It is — the card IS the scroll, and that ratio cannot be changed (see
           the note over .after-hero) — but the card was not the only thing stepping. Two
           scrubbed motions ran together, so every wheel notch moved the card AND jumped
           the headline 4px while fading it. One stepping edge is a scroll; two stepping
           motions racing each other is what reads as jitter.

           What replaces it: nothing. The hero simply sits still and the card scrolls over
           it, which is what the mitra and agen faces will now do too. The card is opaque,
           so it covers the hero on its own; the dimming was only a depth cue.

           If the hero needs to fall back again, do it with opacity ALONE and no
           transform — a fade does not step the way a position does. */
        .hero-floats[b-x69esb1yx8] {
            animation: ornament-sleep-b-x69esb1yx8 linear both;
            animation-timeline: --cover-over;
            animation-range: entry 0% entry 78%;
        }

        /* MARKET-ONLY ORNAMENTS, PUT AWAY DURING THE CLIMB (7/29). Susanti, comparing
           the three faces: the slide-up off the mitra and agen heroes is smooth and off
           this one stutters. They are not the same hero — this face carried 3 drifting
           aurora blobs (each its own composited layer), a full-hero dot grid behind a
           `mask-image`, two bobbing cards with 46px shadows and two decorative circles;
           the other two render NONE of it. The grid and the bob were then deleted
           outright, so what is left to put away is the aurora and the cards' layer.

           The aurora fades out over the first half of the climb. Nothing is lost: by the
           time it goes, the cover card is already over half the screen and the reader is
           looking at it. */
        .hero-aurora[b-x69esb1yx8] {
            animation: ornament-sleep-b-x69esb1yx8 linear both;
            animation-timeline: --cover-over;
            animation-range: entry 0% entry 55%;
        }
    }
}

/* 🚨 OPACITY ONLY — NO `visibility`, and this keyframe is why the market face was still
   skipping frames after everything else was fixed (Susanti, 7/30).

   It ended `visibility: hidden` when I wrote it on 7/29, to take the ornaments out of
   compositing entirely. That backfired: an animation is composited only if EVERY
   property it touches is compositable, and `visibility` is discrete, so one `visibility`
   keyframe dragged the whole scroll-driven animation onto the main thread. There were two
   instances of it — on .hero-aurora and on .hero-floats — and both exist ONLY on this
   face, so I had handed the market hero two per-frame main-thread animations that mitra
   and agen do not have, while trying to make it smoother than them.

   Opacity alone still buys most of what the visibility flip was for: Chrome skips
   painting fully transparent content. What it does not do is force the scroll to wait for
   the main thread. That trade is not close. */
@keyframes ornament-sleep-b-x69esb1yx8 {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* `hero-recede` was deleted on 7/30 — see the note where it used to be applied, above
   .hero-floats. Two rules it taught, both still live elsewhere and both worth keeping:

   NEVER put `scale()` in a scroll-driven keyframe. This one ended
   `translateY(-36px) scale(0.95)` until 7/29; a new scale factor every scroll frame
   re-rasterises everything underneath — a 56px headline, the buttons, the trust strip,
   two shadowed cards. Translate and opacity move an already-rasterised layer for free.
   The aurora blobs carry the same rule in their own note ("Translate ONLY — no scale()",
   2026-07-21); that one is about the animated element, this was about a whole subtree.

   And it is why no `filter: blur()` may live inside .hero-inner even when it is baked:
   see .hero-trust. */

/* Once the cover card fully hides the hero, put the WHOLE hero to sleep.
   Without this the pinned hero stays composited for the entire page — aurora
   blobs with 48px blurs, the badge pulse, the dot grid — all painting behind
   content that fully covers them, which dragged scrolling on the rest of the
   page (reported 2026-07-21). visibility is discretely animatable: this flips
   to hidden right as entry completes, and scrolling back up flips it back. */
/* 🚨 ON .hero-stack, NOT .hero (moved 7/30 with the sticky). It has to hide the whole stack:
   a parked face is already visibility:hidden by the swap rules, but the SHOWN one would keep
   painting behind the cover card if this still only reached one of the three. Also, .hero now
   carries a `transition` on visibility for the swap — putting a visibility ANIMATION back on
   the same element would fight it, and the animation always wins, which would strand a
   leaving panel invisible mid-travel. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .hero-stack[b-x69esb1yx8] {
            animation: hero-sleep-b-x69esb1yx8 linear both;
            animation-timeline: --cover-over;
            animation-range: entry 99% entry 100%;
        }
    }
}

@keyframes hero-sleep-b-x69esb1yx8 {
    from { visibility: visible; }
    to   { visibility: hidden; }
}

/* ====== SECTIONS SHARED ====== */
.section-eyebrow[b-x69esb1yx8] {
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    /* The line above a section title is that section's voice, so it speaks in the face's
       colour. 13px caps — the TEXT token, not the fill one. */
    color: var(--face-accent-text);
    margin-bottom: 12px;
    text-align: center;
}

.section-heading[b-x69esb1yx8] {
    font-size: clamp(26px, 4vw, 40px);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.15;
    margin: 0 0 12px;
    text-align: center;
    color: var(--l-text);
}

.section-sub[b-x69esb1yx8] {
    font-size: 16px;
    line-height: 1.6;
    color: var(--l-text-2);
    max-width: 560px;
    margin: 0 auto 48px;
    text-align: center;
}

/* ====== FEATURES ====== */
/* ====== SCROLL REVEAL ======
   Two guards, both required, in this order:
     @supports  — a browser without scroll-driven animations must simply ignore this
     @media     — a reader who asked for less motion must never get any
   The un-animated state is the FINISHED state (declared nowhere, i.e. the element's
   normal appearance), so anything that fails either guard shows fully-formed content.
   That is what makes this safe on the marketing host, where no JavaScript runs and a
   reveal implemented in script would leave the page permanently blank. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .reveal[b-x69esb1yx8] {
            animation: reveal-rise-b-x69esb1yx8 linear both;
            animation-timeline: view();
            /* Finish well before the element reaches the middle of the screen —
               content that is still animating when you are ready to read it is
               worse than content that never moved. */
            animation-range: entry 5% cover 28%;
        }
    }
}

@keyframes reveal-rise-b-x69esb1yx8 {
    from { opacity: 0; transform: translateY(22px); }
    to   { opacity: 1; transform: none; }
}

/* ====== STAGED ENTRANCES (.reveal-seq) ======
   The presentation feel: the parts of a block arrive one after another as the block
   comes up the screen, instead of the whole block being simply there. Heading first,
   then the visual, then each item.

   HOW THE STAGGER IS DONE, because it is not the obvious way. With a scroll-driven
   timeline, `animation-delay` does nothing — there is no clock to delay against; the
   animation's position is a pure function of where the element sits on screen. The
   sequence therefore has to come from giving each child a DIFFERENT animation-range,
   so child 2 finishes its rise slightly further up the screen than child 1. Reaching
   for animation-delay here is the mistake to avoid; it silently does nothing.

   Each child is measured against ITS OWN box. For children stacked vertically that is
   already a stagger by construction, and the per-child offsets below just tighten it.
   For children side by side — the bento tiles, the three steps — all four enter the
   screen together, and these offsets are the only thing creating the sequence.

   Same two guards as .reveal above, in the same order and for the same reasons: an
   unsupporting browser and a reader who asked for less motion both get the finished
   state, which is the element's ordinary appearance. That is what makes this safe on a
   page with no circuit — nothing here is required for the content to exist. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .reveal-seq > *[b-x69esb1yx8] {
            animation: reveal-rise-b-x69esb1yx8 linear both;
            animation-timeline: view();
            animation-range: entry 10% cover 26%;
        }

        /* ~5% of the element's own entry per step. Small on purpose: a stagger a reader
           consciously notices is a stagger that made them wait. */
        .reveal-seq > *:nth-child(2)[b-x69esb1yx8] { animation-range: entry 15% cover 31%; }
        .reveal-seq > *:nth-child(3)[b-x69esb1yx8] { animation-range: entry 20% cover 36%; }
        .reveal-seq > *:nth-child(4)[b-x69esb1yx8] { animation-range: entry 25% cover 41%; }
        .reveal-seq > *:nth-child(n+5)[b-x69esb1yx8] { animation-range: entry 30% cover 46%; }

        /* The "Cek wilayah Anda" picker sits out of the sequence (Susanti, 7/23:
           no fade on it while scrolling). It is an interactive form control, and
           a control that fades and rises under the pointer reads as flaky rather
           than as motion design — it is simply there, solid, at every scroll
           position. Placed after the nth-child rules at equal specificity so the
           cascade settles it. Its OTHER job is untouched: it still carries the
           --agen-fill view-timeline the portal panel reads. */
        .reveal-seq > .agen-pick[b-x69esb1yx8] {
            animation: none;
        }

        /* The facts strip is kept FULLY static — this excludes the UL, and the rule further
           down no longer animates its items either. "Cek wilayah Anda" parks the page right
           where this strip's scroll-reveal is unfinished (UL ~87%, and on mobile its items
           at 40–80%), and a frozen mid-rise is faded + sub-pixel-shifted = a permanent blur
           (Susanti 7/25). Crisp-always beats a flourish that can freeze mid-way here. */
        .reveal-seq > .agen-facts[b-x69esb1yx8] {
            animation: none;
        }

        /* Grids stagger their own items — this is where the sequence is actually felt,
           because these sit side by side and would otherwise all land at once.

           .agen-why, .agen-steps, .agen-product-list and .agen-faq USED to be here.
           They now live inside tab panels, and a tab panel's contents are driven by the
           timed rules below instead — an element carrying both a scroll timeline and a
           timed animation has the two fight over it. The two lists that are still
           reached purely by scrolling are these. */
        /* Only the bench grid still reveals on scroll. The facts strip is deliberately NOT
           animated (see the .agen-facts exclusion above): "Cek wilayah Anda" parks the page
           with the strip low in the viewport — desktop leaves the last item ~96%, mobile
           leaves all three at 40–80% — and a frozen mid-rise is faded + sub-pixel-shifted,
           which reads as a permanent blur (Susanti 7/25). A three-item stat strip loses
           nothing by simply being there, crisp. */
        .agen-bench-grid > *[b-x69esb1yx8] {
            animation: reveal-rise-b-x69esb1yx8 linear both;
            animation-timeline: view();
            animation-range: entry 12% cover 30%;
        }

        .agen-bench-grid > *:nth-child(2)[b-x69esb1yx8] { animation-range: entry 18% cover 36%; }
    }
}


/* ══ PER-SECTION ENTRANCES — each section arrives its OWN way (vivo-style) ══════════
   The staged rise-fade above is the page's DEFAULT. Several sections were given a
   distinct entrance so scrolling the page does not read as the same reveal on repeat
   — the reference is vivo's product pages, where every block arrives differently.
   Approved from a prototype (2026-07-21). The bridge deliberately keeps the plain
   fade: it is a thin hand-off between two pinned chapters, not a section in its own
   right, and a fifth flourish there would crowd them.

   Both guards as everywhere else — no scroll-timeline support or reduced-motion gets
   the finished state, which is each element's ordinary appearance.

   WHY `translate:` AND NOT `transform:` on the slides — .path-card lifts on hover with
   `transform: translateY(-3px)`. A scroll animation that settles on `transform: none`
   would win the cascade and kill that lift forever once it finished. `translate` is a
   separate, composited property, so the entrance slide and the hover lift coexist. The
   other slid elements have no hover transform, but use `translate` too for one rule. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {

        /* Cara Kerja keeps the DEFAULT rise-fade (.reveal on each step) — the
           alternating-sides idea was tried and reverted at Susanti's call. */

        /* Download — three tiles rise ONE AT A TIME and the row is complete exactly
              at screen centre: contiguous cover-phase windows ending at cover 50% (the
              row's centre aligned with the viewport's). Nothing moves while the row is
              still low on the screen.

              🚨 THESE OFFSETS BELONG TO .download's HEIGHT. `cover` percentages are
              relative to H + V, so changing that height silently moves this reveal. When
              the section was briefly given a full screen on 7/30 these same numbers left
              the third tile invisible with the band CENTRED on screen (measured 26% on
              desktop, 0% on a phone). Both were reverted together; if the height ever
              changes again, re-measure this. */
        .download-card[b-x69esb1yx8] { animation: card-rise-b-x69esb1yx8 ease-out both; animation-timeline: view(); }
        .download-card:nth-child(1)[b-x69esb1yx8] { animation-range: cover 20% cover 30%; }
        .download-card:nth-child(2)[b-x69esb1yx8] { animation-range: cover 30% cover 40%; }
        .download-card:nth-child(3)[b-x69esb1yx8] { animation-range: cover 40% cover 50%; }

        /* CTA — the full-bleed band opens from the centre like a curtain (clip-path),
              edge to edge — not a fade, not a slide. It opens as the band RISES into
              view and completes only when the band is fully in view, so the reveal is
              actually seen (Susanti 7/25: entry-60% finished it far too early — blue
              full long before arrival). 🚨 STAY IN THE `entry` PHASE: `cover` spans
              viewport-height + element-height, so cover 10% is only ~entry 38% here — a
              range like `entry 40% cover 10%` is DEGENERATE (end before start) and snaps
              to full. `entry 100%` = band's bottom at the viewport bottom = fully
              visible; the page then scrolls ~footer-height (≈90px) more — a small,
              geometry-independent settle. */
        /* Two halves of one reveal, on the same range: ::before is the curtain opening
           from the centre, the section itself only fades. Both are compositable. */
        .cta-banner[b-x69esb1yx8]::before { animation: cta-curtain-b-x69esb1yx8 ease-out both; animation-timeline: view(); animation-range: entry 30% entry 100%; }
        .cta-banner[b-x69esb1yx8]         { animation: cta-fade-b-x69esb1yx8    ease-out both; animation-timeline: view(); animation-range: entry 30% entry 100%; }
    }
}
@keyframes card-rise-b-x69esb1yx8        { from { opacity: 0; translate: 0 44px; } to { opacity: 1; translate: 0 0; } }
/* scaleX(0.08) is the old `inset(0 46%)`: 100% − 46% − 46% = 8% of the width left in
   the middle. Centre origin, so it opens both ways exactly as the clip did. */
@keyframes cta-curtain-b-x69esb1yx8      { from { transform: scaleX(0.08); } to { transform: scaleX(1); } }
@keyframes cta-fade-b-x69esb1yx8         { from { opacity: 0.45; } to { opacity: 1; } }


/* ── The expanded "Sebelum memutuskan" sections: entrance + inner stagger ──
   These were tab panels (a timed animation replayed on each tab switch). Now they are
   a scrolled stack, so the motion is scroll-DRIVEN: each section rises gently as it
   enters, and its inner pieces (why-cards, list rows, steps, FAQ items) follow one
   after another — the "focus direction" that walks the eye down a section instead of
   dropping it on a wall of text.

   The rise is deliberately SOFT — agen-soft-in starts at 0.67 opacity / +10px, about a
   THIRD of the page's default reveal-rise. The panel already makes a big move riding up
   over the cover photo, and a full fade on top of that read as too strong (reported);
   a third is the ask. animation-delay does nothing on a scroll timeline, so the stagger
   is per-item animation-range, not delay. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .agen-section[b-x69esb1yx8] {
            animation: agen-soft-in-b-x69esb1yx8 ease-out both;
            animation-timeline: view();
            animation-range: entry 8% cover 18%;
        }

        .agen-section .agen-why-card[b-x69esb1yx8],
        .agen-section .step-card[b-x69esb1yx8],
        .agen-section .agen-product-list li[b-x69esb1yx8],
        .agen-section .agen-faq-item[b-x69esb1yx8],
        .agen-section .agen-anchor[b-x69esb1yx8],
        .agen-section .agen-sim[b-x69esb1yx8] {
            animation: agen-soft-in-b-x69esb1yx8 ease-out both;
            animation-timeline: view();
            animation-range: entry 12% cover 22%;
        }

        .agen-section .agen-why-card:nth-child(2)[b-x69esb1yx8],
        .agen-section .step-card:nth-child(3)[b-x69esb1yx8],
        .agen-section .agen-product-list li:nth-child(2)[b-x69esb1yx8],
        .agen-section .agen-faq-item:nth-child(2)[b-x69esb1yx8] { animation-range: entry 17% cover 27%; }

        .agen-section .agen-why-card:nth-child(3)[b-x69esb1yx8],
        .agen-section .step-card:nth-child(5)[b-x69esb1yx8],
        .agen-section .agen-product-list li:nth-child(3)[b-x69esb1yx8],
        .agen-section .agen-faq-item:nth-child(3)[b-x69esb1yx8] { animation-range: entry 22% cover 32%; }

        .agen-section .agen-why-card:nth-child(4)[b-x69esb1yx8],
        .agen-section .agen-product-list li:nth-child(4)[b-x69esb1yx8],
        .agen-section .agen-faq-item:nth-child(4)[b-x69esb1yx8] { animation-range: entry 27% cover 37%; }

        .agen-section .agen-faq-item:nth-child(5)[b-x69esb1yx8] { animation-range: entry 32% cover 42%; }
        .agen-section .agen-faq-item:nth-child(6)[b-x69esb1yx8] { animation-range: entry 37% cover 47%; }
    }
}
@keyframes agen-soft-in-b-x69esb1yx8 { from { opacity: 0.67; translate: 0 10px; } to { opacity: 1; translate: 0 0; } }

/* ====== CARA KERJA — sticky story ======
   Left column pins while the three steps scroll past it. The pinned side carries no
   per-step state on purpose: knowing which step is "active" would need script, and
   this section has to work with none. */
.story[b-x69esb1yx8] {
    padding: 96px 0;
    background: var(--l-bg-alt);
    border-top: 1px solid var(--l-border);
    border-bottom: 1px solid var(--l-border);
    /* The cover card's rounded top lives HERE, on the first section inside
       .after-hero, not on the wrapper — a radius on the wrapper needed
       overflow:clip to trim this section's square corners, and that clip layer
       around the whole page is what made scrolling laggy. */
    border-radius: 28px 28px 0 0;
}

.story-inner[b-x69esb1yx8] {
    display: grid;
    grid-template-columns: 0.85fr 1fr;
    gap: 64px;
    /* NOT align-items: start. That shrink-wraps the left column to its own content,
       which leaves a sticky child no distance to travel — the classic reason
       "position: sticky does nothing" inside a grid. The column must stretch to the
       full row height so the pinned block has the steps' height to move through. */
    align-items: stretch;
}

.story-aside-pin[b-x69esb1yx8] {
    position: sticky;
    /* Clears the 64px sticky navbar with room to breathe. */
    top: 120px;
}

.story-eyebrow[b-x69esb1yx8] {
    text-align: left;
    margin-bottom: 14px;
}

.story-heading[b-x69esb1yx8] {
    font-size: clamp(28px, 3.6vw, 44px);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.14;
    margin: 0 0 16px;
    color: var(--l-text);
    /* Same reason as .hero-title — see the note there. */
    text-wrap: balance;
}

/* THE TITLE SWAP IS GONE (2026-07-27). The pinned title used to CROSSFADE between
   two faces — "Yang butuh, tinggal panggil" and "Yang ahli, tinggal cuan" — each
   with its own photo, answering whichever of the two tracks was passing beside it,
   driven by a --story-earn view-timeline declared on the earn track. The provider
   track moved to /jadi-mitra, so there is one track, one title, one photo, and no
   timeline: .story-heading-static, .story-heading-swap, .story-face*, the two
   story-fade keyframes and --story-earn were all deleted together. Reintroducing
   any one of them alone will produce a heading that is visually hidden with nothing
   drawn in its place — that rule (position:absolute + clip-path on the static
   heading) is exactly what made this a delete-all-or-nothing block.

   One photo under the title on both faces, capped in height so the pinned block
   stays comfortably shorter than the viewport and the pin keeps its travel. */
.story-photo[b-x69esb1yx8] {
    display: block;
    width: 100%;
    height: auto;
    margin-top: 12px;
    aspect-ratio: 3 / 2;
    max-height: 260px;
    object-fit: cover;
    border-radius: var(--radius);
    border: 1px solid var(--l-border);
    box-shadow: var(--l-shadow);
}

.story-steps[b-x69esb1yx8] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    /* Generous on purpose, and it is doing two jobs. It gives the long-scroll section
       the one-card-at-a-time rhythm it wants, and it lengthens this column — which is
       what the pinned heading opposite has to travel through. Tighten this and the
       heading stops feeling pinned. */
    gap: 60px;
}

.story-step[b-x69esb1yx8] {
    position: relative;
    padding: 32px 32px 32px 34px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius);
    box-shadow: var(--l-shadow);
    overflow: hidden;
}

/* The blue spine down the left edge — the only ornament, and it does a job:
   it ties the three cards into one sequence. */
.story-step[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 4px;
    background: linear-gradient(180deg, var(--l-blue), #4FA3E8);
}

.story-num[b-x69esb1yx8] {
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 0.16em;
    color: var(--l-blue);
    margin-bottom: 10px;
}

.story-step-title[b-x69esb1yx8] {
    font-size: clamp(20px, 2.4vw, 26px);
    font-weight: 800;
    letter-spacing: -0.02em;
    margin: 0 0 10px;
    color: var(--l-text);
}

.story-step-desc[b-x69esb1yx8] {
    font-size: 15px;
    line-height: 1.7;
    color: var(--l-text-2);
    margin: 0;
}

/* ── Two tracks: the consumer flow (blue) and the provider flow (green) ──
   This section absorbed the old "Dua Cara" cards (2026-07-22). The pinned aside carries
   the shared safety promise; each track owns its steps and CTA, and the blue/green split
   — carried over from Dua Cara — is what tells the two audiences apart. */
.story-tracks[b-x69esb1yx8] {
    display: flex;
    flex-direction: column;
    gap: 44px;
}

/* Six steps across two tracks now, not three, so the generous 60px step gap that used
   to lengthen the column for the pin is no longer needed to give the pin travel. */
.story-tracks .story-steps[b-x69esb1yx8] {
    gap: 16px;
}

.story-track-head[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 18px;
    flex-wrap: wrap;
}

.story-track-title[b-x69esb1yx8] {
    margin: 0;
    font-size: 19px;
    font-weight: 800;
    color: var(--l-text);
}

/* The provider track turns the blue ornament green. */
.story-track-earn .story-step[b-x69esb1yx8]::before { background: linear-gradient(180deg, var(--l-green), #4ADE80); }
.story-track-earn .story-num[b-x69esb1yx8] { color: var(--l-green-text); }

/* Inline CTA at the foot of each track — self-contained (does not lean on the removed
   .path-cta), blue by default and green in the provider track. */
.story-track-cta[b-x69esb1yx8] {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 22px;
    padding: 13px 24px;
    border-radius: var(--radius-sm);
    background: var(--l-blue);
    color: #FFFFFF;
    font-size: 15px;
    font-weight: 700;
    text-decoration: none;
    box-shadow: 0 2px 10px rgba(21, 101, 192, 0.3);
    transition: background 0.15s, box-shadow 0.15s;
}

.story-track-earn .story-track-cta[b-x69esb1yx8] {
    background: var(--l-green);
    box-shadow: 0 2px 10px rgba(22, 163, 74, 0.28);
}

@media (hover: hover) {
    .story-track-cta:hover[b-x69esb1yx8] { background: var(--l-blue-deep); box-shadow: 0 6px 18px rgba(21, 101, 192, 0.32); }
    .story-track-earn .story-track-cta:hover[b-x69esb1yx8] { background: var(--l-green-deep); box-shadow: 0 6px 18px rgba(22, 163, 74, 0.32); }
}

.story-track-cta:focus-visible[b-x69esb1yx8] {
    outline: none;
    box-shadow: 0 0 0 3px rgba(21, 101, 192, 0.3);
}

.story-track-earn .story-track-cta:focus-visible[b-x69esb1yx8] {
    box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.3);
}

/* ====== FITUR — showcase carousel ====== */
.showcase[b-x69esb1yx8] {
    padding: 88px 0;
    background: var(--l-bg);
    /* clip, not hidden — hidden makes this a scroll container, and the sticky
       .showcase-pin inside (the fitur lock, end of this file) would pin to THIS
       box instead of the viewport and silently never move. Same trap as .landing. */
    overflow: clip;
}

/* This is the page's longest heading (50 characters). Left unconstrained it ran
   as a single 1010px line at 1440 — a measure far past the ~75-character comfort
   limit; the cap turns it into two balanced lines and costs nothing narrower. */
.showcase .section-heading[b-x69esb1yx8] {
    max-width: 760px;
    margin-inline: auto;
    /* Without this the cap left "beres" alone on line 2 at 820px. Ignored by
       browsers that don't support it, which just means the old ragged break. */
    text-wrap: balance;
}

/* Hidden outside the locked mode — the end-of-file jack block reveals it. */
.jack-progress[b-x69esb1yx8] {
    display: none;
    width: min(280px, 60vw);
    height: 4px;
    margin: 28px auto 0;
    background: var(--l-border);
    border-radius: 999px;
    overflow: hidden;
}

.jack-progress i[b-x69esb1yx8] {
    display: block;
    height: 100%;
    /* Same reasoning as the navbar rail: a progress readout belongs to the face whose
       content it is measuring. */
    background: linear-gradient(90deg, var(--face-accent), var(--face-accent-lite));
    transform: scaleX(0);
    transform-origin: 0 50%;
}

.sc[b-x69esb1yx8] {
    margin-top: 40px;
}

/* Scroll-snap does the whole job on its own: swipe on touch, drag on a trackpad,
   arrow keys once focused. The buttons below are convenience, not the mechanism. */
.sc-viewport[b-x69esb1yx8] {
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    padding: 8px 0 24px;
}

.sc-viewport[b-x69esb1yx8]::-webkit-scrollbar {
    display: none;
}

/* Fade the strip out at both edges so the neighbouring slides read as "there is more this way"
   instead of being guillotined by the viewport edge. Applied only where masks are supported;
   without it the layout is identical, just hard-edged.

   🚨 THE FADE IS A LENGTH TIED TO THE GUTTER, NOT A PERCENTAGE OF THE VIEWPORT (fixed 7/30).
   It was `7%`, and a percentage of the viewport has nothing to do with how much empty gutter
   there is to fade across: at 1440 the gutter is 184px and 7% is 100px, fine; at 820 the
   gutter is 24px and 7% is 57px, so the fade reached 33px INTO the first and last cards and
   dimmed the very cards the reader was on. Reported as "bagian awal dan bagian akhir kartunya
   tertutup" on a narrow tablet. --sc-fade is min(64px, gutter), so it can never reach past a
   card edge at any width, and it still gets a generous 64px wherever there is room. */
@supports (mask-image: linear-gradient(90deg, transparent, black)) {
    @media (min-width: 769px) {
        .sc-viewport[b-x69esb1yx8] {
            mask-image: linear-gradient(90deg,
                transparent 0,
                #000 var(--sc-fade),
                #000 calc(100% - var(--sc-fade)),
                transparent 100%);
        }
    }
}

.sc-viewport:focus-visible[b-x69esb1yx8] {
    outline: none;
    box-shadow: 0 0 0 3px rgba(21, 101, 192, 0.28);
    border-radius: var(--radius);
}

/* 🚨 THE STRIP'S MEASUREMENTS LIVE ON .sc-viewport, NOT ON .sc-track — moved 7/30. The edge
   fade is painted on the VIEWPORT, and its width has to be the gutter; a variable declared on
   the track is invisible to its own parent, which is why that fade was a hard-coded 7% and why
   it ate into the cards on a tablet. Declared here they inherit down to the track as before.

   The pad uses 100vw rather than 100%: a percentage inside the translateX() that reads it
   would resolve against the TRACK's own width, which is max-content and several thousand
   pixels wide. The two differ only by the scrollbar, which the ride's end position
   (calc(-100% + 100vw)) has always accepted. */
.sc-viewport[b-x69esb1yx8] {
    --sc-gap: 24px;
    /* 440px until 7/28 (Susanti: "kartu kartunya terlalu besar, perkecil"). The wide
       card came from the pass that set out to fill the pinned screen, and it filled it
       by making one card enormous rather than by showing more of them — at 1440 the
       ride held two and a half cards, so the section read as two big panels sliding.
       At 360 it holds nearly four, which fills the same screen with the same six
       claims and no card that dominates it. The vh cap comes down with it. */
    --sc-card: min(360px, 78vw);
    /* The gutter the first card starts behind and the last card ends against — and since the
       ride starts at translateX(0) and ends flush with the viewport, THIS number is the margin
       on both screen edges.

       🚨 THE 5vw FLOOR IS THE POINT (Susanti, 7/30: "kalau layar tablet kartu mulai dengan
       margin 5% dari tepi layar kiri, berakhirnya juga begitu tapi tepi kanan, makin besar
       layar buat margin ke tepi makin besar"). The second term already grows with the screen —
       it is what keeps the strip inside --landing-max — but below about 1220px it falls back to
       the flat 24px page gutter, which is only 2.9% of an 820px tablet and reads as cards
       jammed against the glass. 5vw takes over there and the centring term takes over above it,
       so the margin only ever increases: measured 41px @820, 51 @1024, 59 @1180, 104 @1280,
       184 @1440, 264 @1600. */
    --sc-pad: max(5vw, var(--landing-gap), calc((100vw - var(--landing-max)) / 2 + var(--landing-gap)));
    /* How far the edge fade reaches. NEVER more than the gutter, or it reaches past the first
       card's edge and dims the card the reader is trying to read — which is exactly what the
       old 7%-of-viewport fade did on a tablet: 57px of fade against a 24px gutter at 820px.

       The 2px inset is not decoration: the gutter is a vw calculation and the card's edge lands
       on a fractional pixel, so a fade of exactly the gutter overshot it by 1px at 901px wide.
       Measured. */
    --sc-fade: min(64px, calc(var(--sc-pad) - 2px));
}

.sc-track[b-x69esb1yx8] {
    display: flex;
    gap: var(--sc-gap);
    /* Side padding equal to the gutter keeps the first and last slide from
       sitting flush against the viewport edge when snapped. */
    padding-inline: var(--sc-pad);
}

.sc-slide[b-x69esb1yx8] {
    scroll-snap-align: center;
    flex: 0 0 var(--sc-card);
    /* Two halves, no padding of its own: a picture (.sc-art) that bleeds into the
       card's top corners, and the words (.sc-body) below it, which carry the
       padding. overflow:hidden is what lets the tint meet the rounded corner. */
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius);
    box-shadow: var(--l-shadow);
    /* The inactive-slide dimming is set by landing-motion.js via .is-active;
       without script every slide simply stays fully lit, which is correct. */
    transition: box-shadow 0.35s ease, transform 0.35s ease, opacity 0.35s ease;
}

.sc.is-enhanced .sc-slide[b-x69esb1yx8] {
    opacity: 0.55;
    transform: scale(0.97);
}

.sc.is-enhanced .sc-slide.is-active[b-x69esb1yx8] {
    opacity: 1;
    transform: none;
    box-shadow: var(--l-shadow-lift);
}

@media (prefers-reduced-motion: reduce) {
    .sc-viewport[b-x69esb1yx8] { scroll-behavior: auto; }
    .sc.is-enhanced .sc-slide[b-x69esb1yx8] { opacity: 1; transform: none; }
}

/* The picture half. It GROWS (flex:1) so that in the locked ride, where the card
   is as tall as the pinned screen leaves it, the extra height goes into the
   drawing rather than into empty white under the text. min-height is the
   swipeable fallback's size, where the card has no height to fill. */
.sc-art[b-x69esb1yx8] {
    flex: 1 1 auto;
    min-height: 190px;
    display: grid;
    place-items: center;
    padding: 22px;
    background: var(--acc-pale);
    border-bottom: 1px solid var(--l-border);
    overflow: hidden;
}

.sc-body[b-x69esb1yx8] {
    flex: 0 0 auto;
    padding: 22px 26px 26px;
}

.sc-title[b-x69esb1yx8] {
    font-size: clamp(20px, 2.4vw, 26px);
    font-weight: 800;
    letter-spacing: -0.02em;
    margin: 0 0 10px;
    color: var(--l-text);
}

.sc-desc[b-x69esb1yx8] {
    font-size: 15px;
    line-height: 1.7;
    color: var(--l-text-2);
    margin: 0;
    /* Floor of THREE lines — the longest card in the set. Up to three cards are
       on screen at once, and without it the two short cards ("Yang terdekat",
       "Satu akun") ended their tint 25px lower than their neighbours', so the
       row of pictures stepped up and down. Written as line-height × 3 rather
       than a pixel count so it follows the type, not a measurement of it. */
    min-height: calc(1.7em * 3);
}

/* ── The six accents ──
   Same six hues the .icon-* plates below already use, exposed per slide as two
   variables so every part of a drawing (bar, ring, outline, chip) can reach its
   own card's colour without a rule per hue per part. --acc is the dark end and
   is the only one that ever carries a glyph; --acc-pale is the panel wash. */
.sc-acc-blue[b-x69esb1yx8]   { --acc: #1565C0; --acc-pale: #E7F0FB; }
.sc-acc-green[b-x69esb1yx8]  { --acc: #15803D; --acc-pale: #E4F6EB; }
.sc-acc-purple[b-x69esb1yx8] { --acc: #7E22CE; --acc-pale: #F1E9FD; }
.sc-acc-orange[b-x69esb1yx8] { --acc: #C2410C; --acc-pale: #FDEEE1; }
.sc-acc-teal[b-x69esb1yx8]   { --acc: #0F766E; --acc-pale: #E0F5F3; }
.sc-acc-rose[b-x69esb1yx8]   { --acc: #BE185D; --acc-pale: #FCE8F1; }

/* ── Mock drawings ──
   Shared vocabulary first (surface, grey text bars, avatar disc), then the six
   drawings. Everything is sized in px and centred in the panel: the panel can be
   any height (see .sc-art) and a drawing that stretched with it would distort.
   Nothing in here is interactive or reachable — the whole panel is aria-hidden. */
.mk[b-x69esb1yx8] {
    width: min(100%, 360px);
    display: flex;
    color: var(--acc);
}

/* A stand-in for a line of text. Widths differ per line so a block of them reads
   as writing rather than as a barcode. */
.mk-lines[b-x69esb1yx8] {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

.mk-lines i[b-x69esb1yx8] {
    height: 7px;
    border-radius: 999px;
    background: var(--l-border);
}

.mk-lines i:first-child[b-x69esb1yx8] { width: 78%; }
.mk-lines i:nth-child(2)[b-x69esb1yx8] { width: 58%; }
.mk-lines i:nth-child(3)[b-x69esb1yx8] { width: 40%; }

.mk-av[b-x69esb1yx8] {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--l-card);
    border: 1.5px solid var(--acc);
}

/* 1 · Verifikasi KTP — a card with its portrait and lines blanked, check-stamped. */
.mk-id[b-x69esb1yx8] {
    justify-content: center;
    position: relative;
    padding-bottom: 10px;
}

.mk-id-card[b-x69esb1yx8] {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 18px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--l-shadow);
}

.mk-id-photo[b-x69esb1yx8] {
    flex: 0 0 auto;
    width: 46px;
    height: 56px;
    border-radius: var(--radius-xs);
    background: var(--acc-pale);
    position: relative;
    overflow: hidden;
}

/* Head and shoulders — enough to read as a portrait, not enough to be a face. */
.mk-id-photo[b-x69esb1yx8]::before,
.mk-id-photo[b-x69esb1yx8]::after {
    content: '';
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    background: var(--acc);
    opacity: 0.45;
}

.mk-id-photo[b-x69esb1yx8]::before {
    top: 12px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
}

.mk-id-photo[b-x69esb1yx8]::after {
    bottom: 0;
    width: 28px;
    height: 17px;
    border-radius: 14px 14px 0 0;
}

/* Blue, not green: this stands for the check the app actually draws beside a
   verified mitra's name (Home, Search, KatalogJasa, JasaDetail, ProfilTukang all
   render it at #1565C0, which is this card's own --acc). A green stamp would be
   inventing a mark the reader will never see in the product. */
.mk-stamp[b-x69esb1yx8] {
    position: absolute;
    right: 6px;
    bottom: 0;
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--acc);
    color: #FFFFFF;
    border: 3px solid var(--l-card);
}

/* 2 · Uang ditahan — paid, held, released, left to right. */
.mk-pay[b-x69esb1yx8] {
    align-items: center;
    justify-content: center;
    gap: 0;
}

.mk-pay-node[b-x69esb1yx8] {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--l-card);
    border: 1.5px solid var(--l-border-strong);
    color: var(--l-text-2);
    font-size: 14px;
    font-weight: 800;
}

.mk-pay-done[b-x69esb1yx8] {
    border-color: var(--l-green);
    color: var(--l-green-text);
}

/* Dashed, because the money is in transit — a solid rule would read as done. */
.mk-wire[b-x69esb1yx8] {
    flex: 1 1 auto;
    height: 2px;
    background: repeating-linear-gradient(90deg, var(--l-border-strong) 0 5px, transparent 5px 11px);
}

.mk-pay-lock[b-x69esb1yx8] {
    flex: 0 0 auto;
    width: 62px;
    height: 62px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--acc);
    color: #FFFFFF;
    box-shadow: 0 6px 18px rgba(21, 128, 61, 0.28);
}

/* 3 · Chat & panggilan — two bubbles and a call bar. */
.mk-chat[b-x69esb1yx8] {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
}

.mk-bub[b-x69esb1yx8] {
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 74%;
    padding: 12px 14px;
    border-radius: 14px;
}

.mk-bub i[b-x69esb1yx8] {
    height: 7px;
    border-radius: 999px;
}

.mk-bub-in[b-x69esb1yx8] {
    align-self: flex-start;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-bottom-left-radius: 4px;
}

.mk-bub-in i[b-x69esb1yx8] { background: var(--l-border); }
.mk-bub-in i:first-child[b-x69esb1yx8] { width: 88%; }
.mk-bub-in i:last-child[b-x69esb1yx8] { width: 56%; }

.mk-bub-out[b-x69esb1yx8] {
    align-self: flex-end;
    background: var(--acc);
    border-bottom-right-radius: 4px;
}

.mk-bub-out i[b-x69esb1yx8] { background: rgba(255, 255, 255, 0.72); }
.mk-bub-out i:first-child[b-x69esb1yx8] { width: 70%; }
.mk-bub-out i:last-child[b-x69esb1yx8] { width: 44%; }

.mk-callbar[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 2px;
    padding: 10px 14px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: 999px;
    box-shadow: var(--l-shadow);
}

/* A voice, drawn: seven bars, tallest in the middle. The bars are a FIXED 4px
   wide — flexed to fill they came out wider than they were tall, which reads as
   a row of dashes, not as a waveform. */
.mk-wave[b-x69esb1yx8] {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    height: 22px;
}

.mk-wave i[b-x69esb1yx8] {
    flex: 0 0 4px;
    border-radius: 999px;
    background: var(--acc);
    opacity: 0.55;
}

.mk-wave i:nth-child(1)[b-x69esb1yx8], .mk-wave i:nth-child(7)[b-x69esb1yx8] { height: 30%; }
.mk-wave i:nth-child(2)[b-x69esb1yx8], .mk-wave i:nth-child(6)[b-x69esb1yx8] { height: 62%; }
.mk-wave i:nth-child(3)[b-x69esb1yx8], .mk-wave i:nth-child(5)[b-x69esb1yx8] { height: 100%; opacity: 0.8; }
.mk-wave i:nth-child(4)[b-x69esb1yx8] { height: 48%; }

/* 4 · Yang terdekat dulu — a list sorted by distance, nearest lit. */
.mk-near[b-x69esb1yx8] {
    flex-direction: column;
    gap: 8px;
}

.mk-near-row[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 14px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-xs);
}

.mk-near-first[b-x69esb1yx8] {
    border-color: var(--acc);
    box-shadow: var(--l-shadow);
}

/* The open/closed dot the app puts on a worker's avatar (.wc-status in Home).
   It is here because the card's second sentence claims the opening hours are
   visible, and a drawing that showed only distances would be selling half the
   sentence. Grey by default, green on the nearest row — one is open. */
.mk-near-row .mk-av[b-x69esb1yx8] {
    position: relative;
}

.mk-near-row .mk-av[b-x69esb1yx8]::after {
    content: '';
    position: absolute;
    right: -2px;
    bottom: -2px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--l-border-strong);
    border: 2px solid var(--l-card);
}

.mk-near-first .mk-av[b-x69esb1yx8]::after {
    background: var(--l-green);
}

.mk-km[b-x69esb1yx8] {
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 700;
    color: var(--l-text-3);
}

.mk-near-first .mk-km[b-x69esb1yx8] {
    color: var(--acc);
}

/* 5 · Bukan ulasan titipan — one review, check-stamped as coming off a finished
   job. Was a two-row "each side scores the other" arrangement until 7/27; the
   rows and the swap arrow went with the copy they illustrated. */
.mk-rate[b-x69esb1yx8] {
    justify-content: center;
    position: relative;
    padding-bottom: 10px;
}

.mk-rate-card[b-x69esb1yx8] {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 16px 18px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--l-shadow);
}

.mk-rate-head[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Amber, not the hero float-cards' #FDE68A: that value is tuned for stars on the
   dark blue hero and is nearly invisible on a white row. */
.mk-stars[b-x69esb1yx8] {
    flex: 0 0 auto;
    font-size: 15px;
    letter-spacing: 2px;
    color: #F59E0B;
}

/* The review's own two lines sit under the head row, so they start full-width
   rather than at the 78% the shared .mk-lines uses for a name beside an avatar. */
.mk-rate-text i:first-child[b-x69esb1yx8] { width: 100%; }
.mk-rate-text i:nth-child(2)[b-x69esb1yx8] { width: 62%; }

/* 6 · Satu akun, semua perangkat — three screens on one baseline. */
.mk-dev[b-x69esb1yx8] {
    align-items: flex-end;
    justify-content: center;
    gap: 16px;
    padding-bottom: 8px;
}

/* White bodies with a soft border, like every other drawing on the page — the
   first version outlined all three in 2px of the card's accent and came out as
   three loud pink shapes next to five calm white ones. The accent survives as
   the screen wash and one detail per device. */
.mk-dev-phone[b-x69esb1yx8],
.mk-dev-laptop[b-x69esb1yx8],
.mk-dev-win[b-x69esb1yx8] {
    flex: 0 0 auto;
    position: relative;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    box-shadow: var(--l-shadow);
}

.mk-dev-phone[b-x69esb1yx8] {
    width: 46px;
    height: 82px;
    border-radius: 10px;
}

.mk-dev-phone[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    inset: 6px 5px 13px;
    border-radius: 5px;
    background: var(--acc-pale);
}

.mk-dev-phone[b-x69esb1yx8]::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: 5px;
    transform: translateX(-50%);
    width: 16px;
    height: 3px;
    border-radius: 999px;
    background: var(--acc);
    opacity: 0.55;
}

.mk-dev-laptop[b-x69esb1yx8] {
    width: 96px;
    height: 62px;
    margin-bottom: 8px;
    border-radius: 8px 8px 3px 3px;
}

.mk-dev-laptop[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    inset: 6px;
    border-radius: 4px;
    background: var(--acc-pale);
}

/* The base the screen stands on, wider than the lid on both sides. Neutral, not
   accent: it is furniture, and in accent it read as an underline. */
.mk-dev-laptop[b-x69esb1yx8]::after {
    content: '';
    position: absolute;
    left: -12px;
    right: -12px;
    bottom: -10px;
    height: 6px;
    border-radius: 999px;
    background: var(--l-border-strong);
}

.mk-dev-win[b-x69esb1yx8] {
    width: 78px;
    height: 60px;
    border-radius: 8px;
}

.mk-dev-win[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 15px;
    border-radius: 7px 7px 0 0;
    background: var(--acc-pale);
}

/* The three window dots, drawn as one repeating gradient rather than three
   elements — a pseudo-element is all the budget this detail deserves. */
.mk-dev-win[b-x69esb1yx8]::after {
    content: '';
    position: absolute;
    top: 6px;
    left: 7px;
    width: 22px;
    height: 4px;
    background: radial-gradient(circle 2px at 2px 2px, var(--acc) 98%, transparent 100%) repeat-x;
    background-size: 8px 4px;
    opacity: 0.55;
}

/* ── Three more drawings, for the mitra ride (7/30) ──
   The other three slides on that face reuse mk-near, mk-pay and mk-chat unchanged,
   because those really are the same screens seen from the provider's side. These
   three have no counterpart on the customer side, so they are new — same vocabulary
   as everything above: white surfaces, --l-border edges, grey bars for text, the
   card's own --acc for the one thing that carries the claim. */

/* Jam kerja Anda sendiri — a week of opening hours. Five days carry a bar the mitra
   set; two are empty, and the empty pair IS the claim, so they keep their frame
   rather than disappearing. */
.mk-hours[b-x69esb1yx8] {
    align-items: flex-end;
    justify-content: center;
    gap: 8px;
    height: 96px;
}

.mk-hour[b-x69esb1yx8] {
    flex: 0 0 auto;
    position: relative;
    width: 22px;
    height: 100%;
    display: flex;
    align-items: flex-end;
    border-radius: 6px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    overflow: hidden;
}

/* 🚨 The fill is --acc, NOT --acc-pale. First cut used the pale tint with a 2px accent
   cap, and the whole drawing inverted: the panel behind these columns is ALREADY
   --acc-pale (see .sc-art), so an --acc-pale fill was invisible and the two EMPTY days
   — plain white columns — came out as the loudest thing on the card. The open days have
   to be the solid ones; they are the hours the mitra chose. */
.mk-hour i[b-x69esb1yx8] {
    display: block;
    width: 100%;
    background: var(--acc);
    opacity: 0.55;
}

/* Different heights per day, or the week reads as a bar chart of nothing. */
.mk-hour:nth-child(1) i[b-x69esb1yx8] { height: 62%; }
.mk-hour:nth-child(2) i[b-x69esb1yx8] { height: 78%; }
.mk-hour:nth-child(3) i[b-x69esb1yx8] { height: 54%; }
.mk-hour:nth-child(4) i[b-x69esb1yx8] { height: 84%; }
.mk-hour:nth-child(5) i[b-x69esb1yx8] { height: 70%; }

/* The two days off: same frame, nothing in it, dashed. The border is --acc and not
   --l-border: with the neutral edge on this pale panel they vanished completely and the
   drawing read as a five-bar chart, which drops "hari libur" from a card whose sentence
   promises it. Dashed accent at low opacity is present without competing with the five
   solid days. */
.mk-hour-off[b-x69esb1yx8] {
    border: 1.5px dashed var(--acc);
    background: transparent;
    opacity: 0.45;
}

/* Tarif Anda yang tentukan — the price field, with its stepper. NO FIGURE: a made-up
   rate on a card whose whole point is that we do not set one would read as a
   suggested rate. The amount is a grey bar, like every other stand-in for text. */
.mk-price[b-x69esb1yx8] {
    justify-content: center;
}

.mk-price-card[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    max-width: 300px;
    padding: 14px 14px 14px 12px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius);
    box-shadow: var(--l-shadow);
}

.mk-price-tag[b-x69esb1yx8] {
    flex: 0 0 auto;
    padding: 4px 9px;
    border-radius: 8px;
    background: var(--acc-pale);
    color: var(--acc);
    font-size: 12px;
    font-weight: 800;
    letter-spacing: 0.02em;
}

.mk-price-amt[b-x69esb1yx8] {
    flex: 1;
    height: 12px;
    border-radius: 999px;
    background: var(--l-border-strong);
}

/* Up and down, drawn as two stacked chevron-less blocks — at this size an arrow
   glyph is mud, and two small accent squares read as a stepper. */
.mk-price-step[b-x69esb1yx8] {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.mk-price-step i[b-x69esb1yx8] {
    display: block;
    width: 16px;
    height: 9px;
    border-radius: 3px;
    background: var(--acc);
    opacity: 0.85;
}

.mk-price-step i:last-child[b-x69esb1yx8] { opacity: 0.4; }

/* Saldo bisa ditarik kapan saja — balance on top, the reader's own account below,
   one arrow between. VERTICAL on purpose: slide 2 on this same face is the
   horizontal escrow path, and two horizontal money drawings on one ride would read
   as the same claim twice. */
.mk-cash[b-x69esb1yx8] {
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

/* White, not --acc-pale: the panel behind it is already --acc-pale (.sc-art), so a pale
   fill left only the border showing and the balance read as an empty outline. Same trap
   .mk-hour fell into — on these cards --acc-pale is the GROUND, never a fill. */
.mk-cash-card[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    width: 100%;
    max-width: 220px;
    padding: 16px 16px;
    background: var(--l-card);
    border: 1.5px solid var(--acc);
    border-radius: var(--radius);
    box-shadow: var(--l-shadow);
}

.mk-cash-amt[b-x69esb1yx8] {
    width: 62%;
    height: 12px;
    border-radius: 999px;
    background: var(--acc);
    opacity: 0.55;
}

.mk-cash-arrow[b-x69esb1yx8] {
    display: block;
    color: var(--acc);
}

/* The destination is neutral, not accent — it is the reader's own bank, not ours. */
.mk-cash-bank[b-x69esb1yx8] {
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 100%;
    max-width: 180px;
    padding: 12px 14px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius);
    box-shadow: var(--l-shadow);
}

.mk-cash-bank i[b-x69esb1yx8] {
    display: block;
    height: 7px;
    border-radius: 999px;
    background: var(--l-border);
}

.mk-cash-bank i:first-child[b-x69esb1yx8] { width: 70%; }
.mk-cash-bank i:last-child[b-x69esb1yx8] { width: 44%; }

.sc-controls[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 18px;
    margin-top: 8px;
}

.sc-arrow[b-x69esb1yx8] {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--l-card);
    border: 1px solid var(--l-border-strong);
    border-radius: 50%;
    color: var(--l-text);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.sc-arrow:focus-visible[b-x69esb1yx8] {
    outline: none;
    background: var(--l-blue-pale);
    border-color: var(--l-blue);
    color: var(--l-blue);
}

@media (hover: hover) {
    .sc-arrow:hover[b-x69esb1yx8] {
        background: var(--l-blue-pale);
        border-color: var(--l-blue);
        color: var(--l-blue);
    }
}

.sc-arrow[disabled][b-x69esb1yx8] {
    opacity: 0.4;
    cursor: default;
}

.sc-dots[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* 44px hit area around a 8px dot — the target must be tappable even though the
   mark is small. */
.sc-dot[b-x69esb1yx8] {
    width: 24px;
    height: 32px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.sc-dot[b-x69esb1yx8]::after {
    content: '';
    width: 8px;
    height: 8px;
    border-radius: 999px;
    background: var(--l-border-strong);
    transition: background 0.2s, width 0.2s;
}

.sc-dot[aria-selected="true"][b-x69esb1yx8]::after {
    width: 26px;
    background: var(--l-blue);
}

.sc-dot:focus-visible[b-x69esb1yx8] {
    outline: none;
}

.sc-dot:focus-visible[b-x69esb1yx8]::after {
    box-shadow: 0 0 0 3px rgba(21, 101, 192, 0.28);
}

@media (hover: hover) {
    .sc-dot:hover[b-x69esb1yx8]::after {
        background: var(--l-blue);
    }
}

.feature-icon[b-x69esb1yx8] {
    width: 44px;
    height: 44px;
    border-radius: var(--radius-xs);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 16px;
}

/* Icon glyphs are the DARK end of each hue, not the light end the dark theme
   used: a #60A5FA stroke on a white card is barely a suggestion of an icon.
   The tinted plates stay pale so the cards keep their calm. */
.icon-blue[b-x69esb1yx8] { background: #E7F0FB; color: #1565C0; }
.icon-green[b-x69esb1yx8] { background: #E4F6EB; color: #15803D; }
.icon-purple[b-x69esb1yx8] { background: #F1E9FD; color: #7E22CE; }
.icon-orange[b-x69esb1yx8] { background: #FDEEE1; color: #C2410C; }
.icon-teal[b-x69esb1yx8] { background: #E0F5F3; color: #0F766E; }
.icon-rose[b-x69esb1yx8] { background: #FCE8F1; color: #BE185D; }

.feature-title[b-x69esb1yx8] {
    font-size: 16px;
    font-weight: 700;
    margin: 0 0 8px;
    color: var(--l-text);
}

.feature-desc[b-x69esb1yx8] {
    font-size: 14px;
    line-height: 1.6;
    color: var(--l-text-2);
    margin: 0;
}

/* ====== DOWNLOAD ====== */
/* Deliberately short — 0.47 screens, the least of anything on this page that has its own nav
   link. It was given a full screen (min-height + centred content) on 7/30 to stop its rail
   blinking and REVERTED the same day ("jelek revert"): a three-tile band centred in a screen's
   worth of white read as a strip with air around it, and the blink turned out not to be about
   this section's height at all — see the thresholds on .nav-link-cta. Do not reach for height
   here again to fix something in the navbar. */
.download[b-x69esb1yx8] {
    padding: 64px 0;
    background: var(--l-bg);
}

.download-inner[b-x69esb1yx8] {
    text-align: center;
}

.download-grid[b-x69esb1yx8] {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    max-width: 720px;
    margin: 0 auto 24px;
}

.download-card[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 20px 24px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--l-shadow);
    text-decoration: none;
    color: var(--l-text);
    transition: box-shadow 0.2s, border-color 0.2s;
    cursor: pointer;
}

/* Navigation cards leave the page on tap, so they don't need the touch guard. */
.download-card:hover[b-x69esb1yx8],
.download-card:focus-visible[b-x69esb1yx8] {
    outline: none;
    box-shadow: var(--l-shadow-lift);
    border-color: var(--l-blue);
}

/* iOS card: no build yet, so it is a static "Segera hadir" tile, not a link.
   Default cursor and no lift on hover so it does not invite a tap that goes nowhere. */
.download-card-soon[b-x69esb1yx8] {
    cursor: default;
}

.download-card-soon:hover[b-x69esb1yx8],
.download-card-soon:focus-visible[b-x69esb1yx8] {
    box-shadow: var(--l-shadow);
    border-color: var(--l-border);
}

/* Read as pending by MUTING THE INK, not by dropping opacity: the card-rise reveal
   animates opacity to 1 with fill:both (see @keyframes card-rise) and would override a
   static opacity once the card is on screen. Colour is not animated, so it holds — the
   blue platform icon going grey is the cue that this tile is not yet active. */
.download-card-soon[b-x69esb1yx8]  .download-platform-icon,
.download-card-soon .download-platform[b-x69esb1yx8] {
    color: var(--l-text-3);
}

.download-body[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 12px;
}

[b-x69esb1yx8] .download-platform-icon {
    color: var(--face-accent-text);
    flex-shrink: 0;
}

.download-platform[b-x69esb1yx8] {
    font-size: 16px;
    font-weight: 700;
    color: var(--l-text);
}

/* nowrap because the pill now sits on all three cards, and the narrowest of them
   (Windows, the longest platform name) broke "Segera hadir" across two lines at
   tablet width. It is a two-word badge; it should never wrap. */
.download-soon[b-x69esb1yx8] {
    font-size: 12px;
    font-weight: 600;
    color: var(--l-text-2);
    background: var(--l-bg-alt);
    padding: 4px 10px;
    border-radius: 999px;
    white-space: nowrap;
}

.download-note[b-x69esb1yx8] {
    font-size: 14px;
    color: var(--l-text-3);
    margin: 0;
}

/* Same reason as .section-sub above: a face wrapper inside a paragraph has to stay
   inline, or the shown sentence becomes a block and breaks the line in two. */
.download-note .face[b-x69esb1yx8] {
    --face-display: inline;
}

/* Every platform card is pending, so this link is the only way into the product from
   this section. It is painted as a link rather than left as muted body text, and it
   takes the face's own colour so it does not read as a stray blue word on the green
   mitra page. */
.download-note a[b-x69esb1yx8] {
    color: var(--l-blue);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-thickness: 1px;
}

.download-note .face-mitra a[b-x69esb1yx8] {
    color: var(--l-green-text);
}

.download-note a:hover[b-x69esb1yx8],
.download-note a:focus-visible[b-x69esb1yx8] {
    text-decoration-thickness: 2px;
}

/* ====== STEP GRID ======
   The .how-it-works section wrapper was deleted along with the old three-card
   layout — the customer story is now the sticky .story section above. These step
   styles stay because the AGEN section still uses the same three-step grid. */
.steps-grid[b-x69esb1yx8] {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    gap: 16px;
    max-width: 800px;
    margin: 24px auto 0;
}

/* ── The steps stack on their CONTAINER, not on the viewport (2026-07-28) ──
   Three cards plus two connectors need ~700px to keep a step's sentence on two or
   three lines. The agen quest's answers column does not have it: measured 658px at
   1440 (182px per card) and 419px on a 1024 laptop (119px per card, which wraps the
   copy to one or two words a line). A viewport media query cannot fix this, and the
   numbers say why — that column is 820px wide at a 820px TABLET but only 553px at a
   1024px LAPTOP, because the quest's own two-column layout is still on at 1024 and
   already stacked at 820. Wider screen, narrower column.
   So the question has to be asked of the container itself. Everything inside matches
   the phone treatment further down (column, connector rotated a quarter turn), and
   the card is capped so a stacked step does not stretch to 700px of one-line text. */
.agen-section[b-x69esb1yx8] {
    container-type: inline-size;
    container-name: agen-answer;
}

@container agen-answer (max-width: 720px) {
    .steps-grid[b-x69esb1yx8] {
        flex-direction: column;
        align-items: center;
        gap: 0;
    }

    .step-connector[b-x69esb1yx8] {
        transform: rotate(90deg);
        padding: 0;
    }

    .step-card[b-x69esb1yx8] {
        max-width: 340px;
    }
}

.step-card[b-x69esb1yx8] {
    flex: 1;
    text-align: center;
    padding: 24px 16px;
}

/* The drawn panel that opens each step. Ratio-locked so the three stay identical in
   height whatever the column width — three panels of different heights would put the
   three headings on three different baselines and break the row. */
.step-art[b-x69esb1yx8] {
    display: block;
    width: 100%;
    max-width: 190px;
    height: auto;
    aspect-ratio: 160 / 110;
    margin: 0 auto 16px;
}

/* SVG <text> does not inherit the page font unless it is told to. Set here rather than
   as a presentation attribute so the panel matches the rest of the page in one place. */
.step-art-label[b-x69esb1yx8] {
    font-family: inherit;
    font-size: 13px;
    font-weight: 800;
}

/* Replaced the 44px filled circle. With a drawn panel above it, a big blue numeral was a
   second competing focal point in a 190px-wide card — and the panel already carries the
   step's identity. This is the same quiet index the story section uses for its steps, so
   the two "how it works" sequences on this page now speak the same way. */
.step-index[b-x69esb1yx8] {
    font-size: 12px;
    font-weight: 800;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--l-blue);
    margin-bottom: 8px;
}

.step-num[b-x69esb1yx8] {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--l-blue);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    font-weight: 800;
    margin-bottom: 16px;
}

.step-title[b-x69esb1yx8] {
    font-size: 16px;
    font-weight: 700;
    margin: 0 0 8px;
    color: var(--l-text);
}

.step-desc[b-x69esb1yx8] {
    font-size: 14px;
    line-height: 1.6;
    color: var(--l-text-2);
    margin: 0;
}

/* Lines up with the middle of the drawn panels rather than the middle of the whole card:
   the chevron's job is to join the three pictures into a sequence, and a card is mostly
   text below them, so centring on the card put the arrow down beside the paragraphs.
   24px card padding + half of a 190×130 panel = 89px, minus half the 24px chevron so it
   is the ARROW that is centred and not its top edge. Measured: 12px out before this. */
.step-connector[b-x69esb1yx8] {
    display: flex;
    align-items: flex-start;
    padding-top: 77px;
    color: var(--l-border-strong);
    flex-shrink: 0;
}

/* The JEMBATAN band (a kabupaten under a navy scrim, pivoting the mitra page into
   the agen offer) and AJAKAN MITRA (the light green band that opened /jadi-mitra
   from /welcome) both lived here until 7/28. Both are gone with their markup — see
   FACE SWITCHING at the top of this file. Each existed for one job: carrying a
   reader from one audience's page to another's. The hero switcher does that from
   the first screen, on every face, in a single press, so a full-bleed band spending
   a screen of scrolling on the same errand is a slower copy of a button.

   wwwroot/images/marketing/kota-aerial.jpg lost its only reference with the bridge
   band. It is still on disk and still credited in CREDITS.md; delete it there too if
   nothing claims it by launch. */

/* ====== AGEN (regional partner recruitment) ======
   MERGED 2026-07-21. Two things were pulled together here on Susanti's instruction,
   and the seam is worth knowing about because each half came from a different place.

   THE LOOK AND THE MOTION are the original ones, restored from commit 7580fbf: the
   numbered chapter marks, the cover pictures with their scrims, the ground colours,
   and — the part she singled out — the original staged-entrance timing. A later pass
   had stretched that timing to `entry 0% cover 45%` in the belief that longer meant
   smoother; it read as mushy, and the short original range is back.

   THE COMPOSITION is the rebuilt one from 450e967, which she preferred: the province
   picker opens the section instead of being buried in a card three blocks down, the
   map and kabupaten list and result bar form one working surface, and eight blocks of
   prose are folded into five tabs rather than five chapters of scrolling.

   So: two chapters, not five, wearing the original clothes.
     01  Wilayah              cover, picker, map + list, result bar
     02  Sebelum memutuskan   cover, five tabs
     closing band
*/
.agen[b-x69esb1yx8] {
    /* No bottom padding any more: the chapters below own their own vertical space, and
       the closing band ends the section. This stays as the ground the intro sits on. */
    padding: 0;
    background: var(--l-bg-blue);
    border-top: 1px solid var(--l-border);
    border-bottom: 1px solid var(--l-border);
    /* The agen face's cover-card rounded top — same corner the consumer face
       gets from .story, and the same reasoning: the radius lives on the first
       section INSIDE .after-hero, never on the wrapper (a wrapper radius needs
       overflow:clip around the whole page, which kills scroll performance —
       see the note on .story). Susanti, 7/23: the ch02 panel has rounded
       corners while the hero's cover edge was a straight line. */
    border-radius: 28px 28px 0 0;
}

/* The first chapter's white ground would overpaint those corners square again
   — it wears the same radius so the visible edge stays rounded. Chapter 01 has
   no full-bleed child background in its top corners, so no clipping is needed. */
.agen-ch-wilayah[b-x69esb1yx8] {
    border-radius: 28px 28px 0 0;
}

/* ── Chapters ──
   Each is a full-bleed band with its own ground. The point is not decoration: a reader
   scrolling a single flat colour cannot feel that one argument ended and another began,
   so every heading looks like more of the same thing and they stop. A change of ground
   is felt before it is read. */
.agen-chapter[b-x69esb1yx8] {
    position: relative;
    /* No top padding: the picture strip is the chapter's opening now and supplies it. */
    padding: 0 0 72px;
}

/* A "content inside the picture" treatment was prototyped on 2026-07-21 and REJECTED:
   the photograph became the ground for a whole chapter and the step cards sat on it as
   solid white tiles. It was measured and it worked (title 8.43:1, cards equal at 361px,
   the chapter 245px SHORTER than with a cover) — it was simply not liked. Don't rebuild
   it on the assumption it was never tried. */

/* DARK, not white. The title sits on the chapter's own ground now, not on the
   photograph, so it takes the page's ordinary heading colour and needs no shadow and
   no scrim behind it. That is the whole gain of the split: the contrast question
   disappears instead of being measured against every new picture. */
.agen-cover-title[b-x69esb1yx8] {
    margin: 0;
    max-width: 16ch;
    font-size: clamp(26px, 2.9vw, 42px);
    font-weight: 800;
    line-height: 1.12;
    letter-spacing: -0.03em;
    color: var(--l-text);
    text-wrap: balance;
}


/* The two grounds alternate. Kept to a white and ONE tint rather than a different colour
   per chapter: five colours would read as five unrelated pages, and the tint is already
   doing the only job needed — marking that a boundary was crossed. */
.agen-chapter-white[b-x69esb1yx8] {
    background: var(--l-bg);
}

.agen-chapter-tint[b-x69esb1yx8] {
    background: var(--l-bg-blue);
}

/* (The separate closing band is gone, 7/23: on this page "Siap mulai?" IS the
   securing moment, so its urgency line + claim button live in the face-aware
   CTA banner now. Two stacked closers said the same thing twice.) */

/* There used to be a faint blue wash here, softening the seam where two chapter grounds
   met. The picture strip does that job now and does it far better — a band of photograph
   between two flat colours is a boundary nobody can miss — so the wash was removed rather
   than left to darken the top of every photograph by 5%. */

.agen-intro[b-x69esb1yx8] {
    padding-bottom: 8px;
}

/* Every block in this section carries its own bottom margin from when they were all
   stacked in one container. Inside a chapter the last one now sits on top of the
   chapter's own bottom padding, and the two add up — measured at the 01/02 boundary:
   56px of block margin under 72px of chapter padding, so 128px of empty ground before
   the colour changed. The gap read as something failing to load. The chapter owns the
   space between chapters; the block inside it does not get a say. */
.agen-chapter .landing-container > *:last-child[b-x69esb1yx8] {
    margin-bottom: 0;
}

/* ── Chapter mark ──
   "01 · Wilayah". The arrow on the floor. A reader who can see they are on step 2 of 5
   knows the path is finite and keeps going; the same content with no marks reads as an
   unbounded wall, which is what this section was. */
.agen-mark[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 14px;
    margin: 0;
}

/* BLUE pill with a white numeral. It was the other way round — a white pill with a blue
   numeral — while the mark sat on a photograph and had to be the brightest thing there.
   On the chapter's own pale ground white-on-white is a shape you have to hunt for, so it
   goes back to the filled blue the rest of the app uses for a selected pill. */
.agen-mark-num[b-x69esb1yx8] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    height: 36px;
    padding: 0 9px;
    border-radius: 999px;
    background: var(--l-blue);
    color: #FFFFFF;
    font-size: 15px;
    font-weight: 800;
    letter-spacing: 0.02em;
    box-shadow: 0 2px 10px rgba(21, 101, 192, 0.3);
}

/* Ordinary secondary text, and no shadow. Both were white with a shadow to survive a
   photograph underneath; there is no photograph underneath any more. */
.agen-mark-label[b-x69esb1yx8] {
    font-size: 14px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--l-text-2);
}
/* ── Chapter 01 split: the AGENT'S OWN SCREEN pins, the words walk past it ──
   The split keeps its halves, but the pinned half stopped being a photograph on
   2026-07-21. Two reasons, in order of weight: (1) at this point in the page the
   reader has just been offered a job, and the thing that converts them is seeing
   what they would be handed, not another skyline; (2) the bridge band that used to
   sit immediately above carried a big city photo, so a second one below it read as
   one long picture instead of a hand-off. (That band went on 7/28; reason (1) is
   the one still holding this decision up.)

   Sticky needs no @supports and no script — where it fails the panel simply
   scrolls with the page, which is a perfectly good static illustration. The
   filling-in on top of it is scroll-driven and additive; see --agen-words. */
.agen-split[b-x69esb1yx8] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    /* Stretch, not start — the photo column must be as tall as the words column
       or the sticky child has no distance to travel (same trap as .story-inner). */
    align-items: stretch;
    margin-bottom: 52px;
}

.agen-split-photo[b-x69esb1yx8] {
    position: relative;
}

.agen-portal-pin[b-x69esb1yx8] {
    position: sticky;
    top: 64px;                        /* locks just under the sticky navbar */
    /* Shorter than a full screen ON PURPOSE (Susanti 7/25: "sedikit jeda sebelum kiri ikut
       naik", tuned up 7/25). The pin releases when its bottom edge meets the column's
       bottom, so a shorter pin holds the card ~245px longer before it lets go and rides up
       — a dwell with NO trailing whitespace (unlike padding the column, which just left an
       empty band). Bigger offset = longer dwell; the max() floor keeps the pin taller than
       the 445px card on short screens so it never overflows under the navbar. */
    height: max(560px, calc(100vh - 310px));
    height: max(560px, calc(100svh - 310px));
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    padding: 0 clamp(16px, 3vw, 40px);
}

.agen-portal[b-x69esb1yx8] {
    width: 100%;
    max-width: 430px;
    border-radius: 20px;
    border: 1px solid var(--l-border);
    background: var(--l-card);
    box-shadow: var(--l-shadow-lift);
    overflow: hidden;
}

/* A window chrome, not a phone frame: this is the desktop-and-phone portal, and a
   phone bezel here would fight the actual phone mock-ups earlier on the page. */
.agen-portal-top[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 13px 16px;
    border-bottom: 1px solid var(--l-border);
    background: #FBFCFE;
}

.agen-portal-tdot[b-x69esb1yx8] {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--l-border);
}

.agen-portal-app[b-x69esb1yx8] {
    margin-left: 6px;
    font-size: 12.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--l-text-3);
}

.agen-portal-body[b-x69esb1yx8] { padding: 22px; }

.agen-portal-head[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 20px;
}

.agen-portal-wil[b-x69esb1yx8] {
    font-size: 20px;
    font-weight: 800;
    color: var(--l-text);
}

.agen-portal-badge[b-x69esb1yx8] {
    font-size: 11.5px;
    font-weight: 700;
    padding: 4px 10px;
    border-radius: 999px;
    background: var(--l-green-pale);
    color: var(--l-green-text);
}

.agen-portal-grid[b-x69esb1yx8] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    margin-bottom: 22px;
}

.agen-portal-box[b-x69esb1yx8] {
    background: var(--l-bg-alt);
    border-radius: 13px;
    padding: 14px;
}

/* THESE NUMBERS ARE PLAIN TEXT NOW (2026-07-28), and the count-up that used to
   drive them is gone. It was printed by a CSS counter — `counter-reset: agen-n
   var(--agen-n)` plus `content: counter(agen-n)` — because an animated custom
   property is the only way to move an integer in pure CSS. That technique cannot
   put a thousands separator anywhere: counter() prints a raw integer, so the
   moment the mitra figure passed a thousand it rendered "2180" instead of the
   "2.180" Indonesian writes. Every way around it is worse than losing the effect
   — a static "2." prefix in front of an animating remainder shows nonsense
   ("2.85") for the whole sweep, and mod()/round() arithmetic still cannot pad the
   remainder to three digits.
   The panel is not left motionless: the bars still grow and the commission card
   still lands, both on the same --agen-fill timeline. */
.agen-portal-num[b-x69esb1yx8] {
    display: block;
    font-size: 24px;
    font-weight: 800;
    line-height: 1;
    color: var(--l-text);
    font-variant-numeric: tabular-nums;
}

.agen-portal-cap[b-x69esb1yx8] {
    display: block;
    margin-top: 6px;
    font-size: 12.5px;
    color: var(--l-text-3);
}

.agen-portal-earn[b-x69esb1yx8] {
    background: linear-gradient(135deg, var(--l-blue-deep), var(--l-blue));
    border-radius: 14px;
    padding: 17px 18px;
    margin-bottom: 20px;
    color: var(--l-on-blue);
}

.agen-portal-earn-cap[b-x69esb1yx8] {
    display: block;
    font-size: 12px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    opacity: 0.85;
}

.agen-portal-earn-num[b-x69esb1yx8] {
    display: block;
    margin-top: 5px;
    font-size: 27px;
    font-weight: 800;
    letter-spacing: -0.01em;
}

.agen-portal-bars[b-x69esb1yx8] {
    display: flex;
    align-items: flex-end;
    gap: 9px;
    height: 78px;
}

/* height:100% is load-bearing — a flex child under align-items:flex-end takes its
   content height, which for an empty <i> is zero, and the whole chart vanishes. */
.agen-portal-bar[b-x69esb1yx8] {
    flex: 1;
    height: 100%;
    border-radius: 6px 6px 3px 3px;
    background: var(--l-blue-pale);
    transform-origin: bottom;
    transform: scaleY(var(--bar, 0.5));
}

.agen-portal-bar-now[b-x69esb1yx8] { background: var(--l-blue); }

.agen-portal-months[b-x69esb1yx8] {
    display: flex;
    justify-content: space-between;
    margin-top: 9px;
    font-size: 11px;
    color: var(--l-text-3);
}

/* Not a disclaimer in fine print at the bottom of the page — it sits on the thing
   it qualifies. The numbers above are invented, and an invented income on a
   recruitment page reads as a typical one unless it says otherwise. */
.agen-portal-note[b-x69esb1yx8] {
    margin: 0;
    font-size: 12.5px;
    color: var(--l-text-3);
    text-align: center;
}

/* The panel fills itself in while the words column walks past. The timeline is
   DECLARED on the words column and hoisted by timeline-scope on .landing, because
   the animated elements are in the sibling column — the same arrangement the hero
   uses with --cover-over. Everything animated here is a transform or an opacity:
   no layout, no filter, nothing that would cost a frame.

   A `@property --agen-n` registration lived here until 7/28 — it typed the counter
   variable as <integer> so it could be interpolated. It went with the count-up
   itself (see the note on .agen-portal-num); an unused registration is a live
   feature to the next reader. */

/* The timeline's subject is the PICKER CARD, not the words column, and that choice
   is the whole reason this works. Range percentages are fractions of the subject's
   own travel, so a subject taller than the viewport makes them depend on how much
   text the chapter happens to contain: measured on this page, a range written
   against the column was already finished before the panel had even been seen —
   twice, once in entry and once in cover. The picker is short and its travel is
   therefore one screen height on every screen, which makes "while the reader is
   looking at the top of this chapter" expressible as a number. */
.agen-split-words .agen-pick[b-x69esb1yx8] {
    view-timeline: --agen-fill block;
}

@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        /* Read these as: the bars start growing as the picker rises off the bottom of
           the screen, and the commission card lands as it reaches the middle. They
           overlap on purpose — things arriving in strict single file read as a
           loading screen. (Two counting numbers used to open this sequence; see the
           note on .agen-portal-num for why they are static text now.) */
        .agen-portal-bar[b-x69esb1yx8] {
            animation: agen-bar-grow-b-x69esb1yx8 linear both;
            animation-timeline: --agen-fill;
            animation-range: cover 22% cover 66%;
        }

        .agen-portal-earn[b-x69esb1yx8] {
            animation: agen-earn-in-b-x69esb1yx8 linear both;
            animation-timeline: --agen-fill;
            animation-range: cover 34% cover 72%;
        }
    }
}

@keyframes agen-bar-grow-b-x69esb1yx8 { from { transform: scaleY(0.06); } to { transform: scaleY(var(--bar, 0.5)); } }
@keyframes agen-earn-in-b-x69esb1yx8 { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

.agen-split-words[b-x69esb1yx8] {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 24px;
    padding: 72px clamp(24px, 4vw, 64px);
    max-width: 640px;
}

/* Lede, picker and facts carry centred margin-auto styling from the full-width
   layout they used to sit in; inside the words column they hug the left edge. */
.agen-split-words .agen-lede[b-x69esb1yx8] { margin: 0; text-align: left; }
.agen-split-words .agen-pick[b-x69esb1yx8] { margin: 0; width: 100%; max-width: 520px; }
.agen-split-words .agen-facts[b-x69esb1yx8] { margin: 0; justify-content: flex-start; text-align: left; }

/* The bench, re-dressed for the column. It was a full-width BAND (own tinted
   ground, band padding); in a 640px column that chrome reads as a broken stripe,
   so only the working card itself remains. The grid stacks map-over-list — the
   same order the phone layout uses — because a 1.5fr/1fr split of 640px leaves a
   map nobody can use. */
.agen-split-words .agen-bench[b-x69esb1yx8] {
    width: 100%;
    padding: 0;
    background: transparent;
    border-bottom: none;
}

.agen-split-words .agen-bench-grid[b-x69esb1yx8] {
    grid-template-columns: 1fr;
}

.agen-split-words .agen-map[b-x69esb1yx8] {
    min-height: 340px;
    border-right: none;
    border-bottom: 1px solid var(--l-border);
}

.agen-split-words .agen-list-pane[b-x69esb1yx8] {
    min-height: 0;
}

.agen-split-words .agen-result[b-x69esb1yx8] {
    flex-wrap: wrap;
    gap: 14px;
}

@media (max-width: 768px) {
    /* One column on phones: the pinned column would have no height of its own to
       travel through, so the panel stops being sticky and simply sits at the top of
       the chapter, finished — every figure already carries its final value, so a
       still panel is a complete picture, not a half-drawn one. The full-bleed
       pinned cover on chapter 02 is the phone-friendly pattern. */
    .agen-split[b-x69esb1yx8] {
        grid-template-columns: 1fr;
        margin-bottom: 40px;
    }

    .agen-portal-pin[b-x69esb1yx8] {
        position: static;
        height: auto;
        padding: 32px 24px 0;
    }

    .agen-portal-bar[b-x69esb1yx8],
    .agen-portal-earn[b-x69esb1yx8] {
        animation: none;
    }

    .agen-portal-bar[b-x69esb1yx8] { transform: scaleY(var(--bar, 0.5)); }

    .agen-split-words[b-x69esb1yx8] {
        max-width: none;
        padding: 40px 24px 8px;
    }
}

/* ── Chapter 02 full-bleed cover: the photograph locks, the panel rides over ──
   The mirror of chapter 01's split — here the photo takes the WHOLE screen with
   the title standing on it, pins (sticky within the chapter, so it can never leak
   past it), and the content panel climbs up over it exactly like .after-hero
   climbs the hero. Sticky-only, no @supports: an unsupporting browser just
   scrolls the cover away normally.

   Text stands on the photograph again on this one cover, so the scrim comes
   back for it: dark at the ends, thin in the middle. NO saturate/contrast filter
   here, unlike the other covers — the filter would wash the title (a filter
   applies to the element's whole subtree), and the scrim already tames the photo. */
/* ── "Sebelum memutuskan": questions transition + persistent navy rail (7/24) ──
   Susanti's spec: the cover's questions keep their irregular opening EXACTLY (the
   nth-child sizes / stagger / quotes below), then only MOVE LEFT — no shrink, no
   vertical drift — to settle as a numbered index; the numbers appear only once they
   have stopped; the navy sheet wipes right->left to the rail; the first section rises
   on the white; and the navy index-rail then STAYS (sticky) beside the scrolling
   sections ("rail navy menetap"). Pure CSS. Two height:0 sticky overlays (.q-stick)
   carry the full-bleed navy and the cover head so they overlay the grid without
   reserving flow space; the intro is driven by a view-timeline on .agen-quest-spacer,
   hoisted here via timeline-scope. */
.agen-quest[b-x69esb1yx8] {
    position: relative;
    timeline-scope: --agenq;
    /* clip (NOT hidden) trims the 100svh navy/rail overlays where they would otherwise
       hang BELOW the section's end and cover the CTA band. clip establishes no scrollport,
       so the sticky overlays and the --agenq view-timeline still resolve to the document
       (the .paths/.after-hero rule); no border-radius here, so no compositor penalty. */
    overflow: clip;
    --nav: 64px;                     /* the sticky navbar height */
    --agen-rail: min(600px, 46vw);   /* where the navy stops = the index column */
}

/* A sticky wrapper with NO height reserves no flow space; its absolutely-positioned
   child hangs down as a viewport-tall band and sticks with it — that is how the navy
   and the head overlay the grid without pushing it down. */
.q-stick[b-x69esb1yx8] { position: sticky; top: var(--nav); height: 0; }
.q-stick-navy[b-x69esb1yx8] { z-index: 1; }
.q-stick-head[b-x69esb1yx8] { z-index: 3; }

.agen-quest-navy[b-x69esb1yx8] {
    /* width:100%, NOT 100vw — 100vw includes the scrollbar and overflows the page. */
    position: absolute; top: 0; left: 0; width: 100%;
    height: calc(100svh - var(--nav));
    /* ── NAVY, AND IT STAYS NAVY (Susanti, 7/30, after trying all three) ──
       This scene was tried warm near-black (gold accents) and then pale blue #EAF2FC with
       the whole ink flipped dark, and it came back to the navy both times: "lebih bagus
       warna navy". The record of what the pale version cost is worth keeping, because it is
       the reason not to try it a third time — every piece of text here is white, including
       a five-step hierarchy at 0.55 → 0.92 ("the overheard voices"), and on a pale ground
       all of it inverts AND the faint end has to lift to 0.68 to stay legible, which
       flattens the very hierarchy the scene exists for. Dark-on-pale is not a recolour of
       this scene; it is a different scene. */
    background:
        radial-gradient(90% 70% at 80% 10%, rgba(27, 115, 212, 0.22), transparent 60%),
        radial-gradient(70% 60% at 10% 90%, rgba(21, 101, 192, 0.16), transparent 55%),
        #0B1524;
    clip-path: inset(0 calc(100% - var(--agen-rail)) 0 0);   /* settled / fallback: rail only */
}

.agen-quest-head[b-x69esb1yx8] {
    position: absolute; top: 8vh;
    left: clamp(24px, 6vw, 80px); width: min(880px, 86vw);
    opacity: 0;                       /* settled: gone */
}
.agen-spec-eyebrow[b-x69esb1yx8] {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #7CC6FF;
    margin-bottom: 16px;
}
.agen-cover-full-title[b-x69esb1yx8] {
    margin: 0;
    font-size: clamp(30px, 5vw, 56px);
    font-weight: 800;
    line-height: 1.1;
    letter-spacing: -0.03em;
    color: #FFFFFF;
    text-wrap: balance;
}

/* The cover eyebrow now uses section 01's numbered pill badge (.agen-mark) so the two
   chapters read as one family (Susanti 7/25). Its label colour defaults to dark for the
   white chapter, so on the navy cover it flips light; the badge keeps a gap above the title. */
.agen-quest-head .agen-mark[b-x69esb1yx8] { margin-bottom: 18px; }
.agen-quest-head .agen-mark-label[b-x69esb1yx8] { color: rgba(255, 255, 255, 0.75); }

/* Scroll cue: sits at the bottom-centre of the navy cover so the lower half no longer
   reads as an empty void, and signals that answers arrive on scroll (Susanti 7/25).
   Lives in the head overlay layer (z-index 3, above the navy); the chevron bobs on a
   time loop, the whole cue fades out on the first bit of scroll (desktop block below). */
.agen-quest-cue[b-x69esb1yx8] {
    position: absolute; top: calc(100svh - var(--nav) - 78px);
    left: 0; right: 0;
    display: flex; flex-direction: column; align-items: center; gap: 12px;
    pointer-events: none;
}
.agen-quest-cue-text[b-x69esb1yx8] {
    font-size: 12px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
    color: rgba(255, 255, 255, 0.5);
}
.agen-quest-cue-chevron[b-x69esb1yx8] {
    width: 15px; height: 15px;
    border-right: 2px solid rgba(255, 255, 255, 0.5);
    border-bottom: 2px solid rgba(255, 255, 255, 0.5);
    transform: rotate(45deg);
    animation: agenq-cue-bob-b-x69esb1yx8 1.8s ease-in-out infinite;
}
@keyframes agenq-cue-bob-b-x69esb1yx8 {
    0%, 100% { transform: rotate(45deg) translate(0, 0); }
    50%      { transform: rotate(45deg) translate(3px, 3px); }
}
@media (prefers-reduced-motion: reduce) { .agen-quest-cue-chevron[b-x69esb1yx8] { animation: none; } }

.agen-quest-inner[b-x69esb1yx8] { display: grid; grid-template-columns: var(--agen-rail) minmax(0, 1fr); }
.agen-quest-rail[b-x69esb1yx8] {
    position: sticky; top: var(--nav); height: calc(100svh - var(--nav));
    z-index: 2;
    display: flex; flex-direction: column; justify-content: center;   /* balance the cover vertically — fills the old bottom void */
    padding: 24px 24px 24px 0;
}

/* The questions: irregular opening preserved (nth-child), only --ix animated away. */
.agen-quests[b-x69esb1yx8] {
    position: relative;
    /* left inset holds the number column off the screen edge; the text sits at this
       inset, the numbers right-align in the gutter to its left. */
    margin: 0; padding: 0 0 0 clamp(58px, 6.5vw, 98px); list-style: none;
    display: flex; flex-direction: column; align-items: flex-start; gap: clamp(18px, 2.6vh, 34px);
    font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; width: max-content;
    transform: translateX(0);         /* settled: at the rail */
}
/* Each row keeps its OWN varied size + colour (the "overheard voices") through the
   cover, the leftward move AND the align — nothing is equalised up front. Only AFTER a
   row has aligned left does agenq-normalize animate its size AND colour TO the uniform
   settled values (Susanti 7/24: harmonise as a TRANSFORMATION after aligning, NOT from
   the start). Base here = the uniform END state (also the no-scroll-timeline fallback);
   --q-size / --q-color are the varied START values the normalize interpolates FROM. */
.agen-quests li[b-x69esb1yx8] {
    position: relative; max-width: 540px; transform: translateX(0);
    font-size: clamp(19px, 2.4vw, 26px);
    color: rgba(255, 255, 255, 0.92);
}
.agen-quests li:nth-child(1)[b-x69esb1yx8] { --ix: 0px;                      --q-size: clamp(22px, 3.2vw, 36px); --q-color: #FFFFFF; }
.agen-quests li:nth-child(2)[b-x69esb1yx8] { --ix: clamp(20px, 6vw, 90px);  --q-size: clamp(18px, 2.4vw, 27px); --q-color: rgba(255, 255, 255, 0.72); }
.agen-quests li:nth-child(3)[b-x69esb1yx8] { --ix: 0px;                      --q-size: clamp(20px, 2.8vw, 31px); --q-color: rgba(255, 255, 255, 0.88); }
.agen-quests li:nth-child(4)[b-x69esb1yx8] { --ix: clamp(12px, 4vw, 48px);  --q-size: clamp(17px, 2.2vw, 24px); --q-color: rgba(255, 255, 255, 0.55); }
.agen-quests li:nth-child(5)[b-x69esb1yx8] { --ix: clamp(28px, 9vw, 140px); --q-size: clamp(19px, 2.6vw, 29px); --q-color: rgba(255, 255, 255, 0.80); }

.agen-quest-n[b-x69esb1yx8] {
    /* Absolute so it NEVER shifts the text (the cover stays exactly as-is). Sized to
       ITS OWN question (0.6em) so number and question read as balanced, and right-
       aligned a fixed 14px gap before the text so the numbers form a tidy column
       against the left-aligned questions. top:0.32em ≈ baseline-aligns the smaller
       number with the question's first line. */
    position: absolute; left: 0; top: 0.32em;
    transform: translateX(calc(-100% - 14px));
    font-size: 0.6em; font-weight: 800; letter-spacing: 0.01em; color: #7CC6FF;
    opacity: 1;                       /* settled: visible */
}

/* 🚨 THIS SCENE IS THE ONE PLACE ON THE AGEN FACE WITH NO GOLD IN IT (Susanti, 7/30, over
   four notes: gold accents on a warmed ground → "warna coklat tidak cocok" → "highlight
   tulisan bagian sini jangan gold lagi" → pale blue → "lebih bagus warna navy"). It briefly
   ran gold numerals, a gold active question and metal-clipped numerals; all of it is out.
   Navy ground, #7CC6FF accents — what it was before any of this. The gold belongs to the
   chrome and the closing band, not here. */
.agen-quest-mark[b-x69esb1yx8] { color: inherit; opacity: 0; }               /* settled: quotes gone */
/* Blue quote accent (Susanti 7/24: "kutipnya biru untuk yang ketiga dan keempat" —
   kept on 1 too). The other rows' quotes take their line's own varied colour. */
.agen-quests li:nth-child(1) .agen-quest-mark[b-x69esb1yx8],
.agen-quests li:nth-child(3) .agen-quest-mark[b-x69esb1yx8],
.agen-quests li:nth-child(4) .agen-quest-mark[b-x69esb1yx8] { color: #7CC6FF; }

.agen-sections[b-x69esb1yx8] { padding: 0 clamp(24px, 4vw, 64px) 0 clamp(28px, 3vw, 48px); max-width: calc(var(--agen-rail) + 900px); }
.agen-quest-spacer[b-x69esb1yx8] { height: 112svh; }

/* ── <=900px: a plain stacked navy cover, then the white sections (no wipe, no pin) ──
   🚨 THIS BLOCK CARRIES ITS OWN COPY of the scene's ground and ink. Change the colour up
   there and not here and the phone gets the other combination — white text on a pale field,
   or dark text on navy. Both happened while this scene was being tried in three colours. */
@media (max-width: 900px) {
    .q-stick-navy[b-x69esb1yx8] { display: none; }
    .q-stick-head[b-x69esb1yx8] { position: static; height: auto; background: #0B1524; }
    .agen-quest-head[b-x69esb1yx8] { position: static; top: auto; left: auto; width: auto; opacity: 1; padding: 44px clamp(22px, 6vw, 32px) 4px; }
    .agen-quest-inner[b-x69esb1yx8] { display: block; }
    .agen-quest-rail[b-x69esb1yx8] { position: static; height: auto; padding: 12px clamp(22px, 6vw, 32px) 40px; background: #0B1524; }
    .agen-quests[b-x69esb1yx8] { position: static; transform: none; padding: 0; width: 100%; gap: 16px; }
    .agen-quests li[b-x69esb1yx8] { transform: none; max-width: none; font-size: 17px; color: #FFFFFF; }
    .agen-quest-n[b-x69esb1yx8] { position: static; transform: none; top: auto; font-size: 0.8em; margin-right: 10px; }
    .agen-quest-mark[b-x69esb1yx8] { display: none; }
    .agen-quest-cue[b-x69esb1yx8] { display: none; }   /* mobile cover is content-height — no void to fill */
    .agen-sections[b-x69esb1yx8] { max-width: none; padding: 32px clamp(22px, 6vw, 32px) 0; }
    /* The spacer only exists to give the DESKTOP intro choreography scroll room
       (see its height: 112svh above and the min-width:901px block below). On mobile
       the choreography is off, so leaving the spacer's height standing produced a
       huge empty gap between the questions and the first answer. It is flow-only
       decoration, so display:none is the honest reset. */
    .agen-quest-spacer[b-x69esb1yx8] { display: none; }
}

/* ── Desktop choreography: the intro plays while the rail is stuck, driven by the
   spacer's view-timeline (hoisted onto .agen-quest). Pure transforms + clip-path so
   nothing thrashes layout; sizes/colours NEVER animate (Susanti: no shrink). Fallback
   / reduced motion: the settled split above, static. ── */
@media (min-width: 901px) {
    @supports (animation-timeline: view()) {
        @media (prefers-reduced-motion: no-preference) {
            .agen-quest-spacer[b-x69esb1yx8] { view-timeline: --agenq block; }
            .agen-quest-head[b-x69esb1yx8] { animation: agenq-head-b-x69esb1yx8 linear both; animation-timeline: --agenq; animation-range: exit 2% exit 26%; }
            .agen-quest-cue[b-x69esb1yx8]  { animation: agenq-cuefade-b-x69esb1yx8 linear both; animation-timeline: --agenq; animation-range: exit 0% exit 16%; }
            .agen-quests[b-x69esb1yx8]     { animation: agenq-move-b-x69esb1yx8 linear both; animation-timeline: --agenq; animation-range: exit 4% exit 46%; }
            /* two animations on the row: the leftward indent-collapse (align), then —
               only AFTER it has aligned — the size+colour normalize. */
            .agen-quests li[b-x69esb1yx8]  { animation: agenq-indent-b-x69esb1yx8 linear both, agenq-normalize-b-x69esb1yx8 linear both;
                               animation-timeline: --agenq, --agenq;
                               animation-range: exit 4% exit 46%, exit 58% exit 82%; }
            .agen-quest-mark[b-x69esb1yx8] { animation: agenq-mk-b-x69esb1yx8 linear both; animation-timeline: --agenq; animation-range: exit 8% exit 36%; }
            .agen-quest-n[b-x69esb1yx8]    { animation: agenq-num-b-x69esb1yx8 linear both; animation-timeline: --agenq; animation-range: exit 46% exit 58%; }
            .agen-quest-navy[b-x69esb1yx8] { animation: agenq-wipe-b-x69esb1yx8 linear both; animation-timeline: --agenq; animation-range: exit 46% exit 78%; }
            /* A quiet beat AFTER the peel: the first answer sits ~a screen lower, so the
               revealed white stays empty for a moment and the answer rises INTO it rather
               than already being near the top when the navy settles (Susanti 7/25). */
            .agen-quest-spacer + .agen-section[b-x69esb1yx8] { margin-top: 60svh; }
        }
    }
}

/* ── Index light-up: each question row turns blue while ITS answer section is on screen
   (Susanti 7/25 — the old TOC light-up, now on the questions, which map 1:1 to the
   sections). Put on .agen-quest-q (not the whole row) so it never fights the row's
   normalize, and `forwards` fill so the varied cover colours during the intro stay
   untouched — a section's --agen-s* only reaches its `entry` AFTER the intro (the 60svh
   gap keeps section 1 below the fold until then). Colour only → not gated on reduced
   motion, like the original toc-on. --agen-s1..s5 hoisted via timeline-scope on .landing. */
@media (min-width: 901px) {
    @supports (animation-timeline: view()) {
        .agen-quests li:nth-child(1) .agen-quest-q[b-x69esb1yx8] { animation: agenq-active-b-x69esb1yx8 linear forwards; animation-timeline: --agen-s1; animation-range: entry 0% exit 100%; }
        .agen-quests li:nth-child(2) .agen-quest-q[b-x69esb1yx8] { animation: agenq-active-b-x69esb1yx8 linear forwards; animation-timeline: --agen-s2; animation-range: entry 0% exit 100%; }
        .agen-quests li:nth-child(3) .agen-quest-q[b-x69esb1yx8] { animation: agenq-active-b-x69esb1yx8 linear forwards; animation-timeline: --agen-s3; animation-range: entry 0% exit 100%; }
        .agen-quests li:nth-child(4) .agen-quest-q[b-x69esb1yx8] { animation: agenq-active-b-x69esb1yx8 linear forwards; animation-timeline: --agen-s4; animation-range: entry 0% exit 100%; }
        .agen-quests li:nth-child(5) .agen-quest-q[b-x69esb1yx8] { animation: agenq-active-b-x69esb1yx8 linear forwards; animation-timeline: --agen-s5; animation-range: entry 0% exit 100%; }
    }
}
@keyframes agenq-head-b-x69esb1yx8   { 0% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-20px); } }
@keyframes agenq-move-b-x69esb1yx8   { 0% { transform: translateX(24vw); } 100% { transform: translateX(0); } }
@keyframes agenq-indent-b-x69esb1yx8 { 0% { transform: translateX(var(--ix, 0px)); } 100% { transform: translateX(0); } }
@keyframes agenq-mk-b-x69esb1yx8     { 0% { opacity: 1; } 100% { opacity: 0; } }
@keyframes agenq-num-b-x69esb1yx8    { 0% { opacity: 0; } 100% { opacity: 1; } }
@keyframes agenq-wipe-b-x69esb1yx8   { 0% { clip-path: inset(0 0 0 0); } 100% { clip-path: inset(0 calc(100% - var(--agen-rail)) 0 0); } }
/* AFTER a row has aligned, its varied cover size+colour interpolate to the uniform
   settled values — the "harmonise" transformation. Numbers (0.6em) resize with it. */
@keyframes agenq-normalize-b-x69esb1yx8 { 0% { font-size: var(--q-size); color: var(--q-color); } 100% { font-size: clamp(19px, 2.4vw, 26px); color: rgba(255, 255, 255, 0.92); } }
/* off (settled white) → blue while the section is on screen → off; the middle 76% holds
   blue so a hand-off between two rows reads as a pass, not an overlap. */
@keyframes agenq-active-b-x69esb1yx8 { 0% { color: rgba(255, 255, 255, 0.92); } 12% { color: #7CC6FF; } 88% { color: #7CC6FF; } 100% { color: rgba(255, 255, 255, 0.92); } }
@keyframes agenq-cuefade-b-x69esb1yx8 { 0% { opacity: 1; } 100% { opacity: 0; } }

/* Each of the five sections carries a named timeline. These are READ by the
   question rail above (.agen-quests li:nth-child(n) .agen-quest-q) — the pinned
   table of contents that used to read them was deleted along with its markup;
   the questions on the cover serve as the index now. Hoisted by timeline-scope
   on .landing, the same arrangement as --agen-fill and --story-earn. */
.agen-sections > .agen-section:nth-of-type(1)[b-x69esb1yx8] { view-timeline: --agen-s1 block; }
.agen-sections > .agen-section:nth-of-type(2)[b-x69esb1yx8] { view-timeline: --agen-s2 block; }
.agen-sections > .agen-section:nth-of-type(3)[b-x69esb1yx8] { view-timeline: --agen-s3 block; }
.agen-sections > .agen-section:nth-of-type(4)[b-x69esb1yx8] { view-timeline: --agen-s4 block; }
.agen-sections > .agen-section:nth-of-type(5)[b-x69esb1yx8] { view-timeline: --agen-s5 block; }

/* The four-part version of this section carried gradient fades between its grounds,
   because the parts butted on hard lines. Those rules were removed rather than left
   to fight the chapter system, which washes every boundary on its own. (The picture
   strip that used to do the washing — .agen-chapter-figure and its seam gradients —
   is gone too; the alternating chapter grounds carry the boundary now.) */

/* The picker no longer has a band of its own: it opens chapter 01, under that
   chapter's cover, so the .agen-open* rules that used to live here went with it. What
   the picker still needs is its own card and its own label. */
.agen-pick[b-x69esb1yx8] {
    max-width: 560px;
    margin: 0 auto 28px;
    padding: 22px 24px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius);
    box-shadow: var(--l-shadow-lift);
    text-align: left;
}

.agen-pick-label[b-x69esb1yx8] {
    display: block;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--l-text-3);
    margin-bottom: 10px;
}

/* A native <select>. The app-wide rule is the shared <SelectField>, but that needs
   interactivity and this page renders with no circuit. Styled so it does not read as
   a raw browser control. */
.agen-prov-select[b-x69esb1yx8] {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    background: #FFFFFF url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%237A899C' stroke-width='2.5' stroke-linecap='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E") no-repeat right 14px center;
    color: var(--l-text);
    border: 1.5px solid var(--l-border-strong);
    border-radius: var(--radius-xs);
    padding: 13px 40px 13px 15px;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.agen-prov-select:focus-visible[b-x69esb1yx8] {
    outline: none;
    border-color: var(--l-blue);
    box-shadow: 0 0 0 3px rgba(21, 101, 192, 0.18);
}

@media (hover: hover) {
    .agen-prov-select:hover[b-x69esb1yx8] {
        border-color: var(--l-blue);
        box-shadow: 0 0 0 3px rgba(21, 101, 192, 0.18);
    }
}

.agen-pick-note[b-x69esb1yx8] {
    font-size: 13px;
    line-height: 1.6;
    color: var(--l-text-2);
    margin: 14px 0 0;
}

/* The three facts, under the picker rather than beside a headline. */
.agen-facts[b-x69esb1yx8] {
    list-style: none;
    margin: 0 auto 32px;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 14px 40px;
    font-size: 14px;
    color: var(--l-text-2);
    text-align: center;
}

.agen-facts strong[b-x69esb1yx8] {
    display: block;
    font-size: 26px;
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--l-green-text);
    margin-bottom: 2px;
}

.agen-lede[b-x69esb1yx8] {
    max-width: 620px;
    margin: 0 auto 26px;
    font-size: 16px;
    line-height: 1.65;
    color: var(--l-text-2);
    text-align: center;
}
/* ── 2 · Workbench ────────────────────────────────────────────────────────────
   No heading above it. The reader got here by choosing a province, so a line
   explaining what this is would be explaining something they just did. */
.agen-bench[b-x69esb1yx8] {
    position: relative;
    padding: 40px 0 56px;
    background: var(--l-bg-alt);
    border-bottom: 1px solid var(--l-border);
}

.agen-bench-grid[b-x69esb1yx8] {
    position: relative;
    display: grid;
    grid-template-columns: 1.5fr 1fr;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius);
    box-shadow: var(--l-shadow-lift);
    /* `clip` for the same reason as .agen-open above: this needs to trim the map to the
       card's rounded corner, and `hidden` would make it a scrollport and freeze the
       scroll-driven entrance on the map and the list inside it. */
    overflow: clip;
}

.agen-map[b-x69esb1yx8] {
    position: relative;
    min-height: 440px;
    background: var(--l-bg-alt);
    border-right: 1px solid var(--l-border);

    /* Raster tiles land on fractional DEVICE pixels under the app's display zoom and
       show hairline seams. Draw at native scale — the same fix .map-container uses
       app-wide. Never a transform: that scales already-rasterised tiles. */
    zoom: var(--app-unzoom, 1);
}

.agen-map-placeholder[b-x69esb1yx8] {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 28px;
    text-align: center;
    font-size: 14px;
    color: var(--l-text-3);
}

.agen-map-placeholder[b-x69esb1yx8]  svg {
    opacity: 0.55;
}

.agen-map-placeholder span[b-x69esb1yx8] {
    max-width: 26ch;
}

.agen-list-pane[b-x69esb1yx8] {
    display: flex;
    flex-direction: column;
    min-height: 440px;
}

.agen-legend[b-x69esb1yx8] {
    display: flex;
    gap: 18px;
    padding: 15px 20px;
    border-bottom: 1px solid var(--l-border);
    font-size: 12px;
    color: var(--l-text-2);
}

.agen-legend-item[b-x69esb1yx8] {
    display: inline-flex;
    align-items: center;
    gap: 7px;
}

.agen-dot[b-x69esb1yx8] {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    flex-shrink: 0;
}

.agen-dot-open[b-x69esb1yx8] { background: var(--l-green); }
.agen-dot-taken[b-x69esb1yx8] { background: #94A3B8; }

.agen-list[b-x69esb1yx8] {
    flex: 1;
    overflow-y: auto;
    max-height: 396px;
    /* Keep a flick that runs past the last row from carrying on into the page
       behind it. Without this the list hands its leftover momentum upward and the
       reader shoots past the result bar they were heading for. */
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding: 8px;
    /* Breathing room between rows (Susanti, 7/24): the rows are rounded,
       hover-filled chips, and stacked flush their highlights touched and read
       as one smear. The gap is what lets each chip's hover/selected state be
       its own shape. */
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.agen-list-empty[b-x69esb1yx8],
.agen-list-summary[b-x69esb1yx8] {
    font-size: 13px;
    color: var(--l-text-3);
    padding: 14px;
    margin: 0;
    text-align: center;
}

.agen-item[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius-xs);
    color: var(--l-text);
    font-size: 14px;
    text-align: left;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}

/* Focus and hover carry the SAME cue (design-discipline §1), hover guarded: these
   rows stay on the page after a tap, so an unguarded :hover would stick to the
   last-tapped kabupaten on a touch device. */
.agen-item:focus-visible:not(:disabled)[b-x69esb1yx8] {
    outline: none;
    background: var(--l-green-pale);
    border-color: var(--l-green);
}

@media (hover: hover) {
    .agen-item:hover:not(:disabled)[b-x69esb1yx8] {
        background: var(--l-green-pale);
        border-color: var(--l-green);
    }
}

.agen-item.is-selected[b-x69esb1yx8] {
    background: var(--l-green-pale);
    border-color: var(--l-green);
    box-shadow: inset 0 0 0 1px var(--l-green);
}

.agen-item.is-taken[b-x69esb1yx8] {
    color: var(--l-text-3);
    cursor: not-allowed;
}

.agen-item-name[b-x69esb1yx8] { flex: 1; }

.agen-item-state[b-x69esb1yx8] {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--l-text-3);
}

.agen-item:not(.is-taken) .agen-item-state[b-x69esb1yx8] {
    color: var(--l-green-text);
}

/* The result bar — the section's main ask, directly under the thing that produced
   it rather than seven screens later. */
.agen-result[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    margin-top: 16px;
    padding: 18px 22px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--l-shadow);
}

.agen-result-text[b-x69esb1yx8] {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.agen-result-region[b-x69esb1yx8] {
    font-size: 16px;
    font-weight: 600;
    color: var(--l-text-2);
}

.agen-result-region.is-picked[b-x69esb1yx8] {
    font-size: 19px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--l-green-text);
}

.agen-result-terms[b-x69esb1yx8] {
    font-size: 13px;
    color: var(--l-text-3);
}

/* ── The five answer sections, laid out top to bottom ──
   These were tabs (radio inputs + a pill strip, click to switch). Expanded to a plain
   vertical stack at Susanti's call (2026-07-22): a horizontal scroll-jack here would
   have stacked a THIRD pin on the cover pin + panel ride, and duplicated the "Kenapa
   Tepatjasa" ride — and this content is dense and interactive (a calculator, an FAQ),
   which reads better standing still than sliding past. It is a long section now; the
   back-to-top button (.landing-top, end of file) is the concession to the length. */
/* Each section is its OWN panel: a light card with the photograph (where it has one)
   as a full-bleed cover band across the top and a coloured accent on the heading — so a
   section reads as a place you arrived at, not a plain block of text (reported flat). */
.agen-section[b-x69esb1yx8] {
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: 22px;
    /* clip (NOT hidden) rounds the cover band's top corners without making the panel a
       scroll container — hidden would freeze the reveal timelines inside it. */
    overflow: clip;
    box-shadow: 0 6px 26px rgba(15, 23, 42, 0.06);
    padding: 0 clamp(22px, 3vw, 40px) clamp(30px, 4vw, 42px);
}

.agen-section + .agen-section[b-x69esb1yx8] { margin-top: 40px; }

/* The photograph is the section's cover band — full width, flush to the top, its own
   straight bottom edge meeting the content. All five sections have one now (Susanti
   asked for content-depicting urban photos on the two that were gradient bands). */
.agen-section .tab-art[b-x69esb1yx8] {
    margin: 0 calc(-1 * clamp(22px, 3vw, 40px));
    border-radius: 0;
    height: clamp(120px, 15vw, 200px);
}

.agen-section-head[b-x69esb1yx8] {
    font-size: clamp(20px, 2.4vw, 26px);
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--l-text);
    margin: 24px 0 22px;
    padding-left: 14px;
    border-left: 4px solid var(--l-blue);
}

/* ── The rule beside each section head stays BLUE on every face, including agen ──
   It went gold-with-metal, then flat gold, then back to blue over three notes on 7/30
   ("vertical bar di sebelah kanannya juga dikembalikan ke warna biru"), and the end state
   is the base rule above with no override at all. Worth knowing why it did not settle
   gold: the reader sees this bar in the same glance as the navy chapter rail on its left,
   and that rail is blue — a gold mark beside it read as a third colour arriving with no
   reason. The gold on this face lives in the chrome and the closing band. */

/* ── The picture at the head of each tab panel ──
   Five photographs were bought, cropped, credited and measured, and the section
   rebuild used exactly one of them. That was a regression, not a simplification:
   the page went back to the flat, sterile thing it was before any photography.

   They go here because a tab is the one place on this page where a picture EARNS
   its keep by changing. Switching tabs swaps the photograph, so the switch is felt
   as arriving somewhere rather than as text being replaced. No text sits on them,
   so there is no contrast to manage — the panel's own content starts below.

   Ratio-locked and modest in height: this is the lid on a box of answers, not a
   chapter cover. Those were tried at 440px and rejected. */
.tab-art[b-x69esb1yx8] {
    height: clamp(110px, 13vw, 170px);
    margin-bottom: 26px;
    border-radius: var(--radius-sm);
    background-size: cover;
    background-position: center;
    background-color: var(--l-bg-alt);
    /* Held back a little so a photograph never out-shouts the answer under it. */
    filter: saturate(0.86) brightness(1.02);
}

/* ALL FIVE sections carry a photograph now, each an URBAN scene chosen to depict that
   section's content (Susanti, 2026-07-22 — the two that were gradient bands looked flat,
   and she asked for content-fitting urban images on every one):
     kenapa  city flats from the air — the territory this section says is yours
     siapa   people out on a car-free avenue — literally "cocok untuk siapa", who it's for
     cara    a working city street / transit — how the thing runs
     uang    a dusk city aerial — the money section; a literal financial district was
             impossible to ship brand-free (every Jakarta one carried a bank/hotel logo
             — Sun Life, BNI, BCA, Mandiri, Bank MAS, Grand Hyatt; see CREDITS reject
             record), so a high aerial where nothing is legible stands in for "the city
             / the economy your commission comes from"
     kami    a corporate glass facade reflecting the real city — a real registered company

   (An earlier note here argued money/legal sections should carry no photo at all. That
   was overruled; the constraint it was really about — no legible brand or face — still
   holds and is why uang/kami took the aerial and the facade, not a branded skyline.) */
.tab-art-kenapa[b-x69esb1yx8] { background-image: url('../../../images/marketing/ch-kenapa.jpg'); }
.tab-art-siapa[b-x69esb1yx8] { background-image: url('../../../images/marketing/ch-cocok.jpg'); }
.tab-art-cara[b-x69esb1yx8] { background-image: url('../../../images/marketing/ch-cara.jpg'); }
.tab-art-uang[b-x69esb1yx8] { background-image: url('../../../images/marketing/ch-uang.jpg'); }
.tab-art-kami[b-x69esb1yx8] { background-image: url('../../../images/marketing/ch-kami.jpg'); }

/* ── Shared inside the panels ── */
.agen-sub-head[b-x69esb1yx8] {
    font-size: 17px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--l-text);
    margin: 0 0 14px;
}

/* TWO COLUMNS ONLY WHERE TWO FIT (2026-07-28). It was a hard `1fr 1fr` that collapsed
   at 768px, and — like .agen-why above — the breakpoint was never the problem: this
   grid lives in the quest ANSWERS column, which is 658px at 1440 and 419px on a 1024
   laptop. Measured before this change: 313px per column at 1440, 193px at 1024. The
   left column carries a comparison table whose own row is `label 130px | bar | value`,
   so at 193px the BAR WAS 0px WIDE — a comparison chart with no bars.
   380px is what that table needs to keep a readable bar; below two of those the grid
   stacks, which is what the money section does at every width it is currently drawn
   at. If it ever moves back to a full-width band, two columns return by themselves. */
.agen-two[b-x69esb1yx8] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
    gap: 32px;
    align-items: start;
    max-width: 920px;
    margin: 0 auto;
}

/* FOUR, TWO OR ONE — NEVER THREE (2026-07-28, second pass).

   History, because both passes were corrections of the same underlying mistake. The
   rule was `repeat(4, 1fr)` collapsing at 768px, which ignored that this grid no
   longer lives in a full-width band: it sits in the quest's answers column, 658px
   wide at 1440 after the section's padding. Four columns of that is 151px per card
   — measured — for cards holding twenty words, which is the one-word-per-line
   wrapping first reported. Pass one replaced it with auto-fit + minmax(250px), which
   fixed the squeeze but let the count land on THREE, and three columns of four cards
   leaves an orphan on a second row (Susanti's screenshot).

   So the count is now stated rather than derived, and only in values that divide
   four: wide enough for four, they stay the tall portrait cards the section opened
   with; too tight for that, two; too tight for two, one — the plain top-to-bottom
   scroll a phone wants.

   The breakpoints are the same arithmetic as before, applied to the CONTAINER: four
   250px cards plus three 18px gaps is 1054px, two is 518px. Container queries and
   not media queries because the answers column is 820px wide on a 820px tablet but
   553px on a 1024px laptop — the quest's own two-column layout is still on at 1024
   and already stacked at 820, so a viewport number cannot describe this box. */
/* ── ONE COLUMN AT EVERY WIDTH (Susanti, 7/30: "dulunya vertical designnya, sekarang jadi
      horizontal, lebih bagus desain vertical, tetapi kalau layar mengecil, buat dia jadi card
      seperti sekarang, agar tidak squeezed") ──
   The 2-up and 4-up container queries are gone: these four are a list of reasons that build
   on each other, and a 2×2 grid asks the reader to pick a reading order for them.

   WHAT CHANGES WITH WIDTH IS THE CARD, NOT THE COLUMN COUNT, and that is the whole of her
   second clause. Wide, each card turns its icon out to the LEFT and sets the words beside it
   — a row, which is what keeps a full-width card from reading as a mostly-empty box. Narrow,
   the icon goes back on top and the card is exactly the card it is today, because an icon
   holding a 44px column beside 14px text is precisely the squeeze she is naming. */
.agen-why[b-x69esb1yx8] {
    display: grid;
    grid-template-columns: 1fr;
    gap: 14px;
    max-width: 900px;
    margin: 0 auto;
}

@container agen-answer (min-width: 560px) {
    .agen-why-card[b-x69esb1yx8] {
        display: grid;
        grid-template-columns: 44px minmax(0, 1fr);
        column-gap: 18px;
        align-items: start;
        padding: 22px 24px;
    }

    /* Spans both rows so the title and the paragraph stack beside it, not under it. */
    .agen-why-card .feature-icon[b-x69esb1yx8] {
        grid-row: 1 / span 2;
        margin-bottom: 0;
    }

    .agen-why-card .feature-title[b-x69esb1yx8] {
        margin: 2px 0 6px;
    }

    /* The measure, not the card, is what caps the line: a 900px card would otherwise run
       these two sentences out to ~120 characters. */
    .agen-why-card .feature-desc[b-x69esb1yx8] {
        max-width: 62ch;
    }
}

.agen-why-card[b-x69esb1yx8] {
    padding: 24px 20px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--l-shadow);
}

.feature-icon[b-x69esb1yx8] {
    width: 44px;
    height: 44px;
    border-radius: var(--radius-xs);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 14px;
}

/* Icon glyphs are the DARK end of each hue: a light stroke on a white card is
   barely a suggestion of an icon. The plates stay pale so the cards keep calm. */
.icon-blue[b-x69esb1yx8] { background: #E7F0FB; color: #1565C0; }
.icon-green[b-x69esb1yx8] { background: #E4F6EB; color: #15803D; }
.icon-purple[b-x69esb1yx8] { background: #F1E9FD; color: #7E22CE; }
.icon-orange[b-x69esb1yx8] { background: #FDEEE1; color: #C2410C; }
.icon-teal[b-x69esb1yx8] { background: #E0F5F3; color: #0F766E; }
.icon-rose[b-x69esb1yx8] { background: #FCE8F1; color: #BE185D; }

.feature-title[b-x69esb1yx8] {
    font-size: 16px;
    font-weight: 700;
    margin: 0 0 8px;
    color: var(--l-text);
}

.feature-desc[b-x69esb1yx8] {
    font-size: 14px;
    line-height: 1.6;
    color: var(--l-text-2);
    margin: 0;
}

.agen-product-list[b-x69esb1yx8] {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: 10px;
    align-content: start;
}

.agen-product-list li[b-x69esb1yx8] {
    position: relative;
    padding: 13px 16px 13px 42px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-xs);
    font-size: 14px;
    line-height: 1.6;
    color: var(--l-text-2);
}

.agen-product-list li[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    left: 16px;
    top: 18px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--l-green-pale);
    box-shadow: inset 0 0 0 2px var(--l-green);
}

.agen-product-list strong[b-x69esb1yx8] {
    color: var(--l-text);
    font-weight: 600;
}

.agen-ready[b-x69esb1yx8] {
    font-size: 14px;
    line-height: 1.65;
    color: var(--l-text-2);
    margin: 16px 0 0;
}

.agen-ready strong[b-x69esb1yx8] { color: var(--l-text); font-weight: 700; }

.agen-ready a[b-x69esb1yx8] {
    color: var(--l-blue);
    font-weight: 600;
    text-decoration: none;
    border-bottom: 1.5px solid rgba(21, 101, 192, 0.35);
}

.agen-ready a:focus-visible[b-x69esb1yx8] {
    outline: none;
    color: var(--l-blue-deep);
    border-bottom-color: var(--l-blue-deep);
}

@media (hover: hover) {
    .agen-ready a:hover[b-x69esb1yx8] {
        color: var(--l-blue-deep);
        border-bottom-color: var(--l-blue-deep);
    }
}

/* ── Capital anchor ── */
.agen-anchor[b-x69esb1yx8] {
    padding: 20px 22px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--l-shadow);
}

/* The label column FLEXES down to 90px (2026-07-28). At a hard 130px, a narrow card
   left the middle track nothing: measured 30px of bar at 1440 and 0px at 1024, so the
   thing the row exists to show simply was not drawn. The 40px floor on the bar is the
   backstop — a row can lose label width before it loses the bar. */
.agen-anchor-row[b-x69esb1yx8] {
    display: grid;
    grid-template-columns: minmax(90px, 130px) minmax(40px, 1fr) auto;
    align-items: center;
    gap: 12px;
    margin-bottom: 14px;
    font-size: 13px;
    color: var(--l-text-2);
}

.agen-anchor-bar[b-x69esb1yx8] {
    display: block;
    height: 8px;
    border-radius: 999px;
    background: var(--l-bg-alt);
    overflow: hidden;
}

.agen-anchor-bar i[b-x69esb1yx8] {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: #94A3B8;
    min-width: 2px;
}

.agen-anchor-val[b-x69esb1yx8] {
    font-weight: 700;
    color: var(--l-text);
    white-space: nowrap;
}

/* The agent row is the CONCLUSION of the comparison, not one more line. It breaks out
   as a highlighted green card so the eye lands on it (and on Rp 0) immediately, instead
   of the green text being the only cue (Susanti 7/25: fokuskan agen kita). */
.agen-anchor-row.is-us[b-x69esb1yx8] {
    color: var(--l-text);
    grid-template-columns: 1fr auto;   /* 2 cols: name+tag stacked left, big Rp 0 right */
    align-items: center;
    row-gap: 2px;
    margin-top: 18px;
    margin-bottom: 0;
    padding: 12px 16px;
    background: var(--l-green-pale);
    border: 1px solid var(--l-green);
    border-radius: 12px;
}
.agen-anchor-row.is-us .agen-anchor-name[b-x69esb1yx8] {
    grid-column: 1; grid-row: 1;
    font-weight: 800; color: var(--l-text); font-size: 15px;
}

/* Replaces the (empty) bar for the zero-modal row: says WHY it is Rp 0 — no premises,
   no inventory — the real contrast with every franchise above. Sits on its own line
   under the name so it never wrap-crushes in a narrow column. */
.agen-anchor-ustag[b-x69esb1yx8] {
    grid-column: 1; grid-row: 2;
    font-size: 12.5px; font-weight: 600; color: var(--l-green-text);
}

.agen-anchor-row.is-us .agen-anchor-val[b-x69esb1yx8] {
    grid-column: 2; grid-row: 1 / span 2; align-self: center;
    color: var(--l-green-text);
    font-size: 24px;
    font-weight: 800;
}

.agen-anchor-note[b-x69esb1yx8] {
    font-size: 12px;
    line-height: 1.6;
    color: var(--l-text-3);
    margin: 16px 0 0;
}

/* ── Calculator ──
   Open, not collapsed, and only inside the "Soal uang" tab. A reader who opened a
   tab with that label has asked the question; a second click to see the answer is
   the evasiveness this page is trying not to have. Every input is still the
   reader's own assumption — we have no launched platform and therefore no measured
   earnings, so a headline figure of ours would be invented. */
.agen-sim[b-x69esb1yx8] {
    padding: 20px 22px;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--l-shadow);
}

.agen-sim-row[b-x69esb1yx8] {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 6px 12px;
    margin-bottom: 16px;
}

.agen-sim-label[b-x69esb1yx8] { font-size: 13px; color: var(--l-text-2); }

.agen-sim-out[b-x69esb1yx8] {
    font-size: 13px;
    font-weight: 700;
    color: var(--l-text);
    text-align: right;
}

.agen-sim-range[b-x69esb1yx8] {
    grid-column: 1 / -1;
    width: 100%;
    accent-color: var(--l-blue);
    cursor: pointer;
}

.agen-sim-result[b-x69esb1yx8] {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 16px;
    margin-top: 4px;
    background: var(--l-green-pale);
    border: 1px solid rgba(22, 163, 74, 0.28);
    border-radius: var(--radius-xs);
}

.agen-sim-result-label[b-x69esb1yx8] {
    font-size: 13px;
    color: var(--l-text-2);
    width: 100%;
    text-align: center;
}

.agen-sim-result-num[b-x69esb1yx8] {
    font-size: 26px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: #15803D;
}

.agen-sim-result-unit[b-x69esb1yx8] { font-size: 13px; color: var(--l-text-2); }

.agen-sim-note[b-x69esb1yx8] {
    font-size: 12px;
    line-height: 1.6;
    color: var(--l-text-3);
    margin: 14px 0 0;
}

/* ── Trust + FAQ (one tab) ── */
.agen-trust[b-x69esb1yx8] {
    max-width: 760px;
    margin: 0 auto 24px;
    padding: 20px 24px;
    background: var(--l-blue-pale);
    border: 1px solid rgba(21, 101, 192, 0.18);
    border-radius: var(--radius-sm);
}

.agen-trust-body[b-x69esb1yx8] {
    font-size: 14px;
    line-height: 1.7;
    color: var(--l-text-2);
    margin: 0 0 12px;
}

.agen-trust-body strong[b-x69esb1yx8] { color: var(--l-text); font-weight: 700; }

.agen-trust-links[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    margin: 0;
    color: var(--l-text-3);
}

.agen-trust-links a[b-x69esb1yx8] {
    color: var(--l-blue);
    text-decoration: none;
    font-weight: 600;
}

.agen-trust-links a:hover[b-x69esb1yx8],
.agen-trust-links a:focus-visible[b-x69esb1yx8] {
    outline: none;
    text-decoration: underline;
}

.agen-faq[b-x69esb1yx8] {
    max-width: 760px;
    margin: 0 auto;
    display: grid;
    gap: 10px;
}

.agen-faq-item[b-x69esb1yx8] {
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: var(--radius-xs);
    box-shadow: var(--l-shadow);
    overflow: hidden;
}

.agen-faq-item summary[b-x69esb1yx8] {
    padding: 15px 20px;
    font-size: 15px;
    font-weight: 600;
    color: var(--l-text);
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    transition: background 0.15s;
}

.agen-faq-item summary[b-x69esb1yx8]::-webkit-details-marker { display: none; }

.agen-faq-item summary[b-x69esb1yx8]::after {
    content: '+';
    font-size: 20px;
    font-weight: 400;
    color: var(--l-blue);
    flex-shrink: 0;
    line-height: 1;
}

.agen-faq-item[open] summary[b-x69esb1yx8]::after { content: '\2212'; }

.agen-faq-item summary:focus-visible[b-x69esb1yx8] {
    outline: none;
    background: var(--l-blue-pale);
}

@media (hover: hover) {
    .agen-faq-item summary:hover[b-x69esb1yx8] { background: var(--l-blue-pale); }
}

.agen-faq-item p[b-x69esb1yx8] {
    margin: 0;
    padding: 0 20px 18px;
    font-size: 14px;
    line-height: 1.65;
    color: var(--l-text-2);
}

/* ====== CTA BANNER ====== */
/* Second and last blue field, closing the page the way the hero opened it. */
/* The blue lives on a pseudo-element, not on the section, so the curtain can open by
   SCALING that layer instead of clipping the section (7/29). `clip-path` is not
   compositable — the old `inset(0 46%)` → `inset(0 0)` recomputed and repainted the
   whole band on the main thread every frame of the reveal, and, because a view-timeline
   animation is "running" whether or not its element is on screen, it was ticking there
   the entire time the reader was still up in the hero. Scaling the ground gives the
   same edge-to-edge opening; the text sits above it and simply fades, so nothing
   stretches. See @keyframes cta-curtain. */
.cta-banner[b-x69esb1yx8] {
    position: relative;
    isolation: isolate;
    padding: 64px 0;
    /* White on the two dark bands, near-black on the gold one — see --face-band-ink. */
    color: var(--face-band-ink);
}

.cta-banner[b-x69esb1yx8]::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    /* The band is shared chrome — ONE section for all three faces — so it takes its whole
       background from the face token rather than from three copies of the markup. The blue
       ramp is still the default, so the marketplace face is pixel-for-pixel what it was. */
    background: var(--face-band);
    transform: scaleX(1);
    pointer-events: none;
}

.cta-inner[b-x69esb1yx8] {
    text-align: center;
    max-width: 560px;
}

.cta-title[b-x69esb1yx8] {
    font-size: clamp(26px, 4vw, 36px);
    font-weight: 800;
    letter-spacing: -0.03em;
    margin: 0 0 12px;
    color: var(--face-band-ink);
}

/* The old value here was --l-on-blue, and the note attached to it still holds: this text is
   FULL ink, never a translucent one, because the band's lightest stop leaves no headroom to
   spend on transparency. Hierarchy against the title comes from size and weight only. The
   token simply lets "full ink" mean white on the two dark bands and near-black on the gold. */
.cta-sub[b-x69esb1yx8] {
    font-size: 16px;
    line-height: 1.6;
    color: var(--face-band-ink);
    margin: 0 0 32px;
}

.cta-buttons[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    flex-wrap: wrap;
}

/* The on-blue counterpart to .btn-solid. Overrides the blue fill AND the blue
   shadow/hover, which would otherwise still fire from .btn-solid underneath. */
.btn-white[b-x69esb1yx8] {
    background: #fff;
    color: var(--l-blue-deep);
    box-shadow: 0 1px 2px rgba(13, 71, 161, 0.2);
}

.btn-white:focus-visible[b-x69esb1yx8] {
    background: #fff;
    box-shadow: 0 6px 18px rgba(8, 47, 110, 0.28);
}

@media (hover: hover) {
    .btn-white:hover[b-x69esb1yx8] {
        background: #fff;
        box-shadow: 0 6px 18px rgba(8, 47, 110, 0.28);
    }
}

/* …and inside the closing band it takes the face's deep shade, so the white button reads as
   cut out of the band it sits on rather than borrowed from another page. SCOPED TO THE BAND
   on purpose: .btn-white is also the hero's own button on all three faces, and the hero owns
   its colours — that one is a separate decision, not a side effect of this one. All three
   deep shades clear 4.5:1 on white (green 5.0, blue 8.6, amber 7.1). */
.cta-banner .btn-white[b-x69esb1yx8] {
    color: var(--face-accent-deep);
    box-shadow: 0 1px 2px rgba(var(--face-accent-rgb), 0.28);
}

.cta-banner .btn-white:focus-visible[b-x69esb1yx8] {
    box-shadow: 0 6px 18px rgba(var(--face-accent-rgb), 0.38);
}

@media (hover: hover) {
    .cta-banner .btn-white:hover[b-x69esb1yx8] {
        box-shadow: 0 6px 18px rgba(var(--face-accent-rgb), 0.38);
    }
}

/* ── …except on gold, where the primary button goes DARK (7/30) ──
   The white pill works on the two dark bands because it is the lightest thing there. On the
   gold band it is not: the sheen's crest is nearly white and runs straight through the middle
   of the row, so a white button sat on a white highlight and lost its edge. Near-black with a
   warm-light label is the other half of the same idea that made the band's ink dark — on gold
   the contrast has to come from something darker than the field, not lighter.
   #F3E3B4 on #2E1D05 is 12:1. */
#face-agen:checked ~ * .cta-banner .btn-white[b-x69esb1yx8] {
    background: #2E1D05;
    color: #F3E3B4;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14),
                0 2px 6px rgba(46, 29, 5, 0.38);
}

#face-agen:checked ~ * .cta-banner .btn-white:focus-visible[b-x69esb1yx8] {
    background: #241505;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14),
                0 8px 20px rgba(46, 29, 5, 0.46);
}

@media (hover: hover) {
    #face-agen:checked ~ * .cta-banner .btn-white:hover[b-x69esb1yx8] {
        background: #241505;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14),
                    0 8px 20px rgba(46, 29, 5, 0.46);
    }
}

/* "Masuk", the quiet half of the pair. It is drawn in the band's own ink for the same reason
   the sentence above it is: on the gold face a white outline and white label sit at 2.4:1 and
   simply disappear. */
.btn-white-outline[b-x69esb1yx8] {
    border-color: var(--face-band-line);
    color: var(--face-band-ink);
}

.btn-white-outline:hover[b-x69esb1yx8] {
    background: var(--face-band-wash);
    border-color: var(--face-band-line-strong);
}

/* The GREEN variant of .btn-solid, for the "Tawarkan Jasamu" provider path.
   It is the same shape and elevation as the blue CTA, in the marketplace's
   second brand colour — so the two doors (hire=blue, earn=green) read as a
   matched pair anywhere they appear together (hero, paths section). */
.btn-solid.btn-green[b-x69esb1yx8] {
    background: var(--l-green);
    box-shadow: 0 1px 2px rgba(22, 163, 74, 0.24);
}

.btn-solid.btn-green:focus-visible[b-x69esb1yx8] {
    background: var(--l-green-deep);
    box-shadow: 0 6px 18px rgba(22, 163, 74, 0.32);
}

@media (hover: hover) {
    .btn-solid.btn-green:hover[b-x69esb1yx8] {
        background: var(--l-green-deep);
        box-shadow: 0 6px 18px rgba(22, 163, 74, 0.32);
    }
}

/* ====== DUA CARA — the marketplace in one screen ======
   The artistic centrepiece: two big split-cards side by side, CARI (blue, the
   customer) and TAWARKAN (green, the provider), each carrying its own number,
   icon, three-step "how" and exit button. This is the section that makes the two
   goals of the page visible at once — the hero CTA already splits them, this is the
   room each door opens.

   The cards are taller than the feature slides and carry more information, so they
   get their own visual language: a tilted accent shape behind each one (a soft
   gradient blob in the card's own hue), a large gradient numeral, and an icon row
   that pairs the feature-icon plate with a small tag pill. The accent shape is
   absolutely positioned and clipped by overflow:hidden on the card, so it sits
   behind the content without escaping. */
/* .path-tag / .path-tag-green are kept: the two-sided Cara Kerja tracks reuse them as
   their "Cari Jasa" / "Tawarkan Jasa" pills. The rest of the .path-* rules went with the
   Dua Cara section they styled (folded into Cara Kerja, 2026-07-22). */
.path-tag[b-x69esb1yx8] {
    display: inline-flex;
    align-items: center;
    padding: 5px 12px;
    border-radius: 999px;
    background: var(--l-blue-pale);
    color: var(--l-blue-deep);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.path-tag-green[b-x69esb1yx8] {
    background: var(--l-green-pale);
    color: var(--l-green-text);
}

/* ====== FOOTER ====== */
.landing-footer[b-x69esb1yx8] {
    padding: 32px 0;
    background: var(--l-bg-alt);
    border-top: 1px solid var(--l-border);
}

.footer-inner[b-x69esb1yx8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 16px;
}

/* .footer-brand / .footer-logo / .footer-wordmark are GONE (Susanti, 7/30) — she asked
   for the logo and the app name out of the footer, so the markup for all three went with
   the rules. .footer-inner is down to two children; `space-between` above now means links
   left, copyright right. */

.footer-links[b-x69esb1yx8] {
    display: flex;
    /* Four links since Hubungi Kami joined (2026-09-16); at 390px they need a
       second row, centred under the column layout the mobile block sets. */
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px 24px;
}

.footer-links a[b-x69esb1yx8] {
    font-size: 13px;
    color: var(--l-text-2);
    text-decoration: none;
    transition: color 0.15s;
}

.footer-links a:hover[b-x69esb1yx8],
.footer-links a:focus-visible[b-x69esb1yx8] {
    outline: none;
    color: var(--face-accent-text);
}

.footer-copy[b-x69esb1yx8] {
    font-size: 13px;
    color: var(--l-text-3);
}

/* ====== RESPONSIVE ====== */

/* ====== TABLET ======
   READ THIS BEFORE CHANGING ANY NUMBER BELOW — the widths look wrong until you know
   about the zoom.

   The app sets `zoom` on <html> at page load: 1.2 for any screen 600px or wider
   (__applyBootState in App.razor, and the same curve in core.js). A media query is
   matched against the RAW screen width, but every length inside the page is then
   multiplied by that zoom. So the two numbers disagree by 1.2×: an iPad in portrait
   reports 834px to the query while the layout only has 834 / 1.2 = 695px to draw in.

   That is why the 768px rules below never fired on a tablet, and the full desktop
   layout was being asked to fit into 695px. Two things broke, both measured at 834px
   and both stated here in AUTHORED px (what you write in this file = measured ÷ 1.2):

     - The navbar. Its three children need 118 + 300 + 197 = 615px inside a 647px
       container, leaving 32px for the two gaps between them. Rounding ate it and the
       links wrapped onto a second line.
     - The download row. One card needs 204px of content; three of them plus two 20px
       gaps only had 202px each, so the cards were squeezed to uneven widths.

   The band ends at 900px because that is where the arithmetic stops being tight:
   900 / 1.2 = 750px of layout, 702px of container, which leaves the navbar 87px of
   slack instead of 32.

   The 900px edge is this page's own scale, not the app's 600px breakpoint — the
   landing page is a single wide document, not a card stack, so `docs/design-discipline.md`
   §12 (key at 600, let width decide) does not apply to it. */
@media (min-width: 769px) and (max-width: 900px) {
    /* 32 → 20 buys back 36px across the three gaps: 204px of link text + 60px of gaps
       = 264px, against the 300px the links were being squeezed into. nowrap is the
       belt to that braces — without it a single link can still break inside itself and
       the whole bar grows a second line again. */
    .nav-links[b-x69esb1yx8] {
        gap: 20px;
    }

    .nav-link[b-x69esb1yx8] {
        white-space: nowrap;
    }

    /* Equal columns, tighter gutter: (647 − 36) / 2 = 305px each, against the 268px the
       0.85fr column was getting. The heading and the photo both live in that column and
       268px was too narrow for either. */
    .story-inner[b-x69esb1yx8] {
        grid-template-columns: 1fr 1fr;
        gap: 36px;
    }

    /* Both hard breaks are tuned for the desktop column width. Narrower, the line AFTER
       the break wraps again and strands its last word on a line of its own — measured at
       834px: "kelar," in the story heading, "datang." in the hero. Dropping the break lets
       the whole sentence rewrap, and text-wrap: balance (declared on both) then spreads
       the words evenly instead of leaving a short last line. */
    .story-heading br[b-x69esb1yx8],
    .hero-title br[b-x69esb1yx8] {
        display: none;
    }

    /* 12px gaps give each card (647 − 24) / 3 = 207px, and the trimmed padding drops
       what a card needs from 204px to 192px. 15px of slack, where there was −2. */
    .download-grid[b-x69esb1yx8] {
        gap: 12px;
    }

    .download-card[b-x69esb1yx8] {
        padding: 16px 18px;
    }

}

@media (max-width: 768px) {
    .hero[b-x69esb1yx8] {
        /* No top padding — the nav must sit flush against the very top.
           Hero content spacing comes from .hero-inner's padding-top.

           Bottom padding was 40px until 7/28 and is now 72px: the face dots are
           parked just inside this element's bottom edge and their 44px tap target
           is much taller than the 9px circle you can see. At 40px the mitra face's
           lowest card ran 4px into that target — measured — so a tap meant for the
           card could have changed the face instead. The room the dots need is
           theirs, not borrowed from the content. */
        padding: 0 0 72px;
    }

    /* Stack the three claims: side by side they wrap mid-sentence on a phone. */
    .hero-trust[b-x69esb1yx8] {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        gap: 12px;
        padding: 16px 20px;
        text-align: left;
    }

    /* The pinned column has nothing to pin against on a phone — one column means
       the heading would just sit still while its own steps scroll under it, which
       reads as a bug. Stack it and let it scroll normally. */
    .story[b-x69esb1yx8] {
        padding: 56px 0;
    }

    .story-inner[b-x69esb1yx8] {
        grid-template-columns: 1fr;
        gap: 28px;
    }

    .story-aside-pin[b-x69esb1yx8] {
        position: static;
    }

    .story-step[b-x69esb1yx8] {
        padding: 24px 22px 24px 26px;
    }

    /* The desktop gap exists to lengthen the pinned column's travel; on a phone the
       heading is not pinned at all, so that spacing is just dead scrolling. */
    .story-steps[b-x69esb1yx8] {
        gap: 20px;
    }

    .showcase[b-x69esb1yx8] {
        padding: 56px 0;
    }

    /* The padding lives on the text half now — the picture half above it has its
       own, and shrinking the card's old outer padding here would do nothing. */
    .sc-body[b-x69esb1yx8] {
        padding: 18px 20px 22px;
    }

    .sc-art[b-x69esb1yx8] {
        min-height: 168px;
        padding: 18px;
    }

    .download-grid[b-x69esb1yx8] {
        grid-template-columns: 1fr;
        gap: 12px;
    }

    .steps-grid[b-x69esb1yx8] {
        flex-direction: column;
        align-items: center;
        gap: 0;
    }

    .step-connector[b-x69esb1yx8] {
        transform: rotate(90deg);
        padding-top: 0;
        padding: 0;
    }

    .step-card[b-x69esb1yx8] {
        padding: 16px;
    }

    .nav-links[b-x69esb1yx8] {
        display: none;
    }

    /* This used to hide .nav-actions .btn-solid on phones, back when the bar held two
       buttons and the wider one could be dropped to save room. There is only one button
       now, so hiding it would leave the bar with nothing on the right at all. It stays,
       just tighter: at 355px of layout the brand takes 118px and this takes about 150px,
       inside 307px of container. */
    .nav-contact[b-x69esb1yx8] {
        padding: 9px 14px;
        gap: 6px;
    }

    /* Anchors the button to the burger instead of letting `space-between` park it in the
       middle of the bar. Below the breakpoint the row has exactly three visible boxes —
       brand, this, burger — so space-between spread them evenly and the button ended up
       floating with dead air on both sides. That got worse the moment the brand shrank to
       a 32px mark at ≤480 (86px of freed space went straight into those gaps), so: brand
       hard left, button and burger together hard right. An auto margin takes the free
       space before space-between gets to distribute any of it. */
    .nav-actions[b-x69esb1yx8] {
        margin-left: auto;
    }

    .footer-inner[b-x69esb1yx8] {
        flex-direction: column;
        text-align: center;
    }

    .download[b-x69esb1yx8] {
        padding: 48px 0;
    }

    /* ── AGEN, rebuilt 2026-07-21 ──
       Everything above the phone breakpoint assumes room for two columns. None of
       this section has any. */

    /* ── AGEN, phone ── */
    .agen-chapter[b-x69esb1yx8] {
        padding: 0 0 44px;
    }

    .agen-cover-title[b-x69esb1yx8] {
        font-size: 26px;
        max-width: none;
    }

    .agen-pick[b-x69esb1yx8] {
        padding: 18px;
    }

    .agen-facts[b-x69esb1yx8] {
        gap: 12px 24px;
    }

    .agen-facts strong[b-x69esb1yx8] {
        font-size: 22px;
    }

    .agen-bench[b-x69esb1yx8] {
        padding: 24px 0 40px;
    }

    .agen-bench-grid[b-x69esb1yx8] {
        grid-template-columns: 1fr;
    }

    .agen-map[b-x69esb1yx8] {
        min-height: 260px;
        border-right: none;
        border-bottom: 1px solid var(--l-border);
    }

    .agen-list-pane[b-x69esb1yx8] {
        min-height: 0;
    }

    /* The cap STAYS on at phone/tablet width (Susanti, 7/30) — reversing the earlier
       "no inner scroller on a phone" rule, which uncapped the list here.

       What forced the reversal: uncapped, the list is as tall as the province is long,
       and the province picker does not bound that. Jawa Timur is 38 regencies ≈ 1800px,
       Jawa Tengah 35, Sumatera Utara 33 — around two and a half phone screens of nothing
       but rows, with the result bar and the whole rest of the section pushed below it.
       At 768px this also catches iPad portrait, which is exactly 768px wide.

       The cost is the one the old comment named and it is real: a swipe that starts on
       the list scrolls the list, not the page. We are taking it because a reader who
       flicks slightly off the list still moves the page, whereas a 1800px wall has no
       way out but to keep scrolling past it. overscroll-behavior on the base rule keeps
       that inner scroll from chaining once the list bottoms out. */
    .agen-list[b-x69esb1yx8] {
        max-height: 396px;
        overflow-y: auto;
    }

    .agen-result[b-x69esb1yx8] {
        flex-direction: column;
        align-items: stretch;
        gap: 14px;
    }

    .agen-result .btn-lg[b-x69esb1yx8] {
        width: 100%;
        justify-content: center;
    }

    .agen-why[b-x69esb1yx8] {
        grid-template-columns: 1fr;
        gap: 12px;
    }

    .agen-why-card[b-x69esb1yx8] {
        padding: 18px;
    }

    .agen-two[b-x69esb1yx8] {
        grid-template-columns: 1fr;
        gap: 28px;
    }

    .agen-anchor[b-x69esb1yx8] {
        padding: 18px 16px;
    }

    /* Label above its own bar — a 130px name column plus a bar plus a figure cannot
       share a phone row without crushing all three. */
    .agen-anchor-row[b-x69esb1yx8] {
        grid-template-columns: 1fr auto;
        gap: 6px 12px;
    }

    .agen-anchor-bar[b-x69esb1yx8] {
        grid-column: 1 / -1;
        order: 3;
    }

    .agen-sim[b-x69esb1yx8] {
        padding: 18px 16px;
    }

}

@media (max-width: 480px) {
    /* ── The brand swaps to the mark alone (7/30) ──
       The other half of the pair declared beside .nav-brand at the top of this file.
       Not a style choice: at 360px the bar's six pieces added up to exactly 360px
       (24 + 118 brand + 16 + 126 contact + 12 + 40 burger + 24), and the word was 76
       of the brand's 118. The mark alone is 32, so the row goes from zero slack to
       86px of it — which is what keeps the contact button and the burger on one line
       on the narrowest phone we support.

       480 rather than the 768px phone breakpoint: a tablet bar has room to spare for
       the word, and this is only meant to fire where the row is actually tight. */
    .nav-logo[b-x69esb1yx8] {
        display: block;
    }

    .nav-wordmark[b-x69esb1yx8] {
        display: none;
    }

    .hero-title[b-x69esb1yx8] {
        font-size: 28px;
    }

    .hero-cta[b-x69esb1yx8] {
        flex-direction: column;
    }

    .hero-cta .btn-lg[b-x69esb1yx8] {
        width: 100%;
        justify-content: center;
    }

    .cta-buttons[b-x69esb1yx8] {
        flex-direction: column;
    }

    .cta-buttons .btn-lg[b-x69esb1yx8] {
        width: 100%;
        justify-content: center;
    }
}

/* ====== FITUR LOCK — vertical scroll becomes a horizontal ride ======
   The vivo product-page move: the section snaps to fill the screen, the reader's
   vertical scroll slides the six claims HORIZONTALLY past them, and when the last
   one arrives the page lets go and scrolls on.

   Mechanics: .showcase grows to 340vh — that extra height is the scroll distance
   the ride consumes. .showcase-pin sticks inside it for that whole distance, and
   the track's translateX is a pure function of how far through the 340vh the
   reader is (animation-range: contain), so scrubbing back rides the cards back.
   -100% + 100vw = "right edge of the track reaches the right edge of the screen".

   AFTER the responsive blocks ON PURPOSE: the 768px block re-declares .showcase
   padding at equal specificity, and these rules must win the cascade on phones too
   (the ride is verified at 390px — one card at a time).

   Same two guards as every scrub on this page. Without support (Safari/Firefox)
   or with reduced motion, NONE of this applies and the section stays exactly
   what it is above: a scroll-snapping swipeable strip, whose arrows/dots/auto-
   advance landing-motion.js only wires up in that fallback (it checks the same
   two conditions and stands down when the lock is active). */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .showcase[b-x69esb1yx8] {
            height: 340vh;
            padding: 0;
            /* TWO NAMES, and both are required. --jack drives the ride below;
               --sec-fitur is what lights "Fitur" in the navbar. `view-timeline` is a
               shorthand, so declaring one here would silently drop the other — the
               base declaration next to the navbar rules cannot survive this one. */
            view-timeline: --jack block, --sec-fitur block;
            /* NO scroll-snap-align here, ever. The first version paired this
               with proximity snap on <html>, and Chromium treated the whole
               340vh area as "snap to my start edge" from anywhere inside it —
               every wheel gesture in the section was eaten and the page
               appeared frozen. The snap-into-place moment is a scroll-end
               nudge in landing-motion.js instead. */
        }

        .showcase-pin[b-x69esb1yx8] {
            position: sticky;
            top: 0;
            height: 100vh;
            height: 100svh;
            display: flex;
            flex-direction: column;
            justify-content: center;
            overflow: clip;
            /* Top clears the sticky navbar (64px) AND leaves air under it — at a
               bare 64 the eyebrow sat 9px below the bar and read as attached to
               it. The bottom keeps the ride's progress bar off the screen edge.
               Nothing overflows when this grows: the strip below is the flexible
               one, so extra padding comes out of the card height. */
            padding: 92px 0 32px;
        }

        /* ── Filling the pinned screen (2026-07-26) ──
           This section takes the WHOLE screen for the length of the ride, so
           anything it leaves empty is empty at full size. It was leaving about
           half: measured before this pass, the content filled 47% of the screen
           at 820×1180, 62% at 1440×900 and 67% at 390×844, with the rest dead
           space above the eyebrow and below the cards.
           The fix is not more furniture — it is letting what is already here take
           the room: the header block keeps its natural height, and the card strip
           takes EVERYTHING that is left (flex:1), which each card spends on its
           picture half (.sc-art, itself flex:1). Heights are therefore driven by
           the screen, never hardcoded — the same rules hold on a 390px phone and
           a 1180px-tall tablet. */
        /* A `.showcase-pin .section-sub { margin-bottom: 0 }` lived here: the section
           had a sub-heading whose 48px of normal-flow spacing was eating into the
           pictures. The sub-heading itself was deleted on 7/28 (it announced the cards
           instead of being one), so the rule went with it — the heading block is now
           just the eyebrow and the h2. */
        .sc[b-x69esb1yx8] {
            flex: 1 1 auto;
            min-height: 0;
            margin-top: 26px;
            display: flex;
            flex-direction: column;
        }

        /* The strip is DRIVEN now, not scrolled — so the viewport must not be a
           scroll container AT ALL. Both axes, shorthand, on purpose: a lone
           `overflow-x: clip` next to the base rule's `overflow-y: hidden`
           computes back to hidden (clip cannot pair with a scrolling value), and
           that left a live horizontal scroller with 1500px of overflow under the
           cursor — the browser latched wheel gestures onto it and page scrolling
           stalled whenever the pointer sat on a card (reported 2026-07-21).
           clip on both axes is not a scroll container, so the wheel passes
           straight through to the page. */
        .sc-viewport[b-x69esb1yx8] {
            overflow: clip;
            scroll-snap-type: none;
            padding-bottom: 8px;
            /* A flex box so the track — and through it every card — inherits the
               strip's full height without a percentage chain. */
            flex: 1 1 auto;
            min-height: 0;
            display: flex;
        }

        .sc-track[b-x69esb1yx8] {
            width: max-content;
            /* flex-shrink MUST be 0: as a flex item, a max-content track inside a
               narrower box would otherwise be squeezed down to the viewport width
               and the ride would have nothing left to travel. */
            flex: 0 0 auto;
            align-items: center;
            /* 🚨 THE RIDE STARTS AT ZERO — the first card against the page's own gutter, so
               it is WHOLE. Removed 7/30, and the arithmetic is why.

               From 7/28 this was
                   (100vw − card) / 2 − pad − card − gap
               meant to land card TWO in the middle of the screen and pull card 1 into a
               balanced partial peek. Substitute the pad, which is itself
               (100vw − landing-max) / 2 + gap above 1120px, and the whole thing collapses to
               a CONSTANT −28px at every width above 1120 — it never delivered the balance it
               was added for, because 184px of left margin became 156px. Below 1120 the pad
               floors at the 24px page gutter while the (100vw − card) / 2 term keeps
               shrinking, so the offset runs away negative: measured, the first card was cut
               off by 52px at 1024, 84px at 960, 114px at 901 and 154px at 820 — her report.

               So it bought nothing where it worked and clipped the first card everywhere
               else. The keyframe keeps a `var(--jack-start, 0px)` fallback and nothing
               declares it; if a start offset is ever wanted again it must be FLOORED at
               `gap − pad`, which is the point where it begins eating the gutter. */
            animation: jack-slide-b-x69esb1yx8 linear both;
            animation-timeline: --jack;
            animation-range: contain 0% contain 100%;
        }

        /* Full height of the strip, capped: past the cap a card stops looking like a
           card and starts looking like a column, and the drawing inside it is stranded
           in the middle of a tall wash. Above it the strip simply centres the cards
           (align-items above), which reads as breathing room.

           A FLAT 620px until 7/28, when the cards came down to 360 wide. The two
           numbers are one decision: at 620 tall and 360 wide a card is a 1:1.7 column,
           which is the exact shape the cap exists to prevent.

           It is now proportional, and that is the second half of the fix. A flat cap
           has to answer two opposite screens with one number: a 900-tall laptop, where
           620px was 68% of everything the reader could see, and a 1180-tall tablet,
           where the same 620 left the section looking half-empty. Half a screen is the
           honest answer to both — measured after the change: 450px at 1440×900 (50%),
           520px at 820×1180 (44%), 400px at 1024×768 (52%). The floor and ceiling stop
           it degenerating: below 400 the picture is a stripe, above 520 the column
           shape comes back. */
        .sc-slide[b-x69esb1yx8] {
            height: min(100%, clamp(400px, 50vh, 520px));
        }

        /* The 190px floor is the SWIPEABLE fallback's size, where a card has no
           height to inherit. Here the height comes from the screen, and a floor
           can only push the text out of a card that is already short. */
        .sc-art[b-x69esb1yx8] {
            min-height: 0;
        }

        /* ── Short screens ──
           Everything above assumes the screen can hold a header AND a picture AND
           a paragraph. A landscape phone (390px tall) cannot: measured at 844×390,
           the header alone left the cards 25px, i.e. nothing. So the layout gives
           things up in the order they can be spared, and it gives them up in two
           steps rather than one so an ordinary short laptop window (~640px, a
           1366×768 screen once browser chrome is off) keeps its pictures. */
        @media (max-height: 700px) {
            .showcase-pin[b-x69esb1yx8] { padding: 76px 0 24px; }

            /* This tier used to start by hiding the sub-heading — the cheapest thing on
               the screen to lose. There is no sub-heading to hide since 7/28, so the
               tier now begins at the paddings. */
            .sc[b-x69esb1yx8] { margin-top: 18px; }
            .jack-progress[b-x69esb1yx8] { margin-top: 18px; }
        }

        @media (max-height: 480px) {
            /* Below this there is no room for a picture at all — a 60px-tall wash
               is not a drawing, it is a stripe. The cards go back to being text,
               which is exactly what this section was before the pictures, and the
               ride itself is untouched. */
            .sc-art[b-x69esb1yx8] { display: none; }
            .showcase-pin[b-x69esb1yx8] { padding: 72px 0 16px; }
            .showcase-pin .section-eyebrow[b-x69esb1yx8] { display: none; }
            .sc[b-x69esb1yx8] { margin-top: 12px; }
            .jack-progress[b-x69esb1yx8] { margin-top: 14px; }
            .sc-body[b-x69esb1yx8] { padding: 18px 22px 20px; }
        }

        /* Arrows and dots belong to the swipeable fallback; the ride has the
           progress bar below instead. */
        .sc-controls[b-x69esb1yx8] { display: none; }

        .jack-progress[b-x69esb1yx8] { display: block; }

        .jack-progress i[b-x69esb1yx8] {
            animation: bar-grow-b-x69esb1yx8 linear both;
            animation-timeline: --jack;
            animation-range: contain 0% contain 100%;
        }
    }
}

/* 🚨 THE LAST CARD LANDS AGAINST THE RIGHT GUTTER, NOT CENTRED — and that was tried and
   rejected on 7/30. Ending the ride with the last card centred (an extra
   `(100vw − card)/2 − pad` of travel) left half a screen of white to its right: "terlalu
   banyak white space di sebelah kanannya". Flush against --sc-pad is the least empty ending
   available, and it mirrors the left gutter the first card starts behind.

   THE THING THAT ACTUALLY NEEDED FIXING WAS A DIFFERENT BAR. Her "progress bar bagian fitur"
   is the rail under the FITUR NAV LINK, not `.jack-progress` under the strip — that one has
   always been in step with the cards (both read `--jack` over `contain 0% contain 100%`, and
   measured identical at every step). The nav rail runs on the SECTION's timeline instead, so
   its fill had to be pointed at the ride's range; see .nav-link-fitur.

   Fallbacks on the var(): outside the locked block --jack-start is not declared, and one
   unresolved var() invalidates the whole transform, not just its own term. */
@keyframes jack-slide-b-x69esb1yx8 {
    from { transform: translateX(var(--jack-start, 0px)); }
    to   { transform: translateX(calc(-100% + 100vw)); }
}


/* ── Back to top ──
   The agen "Sebelum memutuskan" chapter is a long flat read now that its answers are
   stacked instead of tabbed, so the page earns a way back up. Fixed bottom-right.
   Hidden at the top and revealed once the reader has scrolled well past the first
   screen, via a scroll() timeline on the root. Where scroll-driven animation is
   unsupported it simply stays visible — a permanently-shown back-to-top is fine. */
.landing-top[b-x69esb1yx8] {
    position: fixed;
    right: clamp(16px, 3vw, 32px);
    bottom: clamp(16px, 3vw, 32px);
    z-index: 60;
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    /* --face-solid: the chevron inside is white. */
    background: var(--face-solid);
    color: #FFFFFF;
    border-radius: 50%;
    box-shadow: 0 8px 24px rgba(var(--face-accent-rgb), 0.35);
    text-decoration: none;
}

@media (hover: hover) {
    .landing-top:hover[b-x69esb1yx8] { background: var(--face-accent-deep); }
}

@supports (animation-timeline: scroll()) {
    .landing-top[b-x69esb1yx8] {
        opacity: 0;
        visibility: hidden;
        animation: landing-top-in-b-x69esb1yx8 linear both;
        animation-timeline: scroll(root block);
        /* appear after ~one screen of scrolling, then stay */
        animation-range: 700px 900px;
    }
}

@keyframes landing-top-in-b-x69esb1yx8 {
    to { opacity: 1; visibility: visible; }
}

/* ====== FITUR ON A PHONE — a plain list, no ride ========================
   Susanti, 2026-07-28: on mobile the section should not spend vertical scrolling
   to drive a horizontal one — "cukup list from top to bottom seperti biasa".

   She is describing a real cost, not a preference. The locked ride buys a phone
   reader nothing it does not already have: a phone shows ONE card at a time either
   way, so the pin adds ~2 extra screens of scrolling and takes away the plainest
   thing a phone is good at — flicking down a list. It also removes the ride's one
   failure mode on touch, where a vertical flick that starts on a card has to be
   handed to the page.

   LAST BLOCK IN THE FILE ON PURPOSE. It cancels rules from the FITUR LOCK block
   (which is itself deliberately placed after the responsive blocks), and every rule
   here is at the same specificity as the one it overrides, so source order is what
   settles it. Moving this above that block silently restores the ride. */
@media (max-width: 768px) {
    /* The 340vh that WAS the ride's scroll distance. Without it the section is as
       tall as its content, which is the whole point. */
    .showcase[b-x69esb1yx8] {
        height: auto;
        padding: 56px 0;
    }

    /* Nothing pins, so the wrapper stops being a screen and goes back to being a
       plain box. overflow:visible matters: `clip` on a static box would cut the
       cards' shadows. */
    .showcase-pin[b-x69esb1yx8] {
        position: static;
        height: auto;
        display: block;
        overflow: visible;
        padding: 0;
    }

    /* The heading block is the eyebrow and the h2 only since 7/28 (the sub-heading was
       deleted), so the air under it belongs to the strip rather than to a paragraph
       that no longer exists. */
    .sc[b-x69esb1yx8] {
        display: block;
        margin-top: 24px;
    }

    /* Not a viewport any more — no clipping, no snapping, no flex height chain. */
    .sc-viewport[b-x69esb1yx8] {
        display: block;
        overflow: visible;
        scroll-snap-type: none;
        padding: 0;
    }

    /* The strip becomes the list: one column, natural width, and the jack-slide
       animation cancelled — it would otherwise translate the whole column sideways. */
    .sc-track[b-x69esb1yx8] {
        display: flex;
        flex-direction: column;
        width: auto;
        gap: 16px;
        padding-inline: var(--landing-gap);
        animation: none;
    }

    .sc-slide[b-x69esb1yx8] {
        flex: none;
        width: 100%;
        height: auto;
        max-height: none;
        scroll-snap-align: none;
    }

    /* The locked mode zeroes this floor so a short screen can squeeze the picture;
       in a list every card sizes itself, so the floor comes back. */
    .sc-art[b-x69esb1yx8] {
        flex: none;
        min-height: 168px;
    }

    /* Both belong to the two modes this replaces: arrows and dots drive a strip the
       reader can no longer swipe, and the progress bar measures a ride that no
       longer runs. landing-motion.js still un-hides .sc-controls in its fallback
       path — this is what keeps them off screen when it does. */
    .sc-controls[b-x69esb1yx8],
    .jack-progress[b-x69esb1yx8] {
        display: none;
    }

    /* The same script dims every slide but the "active" one. In a vertical list that
       would leave five of six cards greyed out at all times. */
    .sc.is-enhanced .sc-slide[b-x69esb1yx8],
    .sc.is-enhanced .sc-slide.is-active[b-x69esb1yx8] {
        opacity: 1;
        transform: none;
        box-shadow: var(--l-shadow);
    }
}
