/* explainer.css — scroll-through product explainers (/pear, /mantle) */

/*
   The page dresses itself in the product's own interface colours, so the copy
   and the screenshots read as one surface instead of a marketing skin wrapped
   around a foreign UI. Every value below comes from the shipped app: sampled
   backgrounds, panel fills, hairlines and accent colours.

   Layout: each chapter owns its screenshot. On a phone the shot sits in the
   flow, whole and uncropped, right under its heading; from 900px up the same
   figure becomes a sticky column beside the copy. Nothing is ever cropped and
   nothing is pinned over half a phone screen.
*/

body.xp-body {
    /* Neutral fallback; the per-product blocks below overwrite all of it. */
    --xp-bg: #121212;
    --xp-panel: #1c1c1d;
    --xp-panel-2: #252526;
    --xp-line: #2d2d2d;
    --xp-accent: #65b3f4;
    --xp-accent-2: #9cc280;
    --xp-dim: rgba(255, 255, 255, 0.62);
    --xp-bar: 3.25rem;

    background: var(--xp-bg);
    color: #fff;
    margin: 0;
}

/* PEAR's interface is slate blue with amber and green status colour. */
body.xp-pear {
    --xp-bg: #111826;
    --xp-panel: #1f2936;
    --xp-panel-2: #26313f;
    --xp-line: #334154;
    --xp-accent: #f3a02b;
    --xp-accent-2: #32a555;
}

/* Mantle's is near-black neutral with a blue link accent. */
body.xp-mantle {
    --xp-bg: #121212;
    --xp-panel: #1c1c1d;
    --xp-panel-2: #252526;
    --xp-line: #2d2d2d;
    --xp-accent: #65b3f4;
    --xp-accent-2: #9cc280;
}

.xp-shell {
    position: relative;
}

/* ---------------------------------------------------------------- top bar */

.xp-bar {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: 60;
    height: var(--xp-bar);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0 clamp(0.75rem, 3vw, 1.5rem);
    background: var(--xp-bg);
    border-bottom: 1px solid var(--xp-line);
}

.xp-back {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-body);
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--xp-dim);
    min-height: 44px;
}

.xp-back:hover {
    color: #fff;
}

.xp-bar-name {
    font-family: var(--font-heading);
    font-weight: 800;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    font-size: 0.8rem;
    color: #fff;
}

/* Scroll progress rail — pure CSS, no scroll listener. */
.xp-rail {
    position: absolute;
    left: 0;
    bottom: -1px;
    height: 2px;
    width: 100%;
    transform-origin: 0 50%;
    transform: scaleX(0);
    background: var(--xp-accent);
    display: none;
}

@supports (animation-timeline: scroll()) {
    .xp-rail {
        display: block;
        animation: xp-rail linear both;
        animation-timeline: scroll(root block);
    }
}

@keyframes xp-rail {
    to { transform: scaleX(1); }
}

/* ------------------------------------------------------------------- hero */

.xp-hero {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
    padding: calc(var(--xp-bar) + 3.5rem) clamp(1.1rem, 5vw, 2rem) 3rem;
    max-width: 70rem;
    margin: 0 auto;
}

.xp-eyebrow {
    font-family: var(--font-body);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--xp-accent);
}

.xp-hero h1 {
    font-family: var(--font-heading);
    font-weight: 800;
    font-size: clamp(3rem, 15vw, 6.5rem);
    line-height: 0.95;
    margin: 0;
    letter-spacing: -0.02em;
    color: #fff;
}

.xp-hero h2 {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: clamp(1.05rem, 4.4vw, 1.6rem);
    margin: 0;
    color: var(--xp-accent-2);
}

.xp-hero p {
    font-family: var(--font-body);
    font-size: clamp(1rem, 3.6vw, 1.2rem);
    line-height: 1.6;
    color: rgba(255, 255, 255, 0.82);
    max-width: 34rem;
    margin: 0;
}

.xp-cue {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    margin-top: 0.5rem;
    font-family: var(--font-body);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--xp-dim);
}

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

@keyframes xp-nudge {
    0%, 100% { transform: translateY(0); opacity: 0.55; }
    50%      { transform: translateY(0.35rem); opacity: 1; }
}

/* ---------------------------------------------------------------- chapters */

.xp-act {
    max-width: 78rem;
    margin: 0 auto;
    padding: 0 clamp(0.9rem, 4vw, 2rem);
}

.xp-chapter {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    padding: 2.5rem 0 3rem;
    border-top: 1px solid var(--xp-line);
}

.xp-chapter:first-child {
    border-top: 0;
}

.xp-num {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    font-family: var(--font-body);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--xp-accent);
}

.xp-num::before {
    content: "";
    width: 1.75rem;
    height: 1px;
    background: var(--xp-accent);
    opacity: 0.7;
}

.xp-chapter h3 {
    font-family: var(--font-heading);
    font-weight: 800;
    font-size: clamp(1.55rem, 6.2vw, 2.4rem);
    line-height: 1.14;
    margin: 0;
    color: #fff;
}

.xp-copy {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}

.xp-chapter p {
    font-family: var(--font-body);
    font-size: clamp(1rem, 3.7vw, 1.12rem);
    line-height: 1.68;
    color: rgba(255, 255, 255, 0.84);
    max-width: 34rem;
    margin: 0;
}

.xp-chapter strong {
    color: #fff;
    font-weight: 700;
}

/* ------------------------------------------------------------- screenshots */

/* Shown whole: these are wide desktop screens, so the phone gets the entire
   frame and the shared lightbox handles reading the detail. */
.xp-shot {
    margin: 0.4rem 0 0.2rem;
    border: 1px solid var(--xp-line);
    border-radius: 0.5rem;
    background: var(--xp-panel);
    overflow: hidden;
}

.xp-shot img {
    display: block;
    width: 100%;
    height: auto;
    max-width: 100%;
    border-bottom: 1px solid var(--xp-line);
}

.xp-shot figcaption {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.55rem 0.7rem;
    font-family: var(--font-body);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.82);
    background: var(--xp-panel-2);
}

/* Not decoration: it opens the same lightbox the screenshot itself does, so it
   is a real button. */
.xp-hint {
    flex: none;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 400;
    letter-spacing: 0.04em;
    text-transform: none;
    color: var(--xp-dim);
    cursor: pointer;
    transition: color 160ms ease;
}

.xp-hint:hover,
.xp-hint:focus-visible {
    color: var(--xp-accent);
}

/* --------------------------------------------------------------- diagrams */

.xp-figure {
    margin: 0.25rem 0 0;
    padding: 0.9rem;
    border-radius: 0.5rem;
    border: 1px solid var(--xp-line);
    background: var(--xp-panel);
    max-width: 34rem;
}

.xp-figure svg {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

.xp-figure figcaption {
    margin-top: 0.6rem;
    font-family: var(--font-body);
    font-size: 0.76rem;
    line-height: 1.5;
    color: var(--xp-dim);
}

/* Diagram primitives, drawn in the same palette as the product's own UI. */
.d-box { fill: var(--xp-panel-2); stroke: var(--xp-line); stroke-width: 1.2; }
.d-box-a { fill: var(--xp-panel-2); stroke: var(--xp-accent); stroke-width: 1.4; }
.d-txt { fill: rgba(255, 255, 255, 0.92); font-family: var(--font-body); font-size: 9px; font-weight: 700; }
.d-txt-s { fill: var(--xp-dim); font-family: var(--font-body); font-size: 7.5px; }
.d-mint { fill: var(--xp-accent-2); }
.d-amber { fill: #f0a92e; }
.d-rose { fill: #e0566d; }
.d-line { fill: none; stroke: var(--xp-line); stroke-width: 1.4; stroke-linecap: round; }
.d-flow { fill: none; stroke: var(--xp-accent); stroke-width: 1.6; stroke-linecap: round; }
.d-mid { text-anchor: middle; }

/* Tabler icons, inlined. These diagrams are inline SVG, so an icon has to be
   part of the same document - it cannot be an <img>. Each one keeps its native
   24x24 geometry and is placed by a transform on its <g>, which also scales the
   stroke: at scale 0.5 the tabler stroke-width of 2 lands on 1, near .d-line.

   The stroke itself is opaque and the fade lives on the group, which is not the
   same thing: --xp-dim is 62% white, and a translucent stroke composites against
   itself wherever two of an icon's paths cross, printing the overlap as a
   brighter patch. Fading the whole group flattens it first, so the crossings
   disappear. */
.d-ico { fill: none; stroke: #fff; opacity: 0.62; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.d-ico-a { stroke: var(--xp-accent); opacity: 1; }

/* Every animated piece of a diagram carries --i: its place in the order the
   diagram is meant to be read, not its place in the markup. One step of --i is
   one beat, so re-ordering a diagram is a matter of renumbering it. */
.d-pop,
.d-draw { --i: 0; }

/* Drawn-in flows: dashes are laid down once the chapter is on screen.
   Paths carry pathLength="100", so the dash maths is the same for a 20-unit
   stub and a 260-unit detour, and every flow draws at the same speed.

   The gap is 200 and the offset 101, not a plain `100`: with dasharray 100 the
   gap is 100 too, so the dash ends exactly on length 0 and the caps round that
   zero-length remainder into a visible dot at every flow's start. Overshooting
   by 1 parks the dash clear of the path, and the long gap keeps the pattern's
   next repeat from reaching the far end. */
.d-draw {
    stroke-dasharray: 100 200;
    stroke-dashoffset: 101;
}

.xp-chapter.is-active .d-draw {
    animation: xp-draw 620ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
    animation-delay: calc(var(--i) * 130ms);
}

@keyframes xp-draw {
    to { stroke-dashoffset: 0; }
}

/* An arrowhead belongs to the flow it terminates, so it carries the same --i as
   the shaft and then waits that shaft out. It has to be its own path: as a
   trailing subpath it shares one dash offset with the shaft, which puts the
   point on screen while the line is still arriving. */
.d-tip {
    --i: 0;
    stroke-dasharray: 100 200;
    stroke-dashoffset: 101;
}

.xp-chapter.is-active .d-tip {
    animation: xp-draw 200ms ease-out forwards;
    animation-delay: calc(var(--i) * 130ms + 620ms);
}

/* fill-box, or every element would scale about the centre of the whole canvas. */
.d-pop {
    transform-box: fill-box;
    transform-origin: center;
}

.xp-chapter.is-active .d-pop {
    animation: xp-pop 460ms cubic-bezier(0.34, 1.4, 0.64, 1) backwards;
    animation-delay: calc(var(--i) * 130ms);
}

@keyframes xp-pop {
    from { opacity: 0; transform: scale(0.7); }
    to   { opacity: 1; transform: none; }
}

.d-pulse { transform-origin: center; }

.xp-chapter.is-active .d-pulse {
    animation: xp-pulse 2.4s ease-in-out infinite;
}

@keyframes xp-pulse {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 1; }
}

/* A text caret, drawn where the writing has got to. It inherits --i from the
   group it sits in, so it takes its turn like everything else - it belongs
   after the answer it is at the end of, not before it. Sitting at 0 until its
   delay runs out is what keeps it off screen until then. It does not blink:
   nothing else in these diagrams moves once it has arrived.

   The shape is filled rather than stroked because a filled I-beam has no
   self-overlap - a translucent stroke would print its own serif joins. */
.d-caret { fill: #fff; opacity: 0; }

.xp-chapter.is-active .d-caret {
    animation: xp-caret-in 260ms ease-out forwards;
    animation-delay: calc(var(--i) * 130ms);
}

@keyframes xp-caret-in {
    to { opacity: 0.7; }
}

html:not(.xp-js) .d-caret { opacity: 0.7; }

/* ----------------------------------------------------------------- closing */

.xp-end {
    max-width: 44rem;
    margin: 0 auto;
    padding: 3.5rem clamp(1.1rem, 5vw, 2rem) 5rem;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.1rem;
    border-top: 1px solid var(--xp-line);
}

.xp-end h3 {
    font-family: var(--font-heading);
    font-weight: 800;
    font-size: clamp(1.7rem, 7vw, 2.6rem);
    margin: 0;
}

.xp-end p {
    font-family: var(--font-body);
    color: rgba(255, 255, 255, 0.82);
    margin: 0;
}

.xp-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    justify-content: center;
    margin-top: 0.5rem;
}

.xp-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 48px;
    padding: 0.8rem 1.6rem;
    border-radius: 0.4rem;
    font-family: var(--font-body);
    font-weight: 700;
    background: var(--xp-accent);
    color: var(--xp-bg);
    border: 1px solid var(--xp-accent);
}

.xp-cta:hover {
    filter: brightness(1.12);
}

.xp-cta.is-ghost {
    background: var(--xp-panel);
    color: #fff;
    border-color: var(--xp-line);
}

.xp-cta.is-ghost:hover {
    filter: none;
    background: var(--xp-panel-2);
}

.xp-cta:active {
    transform: scale(0.97);
}

/* ------------------------------------------------------------- desktop 2up */

/* Wide enough for two columns: the copy scrolls, its own screenshot sticks
   alongside until the chapter is done. Still the whole image, still no crop. */
@media (min-width: 900px) {
    .xp-chapter {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
        column-gap: clamp(2rem, 5vw, 4rem);
        align-items: start;
        padding: 3.5rem 0 4rem;
    }

    .xp-num   { grid-column: 1; grid-row: 1; }
    .xp-chapter h3 { grid-column: 1; grid-row: 2; margin-top: 0.6rem; }
    .xp-copy  { grid-column: 1; grid-row: 3; margin-top: 1rem; }

    .xp-shot {
        grid-column: 2;
        grid-row: 1 / 4;
        position: sticky;
        top: calc(var(--xp-bar) + 2rem);
        margin: 0;
    }

    /* A chapter with no screenshot of its own — it runs on its diagram alone,
       so it drops back to one centred column instead of leaving a dead half. */
    .xp-chapter.is-solo {
        display: block;
        max-width: 46rem;
        margin: 0 auto;
    }

    .xp-chapter.is-solo h3 {
        margin-top: 0.6rem;
    }

    .xp-chapter.is-solo .xp-copy {
        margin-top: 1rem;
    }

    .xp-chapter.is-solo p,
    .xp-chapter.is-solo .xp-figure {
        max-width: none;
    }

    .xp-hero {
        padding-top: calc(var(--xp-bar) + 6rem);
    }
}

/* -------------------------------------------------- scroll-driven polish */

@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .xp-chapter > * {
            animation: xp-rise linear both;
            animation-timeline: view();
            animation-range: entry 8% entry 55%;
        }

        .xp-hero > * {
            animation: xp-rise linear both;
            animation-timeline: view();
            animation-range: entry 0% cover 22%;
        }
    }
}

@keyframes xp-rise {
    from { opacity: 0; transform: translateY(1.25rem); }
    to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ no-JS floor */

/* `xp-js` is set on <html> by an inline head script. The layout itself needs
   no JS; only the diagram animations wait for a chapter to be marked active,
   so without it they are shown already finished. */
html:not(.xp-js) .d-draw,
html:not(.xp-js) .d-tip {
    stroke-dashoffset: 0;
}

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

@media (prefers-reduced-motion: reduce) {
    .xp-cue i,
    .xp-chapter.is-active .d-draw,
    .xp-chapter.is-active .d-tip,
    .xp-chapter.is-active .d-pop,
    .xp-chapter.is-active .d-pulse,
    .xp-chapter.is-active .d-caret {
        animation: none;
    }

    .d-draw,
    .d-tip {
        stroke-dashoffset: 0;
    }

    .d-caret {
        opacity: 0.7;
    }
}
