/* ==========================================================================
   BlueTTS site: layout and every section outside the demo panel.
   The demo panel (.tts, its inside and its clip list) is css/demo.css.
   Mobile first: the base rules are the 390-px layout; min-width queries at
   640 and 900. Colors, type sizes and spacing come only from css/tokens.css.

   Direction: the product first, then one calm column of short sections.
   - one paper under the whole page, one 1120px container, one left edge;
   - one card material (.card): white, a 1px edge, 24px corners, a hairline
     of shadow. The demo, the recording, the code, the speed, the bots and
     MamboTTS all wear it;
   - one heading pattern: a medium-weight title with tight tracking and one
     line under it in the softer ink (no eyebrows, no numbering);
   - one button system (.btn): flat 48px pills, the cobalt primary;
   - one reveal: a part rises 12px and fades in, once.
   See DESIGN.md.
   ========================================================================== */

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

html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    scroll-behavior: smooth;
    /* Anchors land under the sticky header, not behind it. */
    scroll-padding-top: var(--header-h);
    background: var(--paper);
}

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    font-weight: 400;
    overflow-x: clip;
}

h1, h2, h3, p, ul, ol, dl, dd, figure { margin: 0; }
ul[role="list"] { list-style: none; padding: 0; }
img, video, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
code { font-family: var(--mono); font-size: .9em; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: .18em; }
@media (hover: hover) {
    a:hover { color: var(--accent-soft-ink); }
}

/* Focus: one plain solid outline everywhere, never a glow. */
:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

::selection { background: var(--selection); color: var(--ink); }

.num { font-variant-numeric: tabular-nums; }

/* A row that scrolls sideways fades at an edge only while there is more past
   it (js/home.js sets data-fade="left right"): a cut chip or line of code
   reads as "scroll for more". */
[data-scroll-fade] {
    --fade-l: 0px;
    --fade-r: 0px;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent); /* taste-skill-ignore: an edge fade on a scrolling row */
            mask-image: linear-gradient(to right, transparent, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent); /* taste-skill-ignore */
}
[data-scroll-fade][data-fade~="left"] { --fade-l: 40px; }
[data-scroll-fade][data-fade~="right"] { --fade-r: 40px; }

.visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: var(--s-4);
    top: var(--s-2);
    z-index: 30;
    padding: var(--s-2) var(--s-4);
    background: var(--field);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: var(--r-control);
    transform: translateY(-200%);
}
.skip-link:focus { transform: none; }

/* --- Layout ------------------------------------------------------------- */
.container {
    width: 100%;
    max-width: calc(var(--container) + 2 * var(--gutter));
    margin-inline: auto;
    padding-inline: max(var(--gutter), env(safe-area-inset-left)) max(var(--gutter), env(safe-area-inset-right));
}

/* Every titled section after the hero: the same padding top and bottom. An
   untitled card (the recording under the demo, the MamboTTS strip) takes
   half of it, so it reads as part of what is around it. */
.section { padding-block: var(--band-pad); }
.section-strip { padding-block: calc(var(--band-pad) / 2); }

/* --- The card: one material for the whole page --------------------------------- */
/* White on the paper, a 1px slate edge, 24px corners and a hairline of
   shadow under it: a sheet on the page, not a floating panel. */
.card {
    background: var(--field);
    border: 1px solid var(--line);
    border-radius: var(--r-panel);
    box-shadow: var(--shadow-card);
}

/* --- Headings: one pattern ---------------------------------------------------- */
h1, h2, h3 { text-wrap: balance; }

/* Every section title, the contact title, the dialog title and the legal
   title: medium weight, tight tracking. */
.section-head h2,
.listen h2,
.contact h2,
.more-dialog h2,
.legal-title {
    color: var(--heading);
    font-size: var(--fs-h2);
    line-height: var(--lh-heading);
    letter-spacing: var(--track-heading);
    font-weight: 500;
}
/* A section's heading block: the title, then one line, in the prose column. */
.section-head { max-width: var(--measure); }
.section-head p,
.listen-text > p,
.contact-lede {
    margin-top: var(--s-3);
    font-size: var(--fs-lede);
    line-height: var(--lh-body);
    color: var(--ink-2);
    text-wrap: balance; /* one or two lines: even lines, never one word left alone */
}
.section-body { margin-top: var(--head-gap); }

h3 {
    color: var(--heading);
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    font-weight: 500;
}
.legal-section h2 {
    color: var(--heading);
    font-size: var(--fs-body);
    line-height: var(--lh-h3);
    font-weight: 500;
}

/* --- Buttons: one system --------------------------------------------------- */
/* 48px tall pills, 16px medium. .btn-sm (the header) is 40px with a 44px
   hit area. Fills: the cobalt primary, white with a soft edge (secondary).
   Flat: no shadow, no gloss. */
.btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-2);
    min-height: var(--btn-h);
    padding: 0 var(--s-6);
    border-radius: var(--r-btn);
    border: 1px solid transparent;
    font-size: var(--fs-control);
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
    text-decoration: none;
    transition-property: background-color, border-color, color, transform;
    transition-duration: var(--dur-fade);
    transition-timing-function: ease-out;
}
.btn svg { flex: none; }
.btn:active { transform: scale(.97); transition-duration: var(--dur-press); }
.btn-sm {
    min-height: var(--btn-h-sm);
    padding: 0 var(--s-5);
    font-size: var(--fs-ui);
}
.btn-sm::after { content: ''; position: absolute; inset: -2px 0; } /* 44px hit area */

.btn-primary { background: var(--primary); color: var(--accent-ink); }
.btn-secondary { background: var(--field); border-color: color-mix(in srgb, var(--rule-strong) 40%, var(--line)); color: var(--ink); }
@media (hover: hover) {
    .btn-primary:hover { background: var(--primary-hover); color: var(--accent-ink); }
    .btn-secondary:hover { background: var(--paper-2); border-color: var(--rule-strong); color: var(--ink); }
}

/* A row of buttons. */
.actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-3);
}

/* --- Lists: rows set apart by space, a check in a soft blue disc as the marker --- */
/* The disc (::before, the ramp's lightest tint) and the check (::after, the
   link blue) sit on the first line's centre, 12px from the text. */
.rule-list {
    --mark: 20px;
    display: grid;
    gap: var(--s-3);
    padding: 0;
    list-style: none;
    font-size: var(--fs-ui);
    line-height: 1.45;
}
.rule-list li {
    position: relative;
    padding-inline-start: calc(var(--mark) + var(--s-3));
    color: var(--ink);
    text-wrap: pretty;
}
.rule-list li::before,
.rule-list li::after {
    content: '';
    position: absolute;
    inset-inline-start: 0;
    top: calc((1.45em - var(--mark)) / 2);
    width: var(--mark);
    height: var(--mark);
}
.rule-list li::before { border-radius: 50%; background: color-mix(in srgb, var(--accent) 12%, var(--field)); } /* a soft blue that holds on the paper and on white */
.rule-list li::after {
    background: var(--accent);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M6.25 10.25l2.5 2.5 5-5.25' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M6.25 10.25l2.5 2.5 5-5.25' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* --- Header: sticky, on the paper; a hairline appears once the page scrolls --- */
.site-header {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--paper);
    border-bottom: 1px solid transparent; /* the hairline switches on at once: no transition on a scroll state */
}
.site-header[data-scrolled="true"] { border-bottom-color: var(--line); }
.site-header-inner {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    min-height: var(--header-h);
    padding-top: env(safe-area-inset-top);
}
.brand {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    margin-right: auto;
    border-radius: var(--r-toggle);
}
/* The cloud lockup, 28px tall. The page swaps in the small cut at 1x (srcset). */
.brand-logo { height: 28px; width: auto; }

.site-nav { display: none; }

.icon-btn {
    display: inline-grid;
    place-items: center;
    flex: none;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    color: var(--ink-2);
    transition: background-color var(--dur-fade) ease-out, color var(--dur-fade) ease-out;
}
@media (hover: hover) {
    .icon-btn:hover { background: var(--paper-2); color: var(--heading); }
}
/* The narrowest phones keep the logo and Try it now; GitHub is in the footer. */
@media (max-width: 359px) {
    .site-header .icon-btn { display: none; }
}

@media (min-width: 900px) {
    .site-header-inner { gap: var(--s-3); }
    .site-nav { display: flex; gap: var(--s-1); margin-right: var(--s-2); }
    .site-nav a {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding: 0 var(--s-3);
        border-radius: var(--r-toggle);
        color: var(--ink-2);
        font-size: var(--fs-ui);
        text-decoration: none;
        transition: color var(--dur-fade) ease-out;
    }
}
@media (min-width: 900px) and (hover: hover) {
    .site-nav a:hover { color: var(--heading); }
}

/* --- The hero: the headline, one line, then the demo itself ------------------- */
/* On the page's left edge like everything else. The demo card takes the
   container's full width right under the line, so the product is in the
   first screen at every size. */
.hero { padding-top: var(--s-5); padding-bottom: var(--band-pad); }
.hero-head { max-width: 22em; }
.hero h1 {
    color: var(--heading);
    font-size: var(--fs-h1);
    line-height: 1.04;
    letter-spacing: var(--track-h1);
    font-weight: 500;
}
.hero-lede {
    margin-top: var(--s-3);
    max-width: 34ch;
    font-size: var(--fs-lede);
    line-height: var(--lh-body);
    color: var(--ink-2);
    text-wrap: balance;
}
.hero .tts { margin-top: var(--s-5); }

@media (min-width: 640px) {
    .hero { padding-top: var(--s-7); }
    .hero-lede { max-width: none; }
    .hero .tts { margin-top: var(--s-6); }
}
@media (min-width: 900px) {
    /* One line across the container from here: 22em holds it with air. */
    .hero-head { max-width: none; }
    .hero { padding-top: var(--s-8); }
    .hero-lede { margin-top: var(--s-4); }
    .hero .tts { margin-top: var(--s-7); }
}

/* The demo card's padding (its inside is css/demo.css). --tts-pad is the
   side padding: the samples' row bleeds into it on phones, so a chip that
   does not fit runs to the card's edge and fades there. */
.hero .tts { --tts-pad: var(--s-4); padding: var(--s-4) var(--tts-pad); }
@media (max-width: 359px) { .hero .tts { --tts-pad: var(--s-3); } } /* the controls keep one row at 320 */
@media (min-width: 400px) { .hero .tts { --tts-pad: var(--s-5); padding-block: var(--s-5); } }
@media (min-width: 900px) { .hero .tts { --tts-pad: var(--s-7); padding-block: var(--s-6); } }

/* The load sequence (CSS, once, so it also runs without script): the
   headline, its line, then the card rise 12px and fade in, 60ms apart.
   The start states live in the keyframes with fill `backwards`, so reduced
   motion (no animation at all) shows the finished page. */
@media (prefers-reduced-motion: no-preference) {
    .hero-head > *, .hero .tts { animation: rise-in var(--dur-reveal) var(--ease-expo) backwards; }
    .hero-lede { animation-delay: 60ms; }
    .hero .tts { animation-delay: 120ms; }
}
@keyframes rise-in {
    from { opacity: 0; transform: translateY(12px); }
    to { opacity: 1; transform: none; }
}

/* --- The recording: its title card beside its title -------------------------- */
/* The title card is the button: on the recording's own dark stage, the
   reversed cloud lockup, the five languages it speaks (each in its own
   script, Hebrew right to left) and the play disc, centred as one stack; the
   length sits in the lower right. It is HTML, not a baked frame, so it is
   sharp at any size: every measure inside it is a share of its width (cqi),
   so it composes the same at the card's half width and on a 320px phone.
   Its corners follow the card's (outer radius minus the padding). Phones:
   the title card on top, the text under it. */
.listen {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-4);
    padding: var(--s-3);
    overflow: hidden;
}
.listen-thumb {
    position: relative;
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    border-radius: calc(var(--r-panel) - var(--s-3));
    overflow: hidden;
    background: var(--stage);
    color: var(--on-stage);
    font: inherit;
    aspect-ratio: 16 / 9;
    container-type: inline-size;
    cursor: pointer;
}
.listen-thumb:focus-visible { outline-offset: 3px; }
.listen-cover {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0 4cqi;
}
/* The kit's reversed lockup: the cloud in full colour, the wordmark in white. */
.listen-logo { width: clamp(120px, 38cqi, 240px); height: auto; }
.listen-langs {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0 max(8px, 3.2cqi);
    margin-top: max(8px, 2.6cqi);
    font-size: clamp(11px, 3cqi, 16px);
    line-height: 1.4;
    font-weight: 400;
    color: var(--on-stage-2);
}
.listen-langs [lang="he"] { font-size: 1.06em; } /* Hebrew sets a touch small beside Latin */
.listen-play {
    display: grid;
    place-items: center;
    width: clamp(44px, 12cqi, 64px);
    height: clamp(44px, 12cqi, 64px);
    margin-top: max(14px, 5cqi);
    border-radius: 50%;
    background: var(--primary);
    color: var(--accent-ink);
    transition: background-color var(--dur-fade) ease-out, transform var(--dur-press) ease-out;
}
.listen-play svg { width: 42%; height: 42%; margin-left: 6%; } /* the triangle's optical centre */
.listen-thumb:active .listen-play { transform: scale(.95); }
@media (hover: hover) {
    .listen-thumb:hover .listen-play { background: var(--primary-hover); }
}
.listen-time {
    position: absolute;
    right: max(10px, 2.6cqi);
    bottom: max(10px, 2.6cqi);
    padding: 2px 8px;
    border-radius: 999px;
    background: rgb(255 255 255 / .12);
    color: var(--on-stage);
    font-size: 13px;
    line-height: 18px;
    font-weight: 500;
}
.listen-text { padding: 0 var(--s-2) var(--s-3); }
.listen h2 { font-size: clamp(24px, 1.25vw + 18px, 36px); }
.listen-text > p { margin-top: var(--s-2); max-width: var(--measure); }
@media (min-width: 768px) {
    .listen {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        align-items: center;
        gap: var(--s-6);
    }
    .listen-text { padding: 0 var(--s-6) 0 0; }
}

/* --- The recording's dialog: its own dark frame, a close button above it ------ */
.video-dialog {
    /* As wide as fits: never past the screen's height in 16:9. */
    width: min(1024px, calc(100% - 2 * var(--s-4)), calc((100dvh - 120px) * 16 / 9));
    max-width: none;
    max-height: none;
    margin: auto;
    padding: 0;
    border: 0;
    background: transparent;
    overflow: visible;
}
.video-dialog-body { position: relative; padding-top: 52px; }
.video-frame {
    overflow: hidden;
    border-radius: var(--r-media);
    background: var(--stage);
    aspect-ratio: 16 / 9;
    box-shadow: var(--shadow-float);
}
.video-frame video { display: block; width: 100%; height: 100%; object-fit: contain; }
.video-dialog .video-close {
    position: absolute;
    top: 0;
    right: 0;
    margin: 0;
    background: var(--field);
    color: var(--ink);
}
@media (hover: hover) {
    .video-dialog .video-close:hover { background: var(--paper-2); }
}

/* --- WhatsApp & Telegram: two equal link cards and one live line ------------- */
.bot-list {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-3);
}
@media (min-width: 640px) {
    .bot-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--col-gap); }
}
/* Each app is one card that is one link: its icon in the app's own colour,
   its name, the number or handle, and an arrow out. Hover deepens the edge
   and moves the arrow; nothing lifts. */
.bot-card {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--s-4);
    min-height: 88px;
    padding: var(--s-4) var(--s-5);
    color: var(--ink);
    text-decoration: none;
    transition: border-color var(--dur-fade) ease-out;
}
.bot-tile {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    color: var(--on-brand);
}
[data-channel="whatsapp"] .bot-tile { background: var(--whatsapp); border-radius: 14px; }
[data-channel="telegram"] .bot-tile {
    background: linear-gradient(to bottom, var(--telegram-light), var(--telegram)); /* taste-skill-ignore: the Telegram logo's own fill */
    border-radius: 50%;
}
.bot-text { display: grid; gap: 2px; min-width: 0; }
.bot-name { font-size: var(--fs-h3); font-weight: 500; line-height: 1.3; color: var(--ink); }
.bot-handle {
    font-size: var(--fs-small);
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.bot-arrow { color: var(--ink-3); transition: transform var(--dur-fade) ease-out, color var(--dur-fade) ease-out; }
.bot-card:active { transform: scale(.99); }
@media (hover: hover) {
    .bot-card:hover { color: var(--ink); border-color: color-mix(in srgb, var(--rule-strong) 55%, var(--line)); }
    .bot-card:hover .bot-arrow { color: var(--ink); transform: translate(2px, -2px); }
}
@media (min-width: 900px) {
    .bot-card { min-height: 104px; padding: var(--s-5) var(--s-6); }
}

/* The live line: read from the running bots on load and every minute
   (js/script.js). While it loads it keeps its place, invisible (placeholder
   figures of the same width), so nothing below moves when it lands; if the
   counts never come it goes, rather than show zeros. */
.bot-stats {
    display: flex;
    align-items: baseline;
    gap: var(--s-3);
    margin-top: var(--s-5);
    font-size: var(--fs-ui);
    line-height: var(--lh-small);
    color: var(--ink-2);
    text-wrap: pretty;
}
.bot-stats b { font-weight: 500; color: var(--ink); }
.bot-stats[data-state="loading"] { visibility: hidden; }
.bot-stats[data-state="error"] { display: none; }
.bot-stats[data-state="ready"] { animation: stats-in 400ms var(--ease-out) both; }
@keyframes stats-in { from { opacity: 0; } }
/* The dot says the count is live. Still: no pulse. */
.live-dot {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ok);
    transform: translateY(-1px);
}

/* --- Open source: the code beside its speed, the repo, the two ways in -------- */
.oss {
    margin-top: var(--head-gap);
    display: grid;
    gap: var(--col-gap);
}
@media (min-width: 900px) {
    .oss { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); align-items: stretch; }
}

/* The code card: a head with what it is and Copy, then the code. */
.code-card { min-width: 0; overflow: hidden; }
.code-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    min-height: 52px;
    padding: var(--s-1) var(--s-2) var(--s-1) var(--s-5);
    border-bottom: 1px solid var(--line);
    background: var(--paper);
}
.code-head figcaption { font-size: var(--fs-small); color: var(--ink-2); }
.code-copy {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 44px;
    padding: 0 var(--s-3);
    border: 0;
    border-radius: var(--r-btn);
    background: transparent;
    font-size: var(--fs-small);
    font-weight: 500;
    color: var(--ink-2);
    cursor: pointer;
    transition: background-color var(--dur-fade) ease-out, color var(--dur-fade) ease-out, transform var(--dur-press) ease-out;
}
.code-copy svg { transition: opacity var(--dur-fade) ease-out, transform var(--dur-fade) ease-out; }
.code-copy .i-done { position: absolute; left: var(--s-3); opacity: 0; transform: scale(.6); color: var(--ok); }
.code-copy.is-done .i-copy { opacity: 0; transform: scale(.6); }
.code-copy.is-done .i-done { opacity: 1; transform: none; }
.code-copy:active { transform: scale(.96); }
@media (hover: hover) {
    .code-copy:hover { background: var(--paper-2); color: var(--ink); }
}
.code {
    margin: 0;
    padding: var(--s-4) var(--s-5) var(--s-5);
    overflow-x: auto;
    font-family: var(--mono);
    font-size: 13px;
    line-height: 1.8;
    color: var(--ink);
    tab-size: 4;
}
.code:focus-visible { outline-offset: -4px; border-radius: var(--r-panel); -webkit-mask-image: none; mask-image: none; } /* the ring is not faded at the edge */
.code code { font-size: inherit; }
.c-k { color: var(--accent); }
.c-s { color: #1D6B62; } /* taste-skill-ignore: code strings, 6:1 on white */
@media (min-width: 900px) {
    .code { padding: var(--s-5) var(--s-6) var(--s-6); font-size: 14px; }
    .code-head { padding-inline-start: var(--s-6); }
}

/* Speed: three rows, name and figure on a line, a bar under each on a log
   scale (log × / log 1500), so the CPUs read as real bars beside the GPU.
   Neutral ink: the blue is for what can be pressed. */
.speed { display: flex; flex-direction: column; padding: var(--s-5); }
.speed-sub { margin-top: 2px; font-size: var(--fs-small); color: var(--ink-2); }
.speed-list {
    display: grid;
    gap: var(--s-5);
    margin: var(--s-6) 0 0;
    padding: 0;
    list-style: none;
}
.speed-list li { display: grid; gap: var(--s-2); }
.speed-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3); font-size: var(--fs-ui); color: var(--ink); }
.speed-x { font-weight: 500; }
.speed-track {
    display: block;
    height: 6px;
    border-radius: 3px;
    background: var(--paper-2);
    overflow: hidden;
}
.speed-bar {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--ink-2);
    transform: scaleX(var(--v));
    transform-origin: left;
}
@media (min-width: 900px) {
    .speed { padding: var(--s-6); }
    .speed-list { margin-top: auto; padding-top: var(--s-6); }
}

/* Under the two cards: what is in the repo (a small title like the speed
   card's, then the checked rows, two columns from 640px), then GitHub and
   the weights, on the same left edge. */
.oss-foot { margin-top: var(--s-7); display: grid; gap: var(--s-6); }
.oss-repo { max-width: 760px; }
.oss-repo .rule-list { margin-top: var(--s-4); }
@media (min-width: 640px) {
    .oss-repo .rule-list { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-6); row-gap: var(--s-4); }
}
/* Desktop: the two columns of the list stand on the two cards above (7 : 5),
   so the checks line up with the cards' edges. */
@media (min-width: 900px) {
    .oss-repo { max-width: none; }
    .oss-repo .rule-list { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); column-gap: var(--col-gap); }
}
@media (max-width: 639px) {
    .oss-actions { display: grid; grid-template-columns: minmax(0, 1fr); }
    .code { padding-inline: var(--s-4); }
    .code-head { padding-inline-start: var(--s-4); }
}

/* --- MamboTTS: one slim strip that opens its dialog ---------------------------- */
/* One card that is one button: a crop of the app, one line, and a small
   pill that says what the press does. Its own section, half the rhythm. */
.more-item {
    display: grid;
    grid-template-columns: 112px minmax(0, 1fr);
    grid-template-areas: "shot text" "shot cta";
    align-items: center;
    column-gap: var(--s-4);
    row-gap: var(--s-3);
    width: 100%;
    padding: var(--s-3);
    color: var(--ink);
    font: inherit;
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fade) ease-out, transform var(--dur-press) ease-out;
}
.more-item:active { transform: scale(.995); }
.more-shot {
    grid-area: shot;
    position: relative;
    display: block;
    align-self: center;
    aspect-ratio: 620 / 440;
    overflow: hidden;
    border-radius: var(--r-media);
    background: var(--paper-2);
    outline: 1px solid rgb(0 0 0 / .08);
    outline-offset: -1px;
}
/* The crop: the editor and its clip player at a readable scale. */
.more-shot img {
    position: absolute;
    left: -4.84%;
    top: -17.05%;
    width: 165.16%;
    max-width: none;
    height: auto;
    transform-origin: 30% 30%;
    transition: transform 400ms var(--ease-out-quint);
}
.more-text { grid-area: text; display: grid; gap: var(--s-1); align-self: end; }
/* Phones: the crop sits on the text's first line, not floating in the
   middle of a tall card. */
@media (max-width: 639px) {
    .more-item { align-items: start; }
    .more-shot { align-self: start; }
    .more-text { align-self: start; }
}
.more-name { font-size: var(--fs-h3); font-weight: 500; line-height: 1.3; color: var(--ink); text-wrap: balance; }
.more-desc { font-size: var(--fs-small); color: var(--ink-2); line-height: var(--lh-small); text-wrap: pretty; }
/* What the press does, drawn as a small secondary pill (it is a label inside
   the one button, not a second button). */
.more-cta {
    grid-area: cta;
    justify-self: start;
    align-self: start;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    min-height: 36px;
    padding: 0 var(--s-3) 0 var(--s-4);
    border: 1px solid color-mix(in srgb, var(--rule-strong) 40%, var(--line));
    border-radius: var(--r-btn);
    background: var(--field);
    font-size: var(--fs-small);
    font-weight: 500;
    white-space: nowrap;
    color: var(--ink);
    transition: border-color var(--dur-fade) ease-out, background-color var(--dur-fade) ease-out;
}
.more-chevron { flex: none; transition: transform var(--dur-fade) ease-out; }
@media (hover: hover) {
    .more-item:hover { border-color: color-mix(in srgb, var(--rule-strong) 55%, var(--line)); }
    .more-item:hover .more-cta { background: var(--paper-2); border-color: var(--rule-strong); }
    .more-item:hover .more-chevron { transform: translateX(2px); }
    .more-item:hover .more-shot img { transform: scale(1.03); }
}
@media (min-width: 640px) {
    .more-item {
        grid-template-columns: 168px minmax(0, 1fr) auto;
        grid-template-areas: "shot text cta";
        column-gap: var(--s-6);
        padding: var(--s-3) var(--s-6) var(--s-3) var(--s-3);
    }
    .more-text { align-self: center; }
    .more-cta { align-self: center; min-height: var(--btn-h-sm); font-size: var(--fs-ui); }
}

/* --- MamboTTS's dialog ----------------------------------------------------------- */
/* A white sheet over a soft ink scrim. It floats, so it has the real shadow.
   Phones: nearly the full screen, scrolling inside. */
.more-dialog {
    width: min(760px, calc(100% - 2 * var(--s-2)));
    max-width: none;
    max-height: calc(100dvh - 2 * var(--s-2));
    margin: auto;
    padding: 0;
    border: 0;
    border-radius: var(--r-panel);
    background: var(--field);
    color: var(--ink);
    box-shadow: var(--shadow-float); /* taste-skill-ignore: a modal dialog really floats */
    overscroll-behavior: contain;
}
.more-dialog::backdrop, .video-dialog::backdrop { background: rgb(15 23 42 / .45); }
.video-dialog::backdrop { background: rgb(15 23 42 / .72); }
.more-dialog-body { padding: var(--s-5); }
.more-dialog-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s-4);
}
.more-dialog h2 { font-size: clamp(24px, 1vw + 18px, 30px); padding-top: var(--s-1); }
.more-dialog-lede { margin-top: var(--s-3); max-width: 52ch; color: var(--ink-2); font-size: var(--fs-ui); text-wrap: pretty; }
.more-dialog .app-shot { margin-top: var(--s-6); }
.more-dialog .rule-list { margin-top: var(--s-6); }
.more-dialog .actions { margin-top: var(--s-6); }
.dialog-close {
    flex: none;
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    margin: calc(-1 * var(--s-2)) calc(-1 * var(--s-2)) 0 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--ink-2);
    cursor: pointer;
    transition: background-color var(--dur-fade) ease-out, color var(--dur-fade) ease-out;
}
@media (hover: hover) {
    .dialog-close:hover { background: var(--paper-2); color: var(--ink); }
}
html.has-dialog { overflow: hidden; }
@media (max-width: 639px) {
    .more-dialog .actions { display: grid; grid-template-columns: minmax(0, 1fr); }
}
@media (min-width: 640px) {
    .more-dialog-body { padding: var(--s-6) var(--s-7) var(--s-7); }
}
/* The picture in the dialog: the window in a soft well (12 + 12 = 24). */
.app-shot {
    padding: var(--s-3);
    background: var(--paper-2);
    border-radius: var(--r-panel);
}
.app-shot picture {
    display: block;
    overflow: hidden;
    border-radius: var(--r-media);
    outline: 1px solid rgb(0 0 0 / .08);
    outline-offset: -1px;
    background: var(--field);
}
.app-shot img { width: 100%; height: auto; }
/* Phones: the whole window would be unreadable, so show the editor and the
   clip player at a larger scale. */
@media (max-width: 639px) {
    .app-shot picture { position: relative; aspect-ratio: 620 / 440; }
    .app-shot img {
        position: absolute;
        left: -4.84%;
        top: -17.05%;
        width: 165.16%;
        max-width: none;
    }
}
/* Opening: the sheet rises 8px and fades in; the scrim fades in. Closing is
   instant, so Esc and the backdrop feel immediate. Off with reduced motion. */
@media (prefers-reduced-motion: no-preference) {
    .more-dialog[open], .video-dialog[open] { animation: dialog-in 240ms var(--ease-out-quint); }
    .more-dialog[open]::backdrop, .video-dialog[open]::backdrop { animation: scrim-in 200ms ease-out; }
}
@keyframes dialog-in { from { opacity: 0; transform: translateY(8px) scale(.99); } }
@keyframes scrim-in { from { opacity: 0; } }

/* --- Contact: centred, the title, one line, two pills of one width ------------ */
.contact-inner { display: flex; flex-direction: column; align-items: center; text-align: center; }
.contact-lede { max-width: 32ch; }
.contact-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 168px));
    justify-content: center;
    gap: var(--s-3);
    width: 100%;
    margin-top: var(--s-6);
}
.contact-actions .btn { padding-inline: var(--s-4); }
/* The sign-off: two band-pads from the strip above, as a titled section,
   and the same air under it before the footer. */
.contact { padding-block: calc(var(--band-pad) * 1.5); }

/* --- Footer: on the same paper, set off by a hairline ---------------------------- */
.site-footer {
    background: var(--footer);
    border-top: 1px solid var(--line);
    padding-bottom: max(var(--s-6), env(safe-area-inset-bottom));
    font-size: var(--fs-ui);
    color: var(--on-footer-2);
}
.footer-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-6) var(--s-5);
    padding-top: var(--s-7);
}
.footer-brand { grid-column: 1 / -1; }
.footer-logo { height: 28px; width: auto; }

.footer-nav { display: flex; flex-direction: column; align-items: flex-start; }
.footer-heading {
    margin-bottom: var(--s-1);
    font-size: var(--fs-small);
    line-height: var(--lh-small);
    font-weight: 400;
    color: var(--on-footer-2);
}
.footer-nav a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--on-footer);
    text-decoration: none;
    transition: color var(--dur-fade) ease-out;
}
.footer-base {
    margin-top: var(--s-6);
    font-size: var(--fs-small);
    color: var(--on-footer-2);
}
.site-footer :focus-visible { outline-color: var(--on-footer); }

/* Narrow phones: the first column takes the width of its longest link, so
   "WhatsApp & Telegram" stays on one line and the rows line up. */
@media (max-width: 379px) {
    .footer-grid { grid-template-columns: max-content minmax(0, 1fr); column-gap: var(--s-4); }
}
@media (min-width: 640px) {
    .footer-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
    .footer-grid {
        grid-template-columns: repeat(12, minmax(0, 1fr));
        column-gap: var(--col-gap);
        padding-top: var(--s-8);
    }
    .footer-brand { grid-column: 1 / span 6; }
    .footer-nav:nth-of-type(1) { grid-column: 7 / span 2; }
    .footer-nav:nth-of-type(2) { grid-column: 9 / span 2; }
    .footer-nav:nth-of-type(3) { grid-column: 11 / span 2; }
    .footer-heading { margin-bottom: var(--s-2); }
    .footer-nav a { min-height: 32px; } /* desktop: pointer targets, 8px+ apart */
    .footer-base { margin-top: var(--s-7); }
}

/* Once the phone bar (css/demo.css) can cover the bottom, keep the end of
   the page reachable above it. */
@media (max-width: 899px) and (min-height: 360px) {
    .has-tts-sticky .site-footer { padding-bottom: calc(var(--s-6) + 64px + env(safe-area-inset-bottom)); }
}

/* --- Privacy and 404: the page, then the footer, which reaches the bottom
   of the window on a short page ------------------------------------------- */
body:has(> .legal-page) { display: flex; flex-direction: column; min-height: 100dvh; }
.legal-page {
    flex: 1 0 auto;
    padding-bottom: var(--band-pad);
}
.legal-header { padding-top: var(--s-6); max-width: 760px; }
.legal-title { font-size: var(--fs-h1); letter-spacing: var(--track-h1); line-height: 1.04; }
.legal-lede { margin-top: var(--s-4); font-size: var(--fs-body); color: var(--ink-2); }
.legal-updated { margin-top: var(--s-2); font-size: var(--fs-ui); color: var(--ink-2); }
.legal-body { margin-top: var(--s-7); max-width: 68ch; display: grid; gap: var(--s-6); }
.legal-section { color: var(--ink); }
.legal-section h2 { margin-bottom: var(--s-2); }
.legal-section p + p { margin-top: var(--s-3); }
.legal-section strong { color: var(--ink); font-weight: 500; }
.legal-section a { overflow-wrap: anywhere; }
.legal-list { padding-left: 1.1em; }
.legal-list li + li { margin-top: var(--s-2); }
.legal-list li::marker { color: var(--ink-3); }

.not-found { padding-top: var(--s-8); }
.not-found-mark { width: 64px; height: auto; margin-bottom: var(--s-5); }
.not-found-action { margin-top: var(--s-5); }

@media (min-width: 900px) {
    .legal-header { padding-top: var(--s-8); }
    .legal-container:has(> .legal-body) {
        display: grid;
        grid-template-columns: repeat(12, minmax(0, 1fr));
        column-gap: var(--col-gap);
        align-items: start;
    }
    .legal-container > .legal-header:not(.not-found) { grid-column: 1 / span 5; }
    .legal-container > .legal-body { grid-column: 6 / span 7; margin-top: 0; padding-top: var(--s-8); max-width: none; }
}

/* --- Motion: one reveal ------------------------------------------------------------
   Below the hero, each part arrives once as it scrolls into view: it rises
   12px and fades in (560ms, expo out). [data-reveal] moves the element
   itself (a card); [data-reveal="group"] moves its children one after the
   other, 60ms apart (a heading block, a row of cards). js/motion.js marks
   what is in view with .is-in; the figures count and the speed bars grow
   with their card. Every hidden start state sits behind html.motion-ready,
   which is only set when motion is allowed (inline script in the head;
   removed again on load if js/motion.js never ran), so without script or
   with reduced motion the page is simply there. Transform and opacity only. */
@media (prefers-reduced-motion: no-preference) {
    .motion-ready [data-reveal]:not([data-reveal="group"]),
    .motion-ready [data-reveal="group"] > * {
        opacity: 0;
        transform: translateY(12px);
        transition-property: opacity, transform;
        transition-duration: var(--dur-reveal);
        transition-timing-function: var(--ease-expo);
        transition-delay: calc(var(--d0, 0ms) + var(--i, 0) * var(--stagger));
    }
    .motion-ready [data-reveal].is-in:not([data-reveal="group"]),
    .motion-ready [data-reveal="group"].is-in > * {
        opacity: 1;
        transform: none;
    }
    .motion-ready [data-reveal="group"] > :nth-child(2) { --i: 1; }
    .motion-ready [data-reveal="group"] > :nth-child(3) { --i: 2; }
    .motion-ready [data-reveal="group"] > :nth-child(4) { --i: 3; }

    /* The speed bars grow from the left, the fastest first, once their card is in. */
    .motion-ready [data-reveal] .speed-bar { transform: scaleX(0); }
    .motion-ready [data-reveal].is-in .speed-bar {
        transform: scaleX(var(--v));
        transition: transform 700ms var(--ease-expo);
        transition-delay: calc(var(--d0, 0ms) + 200ms + var(--i, 0) * 70ms);
    }
}

/* Links in the header and footer: an underline draws in from the left on
   hover and leaves to the right. No lift, no colour wash. */
.site-nav a, .footer-nav a { position: relative; }
.site-nav a::after, .footer-nav a::after {
    content: '';
    position: absolute;
    left: var(--x, 0);
    right: var(--x, 0);
    bottom: calc(50% - .78em);
    height: 1px; /* taste-skill-ignore: the hover underline of a link, not a divider */
    background: currentColor;
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 260ms var(--ease-out-quint);
    pointer-events: none;
}
.site-nav a { --x: var(--s-3); }
@media (hover: hover) {
    .site-nav a:hover::after, .footer-nav a:hover::after { transform: scaleX(1); transform-origin: left; }
}

/* --- Reduced motion ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *,
    *::before,
    *::after {
        transition-duration: 0ms !important;
        animation-duration: 0ms !important;
        animation-iteration-count: 1 !important;
    }
    .btn:active, .bot-card:active, .more-item:active, .listen-play:active { transform: none; }
}
