/* WrenchTime website. Extracted from the design canvas inline styles and made
   responsive: the canvas was pinned to min-width 1080px, which fails mobile-first
   indexing for a landing page whose product is an iPhone app.

   Type scales are clamp()ed rather than fixed, because German runs 15-30% longer
   than English and would overflow a hard 58px headline. */

/* Self-hosted fonts (bin/sync-fonts.sh -> assets/fonts/). Nothing is fetched from a
   third party: a Google Fonts stylesheet sends every visitor's IP to Google before the
   first paragraph renders, which is a GDPR problem in the EU (LG Muenchen, 2022) and a
   contradiction on a page whose pitch is "no server of ours". Variable weight files,
   latin + latin-ext only; the CJK locales below use system stacks and download none. */
@font-face {
    font-family: 'Archivo';
    font-style: normal;
    font-display: swap;
    font-weight: 100 900;
    src: url(/assets/fonts/archivo-latin-ext-wght-normal.woff2) format('woff2-variations');
    unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
    font-family: 'Archivo';
    font-style: normal;
    font-display: swap;
    font-weight: 100 900;
    src: url(/assets/fonts/archivo-latin-wght-normal.woff2) format('woff2-variations');
    unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-display: swap;
    font-weight: 100 800;
    src: url(/assets/fonts/jetbrains-mono-latin-ext-wght-normal.woff2) format('woff2-variations');
    unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-display: swap;
    font-weight: 100 800;
    src: url(/assets/fonts/jetbrains-mono-latin-wght-normal.woff2) format('woff2-variations');
    unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

:root {
    --bg:            #0c0b0a;
    --bg-band:       #12100d;
    --bg-card:       #14110d;
    --bg-highlight:  #1a1610;
    --bg-footer:     #0a0908;
    --border:        #241f18;
    --border-strong: #352d24;
    --text:          #b5aea1;
    --text-strong:   #f4ede0;
    --text-card:     #cfc8ba;
    --text-muted:    #8a8478;
    --accent:        #f5a10c;
    --accent-hover:  #ffc04d;
    --ink:           #171410;

    /* One radius for every image on the page: a phone frame, an iPad frame, a
       head unit, the Live Activity card and the hero slides all sit in the
       same card-shaped hole. Matches the `.card` radius on purpose. */
    --shot-radius: 18px;

    --wrap:   1160px;
    --gutter: clamp(20px, 5vw, 48px);
    --radius: 22px;

    --font: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* CJK: Archivo carries no CJK glyphs, so ja/ko/zh would silently fall back to an
   arbitrary system face. Pin a real stack per script instead. System fonts, so
   these three locales download no extra font at all. */
:root:lang(ja) { --font: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic', Meiryo, system-ui, sans-serif; }
:root:lang(ko) { --font: 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', system-ui, sans-serif; }
:root:lang(zh-Hans) { --font: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', system-ui, sans-serif; }
/* CJK sets no word spaces, so tracking and pretty-wrap do nothing; line-height
   wants to be looser and letter-spacing neutral. */
.cjk h1, .cjk h2, .cjk h3 { letter-spacing: 0; line-height: 1.3; }
.cjk p, .cjk li { line-height: 1.85; }

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: 16px;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}
img { max-width: 100%; display: block; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); }
::selection { background: var(--accent); color: var(--ink); }

.wrap { max-width: var(--wrap); margin: 0 auto; padding-left: var(--gutter); padding-right: var(--gutter); }
.band { background: var(--bg-band); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.section { padding-top: clamp(56px, 9vw, 96px); padding-bottom: clamp(56px, 9vw, 96px); }

/* ---- type ---- */
.eyebrow {
    font-family: var(--mono); font-size: 12px; letter-spacing: 0.16em;
    text-transform: uppercase; color: var(--accent); margin: 0 0 16px;
}
h1 {
    font-size: clamp(34px, 5.4vw, 58px); line-height: 1.06; font-weight: 900;
    letter-spacing: -0.025em; color: var(--text-strong); margin: 0 0 22px; text-wrap: pretty;
}
h2 {
    font-size: clamp(28px, 4vw, 42px); line-height: 1.12; font-weight: 800;
    letter-spacing: -0.02em; color: var(--text-strong); margin: 0 0 18px; text-wrap: pretty;
}
h3 { font-size: 17px; font-weight: 700; color: var(--text-strong); margin: 0 0 10px; }
.lede { font-size: clamp(16px, 1.7vw, 19px); line-height: 1.62; margin: 0 0 28px; text-wrap: pretty; }
.lede-wide { max-width: 760px; }

/* ---- nav ---- */
.nav {
    position: sticky; top: 0; z-index: 50; display: flex; align-items: center;
    justify-content: space-between; gap: 24px;
    padding: 12px var(--gutter);
    background: rgba(12, 11, 10, 0.85); backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--border);
}
.nav-brand { flex-shrink: 0; display: flex; align-items: center; gap: 12px; color: var(--text-strong); font-weight: 800; font-size: 19px; letter-spacing: -0.01em; }
.nav-brand img { width: 34px; height: 34px; border-radius: 8px; }
.nav-links { display: flex; align-items: center; gap: 26px; font-size: 14.5px; font-weight: 600; }
.nav-links a { color: var(--text); }
.nav-links > a { white-space: nowrap; }
.nav-links a:hover { color: var(--text-strong); }
.nav-end { flex-shrink: 0; display: flex; align-items: center; gap: 14px; }
.nav .btn-sm { white-space: nowrap; }

/* ---- the Guides dropdown ----
   Opens on hover AND on :focus-within, so it works with no JavaScript and
   with the keyboard; the button exists for touch, where there is no hover,
   and the script only flips a class and `aria-expanded`. The links inside are
   plain <a> elements in the document: the cluster is only reachable if a
   crawler can walk into it, and a menu that builds its links on click is a
   menu the AI crawlers never open.

   The padding/negative-margin pair extends the button's hover box down into
   the gap under the bar, so the pointer does not cross dead space on its way
   to the panel and lose it. */
.navdrop { position: relative; padding-bottom: 14px; margin-bottom: -14px; }
.navdrop-btn {
    display: inline-flex; align-items: center; gap: 6px;
    font-family: inherit; font-size: 14.5px; font-weight: 600;
    color: var(--text); background: none; border: 0; padding: 0; cursor: pointer; white-space: nowrap;
}
.navdrop-btn:hover, .navdrop:focus-within .navdrop-btn, .navdrop.open .navdrop-btn { color: var(--text-strong); }
.navdrop-caret {
    width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent;
    border-top: 5px solid currentColor; opacity: 0.7;
}
.navdrop-panel {
    position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
    z-index: 60; display: grid; grid-template-columns: repeat(2, minmax(230px, 270px));
    gap: 4px; padding: 10px;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: var(--shot-radius); box-shadow: 0 24px 48px rgba(0, 0, 0, 0.55);
    max-height: calc(100vh - 96px); overflow-y: auto; overscroll-behavior: contain;
    opacity: 0; visibility: hidden; pointer-events: none;
}
.navdrop:hover .navdrop-panel,
.navdrop:focus-within .navdrop-panel,
.navdrop.open .navdrop-panel { opacity: 1; visibility: visible; pointer-events: auto; }

/* One row per guide: the title, then the one-liner the use-case card uses. */
.guide-row {
    display: block; padding: 10px 12px; border-radius: 12px;
    color: var(--text); transition: background 0.15s, color 0.15s;
}
.guide-row strong { display: block; color: var(--text-strong); font-size: 14.5px; font-weight: 700; margin-bottom: 3px; }
.guide-row span { display: block; font-size: 13px; line-height: 1.45; color: var(--text-muted); }
a.guide-row:hover { background: var(--bg-highlight); color: var(--text); }
a.guide-row:hover strong { color: var(--accent); }
.guide-row.on { background: var(--bg-highlight); }
.guide-row.on strong { color: var(--accent); }
.guide-row.on strong::before { content: '\2022  '; }
.guide-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ---- the mobile menu ----
   `.nav-links` is display:none under the breakpoint and nothing replaced it,
   so the whole site was one CTA wide on a phone. */
.menu-btn {
    display: none; flex-direction: column; justify-content: center; gap: 4px;
    width: 40px; height: 36px; padding: 0 8px; cursor: pointer;
    background: none; border: 1px solid var(--border-strong); border-radius: 999px;
}
.menu-btn span { display: block; height: 2px; border-radius: 2px; background: var(--text); }
.menu-btn:hover { border-color: var(--accent); }
.menu-btn:hover span { background: var(--text-strong); }
.menu-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nav-panel {
    position: absolute; top: 100%; left: 0; right: 0; z-index: 55;
    /* `dvh`, not `vh`: on iOS Safari `100vh` is the viewport with the toolbars
       hidden, so the panel's tail sat under the bottom bar, the panel never
       overflowed its own max-height, and with the page locked by `nav-open`
       nothing scrolled at all. `100%` is the nav's own height (the panel's
       containing block), so no guessed 64px either. */
    max-height: calc(100vh - 100%);
    max-height: calc(100dvh - 100%);
    overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
    background: var(--bg-card); border-bottom: 1px solid var(--border);
    box-shadow: 0 24px 48px rgba(0, 0, 0, 0.55);
}
.nav-panel-in { padding: 18px var(--gutter) 26px; }
.nav-panel-sections { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 16px; margin-bottom: 18px; }
.nav-panel-sections a { padding: 10px 2px; font-size: 15px; font-weight: 600; color: var(--text); }
.nav-panel-sections a:hover { color: var(--text-strong); }
.nav-panel-label {
    margin: 0 0 6px; font-family: var(--mono); font-size: 11px;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted);
}
.nav-panel-guides { display: grid; gap: 2px; }
.nav-panel-label--gap { margin-top: 20px; }
.nav-panel-langs { display: flex; flex-wrap: wrap; gap: 8px 18px; font-size: 14px; font-weight: 600; }
.nav-panel-langs a { color: var(--text); padding: 6px 0; }
.nav-panel-langs a:hover { color: var(--text-strong); }
.nav-panel-guides .guide-row { padding: 10px 10px; }
body.nav-open { overflow: hidden; }

/* ---- buttons ---- */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 10px;
    font-weight: 800; border-radius: 14px; padding: 15px 28px; font-size: 16px;
    text-align: center; transition: background 0.15s, border-color 0.15s;
}
.btn-primary { background: var(--accent); color: var(--ink); }
.btn-primary:hover { background: var(--accent-hover); color: var(--ink); }
.btn-ghost { border: 1px solid var(--border-strong); color: var(--text-strong); font-weight: 700; font-size: 15px; padding: 15px 22px; }
.btn-ghost:hover { border-color: var(--accent); color: var(--text-strong); }
.btn-sm { padding: 10px 20px; font-size: 14px; font-weight: 700; border-radius: 999px; }
.btn-block { display: flex; width: 100%; padding: 14px; font-size: 15px; border-radius: 12px; }
.applemark { width: 1em; height: 1em; flex: none; }

/* ---- hero ---- */
.hero { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: clamp(32px, 6vw, 72px); align-items: center; padding-top: clamp(48px, 7vw, 88px); padding-bottom: clamp(48px, 6vw, 72px); }
.hero .lede { max-width: 560px; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin-bottom: clamp(28px, 4vw, 44px); }
.stats { display: flex; flex-wrap: wrap; gap: clamp(24px, 4vw, 40px); margin: 0; }
.stats dt { font-size: clamp(24px, 3vw, 30px); font-weight: 800; color: var(--text-strong); letter-spacing: -0.02em; }
.stats dt.accent { color: var(--accent); }
.stats dd { margin: 4px 0 0; font-size: 13px; font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.08em; }

/* ---- device frames ----
   The bezel used to be CSS: clip, rounding and a drop shadow around a bare
   screenshot. It is now baked into the PNG itself — StoreShotMockupView's own
   device mockup, margin included — so .shot only sizes and positions the
   image; it does not frame it a second time. No shadow either: the image is
   itself opaque BrandBackground, so a box-shadow behind it would draw as a
   visible rectangle rather than read as depth. */
/* An explicit width, not `auto`. `.shot` is often a grid item, and a grid item
   that is centred (auto inline margins, or `justify-self`/`justify-items`
   other than stretch) is sized to FIT-CONTENT rather than stretched. With a
   real <img> inside, fit-content is the image's intrinsic width clamped by
   max-width, which happens to look right; an empty `.shot--missing`
   placeholder has no intrinsic width and collapsed to its 2px border. A
   percentage width resolves against the containing block in every one of
   those contexts, and max-width still caps it. */
.shot, .heroshow { width: 100%; }
/* Every image on the site is framed the same way: one radius, a hairline in
   the same border token the cards use, and `overflow: hidden` so the corner
   actually cuts the pixels. The iPhone and iPad PNGs carry their own
   BrandBackground margin around the bezel, so the border encloses that margin
   and the whole thing reads as a card holding a device, which is the point. */
.shot {
    border-radius: var(--shot-radius); border: 1px solid var(--border);
    overflow: hidden;
}
.shot img, .shot picture img { width: 100%; height: auto; display: block; }
.shot-sm { max-width: 380px; }
.shot-end { justify-self: end; }
/* The hero column is 421px wide at a 1440 viewport, one pixel past the cap a
   phone frame gets everywhere else. Same ceiling here, for the same reason:
   the source PNG is 1320x2613 and has nothing to give above it. */
.hero .heroshow, .hero > .shot { max-width: 420px; }

/* Neither a head-unit render nor a Live Activity card carries an edge of its
   own, and both are drawn on the same flat BrandBackground as this page, so
   without a frame they bleed into the canvas and read as loose pixels. One
   rule for both: a hairline edge, the brand radius, and a faint inner shadow
   so the surface sits slightly below the page rather than floating on it. The
   iPhone and iPad frames need none of this, their bezel being in the PNG. */
.shot--carplay, .shot--activity {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 2px 18px rgba(0, 0, 0, 0.35);
}
.shot--carplay { background: #000; }

/* Both are landscape, so they run wider than a portrait column: see
   `.figrow--wide` below. */

/* A ref with no source anywhere, not even en (empty asset tree, or a shot the
   pipeline has not generated yet): a bordered box in place of the image so the
   layout does not collapse. The aspect is the real one of the PNG that is not
   there yet, per device, so an empty tree reserves the right shape rather than
   one portrait iPhone shape for a head-unit render too. Landscape iPhone shots
   (live-landscape, cinematic-landscape) are the one case this cannot know
   about; they get the portrait box until their source exists. */
.shot--missing {
    aspect-ratio: 1320 / 2613;
    background: var(--bg-card);
    /* Same radius and same 1px edge as a real frame, so a layout measured
       against an empty asset tree matches one measured against a full one;
       only the dashed stroke says "this one is not here yet". */
    border-style: dashed; border-color: var(--border-strong);
}
.shot--missing.shot--ipad { aspect-ratio: 2064 / 2848; }
.shot--missing.shot--carplay { aspect-ratio: 16 / 9; }
.shot--missing.shot--activity { aspect-ratio: 1320 / 574; }

/* Legacy alias kept for one release so markup still carrying class="phone"
   does not lose its frame outright. Nothing in this build emits it any more
   (render.ts emits .shot); safe to delete once nothing references it. */
.phone {
    border-radius: clamp(24px, 3vw, 40px); overflow: hidden; aspect-ratio: 1320 / 2868;
    box-shadow: 0 40px 80px rgba(0, 0, 0, 0.55), 0 0 0 1px var(--border);
    background: #171410;
}
.phone img { width: 100%; height: 100%; object-fit: cover; object-position: center top; }
.phone-sm { max-width: 380px; box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--border); }
.phone-end { justify-self: end; }

/* The bento `.tile` class is gone with the `tile` device: every web shot is a
   full screen or a full head-unit render now, never a crop of the store
   mosaic, so a fragment card has nothing to hold. */

/* ---- split sections ---- */
.split { display: grid; gap: clamp(32px, 6vw, 72px); align-items: center; }
.split-media-left  { grid-template-columns: 0.8fr 1.2fr; }
.split-media-right { grid-template-columns: 1.2fr 0.8fr; }

/* ---- grids ---- */
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(24px, 3vw, 40px); align-items: start; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 4vw, 72px); align-items: start; }

.card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 18px; padding: 26px; }
.card p { font-size: 14.5px; line-height: 1.6; margin: 0; }

figure { margin: 0; }
figcaption { font-size: 14px; line-height: 1.55; margin-top: 16px; }
figcaption strong { color: var(--text-strong); }

/* ---- landing feature sections: two shapes, decided by figure count ----
   Three or more figures: the heading block and the bullets side by side, the
   figures in a row underneath. Exactly two: a split, text on one side and the
   pair on the other, media side alternating down the page — nine identical
   "heading, bullets, row of phones" blocks read as one repeated block.

   `--figcap` is the width a frame may never exceed. A phone frame wider than
   420px is a 1320x2613 PNG upscaled past its purpose and, in a two-up row,
   850px tall; iPad frames are 2064x2848 and earn more. The row caps its own
   TRACKS at the same value and centres what is left over, so the cap holds
   whether the grid has room or not. */
.section-head {
    display: grid; grid-template-columns: 1.05fr 0.95fr;
    gap: clamp(28px, 5vw, 72px); align-items: start;
    margin-bottom: clamp(36px, 5vw, 60px);
}
.figrow { display: grid; gap: clamp(24px, 3vw, 40px); align-items: start; justify-content: center; }
.figrow-2 { grid-template-columns: repeat(2, minmax(0, var(--figcap, 420px))); }
.figrow-3 { grid-template-columns: repeat(3, minmax(0, var(--figcap, 420px))); }
/* The pair inside a two-figure split. Shares the frame cap; the column it sits
   in is usually the tighter constraint. */
.figpair {
    display: grid; grid-template-columns: repeat(2, minmax(0, var(--figcap, 420px)));
    gap: clamp(16px, 2vw, 28px); align-items: start; justify-content: center;
}
.figrow > figure, .figpair > figure { max-width: var(--figcap, 420px); }
.figrow--ipad, .figpair--ipad { --figcap: 640px; }
/* Landscape frames get a row of their own and span what two portrait columns
   would: laid out in the portrait grid, a 2.78:1 head unit went out at 326px
   with half its column empty beside it while the phones next to it stood
   650px tall. `--figcap` is the width a frame may never exceed, so a wide row
   raises it and the tracks follow. */
.figrow--wide {
    --figcap: 720px;
    /* ONE per row, centred. Two of them inside the 1160px wrap would be 512px
       each, which is the same complaint again in a different shape: a
       landscape frame has to be about as wide as two portrait columns
       (2 x 328 + gap) to be worth showing at all. They are short, so stacking
       them costs less height than a phone row does. */
    grid-template-columns: minmax(0, var(--figcap));
    gap: clamp(20px, 2.5vw, 32px);
}

/* ---- the scroll-snap strip: four or more portrait frames ----
   A 2x2 or 3+2 grid of phone frames is two screens of scrolling for one
   section. The strip keeps the section about one phone tall and makes the
   extra frames something the reader opts into: it bleeds to the container
   edge so the cut-off frame on the right reads as "there is more", snaps one
   figure at a time, and fades at both edges. The prev/next buttons ship
   `hidden` and the inline script reveals them, because without JavaScript the
   strip is still scrollable by dragging. */
/* The nav buttons get a band of their own above the strip rather than being
   hung in whatever whitespace happens to be there: on a phone the bullets run
   all the way down to the strip and the buttons landed on top of the last
   line. */
.figstrip-wrap { position: relative; padding-top: 48px; }
.figstrip {
    display: grid; grid-auto-flow: column; grid-auto-columns: 280px;
    gap: 20px; align-items: start;
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory; scrollbar-width: none;
    /* Without this the snap aligns the first figure to the SCROLLPORT edge and
       the browser quietly scrolls the strip by the padding, putting the first
       frame under the edge fade at rest. `scroll-padding` is what tells the
       snapping where the content really starts. */
    scroll-padding-inline: var(--gutter);
    /* Bleed to the container edge, then pad the gutter back in so the first
       figure still lines up with the copy above it. */
    margin-inline: calc(var(--gutter) * -1);
    padding: 4px var(--gutter) 8px;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--gutter), #000 calc(100% - var(--gutter)), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 var(--gutter), #000 calc(100% - var(--gutter)), transparent 100%);
}
.figstrip::-webkit-scrollbar { display: none; }
.figstrip > figure { scroll-snap-align: start; }
.strip-nav { position: absolute; top: 0; right: 0; display: flex; gap: 8px; }
.strip-btn {
    width: 36px; height: 36px; padding: 0; line-height: 1;
    font-family: var(--font); font-size: 20px; font-weight: 700;
    color: var(--text); background: none; cursor: pointer;
    border: 1px solid var(--border-strong); border-radius: 999px;
    transition: border-color 0.15s, color 0.15s;
}
.strip-btn:hover { border-color: var(--accent); color: var(--text-strong); }
.strip-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- the light preview and the accent picker crossfade ----
   Both appearances (and all twenty-five accent frames) occupy ONE grid cell, so the
   swap is an opacity transition rather than a layout flick. Without JS the
   `hidden` attribute the markup ships is never lifted and only the dark, amber
   frame is there at all, which is why `.off` is applied by the script. */
.lightstack, .accent-shots, .slidestack { display: grid; }
.lightstack > .shot, .accent-shots > .shot, .slidestack > .shot { grid-area: 1 / 1; }
.js .lightstack > .shot, .js .accent-shots > .shot { transition: opacity 0.25s ease; }
/* The hero crossfades slower: it changes on its own, unasked, so the change
   has to read as a dissolve rather than as a flicker. */
.js .slidestack > .shot { transition: opacity 0.6s ease; }
.js .shot.off { opacity: 0; pointer-events: none; }

/* ---- hero slideshow dots ---- */
.dots { display: flex; gap: 10px; align-self: center; }
.dot {
    width: 9px; height: 9px; padding: 0; border: 0; border-radius: 50%;
    background: var(--border-strong); cursor: pointer;
    transition: background 0.2s ease, transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}
.dot[aria-pressed="true"] { background: var(--accent); transform: scale(1.3); }
.dot:hover { background: var(--accent-hover); }
.dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* A two-figure split needs more room for the media than the 0.8fr a single
   frame gets, and less for the text. Specificity beats .split-media-* on its
   own, so the collapse rule below has to name this modifier too. */
.split--pair.split-media-left  { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); }
.split--pair.split-media-right { grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr); }

/* ---- light-appearance preview: both <picture>s are in the HTML, the light
   one carrying `hidden`, and the button swaps the attribute. Without JS the
   dark frame shows, which is the page's own appearance anyway. ---- */
[hidden] { display: none !important; }
/* Inside a figure the button follows the CAPTION, not the frame: with it
   between the two, the one column that has a button pushed its caption a line
   lower than its neighbours' and the row stopped aligning. */
figure > .btn-light { margin-top: 12px; }
.btn-light {
    font-family: inherit; font-size: 13px; font-weight: 700;
    color: var(--text); background: none; cursor: pointer;
    border: 1px solid var(--border-strong); border-radius: 999px; padding: 8px 16px;
    transition: border-color 0.15s, color 0.15s;
}
.btn-light:hover { border-color: var(--accent); color: var(--text-strong); }
.btn-light[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.btn-light:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- accent picker: eleven frames, one visible, a row of swatch buttons ---- */
.accent-stage { display: flex; flex-direction: column; align-items: center; gap: clamp(20px, 3vw, 28px); }
.accent-shots { width: 100%; max-width: 340px; }
.swatches { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
.swatch {
    width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%;
    background: var(--swatch, var(--accent)); cursor: pointer;
    /* The ring transitions with the scale, so a swatch that grows under the
       pointer and is then picked reads as one control, not two. */
    transition: box-shadow 0.2s ease, transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}
.swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--bg-band), 0 0 0 4px var(--swatch); }
.swatch:focus-visible { outline: 2px solid var(--text-strong); outline-offset: 4px; }
/* Visually hidden, still read aloud: the swatch's own colour name. */
.vh {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---- feature bullet lists ---- */
.bullets { display: flex; flex-direction: column; gap: 14px; margin: 0; padding: 0; list-style: none; font-size: 15.5px; line-height: 1.55; }
.bullets strong { color: var(--text-strong); }
.rules { display: flex; flex-direction: column; gap: 18px; margin: 8px 0 0; padding: 0; list-style: none; font-size: 15.5px; line-height: 1.6; }
.rules li { border-left: 2px solid var(--accent); padding-left: 20px; }
.rules strong { color: var(--text-strong); }

/* ---- summary / TL;DR ---- */
.summary-box { border: 1px solid var(--border-strong); border-radius: 20px; padding: clamp(24px, 3vw, 36px) clamp(24px, 3vw, 40px); background: var(--bg-band); }
.summary-box p { font-size: 16px; line-height: 1.75; margin: 0; color: var(--text-card); text-wrap: pretty; }
.summary-box strong { color: var(--text-strong); }

/* ---- comparison table ---- */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: 14px; line-height: 1.5; min-width: 760px; }
thead th { text-align: left; padding: 14px 16px; border-bottom: 1px solid var(--border-strong); color: var(--text-strong); font-size: 15px; }
thead th:first-child { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); }
thead th.own { border-bottom: 2px solid var(--accent); background: var(--bg-highlight); color: var(--accent); }
tbody th { text-align: left; padding: 14px 16px; border-bottom: 1px solid var(--border); color: var(--text-strong); font-weight: 600; }
tbody td { padding: 14px 16px; border-bottom: 1px solid var(--border); }
tbody td.own { background: var(--bg-highlight); color: var(--text-card); }
tbody tr:last-child td.own { border-bottom: 2px solid var(--accent); }
.footnote { font-size: 13px; color: var(--text-muted); margin-top: 20px; }

/* ---- pricing ---- */
.pricing-head { text-align: center; max-width: 640px; margin: 0 auto clamp(32px, 5vw, 52px); }
.plans { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; max-width: 880px; margin: 0 auto; }
.plan { border: 1px solid var(--border); border-radius: var(--radius); padding: clamp(26px, 3vw, 36px); background: var(--bg-band); position: relative; }
.plan-pro { border-color: var(--accent); background: var(--bg-highlight); }
.plan h3 { font-size: 22px; font-weight: 800; margin: 0 0 6px; }
.plan-pro h3 { color: var(--accent); }
.plan-sub { font-size: 14px; margin: 0 0 24px; color: var(--text-muted); }
.plan ul { display: flex; flex-direction: column; gap: 12px; margin: 0 0 28px; padding: 0; list-style: none; font-size: 15px; line-height: 1.5; }
.plan-free ul { margin-bottom: 0; }
/* The Pro card's price lines: Apple's own formatted figure per storefront, read by
   bin/prices.ts, bold inside a translated sentence. */
.plan ul.plan-prices { gap: 6px; margin: 0 0 22px; padding: 0 0 18px; border-bottom: 1px solid var(--border-strong); font-size: 16px; }
.plan-prices strong { color: var(--text-strong); font-family: var(--mono); font-size: 19px; font-weight: 700; letter-spacing: -0.01em; }
.plan .plan-note { margin-top: 14px; font-size: 12.5px; }
.plan li.included { color: var(--text-strong); }
.plan-pro li { color: var(--text-card); }
.plan-badge {
    position: absolute; top: -12px; right: 28px; margin: 0; background: var(--accent); color: var(--ink);
    font-family: var(--mono); font-size: 11px; font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; padding: 4px 12px; border-radius: 999px;
}

/* ---- faq ---- */
.faq { max-width: 860px; }
.faq h2 { margin-bottom: clamp(24px, 4vw, 40px); }
details { border-top: 1px solid var(--border); padding: 22px 4px; }
details:last-of-type { border-bottom: 1px solid var(--border); }
details > summary { list-style: none; cursor: pointer; display: flex; justify-content: space-between; align-items: center; gap: 16px; font-size: 17px; font-weight: 700; color: var(--text-strong); }
details > summary::-webkit-details-marker { display: none; }
.faq-chevron { color: var(--accent); font-size: 20px; transition: transform 0.2s; flex: none; }
details[open] .faq-chevron { transform: rotate(45deg); }
details p { font-size: 15.5px; line-height: 1.65; margin: 14px 0 0; max-width: 700px; }

/* ---- closing cta ---- */
.closing { text-align: center; padding-top: clamp(64px, 10vw, 112px); padding-bottom: clamp(64px, 10vw, 112px); }
.closing img.icon { width: 88px; height: 88px; border-radius: 20px; margin: 0 auto 28px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4); }
.closing h2 { font-size: clamp(30px, 4.6vw, 46px); font-weight: 900; letter-spacing: -0.025em; }
.closing .lede { margin: 0 auto 36px; max-width: 540px; }
.closing .btn { font-size: 17px; padding: 17px 34px; border-radius: 16px; }

/* ---- footer ---- */
.footer { border-top: 1px solid var(--border); background: var(--bg-footer); }
.footer-in { display: flex; flex-direction: column; align-items: flex-start; padding-top: 40px; padding-bottom: 40px; font-size: 13.5px; color: var(--text-muted); }
.footer-in p { margin: 0; }
.footer-links { display: flex; flex-wrap: wrap; gap: 24px; }
.footer-links a { color: var(--text-muted); }
.footer-links a:hover { color: var(--text-strong); }

/* ---- language picker: real <a> links in a <details>, so the alternates stay
   crawlable and the control needs no JavaScript ---- */
/* `details.langpick`, not `.langpick`: it has to outrank the FAQ's
   `details:last-of-type` border, which is the same element type. */
details.langpick { position: relative; border: 0; padding: 0; }
.langpick > summary {
    list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
    border: 1px solid var(--border-strong); color: var(--text);
    font-size: 13.5px; font-weight: 600; padding: 8px 14px; border-radius: 999px;
}
.langpick > summary::-webkit-details-marker { display: none; }
.langpick > summary::after { content: '\25BE'; font-size: 10px; color: var(--text-muted); }
.langpick > summary:hover { border-color: var(--accent); color: var(--text-strong); }
.langpick-menu {
    position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
    display: flex; flex-direction: column; min-width: 170px; padding: 6px;
    background: var(--bg-band); border: 1px solid var(--border-strong);
    border-radius: 14px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
}
.langpick-menu a { padding: 9px 12px; border-radius: 9px; font-size: 14px; font-weight: 600; color: var(--text); }
.langpick-menu a:hover { background: var(--bg-highlight); color: var(--text-strong); }

/* ---- motion ----
   All of it inside one `prefers-reduced-motion: no-preference` block, so a
   reader who has asked for stillness gets a page that never moves: the reveal
   never applies (it is also gated on the `.js` class), the hero does not
   float, nothing lifts on hover and the stat numbers are simply the numbers
   that are in the HTML. */
@media (prefers-reduced-motion: no-preference) {
    .js .reveal { opacity: 0; transform: translateY(28px); transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1), transform 0.7s cubic-bezier(0.22, 1, 0.36, 1); }
    .js .reveal.in { opacity: 1; transform: none; }

    /* A figure lifts and picks up a soft accent glow under the pointer. The
       shadow is a `drop-shadow` filter rather than a box-shadow because a
       device frame is an opaque rectangle and the two look the same here,
       while the filter also follows a rounded `.shot--carplay` edge. */
    .figrow > figure, .figpair > figure, .figstrip > figure {
        transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
    }
    .figrow > figure:hover, .figpair > figure:hover, .figstrip > figure:hover {
        transform: translateY(-4px);
        filter: drop-shadow(0 14px 30px rgba(245, 161, 12, 0.20));
    }

    /* The hero frame breathes, over a radial accent glow that is part of the
       page rather than of the image. `isolation` keeps the negative z-index
       inside this stacking context instead of dropping it behind the body. */
    .hero .slidestack { animation: shot-float 6s ease-in-out infinite; }
    @keyframes shot-float {
        0%, 100% { transform: translateY(0); }
        50%      { transform: translateY(-6px); }
    }

    .swatch:hover { transform: scale(1.15); }

    .navdrop-panel { transition: opacity 0.15s ease, visibility 0.15s; }
    .nav-panel { animation: nav-drop 0.15s ease-out; }
    @keyframes nav-drop { from { opacity: 0; transform: translateY(-6px); } }
}

/* `overflow-x: clip` rather than `hidden`: it clips without becoming a scroll
   container, so the glow below can bleed sideways out of the phone without
   widening the page. `body { overflow-x: hidden }` does NOT cover this, it
   hides the scrollbar while the document still reports the wider scrollWidth. */
.hero { overflow-x: clip; }
.hero .heroshow { position: relative; isolation: isolate; display: flex; flex-direction: column; gap: 18px; }
.hero .heroshow::before {
    content: ''; position: absolute; z-index: -1; inset: -14% -20%;
    background: radial-gradient(closest-side, rgba(245, 161, 12, 0.18), transparent 72%);
    pointer-events: none;
}

/* ---- responsive ---- */
@media (max-width: 1000px) {
    .hero, .split-media-left, .split-media-right, .section-head,
    .split--pair.split-media-left, .split--pair.split-media-right { grid-template-columns: 1fr; }
    /* `justify-self: center`, never `margin-inline: auto`. An auto inline margin
       on a grid item cancels the default stretch and sizes the item to
       fit-content: with a real <img> inside that is the image's intrinsic width
       clamped by max-width, which looks right, but an empty `.shot--missing`
       placeholder has no intrinsic width and collapsed to its 2px border. */
    .hero .shot, .split .shot, .hero .phone, .split .phone,
    .hero .heroshow { max-width: 340px; justify-self: center; }
    .shot-end, .phone-end { justify-self: center; }
    .grid-4 { grid-template-columns: repeat(2, 1fr); }
    .grid-2 { grid-template-columns: 1fr; }
    .nav-links { display: none; }
    .menu-btn { display: flex; }
    /* The bar has no room for the picker next to the CTA and the menu button;
       the panel carries the same links, from the same list. */
    .nav-end .langpick { display: none; }
}
/* The breakpoint above is the no-JS fallback. How wide the full bar is
   depends on the locale (German and French labels run far longer than
   English), so the script measures it and sets `.compact` whenever the links
   would overflow, instead of a fixed width wrapping them mid-label. */
.nav.compact .nav-links, .nav.compact .nav-end .langpick { display: none; }
.nav.compact .menu-btn { display: flex; }
@media (max-width: 760px) {
    .grid-3 { grid-template-columns: 1fr; }
    /* One capped track, centred by the row's own `justify-content`, rather than
       a 1fr track holding a figure with auto margins: same reason as above, an
       auto margin would stop the figure stretching and collapse an empty
       placeholder. */
    .figrow-2, .figrow-3, .figpair { grid-template-columns: minmax(0, 380px); }
    .figrow > figure, .figpair > figure { max-width: none; }
    .plans { grid-template-columns: 1fr; }
    .hero-actions .btn { flex: 1 1 100%; }
    .footer-top { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 760px) {
    /* The bar has to hold a brand, a CTA and now a menu button on a 390px
       phone. The CTA stays (it is the point of the page) and shrinks; below
       520 the brand word steps aside and the icon carries the link home, the
       name being in the title, the H1 and the footer anyway. */
    .nav { gap: 12px; }
    .nav-end { gap: 10px; }
    .nav .btn-sm { padding: 9px 14px; font-size: 12.5px; white-space: nowrap; }
}
@media (max-width: 520px) {
    .grid-4 { grid-template-columns: 1fr; }
    .stats { gap: 20px 28px; }
    .nav-brand-name { display: none; }
    .nav-panel-sections { grid-template-columns: 1fr; }
}

/* ---- legal pages (privacy policy) ---- */
.legal { padding-top: clamp(40px, 6vw, 72px); padding-bottom: clamp(56px, 9vw, 96px); max-width: 820px; }
.legal h1 { font-size: clamp(30px, 4.4vw, 44px); margin-bottom: 8px; }
.legal h2 { font-size: clamp(21px, 2.4vw, 26px); margin: 48px 0 14px; }
.legal h3 { font-size: 17px; margin: 28px 0 8px; }
.legal h4 { font-size: 15px; font-weight: 700; color: var(--text-strong); margin: 20px 0 6px; }
.legal p, .legal li { font-size: 15.5px; line-height: 1.72; }
.legal p { margin: 0 0 14px; }
.legal ul, .legal ol { padding-left: 22px; margin: 0 0 16px; }
.legal li { margin-bottom: 8px; }
.legal .subtitle { color: var(--text-muted); font-size: 16px; margin-bottom: 0; }
.legal .last-update { margin-top: 44px; padding-top: 20px; border-top: 1px solid var(--border); color: var(--text-muted); font-size: 14px; }
.legal header { margin-bottom: 12px; }
.legal section { border-top: 1px solid var(--border); margin-top: 40px; padding-top: 8px; }
.legal section:first-of-type { border-top: none; }
.legal strong, .legal b { color: var(--text-strong); }
.legal table { min-width: 0; margin-bottom: 20px; }
.legal code { font-family: var(--mono); font-size: 0.92em; }

/* ---- footer legal + language row ---- */
.footer-top { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px; width: 100%; }
.footer-langs {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
    width: 100%; margin-top: 24px; padding-top: 20px;
    border-top: 1px solid var(--border); font-size: 13px;
}
.footer-langs span:first-child { font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.08em; font-size: 11px; }
.footer-langs a { color: var(--text-muted); }
.footer-langs a:hover { color: var(--text-strong); }
.footer-langs .on { color: var(--accent); font-weight: 600; }

/* ---- deck: the voice line under a keyword-carrying heading (M6) ---- */
.deck { font-size: clamp(19px, 2.2vw, 24px); font-weight: 700; line-height: 1.3; color: var(--text-strong); margin: -8px 0 18px; text-wrap: pretty; }
.pricing-head .deck { margin-bottom: 14px; }

/* ---- use-case cards: one per guide page, on the landing and under a guide ---- */
.usecases .card { position: relative; display: block; color: var(--text); padding-right: 48px; transition: border-color 0.15s; }
.usecases .card:hover { border-color: var(--accent); color: var(--text); }
.usecases .card h3 { color: var(--text-strong); }
.usecases .card-more { position: absolute; right: 22px; top: 24px; color: var(--accent); font-size: 20px; font-weight: 800; }

/* ---- comparison sources ---- */
.sources { margin-top: 8px; }
/* The adapter catalog (guide obd). The table reuses the compare table's cells; the
   status is a pill, the buy link a small ghost button, the disclosure the mandated
   Amazon sentence under the dated footnote. */
.adapters { margin-top: 28px; }
.adapters > p { max-width: 720px; font-size: 16.5px; line-height: 1.7; margin: 0 0 20px; text-wrap: pretty; }
.adapters table { min-width: 640px; }
.adapters tbody th { white-space: nowrap; }
.adapters td { vertical-align: top; }
.adapters .btn-sm { white-space: nowrap; }
.status { display: inline-block; padding: 4px 10px; border: 1px solid var(--border-strong); border-radius: 999px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap; color: var(--text-card); }
.status-works { border-color: var(--accent); color: var(--accent); }
.status-unsupported, .status-unverified { color: var(--text-muted); }
.adapters .disclosure { margin-top: 8px; }
.sources a { color: var(--text-muted); text-decoration: underline; text-underline-offset: 3px; }
.sources a:hover { color: var(--text-strong); }

/* ---- guide pages ---- */
.crumbs { padding-top: 22px; font-size: 13.5px; color: var(--text-muted); display: flex; gap: 8px; }
.crumbs a { color: var(--text-muted); }
.crumbs a:hover { color: var(--text-strong); }
.guide-hero { padding-top: clamp(20px, 3vw, 36px); }
.guide-hero h1 { font-size: clamp(32px, 4.6vw, 50px); }
.answer { font-size: clamp(16px, 1.7vw, 18.5px); line-height: 1.68; color: var(--text-card); margin: 0 0 28px; max-width: 620px; text-wrap: pretty; }
.guide-hero .footnote { margin-top: 0; }
/* The support page has no hero figure, so it is one column rather than the
   guide's media grid. */
.support-hero { padding-top: clamp(20px, 3vw, 36px); padding-bottom: clamp(28px, 4vw, 48px); }
.support-hero h1 { font-size: clamp(32px, 4.6vw, 50px); max-width: 820px; }
.support-hero .answer { max-width: 760px; }
.support-hero .footnote { margin-top: 0; }
.guide-body { display: flex; flex-direction: column; gap: clamp(48px, 7vw, 80px); padding-bottom: clamp(56px, 9vw, 96px); }
.guide-section h2 { font-size: clamp(24px, 3vw, 32px); }
.guide-section > div > p, .guide-section > div > .bullets { max-width: 720px; }
.guide-section > div > p { font-size: 16.5px; line-height: 1.7; margin: 0 0 20px; text-wrap: pretty; }
.guide-section.split { gap: clamp(28px, 5vw, 64px); }
/* An article's data table: the compare table's cells, sized to the text column and
   free of its 760px floor, with the caption as the table's own heading. */
.article-table { max-width: 720px; margin: 8px 0 24px; }
.article-table table { min-width: 480px; }
.article-table caption { caption-side: top; text-align: left; padding: 0 0 10px; font-size: 14px; font-weight: 600; color: var(--text-strong); }
.article-table thead th:first-child { font-family: inherit; font-size: 15px; letter-spacing: 0; text-transform: none; color: var(--text-strong); }
.guide-section .article-table-source { max-width: 720px; margin: -12px 0 24px; }
.related { padding-top: clamp(40px, 6vw, 64px); padding-bottom: 0; }
.related .eyebrow { margin-bottom: 20px; }

/* ---- footer guide links ---- */
.footer-guides {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
    width: 100%; margin-top: 24px; padding-top: 20px;
    border-top: 1px solid var(--border); font-size: 13px;
}
.footer-guides span:first-child { font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.08em; font-size: 11px; }
.footer-guides a { color: var(--text-muted); }
.footer-guides a:hover { color: var(--text-strong); }
.footer-guides .on { color: var(--accent); font-weight: 600; }

/* ---- 404 ---- */
.notfound { padding-top: clamp(56px, 9vw, 96px); padding-bottom: clamp(72px, 12vw, 128px); max-width: 720px; }
.notfound-langs { display: flex; flex-wrap: wrap; gap: 10px 22px; list-style: none; margin: 0; padding: 0; font-weight: 600; }

/* The blog: the index is a card grid like the use-case section; an article is a
   guide without figures. The date sits under the summary on a card and in the
   article header's footnote. */
.article-list { padding-bottom: clamp(56px, 9vw, 96px); }
.usecases .card time { display: block; margin-top: 12px; font-size: 13px; color: var(--text-muted); }
.article-meta { margin-top: 0; }
.article-byline { margin-top: 4px; }
/* The article's one image, between the byline and the first section. A
   landscape head-unit render runs the text column's width; a portrait phone
   frame keeps the cap it has everywhere else. */
.article-figure { margin: 0 auto clamp(40px, 6vw, 64px); }
.article-figure .shot { max-width: 380px; }
.article-figure--wide .shot { max-width: 920px; }
.article-all { margin-top: 24px; font-size: 15px; }
.related + .closing { margin-top: 0; }
