/**
 * Just Currency Converter — the web design system.
 *
 * Every colour here is a token from ui/designsystem/Color.kt and Palettes.kt, and every type
 * size is from Type.kt plus the per-site overrides in AmountStack.kt. Two rules from the app
 * carry over and are load-bearing: **nothing draws a border around a surface** (depth is the one
 * shadow recipe), and **the accent appears only on the answer, the target code, `=`, the caret
 * and selected state** — that is what keeps it loud.
 */

/* ── Faces ───────────────────────────────────────────────────────────────────────────────── */
/* Both are the app's own variable files, self-hosted: no Google Fonts, so the site loads nothing
 * from a third party and the privacy page stays true of the site itself. */

@font-face {
    font-family: 'Manrope';
    src: url('fonts/manrope_variable.ttf');
    font-weight: 200 800;
    font-display: swap;
}

@font-face {
    font-family: 'IBM Plex Mono';
    src: url('fonts/ibm_plex_mono_variable.ttf');
    font-weight: 100 700;
    font-display: swap;
}

/* ── Tokens ──────────────────────────────────────────────────────────────────────────────────
 * The colours are NOT here. `assets/tokens.css` is generated from Color.kt and Palettes.kt by
 * WebDataExportTest and is linked before this file, so a scheme cannot go stale on the web.
 *
 * What is left is what Kotlin cannot hand over: the one shadow recipe (its tint is private to
 * Elevation.kt), the page margin, and the two font stacks. The type scale is transcribed further
 * down for the same reason — `appTypography()` is @Composable and a JVM test cannot call it. */

:root {
    /* The app's one shadow: Charcoal at 18%, cooled. Elevations are its dp values. */
    --shadow-chip: 0 2px 4px rgba(26, 29, 32, 0.10), 0 1px 2px rgba(26, 29, 32, 0.06);
    --shadow-swap: 0 3px 6px rgba(26, 29, 32, 0.12), 0 1px 3px rgba(26, 29, 32, 0.08);
    --shadow-disc: 0 6px 12px rgba(26, 29, 32, 0.14), 0 2px 4px rgba(26, 29, 32, 0.08);
    --shadow-card: 0 2px 4px rgba(26, 29, 32, 0.10), 0 1px 2px rgba(26, 29, 32, 0.06);
    --shadow-panel: 0 18px 48px rgba(26, 29, 32, 0.13), 0 2px 8px rgba(26, 29, 32, 0.06);

    /* For the store badges. `drop-shadow` follows the artwork's own silhouette, so the depth
     * hugs the real rounded corner without anyone having to know its radius, which differs
     * between the two lockups and again at every display size. */
    --drop: drop-shadow(0 3px 6px rgba(26, 29, 32, 0.12)) drop-shadow(0 1px 3px rgba(26, 29, 32, 0.08));
    --drop-lift: drop-shadow(0 8px 16px rgba(26, 29, 32, 0.18)) drop-shadow(0 2px 5px rgba(26, 29, 32, 0.10));

    /* One spacing scale, so the rhythm is chosen rather than accumulated. Proximity carries the
     * grouping: what belongs together sits close, and the gap before a new group is bigger than
     * any gap inside one. */
    --s1: 4px;
    --s2: 8px;
    --s3: 12px;
    --s4: 16px;
    --s5: 24px;
    --s6: 36px;
    --s7: 48px;
    /* The one gap that answers to the viewport: 80px of separation is generous on a tall
     * window and most of a short one. */
    --s8: clamp(48px, 8vh, 80px);

    --margin: 12px;
    --sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
    --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

    color-scheme: light dark;
}

/* On black a charcoal-tinted shadow disappears; the dark set is the same recipe, deepened. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) {
        --shadow-chip: 0 2px 4px rgba(0, 0, 0, 0.55);
        --drop: drop-shadow(0 3px 8px rgba(0, 0, 0, 0.6));
        --drop-lift: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.7));
        --shadow-swap: 0 3px 8px rgba(0, 0, 0, 0.6);
        --shadow-disc: 0 6px 14px rgba(0, 0, 0, 0.65);
        --shadow-card: 0 2px 6px rgba(0, 0, 0, 0.5);
        --shadow-panel: 0 18px 48px rgba(0, 0, 0, 0.7);
    }
}

:root[data-theme='dark'] {
    --shadow-chip: 0 2px 4px rgba(0, 0, 0, 0.55);
    --drop: drop-shadow(0 3px 8px rgba(0, 0, 0, 0.6));
    --drop-lift: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.7));
    --shadow-swap: 0 3px 8px rgba(0, 0, 0, 0.6);
    --shadow-disc: 0 6px 14px rgba(0, 0, 0, 0.65);
    --shadow-card: 0 2px 6px rgba(0, 0, 0, 0.5);
    --shadow-panel: 0 18px 48px rgba(0, 0, 0, 0.7);
}

/* ── Base ────────────────────────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

/* A class that sets `display` outranks the UA's [hidden] rule, so say it louder. */
[hidden] { display: none !important; }

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

body {
    margin: 0;
    background: var(--canvas);
    color: var(--ink);
    font-family: var(--sans);
    font-size: 15px;
    line-height: 21px;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

h1, h2, h3, p { margin: 0; }

a { color: inherit; }

button { font: inherit; color: inherit; border: 0; background: none; cursor: pointer; }

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 6px;
}

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

.wrap {
    max-width: 1120px;
    margin: 0 auto;
    padding: 0 24px;
}

/* ── The page ────────────────────────────────────────────────────────────────────────────────
 * One screen. The converter is not a panel on a page — it IS the page, which is exactly what it
 * is in the app, so nothing here draws a container around it. */

body {
    min-height: 100svh;
    display: grid;
    /* An implicit `auto` column grows to the widest child and takes the page with it. Pinning
     * one `minmax(0, 1fr)` column is what keeps the strip scrolling instead of the page. */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr auto;
    gap: 24px;
    padding: 20px clamp(16px, 4vw, 32px) 24px;
}

.top {
    display: flex;
    justify-content: flex-end;
}

/* A lifted 44px disc that presses at 48 — DiscButton. */
.disc {
    width: 48px;
    height: 48px;
    display: grid;
    place-items: center;
    border-radius: 999px;
    flex: none;
    padding: 0;
}

.disc > span {
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--card);
    box-shadow: var(--shadow-disc);
}

.disc svg { width: 20px; height: 20px; }

/* ── The stage ───────────────────────────────────────────────────────────────────────────── */

.stage {
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 0;
    padding: var(--s2) 0;
}

/* ── The converter ───────────────────────────────────────────────────────────────────────── */

.converter {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
    min-width: 0;
    max-width: 640px;
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    min-height: 48px;
    padding: 0 12px;
    border-radius: 999px;
    transition: background-color 160ms ease;
}

.chip:hover { background: var(--raised); }

.chip__code {
    font-weight: 700;
    font-size: 21px;
    line-height: 26px;
    letter-spacing: -0.2px;
}

/* The accent is on the answer, the target code, the caret and selected state. Nowhere else. */
.chip--answer .chip__code { color: var(--accent); }

.chip__chevron { color: var(--muted); display: grid; place-items: center; }

.flag {
    display: inline-grid;
    place-items: center;
    border-radius: 999px;
    background: var(--raised);
    box-shadow: inset 0 0 0 0.5px color-mix(in srgb, var(--line) 80%, transparent);
    overflow: hidden;
    flex: none;
}

.flag img { display: block; border-radius: 999px; }

.flag--text { font-family: var(--sans); font-weight: 500; color: var(--muted); }

/* The two figure rows. Mono, tabular by construction, so a changing digit cannot shift its
 * neighbours whatever a platform does with `tnum`.
 *
 * Three tracks, `1fr auto 1fr`, are the app's BalancedSymbol: the **digits** land on the centre
 * line and the currency symbol hangs off to their left, instead of symbol-plus-digits being
 * centred as one lump — which reads as a figure nudged to the right. */
.line {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    /* Centred, not baseline. The app sets one symbol size for both rows and centres it against
     * the figure; on a baseline a glyph a third the height just sinks into the bottom corner. */
    align-items: center;
    width: 100%;
    max-width: 100%;
    font-family: var(--mono);
    font-feature-settings: 'tnum';
    white-space: nowrap;
}

.sym {
    color: var(--muted);
    padding-right: 8px;
    justify-self: end;
    font-size: clamp(16px, 2.8vw, 30px);
    line-height: 1;
}

.line > .value { justify-self: start; min-width: 0; }

.entry { margin-top: var(--s2); }

/* The typed figure is drawn twice: a real input that owns the caret, the selection and every
 * editing behaviour, and a layer of glyphs stacked exactly on top of it that can roll the way
 * the answer does. A native input cannot hold per-glyph spans, which is the whole reason for it. */
.field { position: relative; display: inline-block; }

.ghost {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events: none;
    white-space: pre;
    /* Above the input, so a selection sits behind the glyphs instead of hiding them, and the
     * accent caret still shows through the gaps between characters. */
    --cell: 1.2em;
}

/* One set of metrics for all three: the input, the glyph layer drawn over it, and the mirror
 * that measures the width. If any of them drifts, the drawn figure slides off the caret. */
.entry .value,
.mirror {
    font-family: var(--mono);
    font-feature-settings: 'tnum';
    font-size: clamp(28px, 5vw, 48px);
    line-height: 1.2;
    font-weight: 500;
    letter-spacing: -0.01em;
}

input.value {
    /* The text is drawn by .ghost; the input keeps the caret, the selection and the editing. */
    color: transparent;
    background: none;
    border: 0;
    padding: 0;
    outline: none;
    caret-color: var(--accent);
    text-align: left;
    min-width: 24px;
}

input.value::selection { background: var(--accent-soft); }

input.value:focus-visible { outline: none; }

/* Measures the text so the field is exactly as wide as its contents and stays centred. */
.mirror {
    position: absolute;
    visibility: hidden;
    white-space: pre;
}

.evaluated {
    margin: 6px 0 0;
    font-family: var(--mono);
    font-size: 14px;
    line-height: 18px;
    color: var(--muted);
}

.answer { margin-top: var(--s2); overflow: hidden; }

.answer .value {
    font-size: clamp(46px, 10vw, 88px);
    line-height: 1.1;
    font-weight: 500;
    letter-spacing: -0.025em;
    color: var(--accent);
    transform-origin: left center;
    display: inline-block;
}

/* The seam: a lifted disc that half-turns per actual pair flip, counting turns so it always
 * turns the same way. */
.seam { padding: var(--s3) 0; }

.seambox {
    width: 48px;
    height: 48px;
    display: grid;
    place-items: center;
    padding: 0;
    border-radius: 999px;
}

.swap {
    width: 40px;
    height: 40px;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--raised);
    box-shadow: var(--shadow-swap);
    transition: transform 320ms cubic-bezier(0.4, 0, 0.2, 1);
}

.swap svg { width: 18px; height: 18px; }

.seambox:active .swap { scale: 0.92; }

.hint {
    margin: var(--s4) 0 0;
    min-height: 20px;
    font-size: 13px;
    line-height: 20px;
    color: var(--accent);
}

/* ── Store links ─────────────────────────────────────────────────────────────────────────── */

.stores { display: flex; flex-wrap: wrap; gap: var(--s4); justify-content: center; margin-top: var(--s8); }

/* The artwork is used unaltered, so the only thing this styles is the box around it. Apple asks
 * for clear space of at least a quarter of the badge height on every side; the 16px gap and the
 * space above and below both clear that at 52px. */
.badge { display: block; line-height: 0; border-radius: 8px; }

.badge img {
    height: 52px;
    width: auto;
    display: block;
    filter: var(--drop);
    transition: filter 200ms ease, transform 200ms ease;
}

/* Google's lockup is a wider ratio (3.37 against Apple's 3.0), so at a matched height it reads
 * as the larger of the two. Scaling it to 94% brings the pair within 9px of each other and the
 * 3px height difference is invisible. Uniform scaling is all this is: both sets of brand
 * guidelines allow it, and forbid only recolouring, stretching or adding to the artwork. */
.badge[data-store='playStore'] img { height: 49px; }

/* Every one of these has to outrank `.badge img` above, which sets display, so each carries the
 * element selector too. Two class names alone would lose to a class plus an element. */
.badge img.badge__dark { display: none; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) .badge img.badge__light { display: none; }
    :root:not([data-theme='light']) .badge img.badge__dark { display: block; }
}

:root[data-theme='dark'] .badge img.badge__light { display: none; }

:root[data-theme='dark'] .badge img.badge__dark { display: block; }

/* Only a link when there is somewhere to go; until then the badges are simply artwork. */
a.badge[href]:hover img { filter: var(--drop-lift); transform: translateY(-2px); }

a.badge[href]:active img { transform: translateY(0) scale(0.98); }

/* Said beside the badges rather than stamped on them, because the lockups may not be altered. */
.storenote {
    margin: var(--s4) 0 0;
    font-size: 13px;
    line-height: 20px;
    color: var(--muted);
}

/* ── Footer ──────────────────────────────────────────────────────────────────────────────── */

.bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 20px;
    font-size: 13px;
    line-height: 20px;
    color: var(--muted);
}

.bottom nav { display: flex; flex-wrap: wrap; gap: 8px 20px; }

.bottom a { text-decoration: none; }

.bottom a:hover { color: var(--ink); text-decoration: underline; }

/* Freshness is reported, never actioned: there is no refresh here and there must not be. */
.note--stale { color: var(--accent); }

/* ── Picker ──────────────────────────────────────────────────────────────────────────────── */

.sheet {
    position: fixed;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    background: color-mix(in srgb, #000 55%, transparent);
    backdrop-filter: blur(3px);
    animation: fade 200ms ease;
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.sheet__inner {
    display: flex;
    flex-direction: column;
    width: min(520px, 100%);
    height: min(86svh, 760px);
    /* A `card` panel, not `canvas`: on the true-black ground a #000 sheet has no edge at all. */
    background: var(--card);
    border-radius: 28px 28px 0 0;
    padding: 20px 12px 0;
    box-shadow: var(--shadow-panel);
    animation: rise 260ms cubic-bezier(0.2, 0, 0, 1);
}

@media (min-width: 560px) {
    .sheet { align-items: center; }
    .sheet__inner { border-radius: 28px; padding-bottom: 8px; }
}

@keyframes rise { from { transform: translateY(6%); opacity: 0; } to { transform: none; opacity: 1; } }

.sheet__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 4px 16px; }

.sheet__title { font-size: 24px; line-height: 30px; font-weight: 700; letter-spacing: -0.2px; margin: 0; }

.search {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 13px 16px;
    border-radius: 999px;
    background: var(--raised);
    color: var(--muted);
}

.search svg { width: 18px; height: 18px; flex: none; }

.search input {
    flex: 1;
    min-width: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--ink);
    outline: none;
}

.list {
    flex: 1;
    overflow-y: auto;
    padding: 8px 0 20px;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--line) transparent;
}

/* The default bar is a slab over the sheet's rounded corner; this one keeps out of the way. */
.list::-webkit-scrollbar { width: 8px; }

.list::-webkit-scrollbar-track { background: transparent; }

.list::-webkit-scrollbar-thumb { background: var(--line); border-radius: 999px; }

.list::-webkit-scrollbar-thumb:hover { background: var(--muted); }

/* Not sticky. A pinned header sitting over a half-scrolled row is what made the list look
 * broken, and with the arrow keys driving the list there is nothing for it to earn. */
.group {
    margin: 0;
    padding: 18px 12px 8px;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    letter-spacing: 0.6px;
    color: var(--muted);
}

.group:first-child { padding-top: 4px; }

.row {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 52px;
    padding: 0 12px;
    border-radius: 14px;
    text-align: left;
}

.row--selected, .row--selected:hover { background: var(--accent-soft); }

.row--selected .row__code { color: var(--accent); }

.row__text { display: flex; flex-direction: column; }

.row__code { font-weight: 700; font-size: 15px; line-height: 20px; }

.row__name { font-size: 13px; line-height: 18px; color: var(--muted); }

.nomatch { padding-top: 32px; text-align: center; color: var(--muted); }

/* ── Legal pages ─────────────────────────────────────────────────────────────────────────── */

body.page { display: block; padding: 0; }

.wrap { max-width: 1120px; margin: 0 auto; padding: 0 clamp(16px, 4vw, 32px); }

.nav { display: flex; align-items: center; gap: 10px; padding: 20px 0 8px; }

.nav__icon { width: 26px; height: 26px; border-radius: 7px; display: block; }

.nav__name { font-weight: 700; font-size: 15px; letter-spacing: -0.2px; }

.doc { max-width: 68ch; padding: 24px 0 96px; }

.doc h1 { font-size: clamp(32px, 5vw, 46px); letter-spacing: -1.4px; margin-bottom: 12px; }

.doc h2 { font-size: 20px; line-height: 26px; font-weight: 700; letter-spacing: -0.2px; margin: 40px 0 10px; }

.doc p, .doc li { font-size: 16px; line-height: 26px; color: var(--ink); }

.doc ul { padding-left: 20px; margin: 10px 0; }

.doc li { margin-bottom: 6px; }

.doc code { font-family: var(--mono); font-size: 0.92em; }

.doc .updated { color: var(--muted); font-size: 14px; margin-bottom: 8px; }

.doc .draft {
    margin: 24px 0 8px;
    padding: 16px 18px;
    border-radius: 14px;
    background: var(--accent-soft);
    color: var(--ink);
    font-size: 15px;
    line-height: 23px;
}

.footer { padding: 40px 0 56px; font-size: 13px; line-height: 22px; color: var(--muted); }

.footer nav { display: flex; flex-wrap: wrap; gap: 8px 20px; margin-bottom: 12px; padding: 0; }

.footer a { text-decoration: none; }

.footer a:hover { color: var(--ink); text-decoration: underline; }

/* ── Reduce Motion ───────────────────────────────────────────────────────────────────────── */
/* The substitution both platforms ask for. Shortening a transition is still a transition. */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
    }
}

/* ── Theme toggle ────────────────────────────────────────────────────────────────────────── */
/* Three states, three real Hugeicons: a phone for System, the sun for Light, the moon for Dark.
 * The glyph turns as it is replaced, which is the only thing on this page that moves without
 * the numbers moving. */

#themeIcon {
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--card);
    box-shadow: var(--shadow-disc);
    color: var(--ink);
}

#themeIcon svg { width: 20px; height: 20px; }

#themeIcon.spin svg { animation: turnIn 320ms cubic-bezier(0.2, 0, 0, 1); }

@keyframes turnIn {
    from { transform: rotate(-90deg) scale(0.6); opacity: 0; }
    to { transform: none; opacity: 1; }
}

/* ── Odometer ────────────────────────────────────────────────────────────────────────────── */
/* Only glyphs that actually change move. The cell is the row's own line height, so a reel
 * travels exactly one line and the figure never grows a scrollbar. */

.answer .value { --cell: 1.1em; }

.glyph {
    display: inline-block;
    overflow: hidden;
    height: var(--cell);
    line-height: var(--cell);
    vertical-align: top;
}

.glyph--enter { animation: nudge 200ms ease-out both; }

@keyframes nudge { from { opacity: 0; transform: translateY(-0.33em); } to { opacity: 1; transform: none; } }

.reel { display: block; animation: roll cubic-bezier(0.2, 0, 0, 1) both; }

.reel > span { display: block; height: var(--cell); line-height: var(--cell); }

@keyframes roll { from { transform: translateY(0); } to { transform: translateY(calc(var(--steps) * var(--cell) * -1)); } }

/* The answer is a control now — it copies — so it may say so under the pointer. */
.answer .value { cursor: pointer; transition: opacity 160ms ease; }

.answer .value:hover { opacity: 0.82; }

/* ── Favourites ──────────────────────────────────────────────────────────────────────────── */
/* Outlined chips showing what one unit of the source buys. No header line: the app cut it
 * because its two fragments tugged at opposite ends of one row. */

.favorites {
    display: flex;
    gap: var(--s2);
    margin-top: var(--s6);
    width: 100%;
    padding: 0 12px;
    overflow-x: auto;
    scroll-behavior: smooth;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    justify-content: safe center;
    mask-image: linear-gradient(90deg, transparent, #000 28px, #000 calc(100% - 28px), transparent);
}

.favorites::-webkit-scrollbar { display: none; }

/* Painted at 38, pressed at 48 — the chip clears the touch minimum while the strip stays light. */
.fav__slot { display: grid; place-items: center; height: 48px; flex: none; }

.fav {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 38px;
    padding: 0 14px 0 12px;
    border-radius: 999px;
    background: var(--card);
    box-shadow: var(--shadow-chip);
    white-space: nowrap;
    transition: background-color 220ms ease, color 220ms ease, transform 140ms ease;
}

button.fav:hover { transform: translateY(-1px); }

button.fav:active { transform: scale(0.96); }

.fav--target { background: var(--accent-soft); }

.fav--target .fav__code { color: var(--accent); }

.fav__code {
    font-weight: 500;
    font-size: 12px;
    line-height: 16px;
    letter-spacing: 0.6px;
    color: var(--muted);
    transition: color 220ms ease;
}

.fav__rate {
    font-family: var(--mono);
    font-feature-settings: 'tnum';
    font-size: 15px;
    line-height: 18px;
    margin-left: 4px;
    color: var(--ink);
}

.fav--add { width: 38px; height: 38px; padding: 0; justify-content: center; color: var(--muted); }

.fav--add svg { width: 18px; height: 18px; }

/* ── Shortcuts ───────────────────────────────────────────────────────────────────────────── */
/* Shown, not hidden behind a help panel: this page is driven from the keyboard, so the keys are
 * part of the interface. A touch device has no physical keyboard, so it never sees them. */

.keys {
    display: none;
    margin-top: var(--s1);
    flex-wrap: wrap;
    justify-content: center;
    gap: 6px 16px;
    padding: 0;
    list-style: none;
    font-size: 12px;
    line-height: 20px;
    color: var(--muted);
}

@media (pointer: fine) {
    .keys { display: flex; }
}

.keys li { display: inline-flex; align-items: center; gap: 4px; }

.keys li kbd + kbd { margin-right: 2px; }

kbd {
    display: inline-grid;
    place-items: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border-radius: 6px;
    background: var(--raised);
    color: var(--ink);
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 500;
    line-height: 1;
}

/* ── Strip arrows ────────────────────────────────────────────────────────────────────────── */

.favwrap { position: relative; width: 100%; display: flex; align-items: center; }

.scroller {
    position: absolute;
    z-index: 2;
    top: 50%;
    width: 30px;
    height: 30px;
    margin-top: -15px;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--card);
    box-shadow: var(--shadow-chip);
    color: var(--muted);
    transition: color 160ms ease, transform 160ms ease;
}

.scroller:hover { color: var(--ink); transform: scale(1.06); }

.scroller svg { width: 16px; height: 16px; }

.scroller--left { left: 0; }

.scroller--right { right: 0; }

/* A finger swipes; only a pointing device needs these. */
@media (pointer: coarse) {
    .scroller { display: none !important; }
}

/* ── Picker rows ─────────────────────────────────────────────────────────────────────────── */

.rowwrap { display: flex; align-items: center; border-radius: 14px; scroll-margin: 8px; }

.rowwrap:hover { background: var(--raised); }

/* Where the arrow keys are. The search field keeps focus, so this ring is the only thing
 * telling you what Enter would pick. */
/* An outline, not an inset shadow: a shadow paints under the wrap's children, so the row button
 * covered everything but the strip beside the star and the ring came out as a broken fragment.
 * An outline draws above children, and the negative offset keeps it inside the rounded corner. */
.rowwrap--active {
    background: var(--raised);
    outline: 2px solid var(--accent);
    outline-offset: -2px;
    border-radius: 14px;
}

.rowwrap--selected, .rowwrap--selected:hover { background: var(--accent-soft); }

.rowwrap--selected .row__code { color: var(--accent); }

.row__star {
    width: 48px;
    height: 48px;
    display: grid;
    place-items: center;
    color: var(--muted);
    flex: none;
    fill: none;
    transition: color 180ms ease, transform 180ms ease;
}

.row__star svg { width: 18px; height: 18px; }

.row__star:hover { transform: scale(1.08); }

.row__star--on { color: var(--accent); fill: currentColor; }

/* ── The launch ──────────────────────────────────────────────────────────────────────────── */
/* The app icon coming apart: hold, dip each arrow against its direction, then launch it the way
 * it points. Stroke 2.2 — the icon's weight, not the UI's 1.5, because the mark is the icon here
 * and a thinner line reads as the glyph shrinking. */

.launch {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: grid;
    place-items: center;
    background: var(--canvas);
    pointer-events: none;
}

.launch svg {
    width: 96px;
    height: 96px;
    fill: none;
    stroke-width: 2.2;
}

.launch__up { stroke: var(--ink); }

.launch__down { stroke: var(--accent); }

.launch--go { animation: veil 280ms ease 460ms both; }

.launch--go .launch__up { animation: flyUp 500ms both; }

.launch--go .launch__down { animation: flyDown 500ms both; }

/* The dip is against the direction of travel, so the launch has something to launch from. */
@keyframes flyUp {
    0%, 24% { transform: none; }
    52% { transform: translateY(10px); animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45); }
    100% { transform: translateY(-190px); opacity: 0; }
}

@keyframes flyDown {
    0%, 24% { transform: none; }
    52% { transform: translateY(-10px); animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45); }
    100% { transform: translateY(190px); opacity: 0; }
}

@keyframes veil { to { opacity: 0; } }

/* Reduce Motion gets the cross-fade both platforms ask for. Shortening a flight is still a flight. */
.launch--fade { animation: veil 260ms ease both; }

@media (prefers-reduced-motion: reduce) {
    .launch--fade { animation-duration: 260ms !important; }
}

/* ── Pair pages ──────────────────────────────────────────────────────────────────────────────
 * The prose under the converter on a generated page (/usd-to-jpy/). It carries the rate as
 * words because that is the part a crawler reads — the converter above it is script — and the
 * cross-links are what let one page's ranking reach the other 671.
 *
 * It borrows .doc's scale rather than inventing one: same measure, same heading sizes. */
.pairinfo {
    max-width: 68ch;
    margin: 0 auto;
    padding: 8px 0 72px;
    border-top: 1px solid var(--line);
}

.pairinfo h1 {
    font-size: clamp(24px, 4vw, 34px);
    line-height: 1.15;
    letter-spacing: -1px;
    margin: 32px 0 12px;
}

.pairinfo h2 {
    font-size: 18px;
    line-height: 24px;
    font-weight: 700;
    letter-spacing: -0.2px;
    margin: 32px 0 8px;
}

.pairinfo p, .pairinfo li { font-size: 16px; line-height: 26px; color: var(--ink); }

.pairinfo p { margin-bottom: 10px; }

/* The answer, said once in words. Mono for the figures, as everywhere else in the app. */
.pairinfo__lede { font-size: 18px; line-height: 28px; }

.pairinfo strong { font-family: var(--mono); font-weight: 600; }

.pairinfo a { color: var(--ink); text-decoration-color: var(--line); text-underline-offset: 3px; }

.pairinfo a:hover { text-decoration-color: var(--accent); }

.pairinfo__muted { color: var(--muted); }

/* Two columns of links on anything wider than a phone; the lists are short and scannable. */
.pairinfo__links {
    list-style: none;
    padding: 0;
    margin: 8px 0 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 2px 24px;
}

.pairinfo__links li { padding: 6px 0; border-bottom: 1px solid var(--line); }

/* ── Keypad (touch only) ─────────────────────────────────────────────────────────────────── */

/* Hidden by default and shown only on a coarse pointer. A desktop already has a keypad — the
   one on the keyboard — and these pads would be a slower version of it, which is why the app's
   own keypad has no desktop equivalent either. `hidden` on the element is the fallback for a
   browser that reports no pointer at all. */
.pad { display: none; }

@media (pointer: coarse) {
    .pad {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        /* `.stage` centres its children, so without this the grid shrinks to its own content and
           the keypad sits narrower than the favourites strip above it. `.favwrap` states the same
           thing for the same reason. */
        width: 100%;
        /* One 10dp gap everywhere, as on the app: a narrower rail behind a gutter was tried
           there and rejected, and the web has no reason to disagree with it. */
        gap: 10px;
        margin: 14px 0 4px;
    }

    .pad__key {
        /* No height of its own on a phone: the grid rows above decide, so the pads divide the
           space that is actually left instead of adding up to more than the screen. */
        height: auto;
        min-height: 0;
        border: 0;
        border-radius: 16px;
        background: var(--card);
        color: var(--ink);
        font-family: var(--mono);
        font-size: 22px;
        font-weight: 700;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
        /* A pad is pressed, not selected: a long press that selects "7" is a bug on a keypad. */
        user-select: none;
        -webkit-user-select: none;
        touch-action: manipulation;
        transition: transform .09s ease-out, background-color .09s ease-out;
    }

    /* Operators and utilities sit on `raised`, digits on `card` — the app's own division, so the
       fourth column reads as a rail without being drawn as one. */
    .pad__key--op,
    .pad__key--util { background: var(--raised); }

    .pad__key--equals { background: var(--accent); color: var(--on-accent); }

    /* The app's answer to a press: the pad flips to ink and shrinks. No ripple — that would be a
       second answer to one touch. */
    .pad__key:active {
        transform: scale(.94);
        background: var(--ink);
        color: var(--canvas);
    }

    .pad__key--equals:active { background: var(--accent); color: var(--on-accent); }

    /* The keyboard-shortcut hints are for the keyboard that is not here. */
    .keys { display: none; }
}

@media (pointer: coarse) and (prefers-reduced-motion: reduce) {
    .pad__key { transition: none; }
    .pad__key:active { transform: none; }
}

/* ── Store banner ────────────────────────────────────────────────────────────────────────── */

/* On a desktop the badges are just a block in the column, as they always were, and there is
   nothing to dismiss. */
.promo { position: relative; }
.promo__close { display: none; }

@media (pointer: coarse) {
    /* The whole point of the phone layout: one screen, no scroll, the way the app is. `dvh`
       rather than `vh` because mobile browsers shrink the viewport as their chrome retracts, and
       `vh` measures the tall version — which is exactly the amount that would not fit. */
    html { height: 100%; }

    /* The height belongs to the page, not the converter. `body` is an `auto 1fr auto` grid —
       header, stage, footer — so putting 100dvh on the stage made the middle row a full screen
       tall *on top of* the other two rows, the gaps and the padding, and overflowed by exactly
       their sum. Sizing the grid and letting `1fr` hand the stage the remainder is the fix.
       `dvh`, not `vh`: a mobile browser's chrome retracts, and `vh` measures the tall version. */
    body {
        height: 100dvh;
        min-height: 0;
        gap: 8px;
        /* `--promo-h` is set by the script from the banner's measured height, and is 0 once it is
           dismissed — which is how the keypad takes that space back rather than leaving a hole. */
        padding: 8px 12px calc(max(6px, env(safe-area-inset-bottom)) + var(--promo-h, 0px));
        overflow: hidden;
    }

    .stage {
        min-height: 0;
        justify-content: flex-start;
        padding: 0;
        overflow: hidden;
    }

    /* The chain that lets the keypad grow. `.pad` is inside `.converter`, not directly in the
       stage, so `flex: 1` on the pad was measured against a box that sizes to its own content —
       which is why the keypad stopped at its natural height and left dead space under it. Every
       ancestor between the viewport and the pad has to pass the height down, and `min-height: 0`
       is what stops a flex item refusing to shrink below its content. */
    .stage > .converter {
        flex: 1 1 auto;
        min-height: 0;
        justify-content: flex-start;
    }

    /* The keypad takes whatever is left rather than asserting a height — `Modifier.weight(1f)` in
       the app, and the reason it fits screens nobody measured. `minmax(48px, 1fr)` keeps the
       touch minimum as the floor while letting the rows shrink to whatever the screen allows. */
    .pad {
        flex: 1 1 auto;
        min-height: 0;
        /* Five equal rows dividing whatever height the pad ends up with — no floor. A floor
           fights the flex height instead of cooperating with it: rows insisting on 48px in a
           204px box simply overflow, which is how the keys ended up at 33px *and* clipped. The
           app has the same behaviour, its pads shrink on a short screen rather than the answer
           sliding off the top. */
        grid-template-rows: repeat(5, 1fr);
    }

    /* A reserved line for messages, empty until something is copied — and 40px of nothing under
       the keypad until then, which on one screen is 40px the pads should have had. */
    .hint:empty { display: none; }

    /* Never below the touch minimum. This project's rule is 48dp everywhere and a keypad is the
       one place it is pressed hardest; 44 is iOS's floor and the concession made here so that a
       short screen loses a little pad height rather than the whole rule. Below this the page
       scrolls, which is the lesser failure. */
    .pad__key { min-height: 44px; }

    /* The strip is the app's 38dp chip in a 48dp row, not the desktop's. It was 84px tall on a
       phone — most of a keypad row, spent on something read at a glance. */
    .favwrap { flex: 0 0 auto; }

    .favorites { padding-top: 4px; padding-bottom: 4px; }

    .fav {
        min-height: 38px;
        padding-top: 6px;
        padding-bottom: 6px;
    }

    /* The seam holds the swap disc; the app gives it 8dp of air either side, not the desktop's. */
    .seam { margin: 2px 0; }

    /* Out of the column and over it, so the converter keeps the full height whether or not
       anybody dismisses this. */
    .promo {
        position: fixed;
        left: 12px;
        right: 12px;
        bottom: max(12px, env(safe-area-inset-bottom));
        z-index: 40;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: center;
        gap: 10px;
        padding: 14px 44px 14px 14px;
        border-radius: 18px;
        background: var(--card);
        box-shadow: 0 6px 28px rgba(0, 0, 0, .22);
    }

    .promo[hidden] { display: none; }

    .promo .stores { margin-top: 0; }

    .promo .badge img { height: 44px; }

    /* Hidden by the platform check, not by CSS — the element is still in the DOM so a
       desktop keeps both. */
    .promo .badge[hidden] { display: none; }

    /* The storenote is the "coming soon" line; the badges say the same thing more briefly. */
    .promo .storenote { display: none; }

    .promo__close {
        display: grid;
        place-items: center;
        position: absolute;
        top: 6px;
        right: 6px;
        width: 32px;
        height: 32px;
        border: 0;
        border-radius: 50%;
        background: var(--raised);
        color: var(--ink);
        font-size: 18px;
        line-height: 1;
        cursor: pointer;
    }

    /* One line under the keypad, the way the app puts the date under its own. The links stay —
       a policy has to be reachable — but they stop being a paragraph. */
    .bottom {
        justify-content: center;
        gap: 4px 14px;
        font-size: 11px;
        line-height: 16px;
        padding-bottom: 4px;
    }
}
