/* ===========================================================================
   Px POS — public landing page (served at /)
   ---------------------------------------------------------------------------
   Standalone. This page does NOT load vendor.css, AdminLTE, Bootstrap or the
   Tailwind build — it shares only the logo artwork with the admin product.
   Every class is prefixed `pl-` so nothing can collide with the app shell or
   with the previous landing page's `lp-` styles (kept at
   resources/views/landing/index-old.blade.php).

   DIRECTION
   ---------
   The brand colour is taken from the Px mark itself, not from a framework
   default. The icon is a gradient, so the palette is a gradient: cyan
   #06B0FE at the highlight, azure #0D6EF3 through the body, #0E4CE7 at the
   deep edge, #0B2EA5 in the shadow. That ramp is the whole colour story —
   there is no second accent hue on this page. Anywhere you want to "add a
   colour", the answer is a hairline, a tint of the ramp, or nothing.

   Four grounds, in this order down the page:

     1. CANVAS  a pale wash of the brand ramp. Used exactly twice — behind the
        hero + proof strip at the top, and behind the final CTA at the bottom.
        They bookend the page.
     2. WHITE   everything between them.
     3. INK     #071B4D, the deep end of the ramp. No section is built on it;
        it lives in headings, shadows, and the one dark thing on the page — the
        product screen floating in the AI band, which is what makes that
        section land.
     4. BAND    the canvas wash again, faded to white at both edges, for the AI
        section. A fourth ground would have been a fourth thing to justify.

   TYPE
   ----
   Manrope for everything read as language — display and body alike, separated
   by weight, not by a second family. Manrope's high x-height and even colour
   sit correctly beside Noto Sans Bengali, which is not optional here: the
   nav, hero, proof strip and final CTA are all bilingual and Manrope carries
   no Bengali glyphs.

   IBM Plex Mono is the utility face and it is the page's signature. It is
   used only where the content is a readout rather than a sentence — section
   eyebrows, the proof counts, plan quota rows and the pricing totals. A POS
   is a terminal and a receipt printer; the numbers on this page are set the
   way that machine sets them. Do not use it for prose.

   BILINGUAL WIDTH
   ---------------
   Switching to Bengali must not move the layout. Anything in the navbar whose
   label changes with the language is wrapped in .pl-swap, which stacks both
   strings in one grid cell and hides the inactive one with `visibility`
   rather than `display` — so the element is always as wide as the LONGER of
   the two languages and the bar never reflows. See section 4b.
   =========================================================================== */

/* ---------------------------------------------------------------- 1. tokens */

:root {
    /* ---- brand ramp, sampled from public/img/logo/pxpos-mark.png ---- */
    --pl-brand-bright: #06b0fe;   /* the cyan highlight at the top of the mark */
    --pl-brand: #0d6ef3;          /* the mark's body — primary interactive     */
    --pl-brand-deep: #0e4ce7;     /* the deep edge — hover / press             */
    --pl-brand-ink: #071b4d;      /* the shadow, taken darker — headings and depth */

    --pl-brand-50: #eff6ff;
    --pl-brand-100: #dbeafe;
    --pl-brand-200: #bcdcff;
    --pl-brand-300: #8ec6ff;

    /* The mark's own gradient, reused wherever the brand needs to be the
       brand rather than just a blue: the primary button, the eyebrow dot,
       the check chips, the section rules. */
    --pl-grad: linear-gradient(135deg, var(--pl-brand-bright) 0%, var(--pl-brand) 46%, var(--pl-brand-deep) 100%);

    --pl-bg: #ffffff;
    --pl-soft: #f4f8ff;
    --pl-surface: #ffffff;
    --pl-ink: #0a1124;
    --pl-ink-2: #2b3a55;
    --pl-muted: #5a6b85;
    --pl-muted-2: #8b9ab1;
    --pl-line: #e4eaf3;
    --pl-line-2: #cfd9e8;

    /* On the ink ground */
    --pl-on-ink: #b9c9e6;
    --pl-on-ink-strong: #ffffff;
    --pl-on-ink-mute: #7d92b8;
    --pl-on-ink-line: rgba(255, 255, 255, .12);
    --pl-on-ink-card: rgba(255, 255, 255, .045);

    /* ---- families ---- */
    --pl-font-display: 'Manrope', 'Noto Sans Bengali', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --pl-font-body: 'Manrope', 'Noto Sans Bengali', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --pl-font-bn: 'Noto Sans Bengali', 'Hind Siliguri', 'Manrope', system-ui, sans-serif;
    /* Noto Sans Bengali trails the mono stack for one glyph only: Plex Mono
       has no taka sign, and every price on this page is "\09F3 2,499". */
    --pl-font-mono: 'IBM Plex Mono', 'Noto Sans Bengali', ui-monospace, 'SFMono-Regular', Menlo, monospace;

    /*
     * TYPE SCALE — every font-size on this page comes from this list. No rule
     * invents a raw rem value of its own.
     *
     *   3xs  11px  the language switch, the mono section index
     *   2xs  12px  mono eyebrows, badges, footer headings
     *   xs   13px  micro copy, plan footnotes, gallery captions
     *   sm   14px  captions, notes, secondary lists, nav links
     *   md   15px  dense copy inside cards, buttons, tabs
     *   base 16px  card body copy, large buttons
     *   lg   17px  page body
     *   xl   18px  h4
     */
    --pl-t-3xs: .6875rem;
    --pl-t-2xs: .75rem;
    --pl-t-xs: .8125rem;
    --pl-t-sm: .875rem;
    --pl-t-md: .9375rem;
    --pl-t-base: 1rem;
    --pl-t-lg: 1.0625rem;
    --pl-t-xl: 1.125rem;

    /* Headings run a step tighter than before at the top end — Manrope is
       wider than Outfit per character, so 64px hero copy overflowed its
       measure and read as shouting rather than as confidence. */
    --pl-t-h1: clamp(2.375rem, 4.6vw, 3.5rem);     /* 38 → 56px */
    --pl-t-h2: clamp(1.9rem, 3.6vw, 2.875rem);     /* 30 → 46px */
    --pl-t-h3: clamp(1.25rem, 1.7vw, 1.5rem);      /* 20 → 24px */
    --pl-t-h4: var(--pl-t-xl);                     /* 18px */
    --pl-t-lead: clamp(1rem, 1.15vw, 1.125rem);    /* 16 → 18px */
    --pl-t-price: clamp(2.25rem, 3.4vw, 2.75rem);  /* the plan amount, alone */

    /*
     * WEIGHTS — five, and only five. 800 exists for one job: the h1 and h2,
     * where Manrope at 700 is not quite dense enough to hold a 56px line.
     */
    --pl-w-regular: 400;
    --pl-w-medium: 500;
    --pl-w-semibold: 600;
    --pl-w-bold: 700;
    --pl-w-black: 800;

    --pl-lh-h1: 1.08;
    --pl-lh-h2: 1.1;
    --pl-lh-h3: 1.28;
    --pl-lh-h4: 1.38;
    --pl-lh-normal: 1.5;
    --pl-lh-relaxed: 1.62;

    /* Manrope sets tighter than Outfit already; these are correspondingly
       shallower. Carrying the old -.02em over crushed the display sizes. */
    --pl-ls-h1: -.028em;
    --pl-ls-h2: -.024em;
    --pl-ls-h3: -.016em;
    --pl-ls-h4: -.012em;
    --pl-ls-mono: .11em;

    --pl-maxw: 1180px;
    --pl-maxw-wide: 1300px;
    --pl-maxw-narrow: 760px;
    --pl-nav-h: 68px;
    --pl-gutter: clamp(1.15rem, 4vw, 2.25rem);

    /* One section rhythm for the whole page: every section opens on the same
       space above its head, so the eyebrow/h2 pair lands at the same distance
       from the section before it no matter which grounds happen to meet. */
    --pl-section-pad: clamp(3rem, 5.5vw, 4.75rem);

    --pl-r-sm: 8px;
    --pl-r-md: 12px;
    --pl-r-lg: 16px;
    --pl-r-xl: 22px;

    /* Brand-tinted shadows. A neutral grey shadow on this ground reads as
       dirt; tinting it toward the ramp keeps it looking like light. */
    --pl-sh-sm: 0 1px 2px rgba(10, 17, 36, .05), 0 1px 3px rgba(13, 110, 243, .05);
    --pl-sh-md: 0 4px 10px -2px rgba(10, 17, 36, .07), 0 2px 4px -2px rgba(13, 110, 243, .06);
    --pl-sh-lg: 0 14px 30px -8px rgba(10, 17, 36, .13), 0 4px 10px -4px rgba(13, 110, 243, .08);
    --pl-sh-xl: 0 34px 70px -20px rgba(7, 27, 77, .28), 0 10px 24px -10px rgba(13, 110, 243, .14);
    --pl-sh-brand: 0 6px 16px -4px rgba(13, 110, 243, .38);

    /* Motion. Two curves cover everything that arrives on the page:
       --pl-ease-out is a hard deceleration — fast off the mark, no bounce —
       for anything that should feel confident rather than playful.
       --pl-ease-spring overshoots and settles, reserved for the handful of
       moments that are explicitly a POP: the bubble images and the numbers
       landing. --pl-rise / --pl-blur are the one distance and one blur every
       "materialising" reveal in this file travels, so the whole page arrives
       at one speed rather than each section inventing its own. */
    --pl-ease-out: cubic-bezier(.19, 1, .22, 1);
    --pl-ease-spring: cubic-bezier(.34, 1.56, .64, 1);
    --pl-rise: 38px;
    --pl-blur: 14px;
}

/* ---------------------------------------------------------------- 2. reset */

:where(*, *::before, *::after) { box-sizing: border-box; }
html { scroll-padding-top: calc(var(--pl-nav-h) + 1.75rem); scroll-behavior: smooth; }
:where(body.pl) { margin: 0; }
:where(.pl h1, .pl h2, .pl h3, .pl h4, .pl p, .pl figure, .pl ul, .pl ol) { margin: 0; padding: 0; }
:where(.pl ul, .pl ol) { list-style: none; }
:where(.pl a) { color: inherit; text-decoration: none; }
:where(.pl img, .pl svg) { display: block; max-width: 100%; }
:where(.pl button) { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

body.pl {
    font-family: var(--pl-font-body);
    font-size: var(--pl-t-lg);
    font-weight: var(--pl-w-regular);
    line-height: var(--pl-lh-relaxed);
    color: var(--pl-ink);
    background: var(--pl-bg);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overflow-x: hidden;
}

.pl-skip {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 999;
    padding: .75rem 1.15rem;
    background: var(--pl-surface);
    border-radius: 0 0 var(--pl-r-md) 0;
    font-weight: var(--pl-w-semibold);
}
.pl-skip:focus { left: 0; }

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

/* ------------------------------------------------------------ 3. structure */

.pl-wrap {
    width: 100%;
    max-width: var(--pl-maxw);
    margin-inline: auto;
    padding-inline: var(--pl-gutter);
}
.pl-wrap--wide { max-width: var(--pl-maxw-wide); }

.pl-section { padding: var(--pl-section-pad) 0; }
.pl-section--tight { padding: clamp(2.75rem, 4.5vw, 4rem) 0; }

/* ----------------------------------------------------------------- 4. type */

.pl-h1 {
    font-family: var(--pl-font-display);
    font-size: var(--pl-t-h1);
    font-weight: var(--pl-w-black);
    line-height: var(--pl-lh-h1);
    letter-spacing: var(--pl-ls-h1);
    color: var(--pl-ink);
    text-wrap: balance;
}
/*
 * Type coloured by the brand rather than by the ink. The hero headline's key
 * phrase established it; every other piece of brand-blue text on the light
 * ground now uses the same mark gradient, so the page has one blue treatment
 * instead of two.
 *
 * Requires the element's own background to BE the gradient, so this cannot be
 * used on anything that paints a background tint of its own (the nav links,
 * the selected tab, the pill badges) — those keep flat brand blue.
 */
.pl-grad-text,
.pl-h1 em {
    font-style: normal;
    background: var(--pl-grad);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
}

.pl-h2 {
    font-family: var(--pl-font-display);
    font-size: var(--pl-t-h2);
    font-weight: var(--pl-w-black);
    line-height: var(--pl-lh-h2);
    letter-spacing: var(--pl-ls-h2);
    color: var(--pl-ink);
    text-wrap: balance;
}

.pl-h3 {
    font-family: var(--pl-font-display);
    font-size: var(--pl-t-h3);
    font-weight: var(--pl-w-bold);
    line-height: var(--pl-lh-h3);
    letter-spacing: var(--pl-ls-h3);
    color: var(--pl-ink);
}

.pl-h4 {
    font-family: var(--pl-font-display);
    font-size: var(--pl-t-h4);
    font-weight: var(--pl-w-bold);
    line-height: var(--pl-lh-h4);
    letter-spacing: var(--pl-ls-h4);
    color: var(--pl-ink);
}

.pl-lead {
    font-size: var(--pl-t-lead);
    line-height: var(--pl-lh-relaxed);
    color: var(--pl-muted);
    max-width: 62ch;
}

/*
 * Bengali has no uppercase and no tracking tradition: `text-transform` does
 * nothing to it but `letter-spacing` does real damage, because a Bengali
 * cluster is a matra and one or more consonants sitting on a single
 * headstroke, and tracking prises that stroke apart. Both are cancelled HERE,
 * on the base class, rather than context by context — a child's own
 * declaration beats an inherited value whatever its specificity, so this one
 * rule covers every mono readout, eyebrow, flag and column heading on the
 * page, including any added later.
 */
.pl-bn {
    font-family: var(--pl-font-bn);
    line-height: var(--pl-lh-relaxed);
    text-transform: none;
    letter-spacing: 0;
}

/* -------------------------------------------------------- 4a. bilingual copy */

/*
 * Every switchable string exists twice — [data-lang="en"] and
 * [data-lang="bn"] — and exactly one set is hidden. Only the HIDDEN side is
 * ever declared: an element that is showing keeps whatever display its own
 * rules give it, which a `display: block` here would flatten.
 *
 * .is-bn lives on <html>, set by an inline script in the head before first
 * paint, so a remembered Bengali choice never flashes English first.
 */
:root:not(.is-bn) [data-lang="bn"] { display: none; }
:root.is-bn [data-lang="en"] { display: none; }

/* -------------------------------------------------- 4b. width-stable swap */

/*
 * The navbar must not change width when the language does. .pl-swap stacks
 * both strings in a single grid cell, so the box is always as wide and as
 * tall as the LONGER of the two, and hides the inactive one with
 * `visibility` — which still reserves its space — instead of `display`.
 *
 * These selectors carry one more class than the global rules in 4a on
 * purpose; they have to out-rank them.
 */
.pl-swap {
    display: inline-grid;
    grid-template-areas: 'swap';
    align-items: center;
    justify-items: center;
}
.pl-swap > * { grid-area: swap; }

:root:not(.is-bn) .pl-swap > [data-lang="bn"],
:root.is-bn .pl-swap > [data-lang="en"] {
    display: block;
    visibility: hidden;
    pointer-events: none;
}
:root:not(.is-bn) .pl-swap > [data-lang="en"],
:root.is-bn .pl-swap > [data-lang="bn"] {
    display: block;
    visibility: visible;
}

/* ------------------------------------------------ 4c. Bengali fit */

/*
 * Bengali is a taller, wider script than Latin at the same nominal size: it
 * hangs a matra above the x-height and drops conjuncts below the baseline, so
 * a box measured for Manrope crowds it. Everything here is the same two
 * moves — step the size down a touch, open the leading — applied to the
 * places on this page where type is set tight, in mono, or both.
 *
 * The base .pl-bn rule above has already cancelled case and tracking. These
 * only handle SIZE and LEADING, which do have to be judged per context.
 */

/* The mono readouts. Plex Mono has no Bengali glyphs at all — the swap to
   Noto Sans Bengali is not a preference, it is the difference between a
   readout and a row of tofu — and the Bengali face runs optically larger, so
   each of these steps back down to its slot. They are single-line labels, so
   the leading is pinned rather than left at .pl-bn's relaxed 1.62. */
.pl-fit__label .pl-bn,
.pl-price__flag .pl-bn,
.pl-price__unit .pl-bn,
.pl-toggle__label .pl-bn,
.pl-toggle__save .pl-bn {
    font-family: var(--pl-font-bn);
    font-size: var(--pl-t-2xs);
    line-height: 1.15;
}

/* The trade readout is hard right on its row and must not wrap; it is the
   tightest slot on the page and takes the largest step down. */
.pl-trade__tag .pl-bn {
    font-family: var(--pl-font-bn);
    font-size: var(--pl-t-3xs);
    line-height: 1.15;
}

/* The lens caption inside the ring. Its English is sized in cqw against the
   stage, so the Bengali has to be too, or it stops scaling with the
   composition; the floor is the same idea as the English one — Bengali below
   about 10px is a texture, not a word. */
.pl-orbit__line .pl-bn {
    font-family: var(--pl-font-bn);
    font-size: max(10px, .99cqw);
    line-height: 1.35;
}

.pl-ai__note .pl-bn {
    font-family: var(--pl-font-bn);
    font-size: var(--pl-t-xs);
    line-height: 1.65;
}

/*
 * Where .pl-bn lands on the SAME element as a mono class rather than inside
 * one, the base reset above is not enough — an inherited value loses to a
 * child's own declaration, but a competing rule on the same element does not,
 * and .pl-foot__stamp / .pl-proof__n are both declared later in this file.
 * These carry the extra class to out-rank them.
 */
.pl-foot__stamp.pl-bn {
    font-family: var(--pl-font-bn);
    font-size: var(--pl-t-2xs);
    text-transform: none;
    letter-spacing: 0;
}

/* The proof counts are a readout in Bengali numerals — ১,২০০+ and ১.৮ কোটি+ —
   so they need the Bengali face, and the tight negative tracking that pulls
   Latin figures together has to come off. */
.pl-proof__n.pl-bn {
    font-family: var(--pl-font-bn);
    letter-spacing: 0;
}

/* Section heads. The h2 is set in the display face with negative tracking at
   a size chosen for Latin caps; Bengali at that size and leading collides
   with itself, the way the hero's h1 already accounts for. */
.pl-h2 .pl-bn {
    font-size: .93em;
    line-height: 1.34;
}

/* "কোটেশনে" stands where a figure stands in the other column, and it is set
   in the display face at price size — so the negative tracking that flatters
   Latin caps there has to come off, and the line box has to stay the price
   row's. */
.pl-price__amt.pl-bn {
    font-family: var(--pl-font-bn);
    line-height: 1;
    letter-spacing: 0;
}
.pl-price__amt--word.pl-bn {
    font-size: .88em;
}

/* --------------------------------------------------------- 4d. eyebrow */

/*
 * The section label, and the first place the mono face appears. Uppercase,
 * tracked out, small — it reads as a machine label above a human sentence,
 * which is the whole idea. The leading rule is drawn here so no markup
 * carries it.
 */
.pl-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: .6rem;
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-2xs);
    font-weight: var(--pl-w-semibold);
    letter-spacing: var(--pl-ls-mono);
    text-transform: uppercase;
    line-height: 1;
    background: var(--pl-grad);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    margin-bottom: .95rem;
}
/* Bengali has no uppercase and no tracking tradition; forcing both turns it
   into noise. It gets the body face at a readable size instead. */
.pl-eyebrow .pl-bn {
    font-family: var(--pl-font-bn);
    font-size: var(--pl-t-sm);
    text-transform: none;
    letter-spacing: 0;
}

/* The hero's eyebrow is a pill rather than a bare label: it sits on the
   canvas with nothing above it and needs an edge to belong to. Its own white
   ground is the one background it can have, so this is the single eyebrow
   that keeps flat brand blue instead of the clipped gradient. */
.pl-eyebrow--pill {
    padding: .4rem .95rem .4rem .5rem;
    border: 1px solid var(--pl-brand-200);
    border-radius: 999px;
    background: rgba(255, 255, 255, .78);
    -webkit-background-clip: border-box;
            background-clip: border-box;
    color: var(--pl-brand);
    backdrop-filter: blur(6px);
    box-shadow: var(--pl-sh-sm);
}
.pl-eyebrow--pill::before {
    content: '';
    width: 8px;
    height: 8px;
    flex: none;
    border-radius: 50%;
    background: var(--pl-grad);
}

/* --------------------------------------------------------- 4d. section head */

/*
 * Every section head on this page is centred and holds a narrow measure.
 * Centring is not decoration here: each section below the hero introduces a
 * grid, and a left-aligned title over a symmetrical grid reads as a mistake.
 */
.pl-head {
    max-width: var(--pl-maxw-narrow);
    margin-inline: auto;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.pl-head .pl-h2 { max-width: 22ch; }
.pl-head--start { text-align: left; align-items: flex-start; margin-inline: 0; }
.pl-head--start .pl-h2 { max-width: 20ch; }

/*
 * There is no .pl-subhead any more. It existed so a section holding two
 * titles could set the second one a size step down — the only way to say
 * "still the same section" when nothing but a margin separated them. Section
 * 02 now changes GROUND between its two halves instead, which says it better,
 * so both of its titles are full .pl-h2. See section 9b.
 */

/* -------------------------------------------------------------- 5. buttons */

/*
 * Two fills and nothing else: the brand gradient for the action you want
 * taken, white with a hairline for everything beside it. Both are full pills.
 * Every size sets an explicit HEIGHT rather than vertical padding, because
 * Bengali glyphs are taller than Latin ones and padding alone would make the
 * navbar grow by a few pixels on every language switch.
 */
.pl-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    height: 46px;
    padding: 0 1.4rem;
    border: 1px solid transparent;
    border-radius: 999px;
    font-family: var(--pl-font-display);
    font-size: var(--pl-t-md);
    font-weight: var(--pl-w-bold);
    letter-spacing: -.006em;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition: background .18s ease, border-color .18s ease, color .18s ease,
                box-shadow .18s ease, transform .18s ease;
}
.pl-btn:active { transform: translateY(1px); }

.pl-btn--primary {
    background: var(--pl-grad);
    background-size: 140% 140%;
    background-position: 0% 50%;
    color: #fff;
    box-shadow: var(--pl-sh-brand);
}
.pl-btn--primary:hover {
    background-position: 100% 50%;
    box-shadow: 0 10px 24px -6px rgba(13, 110, 243, .5);
}

.pl-btn--ghost {
    background: #fff;
    border-color: var(--pl-line-2);
    color: var(--pl-ink);
    box-shadow: var(--pl-sh-sm);
}
.pl-btn--ghost:hover { border-color: var(--pl-brand); color: var(--pl-brand-deep); }

.pl-btn--outline {
    background: transparent;
    border-color: var(--pl-line-2);
    color: var(--pl-ink);
}
.pl-btn--outline:hover { border-color: var(--pl-brand); background: rgba(255, 255, 255, .7); }

/* On the ink ground the ghost inverts rather than staying white. */
.pl-btn--on-ink {
    background: rgba(255, 255, 255, .08);
    border-color: var(--pl-on-ink-line);
    color: #fff;
}
.pl-btn--on-ink:hover { background: rgba(255, 255, 255, .15); border-color: rgba(255, 255, 255, .3); }

.pl-btn--lg { height: 54px; padding: 0 1.75rem; font-size: var(--pl-t-base); }
.pl-btn--sm { height: 40px; padding: 0 1.15rem; font-size: var(--pl-t-sm); }
.pl-btn--block { width: 100%; }

/* The trailing badge on the hero's pill buttons: the label, then a circular
   arrow in the inverse of the button's own colours. */
.pl-btn--pill { padding-right: .4rem; gap: .75rem; }
.pl-btn--pill.pl-btn--lg { padding-right: .45rem; }
.pl-btn--pill.pl-btn--sm { padding-right: .3rem; gap: .55rem; }
.pl-btn__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex: none;
    border-radius: 50%;
    background: #fff;
    color: var(--pl-brand);
    transition: transform .2s ease;
}
.pl-btn--sm .pl-btn__badge { width: 28px; height: 28px; }
.pl-btn--pill:hover .pl-btn__badge { transform: translate(2px, -2px); }
.pl-btn--ghost .pl-btn__badge,
.pl-btn--outline .pl-btn__badge { background: var(--pl-ink); color: #fff; }

.pl-ctas { display: flex; flex-wrap: wrap; gap: .7rem; }

/* ---------------------------------------------------------------- 6. navbar */

/*
 * Three columns: brand left, links centred, actions right. The centre column
 * is centred against the BAR (grid 1fr auto 1fr), not against whatever the
 * brand and the actions happen to measure, so it stays put regardless.
 *
 * Combined with .pl-swap on every bilingual label, switching to Bengali
 * changes no width and no height anywhere in this bar.
 */
.pl-nav {
    position: fixed;
    inset: 0 0 auto;
    z-index: 60;
    padding-top: .7rem;
    transition: padding .25s ease;
}

.pl-nav__inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1rem;
    height: var(--pl-nav-h);
    padding: 0 .55rem 0 1.1rem;
    border: 1px solid transparent;
    border-radius: 999px;
    background: transparent;
    transition: background .25s ease, border-color .25s ease, box-shadow .25s ease,
                height .25s ease, backdrop-filter .25s ease;
}

/* On scroll the bar keeps its full width and only changes ground: a frosted
   sheet with a hairline. It never shrinks into a floating pill — a bar that
   changes width mid-scroll drags the eye away from the content. */
.pl-nav.is-stuck { padding-top: .5rem; }
.pl-nav.is-stuck .pl-nav__inner {
    height: 60px;
    background: rgba(255, 255, 255, .82);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-color: rgba(13, 110, 243, .1);
    box-shadow: 0 6px 24px -12px rgba(7, 27, 77, .28);
}

.pl-nav__brand { justify-self: start; display: flex; align-items: center; }
.pl-nav__brand img { height: 28px; width: auto; }

.pl-nav__links {
    display: flex;
    align-items: center;
    gap: .2rem;
    justify-self: center;
}
.pl-nav__links a {
    position: relative;
    display: inline-flex;
    align-items: center;
    height: 36px;
    padding: 0 .9rem;
    border-radius: 999px;
    font-size: var(--pl-t-sm);
    font-weight: var(--pl-w-semibold);
    letter-spacing: -.004em;
    color: var(--pl-ink-2);
    transition: color .16s ease, background .16s ease;
}
.pl-nav__links a:hover { color: var(--pl-brand); background: rgba(13, 110, 243, .07); }

/*
 * Active state. .is-active is set by the scroll spy in landing-pos.js — on
 * click, and again whenever a section passes under the bar. The underline is
 * drawn on a pseudo-element rather than with border-bottom so it can animate
 * from the centre out without moving the label. No fill: the underline alone
 * marks the current section.
 */
.pl-nav__links a.is-active { color: var(--pl-brand); }
.pl-nav__links a::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: 3px;
    width: 14px;
    height: 2px;
    border-radius: 2px;
    background: var(--pl-grad);
    transform: translateX(-50%) scaleX(0);
    transform-origin: center;
    transition: transform .22s cubic-bezier(.4, 0, .2, 1);
}
.pl-nav__links a.is-active::after { transform: translateX(-50%) scaleX(1); }

.pl-nav__actions {
    display: flex;
    align-items: center;
    gap: .55rem;
    justify-self: end;
}

/* ------------------------------------------------- 6a. language switch */

/*
 * Compact by construction: a 32px-tall segmented control with two cells of
 * identical fixed width, so the thumb travel is a single known number and
 * neither label can push the other. The mono face keeps "EN" reading as a
 * setting rather than as a word.
 */
.pl-lang {
    --pl-lang-cell: 40px;
    position: relative;
    display: inline-flex;
    align-items: center;
    height: 32px;
    padding: 3px;
    border: 1px solid var(--pl-line);
    border-radius: 999px;
    background: rgba(244, 248, 255, .9);
}
.pl-lang__thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: var(--pl-lang-cell);
    height: 24px;
    border-radius: 999px;
    background: var(--pl-grad);
    box-shadow: 0 2px 6px -1px rgba(13, 110, 243, .45);
    transition: transform .22s cubic-bezier(.4, 0, .2, 1);
    pointer-events: none;
}
:root.is-bn .pl-lang__thumb { transform: translateX(var(--pl-lang-cell)); }

.pl-lang button {
    position: relative;
    z-index: 1;
    width: var(--pl-lang-cell);
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-3xs);
    font-weight: var(--pl-w-semibold);
    letter-spacing: .05em;
    line-height: 1;
    /* Full ink, not muted: at 11px inside a 24px cell the inactive label was
       reading as disabled rather than as the other option. */
    color: var(--pl-ink-2);
    transition: color .18s ease;
}
.pl-lang button:hover { color: var(--pl-brand-deep); }
.pl-lang button[lang="bn"] {
    font-family: var(--pl-font-bn);
    font-size: var(--pl-t-2xs);
    letter-spacing: 0;
}
.pl-lang button[aria-pressed="true"] { color: #fff; }

/* The navbar CTA. .pl-swap already guarantees it is as wide as the longer
   label; min-width stops the English state from looking cramped next to it. */
.pl-nav__cta { min-width: 8.75rem; }

.pl-nav__toggle {
    display: none;
    width: 40px;
    height: 40px;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--pl-line-2);
    border-radius: 999px;
    background: #fff;
    color: var(--pl-ink);
}

.pl-nav__drawer {
    display: none;
    flex-direction: column;
    gap: .3rem;
    margin-top: .5rem;
    padding: .85rem;
    border: 1px solid var(--pl-line);
    border-radius: var(--pl-r-xl);
    background: #fff;
    box-shadow: var(--pl-sh-lg);
}
.pl-nav__drawer.is-open { display: flex; }
.pl-nav__drawer a {
    padding: .7rem .85rem;
    border-radius: var(--pl-r-md);
    font-size: var(--pl-t-base);
    font-weight: var(--pl-w-semibold);
}
.pl-nav__drawer a.is-active { color: var(--pl-brand); }
.pl-nav__drawer .pl-btn { margin-top: .4rem; }

/* ------------------------------------------------------------- 7. 01 hero */

.pl-hero {
    padding: calc(var(--pl-nav-h) + clamp(1.75rem, 4vw, 3rem)) 0 clamp(1.75rem, 3vw, 2.5rem);
    background: transparent;
}

.pl-hero__inner {
    display: grid;
    /* Slightly copy-weighted rather than 50/50: at equal widths the headline
       broke to four lines and the screenshot still was not readable. */
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: clamp(2.25rem, 4vw, 3.5rem);
    align-items: center;
}

/* Bengali runs longer than the English line and has no descender-free cap
   height to lean on, so the display size steps down and the leading opens. */
.pl-h1__bn {
    display: block;
    font-size: .8em;
    font-weight: var(--pl-w-bold);
    line-height: 1.38;
    letter-spacing: 0;
}

.pl-hero__sub { margin-top: 1.25rem; max-width: 52ch; }
.pl-hero__ctas { margin-top: 1.75rem; }

/*
 * The three reassurances under the CTA. One wrapping row of quiet micro-copy:
 * the entry price sits inside it as ordinary text rather than as a separate
 * typographic object, so the row reads as one line of reassurance.
 */
.pl-hero__micro {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem 1.4rem;
    margin-top: 1.6rem;
}
.pl-hero__micro li {
    display: flex;
    align-items: center;
    gap: .4rem;
    font-size: var(--pl-t-sm);
    font-weight: var(--pl-w-regular);
    color: var(--pl-muted);
}
.pl-hero__micro b {
    font-weight: var(--pl-w-medium);
    color: var(--pl-ink-2);
}

/*
 * In the hero the tick is a bare brand checkmark, not the filled chip the
 * pricing and AI lists use — three filled squares in a row read as a form.
 */
.pl-hero__micro .pl-tick {
    width: auto;
    height: auto;
    border-radius: 0;
    background: none;
    color: var(--pl-brand);
}
.pl-hero__micro .pl-tick svg { width: 14px; height: 14px; stroke-width: 2.6; }

/*
 * The check chip. A filled brand square with a white tick, from the pricing
 * reference — it reads at 18px where a bare stroked tick does not, and it is
 * the same object in the hero, the plan features and the AI benefits so the
 * page only teaches it once.
 */
.pl-tick {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    flex: none;
    border-radius: 5px;
    background: var(--pl-grad);
    color: #fff;
}
.pl-tick svg { stroke-width: 3; width: 11px; height: 11px; }
.pl-tick--sm { width: 16px; height: 16px; border-radius: 4px; }
.pl-tick--sm svg { width: 10px; height: 10px; }

/* ------------------------------------------------------ 7a. brand canvas */

/*
 * The pale wash of the brand ramp. Used exactly twice — behind the hero and
 * proof strip at the top of the page, and behind the final CTA at the bottom.
 * Everything between them is white, and the two tinted ends bookend the page.
 *
 * The tint is strongest across the whole top edge and dissolves to pure
 * #ffffff before the sheet ends, so the section under it continues with no
 * visible seam. isolation:isolate makes this a stacking context, which keeps
 * the ::before pinstripe above the background but behind all content.
 */
.pl-canvas {
    position: relative;
    isolation: isolate;
    background:
        radial-gradient(58% 44% at 12% 52%, rgba(6, 176, 254, .16), rgba(6, 176, 254, 0) 70%),
        radial-gradient(55% 40% at 88% 44%, rgba(13, 110, 243, .14), rgba(13, 110, 243, 0) 70%),
        linear-gradient(180deg,
            #dbe9ff 0%,
            #dfecff 16%,
            #e9f2ff 40%,
            #f3f8ff 62%,
            #fafcff 82%,
            #ffffff 95%,
            #ffffff 100%);
}
.pl-canvas::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: repeating-linear-gradient(90deg,
        rgba(255, 255, 255, .65) 0 1px,
        rgba(255, 255, 255, 0) 1px 92px);
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 45%, transparent 82%);
            mask-image: linear-gradient(180deg, #000 0%, #000 45%, transparent 82%);
}

/* The final CTA runs the same wash the other way up, so the page closes on
   the colour it opened with. */
.pl-canvas--flip {
    background:
        radial-gradient(58% 46% at 18% 46%, rgba(6, 176, 254, .18), rgba(6, 176, 254, 0) 70%),
        radial-gradient(55% 42% at 84% 56%, rgba(13, 110, 243, .15), rgba(13, 110, 243, 0) 70%),
        linear-gradient(0deg,
            #dbe9ff 0%,
            #dfecff 18%,
            #e9f2ff 42%,
            #f3f8ff 64%,
            #fafcff 84%,
            #ffffff 96%,
            #ffffff 100%);
}
.pl-canvas--flip::before {
    -webkit-mask-image: linear-gradient(0deg, #000 0%, #000 45%, transparent 82%);
            mask-image: linear-gradient(0deg, #000 0%, #000 45%, transparent 82%);
}

/* ------------------------------------------------------- 7b. proof strip */

/*
 * "Trusted by", rebuilt as one object instead of three stacked rows: a single
 * white card floating on the canvas, counts on the left set as mono readouts,
 * the logo row scrolling on the right.
 *
 * The card matters — the counts and the logos are one argument ("this many
 * businesses, and here are some of them"), and a card is what says so. The
 * marquee matters too: six static logos on a 1300px row leaves gaps that read
 * as "we only have six", while a moving strip reads as a continuing list.
 */
.pl-proof { padding: 0 0 clamp(2.5rem, 5vw, 4rem); }

.pl-proof__card {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: clamp(1.5rem, 3vw, 3rem);
    padding: clamp(1.25rem, 2vw, 1.6rem) clamp(1.25rem, 2.4vw, 2rem);
    border: 1px solid rgba(255, 255, 255, .9);
    border-radius: var(--pl-r-xl);
    background: rgba(255, 255, 255, .72);
    backdrop-filter: saturate(160%) blur(12px);
    -webkit-backdrop-filter: saturate(160%) blur(12px);
    box-shadow: var(--pl-sh-lg);
}

.pl-proof__stats {
    display: flex;
    align-items: center;
    gap: clamp(1.25rem, 2.4vw, 2.25rem);
}
.pl-proof__stats li {
    text-align: center;
}
.pl-proof__stats li + li {
    padding-left: clamp(1.25rem, 2.4vw, 2.25rem);
    border-left: 1px solid var(--pl-line);
}
.pl-proof__n {
    display: block;
    font-family: var(--pl-font-mono);
    font-size: clamp(1.5rem, 2.2vw, 1.875rem);
    font-weight: var(--pl-w-semibold);
    letter-spacing: -.03em;
    line-height: 1.1;
    background: var(--pl-grad);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    font-variant-numeric: tabular-nums;
}
/*
 * The count-up in public/js/landing-pos.js climbs the figure from zero and
 * adds this class the instant it reaches its target — a small overshoot
 * pulse so the number gets a definite landing instead of just stopping.
 */
.pl-proof__n.is-counted {
    animation: pl-count-land .5s var(--pl-ease-spring);
}
@keyframes pl-count-land {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.16); }
    100% { transform: scale(1); }
}
.pl-proof__l {
    display: block;
    margin-top: .3rem;
    /* The labels are short enough to hold one line, and a two-line label next
       to a one-line one makes the pair of figures look misaligned. Phones get
       the wrap back below. */
    white-space: nowrap;
    font-size: var(--pl-t-xs);
    font-weight: var(--pl-w-medium);
    line-height: 1.4;
    color: var(--pl-muted);
}

.pl-proof__logos { min-width: 0; }
.pl-proof__cap {
    text-align: center;
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-2xs);
    font-weight: var(--pl-w-medium);
    letter-spacing: var(--pl-ls-mono);
    text-transform: uppercase;
    color: var(--pl-muted-2);
    margin-bottom: .9rem;
}
.pl-proof__cap .pl-bn {
    font-family: var(--pl-font-bn);
    font-size: var(--pl-t-xs);
    text-transform: none;
    letter-spacing: 0;
}

/* The marquee. The track holds the logo list twice and translates by exactly
   -50%, so the loop is seamless. Duplicating in the markup rather than in JS
   keeps it working with scripting off. */
.pl-marquee {
    overflow: hidden;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.pl-marquee__track {
    display: flex;
    align-items: center;
    gap: clamp(2rem, 4vw, 3.25rem);
    width: max-content;
    animation: pl-marquee 34s linear infinite;
}
.pl-marquee:hover .pl-marquee__track { animation-play-state: paused; }
@keyframes pl-marquee { to { transform: translateX(-50%); } }

.pl-marquee img {
    height: 26px;
    width: auto;
    flex: none;
    filter: grayscale(1);
    opacity: .5;
    transition: filter .2s ease, opacity .2s ease;
}
.pl-marquee li:hover img { filter: none; opacity: 1; }
.pl-marquee__mark {
    font-weight: var(--pl-w-bold);
    font-size: var(--pl-t-base);
    letter-spacing: -.01em;
    white-space: nowrap;
    color: var(--pl-muted-2);
}

/* ------------------------------------------------------ 8. product frame */

.pl-frame {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--pl-line);
    border-radius: var(--pl-r-lg);
    background: #fff;
    box-shadow: var(--pl-sh-lg);
}
.pl-frame img { width: 100%; height: auto; }

.pl-frame--hero { border-radius: var(--pl-r-xl); box-shadow: var(--pl-sh-xl); }

.pl-frame__ph {
    display: grid;
    place-content: center;
    gap: .4rem;
    aspect-ratio: 16 / 10;
    padding: 1.5rem;
    text-align: center;
    background: var(--pl-soft);
    font-size: var(--pl-t-sm);
    color: var(--pl-muted-2);
}
.pl-frame__ph b { color: var(--pl-muted); font-family: var(--pl-font-mono); font-weight: var(--pl-w-medium); }

/* -------------------------- 9. 02 who it is for / what it changes for them */

/*
 * ONE section, one heading, one composition.
 *
 * "Built around the way your business works." and "Less administration. More
 * control." were two sections, and merging their markup was not enough — two
 * centred heads over two card grids still read as two sections however few
 * <section> tags they were wrapped in. They are two beats of one sentence, so
 * they are now one head, and what sits under that head is one object with two
 * columns rather than two grids in a queue:
 *
 *     YOUR TRADE                WHAT CHANGES
 *     six ledger rows    <->    four cards
 *
 * Read across and the section says "whatever of these six you are, these same
 * four things change". That sentence is the whole reason the two halves
 * belong together, and a two-column composition is the only arrangement that
 * lets a reader see it without being told.
 *
 * Three grounds down the section, white -> wash -> white: the head on white,
 * the whole composition on the wash, white again before the product section.
 * The wash peaks at #e4efff — deliberately a step paler than the AI band
 * further down (#d7e6ff), because two tinted bands of equal strength on one
 * page would leave neither of them meaning anything.
 */

/* The band brings its own generous bottom padding, so the section only needs
   enough of its own to show the white foot the composition is built on. */
.pl-fit { padding-bottom: clamp(1.5rem, 2.5vw, 2.25rem); }

/* ---------------------------------------------- 9a. the two-column body */

/*
 * The rail is narrower than the answer column: six one-line trades need less
 * room than four paragraphs, and the reader's eye should land on the right
 * half, which is the part that says what they get.
 */
.pl-fit__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.16fr);
    gap: clamp(2rem, 4vw, 3.5rem);
    /* stretch, not start: six ledger rows and two rows of cards do not
       come out the same height on their own, and a right column that
       stops short of the rail makes the composition look unfinished.
       Whichever column is taller sets the row, and the shorter one
       grows into it. */
    align-items: stretch;
}

/* So the panel inside each column can take the full stretched height. */
.pl-fit__col {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* The column heads. Quiet mono, muted rather than the eyebrow's gradient —
   there is one bright machine label per section and it is above the h2. The
   rule runs from the label to the end of the column, which is what a table
   header does and what these two are. */
.pl-fit__label {
    display: flex;
    align-items: center;
    gap: .85rem;
    margin-bottom: .35rem;
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-2xs);
    font-weight: var(--pl-w-semibold);
    letter-spacing: var(--pl-ls-mono);
    text-transform: uppercase;
    line-height: 1;
    color: var(--pl-muted-2);
}
.pl-fit__label::after {
    content: '';
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, var(--pl-line-2), rgba(207, 217, 232, 0));
}

/* --------------------------------------------- 9b. the trade rail (left) */

/*
 * A ledger, not a card grid. One row per trade: the trade named on the left,
 * and the capability it actually buys the software for set hard right as a
 * mono readout — the way a POS sets a line item, which is the page's own
 * rule for the utility face. The rows sit directly on the wash with hairline
 * rules between them, so the only solid objects in the section are the four
 * outcome cards opposite, which are the payoff.
 *
 * Every readout is condensed from that trade's own verified points list; see
 * $industryTags in the view.
 */
/*
 * The six rows are one object, not six loose lines on a gradient: a
 * translucent panel, so the left of the composition has the same visual
 * weight as the four cards opposite without becoming a fifth card. It reads
 * through to the wash, which keeps the outcome cards the only solid things
 * in the section.
 */
.pl-trades {
    display: flex;
    flex-direction: column;
    margin-top: 1rem;
    border: 1px solid rgba(255, 255, 255, .72);
    border-radius: var(--pl-r-lg);
    background: rgba(255, 255, 255, .42);
    /* The rows run edge to edge and the panel clips them, so the rules
       between them are straight hairlines rather than lines that curve away
       at both ends — which is what a row with its own radius produces. */
    overflow: hidden;
}

.pl-trade {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr) auto;
    align-items: center;
    column-gap: .9rem;
    row-gap: .15rem;
    padding: .95rem 1.05rem;
    border-top: 1px solid rgba(13, 110, 243, .13);
    transition: background .2s ease;
}
.pl-trade:first-child { border-top: 0; }
.pl-trade:hover { background: rgba(255, 255, 255, .82); }

.pl-trade__icon {
    grid-row: 1 / 3;
    align-self: start;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: var(--pl-r-sm);
    background: #fff;
    border: 1px solid var(--pl-brand-100);
    color: var(--pl-brand);
    transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.pl-trade:hover .pl-trade__icon {
    background: var(--pl-grad);
    border-color: transparent;
    color: #fff;
}

.pl-trade__name {
    grid-column: 2;
    font-family: var(--pl-font-display);
    font-size: var(--pl-t-md);
    font-weight: var(--pl-w-bold);
    letter-spacing: -.012em;
    line-height: 1.3;
    color: var(--pl-ink);
}

/* Hard right on the trade's own line — the readout half of the ledger row. */
.pl-trade__tag {
    grid-column: 3;
    justify-self: end;
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-3xs);
    font-weight: var(--pl-w-medium);
    letter-spacing: var(--pl-ls-mono);
    text-transform: uppercase;
    line-height: 1;
    white-space: nowrap;
    color: var(--pl-muted);
    transition: color .2s ease;
}
.pl-trade:hover .pl-trade__tag { color: var(--pl-brand-deep); }

.pl-trade__line {
    grid-column: 2 / -1;
    font-size: var(--pl-t-xs);
    line-height: 1.55;
    color: var(--pl-muted);
}

/* ------------------------------------------ 9c. the wash behind them both */

/*
 * The whole wash lives on ::before and is masked to transparent at the top
 * and bottom, rather than being painted on the element itself. Painting it on
 * the element would mean the two radial brand glows bled into the white ends,
 * which is precisely the edge this composition is built on. ::after carries
 * the pinstripes under the same mask, so they fade out with the colour rather
 * than ending on a hard line.
 *
 * isolation:isolate makes the band a stacking context, which keeps both
 * z-index:-1 layers above its own background but behind every child.
 */
.pl-fit__band {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    margin-top: clamp(2.5rem, 5vw, 4rem);
    padding: clamp(3.5rem, 6vw, 5.25rem) 0 clamp(3.25rem, 5.5vw, 4.75rem);
}
.pl-fit__band::before,
.pl-fit__band::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 16%, #000 84%, transparent 100%);
            mask-image: linear-gradient(180deg, transparent 0%, #000 16%, #000 84%, transparent 100%);
}
.pl-fit__band::before {
    background:
        radial-gradient(38% 42% at 16% 40%, rgba(6, 176, 254, .17), rgba(6, 176, 254, 0) 72%),
        radial-gradient(40% 44% at 84% 62%, rgba(13, 110, 243, .14), rgba(13, 110, 243, 0) 72%),
        linear-gradient(180deg, #edf4ff 0%, #e4efff 50%, #edf4ff 100%);
}
.pl-fit__band::after {
    background: repeating-linear-gradient(90deg,
        rgba(255, 255, 255, .7) 0 1px,
        rgba(255, 255, 255, 0) 1px 92px);
}

/* ---------------------------------------- 10. the four outcomes (right) */

/*
 * White cards floating on the wash — the same object the proof strip uses at
 * the top of the page, so both tinted grounds hold their content the same
 * way. Two by two, which is what makes them sit level with six rail rows
 * opposite and keeps the two columns reading as one object.
 */
.pl-outcomes {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    margin-top: 1rem;
    /* Fill the column the grid stretched, and split that height evenly
       between the two rows, so the last card's foot lands on the last
       rail row's foot opposite. */
    flex: 1;
    grid-auto-rows: 1fr;
    align-content: stretch;
}

.pl-outcome {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    padding: 1.5rem 1.4rem 1.6rem;
    border: 1px solid rgba(255, 255, 255, .72);
    border-radius: var(--pl-r-lg);
    /* Not pure white. The rail opposite reads through to the wash at .42,
       so a solid card beside it looked like it belonged to a different
       section. Same translucent white, a step stronger — these are cards
       and still have to sit above the ground, not in it. */
    background: rgba(255, 255, 255, .62);
    box-shadow: var(--pl-sh-md);
    transition: transform .22s ease, box-shadow .22s ease, background .2s ease;
}
.pl-outcome:hover {
    transform: translateY(-3px);
    box-shadow: var(--pl-sh-lg);
    /* Matches the rail row's own hover value, so both halves of the
       section light up to the same white. */
    background: rgba(255, 255, 255, .82);
}
.pl-outcome__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: var(--pl-brand-50);
    border: 1px solid var(--pl-brand-100);
    color: var(--pl-brand);
    transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.pl-outcome:hover .pl-outcome__icon {
    background: var(--pl-grad);
    border-color: transparent;
    color: #fff;
}
.pl-outcome p {
    font-size: var(--pl-t-sm);
    line-height: 1.6;
    color: var(--pl-muted);
}

/* --------------------------------------------- 11. 03 the product, as a ring */

/*
 * The Px mark at the centre of an orbit, with a module LENS on every seat.
 *
 * This replaced a tab strip over six screenshot galleries. The point of the
 * section is that the modules are ONE system, and that is a shape rather
 * than a sequence — so nothing here is clickable, nothing is behind a
 * control, and the whole argument is legible in a single look.
 *
 * THE LENS is the section's one bold move and everything else stays quiet
 * for it. There is no card: a landscape frame ringed in the brand ramp, with
 * the module's real screen inside it WHOLE, matted across the middle on pale
 * ground. Nothing is cropped and nothing is magnified — the frame is a
 * comfortable rectangle and the picture keeps its own wider one, and the mat
 * between them is what lets both be true. That is also why the frame is a
 * rectangle at all: a disc had to cut every capture down to a band before it
 * would fit, and a screenshot cropped to a coin is a texture rather than a
 * screen. The caption hangs beneath on the bare ground, at one steady
 * measure for all eight, so the varying frames read against a fixed
 * typographic baseline.
 *
 * SIZE FOLLOWS THE ROOM. No two lenses are the same size, and the ladder is
 * the ring's own geometry: the seats at 3 and 9 o'clock have the margin
 * between the mark and the stage edge to themselves and carry the widest
 * frames, the top pair is held to what fits between two neighbours 298px
 * apart, and the bottom pair — read last, and under everything else — is
 * the small one. The widths live in $modules in the view, arrive here as
 * --lens and --lens-h, and are the reason this block types no dimension of
 * its own.
 *
 * GEOMETRY. Not one coordinate in this block is typed. The stage carries the
 * SVG's viewBox aspect ratio and every lens is placed from the --x / --y
 * percentages the Blade computes with the same radius the rings are drawn
 * with (see $orbit / $orbitAt in the view). The pair cannot drift apart, and
 * because both sides are proportional the composition scales with its
 * container instead of stepping at a breakpoint.
 *
 * SIZING. The stage is a container, and everything inside it is sized in
 * `cqw` so the type shrinks with the ring rather than overflowing it. Every
 * cqw declaration is preceded by the px value it computes to at the 1060px
 * design width, which is what a browser without container queries keeps.
 *
 * MOTION. Three layers, deliberately separated, because a transition and an
 * infinite animation cannot share one element's transform:
 *
 *     .pl-orbit__node    seats the lens on the ring          (no motion)
 *     .pl-orbit__float   the endless drift                   (animation)
 *     .pl-orbit__card    the entry                           (transition)
 *
 * ...which leaves .pl-lens's own transform free for hover, the fourth. The
 * entry is driven by .pl-orbit.is-in — the stage is a single .pl-reveal, so
 * the eight lenses arrive together in reading order off ONE observer entry
 * rather than eight independent ones firing as each crosses the fold.
 */

.pl-head__lead { margin-top: .95rem; color: var(--pl-muted); }

.pl-orbit { margin-top: clamp(1.5rem, 3vw, 2.75rem); }

.pl-orbit__stage {
    position: relative;
    width: 100%;
    max-width: 1060px;
    margin-inline: auto;
    aspect-ratio: 1060 / 1040;
    container-type: inline-size;
}

/* ---- the drawing: rings, glow, sparks ---- */

.pl-orbit__rings {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

/*
 * SVG transforms resolve against the element's own bounding box by default,
 * which for a <g> holding one circle is that circle — so a rotation would
 * spin about the stage's centre only by accident, and the two sparks (whose
 * boxes are 5px dots parked out on their orbits) would not spin about it at
 * all. `view-box` pins every rotation in this drawing to one origin, and the
 * origin is --cx / --cy, handed down from $orbit on the <svg> itself rather
 * than typed here where it could fall out of step with the geometry.
 */
.pl-orbit__ring,
.pl-orbit__spark {
    transform-box: view-box;
    transform-origin: var(--cx) var(--cy);
}

.pl-orbit__spark circle { filter: drop-shadow(0 0 6px rgba(6, 176, 254, .85)); }

/* ---- the centre ---- */

/*
 * Centred with `inset: 0; margin: auto` rather than the usual
 * left/top 50% + translate(-50%, -50%), because the transform on this
 * element belongs to the entry animation. Sharing one transform between
 * centring and motion is what forces every later rule — the mobile stack,
 * reduced motion, the no-JS fallback — to remember to re-state the
 * translate, and to break the moment one of them forgets.
 */
.pl-orbit__core {
    position: absolute;
    inset: 0;
    margin: auto;
    width: 216px;
    width: 20.38cqw;
    height: 216px;
    height: 20.38cqw;
    display: grid;
    place-items: center;
}

/* The disc is white so the mark reads as itself; the ring around it and the
   shadow under it are the only brand colour, and both are the ramp. */
.pl-orbit__disc {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    padding: 23%;
    border-radius: 50%;
    background:
        linear-gradient(#fff, #fff) padding-box,
        var(--pl-grad) border-box;
    border: 2px solid transparent;
    box-shadow: 0 24px 50px -18px rgba(7, 27, 77, .35), 0 6px 18px -8px rgba(13, 110, 243, .3);
}
.pl-orbit__disc img { width: 100%; height: auto; }

/* Two rings leaving the disc on a long cycle, offset by --d so the second is
   halfway through the first's travel. This is the section's only element
   that moves without a card or a ring under it, and it is what makes the
   centre read as the thing everything else is attached to. */
.pl-orbit__pulse {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 1px solid var(--pl-brand-300);
    opacity: 0;
}

/* ---- the eight lenses ---- */

.pl-orbit__nodes {
    position: absolute;
    inset: 0;
}

/*
 * The node is a zero-width plumb line dropped on the seat: its children are
 * flex-centred on it, so a 246px lens and a 210px caption both hang from the
 * same axis without the node having to be as wide as the wider of them. The
 * translate then lifts it by HALF THE FRAME'S HEIGHT — what sits on the orbit
 * is the lens's centre, and the caption hangs below the ring. Height, not
 * width: the frames are landscape, so the two are no longer one number.
 */
.pl-orbit__node {
    position: absolute;
    left: var(--x);
    top: var(--y);
    width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    transform: translateY(calc(var(--lens-h-px) / -2));
    transform: translateY(calc(var(--lens-h) * -.5cqw));
}

.pl-orbit__float {
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* No card. The frame is the object and the caption sits on the bare ground
   under it, which is why this element carries no background, border or
   shadow of its own — every one of those lives on .pl-lens. */
.pl-orbit__card {
    display: flex;
    flex-direction: column;
    align-items: center;
    transition: opacity .6s cubic-bezier(.16, 1, .3, 1) var(--d, 0ms);
}

/*
 * THE LENS. A landscape frame rimmed in the brand ramp — the same
 * padding-box / border-box pair the centre mark's disc uses, so the mark and
 * the eight screens still read as one family even though only one of them is
 * round. It is a rectangle because what is inside it is a screen: a disc had
 * to crop every capture down to a slot before it would fit.
 *
 * Width and height both arrive from the view as percentages of the stage;
 * the height is the width over $frameAr, which the view derives rather than
 * letting anyone type one. The frame is deliberately SQUARER than the
 * picture it holds, so the capture sits across the middle of it on a mat —
 * see .pl-lens__band, which is the piece that keeps the picture whole.
 *
 * Sizes are not typed here for the same reason nothing else in this block
 * is: the ladder is an argument the view makes, and this file draws it.
 *
 * The radius scales with the frame, because a fixed one would read as a soft
 * card on Customers and a lozenge on Accounts.
 */
.pl-lens {
    position: relative;
    width: var(--lens-px);
    width: calc(var(--lens) * 1cqw);
    height: var(--lens-h-px);
    height: calc(var(--lens-h) * 1cqw);
    border-radius: 14px;
    border-radius: max(9px, 1.15cqw);
    box-shadow: var(--pl-sh-lg);
    transition: transform .3s cubic-bezier(.16, 1, .3, 1), box-shadow .3s ease;
}

/* The clip lives here and not on .pl-lens because the moon is seated on the
   corner: one `overflow: hidden` covering both would take half the badge
   with it. `border-radius: inherit` so the rounding is stated once, and the
   grid is what centres the matted band in a frame taller than it is. */
.pl-lens__glass {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    border-radius: inherit;
    overflow: hidden;
    border: 1px solid transparent;
    background:
        radial-gradient(115% 115% at 32% 18%, #fff 0%, #eef5ff 52%, #dbe9ff 100%) padding-box,
        var(--pl-grad) border-box;
}

/*
 * THE MAT. This element is the picture's own shape — 1280/560, stated here
 * and nowhere else — laid across the full width of a frame that is squarer
 * than it ($frameAr, 1.8). What is left over is the pale ground above and
 * below, which is the whole reason nothing gets cropped: the frame can be
 * whatever shape the composition wants without the picture having to agree.
 *
 * `height: auto` under an aspect-ratio, never a percentage — a percentage
 * height here would resolve against the frame and put the mat back to zero.
 *
 * The two hairlines are the one piece of decoration in here, and they are
 * what make the ground read as a mat rather than as a picture that failed
 * to fill its box.
 */
.pl-lens__band {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1280 / 560;
    overflow: hidden;
    border-block: 1px solid rgba(13, 110, 243, .16);
    background: var(--pl-soft);
}

/*
 * The whole capture. There is no zoom and no focus point — the band above is
 * already the picture's exact proportion, so `cover` here has nothing to
 * crop and every screen is shown entire. This used to magnify each image
 * about a per-module aim, which was the price of a window that was the wrong
 * shape for the picture; the mat pays that off.
 */
.pl-lens__band img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/*
 * The module's icon, seated on the frame's top-right corner and centred on
 * it, so it sits half on the screen and half off — the badge reads as pinned
 * to the frame rather than drawn inside it. Under the disc this had to be
 * solved off the circle's geometry (50% ± 35.36%); a rectangle's corner is
 * simply its corner. Fixed size across all eight: it is an identifier, not
 * part of the size ladder, and scaling it would say the small modules have
 * small icons.
 */
.pl-lens__moon {
    position: absolute;
    left: 100%;
    top: 0;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 28px;
    width: 2.64cqw;
    height: 28px;
    height: 2.64cqw;
    border-radius: 50%;
    color: #fff;
    background: var(--pl-grad);
    box-shadow: var(--pl-sh-brand), 0 0 0 3px rgba(255, 255, 255, .92);
    transform: translate(-50%, -50%);
}
.pl-lens__moon svg {
    width: 15px;
    width: 1.42cqw;
    height: 15px;
    height: 1.42cqw;
}

/*
 * One measure for all eight captions, whatever the lens above it is doing.
 * The varying frames need something that does not vary to be read against,
 * and this is it.
 */
.pl-orbit__body {
    width: 210px;
    width: 19.81cqw;
    margin-top: 11px;
    margin-top: 1.04cqw;
    text-align: center;
}

.pl-orbit__name {
    font-family: var(--pl-font-display);
    font-size: 15px;
    font-size: max(14px, 1.42cqw);
    font-weight: var(--pl-w-bold);
    line-height: 1.25;
    letter-spacing: var(--pl-ls-h4);
    color: var(--pl-ink);
    white-space: nowrap;
}

/*
 * The readout, in the mono the eyebrows and prices elsewhere on this page
 * are set in, and on ONE line — three terms of a spec sheet, not a sentence.
 * `nowrap` is the contract the 26-character limit in $modules exists to
 * keep; if a new module's line ever runs past the measure, shorten the line
 * rather than letting this wrap.
 */
.pl-orbit__line {
    margin-top: 5px;
    margin-top: .47cqw;
    font-family: var(--pl-font-mono);
    font-size: 11px;
    /* The floor is the point: cqw alone puts this at 9.8px the moment the
       stage is anything under its 1060 design width, and mono at 9.8px is a
       texture, not a readout. */
    font-size: max(10.5px, 1.06cqw);
    font-weight: var(--pl-w-medium);
    letter-spacing: .015em;
    line-height: 1.3;
    color: var(--pl-muted);
    white-space: nowrap;
}

/*
 * Hover lifts the lens off the ring it is sitting on and leaves the caption
 * where it is, so the frame reads as the thing floating. The float
 * underneath keeps running — the transforms are on different elements
 * precisely so this can be true.
 */
.pl-orbit__card:hover .pl-lens {
    transform: translateY(-5px) scale(1.03);
    box-shadow: var(--pl-sh-xl);
}
.pl-orbit__card:hover .pl-orbit__name { color: var(--pl-brand); }

/* ---- entry ---- */

/*
 * Everything starts collapsed toward the centre and settles outward, so the
 * composition assembles itself rather than fading in flat. The stage is one
 * .pl-reveal; .is-in on it releases the rings, then the mark, then the lenses
 * in reading order — clockwise, largest first, which is also the order the
 * size ladder is arguing for.
 */
.pl-orbit__rings,
.pl-orbit__core {
    opacity: 0;
    transform: scale(.9);
    transform-origin: 50% 50%;
    transition: opacity .8s cubic-bezier(.16, 1, .3, 1),
                transform .8s cubic-bezier(.16, 1, .3, 1);
}
.pl-orbit__core { transform: scale(.72); transition-delay: .1s; }

/*
 * BUBBLE, the signature move: each lens is a real screenshot, so it is the
 * clearest statement of the page's one image entrance. It starts as a soft
 * blurred dot at its own centre — a fifth of its size, out of focus — and
 * bubbles up past full size before settling into sharp focus. Small to
 * large, blurred to sharp, overshooting on the way: three channels landing
 * together is what makes it read as a bubble breaking the surface rather
 * than a box resizing.
 */
.pl-orbit__card {
    opacity: 0;
    filter: blur(20px);
    transform: scale(.2);
    transform-origin: 50% 50%;
}

.pl-orbit.is-in .pl-orbit__rings { opacity: 1; transform: none; }
.pl-orbit.is-in .pl-orbit__core { opacity: 1; transform: none; }
.pl-orbit.is-in .pl-orbit__card {
    opacity: 1;
    filter: blur(0);
    transform: none;
    transition: transform .95s var(--pl-ease-spring) var(--d, 0ms),
                filter .55s var(--pl-ease-out) var(--d, 0ms),
                opacity .45s var(--pl-ease-out) var(--d, 0ms);
}

/* ---- ambient motion, started only once the stage has arrived ---- */

/*
 * Faster than the drawing used to turn. At 150s/110s a ring completes a
 * revolution well after a visitor has left the page — motion that slow
 * reads as static, not ambient. This is still slow (a full minute-plus per
 * lap), just not so slow it fails to register as motion at all.
 */
.pl-orbit.is-in .pl-orbit__ring--out { animation: pl-orbit-turn 70s linear infinite; }
.pl-orbit.is-in .pl-orbit__ring--mid { animation: pl-orbit-turn 52s linear infinite reverse; }
.pl-orbit.is-in .pl-orbit__spark--in { animation: pl-orbit-turn 13s linear infinite reverse; }
.pl-orbit.is-in .pl-orbit__pulse { animation: pl-orbit-pulse 4.2s ease-out var(--d, 0s) infinite; }
.pl-orbit.is-in .pl-orbit__float {
    animation: pl-orbit-drift var(--float, 8s) ease-in-out var(--float-delay, 0s) infinite;
}
/* The lens rocks a couple of degrees on its own slower cycle, independent of
   the float's translate — `rotate` is its own transform-adjacent property,
   so this composes with .pl-lens's hover transition instead of fighting it. */
.pl-orbit.is-in .pl-lens {
    animation: pl-lens-wobble calc(var(--float, 8s) * 1.7) ease-in-out var(--float-delay, 0s) infinite;
}

@keyframes pl-orbit-turn {
    to { transform: rotate(360deg); }
}
@keyframes pl-orbit-pulse {
    0%   { opacity: .55; transform: scale(1); }
    70%  { opacity: 0;   transform: scale(1.75); }
    100% { opacity: 0;   transform: scale(1.75); }
}
/*
 * Each lens traces its own circle above its seat — four points around it and
 * back — rather than a straight up-down bob, so the ring's motion carries
 * all the way down to the pictures themselves instead of stopping at the
 * drawing. Wider than the first pass: a 9px bob read as a twitch, not a
 * float.
 */
@keyframes pl-orbit-drift {
    0%   { transform: translate(0, 0); }
    25%  { transform: translate(5px, -9px); }
    50%  { transform: translate(0, -12px); }
    75%  { transform: translate(-5px, -9px); }
    100% { transform: translate(0, 0); }
}
@keyframes pl-lens-wobble {
    0%, 100% { rotate: -1.6deg; }
    50%      { rotate: 1.6deg; }
}

/* ------------------------------------------------------------- 12. 05 AI */

/*
 * The ground here is the same pale-blue wash the hero canvas and the footer
 * run — #dbe9ff through #e6f0ff, with the 92px pinstripes — but faded to white
 * at BOTH edges so it reads as a tinted band the page passes through rather
 * than a slab bolted between two white sections. No new colour enters the
 * page for this section.
 *
 * It used to be the one dark section. The dark moved to the product screen,
 * which floats on this light ground: a dark app window on a pale wash makes
 * the "this part is different" point more sharply than inverting the whole
 * band did, and it costs the page nothing in contrast.
 */
.pl-ai {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background:
        radial-gradient(46% 40% at 10% 30%, rgba(6, 176, 254, .20), rgba(6, 176, 254, 0) 70%),
        radial-gradient(52% 46% at 88% 66%, rgba(13, 110, 243, .17), rgba(13, 110, 243, 0) 70%),
        linear-gradient(180deg,
            #ffffff 0%,
            #eef5ff 6%,
            #dce9ff 18%,
            #d7e6ff 40%,
            #d7e6ff 66%,
            #e0ecff 84%,
            #f5f9ff 94%,
            #ffffff 100%);
}
/* The pinstripes fade at both ends with the wash, otherwise they would end in
   a visible hard line on the white. */
.pl-ai::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: repeating-linear-gradient(90deg,
        rgba(255, 255, 255, .7) 0 1px,
        rgba(255, 255, 255, 0) 1px 92px);
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 20%, #000 78%, transparent 100%);
            mask-image: linear-gradient(180deg, transparent 0%, #000 20%, #000 78%, transparent 100%);
}

/* Copy rail left, screen right — the composition of the approved composite.
   The screen takes the larger share because it is the argument; the rail is
   sized to hold the heading at a comfortable measure, not to balance it. */
.pl-ai__grid {
    display: grid;
    grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
    gap: clamp(2rem, 4vw, 3.75rem);
    align-items: center;
}
.pl-ai__copy .pl-head { max-width: none; }
.pl-ai__copy .pl-head .pl-h2 { max-width: none; }

/* Straight from the composite: the second sentence carries the brand blue.
   Flat, not the eyebrow's gradient — two gradient texts stacked would be one
   effect too many in a 400px column. */
.pl-ai__accent { font-style: normal; color: var(--pl-brand); }

.pl-ai__benefits {
    display: grid;
    gap: .35rem;
    margin-top: clamp(1.75rem, 3vw, 2.25rem);
}
.pl-ai__benefit {
    display: flex;
    align-items: flex-start;
    gap: .85rem;
    padding: .7rem .75rem;
    border-radius: var(--pl-r-lg);
    transition: background .2s ease;
}
.pl-ai__benefit:hover { background: rgba(255, 255, 255, .6); }
.pl-ai__benefit-icon {
    display: grid;
    place-items: center;
    flex: none;
    width: 38px;
    height: 38px;
    border: 1px solid var(--pl-line);
    border-radius: 11px;
    background: #fff;
    box-shadow: var(--pl-sh-sm);
    color: var(--pl-brand);
}
.pl-ai__benefit-text { display: grid; gap: .15rem; }
.pl-ai__benefit-text b {
    font-family: var(--pl-font-display);
    font-size: var(--pl-t-md);
    font-weight: var(--pl-w-bold);
    letter-spacing: -.012em;
    line-height: 1.35;
    color: var(--pl-ink);
}
.pl-ai__benefit-text span {
    font-size: var(--pl-t-xs);
    line-height: 1.55;
    color: var(--pl-muted);
}

/*
 * The assistant capture. It is natively dark, so the white .pl-frame every
 * other screenshot wears is overridden away and the panel is floated instead:
 * its own hairline, the page's deepest shadow, and one brand bloom behind it.
 *
 * 909px is the capture's true pixel width and it is capped there — it may sit
 * smaller in a narrow column, never larger. The chat exchange is the argument
 * this section makes, and upscaling would blur the only part anyone reads.
 */
.pl-ai__stage { position: relative; }
.pl-ai__stage::before {
    content: '';
    position: absolute;
    z-index: -1;
    inset: 8% -5% 2%;
    pointer-events: none;
    background: radial-gradient(50% 50% at 50% 50%, rgba(13, 110, 243, .22), rgba(13, 110, 243, 0) 70%);
}
.pl-ai .pl-ai__device {
    max-width: 909px;
    margin-inline: auto;
    border-radius: var(--pl-r-xl);
    border: 1px solid rgba(7, 27, 77, .22);
    background: #0b1220;
    box-shadow: var(--pl-sh-xl);
}
.pl-ai__device img { width: 100%; height: auto; }

/* Placeholder state, for when the capture is missing — it inherits the dark
   panel above, so it needs its own light-on-dark treatment. */
.pl-ai .pl-frame__ph { background: rgba(255, 255, 255, .05); color: var(--pl-on-ink-mute); }
.pl-ai .pl-frame__ph b { color: var(--pl-on-ink); }

.pl-ai__note {
    margin: 1.15rem auto 0;
    /* Narrow enough to wrap to two lines — set to the full 909px it stretches
       into a rule under the screen instead of reading as its caption. */
    max-width: 48ch;
    text-align: center;
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-2xs);
    letter-spacing: .04em;
    line-height: 1.6;
    color: var(--pl-muted-2);
}

/* ------------------------------------------------------------ 13. 06 pricing */

/*
 * The two plans are ONE object: a single bordered panel split by a perforated
 * spine, not two cards floating side by side. The page's whole type argument
 * is that a POS is a terminal and a receipt printer — this is the one tear-off
 * on it, and it is also the honest shape of the offer, because the halves are
 * two different KINDS of deal rather than two rungs of a ladder.
 *
 * What holds the object together is the CTA baseline. Both feet are pushed
 * down with margin-top:auto so the buttons land on one line across the spine;
 * everything above them is height-matched (a two-line tagline minimum, a
 * two-line note minimum, and a quota grid that is exactly 2×2 in both halves)
 * so the price rows and the spec blocks line up as well.
 *
 * No new colour. The chosen half is signalled with a wash off the brand ramp,
 * a tinted flag and the primary button — never with a border, because you
 * cannot border half of one object without it reading as broken.
 */

.pl-price {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch;
    max-width: 1000px;
    margin: clamp(2.25rem, 4vw, 3.25rem) auto 0;
    border: 1px solid rgba(13, 110, 243, .18);
    border-radius: var(--pl-r-xl);
    background: var(--pl-surface);
    box-shadow: 0 24px 60px -26px rgba(7, 27, 77, .22), 0 4px 14px -6px rgba(13, 110, 243, .1);
    /* Clips the flag rail's fill to the panel's corners. */
    overflow: hidden;
}

.pl-price__col {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* The spine. A dashed border would sit inside the box model and break at the
   flag rail's own border; drawn as a 1px repeating background it runs the
   full height of the column, rail included, as one continuous perforation. */
.pl-price__col + .pl-price__col {
    background-image: repeating-linear-gradient(180deg, var(--pl-line-2) 0 6px, transparent 6px 13px);
    background-size: 1px 100%;
    background-repeat: no-repeat;
    background-position: left top;
}

/* The chosen half, washed from the top and gone by the feature list. The
   stop is in px, not a percentage: as a percentage it scales with the column
   and the tint reads as a filled panel rather than as light falling on one. */
.pl-price__col--pop {
    background-image: linear-gradient(180deg, rgba(13, 110, 243, .06) 0, rgba(13, 110, 243, 0) 300px);
}

/* ---- the flag rail across the top of both halves ---- */

.pl-price__flag {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .45rem;
    height: 44px;
    border-bottom: 1px solid var(--pl-line);
    background: var(--pl-soft);
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-3xs);
    font-weight: var(--pl-w-semibold);
    letter-spacing: .14em;
    line-height: 1;
    text-transform: uppercase;
    color: var(--pl-muted-2);
}
.pl-price__flag svg { width: 13px; height: 13px; }

.pl-price__col--pop .pl-price__flag {
    background: rgba(13, 110, 243, .1);
    border-bottom-color: rgba(13, 110, 243, .2);
    color: var(--pl-brand-deep);
}

.pl-price__body { padding: clamp(1.5rem, 2.4vw, 1.9rem) clamp(1.35rem, 2.4vw, 1.9rem) 0; }

/* ---- the price row ---- */

.pl-price__row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .75rem;
}

/* A figure is a readout, so it is set in the mono face with tabular figures
   and does not move when the toggle changes it. */
.pl-price__amt {
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-price);
    font-weight: var(--pl-w-semibold);
    letter-spacing: -.045em;
    line-height: 1;
    color: var(--pl-ink);
    font-variant-numeric: tabular-nums;
}

/* "Custom" is not a readout — it is a conversation — so it is set in the
   display face at the same optical size. The difference between the two
   halves' type is the difference between the two offers. */
.pl-price__amt--word {
    font-family: var(--pl-font-display);
    font-weight: var(--pl-w-black);
    letter-spacing: var(--pl-ls-h2);
    color: var(--pl-brand-ink);
}

.pl-price__unit {
    display: grid;
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-3xs);
    font-weight: var(--pl-w-medium);
    letter-spacing: .1em;
    line-height: 1.55;
    text-transform: uppercase;
    color: var(--pl-muted-2);
}

/* The billing switch sits in the price row rather than over the section,
   because only one half has a number to discount. Its absence on the Custom
   side is the message, not an omission. */
.pl-toggle {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    margin-left: auto;
    padding: .3rem .55rem .3rem .32rem;
    border: 1px solid var(--pl-line-2);
    border-radius: 999px;
    background: var(--pl-surface);
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-3xs);
    font-weight: var(--pl-w-semibold);
    letter-spacing: .08em;
    line-height: 1;
    text-transform: uppercase;
    color: var(--pl-muted);
    transition: border-color .18s ease, background .18s ease, color .18s ease;
}
.pl-toggle:hover { border-color: var(--pl-brand-300); }

.pl-toggle__track {
    flex: none;
    display: block;
    position: relative;
    width: 30px;
    height: 17px;
    border-radius: 999px;
    background: var(--pl-line-2);
    transition: background .2s ease;
}
.pl-toggle__knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(10, 17, 36, .3);
    transition: transform .22s cubic-bezier(.4, 0, .2, 1);
}
.pl-toggle__save {
    padding: .14rem .38rem;
    border-radius: 999px;
    background: rgba(13, 110, 243, .12);
    color: var(--pl-brand-deep);
}

.pl-toggle[aria-pressed="true"] {
    border-color: rgba(13, 110, 243, .28);
    background: var(--pl-brand-50);
    color: var(--pl-brand-deep);
}
.pl-toggle[aria-pressed="true"] .pl-toggle__track { background: var(--pl-brand); }
.pl-toggle[aria-pressed="true"] .pl-toggle__knob { transform: translateX(13px); }

.pl-price__note {
    margin-top: .65rem;
    font-size: var(--pl-t-xs);
    line-height: 1.5;
    color: var(--pl-muted-2);
    min-height: 2.25em;
}

/* ---- the spec block ---- */

/* 2×2 rather than wrapping chips: a fixed grid is exactly two rows in both
   halves whatever the values are, which is what keeps the rule below it — and
   everything after it — level across the spine. */
.pl-price__quota {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 1.5rem;
    margin-top: 1.1rem;
}
/* Ruled rows, not chips: four boxed chips inside an already-bordered panel
   stacked one frame too many, so each pair keeps a single hairline under it
   and the column gap does the separating sideways. */
.pl-price__quota span {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .4rem;
    padding: .42rem 0;
    font-size: var(--pl-t-3xs);
    line-height: 1.4;
    color: var(--pl-muted);
}
.pl-price__quota b {
    font-family: var(--pl-font-mono);
    font-weight: var(--pl-w-semibold);
    color: var(--pl-ink);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.pl-price__inherits {
    margin-top: 1.3rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--pl-line);
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-3xs);
    font-weight: var(--pl-w-medium);
    letter-spacing: .11em;
    text-transform: uppercase;
    color: var(--pl-muted-2);
}

/*
 * Two marks, because the two lists say different things. Small Business rows
 * all mean "included" and take the page's check chip. Custom rows each name a
 * different dimension the buyer sets, so each takes its own glyph — the icon
 * is carrying the information a repeated tick could not.
 */
.pl-price__list {
    display: grid;
    align-content: start;
    gap: .7rem;
    margin-top: 1rem;
}
.pl-price__list li {
    display: flex;
    align-items: flex-start;
    gap: .65rem;
    font-size: var(--pl-t-sm);
    line-height: 1.5;
    color: var(--pl-ink-2);
}
.pl-price__list .pl-tick { flex: none; margin-top: .14rem; }
.pl-price__list b { font-weight: var(--pl-w-bold); color: var(--pl-ink); }

.pl-price__mark {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border: 1px solid var(--pl-line);
    border-radius: var(--pl-r-sm);
    background: var(--pl-soft);
    color: var(--pl-brand-deep);
}
.pl-price__mark svg { width: 13px; height: 13px; }

/* ---- the feet, on one baseline across the spine ---- */

.pl-price__foot {
    margin-top: auto;
    padding: clamp(1.4rem, 2.2vw, 1.75rem) clamp(1.35rem, 2.4vw, 1.9rem) clamp(1.4rem, 2.2vw, 1.75rem);
}
.pl-price__link {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    margin-top: .85rem;
    font-size: var(--pl-t-sm);
    font-weight: var(--pl-w-semibold);
    color: var(--pl-muted);
    transition: color .18s ease;
}
.pl-price__link svg { width: 14px; height: 14px; transition: transform .18s ease; }
.pl-price__link:hover { color: var(--pl-brand-deep); }
.pl-price__link:hover svg { transform: translateX(3px); }

.pl-reassure {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .6rem 1.6rem;
    margin-top: 2rem;
    font-size: var(--pl-t-sm);
    color: var(--pl-muted);
}
.pl-reassure span { display: inline-flex; align-items: center; gap: .5rem; }

/* ---------------------------------------------------------- 14. 07 final CTA */

.pl-final { padding: var(--pl-section-pad) 0 clamp(4.5rem, 8vw, 7rem); text-align: center; }
.pl-final .pl-h2 { max-width: 18ch; margin-inline: auto; }
.pl-final .pl-lead {
    margin: 1.15rem auto 0;
    max-width: 56ch;
}

/*
 * One action, and it is a phone call. The self-serve signup already has two
 * prominent entries above this point (the hero and every plan card); at the
 * bottom of the page the visitor who has read this far and not clicked either
 * is the one who wants to talk to a person. The number is set in the mono
 * face at display size and IS the button's label — a "Talk to us" button that
 * hides the number behind a tap is one step too many on a desktop.
 */
.pl-final__call {
    display: inline-flex;
    align-items: center;
    gap: 1rem;
    margin-top: 2.25rem;
    padding: .5rem .5rem .5rem 1.5rem;
    border-radius: 999px;
    background: #fff;
    border: 1px solid rgba(13, 110, 243, .18);
    box-shadow: var(--pl-sh-lg);
    transition: box-shadow .2s ease, transform .2s ease, border-color .2s ease;
}
.pl-final__call:hover {
    transform: translateY(-2px);
    border-color: var(--pl-brand);
    box-shadow: 0 18px 40px -14px rgba(13, 110, 243, .4);
}
.pl-final__call-label {
    display: grid;
    gap: .15rem;
    text-align: left;
}
.pl-final__call-label small {
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-3xs);
    font-weight: var(--pl-w-medium);
    letter-spacing: var(--pl-ls-mono);
    text-transform: uppercase;
    color: var(--pl-muted-2);
    line-height: 1;
}
.pl-final__call-label .pl-bn { font-family: var(--pl-font-bn); text-transform: none; letter-spacing: 0; }
.pl-final__call-n {
    font-family: var(--pl-font-mono);
    font-size: clamp(1.125rem, 1.9vw, 1.375rem);
    font-weight: var(--pl-w-semibold);
    letter-spacing: -.02em;
    line-height: 1.2;
    color: var(--pl-ink);
    font-variant-numeric: tabular-nums;
}
.pl-final__call-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    flex: none;
    border-radius: 50%;
    background: var(--pl-grad);
    color: #fff;
    box-shadow: var(--pl-sh-brand);
}

.pl-final__micro {
    margin-top: 1.5rem;
    font-size: var(--pl-t-sm);
    color: var(--pl-muted);
}

/* ----------------------------------------------------------------- 15. footer */

/*
 * The footer sits on the closing canvas rather than on white. The final CTA's
 * wash reaches its deepest tint (#dbe9ff) exactly at the section's bottom
 * edge, so the footer picks that colour up and carries it to the end of the
 * page — otherwise the tint stops in a hard line above the footer. Same
 * reason it has no border-top: the colour is continuous, so a rule across it
 * would draw the seam back in.
 */
.pl-foot {
    position: relative;
    isolation: isolate;
    padding: clamp(3rem, 5vw, 4.5rem) 0 2rem;
    background: linear-gradient(180deg, #dbe9ff 0%, #deebff 55%, #e4eeff 100%);
    font-size: var(--pl-t-sm);
    color: var(--pl-muted);
}
/* The CTA's pinstripes run at full strength along its bottom edge, so they
   have to continue here too and fade out inside the footer. */
.pl-foot::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: repeating-linear-gradient(90deg,
        rgba(255, 255, 255, .65) 0 1px,
        rgba(255, 255, 255, 0) 1px 92px);
    -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 70%);
            mask-image: linear-gradient(180deg, #000 0%, transparent 70%);
}

/*
 * Four columns, and they are not four equal ones. The brand blurb wants a
 * measure; the two menus are short labels; contact holds the longest single
 * string on the page (the street address) beside a fixed-width icon, so it
 * gets more room than a menu and less than the blurb. Equal columns would
 * break the address over three lines while the menus sat in half-empty ones.
 */
.pl-foot__top {
    display: grid;
    grid-template-columns: 1.7fr .85fr .85fr 1.3fr;
    gap: clamp(1.75rem, 3vw, 3rem);
}
.pl-foot__brand img { height: 30px; width: auto; margin-bottom: 1.15rem; }
.pl-foot__brand p { max-width: 42ch; line-height: 1.65; }

.pl-foot h4 {
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-2xs);
    font-weight: var(--pl-w-semibold);
    letter-spacing: var(--pl-ls-mono);
    text-transform: uppercase;
    color: var(--pl-ink);
    margin-bottom: 1.15rem;
}
/* Same exception the eyebrow makes: Bengali has no uppercase and no tracking
   tradition, so forcing both on a column heading turns it into noise. */
.pl-foot h4 .pl-bn {
    font-family: var(--pl-font-bn);
    font-size: var(--pl-t-xs);
    text-transform: none;
    letter-spacing: 0;
}

.pl-foot ul { display: grid; gap: .7rem; }

/*
 * Menu links move on hover rather than only changing colour, borrowing the
 * page's one motion idiom — the same nudge the pill buttons give their badge.
 * The shift is on the inline start so a Bengali label travels the same way.
 */
/*
 * Scoped away from the contact column on purpose. This rule sets a `display`,
 * and at (0,1,1) it outranks .pl-foot__row's `display: grid` — leave it on
 * every footer anchor and the contact rows quietly stop being two-column
 * grids, which reads as correct only for as long as their value stays inline.
 */
.pl-foot__col:not(.pl-foot__col--contact) a {
    display: inline-block;
    transition: color .15s ease, transform .15s ease;
}
.pl-foot__col:not(.pl-foot__col--contact) a:hover {
    color: var(--pl-brand-deep);
    transform: translateX(3px);
}

/*
 * Contact is data, not navigation, and the brand chip is what says so. The
 * icon keeps its own column so the values line up whether or not the text
 * beside them wraps.
 */
.pl-foot__col--contact ul { gap: .85rem; }
/*
 * The row grid goes on .pl-foot__row and NOT on the <li>, because a row is
 * sometimes the <li> itself (the address, which links nowhere) and sometimes
 * the <a> inside it. Putting it on both nests one two-column grid inside
 * another and the anchor gets laid into the 26px icon track.
 */
.pl-foot__row {
    display: grid;
    grid-template-columns: 20px minmax(0, 1fr);
    align-items: start;
    gap: .6rem;
}
a.pl-foot__row { transition: color .15s ease; }
a.pl-foot__row:hover { color: var(--pl-brand-deep); }

/*
 * The glyph is bare — no chip, no ground. Both boxes are given the SAME
 * height as one line of the text beside them (1.5em, the --pl-lh below) and
 * centre their own content, so the icon sits on the first line's optical
 * centre whether that line stands alone or the value wraps to three. Nudging
 * a padding-top until it "looks right" only holds at one font size.
 */
.pl-foot__ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 1.5em;
    color: var(--pl-brand);
    transition: color .15s ease;
}
a.pl-foot__row:hover .pl-foot__ico { color: var(--pl-brand-deep); }
/* Block, not the span's default inline: an inline box is only as tall as its
   text (19px), so it starts 3.5px below the row and the icon centred on the
   21px LINE box sits high by half of that. As a block both boxes are the same
   1.5 line and the two centres coincide by construction. */
.pl-foot__val {
    display: block;
    line-height: 1.5;
}
/* A phone number reads as one token; letting it wrap on its spaces turns it
   into three. The email is allowed to break because it can be genuinely long. */
.pl-foot__val--nowrap { white-space: nowrap; }

.pl-foot__bot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .5rem 1.5rem;
    margin-top: clamp(2.25rem, 4vw, 3.25rem);
    padding-top: 1.5rem;
    /* A blue-tinted rule instead of --pl-line: the grey one reads as dirt on
       the canvas tint. */
    border-top: 1px solid rgba(13, 110, 243, .14);
    font-size: var(--pl-t-xs);
    color: var(--pl-muted-2);
}
/* The page's last readout, and the last appearance of the mono face. */
.pl-foot__stamp {
    font-family: var(--pl-font-mono);
    font-size: var(--pl-t-3xs);
    font-weight: var(--pl-w-medium);
    letter-spacing: var(--pl-ls-mono);
    text-transform: uppercase;
}

/* ------------------------------------------------------------- 16. modal */

.pl-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 100;
    align-items: center;
    justify-content: center;
    padding: var(--pl-gutter);
    background: rgba(7, 27, 77, .82);
    backdrop-filter: blur(6px);
}
.pl-modal.is-open { display: flex; }
.pl-modal__box {
    position: relative;
    width: min(960px, 100%);
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: var(--pl-r-lg);
    background: #000;
    box-shadow: var(--pl-sh-xl);
}
.pl-modal__box iframe { width: 100%; height: 100%; border: 0; }
.pl-modal__close {
    position: absolute;
    top: -2.75rem;
    right: 0;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    font-size: var(--pl-t-sm);
    font-weight: var(--pl-w-semibold);
    color: #fff;
}

/* -------------------------------------------------------- 17. scroll reveal */

/*
 * BLUR-REVEAL. Every .pl-reveal arrives out of soft focus — opacity, blur
 * and lift resolving together — rather than a flat fade sliding up. That
 * third channel is what reads as the block MATERIALISING into the page
 * instead of just sliding into place, and it is the one entrance this whole
 * file is built from: everything below is the same three properties, timed
 * differently.
 */
.pl-reveal {
    opacity: 0;
    filter: blur(var(--pl-blur));
    transform: translateY(var(--pl-rise));
    transition: opacity .85s var(--pl-ease-out) var(--d, 0ms),
                filter .85s var(--pl-ease-out) var(--d, 0ms),
                transform .85s var(--pl-ease-out) var(--d, 0ms);
}
.pl-reveal.is-in { opacity: 1; filter: blur(0); transform: none; }

/*
 * TITLE / SUBTITLE / TEXT, staggered inside the block that is already
 * revealing. A .pl-reveal arriving as one piece says "this area is here";
 * it does not say "read the label, then the headline, then the line under
 * it" — which is the order a visitor actually reads a section in. So every
 * eyebrow, heading and lead paragraph carries its OWN blur-reveal on top of
 * its parent's, each a deliberate beat later than the one before — long
 * enough gaps to actually watch the sentence assemble itself, not a flicker.
 * No new observer and no markup: these key off the ancestor .pl-reveal's own
 * .is-in, the same class the IntersectionObserver already adds once, in
 * section 8 of public/js/landing-pos.js. The heading also arrives a little
 * larger than rest, scaling down into place — weight, not just position.
 *
 * Elements that are themselves a .pl-reveal (the standalone leads in the
 * final CTA, the footer columns) are UNAFFECTED — the selector requires an
 * ANCESTOR .pl-reveal, and an element is not its own ancestor — so they keep
 * the one entrance they already had rather than doubling up.
 */
.pl-reveal .pl-eyebrow,
.pl-reveal .pl-h1,
.pl-reveal .pl-h2,
.pl-reveal .pl-h3,
.pl-reveal .pl-lead {
    opacity: 0;
    filter: blur(var(--pl-blur));
    transform: translateY(calc(var(--pl-rise) * .8));
    transition: opacity .8s var(--pl-ease-out),
                filter .8s var(--pl-ease-out),
                transform .8s var(--pl-ease-out);
}
.pl-reveal .pl-h1,
.pl-reveal .pl-h2,
.pl-reveal .pl-h3 { transform: translateY(calc(var(--pl-rise) * .8)) scale(1.04); }
.pl-reveal .pl-eyebrow { transition-delay: var(--d, 0ms); }
.pl-reveal .pl-h1,
.pl-reveal .pl-h2,
.pl-reveal .pl-h3 { transition-delay: calc(var(--d, 0ms) + 180ms); }
.pl-reveal .pl-lead { transition-delay: calc(var(--d, 0ms) + 380ms); }

.pl-reveal.is-in .pl-eyebrow,
.pl-reveal.is-in .pl-h1,
.pl-reveal.is-in .pl-h2,
.pl-reveal.is-in .pl-h3,
.pl-reveal.is-in .pl-lead {
    opacity: 1;
    filter: blur(0);
    transform: none;
}

/*
 * Repeated rows and cards inside a revealed block arrive one after another
 * rather than all at once — the trade ledger, the four outcomes, the two
 * pricing columns and the two proof stats. Six explicit steps covers every
 * list on the page; a list longer than that (the eight module lenses, the
 * benefit rows) already carries its own per-item --d from the loop that
 * renders it. Wider gaps than the old pass — .09s apart, not .09s total —
 * so the ladder is something you actually see happen, row by row.
 */
.pl-fit__col .pl-trade,
.pl-outcomes .pl-outcome,
.pl-price .pl-price__col,
.pl-proof__stats > li {
    opacity: 0;
    filter: blur(calc(var(--pl-blur) * .6));
    transform: translateY(28px);
    transition: opacity .7s var(--pl-ease-out),
                filter .7s var(--pl-ease-out),
                transform .7s var(--pl-ease-out);
}
.pl-fit__col.is-in .pl-trade,
.pl-fit__col.is-in .pl-outcome,
.pl-price.is-in .pl-price__col,
.pl-proof__card.is-in .pl-proof__stats > li {
    opacity: 1;
    filter: blur(0);
    transform: none;
}
.pl-trade:nth-child(1), .pl-outcome:nth-child(1), .pl-price__col:nth-child(1), .pl-proof__stats > li:nth-child(1) { transition-delay: .08s; }
.pl-trade:nth-child(2), .pl-outcome:nth-child(2), .pl-price__col:nth-child(2), .pl-proof__stats > li:nth-child(2) { transition-delay: .2s; }
.pl-trade:nth-child(3), .pl-outcome:nth-child(3) { transition-delay: .32s; }
.pl-trade:nth-child(4), .pl-outcome:nth-child(4) { transition-delay: .44s; }
.pl-trade:nth-child(5) { transition-delay: .56s; }
.pl-trade:nth-child(6) { transition-delay: .68s; }

/*
 * The two full-scale product screens on the page — the hero dashboard and
 * the AI assistant capture — get their own entrance rather than borrowing
 * the block fade-up every other .pl-reveal uses. A screenshot is a screen,
 * and a screen switches on; a left-to-right clip-path wipe says that, where
 * a fade does not. It rides on top of the parent's own opacity/translateY,
 * timed to start just after the frame is in place rather than instead of it.
 *
 * Both .pl-hero__stage and .pl-ai__stage are themselves .pl-reveal, so the
 * same .is-in the IntersectionObserver already adds drives this too — no
 * script change, and prefers-reduced-motion's blanket transition-duration
 * override (section 19) collapses it to instant like everything else. The
 * no-JS path needs its own line, in the <noscript> block in the page head.
 */
.pl-hero__stage .pl-frame,
.pl-ai__stage .pl-frame {
    clip-path: inset(0 100% 0 0);
    transition: clip-path .85s cubic-bezier(.16, 1, .3, 1) calc(var(--d, 0ms) + 160ms);
}
.pl-hero__stage.is-in .pl-frame,
.pl-ai__stage.is-in .pl-frame {
    clip-path: inset(0 0 0 0);
}

/*
 * The picture inside the wipe gets its own, second entrance: it grows out
 * from its own centre, out of soft focus, as the wipe uncovers it — small,
 * blurred and gone, to full size, sharp and overshooting slightly past 100%
 * before it settles. The screen switches on AND the photograph BUBBLES into
 * place, which is the treatment every image on the page shares. Scoped to
 * the img rather than the figure because the figure is what the clip-path
 * above is cropping; scaling the crop itself would slide the picture inside
 * its own frame.
 */
.pl-hero__stage .pl-frame img,
.pl-ai__stage .pl-frame img {
    transform: scale(.4);
    filter: blur(24px);
    transform-origin: 50% 50%;
    transition: transform 1.1s var(--pl-ease-spring) calc(var(--d, 0ms) + 200ms),
                filter .9s var(--pl-ease-out) calc(var(--d, 0ms) + 200ms);
}
.pl-hero__stage.is-in .pl-frame img,
.pl-ai__stage.is-in .pl-frame img {
    transform: scale(1);
    filter: blur(0);
}

/*
 * The hero's one coloured phrase sweeps into its resting gradient instead of
 * simply appearing with the rest of the copy block — the single piece of
 * text on the page that gets its own motion, because it is the phrase
 * carrying the promise. Same ramp, same easing family as everything else on
 * the page; triggered by .pl-hero__copy's own .is-in, so it holds until the
 * copy block has started its own reveal rather than firing early.
 */
.pl-h1 em {
    background-size: 200% 100%;
    background-position: 100% 0;
}
.pl-hero__copy.is-in .pl-h1 em {
    animation: pl-shimmer-in .9s cubic-bezier(.16, 1, .3, 1) calc(var(--d, 0ms) + 220ms) both;
}
@keyframes pl-shimmer-in {
    to { background-position: 0 0; }
}

/* ------------------------------------------------------------ 18. responsive */

/*
 * The ring stops being a ring before its lenses stop being readable, not
 * after. Below 1020px eight lenses on a 300px orbit are either overlapping
 * or set in 11px type, so the geometry is dropped wholesale: the drawing
 * goes, the mark moves to the top as the thing the modules belong to, and
 * the same eight lenses become a plain grid in the same reading order.
 *
 * The size ladder survives, compressed. There is no container to query here
 * (container-type goes back to `normal` so the grid can size the cells), so
 * the frame's width is rebuilt from --lens in px: a fixed floor plus a
 * fraction of the ladder, which keeps the spread visible without the range a
 * ring can afford. Off the ring the ladder is no longer saying "these seats
 * had more room" — it is just variety — but a grid of eight identical frames
 * would read as a stock feature table, which is the one thing this section
 * exists not to be.
 */
@media (max-width: 1020px) {
    .pl-orbit__stage {
        aspect-ratio: auto;
        max-width: 780px;
        container-type: normal;
    }
    .pl-orbit__rings { display: none; }

    /*
     * `relative`, not `static`: the two .pl-orbit__pulse rings are
     * `position: absolute; inset: 0`, so the moment the core leaves the
     * positioned world they resolve against the stage instead and paint as
     * two ellipses the size of the whole stacked section.
     */
    .pl-orbit__core {
        position: relative;
        inset: auto;
        width: 108px;
        height: 108px;
        margin: 0 auto clamp(1.5rem, 3vw, 2.25rem);
    }

    .pl-orbit__nodes {
        position: static;
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 1.75rem .85rem;
    }
    .pl-orbit__node {
        position: static;
        width: auto;
        transform: none;
    }
    /* The drift is a property of floating on an orbit. On a grid it is just
       eight frames refusing to sit still. */
    .pl-orbit.is-in .pl-orbit__float { animation: none; }

    /*
     * Bottom-aligned, all the way down. The lenses in a row are deliberately
     * different heights, so anything anchored at the top leaves the two
     * captions beside each other at two different heights and the grid reads
     * as a mistake rather than a ladder. Pushing the stack to the bottom of
     * the row puts every caption on one line and lets the frames be uneven
     * where being uneven is the point.
     */
    .pl-orbit__float,
    .pl-orbit__card {
        flex: 1;
        width: 100%;
        justify-content: flex-end;
    }

    /*
     * Off the ring there is no stage to take a share of, so the ladder is
     * rebuilt against the GRID CELL: --lens runs 16.98 (Accounts) to 28.30
     * (Customers), which this maps to roughly 76% to 99% of the cell. Wider
     * than the ring could ever afford them, which is the point of dropping
     * the geometry — a phone has the width, it just does not have the room
     * to arrange anything around a circle.
     *
     * The height goes back to `auto` under $frameAr, and the mat inside works
     * exactly as it does on the ring, so the screens are still shown whole
     * here.
     */
    .pl-lens {
        width: calc(41% + var(--lens) * 2.05%);
        height: auto;
        aspect-ratio: 1.8;
    }
    .pl-lens__moon { width: 30px; height: 30px; }
    .pl-lens__moon svg { width: 16px; height: 16px; }

    .pl-orbit__body { width: 100%; margin-top: .7rem; }
    .pl-orbit__name { font-size: var(--pl-t-md); }
    /* Mono is a wide face and the grid cell is about 190px at the narrow end,
       so the readout drops below --pl-t-xs to keep the one-line contract that
       the 26-character limit in $modules is written against. */
    .pl-orbit__line {
        margin-top: .3rem;
        font-size: .6875rem;
        letter-spacing: 0;
    }
}

@media (max-width: 960px) {
    .pl-hero__inner { grid-template-columns: minmax(0, 1fr); gap: 2.25rem; }
    .pl-hero__copy { max-width: 640px; }
    /* The rail stops sitting beside the answer and stacks above it. Reading
       order is unchanged — trades, then what changes — so the argument
       survives the fold even though the "read across" no longer does. */
    .pl-fit__grid { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }
    .pl-proof__card { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
    .pl-proof__stats { justify-content: center; text-align: center; }
    .pl-proof__l { margin-inline: auto; }
    /* The blurb takes the full width and the three columns move under it.
       They are not equal thirds: two of them hold four short labels and the
       third holds a street address, so splitting the row evenly leaves a
       400px void between the menus and still wraps the address. */
    .pl-foot__top { grid-template-columns: minmax(0, .8fr) minmax(0, .8fr) minmax(0, 1.4fr); }
    .pl-foot__brand { grid-column: 1 / -1; }

    /* Copy above, screen below. The rail centres so the heading does not sit
       against the left edge of a full-width column with nothing beside it. */
    .pl-ai__grid { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }
    .pl-ai__copy { max-width: 640px; margin-inline: auto; }
    .pl-ai__benefits { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .35rem 1rem; }
}

@media (max-width: 860px) {
    /* The bar collapses to brand + language + burger. The CTA moves into the
       drawer rather than being duplicated in both — at this width the two
       together push the burger off the edge. */
    .pl-nav__inner { grid-template-columns: 1fr auto; padding-right: .5rem; }
    .pl-nav__links { display: none; }
    .pl-nav__cta { display: none; }
    .pl-nav__toggle { display: inline-flex; }
    .pl-nav__inner {
        background: rgba(255, 255, 255, .88);
        backdrop-filter: saturate(180%) blur(14px);
        -webkit-backdrop-filter: saturate(180%) blur(14px);
        border-color: rgba(13, 110, 243, .1);
        box-shadow: 0 6px 24px -12px rgba(7, 27, 77, .28);
    }

    /* One column. The spine turns through 90 degrees and becomes the seam
       between the two halves rather than disappearing — it is still one
       object, just folded. */
    .pl-price { grid-template-columns: minmax(0, 1fr); max-width: 480px; }
    .pl-price__col + .pl-price__col {
        /* A real dashed border, not the background the vertical spine uses:
           stacked, the seam lands under the second half's flag rail, whose
           own opaque fill would paint straight over a background image. */
        background-image: none;
        border-top: 1px dashed var(--pl-line-2);
    }
    /* Height-matching only exists to align the two halves side by side. */
    .pl-price__note { min-height: 0; }
}

@media (max-width: 640px) {
    /*
     * A trade name and its readout no longer fit on one line, so the ledger
     * row folds: name, then the readout under it, both in the text column.
     * The rail stays a list of rows rather than becoming six cards.
     */
    .pl-trade {
        grid-template-columns: 34px minmax(0, 1fr);
        row-gap: .3rem;
        padding: .85rem .9rem;
    }
    .pl-trade__icon { grid-row: 1 / 4; }
    .pl-trade__tag { grid-column: 2; grid-row: 3; justify-self: start; }
    .pl-trade__line { grid-column: 2; grid-row: 2; }
    .pl-ai__benefits { grid-template-columns: minmax(0, 1fr); }
    .pl-ai__benefit { padding: .55rem .25rem; }
    .pl-outcomes { grid-template-columns: minmax(0, 1fr); }
    /* Five stacked cards is a long scroll on a phone. They keep the card
       ground but lose most of their padding, so the section reads as a list
       of five points rather than five separate panels. */
    .pl-foot__top { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Contact is the one block that cannot share a phone-width column with a
       menu: the address wraps to three lines beside a 26px chip. It keeps the
       full width under the two menus. */
    .pl-foot__col--contact { grid-column: 1 / -1; }

    .pl-proof__stats { flex-direction: column; gap: 1.15rem; }
    .pl-proof__l { white-space: normal; max-width: 24ch; }
    .pl-proof__stats li + li { padding-left: 0; padding-top: 1.15rem; border-left: 0; border-top: 1px solid var(--pl-line); width: 100%; }

    /* The toggle drops onto its own line under the amount and squares up
       with it, rather than hanging off the right edge. */
    .pl-toggle { margin-left: 0; }

    .pl-ctas { flex-direction: column; align-items: stretch; }
    .pl-ctas .pl-btn { width: 100%; }
    .pl-hero__ctas .pl-btn--pill { justify-content: space-between; padding-left: 1.5rem; }

    .pl-final__call { width: 100%; justify-content: space-between; padding-left: 1.15rem; }
    .pl-final__bot, .pl-foot__bot { flex-direction: column; }
    .pl-foot__bot { align-items: flex-start; }
}

@media (max-width: 420px) {
    .pl-orbit__nodes { grid-template-columns: minmax(0, 1fr); }
    /* Two spec columns stop fitting a label and its value on one line here. */
    .pl-price__quota { grid-template-columns: minmax(0, 1fr); }
}

/* --------------------------------------------------------- 19. reduced motion */

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .pl :where(*, *::before, *::after) {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
    .pl-reveal,
    .pl-reveal .pl-eyebrow,
    .pl-reveal .pl-h1,
    .pl-reveal .pl-h2,
    .pl-reveal .pl-h3,
    .pl-reveal .pl-lead,
    .pl-fit__col .pl-trade,
    .pl-fit__col .pl-outcome,
    .pl-price .pl-price__col,
    .pl-proof__stats > li,
    .pl-hero__stage .pl-frame img,
    .pl-ai__stage .pl-frame img {
        opacity: 1;
        filter: none;
        transform: none;
    }
    /*
     * The orbit keeps its geometry — the shape IS the argument — and simply
     * stops. The blanket rule above already collapses every duration to
     * nothing; these lines make sure the cards, the mark and the lenses end
     * up visible and level rather than frozen at their entry scale, blur or
     * mid-wobble tilt.
     */
    .pl-orbit__rings,
    .pl-orbit__core,
    .pl-orbit__card { opacity: 1; filter: none; transform: none; }
    .pl-lens { rotate: 0deg; }
    .pl-orbit__pulse { display: none; }
    /* The marquee is the one motion that would leave content unreadable if it
       simply stopped mid-travel, so it becomes a static wrapped row instead. */
    .pl-marquee { -webkit-mask-image: none; mask-image: none; }
    .pl-marquee__track {
        animation: none;
        flex-wrap: wrap;
        width: auto;
        justify-content: center;
        gap: 1.5rem 2.5rem;
    }
    .pl-marquee__track li:nth-child(n + 7) { display: none; }
}

/* ----------------------------------------------------------------- 20. print */

@media print {
    .pl-nav, .pl-modal, .pl-nav__drawer, .pl-skip { display: none !important; }
    .pl-reveal,
    .pl-reveal .pl-eyebrow, .pl-reveal .pl-h1, .pl-reveal .pl-h2, .pl-reveal .pl-h3, .pl-reveal .pl-lead,
    .pl-orbit__card, .pl-hero__stage .pl-frame img, .pl-ai__stage .pl-frame img {
        opacity: 1; filter: none; transform: none;
    }
    .pl-canvas, .pl-ai, .pl-foot { background: #fff !important; color: #000 !important; }
    .pl-fit__band::before, .pl-fit__band::after { display: none !important; }
    .pl-foot::before { display: none; }
    /* Background-clipped type has no colour of its own and prints as nothing. */
    .pl-grad-text, .pl-h1 em, .pl-eyebrow, .pl-proof__n {
        background: none !important;
        color: #000 !important;
    }
}
