/* ============================================================================
   JustLinks — brand design system (v2, "internet-native")
   ============================================================================
   The marketing surface (home, pricing, explore, legal) is a warm, editorial
   canvas that reads as a creator platform rather than an operations console.
   It is deliberately a separate layer from Bootstrap rather than a theme on top
   of it: Bootstrap still paints the authenticated dashboard, and a theme
   override there would drag every owner-facing table and form along with it.

   Rules that keep this honest:
   - Tokens live in :root (dark) and html[data-theme="light"], and are the only
     source of colour, type and rhythm. Nothing below hardcodes a brand colour
     except in a token definition.
   - Nothing here targets a dashboard class. If a dashboard is ever redesigned
     it should get its own system, not inherit this one by accident.
   - Motion is decorative and always respects prefers-reduced-motion.

   THEME CONTRACT (read before renaming anything)
   - The default theme is DARK and is what an element with no [data-theme]
     attribute gets. That matters: the Identity pages link this file for its
     tokens but never boot the theme script, so they must resolve to the dark
     values they were designed against. The light palette is opt-in, applied by
     js/theme.js setting data-theme="light" on <html> before first paint.
   - Do NOT express the theme with @media (prefers-color-scheme). Doing so would
     flip the Identity pages and the dashboard's token consumers to a light
     palette they were never laid out for. The script decides; the CSS obeys.
   - Some token names are load-bearing for OTHER stylesheets and are kept for
     backward compatibility even though the palette moved on:
         --jl-sky    accent *ink* (readable coloured text), theme-aware
         --jl-cyan    the support/cyan accent
         --jl-deep / --jl-navy   legacy anchors
     Explore.razor.css, LinksPageCard.razor.css and identity.css read these.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   Tokens — dark (default)
   ------------------------------------------------------------------------- */

:root {
    /* Surfaces — deep indigo-charcoal, never pure black, so depth still reads. */
    --jl-canvas: #0f1020;
    --jl-canvas-2: #161832;
    --jl-surface: rgba(255, 255, 255, .045);
    --jl-surface-2: rgba(255, 255, 255, .075);
    --jl-line: rgba(255, 255, 255, .10);
    --jl-line-strong: rgba(255, 255, 255, .20);
    /* Delimits an interactive control, which needs more weight than a divider. At
       --jl-line-strong an outline button reads as disabled rather than as a button:
       this clears 3:1 against the canvas, which is the non-text contrast floor. */
    --jl-outline-border: rgba(255, 255, 255, .34);
    --jl-outline-bg: rgba(255, 255, 255, .07);
    --jl-highlight: rgba(255, 255, 255, .08);
    --jl-nav-bg: rgba(15, 16, 32, .70);
    --jl-glass: rgba(22, 24, 50, .72);

    /* Ink */
    --jl-ink: #f4f4ff;
    --jl-ink-2: rgba(244, 244, 255, .68);
    --jl-ink-3: rgba(244, 244, 255, .46);
    --jl-on-accent: #ffffff;

    /* Brand — the expressive ramp. Decorative uses only; anything carrying text
       uses the interactive ramp below, which is contrast-checked. */
    --jl-primary: #5965ff;
    --jl-violet: #8b5cf6;
    --jl-pink: #ec4899;
    --jl-cyan: #22c7ff;
    --jl-coral: #ff8a65;

    /* Interactive ramp — white text on these clears 4.5:1. */
    --jl-primary-ink: #4b54e8;
    --jl-violet-ink: #7c3aed;

    /* Accent ink: coloured text that has to stay readable on the canvas. */
    --jl-accent-ink: #9aa6ff;
    --jl-sky: var(--jl-accent-ink);

    /* Legacy anchors, kept for the stylesheets that still read them. */
    --jl-deep: #4b54e8;
    --jl-navy: #20245c;

    /* The brand mark's two rings, so an inlined mark can follow the theme. */
    --jl-brand-a: #5965ff;
    --jl-brand-b: #8b5cf6;

    /* Semantic — pricing include/exclude markers, plus the danger/warning set the
       Identity pages need for form validation, alerts and destructive actions. */
    --jl-yes: #34d399;
    --jl-no: #fb7185;
    --jl-danger: rgba(220, 53, 69, .45);
    --jl-danger-ink: #ff8b95;
    --jl-danger-text: #ffd9dd;
    --jl-danger-bg: rgba(220, 53, 69, .16);
    --jl-warn-border: rgba(255, 193, 7, .4);

    /* Ink that intensifies on hover, and a hover wash that works on either canvas.
       Text that used to be a flat #fff on hover cannot stay #fff: on the light
       canvas that is white on white. */
    --jl-ink-strong: #ffffff;
    --jl-surface-hover: rgba(255, 255, 255, .10);

    /* Gradients */
    --jl-grad-brand: linear-gradient(120deg, var(--jl-primary-ink) 0%, var(--jl-violet-ink) 100%);
    --jl-grad-decoration: linear-gradient(120deg, var(--jl-primary) 0%, var(--jl-violet) 52%, var(--jl-pink) 100%);
    --jl-grad-text: linear-gradient(100deg, #ffffff 0%, #bfc4ff 38%, #d8b6ff 68%, #ff9ecb 100%);
    --jl-grad-surface: linear-gradient(170deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .015));

    /* Atmosphere — the radial blooms behind the marketing surface. */
    --jl-bloom-1: rgba(89, 101, 255, .40);
    --jl-bloom-2: rgba(139, 92, 246, .32);
    --jl-bloom-3: rgba(236, 72, 153, .22);
    --jl-bloom-4: rgba(34, 199, 255, .24);

    /* Elevation */
    --jl-shadow-1: 0 1px 2px rgba(4, 4, 16, .40), 0 10px 30px -18px rgba(4, 4, 16, .70);
    --jl-shadow-2: 0 30px 70px -34px rgba(4, 4, 16, .90);
    --jl-shadow-glow: 0 40px 90px -45px rgba(139, 92, 246, .65);

    /* Type */
    --jl-font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --jl-font-display: 'Inter Tight', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --jl-font-serif: 'Instrument Serif', Georgia, 'Times New Roman', serif;

    /* Rhythm — softer, larger radii are a deliberate part of the new language. */
    --jl-radius: 22px;
    --jl-radius-sm: 15px;
    --jl-radius-lg: 28px;
    --jl-gutter: clamp(1.25rem, 4vw, 3rem);
    --jl-section: clamp(5.5rem, 11vw, 10.5rem);
}

/* ---------------------------------------------------------------------------
   Tokens — light (opt-in, applied by js/theme.js)
   ------------------------------------------------------------------------- */

html[data-theme="light"] {
    color-scheme: light;

    /* Warm white and faint blue-violet surfaces, never flat #fff. */
    --jl-canvas: #fbfaff;
    --jl-canvas-2: #f2f0fc;
    --jl-surface: rgba(24, 22, 60, .035);
    --jl-surface-2: rgba(24, 22, 60, .065);
    --jl-line: rgba(24, 22, 60, .10);
    --jl-line-strong: rgba(24, 22, 60, .18);
    /* Higher alpha than the dark theme on purpose: the same perceptual weight needs a
       darker border on a near-white canvas. Both land at roughly 3:1. */
    --jl-outline-border: rgba(22, 21, 47, .45);
    --jl-outline-bg: rgba(22, 21, 47, .05);
    --jl-highlight: rgba(255, 255, 255, .85);
    --jl-nav-bg: rgba(251, 250, 255, .72);
    --jl-glass: rgba(255, 255, 255, .78);

    --jl-ink: #16152f;
    --jl-ink-2: rgba(22, 21, 47, .66);
    /* Higher alpha than the dark theme's .46. Every small-text use of this tier (the
       eyebrow, captions, notes, taglines, footer copy) is below the 18pt/14pt-bold
       large-text threshold, so 4.5:1 applies to all of them. At .50 that measured 3.37:1
       on the canvas and failed; .59 still missed at 4.47:1. .62 is the smallest value
       that clears the floor on every light background, not just the canvas: 4.94:1 on
       #fbfaff, 4.61:1 on a --jl-surface wash, 4.55:1 on --jl-canvas-2. */
    --jl-ink-3: rgba(22, 21, 47, .62);

    /* Accent ink darkens so coloured text keeps its contrast on a light canvas. */
    --jl-accent-ink: #4b54e8;

    --jl-brand-a: #4b54e8;
    --jl-brand-b: #7c5cf6;

    --jl-yes: #047857;
    --jl-no: #e11d48;

    --jl-danger: rgba(220, 53, 69, .50);
    --jl-danger-ink: #b02a37;
    --jl-danger-text: #842029;
    --jl-danger-bg: rgba(220, 53, 69, .07);
    --jl-warn-border: rgba(180, 120, 0, .45);

    --jl-ink-strong: #0b0a1c;
    --jl-surface-hover: rgba(24, 22, 60, .09);

    --jl-grad-text: linear-gradient(100deg, #16152f 0%, var(--jl-primary-ink) 42%, var(--jl-violet-ink) 70%, var(--jl-pink) 100%);
    --jl-grad-surface: linear-gradient(170deg, rgba(255, 255, 255, .92), rgba(255, 255, 255, .62));

    --jl-bloom-1: rgba(89, 101, 255, .20);
    --jl-bloom-2: rgba(139, 92, 246, .18);
    --jl-bloom-3: rgba(236, 72, 153, .13);
    --jl-bloom-4: rgba(34, 199, 255, .16);

    --jl-shadow-1: 0 1px 2px rgba(30, 25, 80, .05), 0 12px 30px -20px rgba(30, 25, 80, .28);
    --jl-shadow-2: 0 30px 70px -38px rgba(30, 25, 80, .38);
    --jl-shadow-glow: 0 40px 90px -45px rgba(139, 92, 246, .45);
}

/* ---------------------------------------------------------------------------
   color-scheme — native controls, the scrollbar and the canvas.

   This is deliberately NOT on :root. color-scheme is inherited, and on the root
   element it also drives the viewport scrollbar and the canvas colour — so
   declaring dark there repaints the light Bootstrap dashboard, which links this
   file only for its tokens and never opts into any of its surfaces. :has() scopes
   the declaration to the shells this system actually paints.

   A theme attribute is still set on the dashboard by js/theme.js, which is why
   the light rule below is safe to leave unscoped: every surface that resolves
   data-theme="light" is light, the dashboard included.

   Order matters for exactly one pair — the public links page must stay dark even
   when the light theme is active — so this block is placed after the light token
   block and re-declares dark for it. The marketing and Identity overrides do not
   depend on order: they win on the extra attribute selector.
   ------------------------------------------------------------------------- */

html:has(.public-page-shell) {
    color-scheme: dark;
}

html:has(.mkt-shell),
html:has(.identity-shell) {
    color-scheme: dark;
}

html[data-theme="light"]:has(.mkt-shell),
html[data-theme="light"]:has(.identity-shell) {
    color-scheme: light;
}

/* ---------------------------------------------------------------------------
   Shell
   ------------------------------------------------------------------------- */

.mkt-shell {
    position: relative;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--jl-canvas);
    color: var(--jl-ink);
    font-family: var(--jl-font-body);
    font-size: 16px;
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overflow-x: clip;
}

/* The atmosphere: soft radial blooms and mesh light, replacing the old
   engineering grid. A single fixed layer with several large, already-soft
   radial gradients — no extra blur filter, which would be expensive to
   rasterise full-screen on every frame. It sits below all content and drifts
   very slowly.

   Drift restarting when the circuit replaces the body is invisible: an infinite
   animation has no perceptible "first frame". (See js/intro.js for the one-shot
   case, which does need gating.) */
.mkt-shell::before {
    content: "";
    position: fixed;
    inset: -18% -12%;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(42% 38% at 12% 8%, var(--jl-bloom-1), transparent 68%),
        radial-gradient(38% 34% at 88% 4%, var(--jl-bloom-4), transparent 70%),
        radial-gradient(46% 40% at 74% 48%, var(--jl-bloom-2), transparent 72%),
        radial-gradient(34% 30% at 6% 78%, var(--jl-bloom-3), transparent 74%),
        radial-gradient(50% 44% at 50% 104%, var(--jl-bloom-2), transparent 74%);
    animation: jl-drift 28s ease-in-out infinite;
    will-change: transform;
}

.mkt-shell > * {
    position: relative;
    z-index: 1;
}

.mkt-shell ::selection {
    background: rgba(89, 101, 255, .34);
    color: #fff;
}

html[data-theme="light"] .mkt-shell ::selection {
    background: rgba(89, 101, 255, .20);
    color: var(--jl-ink);
}

.mkt-main {
    flex: 1;
}

.mkt-container {
    width: min(1180px, 100% - (var(--jl-gutter) * 2));
    margin-inline: auto;
}

.mkt-container--narrow {
    width: min(820px, 100% - (var(--jl-gutter) * 2));
    margin-inline: auto;
}

.mkt-section {
    padding-block: var(--jl-section);
    position: relative;
}

.mkt-hairline {
    height: 1px;
    border: 0;
    margin: 0;
    background: linear-gradient(90deg, transparent, var(--jl-line), transparent);
}

/* ---------------------------------------------------------------------------
   Typography primitives
   ------------------------------------------------------------------------- */

.mkt-display,
.mkt-h1,
.mkt-h2,
.mkt-h3 {
    font-family: var(--jl-font-display);
    font-weight: 700;
    letter-spacing: -.035em;
    line-height: 1;
    margin: 0;
    color: var(--jl-ink);
    text-wrap: balance;
}

.mkt-display {
    font-size: clamp(2.8rem, 7.4vw, 6.4rem);
    line-height: .95;
    letter-spacing: -.045em;
}

.mkt-h1 { font-size: clamp(2.4rem, 5.4vw, 4.4rem); }
.mkt-h2 { font-size: clamp(2rem, 4.4vw, 3.4rem); }
.mkt-h3 { font-size: clamp(1.35rem, 2.4vw, 1.85rem); letter-spacing: -.025em; }

.mkt-serif {
    font-family: var(--jl-font-serif);
    font-style: italic;
    font-weight: 400;
    letter-spacing: 0;
}

.mkt-gradient-text {
    background: var(--jl-grad-text);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.mkt-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: .7rem;
    margin: 0 0 1.6rem;
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: .22em;
    text-transform: uppercase;
    color: var(--jl-ink-3);
}

.mkt-eyebrow::before {
    content: "";
    width: 1.8rem;
    height: 2px;
    border-radius: 2px;
    background: var(--jl-grad-decoration);
    opacity: .9;
}

.mkt-lede {
    margin: 1.7rem 0 0;
    max-width: 48ch;
    font-size: clamp(1.08rem, 1.5vw, 1.32rem);
    line-height: 1.62;
    color: var(--jl-ink-2);
}

.mkt-note {
    font-size: .9rem;
    color: var(--jl-ink-3);
}

/* Bootstrap's reboot paints headings near-black and links default blue. On this
   canvas that is wrong, so the shell re-points the handful of primitives the
   marketing pages actually use. Scoped to .mkt-shell on purpose - the dashboard
   is never inside it and keeps Bootstrap's own defaults.

   Wrapped in :where() so these element-level rules contribute ZERO specificity
   and therefore never out-rank a real component class. Without it, `.mkt-shell a`
   would beat `.mkt-brand` and paint the wordmark accent ink. */
:where(.mkt-shell) h1,
:where(.mkt-shell) h2,
:where(.mkt-shell) h3,
:where(.mkt-shell) h4,
:where(.mkt-shell) h5,
:where(.mkt-shell) h6 {
    color: var(--jl-ink);
}

:where(.mkt-shell) p,
:where(.mkt-shell) li,
:where(.mkt-shell) dl,
:where(.mkt-shell) dd {
    color: inherit;
}

:where(.mkt-shell) a {
    color: var(--jl-sky);
}

.mkt-shell .lead {
    color: var(--jl-ink-2);
    font-size: clamp(1.06rem, 1.5vw, 1.3rem);
}

.mkt-shell .text-muted {
    color: var(--jl-ink-3) !important;
}

.mkt-shell code {
    color: var(--jl-sky);
    background: rgba(139, 92, 246, .12);
    padding: .1rem .42rem;
    border-radius: 6px;
}

.mkt-shell hr {
    border-color: var(--jl-line);
    opacity: 1;
}

/* Form controls on the marketing surface. */
.mkt-shell .form-control,
.mkt-shell .form-select {
    background: var(--jl-surface);
    border: 1px solid var(--jl-line-strong);
    color: var(--jl-ink);
}

.mkt-shell .form-control::placeholder {
    color: var(--jl-ink-3);
}

.mkt-shell .form-control:focus,
.mkt-shell .form-select:focus {
    background: var(--jl-surface-2);
    border-color: var(--jl-primary);
    color: var(--jl-ink);
    box-shadow: none;
}

/* ---------------------------------------------------------------------------
   Buttons — overrides Bootstrap's defaults wherever the marketing shell is
   in scope, so existing markup (.btn-primary) keeps working but reads premium.
   ------------------------------------------------------------------------- */

.mkt-shell .btn {
    --bs-btn-focus-box-shadow: none;
    border-radius: 999px;
    padding: .74rem 1.55rem;
    font-weight: 600;
    font-size: .95rem;
    letter-spacing: -.01em;
    transition: transform .18s ease, background-color .18s ease, border-color .18s ease, box-shadow .18s ease, color .18s ease;
}

.mkt-shell .btn:active {
    transform: translateY(1px);
}

/* The primary action is the ink itself, not the accent, so the accent gradient
   stays the one emphatic colour on the page. */
.mkt-shell .btn-primary {
    background: var(--jl-ink);
    border-color: var(--jl-ink);
    color: var(--jl-canvas);
    box-shadow: none;
}

.mkt-shell .btn-primary:hover,
.mkt-shell .btn-primary:focus-visible {
    background: var(--jl-ink);
    border-color: var(--jl-ink);
    color: var(--jl-canvas);
    filter: brightness(1.06);
    box-shadow: 0 12px 40px -14px rgba(139, 92, 246, .75);
}

.mkt-shell .btn-accent {
    background: var(--jl-grad-brand);
    border: 0;
    color: var(--jl-on-accent);
    box-shadow: 0 16px 44px -18px rgba(124, 58, 237, .85);
    background-size: 160% 160%;
    background-position: 0% 50%;
    transition: background-position .5s ease, transform .18s ease, box-shadow .18s ease, filter .18s ease;
}

.mkt-shell .btn-accent:hover,
.mkt-shell .btn-accent:focus-visible {
    color: var(--jl-on-accent);
    background-position: 100% 50%;
    box-shadow: 0 20px 56px -16px rgba(124, 58, 237, .95);
}

.mkt-shell .btn-accent:disabled {
    opacity: .5;
    box-shadow: none;
}

.mkt-shell .btn-outline-primary,
.mkt-shell .btn-outline-secondary {
    background: var(--jl-outline-bg);
    border: 1px solid var(--jl-outline-border);
    color: var(--jl-ink);
}

.mkt-shell .btn-outline-primary:hover,
.mkt-shell .btn-outline-secondary:hover,
.mkt-shell .btn-outline-primary:focus-visible,
.mkt-shell .btn-outline-secondary:focus-visible {
    background: var(--jl-surface-hover);
    border-color: var(--jl-primary);
    color: var(--jl-ink);
}

.mkt-shell .btn-lg {
    padding: .98rem 2rem;
    font-size: 1.02rem;
}

/* The base rule above out-ranks Bootstrap's own .btn-sm, so the small variant has to be
   re-stated here or the paging and empty-state buttons inherit full-size padding. */
.mkt-shell .btn-sm {
    padding: .45rem .95rem;
    font-size: .84rem;
}

.mkt-shell a:focus-visible,
.mkt-shell button:focus-visible,
.mkt-shell input:focus-visible,
.mkt-shell select:focus-visible {
    outline: 2px solid var(--jl-sky);
    outline-offset: 3px;
    border-radius: 8px;
}

/* ---------------------------------------------------------------------------
   Navigation
   ------------------------------------------------------------------------- */

.mkt-nav {
    position: sticky;
    top: 0;
    z-index: 50;
    background: var(--jl-nav-bg);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    border-bottom: 1px solid transparent;
    transition: border-color .3s ease, background-color .3s ease;
}

.mkt-nav-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
    height: 4.5rem;
}

.mkt-brand {
    display: inline-flex;
    align-items: center;
    gap: .6rem;
    color: var(--jl-ink);
    text-decoration: none;
    font-family: var(--jl-font-display);
    font-weight: 700;
    font-size: 1.18rem;
    letter-spacing: -.03em;
}

.mkt-brand:hover { color: var(--jl-ink); }

/* The mark is inlined in the shell as SVG so its rings can follow the theme
   through --jl-brand-a/--jl-brand-b. An <img> could not inherit a colour. */
.mkt-brand-mark {
    width: 30px;
    height: auto;
    aspect-ratio: 184.62 / 159;
    flex: none;
    overflow: visible;
}

.mkt-brand-dot {
    color: var(--jl-accent-ink);
}

.mkt-nav-links {
    display: flex;
    align-items: center;
    gap: 2rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.mkt-nav-links a {
    position: relative;
    color: var(--jl-ink-2);
    text-decoration: none;
    font-size: .93rem;
    font-weight: 500;
    transition: color .18s ease;
}

.mkt-nav-links a:hover,
.mkt-nav-links a.active {
    color: var(--jl-ink);
}

.mkt-nav-links a.active::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1.55rem;
    height: 2px;
    border-radius: 2px;
    background: var(--jl-grad-decoration);
}

.mkt-nav-actions {
    display: flex;
    align-items: center;
    gap: .6rem;
}

.mkt-nav-actions .btn {
    padding: .55rem 1.15rem;
    font-size: .9rem;
    white-space: nowrap;
}

.mkt-nav-signin {
    color: var(--jl-ink-2);
    text-decoration: none;
    font-size: .93rem;
    font-weight: 500;
    margin-right: .4rem;
}

.mkt-nav-signin:hover { color: var(--jl-ink); }

/* Theme switcher — a glass pill. Both labels live in the DOM and CSS reveals the
   one that matches the current theme, so the accessible name is always correct
   without any script needing to run (the prerendered button is right on first
   paint, and the circuit's re-inserted copy is right too). */
.mkt-theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.4rem;
    flex: none;
    margin-right: .35rem;
    padding: 0;
    border: 1px solid var(--jl-line-strong);
    border-radius: 999px;
    background: var(--jl-surface);
    color: var(--jl-ink-2);
    cursor: pointer;
    transition: color .18s ease, border-color .18s ease, background-color .18s ease, transform .18s ease;
}

.mkt-theme-toggle:hover {
    color: var(--jl-ink);
    border-color: var(--jl-primary);
    background: var(--jl-surface-2);
}

.mkt-theme-toggle:active { transform: scale(.94); }

.mkt-theme-toggle .jl-theme-moon { display: none; }

html[data-theme="light"] .mkt-theme-toggle .jl-theme-sun { display: none; }
html[data-theme="light"] .mkt-theme-toggle .jl-theme-moon { display: inline-flex; }

.mkt-theme-toggle > span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

@media (max-width: 767.98px) {
    .mkt-nav-links { display: none; }
    .mkt-nav-signin { display: none; }
    .mkt-nav-inner { height: 4rem; }
}

@media (max-width: 575.98px) {
    .mkt-nav-actions .btn { padding: .5rem .9rem; font-size: .84rem; }
    .mkt-brand { font-size: 1.05rem; }
    .mkt-brand-mark { width: 26px; }
    .mkt-theme-toggle { width: 2.2rem; height: 2.2rem; margin-right: .15rem; }
}

/* ---------------------------------------------------------------------------
   Footer
   ------------------------------------------------------------------------- */

.mkt-footer {
    border-top: 1px solid var(--jl-line);
    padding-block: clamp(3.5rem, 7vw, 5.5rem) 2.5rem;
    color: var(--jl-ink-2);
}

.mkt-footer-grid {
    display: grid;
    grid-template-columns: 1.6fr repeat(3, 1fr);
    gap: 2.5rem;
}

.mkt-footer-brand p {
    margin: 1rem 0 0;
    max-width: 30ch;
    font-size: .9rem;
    color: var(--jl-ink-3);
}

.mkt-footer h4 {
    margin: 0 0 1rem;
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: var(--jl-ink-3);
}

.mkt-footer ul {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: .6rem;
}

.mkt-footer a {
    color: var(--jl-ink-2);
    text-decoration: none;
    font-size: .92rem;
}

.mkt-footer a:hover { color: var(--jl-ink); }

.mkt-footer-base {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-top: 3.5rem;
    padding-top: 1.75rem;
    border-top: 1px solid var(--jl-line);
    font-size: .84rem;
    color: var(--jl-ink-3);
}

.mkt-footer-base .mkt-theme-toggle { margin-right: 0; }

@media (max-width: 767.98px) {
    .mkt-footer-grid { grid-template-columns: 1fr 1fr; }
    .mkt-footer-brand { grid-column: 1 / -1; }
}

/* ---------------------------------------------------------------------------
   Hero
   ------------------------------------------------------------------------- */

.mkt-hero {
    position: relative;
    padding-block: clamp(4rem, 9vw, 8rem) clamp(5rem, 9vw, 9rem);
    overflow: hidden;
}

/* A second, tighter set of blooms local to the hero, plus a single soft mesh
   wash. The old 64px engineering grid is gone — it was the strongest signal that
   this was an operations console. */
.mkt-hero::before {
    content: "";
    position: absolute;
    inset: -30% -20% auto -20%;
    height: 95%;
    background:
        radial-gradient(46% 58% at 18% 24%, var(--jl-bloom-1), transparent 70%),
        radial-gradient(40% 52% at 84% 10%, var(--jl-bloom-2), transparent 72%),
        radial-gradient(52% 60% at 62% 96%, var(--jl-bloom-3), transparent 74%);
    pointer-events: none;
}

.mkt-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(58% 42% at 50% 0%, var(--jl-bloom-4), transparent 70%),
        linear-gradient(180deg, transparent 62%, var(--jl-canvas) 100%);
    pointer-events: none;
}

.mkt-hero-inner {
    position: relative;
    display: grid;
    grid-template-columns: 1.1fr .9fr;
    align-items: center;
    gap: clamp(2.5rem, 6vw, 5rem);
}

.mkt-hero-copy { max-width: 40rem; }

/* Spacing is owned by .jl-claim-stack's gap, so these two rows carry none of their own. */
.mkt-hero-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .8rem;
    margin-top: 0;
}

/* The reassurance row under the claim field. It carries meaning - "free", "your own
   subdomain", "no card" are answers, not fine print - so it uses --jl-ink-2 rather than
   the tertiary --jl-ink-3, which is reserved for incidental text. --jl-ink-2 puts it at
   8.1:1 dark and 5.7:1 light. */
.mkt-hero-trust {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem 1.4rem;
    margin-top: 0;
    font-size: .85rem;
    color: var(--jl-ink-2);
}

.mkt-hero-trust span {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
}

.mkt-hero-trust i { color: var(--jl-accent-ink); font-size: .78rem; }

/* ---------------------------------------------------------------------------
   Hero — link availability checker

   One field and one button. The input and the ".justlinks.page" suffix sit
   inside a single bordered field so the whole address reads as one control,
   which is what the visitor is actually choosing.

   Focus lights the OUTER field, not the input: the caret is enough to show
   where typing goes, and a second ring inside the box is noise.
   ------------------------------------------------------------------------- */

/* A single column with one gap, so the field, the status line, the trust row and the actions
   are all separated by exactly the same space. The status reserves a line's height, so an
   empty status occupies the same space as a filled one and nothing below ever moves. */
.jl-claim-stack {
    display: flex;
    flex-direction: column;
    gap: .85rem;
    margin-top: 2.5rem;
    max-width: 42rem;
}

.jl-claim {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: .75rem;
    margin: 0;
}

.jl-claim-field {
    flex: 1 1 15rem;
    display: flex;
    align-items: center;
    /* Symmetric horizontal padding: the ".justlinks.page" suffix gets the same breathing room
       from the right edge that the placeholder gets from the left. */
    padding: .18rem 1.15rem;
    border: 1px solid var(--jl-line-strong);
    border-radius: 18px;
    background: var(--jl-glass);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    box-shadow: var(--jl-shadow-1);
    transition: border-color .18s ease, box-shadow .18s ease;
}

/* The outer field is the thing that highlights on focus. */
.jl-claim-field:focus-within {
    border-color: var(--jl-primary);
    box-shadow: 0 0 0 4px rgba(89, 101, 255, .22), var(--jl-shadow-1);
}

.jl-claim-input {
    flex: 1;
    min-width: 4rem;
    padding: .72rem 0;
    border: 0;
    background: transparent;
    color: var(--jl-ink);
    font-size: 1.02rem;
    outline: none;
}

/* The inner textbox itself must NOT highlight. This has to out-specify the global
   .mkt-shell input:focus-visible rule (which draws a 2px accent outline), or the typing area
   shows a ring of its own inside the already-highlighted field. */
.mkt-shell .jl-claim-input:focus,
.mkt-shell .jl-claim-input:focus-visible {
    outline: none;
    box-shadow: none;
}

.jl-claim-input::placeholder { color: var(--jl-ink-3); }

.jl-claim-suffix {
    color: var(--jl-ink-3);
    font-size: 1.02rem;
    white-space: nowrap;
    user-select: none;
}

.jl-claim-btn { flex: 0 0 auto; }

/* In the flow with a FIXED height. min-height was not enough: the text's inherited line box
   (~1.42rem) is taller than the reserved minimum, so a filled status was a pixel or two taller
   than an empty one and pushed the rows below. An explicit height, with line-height:1 so the
   content always fits inside it, is identical in both states and the stack gap never moves. */
.jl-claim-status {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 0;
    height: 1.4rem;
    font-size: .95rem;
    line-height: 1;
    font-weight: 500;
}

.jl-claim-status i { font-size: .85rem; }

/* Green for available, red for refused — distinct from the accent, which is
   decoration rather than a verdict. */
.jl-claim-status--ok { color: var(--jl-yes); }
.jl-claim-status--bad { color: var(--jl-no); }

@media (max-width: 575.98px) {
    /* The button takes the full width under the field so the pair does not wrap
       into a cramped half-row on a phone. */
    .jl-claim-field { flex-basis: 100%; }
    .jl-claim-btn { width: 100%; }
}


@keyframes jl-rise {
    from { opacity: 0; transform: translateY(18px); }
    to { opacity: 1; transform: none; }
}

@keyframes jl-drift {
    0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
    50% { transform: translate3d(-1.5%, -2.5%, 0) scale(1.04); }
}

/* ---------------------------------------------------------------------------
   Entrance animations, gated.

   These play once per document load, not once per element insertion. That
   distinction is load-bearing in a prerendered Blazor app: the circuit's first
   render re-inserts the prerendered markup, and re-inserting an element restarts
   its CSS animations. On a cold load the circuit connects after the browser has
   already painted, so an ungated entrance visibly replays - the hero fades in a
   second time - which reads as the top of the page blinking.

   So the entrance is opted into by a class on <html>, which the framework never
   touches: it is present for the first paint and removed by js/intro.js the
   moment the circuit applies its first render. Markup inserted after that has
   nothing left to replay, and shows its finished state immediately.

   The ungated state is deliberately the FINISHED state (fully opaque, no
   transform), so losing the gate mid-flight snaps to the end rather than hiding
   the content.

   Infinite animations are not gated: replaying one is invisible, and gating it
   would stop it for the rest of the page's life.
   --------------------------------------------------------------------------- */

html.jl-intro .mkt-rise {
    animation: jl-rise .8s cubic-bezier(.22, 1, .36, 1) both;
}

.mkt-rise-1 { animation-delay: .05s; }
.mkt-rise-2 { animation-delay: .14s; }
.mkt-rise-3 { animation-delay: .23s; }
.mkt-rise-4 { animation-delay: .32s; }

/* ---------------------------------------------------------------------------
   Hero product mock — a faithful miniature of a real JustLinks page. Built in
   markup rather than shipped as a screenshot so it stays crisp at any density
   and never goes stale against the live product.
   ------------------------------------------------------------------------- */

.mkt-mock {
    position: relative;
    display: flex;
    justify-content: center;
    perspective: 1400px;
}

.mkt-mock-glow {
    position: absolute;
    inset: 6% 4% -8% 4%;
    background:
        radial-gradient(58% 66% at 42% 28%, var(--jl-bloom-2), transparent 70%),
        radial-gradient(50% 56% at 70% 62%, var(--jl-bloom-3), transparent 72%);
    filter: blur(38px);
    z-index: 0;
}

.mkt-phone {
    position: relative;
    z-index: 1;
    width: min(330px, 82%);
    border-radius: 42px;
    padding: 12px;
    background: linear-gradient(160deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, .04));
    border: 1px solid var(--jl-line-strong);
    box-shadow: var(--jl-shadow-2), inset 0 1px 0 rgba(255, 255, 255, .18);
    transform: rotateX(6deg) rotateY(-8deg) rotateZ(1.5deg);
    transition: transform .6s cubic-bezier(.22, 1, .36, 1);
}

html[data-theme="light"] .mkt-phone {
    background: linear-gradient(160deg, rgba(255, 255, 255, .95), rgba(244, 241, 255, .75));
    box-shadow: var(--jl-shadow-2), inset 0 1px 0 rgba(255, 255, 255, .9);
}

.mkt-mock:hover .mkt-phone {
    transform: rotateX(2deg) rotateY(-3deg) rotateZ(0deg);
}

.mkt-phone-screen {
    border-radius: 32px;
    background: #101126;
    padding: 1.4rem 1.1rem 1.1rem;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, .06);
}

.mock-page-head {
    text-align: center;
    padding-bottom: 1.1rem;
}

.mock-avatar {
    width: 54px;
    height: 54px;
    margin: 0 auto .6rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-family: var(--jl-font-display);
    font-weight: 700;
    font-size: 1.3rem;
    color: #fff;
    background: var(--jl-grad-decoration);
    box-shadow: 0 10px 26px -8px rgba(139, 92, 246, .85);
}

.mock-name {
    font-family: var(--jl-font-display);
    font-weight: 600;
    font-size: .98rem;
    color: #fff;
}

.mock-sub {
    font-size: .72rem;
    color: rgba(244, 244, 255, .5);
    letter-spacing: .02em;
}

.mock-links {
    display: grid;
    gap: .5rem;
}

.mock-link {
    display: flex;
    align-items: center;
    gap: .7rem;
    padding: .7rem .8rem;
    border-radius: 13px;
    background: rgba(255, 255, 255, .06);
    border: 1px solid rgba(255, 255, 255, .08);
    font-size: .84rem;
    color: #f4f4ff;
}

/* Each row gets its own accent from the ramp. Small, deliberate variety is what
   makes the mock read as a person's page rather than a widget. */
.mock-link i { width: 1.6rem; text-align: center; font-size: .82rem; }
.mock-link:nth-child(1) i { color: var(--jl-coral); }
.mock-link:nth-child(2) i { color: var(--jl-pink); }
.mock-link:nth-child(3) i { color: var(--jl-cyan); }
.mock-link:nth-child(4) i { color: var(--jl-violet); }

.mock-link .mock-chev {
    margin-left: auto;
    color: rgba(244, 244, 255, .45);
    font-size: .7rem;
}

.mock-link--pinned {
    background: linear-gradient(120deg, rgba(124, 92, 246, .35), rgba(236, 72, 153, .22));
    border-color: rgba(160, 140, 255, .45);
}

.mock-made {
    margin-top: 1rem;
    text-align: center;
    font-size: .66rem;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: rgba(244, 244, 255, .5);
}

.mkt-mock-chip {
    position: absolute;
    z-index: 2;
    padding: .55rem .95rem;
    border-radius: 999px;
    background: var(--jl-glass);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid var(--jl-line-strong);
    font-size: .78rem;
    font-weight: 600;
    color: var(--jl-ink);
    box-shadow: var(--jl-shadow-1);
    white-space: nowrap;
}

.mkt-mock-chip i { color: var(--jl-accent-ink); margin-right: .35rem; }

.mkt-mock-chip--a { top: 12%; left: -2%; animation: jl-float 7s ease-in-out infinite; }
.mkt-mock-chip--b { bottom: 16%; right: -2%; animation: jl-float 8.5s ease-in-out infinite reverse; }

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

@media (max-width: 991.98px) {
    .mkt-hero-inner { grid-template-columns: 1fr; }
    .mkt-hero-copy { max-width: none; }
    .mkt-mock { margin-top: 1rem; }
    .mkt-mock-chip--a { left: 2%; }
    .mkt-mock-chip--b { right: 2%; }
}

/* On a phone the chips sit on top of the mini page instead of beside it, so they are
   dropped rather than made to collide with the mock. */
@media (max-width: 575.98px) {
    .mkt-mock-chip { display: none; }
    .mkt-phone { transform: none; }
}

/* ---------------------------------------------------------------------------
   Editorial rows — numbered convictions, not feature cards.
   ------------------------------------------------------------------------- */

.mkt-rows {
    border-top: 1px solid var(--jl-line);
}

.mkt-row {
    display: grid;
    grid-template-columns: 5rem 1fr 1.1fr;
    gap: clamp(1.5rem, 5vw, 4rem);
    align-items: start;
    padding-block: clamp(2.25rem, 4vw, 3.25rem);
    border-bottom: 1px solid var(--jl-line);
}

.mkt-row-num {
    font-family: var(--jl-font-display);
    font-size: .82rem;
    font-weight: 600;
    letter-spacing: .18em;
    color: var(--jl-accent-ink);
    padding-top: .5rem;
}

.mkt-row h3 { font-size: clamp(1.6rem, 3.4vw, 2.6rem); }

.mkt-row p {
    margin: 0;
    padding-top: .45rem;
    color: var(--jl-ink-2);
    font-size: clamp(1rem, 1.4vw, 1.12rem);
    max-width: 42ch;
}

@media (max-width: 767.98px) {
    .mkt-row { grid-template-columns: 3rem 1fr; }
    .mkt-row p { grid-column: 2; padding-top: .25rem; }
}

/* ---------------------------------------------------------------------------
   Split feature sections with a product visual.
   ------------------------------------------------------------------------- */

.mkt-split {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(2.5rem, 7vw, 6rem);
    align-items: center;
}

.mkt-split--reverse .mkt-split-visual { order: -1; }

.mkt-split-copy .mkt-h2 { margin-bottom: 1.4rem; }

.mkt-split-copy p {
    color: var(--jl-ink-2);
    font-size: clamp(1rem, 1.4vw, 1.12rem);
    max-width: 42ch;
}

.mkt-split-list {
    margin: 2rem 0 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: .9rem;
}

.mkt-split-list li {
    display: flex;
    align-items: center;
    gap: .8rem;
    color: var(--jl-ink-2);
    font-size: .98rem;
}

.mkt-split-list i { color: var(--jl-accent-ink); }

/* Shared card surface — layered, translucent, softly lifted. Used by the split
   panels and the pricing cards so the whole surface has one card language. */
.mkt-panel {
    position: relative;
    border-radius: var(--jl-radius-lg);
    border: 1px solid var(--jl-line);
    background: var(--jl-grad-surface);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    padding: clamp(1.5rem, 3vw, 2.35rem);
    box-shadow: var(--jl-shadow-1), inset 0 1px 0 var(--jl-highlight);
    transition: transform .3s cubic-bezier(.22, 1, .36, 1), box-shadow .3s ease, border-color .3s ease;
}

.mkt-split-visual:hover .mkt-panel {
    transform: translateY(-4px);
    border-color: var(--jl-line-strong);
    box-shadow: var(--jl-shadow-2), inset 0 1px 0 var(--jl-highlight);
}

.mkt-panel-glow {
    position: absolute;
    inset: -22%;
    background:
        radial-gradient(45% 45% at 62% 28%, var(--jl-bloom-2), transparent 70%),
        radial-gradient(40% 40% at 28% 78%, var(--jl-bloom-3), transparent 72%);
    filter: blur(42px);
    z-index: -1;
}

/* Analytics visual */
.mkt-bars {
    display: flex;
    align-items: flex-end;
    gap: .65rem;
    height: 190px;
    padding-top: 1rem;
}

.mkt-bar {
    flex: 1;
    border-radius: 8px 8px 3px 3px;
    background: linear-gradient(180deg, var(--jl-primary), rgba(89, 101, 255, .22));
    position: relative;
    transform-origin: bottom;
}

.mkt-bar:nth-child(4n) { background: linear-gradient(180deg, var(--jl-violet), rgba(139, 92, 246, .22)); }
.mkt-bar:nth-child(4n+2) { background: linear-gradient(180deg, var(--jl-cyan), rgba(34, 199, 255, .22)); }

/* Gated with the rest of the entrance — see the note above jl-rise. The ungated
   state is the grown bar, so the chart is never left empty. */
html.jl-intro .mkt-bar {
    animation: jl-grow 1s cubic-bezier(.22, 1, .36, 1) both;
}

.mkt-bar:nth-child(1) { height: 34%; animation-delay: .0s; }
.mkt-bar:nth-child(2) { height: 52%; animation-delay: .06s; }
.mkt-bar:nth-child(3) { height: 41%; animation-delay: .12s; }
.mkt-bar:nth-child(4) { height: 68%; animation-delay: .18s; }
.mkt-bar:nth-child(5) { height: 57%; animation-delay: .24s; }
.mkt-bar:nth-child(6) { height: 84%; animation-delay: .30s; }
.mkt-bar:nth-child(7) {
    height: 100%;
    animation-delay: .36s;
    background: linear-gradient(180deg, var(--jl-pink), var(--jl-violet));
}

@keyframes jl-grow {
    from { transform: scaleY(.05); opacity: .3; }
    to { transform: scaleY(1); opacity: 1; }
}

.mkt-panel-stat {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: .25rem;
}

.mkt-panel-stat strong {
    font-family: var(--jl-font-display);
    font-size: clamp(2rem, 4vw, 2.8rem);
    letter-spacing: -.04em;
}

.mkt-panel-stat span { color: var(--jl-ink-3); font-size: .82rem; }

.mkt-panel-caption {
    display: flex;
    justify-content: space-between;
    margin-top: .9rem;
    font-size: .74rem;
    color: var(--jl-ink-3);
    letter-spacing: .04em;
}

/* Subdomain / QR visual */
.mkt-url {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .95rem 1.15rem;
    border-radius: var(--jl-radius-sm);
    border: 1px solid var(--jl-line-strong);
    background: var(--jl-surface);
    font-family: var(--jl-font-display);
    font-size: clamp(.95rem, 2vw, 1.2rem);
    letter-spacing: -.02em;
}

.mkt-url .mkt-url-name {
    color: var(--jl-ink);
    border-bottom: 2px dashed var(--jl-primary);
    padding-bottom: 1px;
}

.mkt-url .mkt-url-suffix { color: var(--jl-ink-3); }

.mkt-qr-row {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    margin-top: 1.4rem;
}

.mkt-qr {
    width: 92px;
    height: 92px;
    flex: none;
    border-radius: 18px;
    background:
        conic-gradient(from 0deg, var(--jl-ink) 0 25%, transparent 0 50%, var(--jl-ink) 0 75%, transparent 0) 0 0 / 18px 18px,
        radial-gradient(circle at 30% 30%, var(--jl-ink) 0 6px, transparent 6px),
        radial-gradient(circle at 70% 30%, var(--jl-ink) 0 6px, transparent 6px),
        radial-gradient(circle at 30% 70%, var(--jl-ink) 0 6px, transparent 6px),
        var(--jl-canvas-2);
    border: 1px solid var(--jl-line-strong);
    box-shadow: inset 0 0 0 8px var(--jl-canvas-2), 0 20px 40px -24px rgba(139, 92, 246, .85);
}

.mkt-qr-copy strong { display: block; font-family: var(--jl-font-display); font-size: 1rem; }
.mkt-qr-copy span { color: var(--jl-ink-3); font-size: .84rem; }

/* SEO / search-result visual — a result card built from the page's own metadata, with the
   signals that make it render that way shown as chips beneath. */
.mkt-serp {
    border-radius: var(--jl-radius-sm);
    border: 1px solid var(--jl-line);
    background: var(--jl-surface);
    padding: 1.15rem 1.25rem;
}

.mkt-serp-source {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: .5rem;
    font-size: .82rem;
    color: var(--jl-ink-2);
}

.mkt-serp-source i {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.35rem;
    height: 1.35rem;
    flex: none;
    border-radius: 50%;
    background: var(--jl-grad-decoration);
    color: #fff;
    font-size: .58rem;
}

.mkt-serp-title {
    margin-bottom: .35rem;
    font-family: var(--jl-font-display);
    font-size: clamp(1.05rem, 2vw, 1.3rem);
    font-weight: 600;
    letter-spacing: -.02em;
    color: var(--jl-sky);
}

.mkt-serp-desc {
    color: var(--jl-ink-2);
    font-size: .92rem;
    line-height: 1.5;
    max-width: 46ch;
}

.mkt-serp-chips {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: 1.4rem;
}

.mkt-serp-chips span {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .4rem .8rem;
    border-radius: 999px;
    border: 1px solid var(--jl-line);
    background: var(--jl-surface);
    font-size: .8rem;
    color: var(--jl-ink-2);
}

.mkt-serp-chips i { color: var(--jl-accent-ink); font-size: .75rem; }


@media (max-width: 991.98px) {
    .mkt-split { grid-template-columns: 1fr; }
    .mkt-split--reverse .mkt-split-visual { order: 0; }
}

/* ---------------------------------------------------------------------------
   Showcase shelf
   ------------------------------------------------------------------------- */

.mkt-section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1.5rem;
    margin-bottom: clamp(2.5rem, 5vw, 4rem);
}

.mkt-section-head p {
    margin: 1rem 0 0;
    max-width: 48ch;
    color: var(--jl-ink-2);
    font-size: clamp(1rem, 1.4vw, 1.12rem);
}

.mkt-shelf {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 1.1rem;
}

/* ---------------------------------------------------------------------------
   Closing call to action
   ------------------------------------------------------------------------- */

.mkt-cta {
    position: relative;
    text-align: center;
    padding-block: clamp(6rem, 12vw, 11rem);
    overflow: hidden;
}

.mkt-cta::before {
    content: "";
    position: absolute;
    inset: auto -10% -45% -10%;
    height: 85%;
    background:
        radial-gradient(42% 58% at 38% 100%, var(--jl-bloom-2), transparent 72%),
        radial-gradient(40% 56% at 70% 100%, var(--jl-bloom-3), transparent 72%);
    pointer-events: none;
    animation: jl-cta-pulse 9s ease-in-out infinite;
}

@keyframes jl-cta-pulse {
    0%, 100% { opacity: .82; transform: translateY(0); }
    50% { opacity: 1; transform: translateY(-2%); }
}

.mkt-cta .mkt-display { font-size: clamp(2.6rem, 8vw, 6.5rem); }

.mkt-cta-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .8rem;
    margin-top: 2.6rem;
}

.mkt-cta-note {
    margin-top: 1.4rem;
    font-size: .88rem;
    color: var(--jl-ink-3);
}

/* ---------------------------------------------------------------------------
   Pricing — editorial tiers
   ------------------------------------------------------------------------- */

.pricing-shell {
    padding-block: clamp(4rem, 8vw, 7rem) clamp(5rem, 9vw, 8rem);
}

.pricing-shell .pricing-head { text-align: center; margin-bottom: clamp(3rem, 6vw, 4.5rem); }

.pricing-shell .pricing-head .mkt-lede { margin-inline: auto; }

.pricing-interval-toggle {
    display: flex;
    width: fit-content;
    gap: .3rem;
    padding: .3rem;
    margin: 2rem auto 0;
    border: 1px solid var(--jl-line);
    border-radius: 999px;
    background: var(--jl-surface);
}

.pricing-interval-toggle .btn {
    padding: .55rem 1.35rem;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--jl-ink-2);
    font-weight: 600;
}

.pricing-interval-toggle .btn-primary,
.pricing-interval-toggle .btn-primary:hover {
    background: var(--jl-ink);
    color: var(--jl-canvas);
}

.pricing-interval-note { font-size: .88rem; color: var(--jl-ink-3); }

.pricing-card {
    position: relative;
    height: 100%;
    border: 1px solid var(--jl-line);
    border-radius: var(--jl-radius-lg);
    background: var(--jl-grad-surface);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: var(--jl-ink);
    box-shadow: var(--jl-shadow-1), inset 0 1px 0 var(--jl-highlight);
    transition: border-color .25s ease, transform .3s cubic-bezier(.22, 1, .36, 1), box-shadow .25s ease;
}

.pricing-card:hover {
    transform: translateY(-5px);
    border-color: var(--jl-line-strong);
    box-shadow: var(--jl-shadow-2), inset 0 1px 0 var(--jl-highlight);
}

.pricing-card-featured {
    border-color: rgba(139, 92, 246, .55);
    background:
        linear-gradient(170deg, rgba(89, 101, 255, .16), rgba(236, 72, 153, .06)),
        var(--jl-grad-surface);
    box-shadow: var(--jl-shadow-glow), inset 0 1px 0 var(--jl-highlight);
}

.pricing-card-featured::before {
    content: "Most popular";
    position: absolute;
    top: 1.1rem;
    right: 1.1rem;
    padding: .3rem .8rem;
    border-radius: 999px;
    background: var(--jl-grad-decoration);
    color: #fff;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
}

.pricing-card .card-body { padding: clamp(1.6rem, 3vw, 2.2rem); }

.pricing-tier-name {
    font-family: var(--jl-font-display);
    font-weight: 700;
    letter-spacing: -.03em;
    font-size: 1.5rem;
    margin: 0;
}

.pricing-tier-tagline { color: var(--jl-ink-3); font-size: .92rem; margin: .35rem 0 1.5rem; }

.pricing-price { display: flex; align-items: baseline; gap: .35rem; }

.pricing-price-amount {
    font-family: var(--jl-font-display);
    font-weight: 700;
    font-size: clamp(2.4rem, 5vw, 3.1rem);
    letter-spacing: -.045em;
    line-height: 1;
}

.pricing-price-period { color: var(--jl-ink-3); font-size: .95rem; }

.pricing-features { display: grid; gap: 0; margin: 0 0 1.75rem; }

.pricing-feature {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .7rem 0;
    border-bottom: 1px solid var(--jl-line);
    font-size: .92rem;
}

.pricing-feature:first-child { border-top: 1px solid var(--jl-line); }

.pricing-feature dt { font-weight: 500; color: var(--jl-ink-2); }
.pricing-feature dd { margin: 0; font-weight: 600; color: var(--jl-ink); text-align: right; }

/* Qualitative subtext under a feature label - the exact URL a capability produces. */
.pricing-feature-note {
    display: block;
    margin-top: .15rem;
    font-size: .78rem;
    font-weight: 400;
    color: var(--jl-ink-3);
    line-height: 1.35;
}

/* Help affordance for a feature whose setup the owner performs (e.g. DNS for a custom
   domain). Native title tooltip, matching the rest of the app - no Bootstrap JS init. */
.pricing-feature-help {
    color: var(--jl-ink-3);
    cursor: help;
    font-size: .85rem;
}

/* Included / not-included markers in the feature table. Colour is the only signal, so the
   words live on in each icon's title and aria-label. Sized a touch above the row text so the
   glyph reads as a status mark rather than an inline character. */
.pricing-feature-yes,
.pricing-feature-no {
    font-size: 1.05rem;
    line-height: 1;
    vertical-align: -.08em;
}

.pricing-feature-yes { color: var(--jl-yes); }
.pricing-feature-no { color: var(--jl-no); }

.pricing-notes {
    max-width: 820px;
    margin: clamp(3.5rem, 7vw, 5rem) auto 0;
    padding-top: clamp(2.5rem, 5vw, 3.5rem);
    border-top: 1px solid var(--jl-line);
}

.pricing-notes h2 { color: var(--jl-ink); font-family: var(--jl-font-display); letter-spacing: -.02em; }
.pricing-notes p { color: var(--jl-ink-2); }
.pricing-notes code { color: var(--jl-sky); background: rgba(139, 92, 246, .12); padding: .1rem .4rem; border-radius: 6px; }

.pricing-shell .alert {
    border: 1px solid var(--jl-line-strong);
    border-radius: var(--jl-radius-sm);
    background: var(--jl-surface);
    color: var(--jl-ink);
}

.pricing-shell .alert-warning { border-color: rgba(255, 193, 7, .4); }
.pricing-shell .alert-success { border-color: rgba(89, 101, 255, .45); }

/* Plan-status card reuses Bootstrap .card */
.pricing-shell .card.bg-white {
    background: var(--jl-grad-surface) !important;
    border: 1px solid var(--jl-line) !important;
    color: var(--jl-ink);
    border-radius: var(--jl-radius-lg);
}

.pricing-shell .text-muted { color: var(--jl-ink-3) !important; }

/* ---------------------------------------------------------------------------
   Public product page — the owner's page, deliberately NOT themed.

   This one surface stays on the fixed dark canvas in both themes. It is not the
   platform's canvas, it is the backdrop the owner's coloured card floats on, and
   the public page's own markup (PublicLinksPage.razor.css) is painted against a
   dark backdrop — translucent white banners, near-white text. Letting the
   visitor's stored marketing theme flip this to a light canvas would put
   near-white chrome on a near-white page, and it would also mean a visitor could
   restyle somebody else's page from a toggle on justlinks.page.

   So it reads the dark constants directly rather than the theme-aware tokens.
   Global because two layouts render this shell.
   ------------------------------------------------------------------------- */

.public-page-shell {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    min-height: 100vh;
    padding: 2.5rem 1rem 3.5rem;
    background:
        radial-gradient(60% 42% at 50% 0%, rgba(139, 92, 246, .26), transparent 70%),
        radial-gradient(50% 40% at 20% 100%, rgba(89, 101, 255, .22), transparent 72%),
        #0f1020;
    color: #f4f4ff;
    font-family: var(--jl-font-body);
    -webkit-font-smoothing: antialiased;
}

.public-brand-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    margin-bottom: 1rem;
    padding: .7rem 1rem;
    background: rgba(22, 24, 50, .72);
    border: 1px solid rgba(255, 255, 255, .10);
    border-radius: 999px;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: #f4f4ff;
    font-size: .95rem;
    font-weight: 600;
    letter-spacing: -.01em;
    text-decoration: none;
}

.public-brand-banner:hover,
.public-brand-banner:focus {
    color: #fff;
    border-color: rgba(255, 255, 255, .20);
    text-decoration: none;
}

.public-message-card {
    background: rgba(255, 255, 255, .045);
    border: 1px solid rgba(255, 255, 255, .10);
    color: #f4f4ff;
    border-radius: var(--jl-radius);
}

.public-message-card .alert-danger {
    background: rgba(220, 53, 69, .16);
    border: 1px solid rgba(220, 53, 69, .4);
    color: #ffd9dd;
}

/* The not-found / suspended card is platform chrome, not owner content, so its button
   joins the brand palette. Scoped to the card on purpose: the email-capture button on a
   live owner page is the owner's surface and keeps Bootstrap's default. */
.public-message-card .btn-primary {
    background: #f4f4ff;
    border-color: #f4f4ff;
    color: #0f1020;
    border-radius: 999px;
    font-weight: 600;
}

.public-message-card .btn-primary:hover,
.public-message-card .btn-primary:focus-visible {
    background: #fff;
    border-color: #fff;
    color: #0f1020;
}

/* ---------------------------------------------------------------------------
   Reduced motion
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    .mkt-rise,
    .mkt-bar,
    .mkt-mock-chip,
    .mkt-shell::before,
    .mkt-cta::before,
    .public-card.animated-gradient-card {
        animation: none !important;
    }

    .mkt-phone,
    .mkt-panel,
    .pricing-card,
    .mkt-shell .btn,
    .mkt-shell .btn-accent,
    .public-link-row {
        transition: none !important;
    }

    .mkt-shell .btn-accent {
        background-position: 0% 50%;
    }
}
