/*
 * Public marketing site. Deliberately independent of the panel's AdminLTE theme: a
 * prospect on a phone should not be downloading a dashboard framework to read a price
 * list, and the two surfaces have opposite jobs - one is a tool, one is a pitch.
 *
 * Mobile first throughout. Every media query adds at a wider breakpoint; nothing is
 * clawed back for small screens, so the phone layout is the one that cannot rot.
 *
 * ---------------------------------------------------------------------------
 * 2026-08-11 rebuild. The page it replaces was an editorial one - white throughout,
 * hairlines, restrained type - and the report on it was that it read as vague and as a
 * wall of text on a phone. Three things changed:
 *
 *  1. The page now has two dark anchors, the hero and the closing ask, with the light
 *     chapters between them. A visitor lands on something built rather than on a
 *     paragraph, and the page closes on the same note. Everything in between is still
 *     white and off-white - what was wrong was not that the chapters were quiet, it was
 *     that nothing on the page was ever loud.
 *  2. Motion carries the reading order: content arrives as it is reached, the accordion
 *     and the gallery open, the timeline draws itself. All of it is suppressed under
 *     prefers-reduced-motion, and none of it is required to understand the page.
 *  3. The long lists are behind disclosure. The network chapter is an accordion and the
 *     dashboard chapter is a gallery, so a phone gets six headings and one screenshot
 *     instead of thirty bullets and seven panels.
 *
 * The bug that made the old page look broken, kept here so it is not reintroduced: the
 * section header and the mocked table's column header were both called .ld-head. Equal
 * specificity, so the later of the two won and every section heading below 900px was laid
 * out as a cramped uppercase flex row - and overflowed the viewport, which is what made a
 * phone render the page zoomed out. The table header is .ld-thead now. Do not name
 * anything .ld-head again.
 */

:root {
    --ld-bg: #ffffff;
    --ld-bg-alt: #f4f7fc;
    --ld-surface: #ffffff;
    --ld-border: #e3e8ef;
    --ld-text: #0d1526;
    --ld-muted: #56617a;
    --ld-accent: #1d6fe0;
    --ld-accent-soft: #eaf1fd;
    --ld-accent-strong: #17539f;
    --ld-success: #0f9d58;

    /* The dashboard's own blue, held separately from --ld-accent so that the per-section
       tones below can move the accent without moving the replica. See the tones block. */
    --ld-brand: #1d6fe0;
    --ld-brand-soft: #eaf1fd;
    --ld-brand-strong: #17539f;

    /* Text and icons sitting ON an accent-filled surface - a solid button, a step number,
       the "Most popular" flag. Never hardcode #fff for these. In light mode the accents are
       dark and white is right, but the dark scheme lifts every accent to a pale tint so it
       can be read as text on a near-black page, and white on those runs 1.9:1 to 2.7:1 - the
       amber Subscribe button was effectively invisible. Flipping this one token to a near
       black in dark mode takes the same surfaces to 7:1 or better. */
    --ld-on-accent: #ffffff;

    /*
     * The ink. Fixed in both schemes, because the two bands it paints - the hero and the
     * closing ask - are dark on purpose rather than dark because the visitor's phone is.
     * Anything set on the ink therefore has to name its own foreground: the page tokens
     * above flip with the scheme and would leave dark text on dark ground half the time.
     */
    --ld-ink: #070c17;
    --ld-ink-2: #101a2e;
    --ld-on-ink: #eef3fb;
    --ld-on-ink-muted: #9dadc8;
    --ld-ink-line: rgba(255, 255, 255, .12);
    --ld-ink-surface: rgba(255, 255, 255, .06);

    /* The grey the fixed header and the footer share. Deliberately one token: they bracket
       the page, and a reader should register them as the same furniture rather than as two
       more coloured bands competing with the section tones in between. */
    --ld-chrome: #f1f4f9;
    --ld-header-bg: rgba(241, 244, 249, .82);

    /*
     * One gradient, used in four places and nowhere else: the hero's highlighted phrase, the
     * chapter number chips, the icon tiles and the meters. A gradient that appears on every
     * surface is the loudest thing a generated page does; a gradient that only ever marks
     * the same class of object reads as a brand mark.
     */
    --ld-glow-1: #2f7bf6;
    --ld-glow-2: #6f5cff;
    --ld-glow-3: #17c1c6;
    --ld-grad: linear-gradient(115deg, var(--ld-glow-1) 0%, var(--ld-glow-2) 52%, var(--ld-glow-3) 100%);

    --ld-shadow: 0 1px 2px rgba(13, 21, 38, .05), 0 12px 32px rgba(13, 21, 38, .07);
    --ld-shadow-lg: 0 2px 6px rgba(13, 21, 38, .06), 0 28px 60px -18px rgba(13, 21, 38, .28);

    /*
     * Three faces, three jobs. Loaded in layouts/public.blade.php.
     *
     * The display face is used for headings and nothing else - it has the tighter apertures
     * and the shorter extenders that make a large line look drawn rather than defaulted, and
     * the same qualities make it a poor choice for a paragraph. Body copy stays on Source
     * Sans 3, which also has by far the widest glyph coverage of the three: the page is
     * published in six languages, and Devanagari and Bengali resolve through the fallbacks
     * below rather than through either of the two Latin-only additions.
     *
     * Mono is not decoration. It sets the chapter labels, every figure and every technical
     * token, so numbers line up in a column and read as measurements. A tabular figure in a
     * price list or a stat tile is the single cheapest signal that somebody laid the page out
     * on purpose.
     */
    --ld-font-display: "Instrument Sans Variable", "Source Sans 3", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --ld-font-body: "Source Sans 3", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --ld-font-mono: "JetBrains Mono Variable", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

    /* Three radii. The 10px the previous pass settled on held an edge but made every panel
       on the page read as the same small card; the larger step is what a surface that holds
       a whole screenshot needs, and the small one still belongs to controls. */
    --ld-radius: 16px;
    --ld-radius-sm: 10px;
    --ld-radius-lg: 24px;

    /*
     * The hairline the layout is built out of. Lighter than --ld-border, because it
     * separates chapters rather than boxing content, and at full border strength a page with
     * this many rules on it starts to look like a spreadsheet.
     */
    --ld-rule: #e9eef6;

    --ld-header-h: 64px;

    /* Set by landing.js from the scroll position, read by the header's progress rule. */
    --ld-progress: 0;
}

@media (prefers-color-scheme: dark) {
    :root {
        --ld-bg: #0b1017;
        --ld-bg-alt: #111925;
        --ld-surface: #141c28;
        --ld-border: #253044;
        --ld-text: #e8edf5;
        --ld-muted: #99a5ba;
        --ld-accent: #5c9dff;
        --ld-accent-soft: #16243a;
        --ld-accent-strong: #8fbcff;
        --ld-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 32px rgba(0, 0, 0, .45);
        --ld-shadow-lg: 0 2px 6px rgba(0, 0, 0, .5), 0 28px 60px -18px rgba(0, 0, 0, .75);

        --ld-brand: #5c9dff;
        --ld-brand-soft: #16243a;
        --ld-brand-strong: #8fbcff;

        /* See the light-mode note: the accents are pale here, so what sits on them is dark. */
        --ld-on-accent: #0d1117;

        /* A grey that still reads as chrome on a near-black page: lifted off --ld-bg rather
           than darkened, or the header and footer disappear into the page entirely. */
        --ld-chrome: #131b26;
        --ld-header-bg: rgba(19, 27, 38, .84);

        /* Sits between --ld-bg and --ld-border. A hairline that works on white by being
           lighter than the border has to work here by being darker than it. */
        --ld-rule: #1c2534;
    }
}

* { box-sizing: border-box; }

body.ld {
    margin: 0;
    background: var(--ld-bg);
    color: var(--ld-text);
    font-family: var(--ld-font-body);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    /* clip rather than hidden: hidden on the body makes the whole document a scroll
       container on iOS, which breaks position:sticky inside it and lets the viewport zoom
       out to fit an overflowing child instead of clipping it. */
    overflow-x: clip;
}

/* Nothing on the page is allowed to be wider than the page. Every element that could be -
   a mocked table, a code, a long host name - says so where it is defined, but this is the
   backstop, and it is what a phone rendering the page zoomed out is telling you is
   missing. */
.ld img,
.ld svg,
.ld video { max-width: 100%; }

/*
 * Headings take the display face and a negative track. The tracking is the half of it that
 * does the work: a display face set at its default spacing at 40px still looks like body
 * copy that happened to be enlarged, because letter-spacing that is right at 16px is loose
 * at 40px. Optical sizing is what a typesetter would do by hand.
 */
.ld h1,
.ld h2,
.ld h3,
.ld .ld-h2 {
    font-family: var(--ld-font-display);
    font-weight: 600;
    letter-spacing: -.022em;
}

/*
 * Neither addition has an Arabic, Devanagari or Bengali glyph, so those scripts resolve
 * through the fallbacks and are set in whatever the system provides. That is the correct
 * outcome and needs no undoing - but the negative track above is not: it is measured for
 * Latin, and applied to a joined script it pulls the letterforms into each other. Arabic and
 * Urdu also sit lower and need the extra leading back.
 */
[dir="rtl"] .ld h1,
[dir="rtl"] .ld h2,
[dir="rtl"] .ld h3,
[dir="rtl"] .ld .ld-h2 {
    letter-spacing: normal;
    line-height: 1.4;
}

/*
 * Figures are tabular everywhere on this page. A price list, a stat tile and a plan's
 * allowances are all columns of numbers that a reader compares down the column rather than
 * reads across, and proportional digits make that comparison harder for no benefit.
 */
.ld-mono,
.ld .ld-num {
    font-family: var(--ld-font-mono);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/*
 * Type that fades down its own height. Used on the two headlines that sit on the ink, where
 * it is the cheapest way to make a large line look lit rather than filled.
 *
 * The plain colour is declared first and is not decoration: a browser without
 * background-clip:text paints the transparent fill and the headline disappears entirely, so
 * the fallback has to be a real, readable colour rather than the gradient's first stop.
 */
.ld-grad-text {
    color: var(--ld-on-ink);
    background: linear-gradient(180deg, #ffffff 34%, #9db4d6 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/*
 * Buttons are excluded, and the :not() is load-bearing rather than tidiness.
 *
 * `.ld a` is a class plus a type selector, so it scores (0,1,1) - higher than the single
 * class of `.ld-btn-primary` (0,1,0). Every anchor styled as a button therefore took its
 * colour from here and none of the variant rules below applied. On a primary button that
 * meant --ld-accent text on a --ld-accent fill: the same colour twice, so the label was
 * simply not there. The hover pair did it again with --ld-accent-strong.
 *
 * It went unnoticed for so long because the two variants it does not erase are the visible
 * ones: a ghost button on a white surface and the green WhatsApp button both stayed
 * readable, just in the wrong colour.
 */
.ld a:not(.ld-btn) { color: var(--ld-accent); text-decoration: none; }
.ld a:not(.ld-btn):hover { color: var(--ld-accent-strong); }

/* Buttons carry their own colour from the variant rules; this is only what an anchor needs
   that a <button> gets for free. */
.ld a.ld-btn { text-decoration: none; }

/* Off-screen until it is focused, rather than display:none - a hidden skip link is no skip
   link, but an always-visible one is the first thing every visitor reads. */
.ld-skip {
    position: absolute;
    inset-inline-start: -9999px;
    top: 0;
    z-index: 100;
    padding: 12px 18px;
    background: var(--ld-accent);
    color: var(--ld-on-accent);
    font-weight: 700;
    border-radius: 0 0 12px 0;
}

.ld-skip:focus {
    inset-inline-start: 0;
    color: var(--ld-on-accent);
}

/* One focus ring for the whole page, drawn in the tone. The default is a 1px system outline
   that vanishes on both the ink bands. */
.ld :focus-visible {
    outline: 2px solid var(--ld-accent);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Tapping a card or a button should not leave a grey box behind on iOS. */
.ld a, .ld button, .ld summary { -webkit-tap-highlight-color: transparent; }

/*
 * Press states, and the reason this block exists at all.
 *
 * Suppressing the tap highlight above removes the only feedback a touch screen gives for
 * free, and everything on this page that reacted to being touched reacted inside
 * `@media (hover: hover)` - which a phone does not match. The result was a page where every
 * control was silent under the thumb: the tab strip, the plan filters, the FAQ rows, the
 * footer and the language menu all changed nothing at all when tapped, so a slow response
 * from the page was indistinguishable from a tap that had missed.
 *
 * So each of them gets an explicit :active. Colour rather than movement, matching the hover
 * behaviour elsewhere, and with no transition on the way in - a press state that eases in
 * over 160ms has already been let go of by the time it arrives, which is worse than none.
 * They are not inside a `(hover: none)` query on purpose: a press state is correct with a
 * mouse too, and the two never apply at the same moment.
 *
 * Extend this whenever a control is added. It is invisible in every desktop browser and no
 * test catches it.
 */
.ld-tab:active,
.ld-filter:active,
.ld-faq summary:active,
.ld-acc-item > summary:active,
.ld-lang > summary:active,
.ld-lang-menu a:active,
.ld-footer a:active,
.ld-nav a:active {
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    transition: none;
}

/* The buttons already dip a pixel; what they lacked was any change of colour, which is the
   half of it that survives being looked at on a phone in daylight. */
.ld-btn-primary:active { background: var(--ld-accent-strong); }
.ld-btn-ghost:active { background: var(--ld-bg-alt); border-color: var(--ld-accent); }
.ld-btn-wa:active { background: #1eb455; }

.ld-wrap {
    width: 100%;
    max-width: 1180px;
    margin-inline: auto;
    padding-inline: 20px;
}

@media (min-width: 768px) { .ld-wrap { padding-inline: 32px; } }

/* ---------------------------------------------------------------- header */

/*
 * Fixed, and frosted rather than translucent.
 *
 * It was flatly translucent once, which meant the content scrolling under it showed through
 * and the bar changed colour on the way down as each section passed beneath. A blurred
 * backdrop over a near-opaque grey keeps the depth without ever becoming unreadable, and
 * the fallback for a browser with no backdrop-filter is the solid grey it used to be.
 *
 * position: fixed takes it out of the flow, so the body has to be padded by hand - see
 * .ld-header-offset. Everything that jumps to an anchor already allows for the bar through
 * scroll-margin-top.
 */
.ld-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 40;
    background: var(--ld-chrome);
    border-bottom: 1px solid transparent;
    transition: box-shadow .2s ease, border-color .2s ease, background-color .2s ease;
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
    .ld-header {
        background: var(--ld-header-bg);
        -webkit-backdrop-filter: saturate(1.6) blur(14px);
        backdrop-filter: saturate(1.6) blur(14px);
    }
}

.ld-header.is-stuck {
    border-bottom-color: var(--ld-border);
    box-shadow: 0 6px 24px rgba(13, 21, 38, .07);
}

/*
 * How far down the page the reader is, drawn as a 2px rule along the bottom edge of the bar.
 * The scaleX comes from a custom property landing.js writes on the document, so the scroll
 * handler touches one property and the compositor does the rest - no layout, no paint.
 */
.ld-progress {
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 2px;
    background: var(--ld-grad);
    transform: scaleX(var(--ld-progress, 0));
    transform-origin: left center;
    /* The bar measures the document, which does not mirror; a right-to-left page still
       fills from the start edge, which is the right. */
    pointer-events: none;
}

[dir="rtl"] .ld-progress { transform-origin: right center; }

.ld-header-offset { padding-top: var(--ld-header-h); }

/* Inside the panel's preview frame the page is topped by a banner. A fixed bar would cover
   it, so there the header rides along in the flow instead - sticky gives the same behaviour
   once the banner has scrolled away, and needs no offset. */
.ld-is-preview .ld-header { position: sticky; }

.ld-header-inner {
    display: flex;
    align-items: center;
    /* Brand hard left, everything else hard right. gap would otherwise let the two groups
       drift toward each other on a wide screen. */
    justify-content: space-between;
    gap: 16px;
    min-height: var(--ld-header-h);
}

/* The right-hand group: section links, then the two calls to action. Kept as one flex row so
   the nav sits beside the buttons rather than floating in the middle of the bar. */
.ld-header-side {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-inline-start: auto;
    min-width: 0;
}

.ld-brand {
    display: inline-flex;
    align-items: center;
    gap: 11px;
    font-weight: 700;
    font-size: 1.15rem;
    color: var(--ld-text);
    letter-spacing: -.01em;
    min-width: 0;
}

.ld-brand:hover { color: var(--ld-text); }

/* contain, never cover. The uploaded logo is a wide wordmark - roughly 2.7:1 - and the
   square 32x32 cover crop this replaced threw away about two thirds of it. Width follows
   the artwork's own aspect ratio, capped so an unusually wide upload still cannot push
   the nav off the header. */
.ld-brand img {
    height: 34px;
    width: auto;
    max-width: 132px;
    border-radius: 6px;
    object-fit: contain;
    flex: none;
}

.ld-brand-fallback {
    width: 36px;
    height: 36px;
    border-radius: 11px;
    background: var(--ld-grad);
    color: #fff;
    display: grid;
    place-items: center;
    font-size: 1rem;
    flex: none;
    box-shadow: 0 6px 16px -6px var(--ld-glow-2);
}

.ld-brand-text { display: grid; line-height: 1.16; min-width: 0; }

.ld-brand-name {
    font-weight: 800;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The second half of the lockup, and the fastest answer to "what is this?". Dropped on
   the narrowest screens, where the header has no room for two lines beside the buttons. */
.ld-brand-tag {
    display: none;
    font-size: .69rem;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--ld-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

@media (min-width: 560px) { .ld-brand-tag { display: block; } }
@media (min-width: 860px) { .ld-brand img { height: 40px; max-width: 168px; } }

.ld-nav {
    display: none;
    align-items: center;
    gap: 2px;
}

.ld-nav a {
    position: relative;
    color: var(--ld-muted);
    font-family: var(--ld-font-display);
    font-weight: 600;
    font-size: .93rem;
    padding: 8px 12px;
    border-radius: var(--ld-radius-sm);
    transition: color .16s ease, background-color .16s ease;
}

.ld-nav a:hover { color: var(--ld-text); background: var(--ld-bg-alt); }

/* The section currently under the reader, marked by landing.js. Tinted rather than
   underlined so it survives the tone changing beneath it - the link picks up whichever hue
   the section it points at is wearing. */
.ld-nav a.is-active {
    color: var(--ld-accent-strong);
    background: var(--ld-accent-soft);
}

/* A divider between the section links and the calls to action, so the right-hand side reads
   as "navigation, then actions" rather than as one undifferentiated row of six things. */
.ld-header-actions { display: flex; align-items: center; gap: 8px; }
.ld-nav:not(:empty) ~ .ld-header-actions { margin-inline-start: 8px; padding-inline-start: 14px; border-inline-start: 1px solid var(--ld-border); }

@media (max-width: 1039px) {
    /* The nav is hidden below this width, so its divider would be a rule against nothing. */
    .ld-header-actions { margin-inline-start: 0; padding-inline-start: 0; border-inline-start: 0; }
}

/* The hamburger is the only nav on a phone; the inline links appear from md up.
   44 square. It was 42, which is under the size a thumb hits reliably, and this is the
   control that opens the only navigation a phone has. */
.ld-burger {
    display: inline-grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius-sm);
    background: var(--ld-surface);
    color: var(--ld-text);
    font-size: 1.25rem;
    cursor: pointer;
}

.ld-burger:active { background: var(--ld-accent-soft); color: var(--ld-accent-strong); }

.ld-mobile-nav {
    display: grid;
    gap: 2px;
    padding: 8px 0 16px;
    border-top: 1px solid var(--ld-border);
    /* The menu scrolls independently: on a short screen in landscape the links plus the CTA
       are taller than the viewport, and a menu whose bottom cannot be reached is worse than
       none. */
    max-height: calc(100dvh - var(--ld-header-h));
    overflow-y: auto;
    overscroll-behavior: contain;
}

.ld-mobile-nav[hidden] { display: none; }

.ld-mobile-nav a {
    display: flex;
    align-items: center;
    gap: 10px;
    color: var(--ld-text);
    font-weight: 600;
    /* 46px tall including the text line: comfortably past the 44px a thumb needs, which
       matters more here than anywhere else on the site - this is the only navigation a
       phone has. */
    padding: 12px 10px;
    border-radius: var(--ld-radius-sm);
}

.ld-mobile-nav a:active { background: var(--ld-bg-alt); }

/* Sits apart from the links rather than reading as a seventh one. */
.ld-mobile-nav-cta { margin-top: 10px; }

/* While the menu is open the page behind it must not scroll - on iOS a swipe over an open
   overlay otherwise moves the page underneath and the menu appears to drift. */
body.ld-nav-open { overflow: hidden; }

/* 1040px, not 860px. Six section links plus the divider and two buttons need the room; at
   860 the bar was packing them tight enough that the nav ran into the Sign in button on a
   tablet. Below this the burger is the navigation, which loses nothing - the menu holds the
   same links and both calls to action. */
@media (min-width: 1040px) {
    .ld-nav { display: flex; }
    .ld-burger { display: none; }
    .ld-mobile-nav { display: none !important; }
}

/* ---------------------------------------------------------------- buttons */

.ld-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 13px 22px;
    border-radius: 999px;
    font-family: var(--ld-font-display);
    font-weight: 600;
    font-size: .95rem;
    letter-spacing: -.005em;
    border: 1px solid transparent;
    cursor: pointer;
    text-align: center;
    transition: background-color .16s ease, border-color .16s ease, color .16s ease, box-shadow .16s ease, transform .16s ease;
}

/* A press still moves, because a press is the one interaction where movement is the
   feedback. Hover lifts by a pixel and nothing more - see the motion block at the foot. */
.ld-btn:active { transform: translateY(1px); }

.ld-btn-primary { background: var(--ld-accent); color: var(--ld-on-accent); }
.ld-btn-primary:hover { background: var(--ld-accent-strong); color: var(--ld-on-accent); }

.ld-btn-ghost { background: var(--ld-surface); color: var(--ld-text); border-color: var(--ld-border); }
.ld-btn-ghost:hover { background: var(--ld-bg-alt); color: var(--ld-text); }

.ld-btn-wa { background: #25d366; color: #06301a; }
.ld-btn-wa:hover { background: #1eb455; color: #06301a; }

@media (hover: hover) {
    .ld-btn-wa:hover { box-shadow: 0 10px 26px -10px rgba(37, 211, 102, .8); transform: translateY(-1px); }
    .ld-btn-primary:hover { box-shadow: 0 10px 26px -12px var(--ld-accent); transform: translateY(-1px); }
}

/* The app download. A ghost button with the Android green on the icon alone: it sits in the
   hero beside the demo button, and a third filled colour there would turn one call to action
   into three of equal weight. */
.ld-btn-android .bi { color: #3ddc84; }

.ld-btn-sm { padding: 9px 16px; font-size: .89rem; }
.ld-btn-block { width: 100%; }

/* Below 480px the header runs out of room before it runs out of buttons. Dropped narrowest
   first: Sign in goes at 400px, the Demo button at 480px, and under 400px only the brand and
   the burger are left - the one arrangement that cannot wrap on a 320px screen. Both calls
   to action survive inside the menu, so nothing is actually lost. */
.ld-hide-xs { display: none; }
@media (min-width: 480px) { .ld-hide-xs { display: inline-flex; } }

.ld-hide-xxs { display: none; }
@media (min-width: 400px) { .ld-hide-xxs { display: inline-flex; } }

/* ----------------------------------------------------------------- tones
 *
 * Each section carries its own hue through three tokens, and the attribute is required on
 * every one of them - LandingPageTest fails on a section without one.
 *
 * What a tone is allowed to paint is deliberately narrow. There is no per-section background
 * wash and there must not be one again: five tinted bands down a page is a colour scheme
 * nobody chose, and it was the single loudest reason an earlier version of this page read as
 * generated. The hues stay desaturated and close together, so moving from one chapter to the
 * next reads as a considered shift rather than as a different site.
 *
 * A tone is still three declarations, and the chapter label, icons, links and buttons all
 * read --ld-accent, so nothing downstream has to know about any of this. Every accent clears
 * 4.5:1 on the page background and every strong variant clears it on the soft tint.
 */

[data-tone="violet"] {
    --ld-accent: #5c4a8f;
    --ld-accent-soft: #f1eff7;
    --ld-accent-strong: #463870;
}

[data-tone="teal"] {
    --ld-accent: #16706a;
    --ld-accent-soft: #e8f2f1;
    --ld-accent-strong: #0f544f;
}

[data-tone="amber"] {
    --ld-accent: #8a5a1c;
    --ld-accent-soft: #f7f1e6;
    --ld-accent-strong: #6b4514;
}

[data-tone="indigo"] {
    --ld-accent: #40509e;
    --ld-accent-soft: #eef0f8;
    --ld-accent-strong: #303c7c;
}

/* Blue restates the root accent rather than inheriting it. A section can rely on the
   cascade, but a tone applied inside another tone's section - the plan icon tiles under
   pricing - would otherwise come out bronze, because a tone block sets nothing else. */
[data-tone="blue"] {
    --ld-accent: #1d6fe0;
    --ld-accent-soft: #eef3fc;
    --ld-accent-strong: #17539f;
}

@media (prefers-color-scheme: dark) {
    /* Lifted well past their light-mode values - a #5c4a8f violet on a near-black page is
       barely distinguishable from the body text. */
    [data-tone="violet"] {
        --ld-accent: #ad9be0;
        --ld-accent-soft: #221e33;
        --ld-accent-strong: #c8bced;
    }

    [data-tone="teal"] {
        --ld-accent: #5cbdb5;
        --ld-accent-soft: #102624;
        --ld-accent-strong: #8ad6d0;
    }

    [data-tone="amber"] {
        --ld-accent: #d5a468;
        --ld-accent-soft: #2a2114;
        --ld-accent-strong: #e8c396;
    }

    [data-tone="indigo"] {
        --ld-accent: #9aa4e8;
        --ld-accent-soft: #1b1f33;
        --ld-accent-strong: #bcc3f2;
    }

    [data-tone="blue"] {
        --ld-accent: #79aeff;
        --ld-accent-soft: #16243a;
        --ld-accent-strong: #a7c9ff;
    }
}

/*
 * The replica is pinned back to the dashboard's own blue. The copy beside it promises these
 * are built from the dashboard's own colours, so a violet voucher code inside the violet
 * section would quietly make the page lie about the product.
 *
 * The surface tokens are restated for a second reason: the mock sits on the ink in the hero,
 * and if the hero remapped them for its own dark ground the white app window inside it would
 * come out with white borders on a white surface. Nothing in the hero touches these - this
 * block is the guarantee that nothing ever can.
 */
.ld-mock,
.ld-shot {
    --ld-accent: var(--ld-brand);
    --ld-accent-soft: var(--ld-brand-soft);
    --ld-accent-strong: var(--ld-brand-strong);
    color: var(--ld-text);
}

/* ---------------------------------------------------------------- sections */

.ld-section { position: relative; padding: 68px 0; }
.ld-section-alt { background: var(--ld-bg-alt); }

/* Anchor targets clear the fixed header. Without this every nav tap and every #pricing
   link lands with the section's label hidden behind the bar - worst on a phone, where
   the header is proportionally tallest and the menu closing hides the mistake. */
.ld-section,
.ld-hero,
#top { scroll-margin-top: calc(var(--ld-header-h) + 12px); }

@media (min-width: 768px) {
    .ld-section { padding: 104px 0; }
}

/*
 * The chapter label. Set in mono and in a numbered sequence, so the labels down the page
 * read as the contents list of a document rather than as nine unrelated badges. The number
 * is a CSS counter, so nothing in the Blade and nothing in any of the six translation files
 * has to know what order the sections are in - and the conditional team chapter leaves no
 * gap in the sequence.
 */
.ld-eyebrow {
    display: flex;
    align-items: center;
    gap: 12px;
    font-family: var(--ld-font-mono);
    font-size: .74rem;
    font-weight: 500;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ld-accent-strong);
    margin-bottom: 18px;
}

/* A short rule trailing the label, in the tone. This is the one place the hue paints
   anything larger than a glyph. flex:1 rather than a fixed width so it runs out to the edge
   of the label's column and lands on a different length in each language. */
.ld-eyebrow::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(to right, color-mix(in srgb, var(--ld-accent) 45%, transparent), transparent);
}

[dir="rtl"] .ld-eyebrow::after { background: linear-gradient(to left, color-mix(in srgb, var(--ld-accent) 45%, transparent), transparent); }

/*
 * Chapter numbers, counted rather than typed, and set in a filled chip.
 *
 * decimal-leading-zero because 01 and 09 are the same width as 10, and a column of labels
 * whose text starts at three different offsets is exactly the sort of detail that separates
 * a laid-out page from a generated one.
 */
#main { counter-reset: ld-chapter; }
.ld-section { counter-increment: ld-chapter; }

.ld-eyebrow::before {
    content: counter(ld-chapter, decimal-leading-zero);
    display: inline-grid;
    place-items: center;
    min-width: 30px;
    height: 22px;
    padding: 0 7px;
    border-radius: 999px;
    background: var(--ld-grad);
    color: #fff;
    font-weight: 700;
    letter-spacing: .06em;
    flex: none;
}

/* The hero's label is not a chapter: it is the product's own one-line description, it sits
   above the h1, and it is outside the counter's scope anyway. It keeps the mono treatment
   and drops the number, the rule and the stretch. */
.ld-hero .ld-eyebrow { display: inline-flex; }
.ld-hero .ld-eyebrow::before,
.ld-hero .ld-eyebrow::after { content: none; }

.ld-h2 {
    font-size: clamp(1.75rem, 5.4vw, 2.7rem);
    line-height: 1.12;
    margin: 0;
    text-wrap: balance;
}

.ld-lede {
    color: var(--ld-muted);
    font-size: 1.03rem;
    max-width: 62ch;
    margin: 0;
}

/*
 * The section header. The label and the heading hold the left column, the lede sits in a
 * second column to their right, and the whole header is closed by a hairline.
 *
 * It stays a single stack below 900px. Two columns on a phone would give the lede about
 * twenty characters a line.
 *
 * Do not rename anything else to .ld-head - see the note at the top of this file.
 */
.ld-head {
    display: grid;
    gap: 14px;
    margin-bottom: 34px;
    padding-bottom: 26px;
    border-bottom: 1px solid var(--ld-rule);
}

/* The label's own bottom margin is for the hero, where it is not in a grid. Inside the
   header it would stack on top of the row gap and open a 32px hole between the label and
   the heading it belongs to - worst on a phone, where the header is the whole screen. */
.ld-head .ld-eyebrow { margin-bottom: 0; }

@media (min-width: 900px) {
    /*
     * Placed explicitly rather than by source order, so the markup stays the flat
     * label/heading/lede stack it already is in every section and in the panel's preview.
     * Auto-placement would put the heading in the second column and drop the lede onto a
     * row of its own.
     *
     * The label spans both columns because its trailing rule is what closes the header's top
     * edge; stopping it at the first column would leave a rule that runs half way across and
     * then gives up.
     */
    .ld-head {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        column-gap: 56px;
        row-gap: 16px;
        margin-bottom: 46px;
    }

    .ld-head .ld-eyebrow { grid-column: 1 / -1; grid-row: 1; margin-bottom: 0; }
    .ld-head .ld-h2 { grid-column: 1; grid-row: 2; }

    /* Set against the bottom of the heading beside it rather than the top of the row, so a
       two-line heading and a three-line lede still finish level. */
    .ld-head .ld-lede { grid-column: 2; grid-row: 2; align-self: end; padding-bottom: 4px; }
}

@media (max-width: 899px) {
    .ld-head { gap: 10px; margin-bottom: 26px; padding-bottom: 20px; }
    .ld-head .ld-lede { font-size: .95rem; }
}

/*
 * The chapter ledes come off on a phone, and this is the single biggest thing on the page
 * for the complaint that it was all text.
 *
 * There are nine of them and they average two hundred characters, so on a 360px screen they
 * are the best part of two full screens of scrolling - and every one of them is a paragraph
 * restating the heading directly above it before the reader has been shown anything. On a
 * wide screen they cost nothing, because the header is two columns and the lede fills the
 * one the heading does not use; stacked under the heading on a phone they are pure delay.
 *
 * display:none rather than a shorter string, deliberately: the copy is the operator's own
 * pitch in six languages, it stays in the markup for a search engine and for the desktop
 * layout, and the decision is one rule to reverse if it is ever wanted back.
 */
@media (max-width: 699px) {
    .ld-head .ld-lede { display: none; }
    .ld-head { padding-bottom: 16px; }
}

/* ---------------------------------------------------------------- hero
 *
 * The first of the two ink bands. Everything set on it names its own foreground: the page
 * tokens flip with the visitor's scheme and the ink does not, so an inherited --ld-text
 * would be dark ink on dark ink half the time.
 */

.ld-hero {
    position: relative;
    isolation: isolate;
    padding: 54px 0 64px;
    background: var(--ld-ink);
    color: var(--ld-on-ink);
    overflow: hidden;
}

/* The ruled grid, and the reason it is still here. It says the product is infrastructure,
   it fills the band behind the headline without competing with it, and it costs no request.
   Masked so it fades out before it reaches the copy. */
.ld-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(to right, var(--ld-ink-line) 1px, transparent 1px),
        linear-gradient(to bottom, var(--ld-ink-line) 1px, transparent 1px);
    background-size: 64px 64px;
    -webkit-mask-image: radial-gradient(120% 90% at 76% 8%, #000 0%, transparent 70%);
    mask-image: radial-gradient(120% 90% at 76% 8%, #000 0%, transparent 70%);
    opacity: .5;
    pointer-events: none;
    z-index: -2;
}

/*
 * Two soft lights behind the band, drifting slowly on different cycles.
 *
 * These are the one piece of pure decoration on the page and they are here on purpose: a
 * flat dark rectangle reads as a placeholder, and a band with depth in it reads as a
 * surface. They are blurred radial gradients rather than images, they sit under everything
 * on their own z-index, and both stop dead under prefers-reduced-motion.
 */
.ld-hero-bg {
    position: absolute;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
}

.ld-orb {
    position: absolute;
    border-radius: 50%;
    filter: blur(70px);
    opacity: .55;
    will-change: transform;
}

.ld-orb-1 {
    width: 46vw;
    min-width: 300px;
    aspect-ratio: 1;
    top: -14%;
    inset-inline-start: -10%;
    background: radial-gradient(circle, var(--ld-glow-1) 0%, transparent 68%);
    animation: ld-drift-a 19s ease-in-out infinite alternate;
}

.ld-orb-2 {
    width: 42vw;
    min-width: 260px;
    aspect-ratio: 1;
    bottom: -22%;
    inset-inline-end: -8%;
    background: radial-gradient(circle, var(--ld-glow-2) 0%, transparent 68%);
    animation: ld-drift-b 23s ease-in-out infinite alternate;
}

.ld-orb-3 {
    width: 30vw;
    min-width: 200px;
    aspect-ratio: 1;
    top: 34%;
    inset-inline-end: 32%;
    background: radial-gradient(circle, var(--ld-glow-3) 0%, transparent 65%);
    opacity: .3;
    animation: ld-drift-a 27s ease-in-out infinite alternate-reverse;
}

@keyframes ld-drift-a {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to { transform: translate3d(6%, 8%, 0) scale(1.12); }
}

@keyframes ld-drift-b {
    from { transform: translate3d(0, 0, 0) scale(1.08); }
    to { transform: translate3d(-7%, -6%, 0) scale(.95); }
}

.ld-hero .ld-eyebrow {
    color: var(--ld-on-ink);
    background: var(--ld-ink-surface);
    border: 1px solid var(--ld-ink-line);
    border-radius: 999px;
    padding: 7px 14px;
    margin-bottom: 22px;
    font-size: .7rem;
}

/* The live dot on the label - a small, slow signal that the thing being described is
   running rather than a claim about it. */
.ld-hero .ld-eyebrow i {
    color: var(--ld-glow-3);
    font-size: .55rem;
    animation: ld-blip 2.6s ease-in-out infinite;
}

@keyframes ld-blip {
    0%, 100% { opacity: 1; }
    50% { opacity: .25; }
}

.ld-hero h1 {
    font-size: clamp(2.15rem, 8vw, 3.9rem);
    line-height: 1.04;
    letter-spacing: -.034em;
    font-weight: 600;
    margin: 0 0 20px;
    text-wrap: balance;
}

/* ------------------------------------------------------------- typewriter
 *
 * The headline typing itself out. landing.js owns the timing and explains the four rules
 * that make it safe to do to an h1; what is here is the caret and the two things the
 * animation would otherwise break.
 *
 * Nothing in this block hides the headline. The text is visible by default and the script
 * clears it only once it is actually running - a page whose CSS loads and whose JavaScript
 * does not must never end up with an invisible h1.
 */

/* The floor landing.js measures and pins before it clears the line. Declared here so the
   property is on the element from the first paint rather than being introduced mid-animation. */
.ld-type { min-height: 0; }

/*
 * Balancing is turned off while the line is being typed.
 *
 * text-wrap: balance re-solves the break points for the whole heading on every character,
 * so a headline that ends up on two lines spends the animation flicking words between them.
 * The balanced result is what the design wants, so it comes back the moment typing stops -
 * one reflow at the end, with the caret sitting right beside it, instead of thirty-five.
 */
.ld-hero h1.is-typing { text-wrap: normal; }

/*
 * The rest of the hero, held back until the headline has finished writing itself.
 *
 * Two rules, and the split between them is deliberate. The transition lives on the elements
 * themselves so it is in force whichever direction they are moving; the *hiding* lives
 * behind .is-staging, a class landing.js sets only once it has committed to typing and
 * removes the moment it is done. An element carrying data-ld-after-type on a page where the
 * script never ran is therefore a completely ordinary element - which is what makes it safe
 * to mark up the subtitle and both call-to-action buttons this way.
 *
 * The per-element delay is set by landing.js from each one's position, so they arrive in the
 * order they are read rather than together.
 */
[data-ld-after-type] {
    transition:
        opacity .5s ease var(--ld-stage-delay, 0ms),
        transform .5s cubic-bezier(.2, .7, .3, 1) var(--ld-stage-delay, 0ms),
        /* Flipped, not faded - it is a discrete property, and the zero duration with the
           same delay is what makes it turn back on exactly as its fade begins. */
        visibility 0s linear var(--ld-stage-delay, 0ms);
}

.ld-hero.is-staging [data-ld-after-type] {
    opacity: 0;
    /* Small: this is a second entrance inside a hero that has already made one, and a long
       travel here reads as the page still loading rather than as the line being answered. */
    transform: translateY(9px);
    /*
     * visibility rather than opacity alone, and this is the accessibility half of the rule.
     *
     * A transparent button is still in the tab order and still in the accessibility tree, so
     * without this a keyboard user pressing Tab during the second the headline is typing
     * lands on a demo button they cannot see, and a screen reader reads out a call to action
     * that is not on screen yet. Hidden takes them both out until the line is finished.
     */
    visibility: hidden;
}

/*
 * The caret.
 *
 * A background, not a character. The headline is painted through -webkit-text-fill-color:
 * transparent so the gradient shows through the letterforms, and any glyph put inside it -
 * a pipe, an underscore, a block - inherits that and is simply not drawn. Resetting the
 * fill colour here as well, because a caret that depends on one browser's prefix behaving
 * is a caret that vanishes on the next one.
 */
.ld-type-caret {
    display: inline-block;
    width: .07em;
    /* Cap height rather than the full line box: a bar as tall as the leading looks like a
       text-selection artefact rather than a cursor. */
    height: .78em;
    margin-inline-start: .06em;
    vertical-align: baseline;
    background: var(--ld-on-ink);
    -webkit-text-fill-color: var(--ld-on-ink);
    animation: ld-caret 1.05s steps(1, end) infinite;
}

@keyframes ld-caret {
    0%, 45% { opacity: 1; }
    50%, 95% { opacity: 0; }
    100% { opacity: 1; }
}

/* The display face is Latin-only and the negative track is measured for it. Arabic and Urdu
   resolve to a system face through the fallbacks, where -.034em is enough to pull a joined
   script into itself. */
[dir="rtl"] .ld-hero h1 { letter-spacing: normal; line-height: 1.3; }

.ld-hero p.ld-hero-sub {
    font-size: clamp(1.02rem, 2.4vw, 1.18rem);
    color: var(--ld-on-ink-muted);
    max-width: 54ch;
    margin: 0 0 30px;
}

/*
 * On a phone the demo button takes its own full-width row and the two secondary actions
 * share the one under it. All three used to be 190px minimum, which on any screen narrower
 * than 400px made them three identical full-width bars - three calls to action of equal
 * weight, which is none. 150px is the widest minimum at which the pair still fits inside a
 * 320px viewport once the wrap's padding and the gap are taken out.
 */
.ld-hero-cta { display: flex; flex-wrap: wrap; gap: 12px; }
.ld-hero-cta .ld-btn { flex: 1 1 auto; min-width: 150px; }
.ld-hero-cta .ld-btn-wa { flex: 1 1 100%; }

/* The ghost button is on ink here, so it inverts: a glass fill and a hairline rather than a
   white card that would be the brightest object in the band. */
.ld-hero .ld-btn-ghost {
    background: var(--ld-ink-surface);
    border-color: var(--ld-ink-line);
    color: var(--ld-on-ink);
}

.ld-hero .ld-btn-ghost:hover {
    background: rgba(255, 255, 255, .13);
    border-color: rgba(255, 255, 255, .3);
    color: var(--ld-on-ink);
}

.ld-hero .ld-btn-ghost:active { background: rgba(255, 255, 255, .18); border-color: rgba(255, 255, 255, .35); }

/* The three objections a MikroTik operator raises before they read anything else, answered
   directly under the buttons. A list rather than a paragraph because on a phone this is the
   last thing above the fold and it has to be scannable in one pass. */
.ld-hero-notes {
    list-style: none;
    margin: 26px 0 0;
    padding: 0;
    display: grid;
    gap: 10px;
}

.ld-hero-notes li {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-size: .93rem;
    color: var(--ld-on-ink-muted);
}

/* Fixed size and nudged onto the cap height, so a note that wraps to two lines keeps its
   tick beside the first line instead of centred against the block. */
.ld-hero-notes i {
    color: var(--ld-glow-3);
    font-size: 1.05rem;
    line-height: 1.4;
    flex: none;
}

.ld-hero-grid { display: grid; gap: 40px; align-items: center; }

@media (min-width: 960px) {
    .ld-hero { padding: 92px 0 104px; }
    .ld-hero-grid { grid-template-columns: 1.02fr .98fr; gap: 56px; }

    /* Every button back to its own label's width once there is a row to put them on - the
       full-width demo button is a phone arrangement, not a wide-screen one. */
    .ld-hero-cta .ld-btn,
    .ld-hero-cta .ld-btn-wa { flex: 0 0 auto; }
}

/*
 * The mocked panel, floating. Two things are happening: a slow vertical drift that never
 * stops, and a perspective tilt landing.js sets from the pointer - so the window reacts to
 * a mouse crossing the hero and sits still on a phone, which has no pointer to react to.
 *
 * The tilt is written into two custom properties rather than into the transform itself, so
 * the drift keyframes and the pointer can share the element without either resetting the
 * other.
 */
.ld-hero .ld-mock {
    --ld-tilt-x: 0deg;
    --ld-tilt-y: 0deg;
    transform: perspective(1400px) rotateX(var(--ld-tilt-x)) rotateY(var(--ld-tilt-y));
    transform-style: preserve-3d;
    transition: transform .5s cubic-bezier(.2, .7, .3, 1);
    box-shadow: 0 40px 80px -30px rgba(0, 0, 0, .75), 0 0 0 1px rgba(255, 255, 255, .06);
}

.ld-hero .ld-mock-float { animation: ld-float 7s ease-in-out infinite; }

@keyframes ld-float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-10px); }
}

/* The one line under the fold that says there is more page. Hidden as soon as the layout is
   wide enough that the next chapter is visible anyway. */
.ld-scroll-cue {
    display: none;
    margin-top: 40px;
    color: var(--ld-on-ink-muted);
    font-family: var(--ld-font-mono);
    font-size: .68rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    align-items: center;
    gap: 10px;
}

/* A short rule running into the arrow, so the mark reads as pointing down the page rather
   than as a stray glyph. */
.ld-scroll-cue b {
    display: block;
    width: 34px;
    height: 1px;
    background: linear-gradient(to right, transparent, var(--ld-ink-line));
}

.ld-scroll-cue i { animation: ld-nudge 2.2s ease-in-out infinite; }

@keyframes ld-nudge {
    0%, 100% { transform: translateY(0); opacity: .5; }
    50% { transform: translateY(4px); opacity: 1; }
}

@media (min-width: 960px) { .ld-scroll-cue { display: inline-flex; } }

/* ---------------------------------------------------------------- marquee
 *
 * "Is this for me?", answered by the venues themselves running past in one line. It replaces
 * a static block of ten chips that took a third of a phone screen and was read by nobody:
 * the same ten names in a moving row occupy 60px, and movement is what makes a reader look
 * at a list they would otherwise scroll past.
 *
 * Two identical copies of the list scroll as one track, and the animation resets exactly one
 * copy along, so the seam never arrives. The second copy is aria-hidden - a screen reader
 * gets the list once.
 */

.ld-strip {
    padding: 22px 0;
    border-block: 1px solid var(--ld-border);
    background: var(--ld-bg-alt);
    overflow: hidden;
}

.ld-strip-lede {
    margin: 0 0 14px;
    font-family: var(--ld-font-mono);
    font-size: .72rem;
    font-weight: 500;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ld-muted);
}

.ld-marquee {
    position: relative;
    overflow: hidden;
    /* Both edges fade, so the row reads as continuous rather than as a list that starts and
       stops at the viewport. */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}

/*
 * No gap between the two copies, and this is the whole trick.
 *
 * The loop translates the track by exactly -50%, which only lands chip one of the second
 * copy where chip one of the first copy started if the two copies are the only thing in the
 * track. A gap between them makes half the track width half a gap too long, the reset lands
 * mid-way through a chip, and the row visibly jumps once every cycle. So the spacing that
 * separates the copies is carried by each copy's own trailing padding instead.
 */
.ld-marquee-track {
    display: flex;
    width: max-content;
    gap: 0;
    animation: ld-marquee 42s linear infinite;
}

.ld-marquee-track > * { flex: none; }

@media (hover: hover) {
    /* Stopping on hover is what turns it from a decoration into something a reader can
       actually use - the row is a list of venue types, and one of them is theirs. */
    .ld-marquee:hover .ld-marquee-track { animation-play-state: paused; }
}

@keyframes ld-marquee {
    from { transform: translate3d(0, 0, 0); }
    to { transform: translate3d(-50%, 0, 0); }
}

[dir="rtl"] .ld-marquee-track { animation-name: ld-marquee-rtl; }

@keyframes ld-marquee-rtl {
    from { transform: translate3d(0, 0, 0); }
    to { transform: translate3d(50%, 0, 0); }
}

.ld-chips {
    list-style: none;
    margin: 0;
    padding: 0;
    /* The gap that separates this copy from the next one - see the track above. Same 10px as
       between two chips, so the spacing is uniform right across the seam. */
    padding-inline-end: 10px;
    display: flex;
    gap: 10px;
}

.ld-chips li {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 16px;
    border-radius: 999px;
    border: 1px solid var(--ld-border);
    background: var(--ld-surface);
    font-size: .88rem;
    font-weight: 600;
    color: var(--ld-muted);
    white-space: nowrap;
}

.ld-chips i { color: var(--ld-accent); font-size: 1rem; }

/* ---------------------------------------------------------------- reviews
 *
 * What customers say, riding the same marquee as the venue chips above - the track, the
 * seam trick and the RTL keyframes are all shared, and only what a card looks like is new.
 *
 * A card is much wider than a chip, so the line is slowed right down: 42s across a row of
 * chips is a readable drift, and the same 42s across eight review cards is a blur. The
 * reader also has two ways to stop it, because unlike the chips these have to be *read* -
 * hovering pauses the track, and so does keyboard focus landing inside a card.
 */

.ld-reviews .ld-marquee-track {
    animation-duration: 96s;
    /* The cards carry a lifted shadow, and a track clipped to its own box shears it off at
       the top and bottom edges. */
    padding-block: 6px;
}

/* Pausing on focus as well as on hover: a card that scrolled away under a keyboard user
   mid-sentence is a card they cannot finish. */
.ld-reviews:focus-within .ld-marquee-track { animation-play-state: paused; }

.ld-review-row {
    list-style: none;
    margin: 0;
    /* The gap that separates this copy of the list from the next one - see the note above
       .ld-marquee-track. Same 18px as between two cards, so the rhythm is unbroken across
       the seam. */
    padding: 0 18px 0 0;
    display: flex;
    gap: 18px;
}

[dir="rtl"] .ld-review-row { padding: 0 0 0 18px; }

.ld-review {
    display: flex;
    flex-direction: column;
    gap: 12px;
    /* Fixed rather than fluid: every card in a moving line has to be the same width or the
       loop's -50% reset lands in a different place each cycle. */
    width: 22rem;
    height: 100%;
    margin: 0;
    padding: 20px 22px;
    border: 1px solid var(--ld-border);
    border-radius: 18px;
    background: var(--ld-surface);
    box-shadow: 0 6px 20px rgba(9, 17, 33, .05);
    /* A quote is read at a sensible measure or it is not read at all. */
    white-space: normal;
}

.ld-review-top {
    display: flex;
    align-items: center;
    gap: 10px;
}

.ld-review-stars {
    display: inline-flex;
    gap: 2px;
    font-size: .82rem;
    /* Amber rather than the section's tone: a star is the one thing on this page a reader
       recognises by its colour before they have read anything. */
    color: #d99521;
}

/*
 * The Example tag.
 *
 * This is not decoration and must not be toned down into it: it is the difference between
 * a card that quotes a customer and a card the platform wrote as an illustration. It sits
 * in the same row as the stars, at full contrast, and it is never rendered smaller than the
 * attribution underneath the quote. See the reviews section of landing/index.blade.php.
 */
.ld-review-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-inline-start: auto;
    padding: 3px 9px;
    border: 1px solid var(--ld-border);
    border-radius: 999px;
    background: var(--ld-bg-alt);
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--ld-muted);
    white-space: nowrap;
}

.ld-review-tag i { font-size: .78rem; }

.ld-review blockquote {
    margin: 0;
    font-size: .95rem;
    line-height: 1.6;
    color: var(--ld-text);
}

/* The quotation marks are drawn rather than typed, so a quote stored with or without its
   own is punctuated the same way - and so a right-to-left reading gets the pair the right
   way round, which a literal " in the database does not. */
.ld-review blockquote::before { content: open-quote; }
.ld-review blockquote::after { content: close-quote; }
.ld-review blockquote { quotes: "“" "”" "‘" "’"; }
[dir="rtl"] .ld-review blockquote { quotes: "”" "“" "’" "‘"; }

.ld-review figcaption {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: auto;
    padding-top: 12px;
    border-top: 1px solid var(--ld-border);
}

/* An initial on a tinted disc, not a photograph. A stock face beside a quote is a claim
   about a person who does not exist, and a real customer's face is theirs to give. */
.ld-review-avatar {
    display: grid;
    place-items: center;
    flex: none;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    font-weight: 800;
    font-size: .95rem;
}

.ld-review-who {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.35;
}

.ld-review-who b { font-size: .88rem; }

.ld-review-who span {
    font-size: .76rem;
    color: var(--ld-muted);
}

/* Read out, never seen. Two callers: the review rating in words beside the stars that draw
   it, and the finished headline that landing.js parks here while the visible copy is being
   typed - see the typewriter block above. */
.ld-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    white-space: nowrap;
    border: 0;
    clip-path: inset(50%);
}

@media (max-width: 560px) {
    /* Just under the narrowest phone this page is built for, so a card never sits wider
       than the screen it is crossing. */
    .ld-review { width: 17rem; padding: 18px; }
    .ld-review blockquote { font-size: .9rem; }
}

/* ------------------------------------------------------------- ui replica
 *
 * The panels on this page are rebuilt from the dashboard's own tokens rather than
 * captured from it. The gradients below are copied verbatim from .stat-indigo and its
 * siblings in mtik.css, and the chart inks are the light-mode series slots from the
 * dashboard's validated palette, so a visitor is looking at the real product surface.
 *
 * Rebuilt rather than screenshotted for two reasons that both still hold: a capture of
 * a live panel would publish a customer's voucher codes, MAC addresses and router names
 * on a public page, and a flat image goes stale the first time the UI moves. Anything
 * here that shows a number shows an illustrative one - no claim is made about fleet
 * size, revenue or uptime anywhere on this page.
 */

.ld-mock {
    background: var(--ld-surface);
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius-lg);
    box-shadow: var(--ld-shadow-lg);
    overflow: hidden;
    /* The mock holds a table of codes and MAC addresses, and it is the widest object on the
       page. min-width:0 is what lets it be squeezed by its grid column instead of setting
       the width of the whole document from its own contents. */
    min-width: 0;
}

.ld-mock-bar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 11px 14px;
    border-bottom: 1px solid var(--ld-border);
    background: var(--ld-bg-alt);
}

.ld-mock-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--ld-border); flex: none; }

.ld-mock-title {
    margin-inline-start: 8px;
    font-size: .76rem;
    color: var(--ld-muted);
    font-weight: 600;
    font-family: var(--ld-font-mono);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Icon rail + content, the panel's actual shape. The rail is decoration at this size and
   the first thing to go when the column narrows. */
.ld-mock-app { display: flex; align-items: stretch; }

.ld-mock-rail {
    display: none;
    flex-direction: column;
    gap: 4px;
    padding: 12px 8px;
    background: var(--ld-bg-alt);
    border-inline-end: 1px solid var(--ld-border);
    flex: none;
}

.ld-mock-rail span {
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    border-radius: 8px;
    color: var(--ld-muted);
    font-size: .9rem;
}

.ld-mock-rail span.is-on { background: var(--ld-accent); color: var(--ld-on-accent); }

@media (min-width: 420px) { .ld-mock-rail { display: flex; } }

.ld-mock-body { padding: 14px; display: grid; gap: 12px; min-width: 0; flex: 1; }

/* The scope banner: which site these numbers belong to. */
.ld-mock-scope {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 11px;
    border: 1px solid var(--ld-border);
    border-radius: 12px;
    background: var(--ld-surface);
    min-width: 0;
}

.ld-mock-scope > i { font-size: 1.1rem; color: var(--ld-muted); flex: none; }
.ld-mock-scope div { display: grid; line-height: 1.25; min-width: 0; }
.ld-mock-scope b { font-size: .84rem; font-weight: 700; }
.ld-mock-scope span.sub { font-size: .7rem; color: var(--ld-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- coloured stat cards: the same gradients the dashboard ships ---- */

.ld-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 9px; }
.ld-stats-4 { grid-template-columns: repeat(2, 1fr); }

@media (min-width: 760px) { .ld-stats-4 { grid-template-columns: repeat(4, 1fr); } }

.ld-stat {
    position: relative;
    overflow: hidden;
    border-radius: .85rem;
    color: #fff;
    padding: 11px 12px;
    min-height: 86px;
    min-width: 0;
    background-image: linear-gradient(135deg, var(--s-from) 0%, var(--s-to) 100%);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .18), 0 6px 16px -6px var(--s-glow, rgba(0, 0, 0, .35));
}

.ld-stat .l {
    font-size: .6rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .82);
}

.ld-stat .n {
    font-size: 1.4rem;
    font-weight: 700;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
}

.ld-stat .n small { font-size: .82rem; font-weight: 400; }
.ld-stat .m { font-size: .62rem; color: rgba(255, 255, 255, .8); margin-top: .15rem; }

/* Decorative watermark, exactly as .stat-card__icon does it. */
.ld-stat > i.bi {
    position: absolute;
    inset-inline-end: -.3rem;
    bottom: -.75rem;
    font-size: 3.1rem;
    line-height: 1;
    opacity: .18;
    pointer-events: none;
}

/* Copied from mtik.css so the two surfaces cannot drift apart. */
.s-indigo { --s-from: #6366f1; --s-to: #4338ca; --s-glow: rgba(79, 70, 229, .5); }
.s-blue   { --s-from: #3b82f6; --s-to: #1d4ed8; --s-glow: rgba(37, 99, 235, .5); }
.s-green  { --s-from: #22c55e; --s-to: #15803d; --s-glow: rgba(22, 163, 74, .5); }
.s-red    { --s-from: #f43f5e; --s-to: #be123c; --s-glow: rgba(225, 29, 72, .5); }
.s-teal   { --s-from: #14b8a6; --s-to: #0f766e; --s-glow: rgba(13, 148, 136, .5); }
.s-amber  { --s-from: #f59e0b; --s-to: #b45309; --s-glow: rgba(217, 119, 6, .5); }
.s-purple { --s-from: #a855f7; --s-to: #7e22ce; --s-glow: rgba(147, 51, 234, .5); }
.s-cyan   { --s-from: #06b6d4; --s-to: #0e7490; --s-glow: rgba(8, 145, 178, .5); }
.s-sky    { --s-from: #38bdf8; --s-to: #0369a1; --s-glow: rgba(2, 132, 199, .5); }
.s-orange { --s-from: #fb923c; --s-to: #c2410c; --s-glow: rgba(234, 88, 12, .5); }
.s-pink   { --s-from: #ec4899; --s-to: #be185d; --s-glow: rgba(219, 39, 119, .5); }
.s-slate  { --s-from: #64748b; --s-to: #334155; --s-glow: rgba(71, 85, 105, .5); }

@media (prefers-color-scheme: dark) {
    /* The panel damps its gradients on a dark surface; match it, or the cards glare. */
    .ld-stat { filter: saturate(.92) brightness(.94); box-shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 6px 16px -6px rgba(0, 0, 0, .6); }
}

/* ---- panels, the card + card-header pairing the dashboard uses ---- */

.ld-panel {
    border: 1px solid var(--ld-border);
    border-radius: 13px;
    background: var(--ld-surface);
    overflow: hidden;
    min-width: 0;
}

.ld-panel-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 12px;
    border-bottom: 1px solid var(--ld-border);
    font-size: .78rem;
    font-weight: 700;
    min-width: 0;
}

.ld-panel-head i { color: var(--ld-muted); flex: none; }

.ld-badge {
    margin-inline-start: auto;
    font-size: .64rem;
    font-weight: 700;
    padding: 3px 8px;
    border-radius: 999px;
    background: var(--ld-bg-alt);
    color: var(--ld-muted);
    border: 1px solid var(--ld-border);
    white-space: nowrap;
    flex: none;
}

.ld-panel-body { padding: 11px 12px; min-width: 0; }

/* ---- the traffic plot: same two hues as the dashboard's flow chart ---- */

.ld-chart { display: block; width: 100%; height: auto; }

.ld-chart-legend {
    display: flex;
    gap: 14px;
    justify-content: flex-end;
    font-size: .66rem;
    color: var(--ld-muted);
    font-weight: 600;
    padding: 0 2px 6px;
}

.ld-chart-legend span { display: inline-flex; align-items: center; gap: 5px; }
.ld-chart-legend b { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }

/* ---- rows and tables ----
 *
 * A mocked table is the one thing on this page with a genuine minimum width: a voucher code
 * and a MAC address cannot be wrapped and still read as what they are. So the rows scroll
 * inside their own panel rather than setting the width of the document - which is what they
 * were doing, and what made a phone show the whole page zoomed out to fit them.
 */

.ld-rows { display: grid; min-width: 0; }

.ld-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 2px;
    font-size: .76rem;
    border-bottom: 1px solid var(--ld-border);
    min-width: 0;
}

.ld-row:last-child { border-bottom: 0; }
.ld-row .code { font-family: var(--ld-font-mono); font-weight: 700; letter-spacing: -.03em; }
.ld-row .sub { color: var(--ld-muted); font-size: .68rem; }
.ld-row .grow { margin-inline-start: auto; }
.ld-row .num { font-variant-numeric: tabular-nums; font-weight: 600; text-align: end; flex: none; }
.ld-row .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* The mocked table's column header. Named .ld-thead and not .ld-head: the two were the same
   class, at the same specificity, and this one being later in the file was silently laying
   out every section header on the page as an uppercase flex row. */
.ld-thead {
    display: flex;
    gap: 9px;
    padding: 0 2px 6px;
    font-size: .64rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--ld-muted);
    border-bottom: 1px solid var(--ld-border);
}

.ld-thead .grow { margin-inline-start: auto; }

.ld-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--ld-muted); }
.ld-dot-on { background: #22c55e; box-shadow: 0 0 0 3px rgba(34, 197, 94, .18); }

/* Status chips, carrying the same four reserved hues as the panel's chips and charts. */
.ld-pill {
    font-size: .64rem;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--ld-accent-soft);
    color: var(--ld-accent);
    white-space: nowrap;
    flex: none;
}

.ld-pill-green { background: rgba(27, 175, 122, .14); color: #0f7d55; }
.ld-pill-red { background: rgba(227, 73, 72, .14); color: #c0342f; }
.ld-pill-amber { background: rgba(237, 161, 0, .16); color: #96650a; }

@media (prefers-color-scheme: dark) {
    .ld-pill-green { color: #4cc99a; }
    .ld-pill-red { color: #f08b8b; }
    .ld-pill-amber { color: #dfa93c; }
}

/* ---- the online-share meter, a 2-slice share rendered as a bar ---- */

.ld-meter { height: 7px; border-radius: 999px; background: var(--ld-bg-alt); overflow: hidden; }

.ld-meter i {
    display: block;
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: #22c55e;
    transition: width 1s cubic-bezier(.2, .7, .3, 1);
}

.is-revealed .ld-meter i { width: var(--w, 50%); }

/* Bars that grow once the panel scrolls into view - motion that carries meaning rather than
   decoration. */
.ld-mock-bars { display: flex; align-items: flex-end; gap: 5px; height: 62px; }

.ld-mock-bars i {
    flex: 1;
    background: var(--ld-accent);
    border-radius: 3px 3px 0 0;
    height: 8%;
    opacity: .35;
    transition: height .9s cubic-bezier(.2, .7, .3, 1), opacity .9s ease;
}

.ld-mock-bars i:nth-child(even) { opacity: .6; }
.is-revealed .ld-mock-bars i { height: var(--h, 40%); opacity: 1; }

/* ---------------------------------------------------------------- cards */

.ld-grid { display: grid; gap: 16px; }

@media (min-width: 640px) { .ld-grid-2, .ld-grid-3 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 920px) { .ld-grid-3 { grid-template-columns: repeat(3, 1fr); } }

/*
 * A card is a sheet with a hairline and one mark on it. What it must not become again is the
 * stock arrangement of a coloured top stripe plus a big radius plus a pastel icon tile plus a
 * hover lift, all on the same object - four component-library flourishes stacked is what made
 * a grid of these read as a template.
 *
 * What is new is the pointer light: a soft radial in the tone that follows the cursor across
 * the card, drawn on a pseudo-element under the content and only on devices with a real
 * pointer. It replaces nothing - the card is identical without it - and it is the difference
 * between a grid that responds and a grid that is printed on the page.
 */
.ld-card {
    position: relative;
    isolation: isolate;
    background: var(--ld-surface);
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius);
    padding: 24px 22px;
    overflow: hidden;
    transition: border-color .2s ease, transform .2s ease, box-shadow .2s ease;
}

.ld-card::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    opacity: 0;
    transition: opacity .3s ease;
    background: radial-gradient(
        260px circle at var(--ld-mx, 50%) var(--ld-my, 0%),
        color-mix(in srgb, var(--ld-accent) 14%, transparent),
        transparent 70%
    );
}

@media (hover: hover) {
    .ld-card:hover {
        border-color: color-mix(in srgb, var(--ld-accent) 45%, var(--ld-border));
        transform: translateY(-3px);
        box-shadow: var(--ld-shadow);
    }

    .ld-card:hover::before { opacity: 1; }
}

/* On a touch screen :hover sticks after the tap, so every card the reader has passed through
   would stay lit behind them. A press is the whole interaction there. */
.ld-card:active { border-color: color-mix(in srgb, var(--ld-accent) 45%, var(--ld-border)); }

/*
 * The icon. A gradient tile at the head of the card - the one flourish kept, because with
 * the card stripped back to a hairline something has to be the object a reader's eye lands
 * on, and twelve identical bare glyphs was the version that read as vague.
 */
.ld-card-icon {
    display: inline-grid;
    place-items: center;
    width: 42px;
    height: 42px;
    border-radius: 13px;
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    font-size: 1.2rem;
    line-height: 1;
    margin-bottom: 16px;
    transition: transform .25s cubic-bezier(.2, .7, .3, 1);
}

@media (hover: hover) {
    .ld-card:hover .ld-card-icon { transform: translateY(-2px) rotate(-4deg); }
}

.ld-card h3 { font-size: 1.06rem; font-weight: 600; margin: 0 0 8px; letter-spacing: -.012em; }
.ld-card p { margin: 0; color: var(--ld-muted); font-size: .94rem; }

/*
 * The feature grid, laid out as a bento rather than as twelve identical tiles.
 *
 * The first two cards are the two claims the whole product rests on - a router set up from a
 * form, and reaching it with no public IP - so they get the width to say it, and the icon
 * and heading to match. Twelve equal boxes is a list; a grid with a shape to it is a page.
 *
 * The six-column track and the spans are measured for the twelve entries in $features: two
 * halves, then two rows of thirds, then two more rows of halves, which tiles exactly. A
 * thirteenth feature would leave a gap on the last row rather than break anything - if the
 * list changes, the two nth-child rules below are what has to be re-counted.
 */
@media (min-width: 920px) {
    .ld-bento { grid-template-columns: repeat(6, 1fr); }
    .ld-bento > * { grid-column: span 2; }

    .ld-bento > :nth-child(-n+2),
    .ld-bento > :nth-child(n+9) { grid-column: span 3; }

    .ld-bento > :nth-child(-n+2) .ld-card-icon { width: 52px; height: 52px; font-size: 1.45rem; border-radius: 16px; }
    .ld-bento > :nth-child(-n+2) h3 { font-size: 1.24rem; }
    .ld-bento > :nth-child(-n+2) p { font-size: 1rem; }
    .ld-bento > :nth-child(-n+2) { padding: 30px 28px; }
}

/* ------------------------------------------------------------- accordion
 *
 * The network chapter. It was six cards each holding a five-point list - thirty lines of
 * specification on one screen, which is the single densest thing the page ever asked a
 * visitor to read, and on a phone it was most of the scroll.
 *
 * As an accordion it is six headings. The points are still in the markup, still in the
 * search index and still one tap away; what changed is that the reader chooses which of the
 * six they care about. Built on <details>, so it works with the script absent and the
 * keyboard behaviour is the browser's.
 */

.ld-acc { display: grid; gap: 12px; max-width: 940px; }

.ld-acc-item {
    background: var(--ld-surface);
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius);
    overflow: hidden;
    transition: border-color .2s ease, box-shadow .2s ease;
}

.ld-acc-item[open] {
    border-color: color-mix(in srgb, var(--ld-accent) 40%, var(--ld-border));
    box-shadow: var(--ld-shadow);
}

.ld-acc-item > summary {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 18px;
    cursor: pointer;
    list-style: none;
    font-family: var(--ld-font-display);
    font-weight: 600;
    font-size: 1.02rem;
    letter-spacing: -.012em;
    min-height: 60px;
}

.ld-acc-item > summary::-webkit-details-marker { display: none; }

.ld-acc-icon {
    display: inline-grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 11px;
    flex: none;
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    font-size: 1.05rem;
    transition: background .25s ease, color .25s ease;
}

.ld-acc-item[open] .ld-acc-icon {
    background: var(--ld-grad);
    color: #fff;
}

.ld-acc-title { min-width: 0; }

/* The count of what is inside, so a reader can tell a heading worth opening from one that
   holds two lines. A figure, so it is set in mono like every other figure on the page. */
.ld-acc-count {
    margin-inline-start: auto;
    font-family: var(--ld-font-mono);
    font-size: .7rem;
    font-weight: 600;
    color: var(--ld-muted);
    flex: none;
}

.ld-acc-mark {
    flex: none;
    color: var(--ld-accent);
    font-size: 1rem;
    transition: transform .25s cubic-bezier(.2, .7, .3, 1);
}

.ld-acc-item[open] .ld-acc-mark { transform: rotate(45deg); }

.ld-acc-body { padding: 0 18px 20px; }

/* The panel's contents slide down rather than appearing. <details> gives no transition of
   its own, so the animation runs on the body once the element is already open - which is
   why it is a wrapper and not the list itself. */
.ld-acc-item[open] .ld-acc-body { animation: ld-acc-in .28s cubic-bezier(.2, .7, .3, 1) both; }

@keyframes ld-acc-in {
    from { opacity: 0; transform: translateY(-6px); }
    to { opacity: 1; transform: none; }
}

/* Indented to clear the icon, so the points line up with the heading's text rather than
   with its tile. */
@media (min-width: 560px) { .ld-acc-body { padding-inline-start: 70px; } }

/* ---------------------------------------------------------------- ticks */

/* Two columns as soon as the lines stay readable; the points are short. */
.ld-ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }

@media (min-width: 700px) { .ld-ticks { grid-template-columns: repeat(2, 1fr); gap: 12px 26px; } }

.ld-ticks li {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-size: .94rem;
    color: var(--ld-muted);
}

.ld-ticks i { color: var(--ld-accent); font-size: 1.05rem; line-height: 1.45; flex: none; }

/* A tick list whose lines are sentences rather than phrases - three of them side by side
   would each wrap to four lines, so this one stays one column until there is real room. */
.ld-ticks-wide { margin-top: 26px; }
.ld-ticks-wide b { font-weight: 700; color: var(--ld-text); }

@media (min-width: 700px) { .ld-ticks-wide { grid-template-columns: 1fr; } }
@media (min-width: 1000px) { .ld-ticks-wide { grid-template-columns: repeat(3, 1fr); gap: 16px 26px; } }

/* Inside an accordion the panel is already the width of the section, and the points are
   short - one column keeps them reading as a list rather than as a table. */
.ld-acc-body .ld-ticks { gap: 11px; }

/* ---------------------------------------------------------------- gallery
 *
 * The dashboard chapter. Seven rebuilt panels, each with a caption, stacked - which is seven
 * screens of scrolling on a phone before the price list, and the reason a visitor gave up
 * before reaching it.
 *
 * It is one panel at a time now, chosen from a strip of labels. Every panel is in the HTML
 * and readable with the script absent; landing.js is what turns them into tabs. The strip
 * doubles as the answer to "what else is in there?", which the stack never gave - a reader
 * had to scroll the whole chapter to find out.
 */

.ld-gallery { display: grid; gap: 18px; }

.ld-gallery .ld-tabpanel { padding: 0; overflow: hidden; }

.ld-shot { display: flex; flex-direction: column; min-width: 0; }

.ld-shot-ui {
    flex: 1;
    padding: 14px;
    background: var(--ld-bg-alt);
    display: grid;
    gap: 10px;
    align-content: start;
    min-width: 0;
}

@media (min-width: 768px) { .ld-shot-ui { padding: 20px; gap: 14px; } }

.ld-shot-cap { padding: 16px 18px 20px; border-top: 1px solid var(--ld-border); }
.ld-shot-cap h3 { margin: 0 0 5px; font-size: 1.06rem; font-weight: 600; }
.ld-shot-cap p { margin: 0; color: var(--ld-muted); font-size: .93rem; max-width: 68ch; }

/* The wide panel puts the plot beside the cards once there is room for both. */
@media (min-width: 760px) {
    .ld-shot-split { display: grid; grid-template-columns: 1.15fr .85fr; gap: 10px; align-items: start; }
}

/* ---- the branded voucher card ---- */

.ld-voucher {
    border: 1px solid var(--ld-border);
    border-radius: 13px;
    background: var(--ld-surface);
    padding: 14px 10px;
    display: grid;
    gap: 9px;
    text-align: center;
    min-width: 0;
}

.ld-voucher-top { display: flex; align-items: center; gap: 8px; justify-content: center; color: var(--ld-accent); font-weight: 800; font-size: .8rem; }
.ld-voucher-top i { font-size: 1rem; }

/*
 * The code, and the one measurement on this page that has to be got right.
 *
 * It is thirteen unbreakable characters. Set at 1.05rem in a third of a 320px screen it is
 * about 80px of glyphs in a 50px box, and because a grid track cannot be narrower than its
 * content the overflow was not the code's - it was the whole document's, which is what made
 * a phone draw the entire page zoomed out to fit it.
 *
 * Three things now stop that: the type scales with the viewport, the row reflows to fewer
 * cards rather than narrower ones, and the token is allowed to break as a last resort. Any
 * one of them would do it; all three mean it cannot come back through a fourth route.
 */
.ld-voucher-code {
    font-family: var(--ld-font-mono);
    font-size: clamp(.7rem, 2.8vw, 1.02rem);
    font-weight: 800;
    letter-spacing: -.02em;
    padding: 9px 4px;
    border-radius: 9px;
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    border: 1px dashed color-mix(in srgb, var(--ld-accent) 45%, transparent);
    overflow-wrap: anywhere;
}

.ld-voucher-meta { display: flex; justify-content: center; gap: 12px; font-size: .66rem; color: var(--ld-muted); }
.ld-voucher-meta b { display: block; color: var(--ld-text); font-size: .76rem; }

/* auto-fit rather than three fixed tracks: on a narrow screen the row becomes one card and
   then two, which shows fewer of them at a readable size instead of three at an unreadable
   one. The batch reads as a batch from two cards up. */
.ld-vouchers-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 8px; }

/* ---------------------------------------------------------------- timeline
 *
 * How it works. Four steps on a rail that draws itself as the section arrives - the line is
 * a scaled pseudo-element, so it costs one transform and no layout.
 */

.ld-steps { position: relative; display: grid; gap: 0; counter-reset: step; }

/* The rail. Vertical on a phone, where the steps stack, and it runs from the first marker to
   the last rather than the full height of the block. */
.ld-steps::before {
    content: "";
    position: absolute;
    inset-inline-start: 15px;
    top: 8px;
    bottom: 40px;
    width: 2px;
    background: var(--ld-rule);
}

.ld-steps::after {
    content: "";
    position: absolute;
    inset-inline-start: 15px;
    top: 8px;
    bottom: 40px;
    width: 2px;
    background: var(--ld-grad);
    transform: scaleY(0);
    transform-origin: top;
    transition: transform 1.4s cubic-bezier(.2, .7, .3, 1) .1s;
}

.ld-steps.is-revealed::after { transform: scaleY(1); }

.ld-step {
    position: relative;
    padding: 0 0 32px 46px;
    min-height: 40px;
}

[dir="rtl"] .ld-step { padding: 0 46px 32px 0; }

.ld-step:last-child { padding-bottom: 0; }

/*
 * The step number, in a filled marker on the rail. It was a bare numeral under a hairline,
 * which is quieter than this - but a rail with nothing on it is just a line, and the marker
 * is what makes four paragraphs read as a sequence with an order to it.
 */
.ld-step::before {
    counter-increment: step;
    content: counter(step, decimal-leading-zero);
    position: absolute;
    inset-inline-start: 0;
    top: 0;
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--ld-surface);
    border: 2px solid var(--ld-rule);
    font-family: var(--ld-font-mono);
    font-size: .72rem;
    font-weight: 700;
    color: var(--ld-muted);
    transition: background .3s ease, color .3s ease, border-color .3s ease;
}

.ld-step.is-revealed::before {
    background: var(--ld-grad);
    border-color: transparent;
    color: #fff;
}

.ld-step h3 { margin: 4px 0 6px; font-size: 1.06rem; font-weight: 600; }
.ld-step p { margin: 0; color: var(--ld-muted); font-size: .95rem; max-width: 62ch; }

/* The panel's address, set as a value rather than a word. Wraps anywhere: it is one
   unbreakable token inside a narrow column, and on a 320px screen a long subdomain would
   otherwise push the whole grid sideways. */
.ld-step-host {
    font-family: var(--ld-font-mono);
    font-size: .85em;
    font-weight: 600;
    color: var(--ld-accent-strong);
    background: var(--ld-accent-soft);
    padding: 1px 6px;
    border-radius: var(--ld-radius-sm);
    overflow-wrap: anywhere;
}

/* Two columns from the tablet up. The rail goes with the single column: four markers on two
   rails reads as two separate sequences. */
@media (min-width: 860px) {
    .ld-steps { grid-template-columns: repeat(2, 1fr); column-gap: 40px; }
    .ld-steps::before,
    .ld-steps::after { display: none; }
    .ld-step { padding-bottom: 36px; }
    .ld-step:nth-last-child(-n+2) { padding-bottom: 0; }
}

.ld-steps-cta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 40px;
}

/* Full width and stacked on a phone, side by side as soon as both fit on one line. */
.ld-steps-cta .ld-btn { flex: 1 1 auto; min-width: 240px; }

@media (min-width: 640px) {
    .ld-steps-cta .ld-btn { flex: 0 0 auto; }
}

/* ---------------------------------------------------------------- tabs */

.ld-tabs { max-width: 980px; }

/*
 * The strip scrolls sideways rather than wrapping - the labels will not fit across a 360px
 * screen, and a tab strip that wraps to three rows stops reading as a tab strip.
 *
 * Which leaves the problem every hidden-scrollbar strip has on a phone: with the scrollbar
 * suppressed and the last tab cut off exactly at the viewport edge, there is nothing to say
 * the row continues, and a reader who does not think to swipe it simply never sees the other
 * panels. The mask fades the trailing edge out, so the strip ends by trailing off rather
 * than by stopping, which is the one cue that reads as "there is more this way".
 */
.ld-tablist {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    padding-bottom: 4px;
    margin-bottom: 20px;
    scroll-snap-type: x proximity;
}

.ld-tablist::-webkit-scrollbar { display: none; }

@media (max-width: 720px) {
    .ld-tablist {
        -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 36px), transparent 100%);
        mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 36px), transparent 100%);
        /* The fade has to sit on the trailing edge, which swaps with the writing direction. */
        padding-inline-end: 28px;
    }

    [dir="rtl"] .ld-tablist {
        -webkit-mask-image: linear-gradient(to left, #000 0, #000 calc(100% - 36px), transparent 100%);
        mask-image: linear-gradient(to left, #000 0, #000 calc(100% - 36px), transparent 100%);
    }
}

.ld-tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
    scroll-snap-align: start;
    min-height: 44px;
    padding: 10px 16px;
    border-radius: 999px;
    border: 1px solid var(--ld-border);
    background: var(--ld-surface);
    color: var(--ld-muted);
    font: inherit;
    font-family: var(--ld-font-display);
    font-weight: 600;
    font-size: .9rem;
    cursor: pointer;
    transition: background-color .18s ease, color .18s ease, border-color .18s ease;
}

@media (hover: hover) {
    .ld-tab:hover { color: var(--ld-text); border-color: color-mix(in srgb, var(--ld-accent) 40%, var(--ld-border)); }
}

.ld-tab[aria-selected="true"] {
    background: var(--ld-accent);
    border-color: var(--ld-accent);
    color: var(--ld-on-accent);
}

.ld-tabpanel {
    background: var(--ld-surface);
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius);
    padding: 24px 22px;
}

/* Only shown when JavaScript has not run and every panel is on the page at once. */
.ld-tabpanel-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 16px;
    font-family: var(--ld-font-display);
    font-size: 1.08rem;
    font-weight: 600;
    letter-spacing: -.015em;
}

.ld-tabpanel-head i { color: var(--ld-accent); }

/* One panel at a time, and each new panel arrives rather than appearing - the tab strip is
   the only thing that moves on this section, so without it a tap reads as a page jump. */
.ld-tabs.is-live .ld-tabpanel { display: none; }
.ld-tabs.is-live .ld-tabpanel-head { display: none; }

.ld-tabs.is-live .ld-tabpanel.is-current {
    display: block;
    animation: ld-tab-in .32s cubic-bezier(.2, .7, .3, 1) both;
}

@keyframes ld-tab-in {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
}

/* -------------------------------------------------------------- tutorials --

   The video chapter and the player it opens.

   Two rules the rest of this file also follows, and which matter more here than anywhere
   else on the page:

    - nothing in the grid downloads a video. A card is a poster and a running time, both
      recorded when the file was uploaded, and the <video> element does not exist until
      somebody presses one. Twenty <video> tags on a marketing page is twenty connections
      opened by scrolling past on a phone.
    - the player is the page's own, not the browser's. `controls` on a <video> renders
      differently on every platform, cannot be translated, and drops a grey Chrome-shaped
      bar into a page that has spent 2,000 lines being something else. The markup keeps
      `controls` so a visitor whose JavaScript never arrives still gets a working player;
      landing.js takes it away the moment it can offer these instead.
*/

.ld-videos { display: block; }

/* There is one filter row, and it is the subject. Language used to be a second row here and
   is not one any more: a card is a tutorial rather than a file, so "show me the Urdu ones"
   would have hidden tutorials that exist in Urdu behind a card titled in another language.
   The question is asked when the card is pressed instead - see .ld-player-ask. */
.ld-filter i { margin-inline-end: 6px; }

.ld-video-grid { display: grid; gap: 18px; }

@media (min-width: 620px) { .ld-video-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .ld-video-grid { grid-template-columns: repeat(3, 1fr); } }

.ld-video[hidden] { display: none; }

/* The whole card is one button. A thumbnail with a separate "play" link beside it gives a
   phone two targets where the finger is aiming at one thing, and the poster is by far the
   larger and more obvious of the two. */
.ld-video-open {
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
    padding: 0;
    text-align: start;
    background: var(--ld-surface);
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius);
    overflow: hidden;
    cursor: pointer;
    color: inherit;
    font: inherit;
    transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}

@media (hover: hover) {
    .ld-video-open:hover {
        transform: translateY(-4px);
        box-shadow: var(--ld-shadow);
        border-color: color-mix(in srgb, var(--ld-accent) 40%, var(--ld-border));
    }

    .ld-video-open:hover .ld-video-play { transform: scale(1.08); background: var(--ld-accent); }
}

.ld-video-open:focus-visible { outline: 3px solid var(--ld-accent); outline-offset: 3px; }

.ld-video-thumb {
    position: relative;
    display: block;
    width: 100%;
    background: var(--ld-ink);
    overflow: hidden;
}

.ld-video-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* No frame was captured at upload - an unusual container, or a browser that would not
   decode it. The subject's own icon on the brand gradient, rather than the grey box that
   every other site shows for a missing thumbnail. */
.ld-video-fallback {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    background: var(--ld-grad);
    color: var(--ld-on-accent);
    font-size: 2.6rem;
}

.ld-video-play {
    position: absolute;
    inset-block-end: 12px;
    inset-inline-start: 12px;
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background: rgba(7, 12, 23, .68);
    backdrop-filter: blur(6px);
    color: #fff;
    font-size: 1.35rem;
    transition: transform .2s ease, background-color .2s ease;
}

/* Mono, because it is a measurement - and tabular figures are what stop a column of cards
   showing "1:05" and "12:40" at two different widths. */
.ld-video-time {
    position: absolute;
    inset-block-end: 12px;
    inset-inline-end: 12px;
    padding: 3px 8px;
    border-radius: 8px;
    background: rgba(7, 12, 23, .78);
    color: #fff;
    font-family: var(--ld-font-mono);
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
    /* A running time is a duration in every language. Mirrored it reads as nonsense. */
    direction: ltr;
}

.ld-video-meta {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 16px 18px 20px;
}

.ld-video-tags { display: flex; flex-wrap: wrap; gap: 6px; }

.ld-video-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border-radius: 999px;
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    font-size: .76rem;
    font-weight: 600;
}

.ld-video-tag-lang {
    background: var(--ld-bg-alt);
    color: var(--ld-muted);
}

.ld-video-title {
    font-family: var(--ld-font-display);
    font-size: 1.08rem;
    font-weight: 600;
    line-height: 1.32;
    color: var(--ld-text);
}

.ld-video-summary {
    color: var(--ld-muted);
    font-size: .92rem;
    line-height: 1.5;
}

/* ----------------------------------------------------------------- player --

   Opened over the page rather than played in the card. A tutorial is watched, not glanced
   at: in place it would be one ninth of a phone screen with the page still scrolling
   underneath it.

   The surface is the ink, fixed in both colour schemes like the hero and the closing band.
   A player is a dark room by convention, and one that turned white for half the visitors
   would be the only thing on the page that changed what it is depending on their phone.
*/
.ld-player {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: grid;
    place-items: center;
    padding: 16px;
}

.ld-player[hidden] { display: none; }

.ld-player-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(4, 8, 16, .82);
    backdrop-filter: blur(3px);
}

.ld-player-box {
    position: relative;
    width: min(1100px, 100%);
    max-height: calc(100dvh - 32px);
    display: flex;
    flex-direction: column;
    background: var(--ld-ink-2);
    border: 1px solid var(--ld-ink-line);
    border-radius: var(--ld-radius-lg);
    box-shadow: var(--ld-shadow-lg);
    overflow: hidden;
    animation: ld-player-in .22s ease both;
}

@keyframes ld-player-in {
    from { opacity: 0; transform: translateY(14px) scale(.985); }
    to { opacity: 1; transform: none; }
}

.ld-player-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--ld-ink-line);
}

.ld-player-title {
    margin: 0;
    flex: 1;
    min-width: 0;
    font-family: var(--ld-font-display);
    font-size: 1.02rem;
    font-weight: 600;
    color: var(--ld-on-ink);
    /* The operator's own title, at whatever length they typed it. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ld-player-x {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 50%;
    background: var(--ld-ink-surface);
    color: var(--ld-on-ink);
    cursor: pointer;
    transition: background-color .16s ease;
}

.ld-player-x:hover { background: rgba(255, 255, 255, .16); }

/* The stage is the shape of the recording, set from the file itself the moment its metadata
   arrives. A fixed 16:9 box was right for everything shot on a laptop and wrong for
   everything shot on a phone: an upright tutorial played as a thin column down the middle of
   a wide black box, and the question of which language to hear it in - drawn over this same
   stage before anything is loaded - had no box to sit in at all.

   The cap is what keeps that honest. An upright recording asks for a stage taller than the
   window; max-height wins over aspect-ratio, so the picture is letterboxed rather than the
   controls being pushed off the bottom of the screen. */
.ld-player-stage {
    position: relative;
    background: #000;
    display: grid;
    place-items: center;
    min-height: 0;
    aspect-ratio: var(--ld-player-ratio, 16 / 9);
    max-height: calc(100dvh - 210px);
}

.ld-player-stage video {
    display: block;
    width: 100%;
    height: 100%;
    /* Never cropped. A tutorial is watched for what is on the screen being recorded, and
       filling the last of the black by cutting the edges off would cut off the menu the
       viewer is being shown. */
    object-fit: contain;
    background: #000;
}

/* --- which language to hear it in -----------------------------------------

   Asked over the poster, before a byte of video is fetched: the same tutorial exists here in
   several languages, and downloading one to find out it is the wrong one is a gigabyte of
   somebody's data. Asked again from the control bar mid-video, which resumes at the same
   second in the other voice. */
.ld-player-ask {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: grid;
    align-content: center;
    justify-items: center;
    gap: 16px;
    padding: 24px;
    text-align: center;
    background: rgba(4, 8, 16, .74);
    backdrop-filter: blur(4px);
}

.ld-player-ask[hidden] { display: none; }

.ld-player-ask-title {
    margin: 0;
    color: var(--ld-on-ink);
    font-family: var(--ld-font-display);
    font-size: 1.06rem;
    font-weight: 600;
}

.ld-player-ask-list {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
}

.ld-player-ask-btn {
    min-height: 46px;
    padding: 11px 22px;
    border: 1px solid var(--ld-ink-line);
    border-radius: 999px;
    background: var(--ld-ink-surface);
    color: var(--ld-on-ink);
    font-family: var(--ld-font-display);
    font-size: .98rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color .16s ease, border-color .16s ease, transform .16s ease;
}

.ld-player-ask-btn:hover { background: rgba(255, 255, 255, .16); }
.ld-player-ask-btn:focus-visible { outline: 2px solid var(--ld-glow-1); outline-offset: 3px; }

/* The one that will be played on Enter: last time's choice, or the language of the page. */
.ld-player-ask-btn.is-current {
    background: var(--ld-accent);
    border-color: var(--ld-accent);
    color: var(--ld-on-accent);
}

/* Nothing is loaded while the question is up, so every control that operates a video is
   furniture - and the big play button in the middle would sit on top of the question. */
.ld-player.is-asking .ld-player-big,
.ld-player.is-asking .ld-player-controls { display: none; }

/* Wider than the icon buttons beside it: it holds a language named in its own script, which
   is a word and not a symbol. */
.ld-player-lang {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0 12px;
    font-family: var(--ld-font-display);
    font-size: .86rem;
    font-weight: 600;
}

/* One target the size of the picture. On a phone the controls below are 44px high and the
   thumb is nowhere near them; the way anybody pauses a video is by tapping it. */
.ld-player-big {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    width: 100%;
    border: 0;
    background: transparent;
    color: #fff;
    font-size: 3.4rem;
    cursor: pointer;
    opacity: 0;
    transition: opacity .18s ease;
}

.ld-player-big i {
    display: grid;
    place-items: center;
    width: 84px;
    height: 84px;
    border-radius: 50%;
    background: rgba(7, 12, 23, .55);
    backdrop-filter: blur(6px);
}

/* Visible while paused, and gone while playing - it is the only thing that would be drawn
   over the picture somebody came here to look at. */
.ld-player.is-paused .ld-player-big { opacity: 1; }

.ld-player-spinner {
    position: absolute;
    width: 46px;
    height: 46px;
    border: 3px solid rgba(255, 255, 255, .25);
    border-top-color: #fff;
    border-radius: 50%;
    animation: ld-player-spin .8s linear infinite;
    pointer-events: none;
}

.ld-player-spinner[hidden] { display: none; }

@keyframes ld-player-spin { to { transform: rotate(360deg); } }

.ld-player-controls {
    padding: 12px 16px 16px;
    background: var(--ld-ink-2);
}

.ld-player-controls[hidden] { display: none; }

/* The scrubber: a track, the buffered range, the played range, and a transparent
   <input type=range> on top that owns every interaction. Native input rather than a div
   with pointer handlers, because it arrives keyboard-operable, screen-reader-operable and
   correct under a touch drag - none of which comes free with a div. */
.ld-player-seek {
    position: relative;
    height: 20px;
    display: flex;
    align-items: center;
    /* A timeline runs left to right in every language, like the traffic chart. */
    direction: ltr;
}

.ld-player-seek::before {
    content: "";
    position: absolute;
    inset-inline: 0;
    height: 5px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .18);
}

.ld-player-buffer,
.ld-player-played {
    position: absolute;
    inset-inline-start: 0;
    height: 5px;
    border-radius: 999px;
    width: 0;
    pointer-events: none;
}

.ld-player-buffer { background: rgba(255, 255, 255, .3); }
.ld-player-played { background: var(--ld-glow-1); }

.ld-player-seek input[type="range"],
.ld-player-volume input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    cursor: pointer;
    margin: 0;
}

.ld-player-seek input[type="range"] {
    position: relative;
    width: 100%;
    height: 20px;
}

.ld-player-seek input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}

.ld-player-seek input[type="range"]::-moz-range-thumb {
    width: 14px;
    height: 14px;
    border: 0;
    border-radius: 50%;
    background: #fff;
}

.ld-player-row {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
    color: var(--ld-on-ink);
}

.ld-player-btn {
    display: grid;
    place-items: center;
    min-width: 40px;
    height: 40px;
    padding: 0 8px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--ld-on-ink);
    font-family: var(--ld-font-mono);
    font-size: 1.05rem;
    cursor: pointer;
    transition: background-color .16s ease;
}

.ld-player-btn:hover { background: var(--ld-ink-surface); }
.ld-player-btn:focus-visible { outline: 2px solid var(--ld-glow-1); outline-offset: 2px; }
.ld-player-btn[hidden] { display: none; }

.ld-player-rate { font-size: .86rem; font-weight: 600; }

.ld-player-time {
    margin-inline: 6px auto;
    font-family: var(--ld-font-mono);
    font-size: .84rem;
    font-variant-numeric: tabular-nums;
    color: var(--ld-on-ink-muted);
    direction: ltr;
}

.ld-player-volume { display: flex; align-items: center; gap: 4px; }

/* Collapsed on a phone: there is no pointer to hover it open with, and the hardware volume
   keys are what anybody uses there anyway. */
.ld-player-volume input[type="range"] { width: 0; height: 20px; transition: width .18s ease; }

@media (min-width: 620px) {
    .ld-player-volume input[type="range"] { width: 78px; }
}

.ld-player-volume input[type="range"]::-webkit-slider-runnable-track {
    height: 4px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .28);
}

.ld-player-volume input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    margin-top: -4px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #fff;
}

.ld-player-volume input[type="range"]::-moz-range-track {
    height: 4px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .28);
}

.ld-player-volume input[type="range"]::-moz-range-thumb {
    width: 12px;
    height: 12px;
    border: 0;
    border-radius: 50%;
    background: #fff;
}

/* Fullscreen is entered on the box, not on the <video>: entering it on the element itself
   hands the picture back to the browser's own controls and everything above disappears. */
.ld-player-box:fullscreen {
    width: 100%;
    max-height: none;
    height: 100%;
    border: 0;
    border-radius: 0;
}

/* Both of these hand the height to the stage rather than to the picture, so aspect-ratio is
   no longer what sizes it - the stage is as tall as what is left, and the recording is
   contained inside it. */
.ld-player-box:fullscreen .ld-player-stage {
    flex: 1;
    aspect-ratio: auto;
    max-height: none;
}

/* --- fit to screen --------------------------------------------------------

   The player takes the whole viewport and the picture keeps its shape inside it: a recording
   filmed sideways ends up as wide as the screen, one filmed upright as tall as it. That is
   the whole difference between watching a phone-shot tutorial on a phone and watching a strip
   across the middle of it - and on a laptop it is what makes an upright recording legible
   instead of a column between two black fields.

   Deliberately not fullscreen. Fullscreen is the browser's: it takes over the display, and on
   an iPhone it hands the video to Safari's own player, which takes every control here with
   it. This one is ours and never leaves the page. */
.ld-player.is-fit { padding: 0; }

.ld-player.is-fit .ld-player-box {
    width: 100%;
    height: 100dvh;
    max-height: none;
    border: 0;
    border-radius: 0;
}

.ld-player.is-fit .ld-player-stage {
    flex: 1;
    aspect-ratio: auto;
    max-height: none;
}

@media (prefers-reduced-motion: reduce) {
    .ld-player-box { animation: none; }
    .ld-player-spinner { animation-duration: 2s; }
}

/* ---------------------------------------------------------------- pricing */

.ld-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 28px;
}

.ld-filter {
    border: 1px solid var(--ld-border);
    background: var(--ld-surface);
    color: var(--ld-muted);
    font-family: var(--ld-font-display);
    font-weight: 600;
    font-size: .89rem;
    min-height: 44px;
    padding: 9px 17px;
    border-radius: 999px;
    cursor: pointer;
    transition: background-color .16s ease, color .16s ease, border-color .16s ease;
}

.ld-filter[aria-pressed="true"] {
    background: var(--ld-accent);
    border-color: var(--ld-accent);
    color: var(--ld-on-accent);
}

.ld-plans { display: grid; gap: 18px; align-items: stretch; }

@media (min-width: 700px) { .ld-plans { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .ld-plans { grid-template-columns: repeat(3, 1fr); } }

.ld-plan {
    position: relative;
    background: var(--ld-surface);
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius);
    padding: 26px 22px;
    display: flex;
    flex-direction: column;
    height: 100%;
    transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}

.ld-plan[hidden] { display: none; }

@media (hover: hover) {
    .ld-plan:hover { transform: translateY(-4px); box-shadow: var(--ld-shadow); }
}

/*
 * The featured plan. A gradient hairline rather than a drop shadow: the card that is being
 * recommended should be marked, but a card that floats above its neighbours is making a
 * design decision out of a marketing one.
 *
 * Drawn as a padded gradient behind the card - border-image cannot follow a border-radius,
 * and this is the one arrangement that does without a wrapper element.
 */
.ld-plan.is-featured {
    border-color: transparent;
    background:
        linear-gradient(var(--ld-surface), var(--ld-surface)) padding-box,
        var(--ld-grad) border-box;
    border: 2px solid transparent;
}

.ld-plan-flag {
    position: absolute;
    top: -12px;
    inset-inline-start: 22px;
    background: var(--ld-grad);
    color: #fff;
    font-family: var(--ld-font-mono);
    font-size: .66rem;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    padding: 5px 12px;
    border-radius: 999px;
}

/* Mono, with the plan type reading as a classification rather than as a small heading -
   which is what it is: it says which service the card belongs to. */
.ld-plan-type {
    font-family: var(--ld-font-mono);
    font-size: .7rem;
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ld-muted);
}

.ld-plan h3 { font-size: 1.3rem; margin: 8px 0 4px; font-weight: 600; letter-spacing: -.018em; }
.ld-plan-summary { color: var(--ld-muted); font-size: .93rem; margin: 0 0 18px; }

/*
 * The tile that replaced the price. Cards carry no figure, so without this a plan is a
 * heading over a column of ticks and every card in the row reads as the same object. The
 * tone comes from a data-tone on this element alone (see the pricing section in
 * landing/index.blade.php): the tokens cascade no further, so the card's border, flag and
 * button stay with the section's amber and only the tile changes colour from one plan to
 * the next.
 */
.ld-plan-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    margin-bottom: 16px;
    border-radius: 14px;
    font-size: 1.3rem;
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
}

.ld-plan-features { list-style: none; margin: 0 0 22px; padding: 0; display: grid; gap: 10px; }

.ld-plan-features li {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    font-size: .93rem;
    color: var(--ld-text);
}

.ld-plan-features i { color: var(--ld-success); margin-top: 3px; flex: none; }

.ld-plan .ld-btn { margin-top: auto; }

.ld-plans-empty {
    text-align: center;
    color: var(--ld-muted);
    border: 1px dashed var(--ld-border);
    border-radius: var(--ld-radius);
    padding: 40px 20px;
}

/* ---------------------------------------------------------------- faq */

/*
 * A rule-separated list, not a stack of boxes. Ten bordered, rounded cards with a gap
 * between each spends ten borders and ten radii saying something a single hairline between
 * two questions already says, and it reads as the back matter of a document - which is what
 * an FAQ is.
 */
.ld-faq {
    display: grid;
    max-width: 860px;
    border-top: 1px solid var(--ld-rule);
}

.ld-faq details { border-bottom: 1px solid var(--ld-rule); }

.ld-faq summary {
    cursor: pointer;
    font-family: var(--ld-font-display);
    font-weight: 600;
    font-size: 1.02rem;
    letter-spacing: -.012em;
    padding: 18px 0;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 44px;
}

/* The open question is marked in the tone. With no card behind it, this is what tells a
   reader which answer they are looking at once they have scrolled past the question. */
.ld-faq details[open] summary { color: var(--ld-accent-strong); }

.ld-faq summary::-webkit-details-marker { display: none; }

.ld-faq summary::after {
    content: "\F282";
    font-family: bootstrap-icons;
    margin-inline-start: auto;
    color: var(--ld-muted);
    transition: transform .2s ease;
}

.ld-faq details[open] summary::after { transform: rotate(180deg); }

.ld-faq p { margin: 0 0 20px; max-width: 68ch; color: var(--ld-muted); font-size: .95rem; }
.ld-faq details[open] p { animation: ld-acc-in .28s cubic-bezier(.2, .7, .3, 1) both; }

/* ---------------------------------------------------------------- cta band
 *
 * The second ink band, and the close of the page. Same ground as the hero, so the document
 * is bracketed by the two darkest things on it and the chapters sit between them as one
 * light run.
 *
 * Everything on it names its own foreground, for the reason given at the hero: the ink does
 * not follow the visitor's scheme and the page tokens do.
 */

.ld-cta-band {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background: var(--ld-ink);
    color: var(--ld-on-ink);
    border-radius: var(--ld-radius-lg);
    padding: 48px 24px;
    text-align: center;
}

/* One light, centred behind the ask. Still rather than drifting: the hero's job is to hold a
   reader who has just arrived, and this one's is to be read and acted on. */
.ld-cta-band::before {
    content: "";
    position: absolute;
    inset: -40% -10% auto;
    height: 150%;
    z-index: -1;
    background: radial-gradient(60% 55% at 50% 0%, color-mix(in srgb, var(--ld-glow-2) 55%, transparent), transparent 70%);
    pointer-events: none;
}

.ld-cta-band h2 {
    font-family: var(--ld-font-display);
    font-size: clamp(1.6rem, 5vw, 2.4rem);
    font-weight: 600;
    margin: 0 0 12px;
    letter-spacing: -.024em;
    text-wrap: balance;
}

.ld-cta-band p { margin: 0 auto 28px; max-width: 52ch; color: var(--ld-on-ink-muted); }

.ld-cta-band .ld-btn-ghost {
    background: var(--ld-ink-surface);
    border-color: var(--ld-ink-line);
    color: var(--ld-on-ink);
}

.ld-cta-band .ld-btn-ghost:hover {
    background: rgba(255, 255, 255, .14);
    border-color: rgba(255, 255, 255, .3);
    color: var(--ld-on-ink);
}

.ld-cta-actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
.ld-cta-actions .ld-btn { flex: 1 1 auto; min-width: 200px; }

@media (min-width: 640px) {
    .ld-cta-band { padding: 68px 40px; }
    .ld-cta-actions .ld-btn { flex: 0 0 auto; }
}

/* ---------------------------------------------------------------- footer */

/* The same grey as the header, so the two read as one frame around the sections rather
   than as two more bands. */
.ld-footer {
    background: var(--ld-chrome);
    border-top: 1px solid var(--ld-border);
    padding: 40px 0 56px;
}

.ld-footer-grid { display: grid; gap: 26px; }

/* Four columns of very different weights. The brand block needs room for a sentence; the
   three lists need barely any. On a phone they stack, but from 480px the lists pair up
   two-by-two rather than each taking a full-width row - three stacked lists of four links
   is a long scroll for nothing. The brand spans both columns there, because a tagline in a
   half-width column wraps to four lines. */
@media (min-width: 480px) {
    .ld-footer-grid { grid-template-columns: repeat(2, 1fr); }
    .ld-footer-brand { grid-column: 1 / -1; }
}

@media (min-width: 760px) {
    .ld-footer-grid { grid-template-columns: 1.6fr 1fr 1fr 1fr; }
    .ld-footer-brand { grid-column: auto; }
}

.ld-footer-brand { display: grid; gap: 12px; align-content: start; }
.ld-footer-brand p { margin: 0; color: var(--ld-muted); font-size: .93rem; max-width: 34ch; }

.ld-footer h4 { font-size: .78rem; text-transform: uppercase; letter-spacing: .08em; color: var(--ld-muted); margin: 0 0 12px; }
.ld-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.ld-footer a { color: var(--ld-muted); font-size: .94rem; }
.ld-footer a:hover { color: var(--ld-accent); }

.ld-footer-note {
    margin-top: 32px;
    padding-top: 20px;
    border-top: 1px solid var(--ld-border);
    color: var(--ld-muted);
    font-size: .86rem;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    justify-content: space-between;
}

/* A phone-sized tap target that follows the page, because the demo button is the whole
   point of the site and must never be more than a thumb away. Hidden on desktop, where
   the header CTA is always visible anyway. */
.ld-float-wa {
    position: fixed;
    inset-inline-end: 16px;
    /* Clears the home indicator on a phone with a gesture bar - the layout opts into the
       full screen with viewport-fit=cover, so without this the button sits under it. */
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    z-index: 50;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .22);
}

/* Room for the floating button to rest against, so it never covers the last line of the
   footer - which is where the company name and contact details are. */
.ld-footer { padding-bottom: calc(96px + env(safe-area-inset-bottom, 0px)); }

@media (min-width: 860px) {
    .ld-float-wa { display: none; }
    .ld-footer { padding-bottom: 56px; }
}

/* ---------------------------------------------------------------- reveal */

/*
 * Content arrives as it is reached. The blur is what separates this from the standard fade:
 * a card that only fades in reads as a page still loading, while one that resolves reads as
 * a page being laid out in front of you.
 *
 * Gated on .ld-js, which landing.js sets on the body as its first statement. Without that
 * gate a script that fails to parse leaves every one of these at opacity 0 for ever, and
 * the visitor gets a hero followed by nothing - the worst failure this page has available
 * to it, from the one class of bug a stylesheet cannot see coming.
 */
.ld-js .ld-reveal {
    opacity: 0;
    transform: translateY(22px);
    filter: blur(6px);
    /* The delay is set per element by landing.js, from its position in its own grid, so a
       row of cards lays itself out rather than all landing on one frame. Defaults to none,
       which is what an element on its own gets. */
    transition:
        opacity .6s ease var(--ld-reveal-delay, 0ms),
        filter .6s ease var(--ld-reveal-delay, 0ms),
        transform .6s cubic-bezier(.2, .7, .3, 1) var(--ld-reveal-delay, 0ms);
}

.ld-reveal.is-revealed { opacity: 1; transform: none; filter: none; }

/* One slow ring off the floating button. It is the only call to action on a phone once the
   header has scrolled away, and this is what stops it reading as part of the page. */
@keyframes ld-pulse {
    0% { box-shadow: 0 8px 24px rgba(0, 0, 0, .22), 0 0 0 0 rgba(37, 211, 102, .5); }
    70% { box-shadow: 0 8px 24px rgba(0, 0, 0, .22), 0 0 0 16px rgba(37, 211, 102, 0); }
    100% { box-shadow: 0 8px 24px rgba(0, 0, 0, .22), 0 0 0 0 rgba(37, 211, 102, 0); }
}

.ld-float-wa { animation: ld-pulse 3.4s ease-out infinite; }

@media (hover: hover) {
    .ld-chips li { transition: border-color .16s ease, color .16s ease; }
    .ld-chips li:hover {
        border-color: color-mix(in srgb, var(--ld-accent) 45%, var(--ld-border));
        color: var(--ld-text);
    }
}

/*
 * Motion is decoration here; anyone who has asked for less of it gets the page with none,
 * fully laid out. Every animation added above has to be named here too - an infinite one
 * that survives this setting is the worst kind to leave running.
 *
 * The marquee is the one that needs more than switching off: with the animation gone the
 * track is still a single unwrapped row twice as wide as the page, so it also has to be
 * given back its wrapping and its duplicate copy has to go.
 */
@media (prefers-reduced-motion: reduce) {
    /* Matched selector for selector with the .ld-js gate above, not a plain .ld-reveal:
       one class scores lower than two and would lose to it, leaving the page hidden for
       exactly the visitors who asked for less motion. */
    .ld-js .ld-reveal { opacity: 1; transform: none; filter: none; transition: none; }
    .ld-mock-bars i, .ld-meter i { transition: none; }
    .ld-btn, .ld-card, .ld-plan { transition: none; }
    .ld-card:hover, .ld-plan:hover { transform: none; }
    .ld-card-icon, .ld-chips li, .ld-tab { transition: none; }
    .ld-steps::after { transition: none; transform: scaleY(1); }

    /* landing.js never types under this preference, so the caret is never inserted - this
       is belt and braces for a browser that reports the query late. */
    .ld-type-caret,
    .ld-float-wa,
    .ld-orb,
    .ld-hero .ld-mock-float,
    .ld-hero .ld-eyebrow i,
    .ld-scroll-cue i,
    .ld-marquee-track,
    .ld-tabs.is-live .ld-tabpanel.is-current,
    .ld-acc-item[open] .ld-acc-body,
    .ld-faq details[open] p { animation: none; }

    .ld-hero .ld-mock { transition: none; }

    .ld-marquee { -webkit-mask-image: none; mask-image: none; }
    .ld-marquee-track { width: auto; flex-wrap: wrap; }
    .ld-marquee-track [aria-hidden="true"] { display: none; }

    /* The chips wrap on the track itself; the reviews are one flex line inside it, so the
       row needs its own wrap or eight cards run off the side of a page that is no longer
       scrolling them past. Centred, and re-inset - the line is full-bleed only because it
       was moving. */
    .ld-reviews { padding-inline: 20px; }
    .ld-review-row {
        flex-wrap: wrap;
        justify-content: center;
        padding-inline: 0;
    }
}

/* ---------------------------------------------------------------- legal
 *
 * The terms and the privacy policy. Long documents that are read on a phone far more often
 * than they are read anywhere else - usually because someone tapped a footer link mid-way
 * through deciding whether to buy - so the measure, the heading rhythm and the contents
 * list are all shaped around a single narrow column.
 *
 * Wears the indigo tone from the FAQ, the section these documents answer after.
 */

.ld-legal-page { padding: 32px 0 64px; }

@media (min-width: 768px) { .ld-legal-page { padding: 48px 0 88px; } }

.ld-back {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-weight: 600;
    font-size: .92rem;
    margin-bottom: 18px;
}

.ld-legal-head { max-width: 70ch; }

.ld-legal-head h1 {
    font-size: clamp(1.8rem, 5.5vw, 2.6rem);
    line-height: 1.15;
    letter-spacing: -.02em;
    font-weight: 800;
    margin: 0 0 12px;
}

.ld-legal-blurb { margin: 0 0 14px; color: var(--ld-muted); font-size: 1.02rem; }

.ld-legal-updated {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0;
    font-size: .88rem;
    color: var(--ld-muted);
}

.ld-legal-sep { opacity: .5; }

/* The other two documents, always one tap away: a reader who wants the privacy policy
   very often arrives at the terms first. Scrolls sideways rather than wrapping, so the row
   stays one line tall on a 320px screen. */
.ld-legal-switch {
    display: flex;
    gap: 8px;
    margin: 28px 0 0;
    padding-bottom: 4px;
    overflow-x: auto;
    scrollbar-width: none;
}

.ld-legal-switch::-webkit-scrollbar { display: none; }

.ld-legal-switch a {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
    padding: 9px 15px;
    border-radius: 999px;
    border: 1px solid var(--ld-border);
    background: var(--ld-surface);
    color: var(--ld-muted);
    font-weight: 600;
    font-size: .9rem;
}

.ld-legal-switch a.is-current {
    background: var(--ld-accent-soft);
    border-color: color-mix(in srgb, var(--ld-accent) 45%, transparent);
    color: var(--ld-accent-strong);
}

.ld-legal-layout { display: grid; gap: 28px; margin-top: 32px; }

/* The contents list moves out into its own column and follows the reader down the page,
   but only once there is a column to spare. Below that it sits above the document, where
   it is a summary to skim rather than a rail to navigate from. */
@media (min-width: 960px) {
    .ld-legal-layout { grid-template-columns: 250px minmax(0, 1fr); gap: 44px; align-items: start; }
    .ld-legal-toc { position: sticky; top: calc(var(--ld-header-h) + 20px); max-height: calc(100dvh - var(--ld-header-h) - 46px); overflow-y: auto; }
}

.ld-legal-toc {
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius);
    background: var(--ld-surface);
    padding: 16px 18px;
}

.ld-legal-toc[hidden] { display: none; }

.ld-legal-toc h2 {
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--ld-muted);
    margin: 0 0 10px;
}

.ld-legal-toc ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; counter-reset: toc; }

.ld-legal-toc a {
    display: block;
    padding: 7px 8px;
    border-radius: 8px;
    font-size: .9rem;
    color: var(--ld-muted);
    font-weight: 600;
}

.ld-legal-toc a:hover { background: var(--ld-bg-alt); color: var(--ld-text); }
.ld-legal-toc a.is-active { background: var(--ld-accent-soft); color: var(--ld-accent-strong); }

/* ---- the document itself ---- */

.ld-legal { max-width: 72ch; }

.ld-legal h2 {
    font-size: 1.35rem;
    font-weight: 800;
    letter-spacing: -.01em;
    margin: 40px 0 12px;
    padding-top: 20px;
    border-top: 1px solid var(--ld-border);
    /* Clears the fixed header when a contents link jumps here. */
    scroll-margin-top: calc(var(--ld-header-h) + 20px);
}

.ld-legal > h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.ld-legal h3 { font-size: 1.05rem; font-weight: 700; margin: 26px 0 8px; }

.ld-legal p,
.ld-legal li { color: var(--ld-muted); }

.ld-legal p { margin: 0 0 14px; }

/* The opening paragraph, which is the plain-English summary of everything under it and the
   only part most readers will read. Set apart so it is not mistaken for clause one. */
.ld-legal p.ld-legal-lead {
    font-size: 1.05rem;
    color: var(--ld-text);
    background: var(--ld-accent-soft);
    border-inline-start: 3px solid var(--ld-accent);
    border-start-end-radius: 12px;
    border-end-end-radius: 12px;
    padding: 16px 18px;
    margin: 0 0 28px;
}

.ld-legal ul, .ld-legal ol { margin: 0 0 16px; padding-inline-start: 22px; display: grid; gap: 8px; }
.ld-legal strong { color: var(--ld-text); font-weight: 700; }
.ld-legal a { text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; }

/* A defined term or an address inside a sentence, which is usually the one thing a reader
   came to check. */
.ld-legal code {
    font-family: var(--ld-font-mono);
    font-size: .88em;
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    padding: 1px 6px;
    border-radius: 6px;
    overflow-wrap: anywhere;
}

.ld-preview-banner {
    background: #92400e;
    color: #fff;
    text-align: center;
    padding: 10px 16px;
    font-size: .9rem;
    font-weight: 600;
}

.ld-preview-banner a { color: #fff; text-decoration: underline; }

/* ===========================================================================
   Language
   ===========================================================================

   The switcher, and everything the page needs when it is read right to left.

   A <details> rather than a scripted menu, so it works before landing.js does - which
   matters more here than anywhere else on the page, because this is the control a reader
   uses when they cannot read the rest of it yet. The open/closed state is the element's
   own, so there is no class to keep in step.
*/

.ld-lang {
    position: relative;
    flex: none;
}

/* 44px, not 38. On a phone the globe is a bare icon - the language name beside it only
   appears from 560px up - so this is one of the smallest targets on the page, and it is the
   one a reader who cannot yet read the page has to hit first. */
.ld-lang > summary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 44px;
    min-width: 44px;
    padding: 0 12px;
    border: 1px solid var(--ld-border);
    border-radius: 999px;
    background: var(--ld-surface);
    color: var(--ld-text);
    font-size: .86rem;
    font-weight: 600;
    cursor: pointer;
    list-style: none;
    user-select: none;
    white-space: nowrap;
}

/* Both are needed: WebKit hides the marker with the pseudo-element, everything else with
   the property above. */
.ld-lang > summary::-webkit-details-marker { display: none; }
.ld-lang > summary:hover { border-color: var(--ld-accent); color: var(--ld-accent-strong); }

.ld-lang > summary .bi-globe2 { font-size: 1.05rem; }
.ld-lang-caret { font-size: .7rem; opacity: .7; transition: transform .18s ease; }
.ld-lang[open] .ld-lang-caret { transform: rotate(180deg); }

/* The name of the language currently showing. Dropped on a narrow screen, where the globe
   alone still says what the control is and the bar has no room for a word in it. */
.ld-lang-current { display: none; }
@media (min-width: 560px) { .ld-lang-current { display: inline; } }

.ld-lang-menu {
    position: absolute;
    top: calc(100% + 8px);
    inset-inline-end: 0;
    z-index: 60;
    min-width: 210px;
    padding: 6px;
    background: var(--ld-surface);
    border: 1px solid var(--ld-border);
    border-radius: var(--ld-radius);
    box-shadow: var(--ld-shadow);
    display: grid;
    gap: 2px;
}

/* 44px minimum per row. Six languages stacked at 36px is a list where the wrong one gets
   picked, and picking the wrong one here leaves a reader on a page they cannot read with a
   cookie that will keep them there. */
.ld-lang-menu a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 44px;
    padding: 8px 12px;
    border-radius: var(--ld-radius-sm);
    color: var(--ld-text);
    font-size: .95rem;
}

.ld-lang-menu a:hover { background: var(--ld-bg-alt); }

.ld-lang-menu a.is-current {
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    font-weight: 700;
}

/* The English name beside the native one, so a reader who arrived on a page in a script
   they cannot read can still find their way back to English. */
.ld-lang-english { font-size: .78rem; color: var(--ld-muted); }
.ld-lang-menu a.is-current .ld-lang-english { color: inherit; opacity: .8; }

/* Native names are set in their own script, and a Devanagari or Bengali line at the same
   size as Latin reads noticeably smaller. */
.ld-lang-native { font-size: 1rem; }

/* ---------------------------------------------------------------- scripts --

   Latin first in every stack: these pages carry English product names - MikroTik, RouterOS,
   PPPoE, CGNAT - inside sentences in every language, and a stack that leads with a script
   font renders those out of a fallback face at a different weight.

   Nothing is downloaded for these. A device that reads Arabic, Devanagari or Bengali ships
   with a face for it, and pulling a 300KB webfont onto a phone on a mobile line to render a
   page it can already draw is a poor trade.
*/
:lang(ar), :lang(ur) {
    font-family: "Source Sans 3", "Noto Naskh Arabic", "Segoe UI", Tahoma, Arial, sans-serif;
}

/* Urdu is set in Nastaliq where the device has it. The style needs the extra line height:
   the script's descenders need more room than Latin text does. */
:lang(ur) { line-height: 1.85; }

:lang(hi), :lang(ne) {
    font-family: "Source Sans 3", "Noto Sans Devanagari", "Nirmala UI", "Mangal", sans-serif;
}

:lang(bn) {
    font-family: "Source Sans 3", "Noto Sans Bengali", "Nirmala UI", "Vrinda", sans-serif;
}

/* ------------------------------------------------------------------- rtl --

   Only the rules that name a side. Everything else in this file is written in flexbox,
   grid and logical properties, which mirror on their own once <html dir> flips.
*/

/* The one thing that must not mirror. A chart's time axis runs left to right in every
   locale - the plot is a picture of a clock, and a mirrored one says the traffic fell
   before it rose. Same for a code, an address and a MAC. */
[dir="rtl"] .ld-chart,
[dir="rtl"] .ld-mock-bars,
[dir="rtl"] .ld-meter { direction: ltr; }

[dir="rtl"] .ld-row .code,
[dir="rtl"] .ld-voucher-code,
[dir="rtl"] .ld-mock-title,
[dir="rtl"] .ld-step-host { direction: ltr; unicode-bidi: isolate; }

/* The legal note about the document's language, under the masthead. */
.ld-legal-lang-note {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 14px 0 0;
    padding: 10px 14px;
    border-radius: 12px;
    background: var(--ld-accent-soft);
    color: var(--ld-accent-strong);
    font-size: .88rem;
    font-weight: 600;
}
