/* ImpactX Partners — launch teaser.
   Self-contained: this deploys as its own Cloudflare Pages project and may not
   reference anything outside teaser/. Tokens below are the distilled brand set;
   nothing here may introduce a colour, face or effect the brand has not sanctioned. */

:root {
    /* Surface */
    --bg-depth: #0a0a0f;
    --bg-surface: #12121a;

    /* Ink */
    --text-primary: #ffffff;
    --text-muted: #a0a0a0;
    /* Deck ink: the strapline's tone as of the brand update. --text-dim is still
       sanctioned palette, it just no longer carries the lockup. */
    --text-muted-deck: #9d9da6;
    --text-dim: #6a6a75;

    /* The one signal */
    --brand-accent: #3b82f6;
    --brand-glow: rgba(59, 130, 246, 0.5);

    /* Hairlines — the sanctioned 0.05–0.15 band, nothing heavier */
    --hairline: rgba(255, 255, 255, 0.05);
    --hairline-strong: rgba(255, 255, 255, 0.15);
    --label-ink: rgba(255, 255, 255, 0.7);

    /* Type. Latin first, Arabic second: font fallback resolves per glyph, so the
       AR page picks up IBM Plex Sans Arabic for Arabic runs and keeps Host
       Grotesk / JetBrains Mono for the Latin ones. */
    --font-display: 'Source Serif 4', georgia, 'Times New Roman', serif;
    --font-body: 'Host Grotesk', 'IBM Plex Sans Arabic', -apple-system, blinkmacsystemfont, 'Segoe UI', helvetica, arial, sans-serif;
    --font-mono: 'JetBrains Mono', 'IBM Plex Sans Arabic', ui-monospace, sfmono-regular, menlo, consolas, monospace;
    --font-strapline: 'Jost', var(--font-body);

    /* Motion */
    --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
    --reveal-dur: 500ms;

    /* Lockup geometry is fixed by the brand recipe against this one number:
       strapline size and gap are calc()d off it, so this is the only dial.
       300px is the brand's FLOOR for a strapline lockup, not its target. */
    --lockup-w: 340px;
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--bg-depth);
    color: var(--text-primary);
    font-family: var(--font-body);
    font-size: 16px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

::selection {
    background: var(--brand-glow);
    color: var(--text-primary);
}

/* ---------------------------------------------------------------- shell -- */

.page {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    min-height: 100svh;
    /* Every vertical gap below is clamped against vh so the composition holds a
       single viewport on a laptop and opens up on a taller screen, rather than
       being fixed at one height and overflowing. */
    padding-block: clamp(1.75rem, 4vh, 3.5rem);
    padding-inline: clamp(1.5rem, 5vw, 2.5rem);
}

.column {
    width: 100%;
    max-width: 720px;
    text-align: center;
}

/* ------------------------------------------------------------- 1 lockup -- */

/* Brand recipe, fixed geometry: strapline = 2.6% of logo width, gap = 1.9%.
   Never composed as a baked image, never floated free of the mark. */
.lockup {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(var(--lockup-w) * 0.019);
}

.lockup-mark {
    display: block;
    width: var(--lockup-w);
    height: auto;
}

.lockup-strapline {
    font-family: var(--font-strapline);
    font-size: calc(var(--lockup-w) * 0.026);
    font-weight: 400;
    line-height: 1;
    color: var(--text-muted-deck);
    letter-spacing: 0.28em;
    /* Tracking trails the final glyph, which pushes the visible text off-centre
       by half a letter-space. Pull that trailing space back so the strapline is
       optically centred under the wordmark, as the recipe intends. */
    margin-inline-end: -0.28em;
}

/* --------------------------------------------------------- 2 trajectory -- */

.trajectory {
    margin-block-start: clamp(1.25rem, 3vh, 2.75rem);
}

/* Deliberately NOT mirrored in RTL. A mirrored curve enters the eye as a
   decline — high on the left, falling to the right — and that 50ms impression
   contradicts the growth it is drawing, whichever way the surrounding text
   runs. The trajectory is the mark's line extracted, and the mark does not
   mirror either: both locales get the identical bottom-left→top-right climb. */

.trajectory-svg {
    display: block;
    width: 100%;
    height: auto;
    /* The settled glow spills past the viewBox; let it. */
    overflow: visible;
}

.trajectory-path {
    fill: none;
    stroke: var(--brand-accent);
    stroke-width: 2;
    filter: drop-shadow(0 0 8px var(--brand-glow));
}

/* The SVG scales uniformly with the column, so an authored 2px stroke thins out
   on narrow screens. These two steps hold the rendered weight near 2px from
   320px up, without vector-effect (whose dash-pattern space differs from the
   user units getTotalLength() returns — it would break the draw). */
@media (max-width: 600px) {
    .trajectory-path {
        stroke-width: 2.8;
    }
}

@media (max-width: 420px) {
    .trajectory-path {
        stroke-width: 4;
    }
}

/* Pre-draw state is JS-only: without JS the curve is simply already there.
   Timing lives here rather than in script.js, which only sets the dash values —
   one place owns the choreography. */
.js .trajectory-path {
    filter: drop-shadow(0 0 0 transparent);
    transition:
        stroke-dashoffset 1200ms var(--ease-out-expo) 240ms,
        filter 600ms ease 1240ms;
}

/* The glow settles once the line has arrived. */
.js .trajectory.is-drawn .trajectory-path {
    filter: drop-shadow(0 0 8px var(--brand-glow));
}

/* --------------------------------------------------- 3 hero + 4 the lede -- */

/* The brand line pivots, and the pivot is fixed by the brand system rather than
   chosen here: "Impact." roman and white, "Multiplied." italic and Electric
   Blue. Never all-italic, never all-blue, never bold. That makes this a second
   electric moment beside the trajectory by design — it is the line's specified
   rendering, not a discretionary accent, so nothing else is dimmed to pay for it. */
.hero {
    margin-block: clamp(1rem, 2.25vh, 1.75rem) 0;
    font-family: var(--font-display);
    font-size: clamp(2.5rem, 7vw, 4rem);
    font-style: normal;
    font-weight: 600;
    line-height: 1.05;
    letter-spacing: -0.02em;
    text-wrap: balance;
}

.hero-pivot {
    font-style: italic;
    color: var(--brand-accent);
}

.lede {
    max-width: 52ch;
    margin-block: 1.125rem 0;
    margin-inline: auto;
    color: var(--text-muted);
    font-size: clamp(0.975rem, 1.5vw, 1.0625rem);
    line-height: 1.65;
    text-wrap: pretty;
}

:root[lang="ar"] .lede,
:root[lang="ar"] .outcome-line {
    /* Arabic carries diacritics above and descenders below — it needs the room. */
    line-height: 1.9;
}

/* ------------------------------------------------------------- 5 intake -- */

/* The intake is its own zone, not a continuation of the copy above it — it needs
   enough air that the eye registers a break before it reaches the eyebrow. */
.intake {
    margin-block-start: clamp(2.5rem, 6vh, 4.75rem);
}

/* Once the form is gone the block keeps the height it had (script.js pins it, so
   nothing above or below moves) — centre the remaining pair inside it rather
   than leaving the message stranded at the top of the reserved space. */
.intake.is-resolved {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/* White-alpha, not electric: the One Signal rule gives the page's accent to the
   trajectory, and inside this zone the button border is the single accent. The
   eyebrow organises, so it takes the same ink as the field labels. */
/* Aligned to the field grid, not to the centred copy above: a centred label over
   start-aligned inputs reads as a stray line rather than the zone's header.
   `start` mirrors to the right on the Arabic page for free. */
.eyebrow {
    margin-block: 0 1.875rem;
    color: var(--label-ink);
    font-family: var(--font-mono);
    text-align: start;
    font-size: 0.75rem;
    font-weight: 400;
    line-height: 1.4;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.fields {
    display: grid;
    gap: 1.125rem;
    text-align: start;
}

@media (min-width: 560px) {
    .fields {
        grid-template-columns: 1fr 1fr;
    }

    .field--wide {
        grid-column: 1 / -1;
    }
}

.field-label {
    display: block;
    margin-block-end: 0.35rem;
    color: var(--label-ink);
    font-family: var(--font-mono);
    font-size: 0.7rem;
    line-height: 1.4;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.field-input {
    display: block;
    width: 100%;
    padding: 0.375rem 0 0.45rem;
    border: 0;
    border-bottom: 1px solid var(--hairline-strong);
    border-radius: 0;
    background: transparent;
    color: var(--text-primary);
    font-family: var(--font-body);
    /* 16px floor keeps iOS from zooming the viewport on focus. */
    font-size: 1rem;
    line-height: 1.5;
    /* The absurd background-color duration is the standard defeat for Chrome's
       autofill wash, which would otherwise repaint these fields pale blue. */
    transition:
        background-color 100000s ease-in-out 0s,
        border-color 200ms ease,
        box-shadow 200ms ease;
}

.field-input:focus {
    border-bottom-color: var(--brand-accent);
    box-shadow: 0 2px 0 0 var(--brand-accent);
    /* Transparent outline survives forced-colors mode, where box-shadow is dropped. */
    outline: 2px solid transparent;
    outline-offset: 2px;
}

.field-input:-webkit-autofill,
.field-input:-webkit-autofill:hover,
.field-input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--text-primary);
}

.submit {
    margin-block-start: 1.5rem;
    padding: 1rem 2.5rem;
    border: 1px solid var(--brand-accent);
    border-radius: 6px;
    background: transparent;
    color: var(--text-primary);
    font-family: var(--font-body);
    font-size: 0.9375rem;
    font-weight: 500;
    line-height: 1.2;
    letter-spacing: 0.01em;
    cursor: pointer;
    transition:
        transform 220ms var(--ease-out-expo),
        box-shadow 220ms ease,
        border-color 220ms ease;
}

.submit:hover {
    transform: translateY(-2px);
    border-color: color-mix(in oklch, var(--brand-accent) 76%, var(--text-primary));
    box-shadow: 0 0 20px var(--brand-glow);
}

.submit:focus-visible {
    outline: 2px solid var(--brand-accent);
    outline-offset: 4px;
}

/* In flight: the control is spent but the page has not resolved yet. */
.form[aria-busy="true"] .submit {
    opacity: 0.5;
    cursor: progress;
}

/* No reserved height: under interaction-only the widget stays collapsed unless a
   visitor is actually challenged, so reserving its 65px would spend the space
   the appearance setting exists to save. The margin is kept for the rare mounted
   case — it gives the challenge room without the footer touching it. */
.turnstile {
    display: flex;
    justify-content: center;
    margin-block-start: 0.75rem;
}

.form-note {
    margin-block: 1.25rem 0;
    color: var(--text-muted);
    font-size: 0.875rem;
    line-height: 1.6;
}

.form-status {
    margin-block: 0;
    font-size: 1.0625rem;
    line-height: 1.7;
}

.form-status:empty {
    display: none;
}

.form-status.is-success {
    color: var(--text-primary);
}

.form-status.is-error {
    color: var(--text-muted);
    font-size: 0.9375rem;
}

.form-note a,
.form-status a {
    color: var(--brand-accent);
    text-underline-offset: 0.2em;
}

.form-note a:hover,
.form-status a:hover {
    color: color-mix(in oklch, var(--brand-accent) 70%, var(--text-primary));
}

.form-note a:focus-visible,
.form-status a:focus-visible {
    outline: 2px solid var(--brand-accent);
    outline-offset: 3px;
    border-radius: 2px;
}

/* Arabic is cursive: letter-spacing severs the joins. Tracking survives only on
   the runs that stay Latin — the strapline, the legal line, the English switch.
   The last selector covers the Arabic switch sitting on the EN page. */
:root[lang="ar"] .eyebrow,
:root[lang="ar"] .field-label,
:root[lang="ar"] .submit,
:root[lang="ar"] .outcome-back,
.footer-lang[lang="ar"] {
    letter-spacing: 0;
}

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

.footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.75rem 1.5rem;
    width: 100%;
    max-width: 720px;
    margin-block-start: clamp(1.75rem, 3.5vh, 3rem);
    padding-block-start: 1rem;
    border-block-start: 1px solid var(--hairline);
    /* --text-dim would sit at 3.7:1 here. This is real text with a real link, so
       it takes the muted token and recedes on size and tracking instead. */
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.75rem;
    line-height: 1.5;
    letter-spacing: 0.04em;
}

.footer-lang,
.outcome-back a {
    color: var(--text-muted);
    text-decoration-color: var(--hairline-strong);
    text-underline-offset: 0.3em;
    transition: color 200ms ease, text-decoration-color 200ms ease;
}

.footer-lang:hover,
.outcome-back a:hover {
    color: var(--text-primary);
    text-decoration-color: currentcolor;
}

.footer-lang:focus-visible,
.outcome-back a:focus-visible {
    outline: 2px solid var(--brand-accent);
    outline-offset: 3px;
    border-radius: 2px;
}

/* ----------------------------------------------------------- outcomes -- */

/* /sent/ and /error/ — where a native form POST lands when JS never ran. Same
   world, one line of it: the primary lockup (no strapline) and the answer. */
.outcome {
    --lockup-w: 240px;
}

.outcome .lockup {
    margin-inline: auto;
    inline-size: fit-content;
}

.outcome-line {
    max-width: 46ch;
    margin-block: clamp(2rem, 5vh, 3rem) 0;
    margin-inline: auto;
    font-size: 1.0625rem;
    line-height: 1.7;
}

.outcome-line a {
    color: var(--brand-accent);
    text-underline-offset: 0.2em;
}

.outcome-line a:hover {
    color: color-mix(in oklch, var(--brand-accent) 70%, var(--text-primary));
}

.outcome-line a:focus-visible {
    outline: 2px solid var(--brand-accent);
    outline-offset: 3px;
    border-radius: 2px;
}

.outcome-back {
    margin-block: clamp(1.75rem, 4vh, 2.5rem) 0;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.04em;
}

/* ------------------------------------------------------------- reveals -- */

/* Default CSS renders the finished page. The hidden state is applied ONLY under
   .js, which an inline head script sets before this sheet is parsed — so a
   broken or blocked script leaves the composition fully visible. */
.reveal {
    transition:
        opacity var(--reveal-dur) var(--ease-out-expo) var(--reveal-delay, 0ms),
        transform var(--reveal-dur) var(--ease-out-expo) var(--reveal-delay, 0ms);
}

.js .reveal {
    opacity: 0;
    transform: translateY(24px);
}

.js .reveal.is-revealed {
    opacity: 1;
    transform: translateY(0);
}

/* The staircase — 60ms apart once the trajectory is under way. The trajectory
   itself is absent on purpose: its draw is its entrance. Last rise lands at
   1100ms, the glow settles at 1840ms. */
.reveal.lockup {
    --reveal-delay: 0ms;
}

.reveal.hero {
    --reveal-delay: 420ms;
}

.reveal.lede {
    --reveal-delay: 480ms;
}

.reveal.intake {
    --reveal-delay: 540ms;
}

.reveal.footer {
    --reveal-delay: 600ms;
}

/* --------------------------------------------------------- small screens -- */

/* The wider intake separation costs about 27px, which is exactly what a 390x844
   phone had left over. Take it back from the outer frame rather than from the
   zone rhythm: page padding and the footer gap carry the least meaning here,
   and the separation between the copy and the intake is the whole point of it. */
@media (max-width: 560px) {
    .page {
        padding-block: clamp(1.25rem, 2.5vh, 2rem);
    }

    .footer {
        margin-block-start: clamp(1.5rem, 3vh, 2.5rem);
    }
}

/* The lockup is a fixed-geometry object — the strapline's size and gap are
   calc()d off --lockup-w — so it steps rather than flexes. 340px is wider than
   a phone's text column (it renders past the viewport edge at 320px), so below
   this width it falls back to 300px, which is the brand's floor for a strapline
   lockup rather than an arbitrary smaller number. */
@media (max-width: 420px) {
    :root {
        --lockup-w: 300px;
    }
}

/* ------------------------------------------------------ reduced motion -- */

/* Every keyframe and every transform-moving transition stills; the end states
   render immediately. Colour and opacity fades under 300ms are left alone.
   script.js makes the same check before it touches the trajectory, because
   inline styles would outrank anything declared here. */
@media (prefers-reduced-motion: reduce) {
    .js .reveal,
    .js .reveal.is-revealed {
        opacity: 1;
        transform: none;
        transition: none;
    }

    .js .trajectory-path {
        filter: drop-shadow(0 0 8px var(--brand-glow));
        transition: none;
    }

    .submit {
        transition: box-shadow 220ms ease, border-color 220ms ease;
    }

    .submit:hover {
        transform: none;
    }
}
