/* ==========================================================================
   iHateFiles — visual system

   A warm, light interface. The palette is built from a coral-to-amber
   primary (energetic, warm, reads as "act now"), balanced by a cool
   periwinkle and a teal so the three tool families are told apart by hue
   alone. Everything sits on a warm off-white rather than pure white, and
   shadows are tinted with the brand hue instead of black, which is what
   keeps the page feeling soft rather than printed.
   ========================================================================== */

:root {
  /* --- surfaces: warm neutrals, never clinical grey --- */
  /*
   * Cream rather than off-white. The page reads warmer, and white cards on
   * it now read as cards — on the old near-white ground a white panel was
   * distinguishable only by its shadow.
   *
   * The depth of the cream is not free: --ink-4 and --brand-deep are both
   * within a tenth of failing AA against the darkest of these, so the family
   * is kept airy deliberately. A heavier beige would mean darkening the type
   * to compensate, which is the opposite of what a cream ground is for.
   */
  --bg: #fdf9f3;
  --bg-deep: #f9f2e8;
  --cream: #fbf3ea;
  --ink-deep: #17120f;
  --ink-deep-2: #241c17;
  --surface: #ffffff;
  --surface-2: #fbf6ee;
  --surface-3: #f5eee3;
  --line: #f1e9dc;
  --line-2: #e8ddcd;

  /* --- ink: warm near-black, never pure black --- */
  --ink: #1c1917;
  /*
   * The three secondary tiers were #5b524c / #92867e / #b5a89f, measuring
   * 6.5, 3.4 and 2.2 against the page. Two of the three failed AA for small
   * text, and the faintest carried the labels that name controls — "FRAME
   * RATE", "I HAVE" — which is the worst place to be unreadable.
   *
   * The scale is shifted rather than squashed: darkening only the faint end
   * would have collapsed it into the tier above, since the lightest passing
   * value for both is the same colour. Each tier keeps roughly its old
   * relationship to the others, and the faintest now clears 4.5:1.
   */
  --ink-2: #443c37;
  --ink-3: #5c534d;
  --ink-4: #736a63;

  /* --- brand: coral → amber, the warmest part of the wheel --- */
  --brand: #f4553c;
  --brand-2: #ff8a3d;
  /* Text, not fill. #d93d26 is 4.1:1 on the tinted soft background it is
     most often used against, which is under the line for the small links
     that use it; this clears it on every surface. */
  --brand-deep: #c3331d;
  --brand-soft: #fff1ec;
  --brand-softer: #fff8f5;
  /*
   * The fill used for large brand-coloured surfaces — the hero, primary
   * buttons, selected chips — as distinct from --brand, which is tuned for
   * small coloured text. The two cannot be the same value: the coral that
   * reads well as a word on the page is far too light to put white text on,
   * measuring 2.7:1, while this is 4.5:1.
   */
  --fill-brand: #d93d26;
  --brand-grad: var(--fill-brand);

  /* --- family hues: warm, cool, cool-green --- */
  --image: #f4553c;
  --image-2: #ff8a3d;
  --image-soft: #fff0eb;
  --video: #6d5efc;
  --video-2: #a78bfa;
  --video-deep: #5344d8;
  --video-soft: #f0eeff;
  --video-softer: #f8f7ff;
  --audio: #0fa697;
  --audio-2: #34d1b6;
  --audio-deep: #0b8377;
  --audio-soft: #e6f7f4;
  --audio-softer: #f2fbf9;

  --ok: #0d9c74;
  --ok-soft: #e6f7f1;
  --err: #e0483f;
  --err-soft: #fdeeed;

  /*
   * --- space ---
   * A 2px-based scale, loosening as the values grow. Before this the
   * stylesheet held seventy-six distinct spacing values, including .38rem,
   * .45rem, .55rem and .65rem within a few lines of each other. Nobody can
   * see one of those; everybody can see that things do not line up.
   */
  --s-1: .125rem;   /*  2px */
  --s-2: .25rem;    /*  4px */
  --s-3: .375rem;   /*  6px */
  --s-4: .5rem;     /*  8px */
  --s-5: .625rem;   /* 10px */
  --s-6: .75rem;    /* 12px */
  --s-7: .875rem;   /* 14px */
  --s-8: 1rem;      /* 16px */
  --s-10: 1.25rem;  /* 20px */
  --s-12: 1.5rem;   /* 24px */
  --s-14: 1.75rem;  /* 28px */
  --s-16: 2rem;     /* 32px */
  --s-20: 2.5rem;   /* 40px */
  --s-24: 3rem;     /* 48px */
  --s-28: 3.5rem;   /* 56px */
  --s-32: 4rem;     /* 64px */

  /*
   * --- type ---
   * Whole pixels, so glyphs land on the pixel grid and render the same way
   * everywhere. There were forty-five distinct sizes here, many separated by
   * a sixth of a pixel: not a scale, just drift.
   */
  --fs-1: .625rem;   /* 10px */
  --fs-2: .6875rem;  /* 11px */
  --fs-3: .75rem;    /* 12px */
  --fs-4: .8125rem;  /* 13px */
  --fs-5: .875rem;   /* 14px */
  --fs-6: .9375rem;  /* 15px */
  --fs-7: 1rem;      /* 16px */
  --fs-8: 1.125rem;  /* 18px */
  --fs-9: 1.25rem;   /* 20px */
  --fs-10: 1.5rem;   /* 24px */
  --fs-11: 1.875rem; /* 30px */

  /* --- geometry: corners come from here, not from taste --- */
  --r-xs: 6px;
  /* The small-control step: chips, segmented options, palette rows, type
     badges. Fifteen places were reaching for a bare 8px because the scale
     jumped 6 → 10 and neither was what they wanted. Naming it is honest
     about the step existing; pretending it does not just spreads the
     magic number around. */
  --r-chip: 8px;
  --r-2xs: 8px;
  --r-sm: 10px;
  --r: 14px;
  --r-md: 18px;
  --r-lg: 24px;
  --r-xl: 32px;
  --r-pill: 999px;

  /* --- warm-tinted elevation --- */
  --sh-1: 0 1px 2px rgb(80 45 30 / 5%), 0 2px 8px -2px rgb(80 45 30 / 6%);
  --sh-2: 0 2px 4px rgb(80 45 30 / 5%), 0 12px 28px -10px rgb(80 45 30 / 12%);
  --sh-3: 0 4px 8px rgb(80 45 30 / 6%), 0 24px 48px -16px rgb(80 45 30 / 18%);
  --sh-brand: 0 4px 12px -2px rgb(244 85 60 / 28%), 0 16px 32px -12px rgb(244 85 60 / 32%);

  /* --- motion: one easing family, so everything feels related --- */
  --ease: cubic-bezier(.22, 1, .36, 1);
  --ease-soft: cubic-bezier(.4, 0, .2, 1);
  --t-fast: .18s;
  --t: .32s;
  --t-slow: .6s;

  --head-h: 72px;

  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI Variable Display",
    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Formats are technical objects, so they are set in the technical face.
     It is the single strongest identity cue on the site. */
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  color-scheme: light;
}

/*
 * Family accents. Any element carrying data-group or data-kind picks up the
 * right hue from here: tool cards, tool-page headers, tokens, pair links,
 * format cards, the wall.
 */
[data-group="image"], [data-kind="image"] { --accent: var(--image); --accent-2: var(--image-2); --accent-soft: var(--image-soft); --accent-fill: #d93d26; }
[data-group="video"], [data-kind="video"] { --accent: var(--video); --accent-2: var(--video-2); --accent-soft: var(--video-soft); --accent-fill: #4c3fc9; }
[data-group="audio"], [data-kind="audio"] { --accent: var(--audio); --accent-2: var(--audio-2); --accent-soft: var(--audio-soft); --accent-fill: #0a7a6f; }

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background-image: radial-gradient(circle at 1px 1px, rgb(120 70 45 / 8%) 1px, transparent 0);
  background-size: 24px 24px;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgb(0 0 0 / 35%) 55%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, rgb(0 0 0 / 35%) 55%, transparent 100%);
}

a { color: inherit; }
p { margin: 0; }
h1, h2, h3 { margin: 0; line-height: 1.15; letter-spacing: -0.032em; font-weight: 700; }
button, input, select { font: inherit; color: inherit; }

.container { width: min(1200px, 100% - 2.5rem); margin-inline: auto; }

/*
 * The `hidden` attribute, made to mean what it says.
 *
 * `hidden` works only through the user agent's `[hidden] { display: none }`,
 * which is the weakest rule in the cascade — any explicit `display` beats
 * it, and nearly every panel here is a grid or a flex container. So an
 * element could be correctly marked hidden in JavaScript and stay fully on
 * screen, which is how a tool with no settings still showed an empty
 * "Advanced" bar. Stating it once, with the weight to win, is the only form
 * of this rule that is actually reliable.
 */
[hidden] { display: none !important; }

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

.skip {
  position: absolute; left: -999px; top: 0; z-index: 60;
  /* White on coral is 3.4:1, which fails the very standard this link exists
     to serve. Near-black carries it in both palettes. */
  background: var(--ink-deep); color: #fff; padding: .75rem 1.25rem;
  box-shadow: 0 0 0 2px var(--brand);
  border-radius: 0 0 var(--r) 0; font-weight: 600;
}
.skip:focus { left: 0; }

:focus-visible {
  outline: 2.5px solid var(--brand);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

::selection { background: var(--brand-soft); color: var(--brand-deep); }

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

@keyframes fade-up {
  from { opacity: 0; transform: translateY(18px); }
  to { opacity: 1; transform: none; }
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes pop-in {
  0% { opacity: 0; transform: scale(.9); }
  60% { transform: scale(1.02); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes slide-in-left {
  from { opacity: 0; transform: translateX(-14px); }
  to { opacity: 1; transform: none; }
}
@keyframes drift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33% { transform: translate(4%, -5%) scale(1.08); }
  66% { transform: translate(-3%, 4%) scale(.95); }
}
@keyframes shimmer {
  from { transform: translateX(-100%); }
  to { transform: translateX(220%); }
}
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
@keyframes ring {
  0% { transform: scale(1); opacity: .55; }
  100% { transform: scale(1.7); opacity: 0; }
}
@keyframes spin { to { transform: rotate(360deg); } }

.reveal { opacity: 0; }
.reveal.is-visible { animation: fade-up var(--t-slow) var(--ease) both; }

#view > * { animation: fade-in var(--t) var(--ease-soft) both; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .reveal { opacity: 1; }
}


/* ==========================================================================
   Glass
   --------------------------------------------------------------------------
   One recipe, three budgets. `data-glass` on <html> is set once by glass.js:

     flat   — no backdrop work at all. Translucent fills only. This is what
              a device gets when it cannot composite a blur cheaply, or when
              the reader asked for reduced transparency.
     blur   — a backdrop blur plus edge lighting. Every phone lands here.
     liquid — the above plus a real refraction lens on a few surfaces.

   The rule that keeps this fast: a backdrop blur costs per pixel of area, per
   frame, whenever what is behind it moves. So glass is only ever applied to a
   handful of small or stationary surfaces — never to a card in a grid, never
   to a row in a list, never to anything that repeats.
   ========================================================================== */

:root {
  --glass-blur: 20px;
  --glass-blur-sm: 12px;
  --glass-sat: 165%;

  --glass-fill: rgb(255 255 255 / 68%);
  --glass-fill-strong: rgb(255 255 255 / 82%);
  --glass-edge: rgb(255 255 255 / 72%);

  /* Edge lighting. A bright top-left rim and a dimmer bottom bounce is what
     reads as thickness; without it a blurred panel just looks like fog. */
  --glass-lighting:
    inset 0 1px 0 0 rgb(255 255 255 / 90%),
    inset 1px 0 0 0 rgb(255 255 255 / 55%),
    inset -1px 0 0 0 rgb(255 255 255 / 40%),
    inset 0 -1px 0 0 rgb(255 255 255 / 30%),
    inset 0 -14px 20px -18px rgb(120 70 45 / 22%);

  /* The conic rim on .glass, pulled out so dark mode can dim it. */
  --rim-hot: rgb(255 255 255 / 95%);
  --rim-cool: rgb(255 255 255 / 25%);
  --rim-warm: rgb(255 255 255 / 70%);

  /*
   * Three idioms that were white literals in about twenty places each:
   * the lit top face of a raised control, the hairline of light along its
   * top edge, and the round knob on a slider or toggle. They are named
   * because they are the only things in the palette that do not simply
   * invert — on a dark ground the light still comes from above, so these
   * stay lighter than their surface rather than becoming darker than it.
   */
  --key-face: #ffffff;
  --raise: rgb(255 255 255 / 95%);
  --knob: #ffffff;

  --head-fill: rgb(253 249 243 / 90%);
  --head-edge: rgb(255 255 255 / 90%);
}

/*
 * --- dark ---
 *
 * Every colour above is a custom property, so this is a palette swap rather
 * than a second stylesheet: nothing below redefines a rule, only the values
 * the rules already read.
 *
 * Two things are deliberately not inverted. The dark bands — the stage, the
 * receipt, the demo — are built on --ink-deep, and here they go *darker*
 * than the page rather than lighter, so they stay separate surfaces instead
 * of collapsing into the background. And the brand coral is lifted, not
 * dimmed: #f4553c on near-black falls to about 4.2:1, which is under the
 * line for the small text it is used for, so the dark palette runs a
 * lighter coral that clears it.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  color-scheme: dark;

  --bg: #16110f;
  --bg-deep: #100c0a;
  --cream: #1e1714;
  --ink-deep: #0a0807;
  --ink-deep-2: #120e0c;
  --surface: #1f1917;
  --surface-2: #26201d;
  --surface-3: #302825;
  --line: #2d2522;
  --line-2: #3b322d;

  --ink: #f6f0ec;
  --ink-2: #d6ccc5;
  --ink-3: #b3a79f;
  --ink-4: #968a82;

  --brand: #ff7057;
  --brand-2: #ffa35c;
  --brand-deep: #ff8f76;
  --brand-soft: #2b1a15;
  --brand-softer: #211511;
  --fill-brand: #d93d26;
  --brand-grad: var(--fill-brand);

  --image: #ff7057;
  --image-2: #ffa35c;
  --image-soft: #2b1a15;
  --video: #9a8dff;
  --video-2: #c0b0ff;
  --video-deep: #b8abff;
  --video-soft: #201d38;
  --video-softer: #1a182c;
  --audio: #2fcdb8;
  --audio-2: #5ce8ce;
  --audio-deep: #5ce8ce;
  --audio-soft: #0f2b28;
  --audio-softer: #0c2120;

  --ok: #35c795;
  --ok-soft: #10291f;
  --err: #ff7268;
  --err-soft: #2e1614;

  /* Warm-tinted elevation does not survive here: a brown shadow on a brown
     ground is invisible, so these go black and considerably deeper. */
  --sh-1: 0 1px 2px rgb(0 0 0 / 40%), 0 2px 8px -2px rgb(0 0 0 / 34%);
  --sh-2: 0 2px 4px rgb(0 0 0 / 36%), 0 12px 28px -10px rgb(0 0 0 / 55%);
  --sh-3: 0 4px 8px rgb(0 0 0 / 38%), 0 24px 48px -16px rgb(0 0 0 / 66%);
  --sh-brand: 0 4px 12px -2px rgb(255 112 87 / 22%), 0 16px 32px -12px rgb(255 112 87 / 26%);

  --glass-fill: rgb(40 33 30 / 78%);
  --glass-fill-strong: rgb(46 38 34 / 90%);
  --glass-edge: rgb(255 255 255 / 12%);
  --glass-lighting:
    inset 0 1px 0 0 rgb(255 255 255 / 11%),
    inset 1px 0 0 0 rgb(255 255 255 / 6%),
    inset -1px 0 0 0 rgb(255 255 255 / 5%),
    inset 0 -1px 0 0 rgb(0 0 0 / 30%),
    inset 0 -14px 20px -18px rgb(0 0 0 / 45%);

  --rim-hot: rgb(255 255 255 / 26%);
  --rim-cool: rgb(255 255 255 / 7%);
  --rim-warm: rgb(255 255 255 / 16%);

  --key-face: #2c2421;
  --raise: rgb(255 255 255 / 9%);
  --knob: #efe7e2;

  --head-fill: rgb(22 17 15 / 90%);
  --head-edge: rgb(255 255 255 / 12%);
  }
}

/* The same values again for an explicit choice, so the toggle can override
   the system in both directions rather than only one. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #16110f;
  --bg-deep: #100c0a;
  --cream: #1e1714;
  --ink-deep: #0a0807;
  --ink-deep-2: #120e0c;
  --surface: #1f1917;
  --surface-2: #26201d;
  --surface-3: #302825;
  --line: #2d2522;
  --line-2: #3b322d;

  --ink: #f6f0ec;
  --ink-2: #d6ccc5;
  --ink-3: #b3a79f;
  --ink-4: #968a82;

  --brand: #ff7057;
  --brand-2: #ffa35c;
  --brand-deep: #ff8f76;
  --brand-soft: #2b1a15;
  --brand-softer: #211511;
  --fill-brand: #d93d26;
  --brand-grad: var(--fill-brand);

  --image: #ff7057;
  --image-2: #ffa35c;
  --image-soft: #2b1a15;
  --video: #9a8dff;
  --video-2: #c0b0ff;
  --video-deep: #b8abff;
  --video-soft: #201d38;
  --video-softer: #1a182c;
  --audio: #2fcdb8;
  --audio-2: #5ce8ce;
  --audio-deep: #5ce8ce;
  --audio-soft: #0f2b28;
  --audio-softer: #0c2120;

  --ok: #35c795;
  --ok-soft: #10291f;
  --err: #ff7268;
  --err-soft: #2e1614;

  /* Warm-tinted elevation does not survive here: a brown shadow on a brown
     ground is invisible, so these go black and considerably deeper. */
  --sh-1: 0 1px 2px rgb(0 0 0 / 40%), 0 2px 8px -2px rgb(0 0 0 / 34%);
  --sh-2: 0 2px 4px rgb(0 0 0 / 36%), 0 12px 28px -10px rgb(0 0 0 / 55%);
  --sh-3: 0 4px 8px rgb(0 0 0 / 38%), 0 24px 48px -16px rgb(0 0 0 / 66%);
  --sh-brand: 0 4px 12px -2px rgb(255 112 87 / 22%), 0 16px 32px -12px rgb(255 112 87 / 26%);

  --glass-fill: rgb(40 33 30 / 78%);
  --glass-fill-strong: rgb(46 38 34 / 90%);
  --glass-edge: rgb(255 255 255 / 12%);
  --glass-lighting:
    inset 0 1px 0 0 rgb(255 255 255 / 11%),
    inset 1px 0 0 0 rgb(255 255 255 / 6%),
    inset -1px 0 0 0 rgb(255 255 255 / 5%),
    inset 0 -1px 0 0 rgb(0 0 0 / 30%),
    inset 0 -14px 20px -18px rgb(0 0 0 / 45%);

  --rim-hot: rgb(255 255 255 / 26%);
  --rim-cool: rgb(255 255 255 / 7%);
  --rim-warm: rgb(255 255 255 / 16%);

  --key-face: #2c2421;
  --raise: rgb(255 255 255 / 9%);
  --knob: #efe7e2;

  --head-fill: rgb(22 17 15 / 90%);
  --head-edge: rgb(255 255 255 / 12%);
}

/* Phones: half the blur radius, and the aurora stops moving. */
@media (max-width: 720px) {
  :root { --glass-blur: 13px; --glass-blur-sm: 9px; --glass-sat: 145%; }
}

.glass {
  position: relative;
  background: var(--glass-fill);
  box-shadow: var(--glass-lighting), var(--sh-2);
  border: 0;
}

/* The specular rim: a sweep of light around the border only, brightest where
   a light at the upper left would catch the bevel. Drawn as a masked gradient
   ring, so it costs one small painted layer and nothing on the backdrop. */
.glass::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.25px;
  background: var(--glass-edge);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  z-index: 0;
}
.glass > * { position: relative; z-index: 1; }

[data-glass="blur"] .glass,
[data-glass="liquid"] .glass {
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}

/* Surfaces that sit over busy artwork need less fill to read as glass;
   surfaces over plain background need more so text stays legible. */
.glass--solid { background: var(--glass-fill-strong); }
.glass--sm { --glass-blur: var(--glass-blur-sm); }

/*
 * Refraction. `--glass-lens` is set per element by glass.js once its filter
 * exists; until then the fallback is empty and this is just a blur.
 */
[data-glass="liquid"] .glass[data-liquid] {
  backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-sat)) var(--glass-lens, );
  -webkit-backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-sat));
}

/* Opaque fallback: keep the lighting, drop the transparency. */
[data-glass="flat"] .glass {
  background: var(--surface);
  box-shadow: var(--glass-lighting), var(--sh-1);
  border: 1px solid var(--line);
}
[data-glass="flat"] .glass::before { opacity: .5; }

/* ================================================================ header */

.site-head {
  position: sticky; top: 0; z-index: 30; height: var(--head-h);
  transform: translateY(0);
  will-change: transform;
  background: var(--head-fill);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t), background var(--t), box-shadow var(--t),
              transform var(--t) var(--ease);
}
/* Slides away on the way down the page and returns on the way back up. */
.site-head.is-tucked { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) {
  .site-head { transition: border-color var(--t), background var(--t), box-shadow var(--t); }
  .site-head.is-tucked { transform: none; }
}
/* A single hairline of light along the bottom edge is all the thickness a
   full-width bar needs; a rim all the way round would read as a floating
   card rather than a sheet of glass laid over the page. */
.site-head::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--head-edge);
  pointer-events: none;
}
[data-glass="blur"] .site-head,
[data-glass="liquid"] .site-head {
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}
[data-glass="liquid"] .site-head {
  backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-sat)) var(--glass-lens, );
}
[data-glass="flat"] .site-head { background: rgb(253 251 250 / 98%); }
.site-head.is-scrolled {
  border-bottom-color: var(--line);
  box-shadow: 0 1px 24px -8px rgb(80 45 30 / 14%);
}

.site-head__inner { display: flex; align-items: center; gap: 1.5rem; height: 100%; }

.logo {
  display: flex; align-items: center; gap: .625rem;
  text-decoration: none; font-size: 1.125rem; letter-spacing: -0.035em;
}
.logo__mark {
  display: flex; border-radius: var(--r-sm);
  box-shadow: 0 3px 10px -3px rgb(244 85 60 / 55%);
  transition: transform var(--t) var(--ease), box-shadow var(--t);
}
.logo:hover .logo__mark { box-shadow: 0 6px 18px -4px rgb(244 85 60 / 60%); }
.logo__text { font-weight: 500; color: var(--ink); }
.logo__text strong { font-weight: 800; }

.site-nav { display: flex; gap: .25rem; margin-left: auto; }
.site-nav a {
  --nav-pad: .875rem;
  position: relative; text-decoration: none; color: var(--ink-2);
  font-size: .9375rem; font-weight: 550; padding: .5rem var(--nav-pad); border-radius: var(--r-pill);
  transition: color var(--t-fast), background var(--t-fast);
}
.site-nav a:hover { color: var(--ink); background: var(--surface-2); }

/*
 * A rule under the current page rather than a tinted pill behind it. The
 * pill read as a glow around the word — a blob competing with the logo for
 * attention, to say something as small as "you are here". A two-pixel line
 * says it and stops.
 */
.site-nav a.is-current { color: var(--ink); font-weight: 650; }
.site-nav a.is-current::after {
  content: '';
  position: absolute; left: var(--nav-pad); right: var(--nav-pad); bottom: .1875rem;
  height: 2px; border-radius: 2px; background: var(--brand);
}

/* ---- the footer's own links ---- */
.site-foot__nav {
  display: flex; flex-wrap: wrap; gap: .25rem 1.25rem;
  margin-top: 1rem;
}
.site-foot__nav a {
  color: var(--ink-3); text-decoration: none; font-size: .875rem;
  transition: color var(--t-fast);
}
.site-foot__nav a:hover { color: var(--ink); text-decoration: underline; }

/*
 * The theme picker. Three positions rather than two, because "system" is a
 * real answer and not the absence of one: a phone that dims itself at sunset
 * should take the site with it.
 */
/*
 * Shown only once the browser has confirmed the site is installable, which
 * is why it ships hidden. Beside the theme picker because it is the same
 * kind of thing: a preference you set once and never think about again.
 */
.site-install {
  display: inline-flex; align-items: center; gap: var(--s-3);
  align-self: flex-start; width: fit-content;
  margin-top: var(--s-6);
  padding: var(--s-4) var(--s-7);
  font: inherit; font-size: var(--fs-3); font-weight: 650;
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast), background var(--t-fast);
}
.site-install:hover { color: var(--ink); border-color: var(--ink-4); }
.site-install:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.site-install:disabled { opacity: .6; cursor: default; }
.site-install svg { display: block; }

.themepick {
  display: inline-flex;
  align-self: flex-start;
  width: fit-content;
  margin-top: var(--s-4);
  padding: 3px;
  gap: 2px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--line);
}
.themepick button {
  font: inherit;
  font-size: var(--fs-2);
  font-weight: 650;
  color: var(--ink-3);
  background: none;
  border: 0;
  padding: var(--s-2) var(--s-6);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast), box-shadow var(--t-fast);
}
.themepick button:hover { color: var(--ink); }
.themepick button.is-on {
  color: var(--ink);
  background: var(--key-face);
  box-shadow: inset 0 1px 0 var(--raise), var(--sh-1);
}
/* The two ways out of a finished batch, stacked so neither reads as the
   secondary of a row that has run out of width. */
.receipt__pair {
  display: grid;
  gap: var(--s-4);
}

.themepick button:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* ================================================================= tools */

/* Declared once. There was a second `.group { margin-top }` further down the
   file that silently won; the groups now live on /tools, and this is the
   only rule that sets their rhythm. */
.group { margin-top: 4.5rem; }
.group:first-of-type { margin-top: 3.25rem; }

.group__head { margin-bottom: 1.5rem; }
.group__eyebrow {
  display: inline-flex; align-items: center; gap: .5rem;
  font-size: .75rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent, var(--brand));
  background: var(--accent-soft, var(--brand-soft));
  padding: .25rem .75rem; border-radius: var(--r-pill); margin-bottom: .75rem;
}
.group__title { font-size: clamp(1.5rem, 2.6vw, 1.9rem); }
.group__blurb { color: var(--ink-3); font-size: 1rem; margin-top: .375rem; max-width: 62ch; }

.tool-grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(265px, 1fr));
}

/* =================================================================== why */

.why {
  position: relative; overflow: hidden; margin-top: 5rem; padding: 3rem;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-xl);
  box-shadow: var(--sh-1);
}
.why::after {
  content: ''; position: absolute; width: 380px; height: 380px;
  right: -110px; top: -140px; border-radius: 50%; pointer-events: none;
  background: none;
}
.why__title { font-size: clamp(1.4rem, 2.4vw, 1.75rem); position: relative; }
.why__grid {
  display: grid; gap: 2rem;
  grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
  margin-top: 2rem; position: relative;
}
.why__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 12px;
  background: var(--surface); color: var(--brand);
  border: 1px solid var(--line); box-shadow: var(--sh-1);
}
.why__item h3 { font-size: 1rem; font-weight: 700; margin: .875rem 0 .375rem; }
.why__item p { color: var(--ink-3); font-size: .9375rem; }

.prose { padding: 4rem 0; max-width: 68ch; }
.prose h1 { font-size: clamp(2rem, 4vw, 2.6rem); }
.prose h2 { font-size: 1.25rem; margin-top: 2.25rem; }
.prose p { color: var(--ink-2); margin-top: .75rem; }
.prose a { color: var(--brand-deep); text-decoration-color: color-mix(in srgb, var(--brand) 40%, transparent); text-underline-offset: 3px; }

/* ============================================================= tool page */

.tool-page { padding-top: 2.5rem; padding-bottom: 1rem; }

/*
 * Each family re-points the brand tokens, so everything carrying that hue —
 * dropzone, chips, slider, toggle, progress bar, primary button, and the
 * drag-and-drop overlay — picks it up from one rule. Warm for images, cool
 * for video, green-cyan for audio.
 */
/*
 * Video and audio tools recolour the brand to their family.
 *
 * These read the family tokens rather than restating the hexes, which they
 * used to do. Restating them meant dark mode never reached here: a video
 * tool kept `--brand-softer: #f8f7ff`, so selected rows and tinted panels
 * stayed near-white on a near-black page. Pointing at the tokens means the
 * dark palette redefines these for free, and there is one place to change a
 * family's colour rather than two that can disagree.
 */
[data-theme-group="video"] {
  --brand: var(--video);
  --brand-2: var(--video-2);
  --brand-deep: var(--video-deep);
  --brand-soft: var(--video-soft);
  --brand-softer: var(--video-softer);
  --fill-brand: #4c3fc9;
  --brand-grad: var(--fill-brand);
  --sh-brand: 0 4px 12px -2px color-mix(in srgb, var(--video) 26%, transparent),
              0 16px 32px -12px color-mix(in srgb, var(--video) 34%, transparent);
}
[data-theme-group="audio"] {
  --brand: var(--audio);
  --brand-2: var(--audio-2);
  --brand-deep: var(--audio-deep);
  --brand-soft: var(--audio-soft);
  --brand-softer: var(--audio-softer);
  --fill-brand: #0a7a6f;
  --brand-grad: var(--fill-brand);
  --sh-brand: 0 4px 12px -2px color-mix(in srgb, var(--audio) 26%, transparent),
              0 16px 32px -12px color-mix(in srgb, var(--audio) 34%, transparent);
}

/*
 * Left-aligned and compact. Centred headings read as a landing page; this is
 * a workbench, and every pixel spent on the title is a pixel the dropzone is
 * pushed below the fold.
 */
.tool-head {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .25rem 1rem;
  align-items: center;
  margin-bottom: 2rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--line-2);
}
.back {
  display: inline-flex; align-items: center; gap: .375rem;
  color: var(--ink-3); text-decoration: none;
  font-size: .875rem; font-weight: 550;
  padding: .375rem .75rem .375rem .625rem; border-radius: var(--r-pill);
  transition: color var(--t-fast), background var(--t-fast);
}
.back:hover { color: var(--ink); background: var(--surface-2); }
/* the sprite's arrow points right, so the back link turns it round */
.back svg { transform: rotate(180deg); transition: transform var(--t) var(--ease); }
.back:hover svg { transform: rotate(180deg) translateX(3px); }

.tool-head > .back { grid-column: 1 / -1; justify-self: start; }
.tool-head__icon {
  grid-row: span 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: var(--r); color: #fff;
  background: var(--accent-fill, var(--fill-brand));
  box-shadow: 0 5px 14px -5px color-mix(in srgb, var(--accent, var(--brand)) 70%, transparent);
}
.tool-head__title {
  align-self: end;
  font-size: clamp(1.6rem, 3.2vw, 2.2rem);
  font-weight: 800; letter-spacing: -0.04em;
}
.tool-head__text { align-self: start; color: var(--ink-3); font-size: .9375rem; }

/*
 * On a phone the header was stacking into four full-width rows: a back link,
 * a 52px badge sitting alone doing nothing, an oversized title, then a
 * two-line tagline. Together they took well over half the screen, so the
 * dropzone, which is the entire point of the page, started below the fold.
 *
 * The badge belongs beside the title, not above it. Same DOM, different
 * placement, and the tool is visible without scrolling.
 */
@media (max-width: 620px) {
  .tool-head {
    grid-template-columns: auto minmax(0, 1fr);
    gap: .5rem .75rem;
    align-items: center;
    margin-bottom: 1.5rem;
    padding-bottom: 1rem;
  }
  .tool-head > .back { margin-left: -.625rem; margin-bottom: -.25rem; }
  .tool-head__icon {
    grid-row: auto;
    width: 42px; height: 42px; border-radius: 12px;
  }
  .tool-head__icon svg { width: 21px; height: 21px; }
  .tool-head__title {
    align-self: center;
    font-size: 1.5rem;
    letter-spacing: -0.035em;
    line-height: 1.15;
  }
  /* The tagline is worth keeping, so it gets the full width rather than a
     column two-thirds as wide with the badge's empty space beside it. */
  .tool-head__text {
    grid-column: 1 / -1;
    font-size: .875rem;
    line-height: 1.45;
  }
}

/* ------------------------------------------------------------- dropzone */

.dropzone {
  position: relative; overflow: hidden; cursor: pointer; text-align: center;
  padding: 4rem 1.5rem;
  background: var(--surface);
  border: 2px dashed var(--line-2); border-radius: var(--r-xl);
  transition: border-color var(--t), background var(--t),
              transform var(--t) var(--ease), box-shadow var(--t);
}
.dropzone::before {
  content: ''; position: absolute; inset: 0; opacity: 0;
  background: var(--brand-soft);
  transition: opacity var(--t);
}
.dropzone:hover, .dropzone:focus-visible {
  border-color: var(--brand); box-shadow: var(--sh-2);
}
.dropzone:hover::before { opacity: 1; }
.dropzone.is-over {
  border-color: var(--brand); background: var(--brand-softer); box-shadow: var(--sh-brand);
}
.dropzone__inner { position: relative; }

.dropzone__label { font-size: 1.25rem; font-weight: 700; margin-top: 1.25rem; letter-spacing: -0.03em; }
.dropzone__hint {
  color: var(--ink-3); font-size: .875rem; margin: .5rem auto 0;
  max-width: 66ch; line-height: 1.6; word-break: break-word;
}

.queue__add .dropzone { padding: 1.75rem; border-radius: var(--r-lg); }
.queue__add .dropzone__icon { width: 44px; height: 44px; }
.queue__add .dropzone__icon svg { width: 22px; height: 22px; }
.queue__add .dropzone__label { font-size: 1rem; margin-top: .75rem; }
.queue__add .dropzone__hint { font-size: .8125rem; }

.drop-overlay {
  position: fixed; inset: 0; z-index: 50; display: none;
  align-items: center; justify-content: center;
  background: rgb(253 251 250 / 82%);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
.drop-overlay.is-active { display: flex; animation: fade-in .2s var(--ease-soft); }
.drop-overlay__box {
  display: grid; justify-items: center; gap: .875rem;
  padding: 3.5rem 5rem; border-radius: var(--r-xl);
  outline: 2.5px dashed color-mix(in srgb, var(--brand) 60%, transparent);
  outline-offset: -10px;
  color: var(--brand-deep); font-weight: 700; font-size: 1.25rem;
  box-shadow: var(--glass-lighting), var(--sh-3);
  animation: pop-in .32s var(--ease) both;
}
.drop-overlay__box svg { animation: bob 1.6s var(--ease-soft) infinite; }

/* ----------------------------------------------------------------- queue */

.queue__head { display: flex; align-items: center; gap: .75rem; }
.queue__title { font-size: 1.125rem; font-weight: 700; }
.queue__size {
  color: var(--ink-3); font-size: .8125rem; font-weight: 600;
  background: var(--surface-2); padding: .125rem .625rem; border-radius: var(--r-pill);
}
.queue__head .btn { margin-left: auto; }

.file-list, .result-list { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: .625rem; }

.file-row, .result-row {
  display: flex; align-items: center; gap: 1rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: .75rem 1rem;
  box-shadow: var(--sh-1);
  animation: slide-in-left .4s var(--ease) both;
  transition: border-color var(--t), box-shadow var(--t), transform var(--t) var(--ease);
}
.file-row:hover, .result-row:hover { border-color: var(--line-2); box-shadow: var(--sh-2); }
.file-row.is-done { border-color: color-mix(in srgb, var(--ok) 30%, var(--line)); }
.file-row.is-error { border-color: color-mix(in srgb, var(--err) 40%, var(--line)); background: var(--err-soft); }

.file-row__body, .result-row__body { flex: 1; min-width: 0; }
.file-row__name, .result-row__name {
  font-weight: 650; font-size: .9375rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.file-row__meta, .result-row__meta {
  color: var(--ink-3); font-size: .8125rem; display: flex; gap: .5rem;
  flex-wrap: wrap; align-items: center;
}
.file-row__status { color: var(--ink-2); font-weight: 550; }
.file-row__tick, .file-row__warn {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 28px; height: 28px; border-radius: 50%;
  animation: pop-in .35s var(--ease) both;
}
.file-row__tick { color: var(--ok); background: var(--ok-soft); }
.file-row__warn { color: var(--err); background: var(--surface); }

.thumb {
  position: relative; flex: none; width: 50px; height: 50px;
  border-radius: 12px; overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
}
.thumb__ext {
  font-size: .625rem; font-weight: 800; color: var(--ink-4);
  letter-spacing: .04em; transition: opacity var(--t);
}
.thumb__img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity var(--t) var(--ease-soft);
}
.thumb.has-preview .thumb__img { opacity: 1; }
.thumb.has-preview .thumb__ext { opacity: 0; }

.icon-btn {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: var(--r-sm); cursor: pointer;
  background: transparent; border: 1px solid transparent; color: var(--ink-4);
  transition: background var(--t-fast), color var(--t-fast),
              border-color var(--t-fast), transform var(--t-fast);
}
.icon-btn:hover {
  background: var(--err-soft); color: var(--err);
  border-color: color-mix(in srgb, var(--err) 20%, transparent);
}

/* -------------------------------------------------------------- progress */

.progress {
  height: 6px; border-radius: var(--r-pill); overflow: hidden;
  background: var(--surface-3); margin-top: .5rem; position: relative;
}
.progress__fill {
  position: relative; height: 100%; width: 2%; border-radius: var(--r-pill);
  background: var(--brand-grad);
  transition: width .3s var(--ease-soft);
}
.progress__fill::after {
  content: ''; position: absolute; inset: 0;
  background: none;
  animation: shimmer 1.6s var(--ease-soft) infinite;
}
.progress.is-indeterminate .progress__fill { width: 40%; animation: shimmer 1.4s var(--ease-soft) infinite; }

/* ---------------------------------------------------------------- fields */

.field { display: grid; gap: .5rem; animation: fade-in var(--t) var(--ease-soft) both; }
.field__label { display: flex; align-items: baseline; gap: .5rem; font-size: .875rem; font-weight: 650; }
.field__value {
  margin-left: auto; color: var(--brand-deep); font-size: .75rem; font-weight: 650;
  background: var(--brand-soft); padding: .125rem .5rem; border-radius: var(--r-pill);
}
.field__hint { color: var(--ink-3); font-size: .8125rem; line-height: 1.5; }
.field--color { grid-template-columns: 1fr auto; align-items: center; }

.input, .select {
  width: 100%; padding: .625rem .875rem; border-radius: var(--r-sm);
  border: 1.5px solid var(--line-2); background: var(--surface); color: var(--ink);
  font-size: .9375rem; font-weight: 500;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
.input:hover, .select:hover { border-color: var(--ink-4); }
.select {
  appearance: none; padding-right: 2.5rem; cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: right 1.1rem center, right .82rem center;
  background-size: 6px 6px, 6px 6px; background-repeat: no-repeat;
}
.input:focus, .select:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 4px var(--brand-soft);
}

.color {
  width: 52px; height: 36px; padding: 3px; cursor: pointer;
  border: 1.5px solid var(--line-2); border-radius: var(--r-sm); background: var(--surface);
  transition: border-color var(--t-fast), transform var(--t-fast);
}
.color:hover { border-color: var(--brand); }

.range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 6px; border-radius: var(--r-pill); cursor: pointer;
  background: linear-gradient(90deg, var(--brand) var(--fill, 50%), var(--surface-3) var(--fill, 50%));
}
.range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--knob); border: 2.5px solid var(--brand);
  box-shadow: 0 2px 8px -1px color-mix(in srgb, var(--brand) 45%, transparent); cursor: grab;
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast);
}
.range::-webkit-slider-thumb:hover {
  transform: scale(1.18);
  box-shadow: 0 3px 12px -1px color-mix(in srgb, var(--brand) 60%, transparent);
}
.range:active::-webkit-slider-thumb { cursor: grabbing; transform: scale(1.06); }
.range::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--knob); border: 2.5px solid var(--brand);
  box-shadow: 0 2px 8px -1px color-mix(in srgb, var(--brand) 45%, transparent); cursor: grab;
}

/* ----------------------------------------------------------------- chips */

.chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.chip {
  position: relative; cursor: pointer;
  font-family: var(--mono);
  padding: .375rem .75rem; border-radius: var(--r-chip);
  border: 1px solid var(--line-2);
  background: var(--key-face);
  color: var(--ink-2);
  font-size: .75rem; font-weight: 700; letter-spacing: .06em;
  box-shadow:
    inset 0 1px 0 var(--raise),
    0 2px 0 0 var(--line-2),
    0 3px 5px -3px rgb(80 45 30 / 26%);
  transition: transform var(--t-fast) var(--ease), background var(--t-fast),
              color var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast);
}
.chip:hover {
  color: var(--brand-deep);
  border-color: color-mix(in srgb, var(--brand) 45%, var(--line-2));
  transform: translateY(1px);
  box-shadow:
    inset 0 1px 0 var(--raise),
    0 1px 0 0 var(--line-2),
    0 2px 3px -2px rgb(80 45 30 / 22%);
}
.chip:active { transform: translateY(2px); box-shadow: inset 0 1px 3px rgb(80 45 30 / 22%); }
/* selected keys are held down */
.chip.is-selected {
  background: var(--brand-grad);
  border-color: transparent;
  color: #fff;
  transform: translateY(2px);
  box-shadow:
    inset 0 1px 2px rgb(120 30 10 / 30%),
    0 0 0 1px color-mix(in srgb, var(--brand) 40%, transparent),
    0 2px 8px -3px color-mix(in srgb, var(--brand) 60%, transparent);
}
.chip.is-unavailable {
  opacity: .4; cursor: not-allowed; text-decoration: line-through;
  transform: none; border-color: var(--line); color: var(--ink-4);
  background: var(--surface-2);
  box-shadow: none;
}

/* ----------------------------------------------------------- radio cards */

.radio-cards { display: grid; gap: .5rem; }
.radio-card {
  position: relative; cursor: pointer; text-align: left;
  display: grid; gap: .125rem; padding: .75rem 2.5rem .75rem .875rem;
  border-radius: var(--r); border: 1.5px solid var(--line-2); background: var(--surface);
  transition: border-color var(--t-fast), background var(--t-fast),
              transform var(--t-fast) var(--ease), box-shadow var(--t-fast);
}
.radio-card::after {
  content: ''; position: absolute; right: .875rem; top: 50%;
  width: 18px; height: 18px; margin-top: -9px; border-radius: 50%;
  border: 1.5px solid var(--line-2);
  transition: border-color var(--t-fast), background var(--t-fast), box-shadow var(--t-fast);
}
.radio-card:hover { border-color: var(--brand); background: var(--brand-softer); }
.radio-card.is-selected {
  border-color: var(--brand); background: var(--brand-softer); box-shadow: var(--sh-1);
}
.radio-card.is-selected::after {
  border-color: var(--brand); background: var(--fill-brand);
  box-shadow: inset 0 0 0 3.5px var(--surface);
}
.radio-card__label { font-size: .9375rem; font-weight: 700; }
.radio-card__hint { font-size: .75rem; color: var(--ink-3); line-height: 1.45; }
.radio-card.is-selected .radio-card__hint { color: var(--ink-2); }

/* ---------------------------------------------------------------- toggle */

.toggle { display: flex; align-items: center; gap: .75rem; cursor: pointer; }
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle__track {
  flex: none; position: relative; width: 42px; height: 24px; border-radius: var(--r-pill);
  background: var(--line-2);
  transition: background var(--t) var(--ease);
}
.toggle__dot {
  position: absolute; top: 3px; left: 3px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--knob);
  box-shadow: 0 1px 3px rgb(80 45 30 / 30%);
  transition: transform var(--t) var(--ease);
}
.toggle input:checked + .toggle__track { background: var(--brand-grad); }
.toggle input:checked + .toggle__track .toggle__dot { transform: translateX(18px); }
.toggle:hover .toggle__track { background: var(--ink-4); }
.toggle:hover input:checked + .toggle__track { background: var(--brand-grad); filter: brightness(1.05); }
.toggle input:focus-visible + .toggle__track { outline: 2.5px solid var(--brand); outline-offset: 3px; }
.toggle__label { font-size: .875rem; font-weight: 550; line-height: 1.4; }

/* --------------------------------------------------------------- buttons */

.btn {
  position: relative; overflow: hidden; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .75rem 1.25rem; border-radius: var(--r);
  border: 1.5px solid transparent; background: var(--surface-2); color: var(--ink);
  font-size: .9375rem; font-weight: 700; text-decoration: none; white-space: nowrap;
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t),
              background var(--t-fast), border-color var(--t-fast), filter var(--t-fast);
}
.btn:active { transform: scale(.985); }
.btn:disabled { opacity: .45; cursor: not-allowed; transform: none; filter: grayscale(.35); }

.btn--primary { background: var(--brand-grad); color: #fff; box-shadow: var(--sh-brand); }
/* The sweep of light that used to run across this on hover was pure motion
   for its own sake, and it read as a flicker. Brightness alone now. */
.btn--primary:hover:not(:disabled) {
  box-shadow: 0 6px 16px -2px color-mix(in srgb, var(--brand) 36%, transparent),
              0 22px 40px -14px color-mix(in srgb, var(--brand) 44%, transparent);
}
.btn--primary:disabled { box-shadow: none; }

.btn--soft { background: var(--surface-2); border-color: var(--line); color: var(--ink-2); }
.btn--soft:hover { background: var(--surface-3); color: var(--ink); }
.btn--ghost { background: transparent; border-color: var(--line-2); color: var(--ink-2); }
.btn--ghost:hover { background: var(--surface-2); color: var(--ink); border-color: var(--ink-4); }
.btn--block { width: 100%; padding: .875rem 1.25rem; font-size: 1rem; border-radius: var(--r-md); }
.btn--sm { padding: .375rem .75rem; font-size: .8125rem; border-radius: var(--r-sm); }

/* --------------------------------------------------------------- results */

.results:empty { display: none; }
.results { animation: fade-up .45s var(--ease) both; }

.results__badge {
  position: relative; display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%; flex: none;
  background: var(--ok); color: #fff;
  box-shadow: 0 4px 12px -2px rgb(13 156 116 / 45%);
  animation: pop-in .45s var(--ease) both;
}
.results__badge::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  border: 2px solid var(--ok); animation: ring 1.8s var(--ease-soft) 2;
}
.results__title { font-size: 1.125rem; font-weight: 750; }
.results__meta { color: var(--ink-2); font-size: .875rem; font-weight: 500; }

.result-row--more { color: var(--ink-3); font-size: .875rem; justify-content: center; }

.notice {
  display: flex; gap: .75rem; align-items: flex-start;
  border-radius: var(--r-md); padding: 1rem 1rem; margin-bottom: .75rem;
  font-size: .875rem; line-height: 1.55;
  animation: fade-up .35s var(--ease) both;
}
.notice--error {
  background: var(--err-soft); color: var(--err);
  border: 1px solid color-mix(in srgb, var(--err) 24%, transparent);
}
.notice--check { background: var(--ok-soft); color: var(--ok); }
.notice__icon { display: flex; padding-top: .125rem; flex: none; }
.notice strong { display: block; font-weight: 700; }


/* ==========================================================================
   Format tokens
   --------------------------------------------------------------------------
   Every format name on the site is set in monospace and boxed. It is the
   signature: a format is a technical object, so it is typeset like one, and
   `PNG -> JPG` becomes a mark you can recognise at a glance anywhere.
   ========================================================================== */

/*
 * Format tokens are keycaps.
 *
 * The old chip carried a coloured bar down its left edge, which added a
 * second thing to read for no information — the page already knows whether
 * you are converting an image or a video. What is left is physical: a light
 * top face, a darker side wall underneath, and a highlight along the top
 * edge, so the token reads as something you could press. That matches the
 * split-flap boards these sit next to, and it survives at every size without
 * a single accent colour.
 */

.token {
  position: relative;
  display: inline-flex;
  align-items: center;
  font-family: var(--mono);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--ink);
  padding: .25rem .625rem;
  border-radius: var(--r-chip);
  background: var(--key-face);
  border: 1px solid var(--line-2);
  box-shadow:
    inset 0 1px 0 var(--raise),
    0 2px 0 0 var(--line-2),
    0 3px 5px -3px rgb(80 45 30 / 30%);
  white-space: nowrap;
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

/* pressed: the cap sinks onto its own side wall */
.token--pressed,
a:hover > .token,
.token-tile:hover .token,
.pair-link:hover .token {
  transform: translateY(1px);
  box-shadow:
    inset 0 1px 0 var(--raise),
    0 1px 0 0 var(--line-2),
    0 2px 3px -2px rgb(80 45 30 / 26%);
}

.token--lg {
  font-size: .9375rem;
  padding: .375rem .75rem;
  border-radius: var(--r-sm);
  box-shadow:
    inset 0 1px 0 var(--raise),
    0 2.5px 0 0 var(--line-2),
    0 4px 7px -4px rgb(80 45 30 / 30%);
}

.token--xl {
  font-size: 1.5rem;
  letter-spacing: .04em;
  padding: .625rem 1rem;
  border-radius: 12px;
  box-shadow:
    inset 0 1.5px 0 var(--raise),
    0 3.5px 0 0 var(--line-2),
    0 7px 12px -6px rgb(80 45 30 / 32%);
}

.mark { display: inline-flex; align-items: center; gap: .5rem; }
.mark__arrow { display: flex; color: var(--ink-4); flex: none; }

.counter {
  font-family: var(--mono); font-size: .75rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--brand-deep); background: var(--brand-soft);
  padding: .25rem .625rem; border-radius: var(--r-pill); white-space: nowrap;
}

.section-title { font-size: clamp(1.2rem, 2.2vw, 1.5rem); font-weight: 700; }
.section-note { color: var(--ink-3); font-size: .9375rem; margin-top: .25rem; }

.text-link {
  display: inline-flex; align-items: center; gap: .375rem;
  color: var(--brand-deep); font-size: .875rem; font-weight: 650;
  text-decoration: none; margin-top: 1rem;
}
.text-link:hover { text-decoration: underline; text-underline-offset: 3px; }
.text-link svg { transition: transform var(--t) var(--ease); }
.text-link:hover svg { transform: translateX(3px); }

/* ============================================================ pair links */

.pair-grid {
  display: grid; gap: .5rem; margin-top: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
}

.pair-link {
  display: flex; align-items: center; gap: .625rem;
  padding: .625rem .75rem; border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--line);
  text-decoration: none; box-shadow: var(--sh-1);
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
.pair-link[data-kind="image"] { --accent: var(--image); }
.pair-link[data-kind="video"] { --accent: var(--video); }
.pair-link[data-kind="audio"] { --accent: var(--audio); }
.pair-link:hover {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--line));
  box-shadow: var(--sh-2);
}
.pair-link__go {
  display: flex; margin-left: auto; color: var(--ink-4); flex: none;
  transition: color var(--t-fast), transform var(--t) var(--ease);
}
.pair-link:hover .pair-link__go { color: var(--accent); transform: translateX(3px); }

/* =========================================================== token tiles */

.token-grid {
  display: grid; gap: .5rem; margin-top: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
}
.token-tile {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .75rem .75rem; border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--line);
  text-decoration: none; box-shadow: var(--sh-1);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.token-tile:hover { border-color: var(--line-2); box-shadow: var(--sh-2); }
.token-tile__ext { font-family: var(--mono); font-size: .75rem; color: var(--ink-4); }
.token-tile__flag {
  font-size: .625rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-4); background: var(--surface-2);
  padding: .125rem .375rem; border-radius: var(--r-pill);
}

/* ======================================================== generated pages */

.index-page, .format-page { padding: 2.5rem 0 1rem; }

.index-head, .format-head { margin-bottom: 2.5rem; max-width: 68ch; }
.index-head__row { display: flex; align-items: center; gap: .875rem; flex-wrap: wrap; }
.index-head__title { font-size: clamp(2rem, 4.5vw, 3rem); font-weight: 800; }
.index-head__text { color: var(--ink-2); margin-top: .75rem; font-size: 1rem; }

.format-head__badge { margin-bottom: 1rem; }
.format-head__title { font-size: clamp(1.9rem, 4vw, 2.7rem); font-weight: 800; }
.format-head__text { color: var(--ink-2); margin-top: .625rem; font-size: 1rem; }
.format-head__facts { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.5rem; }

.chip-fact {
  display: grid; gap: .125rem; padding: .5rem .75rem;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--surface);
}
.chip-fact span {
  font-size: .6875rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3);
}
.chip-fact strong { font-size: .875rem; font-weight: 650; }

.format-section { margin-top: 3rem; }

.missing { text-align: center; }
.missing .counter { font-size: 1rem; }
.missing h1 { margin-top: 1rem; }
.missing .btn { margin-top: 1.5rem; }

/* ======================================================== conversion page */

.convert-head {
  margin-bottom: 2rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--line-2);
}
.convert-head > * { animation: fade-up .5s var(--ease) both; }
.convert-head > :nth-child(2) { animation-delay: .05s; }
.convert-head > :nth-child(3) { animation-delay: .1s; }
.convert-head > :nth-child(4) { animation-delay: .15s; }
.convert-head > :nth-child(5) { animation-delay: .2s; }

/*
 * The back link reads as an eyebrow belonging to the heading, so it sits
 * close above it and its text lines up with the heading's left edge. The
 * pill's own padding would otherwise indent the words by 10px against a
 * title starting at zero, which is the kind of near-miss that looks like
 * nothing lines up.
 */
.convert-head .back {
  display: inline-flex;
  margin-left: -.625rem;
  margin-bottom: .25rem;
}
.convert-head__title {
  font-size: clamp(1.7rem, 3.6vw, 2.5rem);
  font-weight: 800; letter-spacing: -0.04em;
}
.convert-head__text { color: var(--ink-2); margin-top: .5rem; max-width: 58ch; }
/* No accent bar down the side. A quiet card carries the note on its own. */
.convert-head__reason {
  color: var(--ink-2); margin-top: 1rem; max-width: 62ch;
  font-size: .9375rem; padding: .875rem 1rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r);
}

/*
 * On a phone the converter is the page, and it was starting 617px down —
 * below the fold on most handsets — behind a back link, the format badge,
 * a heading, an introduction and a note. The tool now comes directly after
 * the heading and the prose follows it.
 *
 * Only the visual order changes. `display: contents` lets the header's
 * children take part in the page's flex order while the markup keeps the
 * heading followed by its own introduction, which is what anything reading
 * the document, search engines included, still sees.
 */
@media (max-width: 620px) {
  .tool-page { display: flex; flex-direction: column; }
  .tool-page > * { order: 8; }
  .tool-page > .convert-head { display: contents; }

  /*
   * Tool pages share this container but keep their own heading in place:
   * it already has a compact treatment above, and without an order of its
   * own the catch-all below would drop the h1 underneath the converter.
   */
  .tool-page > .tool-head { order: 1; }

  .convert-head > .back { order: 1; }
  .convert-head__title  { order: 3; }
  .tool-page > .flow    { order: 5; }
  .convert-head__text   { order: 6; }
  .convert-head__reason { order: 7; }

  /* Tightened to match: the badge and heading were sized for a wide page. */
  .convert-head .back { margin-bottom: 0; }
  .convert-head__title {
    font-size: 1.5rem; line-height: 1.15; letter-spacing: -0.035em;
    margin-top: .25rem;
    /* The header's own rule went with its box, so the divider lives here. */
    margin-bottom: .875rem; padding-bottom: .75rem;
    border-bottom: 1px solid var(--line-2);
  }
  /* 40px of air above a back link is generous on a page this narrow. */
  .tool-page { padding-top: 1rem; }
  .convert-head__text {
    margin-top: 2rem; font-size: .875rem; line-height: 1.45;
  }
  .convert-head__reason { margin-top: .75rem; }
}

.convert-more { margin-top: 3.5rem; }
.convert-more__grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.format-card {
  padding: 1.5rem; border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh-1);
}
.format-card[data-kind="image"] { --accent: var(--image); }
.format-card[data-kind="video"] { --accent: var(--video); }
.format-card[data-kind="audio"] { --accent: var(--audio); }
.format-card__eyebrow {
  font-size: .6875rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3);
}
@media (max-width: 620px) { .format-card__eyebrow { font-size: .75rem; } }
.format-card__head { display: flex; align-items: baseline; gap: .625rem; margin-top: .75rem; }
.format-card__ext { font-family: var(--mono); font-size: .8125rem; color: var(--ink-3); }
.format-card__note { color: var(--ink-2); font-size: .9375rem; margin-top: .75rem; }
.format-card__facts { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: .375rem; }
.format-card__facts li {
  display: flex; justify-content: space-between; gap: 1rem;
  font-size: .875rem; padding-bottom: .375rem; border-bottom: 1px solid var(--line);
}
.format-card__facts li:last-child { border-bottom: 0; padding-bottom: 0; }
.format-card__facts span { color: var(--ink-3); }
.format-card__facts strong { font-weight: 650; }

.convert-swap {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  margin-top: 1.5rem; padding: 1rem 1.5rem;
  border: 1px dashed var(--line-2); border-radius: var(--r-lg);
}
.convert-swap__label { font-weight: 650; font-size: .9375rem; }

.convert-alts { margin-top: 3rem; }

/* ================================================================ footer */

/* ==================================================================== foot

   It used to be four ragged left-aligned paragraphs and a row of links, on
   a page whose every other block is a centred, bordered panel. So it is one
   now: a single column, centred, with the mark at the top and the rhythm
   opening out as it goes down.

   The scoreboard in the middle is the joke and the product description at
   the same time. Everything this thing is known for is a thing it does not
   do, so the numbers are all nothing — and they are the same nothing the
   network log prints further up the page.
   ========================================================================= */

.site-foot {
  margin-top: clamp(5rem, 9vw, 7rem);
  border-top: 1px solid var(--line);
  background: var(--bg-deep);
}

.site-foot__inner {
  padding: clamp(3rem, 6vw, 4.5rem) 0 clamp(2.5rem, 4vw, 3.5rem);
  display: grid; justify-items: center; gap: .875rem;
  text-align: center;
}

.site-foot__mark {
  display: block; line-height: 0;
  border-radius: 13px;
  transition: transform var(--t) var(--ease);
}
.site-foot__mark img { display: block; border-radius: 13px; }
.site-foot__mark:hover { transform: translateY(-2px); }

.site-foot__word { font-size: 1.0625rem; letter-spacing: -0.02em; margin-top: .25rem; }
.site-foot__tag { color: var(--ink-3); font-size: .9375rem; max-width: 46ch; }

/* --- the scoreboard ----------------------------------------------------- */

.site-foot__score {
  list-style: none; margin: .875rem 0 .25rem; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .5rem;
  width: min(560px, 100%);
}
.site-foot__score li {
  display: grid; gap: .125rem; justify-items: center;
  padding: .875rem .5rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--surface);
}
.site-foot__score b {
  font-size: 1.5rem; font-weight: 800; letter-spacing: -0.04em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.site-foot__score span {
  font-size: .6875rem; color: var(--ink-4);
  line-height: 1.35;
}

/* --- links and controls -------------------------------------------------- */

.site-foot__nav {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: .375rem 1.125rem;
  margin-top: .625rem;
}
.site-foot a { color: var(--ink-2); text-underline-offset: 3px; }
.site-foot a:hover { color: var(--brand-deep); }
.site-foot__nav a { font-size: .875rem; }

.site-foot__controls {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: .625rem;
  margin-top: .5rem;
}

.site-foot__fine {
  max-width: 58ch;
  margin-top: .75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
  color: var(--ink-4); font-size: .8125rem; line-height: 1.7;
}
/* Quiet on purpose. The site's whole argument is that it asks nothing of
   you, and a donation line that shouts undermines that more than it earns. */
.site-foot__chip {
  color: var(--brand-deep); font-weight: 650; text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--brand) 40%, transparent);
  transition: border-color var(--t-fast), color var(--t-fast);
}
.site-foot__chip:hover { color: var(--brand); border-bottom-color: var(--brand); }

@media (prefers-reduced-motion: reduce) {
  .site-foot__mark { transition: none; }
  .site-foot__mark:hover { transform: none; }
}

@media (max-width: 560px) {
  /* Two up rather than four squeezed: the labels are the point and they
     stop being readable below about eighty pixels a column. */
  .site-foot__score { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ============================================================ home explore */

.explore {
  display: grid; gap: 1rem; margin-top: 4.5rem;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.explore__card {
  display: block; text-decoration: none; padding: 1.75rem;
  border-radius: var(--r-xl); background: var(--surface);
  border: 1px solid var(--line); box-shadow: var(--sh-1);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.explore__card:hover { border-color: var(--line-2); box-shadow: var(--sh-2); }
.explore__count {
  display: block; font-family: var(--mono);
  font-size: clamp(2rem, 4vw, 2.7rem); font-weight: 700; letter-spacing: -0.03em;
  background: var(--brand-grad);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.explore__card h3 { font-size: 1.125rem; margin-top: .375rem; }
.explore__card p { color: var(--ink-3); font-size: .9375rem; margin-top: .5rem; }

/* ==========================================================================
   The conversion console
   --------------------------------------------------------------------------
   The front page's centrepiece: two slots and a button that routes into the
   thousand conversion pages. It is a real control, not a picture of one, so
   it gets a physical treatment — inset slots, keys that press, and a picker
   under whichever slot you are editing.

   Sized from its own width, not the window's. The console is a container,
   and every measurement inside it — type, padding, key size, gaps, corner
   radii — is a clamp on that width, so it scales continuously from a 320px
   phone to a wide monitor instead of jumping at two or three breakpoints.
   The clamps put a floor under everything a thumb or an eye needs and a
   ceiling on everything that would otherwise sprawl.
   ========================================================================== */

.stage__console {
  container: console / inline-size;
  max-width: 860px;
  margin-inline: auto;
}

.console {
  /* the rhythm: one step, and everything else is a multiple of it */
  --step: clamp(.75rem, .45rem + 1.6cqi, 1.25rem);
  --pad: clamp(1rem, .55rem + 3.1cqi, 2.25rem);
  --radius: clamp(22px, 14px + 2.4cqi, 32px);
  --key-h: clamp(2.375rem, 2rem + 1.2cqi, 2.75rem);
  --key-gap: clamp(.375rem, .25rem + .7cqi, .625rem);

  position: relative;
  margin: 0 auto;
  padding: var(--pad);
  text-align: left;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--line);
  /* The theme's own elevation, so the depth is right in light and dark, with
     a faint rim of light along the top edge. */
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--raise) 50%, transparent),
    var(--sh-1),
    var(--sh-3);
  transition: box-shadow var(--t-slow) var(--ease);
}

/* A fingertip needs 44px whatever the width says. */
@media (pointer: coarse) {
  .console { --key-h: 2.75rem; }
}

/* --- family tabs --- */

.console__tabs {
  position: relative;
  display: flex;
  gap: .25rem;
  padding: .3125rem;
  width: fit-content;
  margin: 0 auto calc(var(--step) * 1.25);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  box-shadow: inset 0 1px 2px rgb(40 20 10 / 8%);
}
.console__tab {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: .4375rem;
  min-height: 2.5rem;
  padding: .5rem clamp(.875rem, .6rem + 1.2cqi, 1.25rem);
  border: 0; border-radius: var(--r-pill);
  background: transparent; color: var(--ink-3);
  font-size: clamp(.875rem, .8rem + .3cqi, .9375rem); font-weight: 650; letter-spacing: -.005em;
  cursor: pointer;
  transition: color var(--t) var(--ease);
}
.console__tab svg { transition: transform var(--t) var(--ease); }
.console__tabs .gooey { top: .3125rem; bottom: .3125rem; }
.console__tab:hover { color: var(--ink); }
.console__tab[aria-selected="true"] { color: var(--brand-deep); background: transparent; box-shadow: none; }
.console__tab[aria-selected="true"] svg { transform: scale(1.08); }

/* --- the two slots --- */

.console__slots {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: clamp(.5rem, .2rem + 1.4cqi, 1rem);
}

.console__slot {
  display: grid; justify-items: center; align-content: center;
  gap: clamp(.4375rem, .3rem + .6cqi, .75rem);
  min-height: clamp(7.75rem, 5.5rem + 9cqi, 10.5rem);
  padding: clamp(.875rem, .6rem + 1.4cqi, 1.5rem) .75rem;
  border-radius: calc(var(--radius) - var(--pad) / 2.5);
  border: 1.5px solid var(--line-2);
  /* Recessed, so it reads as a slot something drops into rather than a card. */
  background: var(--surface-2);
  box-shadow: inset 0 2px 6px -2px rgb(40 20 10 / 14%);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    border-color var(--t) var(--ease),
    background-color var(--t) var(--ease),
    box-shadow var(--t) var(--ease),
    transform var(--t-fast) var(--ease);
}
.console__slot:active { transform: scale(.985); }
.console__slot.is-editing {
  border-color: var(--brand);
  background: var(--brand-softer);
  box-shadow:
    inset 0 2px 6px -2px rgb(40 20 10 / 10%),
    0 0 0 4px color-mix(in srgb, var(--brand) 16%, transparent);
}
.console__slot-label {
  font-size: clamp(.6875rem, .6rem + .35cqi, .8125rem);
  font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3);
}
.console__slot-token { justify-self: center; }
.console__slot .token--xl {
  font-size: clamp(1.375rem, 1rem + 1.9cqi, 2.125rem);
  padding: .4em .66em;
  border-radius: .45em;
  letter-spacing: .05em;
  transition: box-shadow var(--t) var(--ease), transform var(--t) var(--ease);
}
.console__slot.is-editing .token--xl { transform: translateY(-1px); }
.console__slot-hint {
  min-height: 1.2em;
  font-size: clamp(.75rem, .7rem + .2cqi, .8125rem); font-weight: 600;
  color: var(--ink-4);
  transition: color var(--t) var(--ease);
}
.console__slot:not(.is-editing):hover .console__slot-hint { color: var(--ink-2); }
.console__slot.is-editing .console__slot-hint { color: var(--brand-deep); }

@media (hover: hover) {
  .console__slot:not(.is-editing):hover {
    border-color: color-mix(in srgb, var(--brand) 55%, var(--line-2));
    background: color-mix(in srgb, var(--brand) 3%, var(--surface-2));
  }
}

.console__arrow { display: flex; color: var(--brand); }

/* the swap sits between the slots and scales with them */
.console__slots .console__swap {
  width: clamp(2.125rem, 1.8rem + 1.4cqi, 2.75rem);
  height: clamp(2.125rem, 1.8rem + 1.4cqi, 2.75rem);
  transition:
    color var(--t-fast), border-color var(--t-fast), background var(--t-fast),
    transform var(--t-slow) cubic-bezier(.34, 1.56, .64, 1);
}

/* --- the picker --- */

.console__picker { margin-top: calc(var(--step) * 1.5); }
.console__picker-head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .375rem .75rem;
  margin-bottom: calc(var(--step) * .75);
}
.console__picker-label {
  font-size: clamp(.8125rem, .76rem + .25cqi, .9375rem); font-weight: 650;
  color: var(--ink-2);
}
.console__intake { display: flex; min-width: 0; }

/* the other way in: a file instead of a format */
.console__use-file {
  display: inline-flex; align-items: center; gap: .3125rem;
  min-height: 2rem; padding: .25rem .625rem .25rem .5rem;
  border-radius: var(--r-pill);
  border: 1px dashed color-mix(in srgb, var(--brand) 45%, var(--line-2));
  background: transparent; color: var(--brand-deep);
  font-size: clamp(.75rem, .72rem + .15cqi, .8125rem); font-weight: 650;
  cursor: pointer;
  transition: background-color var(--t-fast), border-color var(--t-fast), transform var(--t-fast) var(--ease);
}
@media (hover: hover) {
  .console__use-file:hover { background: var(--brand-softer); border-style: solid; }
}
.console__use-file:active { transform: scale(.97); }
.console__use-file-text { white-space: nowrap; }
.console__use-file-sub { display: none; font-size: .75rem; font-weight: 500; color: var(--ink-4); }

/* a file that has been handed in, and the way to take it back */
.console__file {
  display: inline-flex; align-items: center; gap: .4375rem; min-width: 0; max-width: 100%;
  min-height: 2rem; padding: .1875rem .25rem .1875rem .5rem;
  border-radius: var(--r-pill);
  background: var(--brand-softer);
  border: 1px solid color-mix(in srgb, var(--brand) 30%, var(--line-2));
  font-size: clamp(.75rem, .72rem + .15cqi, .8125rem);
  animation: key-in var(--t) var(--ease) both;
}
.console__file-icon { display: flex; color: var(--brand-deep); flex: none; }
.console__file-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 16ch; font-weight: 650; color: var(--ink);
}
.console__file-size { color: var(--ink-4); font-family: var(--mono); font-size: .9em; white-space: nowrap; }
.console__file-remove {
  display: grid; place-items: center; flex: none;
  width: 1.5rem; height: 1.5rem; border-radius: 50%;
  border: 0; background: transparent; color: var(--ink-3); cursor: pointer;
  transition: background-color var(--t-fast), color var(--t-fast);
}
.console__file-remove:hover { background: var(--surface-3); color: var(--ink); }
@media (pointer: coarse) {
  .console__use-file, .console__file { min-height: 2.5rem; }
  .console__file-remove { width: 2rem; height: 2rem; }
}
.console__slot-hint.is-file {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink-2);
}
.console__options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(4rem, 3.2rem + 3.5cqi, 5.5rem), 1fr));
  gap: var(--key-gap);
  align-content: start;
  /* Three rows, then the list scrolls; the padding keeps the keys' raised
     edges from being clipped by the scroll box. */
  max-height: calc(var(--key-h) * 3 + var(--key-gap) * 2 + .5rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: .125rem .125rem .375rem;
  margin: -.125rem;
  scrollbar-width: thin;
}
/* the console's picker is the same key as everywhere else */
.console__option {
  display: flex; align-items: center; justify-content: center;
  min-height: var(--key-h);
  padding: .25rem .375rem;
  font-family: var(--mono);
  font-size: clamp(.75rem, .7rem + .22cqi, .875rem); font-weight: 700; letter-spacing: .05em;
  line-height: 1.1; text-align: center; overflow-wrap: anywhere;
  border-radius: clamp(8px, 6px + .6cqi, 11px);
  border: 1px solid var(--line-2);
  background: var(--key-face);
  color: var(--ink-2);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  box-shadow:
    inset 0 1px 0 var(--raise),
    0 2px 0 0 var(--line-2),
    0 3px 6px -3px rgb(40 20 10 / 26%);
  transition:
    transform var(--t-fast) var(--ease), border-color var(--t-fast),
    background-color var(--t) var(--ease), color var(--t-fast), box-shadow var(--t-fast);
  animation: key-in .42s var(--ease) both;
  animation-delay: min(calc(var(--i, 0) * 12ms), 240ms);
}
@media (hover: hover) {
  .console__option:hover:not(:disabled):not(.is-selected) {
    color: var(--brand-deep);
    border-color: color-mix(in srgb, var(--brand) 45%, var(--line-2));
    transform: translateY(1px);
    box-shadow:
      inset 0 1px 0 var(--raise),
      0 1px 0 0 var(--line-2),
      0 2px 3px -2px rgb(40 20 10 / 22%);
  }
}
.console__option:active:not(:disabled) { transform: translateY(2px); box-shadow: inset 0 1px 0 var(--raise); }
.console__option.is-wide { grid-column: span 2; }
/* keys that were already on screen when the full list opened stay put */
.console__option.is-settled { animation: none; }
/* the way to the rest of the formats: a key, but plainly not a format */
.console__option.console__more {
  font-family: var(--font); font-weight: 650; letter-spacing: -.01em; white-space: nowrap;
  color: var(--ink-3);
  background: transparent;
  border: 1px dashed var(--line-2);
  box-shadow: none;
}
@media (hover: hover) {
  .console__option.console__more:hover {
    color: var(--brand-deep); transform: none;
    border-color: color-mix(in srgb, var(--brand) 45%, var(--line-2));
    background: var(--brand-softer); box-shadow: none;
  }
}
.console__option.is-selected {
  background: var(--brand-grad); border-color: transparent; color: #fff;
  transform: translateY(2px);
  box-shadow:
    inset 0 1px 2px rgb(120 30 10 / 30%),
    0 0 0 1px color-mix(in srgb, var(--brand) 40%, transparent),
    0 4px 12px -4px color-mix(in srgb, var(--brand) 65%, transparent);
}
.console__option:disabled {
  opacity: .38; cursor: not-allowed; transform: none;
  background: var(--surface-2); box-shadow: none;
}

@keyframes key-in {
  from { opacity: 0; transform: translateY(6px) scale(.97); }
}

/* --- the action --- */

.console__action {
  position: relative;
  display: grid; justify-items: stretch; gap: calc(var(--step) * .6);
  margin-top: calc(var(--step) * 1.5);
  padding-top: calc(var(--step) * 1.5);
}
/* a hairline that fades at both ends, rather than a dashed rule */
.console__action::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-2) 18%, var(--line-2) 82%, transparent);
}
.btn--lg { padding: 1rem 1.5rem; font-size: 1rem; border-radius: var(--r-md); }
.console__go {
  width: 100%;
  min-height: clamp(3.25rem, 2.9rem + 1.4cqi, 3.75rem);
  padding: .75rem 1.25rem;
  gap: .625rem;
  font-size: clamp(.9375rem, .87rem + .3cqi, 1.0625rem); letter-spacing: -.01em;
  border-radius: clamp(14px, 10px + 1.2cqi, 18px);
  transition:
    transform var(--t) var(--ease), box-shadow var(--t) var(--ease),
    background-color var(--t) var(--ease), filter var(--t-fast);
}
.console__go-label { overflow: hidden; text-overflow: ellipsis; }
.console__go-arrow { display: flex; transition: transform var(--t) var(--ease); }
@media (hover: hover) {
  .console__go:hover { transform: translateY(-1px); filter: brightness(1.04); }
  .console__go:hover .console__go-arrow { transform: translateX(4px); }
}
.console__go:active { transform: translateY(0) scale(.985); transition-duration: var(--t-fast); }
.console__go:focus-visible { outline: 2.5px solid var(--brand); outline-offset: 3px; }

/* why this pair, in a sentence, just above the button it justifies */
.console__why {
  font-size: clamp(.8125rem, .76rem + .25cqi, .9375rem); line-height: 1.45;
  color: var(--ink-3);
  text-align: center; text-wrap: balance;
  max-width: 62ch; justify-self: center;
}
.console__note {
  font-size: clamp(.75rem, .72rem + .15cqi, .8125rem); color: var(--ink-4);
  text-align: center; text-wrap: balance;
}

/* --- a file being dragged over the page --- */

.console { transition: box-shadow var(--t-slow) var(--ease), transform var(--t) var(--ease); }
.console__drop {
  position: absolute; inset: 0; z-index: 5;
  display: grid; place-content: center; justify-items: center; gap: .375rem;
  border-radius: inherit; text-align: center; pointer-events: none;
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity var(--t) var(--ease);
}
.console__drop strong { font-size: clamp(1.125rem, 1rem + .6cqi, 1.375rem); letter-spacing: -.02em; }
.console__drop span:last-child { font-size: .875rem; color: var(--ink-3); }
.console__drop-icon {
  display: grid; place-items: center; width: 3rem; height: 3rem; margin-bottom: .25rem;
  border-radius: 1rem; color: #fff; background: var(--brand-grad); box-shadow: var(--sh-brand);
}
body.is-dragging-global .console {
  transform: scale(1.01);
  box-shadow:
    0 0 0 2px var(--brand),
    0 0 0 8px color-mix(in srgb, var(--brand) 14%, transparent),
    var(--sh-3);
}
body.is-dragging-global .console__drop { opacity: 1; }
body.is-dragging-global .console__drop-icon { animation: drop-bob .9s var(--ease) infinite alternate; }
@keyframes drop-bob { to { transform: translateY(-5px); } }
/* The card is the target, so the page-wide outline would only compete. */
body.is-dragging-global:has(.console)::after { display: none; }
.console__unavailable { font-size: .875rem; color: var(--err); text-align: center; text-wrap: balance; }

/* --- narrow: a phone, or anything as narrow as one --- */

@container console (max-width: 520px) {
  /* The tabs become a full-width switch with three equal halves-and-a-third,
     so the indicator never has to cross a lopsided gap. */
  .console__tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); width: 100%; }
  .console__tab { padding-inline: .5rem; }
  /* Four keys across: wide enough for a thumb, and a name like JPEG 2000
     wraps inside its key instead of being cut. */
  .console__options { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .console__slot { padding-inline: .5rem; }
}
@container console (max-width: 340px) {
  .console__option.console__more { font-size: .6875rem; padding-inline: .125rem; }
}

@media (prefers-reduced-motion: reduce) {
  .console__option, .console__file { animation: none; }
  body.is-dragging-global .console { transform: none; }
  body.is-dragging-global .console__drop-icon { animation: none; }
  .console__slots .console__swap { transition: color var(--t-fast), border-color var(--t-fast); }
}

/* The stats become a quiet strip, not four glass cards competing with the
   console for attention. */
.hero__stats {
  grid-template-columns: repeat(4, auto);
  gap: 2.25rem;
  margin-top: 2.5rem;
}
.hero__stats .stat {
  padding: 0; background: none; box-shadow: none; border: 0;
}
.hero__stats .stat:hover { box-shadow: none; }
.hero__stats .stat strong { font-size: 1.25rem; font-family: var(--mono); }
.hero__stats .stat span { font-size: .75rem; }
@media (max-width: 620px) {
  .hero__stats { grid-template-columns: repeat(2, auto); gap: 1.25rem 2rem; }
}

/* ==========================================================================
   The slot
   --------------------------------------------------------------------------
   The dropzone's centrepiece. A recessed mouth with a file card hovering
   above it; the card slides in as you approach and disappears into the slot
   when you drag something over. Everything here is transform and opacity, so
   it composites and never touches layout.
   ========================================================================== */

.mailslot {
  position: relative;
  width: 92px;
  height: 74px;
  margin: 0 auto;
}

/* the card waiting to go in */
.mailslot__card {
  position: absolute;
  left: 50%;
  top: 4px;
  width: 54px;
  height: 46px;
  margin-left: -27px;
  border-radius: var(--r-xs) 12px var(--r-xs) var(--r-xs);
  background: var(--surface);
  border: 1.5px solid var(--line-2);
  box-shadow: var(--sh-1);
  transition: transform var(--t-slow) var(--ease), opacity var(--t) var(--ease);
}
.mailslot__corner {
  position: absolute; top: -1px; right: -1px;
  width: 13px; height: 13px;
  background: var(--surface-3);
  border-left: 1.5px solid var(--line-2);
  border-bottom: 1.5px solid var(--line-2);
  border-radius: 0 12px 0 4px;
}
.mailslot__line {
  position: absolute; left: 9px; height: 3px; border-radius: 2px;
  background: var(--line-2);
}
.mailslot__line:nth-of-type(2) { top: 22px; width: 26px; }
.mailslot__line:nth-of-type(3) { top: 30px; width: 18px; }

/* the mouth it goes into */
.mailslot__mouth {
  position: absolute;
  left: 0; right: 0; bottom: 6px;
  height: 15px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  box-shadow:
    inset 0 3px 6px -1px rgb(90 50 30 / 30%),
    inset 0 -1px 0 0 var(--raise);
}
/* the lip that the card passes behind */
.mailslot__mouth::after {
  content: '';
  position: absolute; inset: auto 6px 0 6px; height: 7px;
  border-radius: var(--r-pill);
  background: var(--brand-grad);
  opacity: 0;
  transition: opacity var(--t) var(--ease);
}

.dropzone:hover .mailslot__card,
.dropzone:focus-visible .mailslot__card { transform: translateY(9px) rotate(-2deg); }

/* on drag-over the card posts itself and the slot lights up */
.dropzone.is-over .mailslot__card { transform: translateY(32px) scale(.9); opacity: 0; }
.dropzone.is-over .mailslot__mouth::after { opacity: 1; }

.queue__add .mailslot { width: 60px; height: 48px; }
.queue__add .mailslot__card { width: 36px; height: 30px; margin-left: -18px; top: 2px; }
.queue__add .mailslot__line:nth-of-type(2) { top: 14px; width: 17px; }
.queue__add .mailslot__line:nth-of-type(3) { top: 20px; width: 11px; }
.queue__add .mailslot__corner { width: 9px; height: 9px; }
.queue__add .mailslot__mouth { height: 11px; bottom: 4px; }

/* ==========================================================================
   The receipt
   --------------------------------------------------------------------------
   Finished work comes back as a torn stub rather than a green banner: mono
   figures, a tear line, and a real before/after. The zigzag is a repeating
   conic gradient on a pseudo-element — no images, no extra DOM.
   ========================================================================== */

.receipt__head {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  padding: 1.5rem 1.5rem 2rem;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--ok-soft);
  border: 1px solid var(--line);
  border-bottom: 0;
  box-shadow: var(--sh-2);
}
.receipt__headline { display: grid; gap: .25rem; min-width: 0; }
/* the torn bottom edge */
.receipt__head::after {
  content: '';
  position: absolute;
  left: -1px; right: -1px; bottom: -10px;
  height: 11px;
  background:
    conic-gradient(from 135deg at 50% 0, var(--surface) 0 25%, transparent 0 100%)
    0 0 / 16px 11px repeat-x;
  filter: drop-shadow(0 1px 0 var(--line));
  z-index: 1;
}

/*
 * The stub below the tear: the files that came out, then the one thing to do
 * with them. Sharing the card means the result reads as a single object
 * rather than four things that happen to be stacked.
 */
.receipt {
  display: grid;
  animation: fade-up .35s var(--ease) both;
}
.receipt__body {
  padding: 1.5rem 1.5rem 1.5rem;
  border: 1px solid var(--line);
  border-top: 0;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  background: var(--surface);
  box-shadow: var(--sh-2);
}
.receipt__action { display: grid; gap: .5rem; margin-top: 1.25rem; }
.receipt__again { color: var(--ink-3); }

/* A file row's name is arbitrary text and must never widen the card. */
.result-row__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result-row__body { min-width: 0; }
.results__meta {
  font-family: var(--mono); font-size: .8125rem; letter-spacing: -0.01em;
  display: flex; align-items: center; gap: .5rem; white-space: nowrap;
}
.results__delta {
  font-family: var(--mono); font-weight: 700; font-size: .75rem;
  padding: .125rem .5rem; border-radius: var(--r-pill);
  background: var(--ok-soft); color: var(--ok);
}
.results__delta[data-dir="up"] { background: var(--surface-3); color: var(--ink-3); }

/* ==========================================================================
   Composition
   --------------------------------------------------------------------------
   The page was sixteen white rounded rectangles on an off-white ground, every
   one of them centred. That reads as a template no matter how nice the
   colours are. This gives it a spine instead: a full-strength colour field at
   the top, type set hard to the left, a technical spec strip, one bordered
   block of tools rather than a scatter of cards, and a dark band so the eye
   has somewhere to rest before the end.
   ========================================================================== */

/* ------------------------------------------------------------- the eyebrow */

.eyebrow {
  display: flex; align-items: center; gap: .75rem; white-space: nowrap;
  font-family: var(--mono);
  font-size: .75rem; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3);
}
.eyebrow__rule { width: 26px; height: 1px; background: currentColor; opacity: .45; flex: none; }
.eyebrow--light { color: rgb(255 255 255 / 80%); }

/* --------------------------------------------------------------- the stage */

/*
 * The converter is the top of the page.
 *
 * It used to ride up over an orange banner with the headline in it, and the
 * two read as a pair of stacked boxes competing for the first glance. With
 * the banner gone the page opens on the thing people came to use. The gap
 * above it follows the screen's height, so a tall monitor gets air and a
 * short laptop does not spend its fold on margin.
 */
.stage { position: relative; padding-top: clamp(.75rem, 3.5vh, 2.5rem); }
.stage__console { position: relative; }

/*
 * On a phone the converter takes the whole first screen.
 *
 * The room the banner used goes to the format list, which grows to fill it,
 * and the convert button lands at the bottom of the screen where a thumb
 * already is. A family with more formats than fit still scrolls inside the
 * list, so the button never drops below the fold. The floor stops a phone
 * held sideways from squashing the list to nothing.
 *
 * Placed with the component rather than in the page-wide responsive block,
 * where an override sits above the base rules and loses the cascade.
 */
@media (max-width: 620px) {
  .stage { padding-top: .5rem; }
  /* A floor rather than a fixed height: the card fills the screen, and on a
     phone too short for everything it grows instead of clipping. */
  .stage__console .console {
    display: flex; flex-direction: column;
    min-height: max(calc(100vh - var(--head-h) - 1.25rem), 500px);
    min-height: max(calc(100svh - var(--head-h) - 1.25rem), 500px);
    box-shadow: inset 0 1px 0 color-mix(in srgb, var(--raise) 50%, transparent), var(--sh-1), var(--sh-2);
  }
  .stage__console .console__slots { flex: none; }
  /* On a tall phone the slots take some of the height too, so the pair reads
     as the headline of the screen rather than a strip across the top. */
  .stage__console .console__slot { min-height: clamp(7.75rem, 16svh, 10.5rem); }
  .stage__console .console__picker { flex: 1; min-height: 0; display: flex; flex-direction: column; }
  .stage__console .console__options {
    flex: none; max-height: none;
    grid-auto-rows: max(var(--key-h), 3.25rem);
  }
  /*
   * The free height goes to the file, not to empty card.
   *
   * On a phone the photo someone wants to convert is usually already on the
   * phone, so choosing it is the most natural way in. The head is unwrapped
   * so the file button can drop below the keys and grow into a proper target
   * that fills whatever height the card has left.
   */
  .stage__console .console__picker-head { display: contents; }
  .stage__console .console__picker-label { margin-bottom: calc(var(--step) * .75); }
  .stage__console .console__intake {
    order: 2; flex: 1 1 3.25rem; min-height: 3.25rem; max-height: 9rem;
    margin-top: calc(var(--key-gap) + .25rem);
  }
  .stage__console .console__use-file,
  .stage__console .console__file {
    flex: 1; justify-content: center;
    border-radius: calc(var(--radius) - var(--pad) / 2.5);
  }
  .stage__console .console__use-file {
    flex-direction: column; gap: .125rem;
    font-size: .9375rem;
    background: color-mix(in srgb, var(--brand) 4%, transparent);
  }
  .stage__console .console__use-file svg { width: 20px; height: 20px; margin-bottom: .125rem; }
  .stage__console .console__use-file-sub { display: block; }
  .stage__console .console__file { padding-inline: .875rem .5rem; }
  .stage__console .console__file-name { max-width: none; flex: 0 1 auto; }
  .stage__console .console__action { flex: none; }
}
/*
 * A phone with very little height — the first iPhone SE, or any phone with
 * its keyboard or a banner up. The button staying on screen matters more
 * than the sentence explaining the pair, so that goes, and the keys drop
 * back to their ordinary height.
 */
@media (max-width: 620px) and (max-height: 640px) {
  .stage__console .console__why { display: none; }
  /* Too short to fill: the card is as tall as what is in it. */
  .stage__console .console { min-height: 0; }
  .stage__console .console__slot { min-height: 0; padding-block: .5rem; gap: .25rem; }
  .stage__console .console__slot .token--xl { font-size: 1.1875rem; }
  .stage__console .console__tabs { margin-bottom: .5rem; }
  .stage__console .console__tab { min-height: 2.25rem; }
  .stage { padding-top: .25rem; }
  /* with no room for a second line, the file button goes back to one row */
  .stage__console .console__use-file { flex-direction: row; gap: .375rem; }
  .stage__console .console__use-file svg { width: 16px; height: 16px; margin: 0; }
  .stage__console .console__options { grid-auto-rows: var(--key-h); }
  .stage__console .console__intake { flex-basis: 2.75rem; min-height: 2.75rem; }
  .stage__console .console__use-file-sub { display: none; }
}

/* The page gutter is 9px here; the card's rounded corners and shadow want a
   little more room from the edge of the glass than that. */
@media (max-width: 420px) {
  .stage { padding-inline: .3125rem; }
}

/* ---------------------------------------------------------------- the tools */

.group__head { margin-bottom: 1.5rem; max-width: 60ch; }
.group__title {
  font-size: clamp(1.7rem, 3.4vw, 2.4rem);
  font-weight: 800; letter-spacing: -0.04em; margin-top: .75rem;
}
.group__blurb { color: var(--ink-3); font-size: 1rem; margin-top: .5rem; }

/* One block, subdivided by hairlines — not a scatter of floating cards. */
.tool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--sh-1);
}

.tool-card {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas: 'num icon arrow' 'title title title' 'text text text';
  gap: .5rem .75rem;
  align-items: center;
  padding: 1.5rem 1.25rem;
  text-decoration: none;
  /* hairlines instead of gaps: the block stays one object */
  box-shadow: 1px 0 0 var(--line), 0 1px 0 var(--line);
  transition: background var(--t-fast);
}
/*
 * --- the travelling light ---
 *
 * One pool of warmth follows the pointer across the whole block. It is
 * painted on the grid rather than on the cards, so it crosses the hairlines
 * instead of stopping at them, which is the entire point: the grid is drawn
 * as one sheet and it should light like one.
 *
 * `@property` is what makes the pool able to ease. Without a registered type
 * the browser treats a custom property as a string and jumps between values,
 * so the light would teleport when the pointer re-entered rather than
 * arriving.
 */
.tool-grid { position: relative; isolation: isolate; }

/*
 * The card under the pointer warms; nothing follows the cursor.
 *
 * There used to be two radial pools tracking the pointer across the whole
 * grid. It was nice to look at and it was one more thing moving on a page
 * that already had plenty, and on a listing of fifty-odd cards the effect
 * was reading as noise rather than as light. The card's own hover state
 * does the job the hover state is actually for: saying which one you are
 * about to click.
 */
.tool-card { position: relative; }
/*
 * The rim on the hovered card. Drawn as an inset ring rather than a border
 * so it cannot shift the grid by a pixel, and kept beneath the pool's layer
 * so the light still passes over it.
 */
.tool-card::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--accent) 42%, transparent),
    inset 0 1px 0 0 color-mix(in srgb, var(--accent) 18%, transparent);
  transition: opacity .38s var(--ease-soft);
}
.tool-card:hover::after { opacity: 1; }

/*
 * One change per hover, not four.
 *
 * The card used to warm, lift and rotate its icon, recolour its number and
 * loosen its title's letter-spacing all at once — five simultaneous
 * animations to say "this is the one under your cursor", on a page where
 * that was happening beside dozens of other cards. Re-spacing live text is
 * the worst of them: it nudges the words as you read them. The surface
 * changes, the arrow slides in, and that is enough.
 */
.tool-card:hover { background: var(--surface-2); }
.tool-card:hover .tool-card__icon {
  box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--accent) 65%, transparent);
}
.tool-card:hover .tool-card__num { color: var(--accent); }
.tool-card__num {
  grid-area: num;
  font-family: var(--mono); font-size: .75rem; font-weight: 700;
  color: var(--ink-4); letter-spacing: .06em;
  transition: color var(--t-fast);
}
.tool-card__icon {
  grid-area: icon; justify-self: start;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--r-sm); color: #fff;
  background: var(--accent-fill, var(--fill-brand));
  transition: box-shadow .42s var(--ease);
}
.tool-card__arrow {
  grid-area: arrow; display: flex; color: var(--ink-4);
  opacity: 0; transform: translateX(-5px);
  transition: opacity var(--t) var(--ease), transform var(--t) var(--ease), color var(--t-fast);
}
.tool-card:hover .tool-card__arrow { opacity: 1; transform: none; color: var(--accent); }
.tool-card__title {
  grid-area: title;
  font-size: 1rem; font-weight: 700; letter-spacing: -0.025em; margin-top: .375rem;
}
.tool-card__text { grid-area: text; color: var(--ink-3); font-size: .875rem; line-height: 1.55; }

/* ==========================================================================
   Responsive
   --------------------------------------------------------------------------
   Three real breakpoints, chosen where the content actually breaks rather
   than at device names: below 700px everything is one column, 700–980 is the
   in-between where two columns still fit but the type must come down, and
   above that is the full layout.
   ========================================================================== */

/* ------ small: phones ------ */
@media (max-width: 700px) {
  .container { width: min(1200px, 100% - 1.625rem); }
  .site-head__inner { gap: .75rem; }
  .site-nav { gap: 0; }
  .site-nav a { --nav-pad: .5rem; padding: .375rem var(--nav-pad); font-size: .875rem; }

  .tool-grid { grid-template-columns: minmax(0, 1fr); }
  .group { margin-top: 3.25rem; }
  .why { padding-inline: 0; }
  .explore { grid-template-columns: minmax(0, 1fr); }


  .convert-more__grid { grid-template-columns: minmax(0, 1fr); }
  .pair-grid { grid-template-columns: minmax(0, 1fr); }
  .token-grid { grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); }

  .receipt__head { gap: .75rem; padding: 1.25rem 1.25rem 1.5rem; }
  .receipt__body { padding: 1.25rem 1.25rem 1.25rem; }
  .results__meta { flex-wrap: wrap; white-space: normal; }

}

/* ------ the header runs out of room before anything else ------ */
@media (max-width: 560px) {
  /* The mark alone still identifies the site, and four destinations matter
     more than the wordmark does. */
  .logo__text { display: none; }
  /*
   * Seven controls in 390px made every one of them small. The theme switch
   * and Formats step out of the header here; both are one scroll away in the
   * footer, which has the full three-way theme picker and the Formats link.
   */
  .site-head__inner .site-theme { display: none; }
  .site-nav a[href="/formats"] { display: none; }
  .site-head__inner { gap: .5rem; }
  .site-nav a { --nav-pad: .5rem; padding: .375rem var(--nav-pad); font-size: .8125rem; }
}

/* ------ very small: narrow phones ------ */
@media (max-width: 420px) {
  .container { width: min(1200px, 100% - 1.125rem); }
  .site-nav a { --nav-pad: .375rem; padding: .25rem var(--nav-pad); font-size: .75rem; }
  .token-grid { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
  .btn--lg { padding: .875rem 1rem; font-size: .9375rem; }
}

/* ------ medium: tablets ------ */
@media (min-width: 701px) and (max-width: 980px) {
  .tool-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/*
 * Short screens, which is a different constraint from narrow ones.
 *
 * Height is the scarce axis here, and it is scarce on far more devices than
 * it looks: a phone reports 812 points but hands the page about 660 of them
 * once the browser's own chrome is counted, and a 1366 by 768 laptop has
 * less usable height than a phone held upright. So these are keyed to
 * height alone, not to width.
 *
 * What gives is the air between the converter's four blocks, never the
 * controls: every tap target keeps its own padding, and only the gaps close.
 */
@media (max-height: 820px) {
  .console { --step: clamp(.625rem, .4rem + 1.1cqi, 1rem); }
  .console__slot { min-height: clamp(7rem, 5.25rem + 6cqi, 8.75rem); }
}
@media (max-height: 740px) {
  .console { --step: clamp(.5rem, .35rem + .8cqi, .75rem); --pad: clamp(.875rem, .5rem + 2cqi, 1.5rem); }
  .console__slot { min-height: 6.5rem; }
  /* The format list already scrolls; on a short screen it shows two rows at
     a time instead of three. Every format is still reachable. */
  .console__options { max-height: calc(var(--key-h) * 2 + var(--key-gap) + .5rem); }
}
@media (max-height: 640px) {
  .console__slot { min-height: 0; }
}


/* ==========================================================================
   Below the console
   --------------------------------------------------------------------------
   This half of the page used to be four numbers, sixteen grey cells and three
   columns of prose — all of it text in boxes. It is now three objects built
   from the same keys as everything else: conversions you can press, every
   format laid out as a keyboard, and the route a file takes drawn instead of
   described.
   ========================================================================== */

/*
 * A door is a way in, not a listing.
 *
 * Both the tool catalogue and the format wall used to print their entire
 * contents on the front page — sixty-six cards and sixty-five chips, which
 * together were most of the scroll. A door names the family, states how many
 * are behind it, and opens. The count is the part that does the work: it
 * answers "is my thing in there" without printing the list.
 */
.doors {
  display: grid;
  gap: .75rem;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

.door {
  position: relative;
  display: grid;
  grid-template-areas: 'eyebrow go' 'count count' 'label label';
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: .25rem;
  padding: 1.125rem 1.25rem 1.25rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--surface-2);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.door:hover { border-color: var(--accent, var(--brand)); background: var(--surface-3); }

.door__eyebrow {
  grid-area: eyebrow;
  font-family: var(--mono); font-size: .6875rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-4);
}
.door__count {
  grid-area: count;
  font-size: clamp(1.75rem, 3.2vw, 2.25rem); font-weight: 800;
  letter-spacing: -0.04em; line-height: 1.05;
  color: var(--accent, var(--brand-deep));
  font-variant-numeric: tabular-nums;
}
.door__label { grid-area: label; color: var(--ink-3); font-size: .875rem; }
.door__go {
  grid-area: go; display: flex; color: var(--ink-4);
  transition: transform var(--t) var(--ease), color var(--t-fast);
}
.door:hover .door__go { transform: translateX(3px); color: var(--accent, var(--brand)); }

@media (prefers-reduced-motion: reduce) {
  .door, .door__go { transition: none; }
  .door:hover .door__go { transform: none; }
}

/*
 * The rhythm, loosened because there is less to separate.
 *
 * 4.5rem between eleven sections reads as a wall; 7rem between six reads as
 * structure. The heading keeps more space above it than below so it groups
 * with the content it introduces rather than floating between two blocks —
 * the oldest rule in typesetting and the one most often skipped in CSS.
 */
.home-section { margin-top: clamp(4.5rem, 8vw, 7rem); }
.home-section__head { margin-bottom: 1.75rem; }
.home-section__row {
  display: flex; align-items: baseline; gap: 1.25rem;
  flex-wrap: wrap; margin-top: .75rem;
}
.home-section__title {
  font-size: clamp(1.55rem, 3.2vw, 2.2rem);
  font-weight: 800; letter-spacing: -0.04em;
}
.home-section__row .text-link { margin-top: 0; margin-left: auto; }
.home-section__text { color: var(--ink-3); font-size: 1rem; margin-top: .5rem; max-width: 62ch; }

/* ------------------------------------------------------------- the shelf */

.shelf { display: flex; flex-wrap: wrap; gap: .625rem; }
.shelf__pair {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .5rem .625rem; border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--line);
  text-decoration: none; box-shadow: var(--sh-1);
  animation: fade-up .5s var(--ease) both; animation-delay: var(--d, 0ms);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.shelf__pair[data-kind="image"] { --accent: var(--image); }
.shelf__pair[data-kind="video"] { --accent: var(--video); }
.shelf__pair[data-kind="audio"] { --accent: var(--audio); }
.shelf__pair:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  box-shadow: var(--sh-2);
}
.shelf__arrow { display: flex; color: var(--ink-4); }
.shelf__pair:hover .shelf__arrow { color: var(--accent); }/* a wire that stops short of the server, cut clean */
@media (max-width: 700px) {  .home-section { margin-top: 3.25rem; }
  .home-section__row .text-link { margin-left: 0; }
}


/* ------------------------------------------------------------- the ticker */

.ticker {
  position: relative; z-index: 1; overflow: hidden;
  margin-top: 2.5rem; padding-top: 1.5rem;
  border-top: 1px solid rgb(255 255 255 / 22%);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
/*
 * Every override of the rule above has to sit below it.
 *
 * A media query adds no specificity, so a `.ticker` rule inside one loses to
 * a plain `.ticker` rule written later in the file — silently, which is the
 * worst way to lose. The hero's height tiers are declared hundreds of lines
 * above this, so their ticker overrides belong here instead, next to the
 * thing they are overriding.
 */
@media (max-width: 620px) { .ticker { margin-top: 1.5rem; padding-top: 1rem; } }

/* Short screens close the gap. */
@media (max-height: 900px) { .ticker { margin-top: 1.375rem; padding-top: 1rem; } }
@media (max-height: 800px) { .ticker { margin-top: .875rem; padding-top: .625rem; } }

/*
 * Below this the ticker gives up its space entirely so the convert button
 * stays on screen. It is decoration, and every format scrolling past in it
 * is also a real link further down the page, so nothing is actually lost.
 *
 * The threshold is measured, not guessed: with everything else tightened,
 * 610px of viewport is the last height where the ticker and the button both
 * fit, so it survives on every phone in ordinary use and only steps aside
 * on genuinely tiny windows.
 */
@media (max-height: 620px) { .ticker { display: none; } }
.ticker__track { display: flex; width: max-content; animation: ticker 64s linear infinite; }
.ticker__row { display: flex; gap: .5rem; padding-right: .5rem; }
.ticker__item {
  font-family: var(--mono); font-size: .75rem; font-weight: 700; letter-spacing: .06em;
  color: rgb(255 255 255 / 82%);
  padding: .25rem .625rem; border-radius: var(--r-xs);
  border: 1px solid rgb(255 255 255 / 26%);
  white-space: nowrap;
}
@keyframes ticker { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .ticker__track { animation: none; } }


/* ==========================================================================
   Interaction components
   --------------------------------------------------------------------------
   Vanilla rebuilds of ideas from React Bits, Origin UI and Kokonut UI. Every
   moving part is a transform or an opacity on its own layer.
   ========================================================================== */

/* ------------------------------------------------------------- split-flap */

.flap-board { display: inline-flex; gap: 1px; }
.flap {
  display: inline-block;
  perspective: 240px;
  background: rgb(28 25 23 / 4%);
  border-radius: 2px;
  padding: 0 .06em;
}
.flap__char {
  display: block;
  transform-origin: center bottom;
  backface-visibility: hidden;
  will-change: transform;
}

/* -------------------------------------------------------------- gooey nav */

.console__tabs { position: relative; }
.gooey {
  position: absolute; left: 0; top: .25rem; bottom: .25rem;
  border-radius: var(--r-pill); background: var(--surface);
  box-shadow: var(--sh-1);
  opacity: 0; transform-origin: center;
  transition: opacity var(--t) var(--ease);
  pointer-events: none;
}
.gooey.is-on { opacity: 1; }
.console__tab[aria-selected="true"] { background: transparent; box-shadow: none; }
.console__tab { position: relative; z-index: 1; }
.console__slot-token { justify-self: center; }

/* ---------------------------------------------------------- elastic slider */

.eslider { position: relative; padding: .5rem 0; }
.eslider__input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}
.eslider__track {
  position: relative; display: block; height: 6px;
  border-radius: var(--r-pill); background: var(--surface-3);
  transition: transform var(--t) var(--ease);
}
.eslider.is-dragging .eslider__track { transform: scaleY(1.5); }
.eslider__fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  border-radius: var(--r-pill); background: var(--brand-grad);
  transform-origin: left center;
}
.eslider__handle { position: absolute; top: 50%; left: 0; width: 100%; height: 0; pointer-events: none; }
.eslider__handle::after {
  content: '';
  position: absolute; top: -10px;
  left: clamp(10px, var(--handle, 50%), calc(100% - 10px));
  width: 20px; height: 20px; margin-left: -10px;
  border-radius: 50%; background: var(--knob);
  border: 2.5px solid var(--brand);
  box-shadow: 0 2px 8px -1px color-mix(in srgb, var(--brand) 45%, transparent);
  transition: transform var(--t) var(--ease);
}
.eslider.is-dragging .eslider__handle::after { transform: scale(1.22); }
.eslider__ticks { display: flex; justify-content: space-between; margin-top: .5rem; padding: 0 2px; }
.eslider__ticks i { width: 1px; height: 4px; background: var(--line-2); border-radius: 2px; }

/*
 * A slider without ticks is 22px tall: half a centimetre of drag target for a
 * control operated with a thumb. The input sits over the track absolutely, so
 * its hit area can be pushed past the visible edges to reach the 44px a touch
 * target wants — nothing moves, and nothing else grows.
 *
 * The 11px each way is safe because settings sit 43px apart, and what falls
 * within reach above and below is the container rather than another control.
 */
@media (pointer: coarse) {
  .eslider__input { inset: -11px 0; height: auto; }
}

/* ---------------------------------------------------------- ring progress */

.rings__wrap { position: relative; display: inline-flex; align-items: center; justify-content: center; }
.rings { display: block; overflow: visible; }
.rings__ring {
  fill: none;
  stroke: color-mix(in srgb, var(--brand) 26%, transparent);
  stroke-width: 1.2; stroke-dasharray: 5 9;
  transform-origin: 32px 32px;
  animation: spin calc(4s + var(--i) * .7s) linear infinite;
}
.rings__ring:nth-child(even) {
  animation-direction: reverse;
  stroke: color-mix(in srgb, var(--brand-2) 30%, transparent);
}
.rings__arc {
  fill: none; stroke: var(--brand); stroke-width: 3.5; stroke-linecap: round;
  transform: rotate(-90deg); transform-origin: 32px 32px;
  transition: stroke-dashoffset .3s var(--ease-soft);
}
.rings__label {
  position: absolute;
  font-family: var(--mono); font-size: .75rem; font-weight: 700;
  color: var(--brand-deep);
}
@media (prefers-reduced-motion: reduce) { .rings__ring { animation: none; } }

/* ---------------------------------------------------------------- stepper */

.stepper { display: grid; gap: .75rem; }
.stepper__head { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.stepper__name { font-size: .9375rem; font-weight: 700; }
.stepper__hint { font-size: .8125rem; color: var(--ink-3); }
.stepper__rail { position: relative; padding: .375rem 0; }
.stepper__track {
  position: absolute; left: 10px; right: 10px; top: 50%; height: 4px;
  margin-top: -2px; border-radius: var(--r-pill); background: var(--surface-3);
}
.stepper__fill {
  position: absolute; inset: 0 auto 0 0; border-radius: var(--r-pill);
  background: var(--brand-grad); transition: width var(--t) var(--ease);
}
.stepper__stops { position: relative; display: flex; justify-content: space-between; }
.stepper__stop {
  width: 20px; height: 20px; padding: 0; cursor: pointer;
  border: 0; background: transparent;
  display: flex; align-items: center; justify-content: center;
}
.stepper__dot {
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--line-2);
  transition: transform var(--t) var(--ease), border-color var(--t-fast), background var(--t-fast);
}
.stepper__stop:hover .stepper__dot { border-color: var(--brand); }
.stepper__stop.is-on .stepper__dot { border-color: var(--brand); background: var(--brand); }
.stepper__stop.is-current .stepper__dot { transform: scale(1.5); box-shadow: 0 0 0 4px var(--brand-soft); }

/* ------------------------------------------------------------ rich select */

.rselect { position: relative; }
.rselect__button {
  display: flex; align-items: center; gap: .5rem; width: 100%; cursor: pointer;
  padding: .625rem .875rem; border-radius: var(--r-sm);
  border: 1.5px solid var(--line-2); background: var(--surface); color: var(--ink);
  font-size: .9375rem; font-weight: 550; text-align: left;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.rselect__button:hover { border-color: var(--ink-4); }
.rselect.is-open .rselect__button { border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-soft); }
.rselect__value { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rselect__caret { display: flex; color: var(--ink-4); transition: transform var(--t) var(--ease); }
.rselect__caret svg { transform: rotate(90deg); }
.rselect.is-open .rselect__caret { transform: rotate(180deg); }
/*
 * Pinned to the viewport and placed by script, because in the top layer
 * there is no offset parent to be positioned against. The `inset` and
 * `margin` resets undo the UA's popover styling, which centres it.
 */
.rselect__list {
  position: fixed; inset: auto; margin: 0; z-index: 20;
  max-height: 244px; overflow-y: auto; padding: .25rem; border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--line-2); color: var(--ink);
  box-shadow: var(--sh-3); display: none;
}
.rselect__list::backdrop { background: none; }
.rselect.is-open .rselect__list,
.rselect__list:popover-open { display: block; animation: fade-up .18s var(--ease) both; }
.rselect.is-up .rselect__list { animation-name: fade-down; }
@keyframes fade-down {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
.rselect__option {
  display: grid; width: 100%; cursor: pointer; text-align: left;
  grid-template-columns: 1fr auto; align-items: center;
  gap: 0 .5rem; padding: .5rem .625rem; border: 0; border-radius: var(--r-chip);
  background: transparent; transition: background var(--t-fast);
}
.rselect__option:hover { background: var(--surface-2); }
.rselect__option.is-selected { background: var(--brand-softer); }
.rselect__option-main { font-size: .875rem; font-weight: 600; }
.rselect__option-note { grid-column: 1; font-size: .75rem; color: var(--ink-3); }
.rselect__tick { grid-row: 1 / -1; grid-column: 2; display: flex; color: var(--brand); }

/* ------------------------------------------------------------ icon switch */

.iswitch { display: flex; align-items: center; gap: .75rem; cursor: pointer; }
.iswitch input { position: absolute; opacity: 0; width: 0; height: 0; }
.iswitch__track {
  position: relative; flex: none; width: 46px; height: 25px;
  border-radius: var(--r-pill); background: var(--line-2);
  transition: background var(--t) var(--ease);
}
.iswitch__on, .iswitch__off {
  position: absolute; top: 50%; margin-top: -6px;
  display: flex; transition: opacity var(--t) var(--ease);
}
.iswitch__on { left: 6px; color: #fff; opacity: 0; }
.iswitch__off { right: 6px; color: var(--ink-3); opacity: 1; }
.iswitch__dot {
  position: absolute; top: 3px; left: 3px; width: 19px; height: 19px;
  border-radius: 50%; background: var(--knob);
  box-shadow: 0 1px 3px rgb(80 45 30 / 32%);
  transition: transform var(--t) var(--ease);
}
.iswitch input:checked + .iswitch__track { background: var(--brand-grad); }
.iswitch input:checked + .iswitch__track .iswitch__dot { transform: translateX(21px); }
.iswitch input:checked + .iswitch__track .iswitch__on { opacity: 1; }
.iswitch input:checked + .iswitch__track .iswitch__off { opacity: 0; }
.iswitch:hover .iswitch__track { background: var(--ink-4); }
.iswitch:hover input:checked + .iswitch__track { background: var(--brand-grad); filter: brightness(1.05); }
.iswitch input:focus-visible + .iswitch__track { outline: 2.5px solid var(--brand); outline-offset: 3px; }
.iswitch__label { font-size: .875rem; font-weight: 550; line-height: 1.4; }

/* ----------------------------------------------------------- animated list */

.file-list > *, .result-list > * { animation: list-in .42s var(--ease) both; }
.file-list > *:nth-child(1), .result-list > *:nth-child(1) { animation-delay: 0ms; }
.file-list > *:nth-child(2), .result-list > *:nth-child(2) { animation-delay: 45ms; }
.file-list > *:nth-child(3), .result-list > *:nth-child(3) { animation-delay: 90ms; }
.file-list > *:nth-child(4), .result-list > *:nth-child(4) { animation-delay: 135ms; }
.file-list > *:nth-child(n+5), .result-list > *:nth-child(n+5) { animation-delay: 175ms; }
@keyframes list-in {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to { opacity: 1; transform: none; }
}
.result-list > * { animation-name: bounce-in; }
@keyframes bounce-in {
  0% { opacity: 0; transform: translateY(16px) scale(.96); }
  55% { opacity: 1; transform: translateY(-4px) scale(1.012); }
  100% { opacity: 1; transform: none; }
}

/* ============================================================ the converter */

.tool-page { max-width: 820px; margin-inline: auto; }
.tool-page .convert-more { max-width: none; }
.flow { display: grid; gap: 1rem; min-width: 0; }

/*
 * Grid and flex children default to min-width: auto, which means "never
 * shrink below your content's intrinsic minimum". One unbreakable filename
 * in a result row was enough to blow a single column out to 567px inside a
 * 357px container, and the whole page scrolled sideways on a phone.
 *
 * The blowout also propagates: fixing the outer grid only moved the problem
 * one level in, three times over. So every layout container the app builds
 * with opts its children out in one place, rather than each being patched
 * the next time an arbitrary filename is long enough to find it.
 */
.flow > *,
.stage-done > *,
.converter > *,
.settings > *,
.settings-grid > *,
.studio__body > *,
.studio__section > *,
.receipt > *,
.result-list,
.result-list > *,
.result-row > * { min-width: 0; }

.converter {
  display: grid; gap: 1.5rem;
  padding: clamp(1.125rem, 3vw, 1.75rem);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-xl); box-shadow: var(--sh-2);
}
/* in place of the settings, until there is a file for them to act on */
.converter__later {
  display: flex; align-items: center; justify-content: center; gap: .4375rem;
  margin: -.5rem 0 0; font-size: .8125rem; color: var(--ink-4);
}
.converter__later svg { flex: none; opacity: .8; }

/*
 * The run button, pinned to the bottom of a phone screen.
 *
 * Once a file is in, the settings sit between it and the button, and on a
 * phone that put the button below the fold on arrival — the most important
 * control on the page was a scroll away. Sticky rather than fixed: it rides
 * the bottom edge while the card is on screen and settles back into place at
 * the end of it, so it never floats over the page's prose.
 */
@media (max-width: 620px) {
  .action__host.is-sticky {
    position: sticky; z-index: 20;
    bottom: calc(.625rem + env(safe-area-inset-bottom, 0px));
    margin-inline: -.5rem; padding: .5rem;
    border-radius: calc(var(--r-md) + .5rem);
    background: color-mix(in srgb, var(--surface) 86%, transparent);
    -webkit-backdrop-filter: blur(12px) saturate(1.2); backdrop-filter: blur(12px) saturate(1.2);
  }
}

/* --- the offer to install, after a download --- */

.install-slot:empty { display: none; }
.install-note {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start;
  gap: .375rem .875rem;
  padding: 1rem 1.125rem;
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--ok) 7%, var(--surface-2));
  border: 1px solid color-mix(in srgb, var(--ok) 22%, var(--line));
  animation: fade-up .45s var(--ease) both;
}
.install-note__icon {
  display: grid; place-items: center; width: 2.25rem; height: 2.25rem;
  border-radius: .75rem; color: var(--ok); background: var(--ok-soft);
}
.install-note__body { min-width: 0; }
.install-note__title { font-weight: 700; font-size: .9375rem; letter-spacing: -.01em; }
.install-note__text,
.install-note__how { margin-top: .1875rem; font-size: .8125rem; line-height: 1.45; color: var(--ink-3); }
.install-note__acts { grid-column: 2; display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .5rem; }
@media (max-width: 420px) {
  .install-note { grid-template-columns: minmax(0, 1fr); }
  .install-note__icon { display: none; }
  .install-note__acts { grid-column: 1; }
  .install-note__acts .btn { flex: 1; }
}
.converter__slots {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: stretch; gap: .875rem;
}
.converter__arrow { display: grid; place-items: center; color: var(--brand); align-self: center; }
/*
 * A stable wrapper for the output slot, so changing the target format
 * redraws the slot without disturbing anything around it. It has to stretch
 * exactly like the slot it holds, or the two sides of the grid stop matching.
 */
.converter__out { display: grid; min-width: 0; }
.converter__add { display: none; }
.converter__add input { display: block; }

/* the converter's own slots */
.slot {
  display: grid; grid-template-rows: auto 1fr auto; gap: .75rem;
  min-height: 190px; padding: 1rem;
  border-radius: var(--r-lg); border: 1.5px solid var(--line-2);
  background: var(--surface-2);
  box-shadow: inset 0 2px 6px -2px rgb(120 70 45 / 13%);
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease);
}
.slot--out { background: var(--surface); }
.slot.is-active { border-style: dashed; }
.slot__label {
  font-family: var(--mono);
  font-size: .6875rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-3);
}
.slot__body { display: grid; align-content: center; min-width: 0; }
.slot--out .slot__body { justify-items: center; }
.slot__token { justify-self: center; }
.slot__plain { text-align: center; font-weight: 650; color: var(--ink-3); }
.slot__foot { font-size: .75rem; color: var(--ink-3); }
.slot--out .slot__foot { text-align: center; }
.slot__footrow { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.slot__size { font-family: var(--mono); font-size: .75rem; }
/*
 * A real control, sized like one.
 *
 * "change format" was 81 by 14 pixels with no padding — a hit area smaller
 * than the word it contains is a target you have to aim at, and this one
 * sits beside a dropzone people are already dragging things onto. The text
 * stays the same size; the box around it grows, and a negative inline
 * margin keeps it optically flush with the row it sits in so nothing moves.
 */
.slot__link {
  display: inline-flex; align-items: center;
  min-height: 32px; padding: .25rem .5rem; margin-inline: -.5rem;
  border: 0; border-radius: var(--r-sm); background: none; cursor: pointer;
  font: inherit; font-size: .75rem; color: var(--ink-3);
  text-decoration: underline; text-underline-offset: 3px;
  transition: color var(--t-fast), background var(--t-fast);
}
.slot__link:hover { color: var(--brand-deep); background: var(--surface-2); }
.slot__link:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.slot__hint { color: var(--ink-4); }

.slot .dropzone { border: 0; background: none; box-shadow: none; padding: .5rem; }
.slot .dropzone:hover { box-shadow: none; background: none; }
.slot .dropzone::before { display: none; }
.slot .dropzone__label { font-size: 1rem; margin-top: .75rem; }
.slot .dropzone__hint { font-size: .75rem; }
.slot .file-list { margin: 0; max-height: 210px; overflow-y: auto; }
.slot .file-row { padding: .5rem .625rem; }

/* ------------------------------------------------------------ the settings */

.settings-grid {
  display: grid; gap: 1rem 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr));
  padding-top: 1.25rem; border-top: 1px solid var(--line);
}
.setting { display: grid; gap: .5rem; align-content: start; min-width: 0; }
.setting--wide { grid-column: 1 / -1; }
.setting__label {
  font-family: var(--mono);
  font-size: .6875rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3);
}
/*
 * The tracked-out micro-labels are a deliberate device and read fine on a
 * desktop, but the same rem value on a phone lands near ten pixels. These
 * name a control rather than decorate it, so they get a floor.
 *
 * Directly under the component, not in a page-wide responsive block: an
 * override there sits hundreds of lines earlier, and at equal specificity
 * the base rule below it simply wins.
 */
@media (max-width: 620px) {
  .setting__label { font-size: .75rem; letter-spacing: .09em; }
}
.setting__hint { font-size: .75rem; color: var(--ink-3); }
.setting__control > .field { gap: .375rem; }
.setting__control > .field > .field__label { display: none; }
.setting__control .field__label { font-size: .8125rem; }

/* ----------------------------------------------------------- segmented row */

.seg {
  position: relative; display: grid;
  grid-template-columns: repeat(var(--seg-n, 3), 1fr);
  padding: 3px; border-radius: var(--r-sm);
  background: var(--surface-3); border: 1px solid var(--line);
}
.seg__indicator {
  position: absolute; top: 3px; bottom: 3px; left: 3px;
  width: calc((100% - 6px) / var(--seg-n, 3));
  border-radius: var(--r-chip); background: var(--surface);
  box-shadow: 0 1px 2px rgb(80 45 30 / 14%), 0 2px 6px -2px rgb(80 45 30 / 16%);
  transform: translateX(calc(var(--seg-i, 0) * 100%));
  transition: transform var(--t) var(--ease);
}
.seg__item {
  position: relative; z-index: 1; min-width: 0;
  padding: .5rem .375rem; cursor: pointer;
  border: 0; background: none; color: var(--ink-3);
  font-size: .8125rem; font-weight: 650; line-height: 1.25; border-radius: var(--r-chip);
  transition: color var(--t-fast);
}
.seg__text { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seg__item:hover { color: var(--ink); }
.seg__item[aria-checked="true"] { color: var(--brand-deep); }
.seg__item:focus-visible { outline: 2.5px solid var(--brand); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .seg__indicator { transition: none; } }

/*
 * A control that could not fit its labels on one row, marked by the
 * component after measuring itself. Neither the viewport nor the number of
 * items decides this: five items reading "Best / High / Balanced / Small /
 * Tiny" need far more room than four reading "None / 90 / 180 / 270", and
 * both appear side by side in the same settings grid.
 *
 * The sliding indicator can only follow a single row of equal columns, so a
 * wrapped control drops it and the selected item carries the pill itself.
 * Same look, no maths to get wrong.
 */
.seg--wrap { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/*
 * An odd number of items leaves the last one alone in a two-column row,
 * which reads as a mistake, so it takes the whole row instead.
 *
 * `even` rather than `odd` because the sliding indicator is the grid's first
 * child: with it in front, the Nth item is child N+1, so an odd count of
 * items puts the last one on an even index.
 */
.seg--wrap .seg__item:nth-child(even):last-child { grid-column: 1 / -1; }
.seg--wrap .seg__indicator { display: none; }
.seg--wrap .seg__item[aria-checked="true"] {
  background: var(--surface);
  box-shadow: 0 1px 2px rgb(80 45 30 / 14%), 0 2px 6px -2px rgb(80 45 30 / 16%);
}

/* -------------------------------------------------------------- the action */

.action--running {
  display: flex; align-items: center; gap: 1rem;
  padding: .875rem 1rem; border-radius: var(--r-lg);
  background: var(--surface-2); border: 1px solid var(--line);
}
.action__text { display: grid; gap: .125rem; }
.action__text strong { font-size: .9375rem; font-weight: 700; }
.action__text span { font-size: .8125rem; color: var(--ink-3); }
.action__note { font-size: .8125rem; color: var(--ink-3); text-align: center; }

.stage-done { display: grid; gap: 1rem; min-width: 0; }
.receipt__action { margin-top: 1.25rem; }

@media (max-width: 700px) {
  /*
   * The two slots stay side by side on a phone.
   *
   * Stacked, the pair stopped reading as one movement from a source to a
   * target and became two unrelated boxes with an arrow between them. Keeping
   * the row means shrinking what is inside it instead: the dropzone loses its
   * generous padding and its graphic, and the arrow keeps pointing across.
   */
  .converter__slots { grid-template-columns: 1fr auto 1fr; gap: .375rem; }
  .converter__arrow { transform: none; }
  .slot { min-height: 0; padding: .625rem .375rem; gap: .375rem; }
  .slot .file-list { max-height: 260px; }
  .settings-grid { grid-template-columns: minmax(0, 1fr); }

  /* The dropzone is now a third of the screen wide, so it drops to a label. */
  .slot .dropzone { padding: 1.25rem .5rem; border-radius: var(--r); }
  .slot .dropzone .mailslot { display: none; }
  .slot .dropzone__label { font-size: var(--fs-4); margin-top: 0; }
  /*
   * The hint lists the formats it accepts, which runs to four lines in a
   * column a third of the screen wide. Two is enough to say "and more
   * besides"; the full list is on the page below and in the accept filter.
   */
  .slot .dropzone__hint {
    font-size: var(--fs-2);
    line-height: 1.35;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }
  .slot__label { font-size: var(--fs-1); }
  .slot__foot, .slot__footrow { font-size: var(--fs-2); }
}

/* ==========================================================================
   Search, shortcuts and messages
   ========================================================================== */

body.has-modal { overflow: hidden; }

/* ------------------------------------------------------------ the searchbar */

.searchbar {
  display: flex; align-items: center; gap: .5rem;
  margin-left: auto; cursor: pointer;
  padding: .375rem .5rem .375rem .75rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink-3); font-size: .875rem;
  min-width: 220px;
  transition: border-color var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
}
.searchbar:hover { border-color: var(--brand); color: var(--ink-2); box-shadow: var(--sh-1); }
.searchbar__icon { display: flex; }
.searchbar__text { flex: 1; text-align: left; }
.searchbar__kbd,
.pal kbd, .keys kbd {
  font-family: var(--mono); font-size: .6875rem; font-weight: 700;
  color: var(--ink-3); background: var(--surface-2);
  border: 1px solid var(--line-2); border-bottom-width: 2px;
  border-radius: 4px; padding: .125rem .375rem; line-height: 1.5;
}
.site-nav { margin-left: 0; }

@media (max-width: 980px) {
  .searchbar { min-width: 0; }
  .searchbar__text { display: none; }
}
@media (max-width: 560px) {
  .searchbar { padding: .375rem; }
  .searchbar__kbd { display: none; }
}

/* --------------------------------------------------------------- palette */

.pal { position: fixed; inset: 0; z-index: 80; display: none; }
.pal.is-open { display: block; }
.pal__backdrop {
  position: absolute; inset: 0;
  background: rgb(28 18 12 / 30%);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  animation: fade-in .16s var(--ease-soft) both;
}
.pal__panel {
  position: relative;
  width: min(600px, 100% - 1.625rem);
  margin: clamp(3rem, 10vh, 7rem) auto 0;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  box-shadow: 0 4px 12px rgb(80 45 30 / 8%), 0 32px 64px -24px rgb(80 45 30 / 34%);
  overflow: hidden;
  animation: pal-in .2s var(--ease) both;
}
@keyframes pal-in {
  from { opacity: 0; transform: translateY(-8px) scale(.99); }
  to { opacity: 1; transform: none; }
}

/* --- the field --- */

.pal__field {
  display: flex; align-items: center; gap: .75rem;
  padding: 1rem 1rem;
  border-bottom: 1px solid var(--line);
}
/* the glyph sits in its own key, so the field belongs to the same set of
   objects as everything it searches */
.pal__icon {
  display: grid; place-items: center; flex: none;
  width: 30px; height: 30px; border-radius: var(--r-chip);
  color: var(--ink-3);
  background: var(--key-face);
  border: 1px solid var(--line-2);
  box-shadow: inset 0 1px 0 var(--raise), 0 1.5px 0 0 var(--line-2);
  transition: color var(--t-fast);
}
.pal.has-query .pal__icon { color: var(--brand); }
.pal__input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font-size: 1rem; font-weight: 550; color: var(--ink);
}
.pal__input::placeholder { color: var(--ink-4); font-weight: 450; }
.pal__esc { flex: none; }

/* --- results --- */

.pal__list { max-height: min(56vh, 460px); overflow-y: auto; padding: .375rem; }
.pal__list::-webkit-scrollbar { width: 8px; }
.pal__list::-webkit-scrollbar-thumb {
  background: var(--line-2); border-radius: var(--r-pill); border: 2px solid var(--surface);
}

.pal__group {
  display: flex; align-items: center; gap: .5rem;
  padding: .75rem .625rem .25rem;
  font-family: var(--mono); font-size: .625rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3);
}
.pal__clear {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: .625rem; letter-spacing: .14em; color: var(--ink-4);
  text-decoration: underline; text-underline-offset: 2px;
}
.pal__clear:hover { color: var(--brand-deep); }

.pal__row {
  display: flex; align-items: center; gap: .75rem; width: 100%;
  padding: .5rem .625rem; border: 0; border-radius: var(--r-chip);
  background: none; cursor: pointer; text-align: left;
  transition: background var(--t-fast);
}
.pal__row.is-active { background: var(--brand-softer); }

/* conversions and formats read as keys, the same object as the rest of the site */
.pal__keys { display: flex; align-items: center; gap: .375rem; flex: none; }
.pal__key {
  font-family: var(--mono); font-size: .75rem; font-weight: 700; letter-spacing: .05em;
  color: var(--ink); padding: .25rem .5rem; border-radius: var(--r-xs);
  background: var(--key-face);
  border: 1px solid var(--line-2);
  box-shadow: inset 0 1px 0 var(--raise), 0 1.5px 0 0 var(--line-2);
  white-space: nowrap;
}
.pal__to { display: flex; color: var(--ink-4); }
.pal__row.is-active .pal__to { color: var(--brand); }

/* tools and pages keep a glyph */
.pal__lead {
  display: grid; place-items: center; flex: none;
  width: 28px; height: 28px; border-radius: var(--r-chip);
  background: var(--surface-2); color: var(--ink-3);
}
.pal__lead[data-kind="image"] { color: var(--image); background: var(--image-soft); }
.pal__lead[data-kind="video"] { color: var(--video); background: var(--video-soft); }
.pal__lead[data-kind="audio"] { color: var(--audio); background: var(--audio-soft); }

.pal__text { display: grid; gap: .125rem; min-width: 0; flex: 1; }
.pal__title { font-size: .875rem; font-weight: 650; color: var(--ink); }
.pal__sub {
  font-size: .75rem; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pal__tag {
  margin-left: auto; flex: none;
  font-family: var(--mono); font-size: .625rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3);
  opacity: 0; transition: opacity var(--t-fast);
}
.pal__row.is-active .pal__tag { opacity: 1; color: var(--brand-deep); }

.pal__empty {
  padding: 2rem 1rem; text-align: center; color: var(--ink-3); font-size: .875rem;
}

.pal__foot {
  display: flex; gap: 1rem; flex-wrap: wrap;
  padding: .625rem 1rem; border-top: 1px solid var(--line);
  background: var(--surface-2);
  font-size: .75rem; color: var(--ink-3);
}
.pal__foot span { display: flex; align-items: center; gap: .375rem; }

@media (max-width: 560px) {
  .pal__panel { margin-top: 1rem; width: calc(100% - 1rem); }
  .pal__sub { display: none; }
  .pal__tag { display: none; }
}

/* ------------------------------------------------------------- key sheet */

.keys { position: fixed; inset: 0; z-index: 80; display: none; }
.keys.is-open { display: block; }
.keys__backdrop {
  position: absolute; inset: 0; background: rgb(28 18 12 / 34%);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.keys__panel {
  position: relative;
  width: min(420px, 100% - 1.625rem);
  margin: clamp(3rem, 14vh, 9rem) auto 0;
  padding: 1.25rem;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-xl); box-shadow: var(--sh-3);
  animation: pal-in .22s var(--ease) both;
}
.keys__head { display: flex; align-items: center; margin-bottom: 1rem; }
.keys__head h2 { font-size: 1rem; font-weight: 700; }
.keys__head .icon-btn { margin-left: auto; }
.keys__row {
  display: flex; align-items: baseline; gap: .875rem;
  padding: .5rem 0; border-top: 1px solid var(--line);
}
.keys__combo { display: flex; gap: .25rem; flex: none; min-width: 72px; }
.keys__label { font-size: .875rem; }
.keys__note { display: block; font-size: .75rem; color: var(--ink-3); }

/* --------------------------------------------------------------- toasts */

.toasts {
  position: fixed; left: 50%; bottom: 1.375rem; z-index: 90;
  transform: translateX(-50%);
  display: grid; gap: .5rem; justify-items: center;
  pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: .5rem;
  padding: .625rem 1rem; border-radius: var(--r-pill);
  background: var(--ink-deep); color: #f6efe9;
  font-size: .875rem; font-weight: 550;
  box-shadow: var(--sh-3);
  pointer-events: auto; cursor: pointer;
  animation: toast-in .3s var(--ease) both;
  max-width: min(420px, calc(100vw - 2rem));
}
.toast__icon { display: flex; color: var(--brand-2); }
.toast--ok .toast__icon { color: #46d6a4; }
.toast--error .toast__icon { color: #ff8a7a; }
.toast.is-going { animation: toast-out .3s var(--ease-soft) both; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(14px) scale(.96); }
  to { opacity: 1; transform: none; }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(8px) scale(.97); }
}

/* the whole page becomes a drop target when it has no dropzone of its own */
body.is-dragging-global::after {
  content: '';
  position: fixed; inset: .625rem; z-index: 70; pointer-events: none;
  border: 2.5px dashed var(--brand);
  border-radius: var(--r-xl);
  background: rgb(244 85 60 / 6%);
}

/* ==========================================================================
   Written content
   --------------------------------------------------------------------------
   The how-to and the questions. These exist to answer what someone typed
   into a search box, so they are set for reading rather than scanning.
   ========================================================================== */

.howto { margin-top: 3.5rem; }
.howto__list { list-style: none; margin: 1.5rem 0 0; padding: 0; display: grid; gap: 1rem; }
.howto__step {
  display: grid; grid-template-columns: auto 1fr; gap: 1rem;
  padding: 1rem 1.25rem; border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh-1);
}
.howto__num {
  font-family: var(--mono); font-size: .75rem; font-weight: 700;
  color: var(--brand-deep); background: var(--brand-soft);
  padding: .25rem .5rem; border-radius: var(--r-xs); height: fit-content;
}
.howto__title { font-size: 1rem; font-weight: 700; }
.howto__text { color: var(--ink-3); font-size: .9375rem; margin-top: .25rem; line-height: 1.6; }

.faq { margin-top: 3.5rem; }
.faq__list {
  margin-top: 1.25rem; border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden; background: var(--surface);
}
.faq__item { border-bottom: 1px solid var(--line); }
.faq__item:last-child { border-bottom: 0; }
.faq__q {
  display: flex; align-items: center; gap: 1rem; cursor: pointer;
  padding: 1rem 1.25rem; font-size: .9375rem; font-weight: 650;
  list-style: none; transition: background var(--t-fast);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { background: var(--surface-2); }
.faq__mark {
  display: flex; margin-left: auto; flex: none; color: var(--ink-4);
  transition: transform var(--t) var(--ease), color var(--t-fast);
}
.faq__item[open] .faq__mark { transform: rotate(45deg); color: var(--brand); }
.faq__a {
  padding: 0 1.25rem 1rem; color: var(--ink-2);
  font-size: .9375rem; line-height: 1.65; max-width: 68ch;
}

@media (max-width: 700px) {
  .howto__step { grid-template-columns: 1fr; gap: .5rem; }
  .howto__num { justify-self: start; }
}

/* ==========================================================================
   Studio pages
   --------------------------------------------------------------------------
   Inspect, fit, spec check and the folder tools answer a question rather than
   producing a converted copy, so they read as a report: a verdict at the top,
   then the evidence.
   ========================================================================== */

.studio { max-width: 820px; margin-inline: auto; padding: 2.5rem 0 1rem; }
.studio__head { margin-bottom: 2rem; }
.studio__title {
  font-size: clamp(1.8rem, 4vw, 2.6rem); font-weight: 800;
  letter-spacing: -0.04em; margin-top: .75rem;
}
.studio__text { color: var(--ink-2); margin-top: .75rem; max-width: 62ch; }
.studio__body { display: grid; gap: 1.25rem; }
.studio__section {
  padding: 1.25rem 1.5rem; border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh-1);
  display: grid; gap: .875rem;
}
.studio__wait { padding: 2.5rem; text-align: center; color: var(--ink-3); }
.studio__empty { color: var(--ink-3); font-size: .9375rem; }
.studio__act, .studio__again { margin-top: .25rem; }
.studio__drop .dropzone { width: 100%; }

/* --- the verdict --- */

.verdict {
  padding: 1.25rem 1.5rem; border-radius: var(--r-lg);
  border: 1px solid var(--line); background: var(--surface);
  box-shadow: var(--sh-1); display: grid; gap: .5rem;
}
.verdict h2 { font-size: 1.125rem; font-weight: 750; }
.verdict p { color: var(--ink-2); font-size: .9375rem; line-height: 1.6; max-width: 66ch; }
.verdict__act { display: flex; gap: .625rem; flex-wrap: wrap; margin-top: .625rem; }
.verdict--ok { background: var(--ok-soft); border-color: color-mix(in srgb, var(--ok) 26%, var(--line)); }
.verdict--bad { background: var(--err-soft); border-color: color-mix(in srgb, var(--err) 26%, var(--line)); }
.verdict--unknown { background: var(--surface-2); }

/* --- facts --- */

.facts { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.fact {
  display: grid; grid-template-columns: 11rem 1fr; gap: .25rem 1rem;
  padding: .625rem 0; border-bottom: 1px solid var(--line);
}
.fact:last-child { border-bottom: 0; }
.fact__label { font-size: .8125rem; color: var(--ink-3); }
.fact__value { font-family: var(--mono); font-size: .8125rem; font-weight: 650; word-break: break-word; }
.fact__note { grid-column: 2; font-size: .8125rem; color: var(--ink-3); }
.fact.is-heavy .fact__value { color: var(--brand-deep); }

.thumbcard {
  display: flex; gap: 1rem; align-items: flex-start;
  padding: 1rem; border-radius: var(--r); background: var(--surface-2);
  border: 1px solid var(--line);
}
.thumbcard__img { width: 96px; height: auto; border-radius: var(--r-chip); flex: none; }
.thumbcard h3 { font-size: .9375rem; font-weight: 700; }
.thumbcard p { font-size: .875rem; color: var(--ink-3); margin-top: .25rem; }

/* --- rules --- */

.rules { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.rule {
  display: flex; align-items: flex-start; gap: .625rem;
  font-size: .875rem; padding: .5rem .75rem; border-radius: var(--r-sm);
}
.rule svg { flex: none; margin-top: .125rem; }
.rule.is-bad { background: var(--err-soft); color: var(--err); }
.rule.is-ok { background: var(--ok-soft); color: var(--ok); }

/* --- the fit search --- */

.fit__log { list-style: none; margin: 0; padding: 0; display: grid; gap: .25rem; }
.fit__try {
  display: flex; align-items: center; gap: .75rem;
  padding: .5rem .75rem; border-radius: var(--r-sm);
  background: var(--surface-2); font-size: .8125rem;
}
.fit__label { font-family: var(--mono); font-size: .75rem; color: var(--ink-2); }
.fit__size { margin-left: auto; font-family: var(--mono); font-weight: 700; }
.fit__verdict { font-size: .75rem; color: var(--ink-4); min-width: 3.625rem; text-align: right; }
.fit__try.is-fit { background: var(--ok-soft); }
.fit__try.is-fit .fit__verdict { color: var(--ok); }

/* --- quality proof --- */

.proof__frame {
  position: relative; overflow: hidden; border-radius: var(--r);
  cursor: ew-resize; touch-action: none; user-select: none;
  border: 1px solid var(--line); background: var(--surface-2);
}
.proof__frame img { display: block; width: 100%; height: auto; }
/*
 * The converted half needs a floor of its own.
 *
 * It is layered straight on top of the original, which is exactly right for
 * an opaque result and exactly wrong for a transparent one: every
 * transparent pixel showed the original through it, so a cutout looked
 * identical to its input and the tool read as broken. It was not — the
 * alpha was correct all along, it just had the "before" image behind it.
 *
 * The checkerboard is the convention for "there is nothing here", and
 * because it sits under the image it is invisible whenever the result is
 * opaque. Nothing changes for a JPG; a cutout finally shows its cutout.
 */
.proof__after {
  position: absolute; inset: 0;
  background:
    repeating-conic-gradient(var(--proof-check-a) 0 25%, var(--proof-check-b) 0 50%)
    0 0 / 18px 18px;
}
.proof__after img { width: 100%; }

/*
 * Light on both themes, deliberately. Dark checks matched the dark page and
 * hid the most common fault in a cutout — a dark fringe round hair — which
 * then showed up the moment the picture was placed on anything pale.
 */
:root { --proof-check-a: #dcd6ce; --proof-check-b: #f4f1ec; }
.proof__after[data-floor="white"] { background: #ffffff; }
.proof__after[data-floor="black"] { background: #0b0908; }
.proof__head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .75rem 1rem; margin-bottom: 1rem;
}
.proof__head .section-title { margin: 0; }
.proof__floor { width: min(100%, 17rem); }
.proof__handle {
  position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
  background: var(--knob); box-shadow: 0 0 0 1px rgb(80 45 30 / 35%);
  pointer-events: none;
}
.proof__handle::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 26px; height: 26px; margin: -13px 0 0 -13px; border-radius: 50%;
  background: var(--knob); box-shadow: 0 2px 8px rgb(80 45 30 / 35%);
}
.proof__score { display: flex; align-items: center; gap: 1rem; }
.proof__score strong {
  font-family: var(--mono); font-size: 1.5rem; font-weight: 800;
  color: var(--brand-deep);
}
.proof__score b { font-size: .9375rem; font-weight: 700; display: block; }
.proof__score p { font-size: .8125rem; color: var(--ink-3); }

/* --- rename plan --- */

.plan { list-style: none; margin: 0; padding: 0; display: grid; gap: .25rem; max-height: 380px; overflow-y: auto; }
.plan__row {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: .625rem; align-items: center;
  padding: .375rem .625rem; border-radius: var(--r-xs); font-size: .8125rem;
}
.plan__row:nth-child(odd) { background: var(--surface-2); }
.plan__row.is-same { opacity: .45; }
.plan__from { font-family: var(--mono); color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plan__to { font-family: var(--mono); font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plan__arrow { display: flex; color: var(--ink-4); }

/* --- duplicates --- */

.dupe {
  display: grid; gap: .5rem; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  padding: .875rem; border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--line);
}
.dupe__item { position: relative; display: grid; gap: .375rem; }
.dupe__img {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--r-sm); border: 1px solid var(--line);
}
.dupe__item.is-keep .dupe__img { border-color: var(--ok); box-shadow: 0 0 0 2px var(--ok-soft); }
.dupe__name { font-size: .75rem; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dupe__size { font-family: var(--mono); font-size: .75rem; color: var(--ink-3); }
.dupe__tag {
  position: absolute; top: .375rem; left: .375rem;
  font-size: .625rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  background: var(--ok); color: #fff; padding: .125rem .375rem; border-radius: var(--r-pill);
}

/* ============================================================== recipes */

.recipes {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding-top: 1rem; border-top: 1px solid var(--line);
}
.recipes__label {
  font-family: var(--mono); font-size: .6875rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3);
  margin-right: .25rem;
}
.recipe-chip {
  display: inline-flex; align-items: center;
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  background: var(--surface); overflow: hidden;
}
.recipe-chip__use {
  border: 0; background: none; cursor: pointer;
  padding: .25rem .625rem; font: inherit; font-size: .75rem; font-weight: 650; color: var(--ink-2);
}
.recipe-chip__use:hover { color: var(--brand-deep); }
.recipe-chip__x {
  display: grid; place-items: center; cursor: pointer;
  border: 0; border-left: 1px solid var(--line); background: none;
  color: var(--ink-4); padding: .25rem .375rem;
}
.recipe-chip__x:hover { color: var(--err); background: var(--err-soft); }
.recipes__add {
  display: inline-flex; align-items: center; gap: .25rem; cursor: pointer;
  border: 1px dashed var(--line-2); border-radius: var(--r-pill); background: none;
  padding: .25rem .75rem; font: inherit; font-size: .75rem; color: var(--ink-3);
}
.recipes__add:hover { border-color: var(--brand); color: var(--brand-deep); }

/* ============================================================= waveform */

.wave__wrap { display: grid; gap: .5rem; }
.wave { position: relative; touch-action: none; user-select: none; cursor: pointer; }
.wave__clip {
  position: relative; overflow: hidden; border-radius: var(--r);
  background: var(--surface-2); border: 1px solid var(--line);
}
.wave__canvas { display: block; width: 100%; height: 104px; }

/* Everything outside the selection is dimmed rather than tinted, so the kept
   part is the part that looks lit. */
.wave__shade {
  position: absolute; top: 0; bottom: 0; pointer-events: none;
  background: color-mix(in srgb, var(--surface-2) 82%, transparent);
  backdrop-filter: saturate(.15);
}
.wave__shade:first-of-type { left: 0; }
.wave__window {
  position: absolute; top: 0; bottom: 0; pointer-events: none;
  background: color-mix(in srgb, var(--brand) 5%, transparent);
}

.wave__handle {
  position: absolute; top: 0; bottom: 0; width: 22px; margin-left: -11px;
  cursor: ew-resize; z-index: 2; display: grid; place-items: center;
}
/* A full-height rail so the edge of the selection is unmistakable, */
.wave__handle::before {
  content: ''; position: absolute; top: 0; bottom: 0; left: 50%;
  width: 2px; margin-left: -1px; background: var(--brand);
}
/* and a grip in the middle so it is obvious it can be dragged. */
.wave__handle::after {
  content: ''; position: relative;
  width: 13px; height: 34px; border-radius: var(--r-xs);
  background: var(--brand); box-shadow: 0 2px 7px -1px rgb(80 45 30 / 45%),
    inset 0 1px 0 var(--raise);
}
.wave__handle:hover::after { transform: scaleY(1.08); }
.wave__handle::after { transition: transform .14s var(--ease); }
.wave__readout {
  font-family: var(--mono); font-size: .75rem; color: var(--ink-3);
  display: flex; gap: .375rem; align-items: center;
}

@media (max-width: 700px) {
  .fact { grid-template-columns: 1fr; }
  .fact__note { grid-column: 1; }
  .plan__row { grid-template-columns: 1fr; }
  .plan__arrow { transform: rotate(90deg); justify-self: start; }
  .thumbcard { flex-direction: column; }
}

.wave__host:empty { display: none; }
.wave__host { margin-bottom: .25rem; }
.wave__handle:focus-visible { outline: none; }
.wave__handle:focus-visible::after { box-shadow: 0 0 0 3px var(--brand-soft), 0 2px 6px -1px rgb(80 45 30 / 40%); }

.action { display: grid; gap: .5rem; }
.proof { display: grid; gap: .875rem; }
.proof__before { display: block; }
.proof__before img { display: block; width: 100%; height: auto; }
.dupe__meta { display: grid; gap: .125rem; }
.stage-done__proof:not(:empty) { margin-top: 1.25rem; }
.wave__handle--from::after { border-top-right-radius: 2px; border-bottom-right-radius: 2px; }
.wave__handle--to::after { border-top-left-radius: 2px; border-bottom-left-radius: 2px; }
.studio__note { font-size: .8125rem; color: var(--ink-3); }
.result-row__note { font-size: .75rem; color: var(--ink-3); margin-top: .125rem; }

/* ==========================================================================
   Advanced settings
   --------------------------------------------------------------------------
   The bar is built like the keycaps elsewhere: a light top face over a
   darker wall, so it reads as a physical thing to press rather than a link.
   ========================================================================== */

.settings { display: grid; gap: 1rem; }

.adv { display: grid; }

.advbar {
  display: flex; align-items: center; gap: .625rem; width: 100%;
  padding: .75rem .875rem; cursor: pointer; font: inherit; text-align: left;
  border: 1px solid var(--line-2); border-radius: var(--r);
  background: var(--surface);
  box-shadow: 0 1px 0 var(--line-2), inset 0 1px 0 var(--raise);
  color: var(--ink-2);
  transition: border-color .15s var(--ease), color .15s var(--ease);
}
.advbar:hover { border-color: var(--brand); color: var(--brand-deep); }
.advbar:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.advbar__icon { display: flex; color: var(--ink-3); }
.advbar:hover .advbar__icon { color: var(--brand); }
.advbar__label { font-size: .875rem; font-weight: 650; }

/* How many are in there. A number is enough; a sentence is not needed. */
.advbar__count {
  margin-left: auto; font-family: var(--mono); font-size: .75rem; font-weight: 700;
  min-width: 1.375rem; height: 1.375rem; padding: 0 .375rem; border-radius: var(--r-pill);
  display: grid; place-items: center;
  background: var(--surface-3, var(--surface-2)); color: var(--ink-3);
  border: 1px solid var(--line);
}
/* Something behind the bar is already changing the output. Say so, because a
   hidden setting quietly altering the result is the worst version of this. */
.advbar__badge {
  margin-left: auto; font-size: .6875rem; font-weight: 700; letter-spacing: .01em;
  padding: .25rem .5rem; border-radius: var(--r-pill);
  background: var(--brand-soft); color: var(--brand-deep);
}
.advbar__chev { display: flex; color: var(--ink-4); transition: transform .2s var(--ease); }
.adv.is-open .advbar__chev { transform: rotate(180deg); }
.adv.is-open .advbar {
  border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  border-bottom-color: transparent; box-shadow: none;
}

.adv__panel { display: none; }
.adv.is-open .adv__panel {
  display: block;
  padding: 1rem .875rem 1rem;
  border: 1px solid var(--line-2); border-top: 0;
  border-radius: 0 0 var(--r) var(--r);
  background: var(--surface-2);
  animation: advIn .18s var(--ease);
}
@keyframes advIn { from { opacity: 0; transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) { .adv.is-open .adv__panel { animation: none; } }

/* The bar above it is already the separator; a rule as well is one line too
   many, and the panel supplies its own padding. */
.settings-grid--adv { gap: 1rem; border-top: 0; padding-top: 0; }

/* A second file picked inside the settings, not in the dropzone. */
.filepick {
  display: flex; align-items: center; gap: .5rem;
  padding: .375rem .5rem .375rem .375rem; border-radius: var(--r);
  background: var(--surface-2); border: 1px solid var(--line);
}
.filepick__pick {
  display: inline-flex; align-items: center; gap: .375rem; flex: none;
  padding: .375rem .75rem; border-radius: var(--r-sm); cursor: pointer;
  border: 1px solid var(--line-2); background: var(--surface);
  font: inherit; font-size: .8125rem; font-weight: 650; color: var(--ink-2);
}
.filepick__pick:hover { border-color: var(--brand); color: var(--brand-deep); }
.filepick__name {
  flex: 1; min-width: 0; font-size: .8125rem; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filepick__clear {
  flex: none; display: grid; place-items: center; cursor: pointer;
  width: 22px; height: 22px; border-radius: 50%;
  border: 0; background: none; color: var(--ink-4);
}
.filepick__clear:hover { color: var(--err); background: var(--err-soft); }
.filepick__clear.is-hidden { display: none; }
.action__host { display: grid; }

/* Which half is which, so the comparison needs no caption to be understood. */
.proof__tag {
  position: absolute; top: .5625rem; z-index: 2; pointer-events: none;
  font-family: var(--mono); font-size: .625rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
  padding: .25rem .5rem; border-radius: var(--r-pill);
  background: rgb(28 20 16 / 58%); color: #fff;
}
.proof__tag--before { left: .5625rem; }
.proof__tag--after { right: .5625rem; }

/* The divider takes focus, so it has to show that it has it. */
.proof__handle:focus-visible { outline: none; }
.proof__handle:focus-visible::after {
  box-shadow: 0 0 0 3px var(--brand), 0 2px 8px rgb(80 45 30 / 35%);
}

/* ==========================================================================
   Hit area on touch
   --------------------------------------------------------------------------
   A fingertip resolves about 44px; a mouse pointer resolves one. So the
   targets grow only where the pointer is coarse, and the desktop layout,
   which depends on these controls being compact, is left exactly as it is.

   Controls that sit in normal flow simply get taller. Controls inside a
   fixed-height bar cannot, so those extend an invisible area from their
   centre instead, which costs no layout and cannot shift anything.
   ========================================================================== */

@media (pointer: coarse) {
  /* --- in flow: grow --- */
  .site-nav a,
  .chip,
  .console__option,
  .console__tab,
  .seg__item,
  .back,
  .slot__link,
  .recipe-chip__use,
  .recipes__add {
    min-height: 44px;
  }
  .site-nav a,
  .back {
    display: inline-flex;
    align-items: center;
  }
  .chip,
  .console__option {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /*
   * --- in a fixed-height bar: extend from the centre ---
   * The pseudo-element is the target, so the visible control keeps its size
   * and the row keeps its height.
   */
  .site-head__logo,
  .searchbar,
  .filepick__clear,
  .file-row__remove,
  .recipe-chip__x {
    position: relative;
  }
  .site-head__logo::after,
  .searchbar::after,
  .filepick__clear::after,
  .file-row__remove::after,
  .recipe-chip__x::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: max(100%, 44px);
    height: max(100%, 44px);
    transform: translate(-50%, -50%);
  }
}
/* ==========================================================================
   Turning the pair around
   --------------------------------------------------------------------------
   The arrow between the two slots becomes the control that reverses them: on
   the console it swaps the two formats, on a conversion page it goes to the
   page for the opposite conversion. Same position, same meaning, so there is
   nothing new to find.
   ========================================================================== */

.console__swap,
.converter__swap {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink-3);
  cursor: pointer;
  padding: 0;
  text-decoration: none;
  box-shadow: var(--sh-1);
  transition: color var(--t-fast), border-color var(--t-fast),
    background var(--t-fast), transform var(--t) var(--ease);
}
.console__swap:hover:not(:disabled),
.converter__swap:hover {
  color: var(--brand-deep);
  border-color: var(--brand);
  background: var(--brand-soft);
  /* A half turn, because that is what the button does to the pair. */
  transform: rotate(180deg);
}
.console__swap:focus-visible,
.converter__swap:focus-visible { outline: 2.5px solid var(--brand); outline-offset: 2px; }
.console__swap:disabled { opacity: .4; cursor: not-allowed; box-shadow: none; }

@media (prefers-reduced-motion: reduce) {
  .console__swap, .converter__swap { transition: color var(--t-fast), border-color var(--t-fast); }
  .console__swap:hover:not(:disabled), .converter__swap:hover { transform: none; }
}

@media (max-width: 620px) {
  .console__swap, .converter__swap { width: 34px; height: 34px; }
}

/* ============================================================== redaction */

/*
 * Picture on the left, controls on the right, stacking on a phone where the
 * image needs the full width to be draggable with a thumb.
 */
.redact {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 280px;
  gap: 1.5rem;
  align-items: start;
}
.redact__stage {
  min-width: 0;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: .75rem;
  display: grid;
  place-items: center;
}
.redact__canvas {
  max-width: 100%;
  height: auto;
  border-radius: calc(var(--r) - 4px);
  /* A crosshair says "draw here" before anyone has read a word of the copy. */
  cursor: crosshair;
  touch-action: none;      /* or a drag scrolls the page instead of marking */
  display: block;
}
.redact__side { display: grid; gap: .75rem; min-width: 0; }
.redact__count { font-size: .9375rem; font-weight: 600; color: var(--ink); margin: 0; }
.redact__hint { font-size: .8125rem; color: var(--ink-3); margin: 0; line-height: 1.45; }
.redact__warn {
  display: flex; align-items: flex-start; gap: .5rem;
  font-size: .8125rem; line-height: 1.4; margin: 0;
  color: var(--warn-ink, var(--ink-2));
  background: var(--warn-soft, var(--surface-2));
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 10px);
  padding: .625rem .75rem;
}
.redact__warn svg { flex: none; margin-top: .1em; }
.redact__acts { display: grid; gap: .5rem; margin-top: .25rem; }

@media (max-width: 720px) {
  .redact { grid-template-columns: minmax(0, 1fr); }
}

/* ================================================== proof, scan, compare */

/* --- watching the network stay quiet --- */
.prove { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
.prove__panel {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r); padding: 1.5rem; text-align: center;
}
.prove__label { font-size: .8125rem; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3); margin: 0 0 .5rem; }
.prove__figure {
  font-size: clamp(2.5rem, 7vw, 4rem); font-weight: 800; letter-spacing: -0.04em;
  line-height: 1; margin: 0; color: var(--ink);
}
.prove__figure[data-zero="true"] { color: var(--ok); }
.prove__note { font-size: .875rem; color: var(--ink-3); margin: .75rem 0 0; line-height: 1.45; }
.prove__side { display: grid; gap: 1rem; min-width: 0; }
.prove__result {
  display: grid; gap: .625rem; padding: 1rem;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r);
}
.prove__result p { margin: 0; font-size: .9375rem; }
.prove__log { grid-column: 1 / -1; margin-top: .5rem; }
.prove__log summary { cursor: pointer; font-size: .875rem; font-weight: 600; color: var(--ink-2); }
.prove__list { list-style: none; margin: .75rem 0 0; padding: 0; display: grid; gap: .25rem;
  max-height: 260px; overflow-y: auto; }
.prove__list li { display: flex; justify-content: space-between; gap: 1rem;
  font-size: .8125rem; padding: .3125rem .5rem; border-radius: var(--r-chip); background: var(--surface-2); }
.prove__url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); }
.prove__size { flex: none; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.prove__hint { font-size: .8125rem; color: var(--ink-3); margin: .75rem 0 0; line-height: 1.5; }
.studio__hint { font-size: .8125rem; color: var(--ink-3); margin: .625rem 0 0; text-align: center; }

/* --- the screenshot leak check --- */
.scan { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 1.5rem; align-items: start; }
.scan__stage {
  min-width: 0; background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r); padding: .75rem; display: grid; place-items: center;
}
.scan__canvas { max-width: 100%; height: auto; border-radius: calc(var(--r) - 4px); display: block; }
.scan__side { display: grid; gap: .5rem; min-width: 0; }
.scan__verdict { padding: .875rem 1rem; border-radius: var(--r); border: 1px solid var(--line); }
.scan__verdict[data-level="bad"] { background: var(--bad-soft, var(--surface-2)); }
.scan__verdict[data-level="warn"] { background: var(--warn-soft, var(--surface-2)); }
.scan__verdict[data-level="ok"] { background: var(--ok-soft, var(--surface-2)); }
.scan__count { margin: 0; font-weight: 700; font-size: 1rem; color: var(--ink); }
.scan__sub { margin: .25rem 0 0; font-size: .8125rem; color: var(--ink-2); line-height: 1.45; }
.scan__row {
  display: flex; gap: .625rem; align-items: flex-start; cursor: pointer;
  padding: .625rem .75rem; border: 1px solid var(--line);
  border-radius: var(--r-sm, 10px); background: var(--surface);
}
.scan__row:hover { background: var(--surface-2); }
.scan__row input { margin-top: .1875rem; flex: none; }
.scan__what { display: grid; gap: .125rem; min-width: 0; }
.scan__label { font-size: .875rem; font-weight: 650; color: var(--ink); }
.scan__row[data-severity="high"] .scan__label { color: var(--brand-deep, var(--ink)); }
.scan__text { font-size: .8125rem; font-family: ui-monospace, monospace; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scan__why { font-size: .75rem; color: var(--ink-3); line-height: 1.4; }
.scan__acts { display: grid; gap: .5rem; margin-top: .5rem; }
.scan__note { font-size: .75rem; color: var(--ink-3); margin: 0; line-height: 1.45; }

/* --- comparing two files --- */
.compare__slots { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }
.compare__slot { display: grid; gap: .5rem; }
.compare__slotLabel { margin: 0; font-size: .8125rem; font-weight: 650;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); }
.compare { display: grid; gap: 1.25rem; }
.compare__figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
.compare__fig { display: grid; gap: .5rem; min-width: 0; }
.compare__fig p { margin: 0; font-size: .8125rem; font-weight: 650; color: var(--ink-3); }
.compare__fig img, .compare__fig canvas {
  width: 100%; height: auto; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--surface-2); display: block;
}
.compare__hint { font-size: .8125rem; color: var(--ink-3); line-height: 1.55; margin: 0; }

/* --- the disk audit --- */
.audit { display: grid; gap: 1.75rem; }
.audit__headline { text-align: center; padding: 1.5rem;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r); }
.audit__label { font-size: .8125rem; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3); margin: 0 0 .5rem; }
.audit__figure { font-size: clamp(2.25rem, 6vw, 3.5rem); font-weight: 800;
  letter-spacing: -0.04em; line-height: 1; margin: 0; color: var(--brand-deep, var(--ink)); }
.audit__note { font-size: .875rem; color: var(--ink-3); margin: .75rem 0 0; }
.audit__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .75rem; }
.audit__row {
  display: grid; grid-template-columns: 62px minmax(0, 1fr) auto; gap: .5rem .75rem;
  align-items: center; padding: .75rem .875rem;
  border: 1px solid var(--line); border-radius: var(--r); background: var(--surface);
}
.audit__kind { font-size: .75rem; font-weight: 700; letter-spacing: .04em; color: var(--ink-2); }
.audit__bar { height: 8px; border-radius: var(--r-pill); background: var(--surface-2); overflow: hidden; }
.audit__fill { display: block; height: 100%; border-radius: var(--r-pill);
  background: var(--brand); }
.audit__num { font-variant-numeric: tabular-nums; font-weight: 650; font-size: .9375rem; }
.audit__why { grid-column: 1 / -1; font-size: .8125rem; color: var(--ink-3); line-height: 1.45; }
.audit__big { list-style: none; margin: 0; padding: 0; display: grid; gap: .25rem; }
.audit__big li { display: flex; justify-content: space-between; gap: 1rem;
  font-size: .875rem; padding: .375rem .625rem; border-radius: var(--r-chip); background: var(--surface-2); }
.audit__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.audit__size { flex: none; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.audit__acts { display: flex; flex-wrap: wrap; gap: .625rem; }

@media (max-width: 860px) {
  .prove, .scan { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================ safe to send */

.safety { display: grid; gap: .75rem; max-width: 680px; }
.safety__verdict {
  padding: 1.25rem 1.375rem; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--surface-2);
}
.safety__verdict[data-level="bad"] { background: var(--bad-soft, var(--surface-2)); }
.safety__verdict[data-level="warn"] { background: var(--warn-soft, var(--surface-2)); }
.safety__verdict[data-level="ok"] { background: var(--ok-soft, var(--surface-2)); }
.safety__headline { margin: 0; font-size: 1.25rem; font-weight: 750; letter-spacing: -0.02em; color: var(--ink); }
.safety__sub { margin: .375rem 0 0; font-size: .875rem; color: var(--ink-2); line-height: 1.5; }
.safety__risk {
  display: grid; gap: .375rem; justify-items: start;
  padding: .875rem 1rem; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--surface);
  border-left-width: 3px;
}
.safety__risk[data-level="high"] { border-left-color: var(--brand); }
.safety__risk[data-level="medium"] { border-left-color: var(--warn, var(--line-2)); }
.safety__risk[data-level="note"] { border-left-color: var(--line-2); }
.safety__title { margin: 0; font-weight: 650; font-size: .9375rem; color: var(--ink); }
.safety__detail { margin: 0; font-size: .8125rem; color: var(--ink-3); line-height: 1.5; }
.safety__acts { margin-top: .5rem; }
.btn--sm { padding: .375rem .75rem; font-size: .8125rem; }

/* ============================================================== pipelines */

.pipe { display: grid; gap: 1.25rem; max-width: 720px; }
.pipe__steps { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.pipe__step {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto auto;
  gap: .625rem; align-items: center;
  padding: .625rem .75rem; border: 1px solid var(--line);
  border-radius: var(--r); background: var(--surface);
}
.pipe__num {
  display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 50%; background: var(--brand-soft); color: var(--brand-deep);
  font-size: .8125rem; font-weight: 700;
}
.pipe__pick {
  min-width: 0; width: 100%; font: inherit; font-size: .9375rem;
  padding: .375rem .5rem; border: 1px solid var(--line);
  border-radius: var(--r-chip); background: var(--surface-2); color: var(--ink);
}
.pipe__takes {
  font-size: .6875rem; color: var(--ink-4); font-family: var(--mono, ui-monospace, monospace);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 110px;
}
.pipe__drop {
  display: grid; place-items: center; width: 30px; height: 30px;
  border: 0; border-radius: var(--r-chip); background: transparent;
  color: var(--ink-4); cursor: pointer;
}
.pipe__drop:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
.pipe__drop:disabled { opacity: .3; cursor: default; }
.pipe__add { display: flex; flex-wrap: wrap; gap: .5rem; }
.pipe__saved { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.pipe__savedLabel {
  margin: 0; font-size: .75rem; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
.pipe__chip {
  display: inline-flex; align-items: center; gap: .125rem;
  border: 1px solid var(--line); border-radius: var(--r-pill); background: var(--surface-2);
}
.pipe__chip > button {
  border: 0; background: none; font: inherit; font-size: .8125rem;
  padding: .3125rem .5rem .3125rem .75rem; color: var(--ink-2); cursor: pointer;
}
.pipe__chip > button:hover { color: var(--ink); }
.pipe__chipX { padding: .3125rem .5rem .3125rem .25rem !important; display: grid; place-items: center; }
.pipe__files { display: grid; gap: .5rem; }
.pipe__count { margin: 0; font-size: .875rem; font-weight: 600; color: var(--ink-2); }
.pipe__out { display: grid; gap: .5rem; }
.pipe__outLabel {
  margin: 0; font-size: .75rem; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
.pipe__result {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  gap: .75rem; align-items: center;
  padding: .5rem .75rem; border-radius: var(--r); background: var(--surface-2);
}
.pipe__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .875rem; }
.pipe__size { font-size: .8125rem; color: var(--ink-3); font-variant-numeric: tabular-nums; }

@media (max-width: 560px) {
  .pipe__step { grid-template-columns: 26px minmax(0, 1fr) auto; }
  .pipe__takes { display: none; }
}

/* ============================================================ start here */

.jobs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: .5rem;
}
.job {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: .875rem; align-items: center;
  padding: .875rem 1rem; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--surface);
  text-decoration: none; color: inherit;
  transition: border-color var(--t-fast), background var(--t-fast), transform var(--t-fast);
}
.job:hover {
  background: var(--surface-2); border-color: var(--line-2);
  transform: translateY(-1px);
}
.job__icon {
  display: grid; place-items: center; width: 34px; height: 34px; flex: none;
  border-radius: var(--r-sm); background: var(--brand-soft); color: var(--brand-deep);
}
.job__text { display: grid; gap: .125rem; min-width: 0; }
.job__label { font-size: .9375rem; font-weight: 620; color: var(--ink); line-height: 1.3; }
.job__hint { font-size: .8125rem; color: var(--ink-3); line-height: 1.4; }
.job__go { color: var(--ink-4); display: flex; transition: transform var(--t-fast), color var(--t-fast); }
.job:hover .job__go { color: var(--brand); transform: translateX(2px); }

/* The tool page says which job it was opened for, so the settings are not a surprise. */
.for-task {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .375rem .75rem; border-radius: var(--r-pill);
  background: var(--brand-soft); color: var(--brand-deep);
  font-size: .8125rem; font-weight: 600;
  margin-bottom: .75rem;
}

/* --------------------------------------------------- a failure you can act on */

.failure { display: grid; gap: .625rem; }
.failure__acts { display: flex; flex-wrap: wrap; gap: .5rem; }

/* ------------------------------------------------------- earlier attempts */

.attempts { display: grid; gap: .375rem; margin-top: 1.25rem; }
.attempts__label {
  margin: 0; font-size: .75rem; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
.attempt {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  gap: .75rem; align-items: center;
  padding: .5rem .75rem; border-radius: var(--r);
  background: var(--surface-2); border: 1px solid var(--line);
}
.attempt__what {
  font-size: .8125rem; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.attempt__size { font-size: .8125rem; color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------- chipping in, up top */

/*
 * An action rather than a destination, so it does not look like the nav
 * links beside it — but tinted rather than solid, because a filled brand
 * button up here would outrank the thing people came to do.
 */
/*
 * The theme toggle. Icon-only because the header runs out of width at 360px
 * before it runs out of things worth putting there, and the icon shown is
 * the theme currently in force rather than the one a press would bring —
 * the label says what the press does, so the button reports state and the
 * name reports the action.
 */
.failure__trail {
  margin-top: var(--s-3);
  font-size: var(--fs-2);
  color: var(--ink-3);
}
.failure__trail summary { cursor: pointer; font-weight: 650; }
.failure__trail ul {
  margin: var(--s-3) 0 0;
  padding-left: var(--s-8);
  font-family: var(--mono);
  line-height: 1.7;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------- theme transition */

/*
 * The two frames of a palette swap.
 *
 * The browser's default is to crossfade them, which on a full-page colour
 * change looks like a grey wash passing over everything. These turn that off
 * so the only motion is the circular wipe the script drives: the old page
 * holds perfectly still underneath while the new one is uncovered.
 */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}
::view-transition-old(root) { z-index: 1; }
::view-transition-new(root) { z-index: 2; }

/*
 * Under the snapshot the real page is still there, and every `transition:
 * color` on it would run a second time, arriving a beat after the wipe and
 * smearing the edge. Off for the duration.
 */
html.theme-swapping *,
html.theme-swapping *::before,
html.theme-swapping *::after {
  transition: none !important;
}

/*
 * The fallback, for browsers with no View Transitions and for anybody who
 * has asked for less motion. One uniform crossfade on the properties that
 * actually carry the palette — applied together, so nothing arrives early.
 */
html.theme-fading *,
html.theme-fading *::before,
html.theme-fading *::after {
  transition: background-color .42s ease, background-image .42s ease,
              color .42s ease, border-color .42s ease, fill .42s ease,
              stroke .42s ease, box-shadow .42s ease, outline-color .42s ease !important;
}
@media (prefers-reduced-motion: reduce) {
  html.theme-fading *,
  html.theme-fading *::before,
  html.theme-fading *::after { transition-duration: .01ms !important; }
}

.site-theme {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 32px; height: 32px; padding: 0;
  border-radius: 50%;
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--line-2);
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast),
              color var(--t-fast), transform var(--t-fast);
}
.site-theme:hover {
  color: var(--ink);
  border-color: var(--ink-4);
  transform: translateY(-1px);
}
.site-theme:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.site-theme__sun, .site-theme__moon { display: none; line-height: 0; }
.site-theme[data-showing="light"] .site-theme__sun,
.site-theme[data-showing="dark"] .site-theme__moon { display: block; }

.site-give {
  display: inline-flex; align-items: center; gap: .375rem; flex: none;
  padding: .375rem .75rem; border-radius: var(--r-pill);
  font-size: .875rem; font-weight: 600; text-decoration: none;
  color: var(--brand-deep);
  background: var(--brand-soft);
  border: 1px solid color-mix(in srgb, var(--brand) 22%, transparent);
  transition: background var(--t-fast), border-color var(--t-fast),
              color var(--t-fast), transform var(--t-fast);
}
.site-give:hover {
  background: color-mix(in srgb, var(--brand) 16%, transparent);
  border-color: color-mix(in srgb, var(--brand) 42%, transparent);
  transform: translateY(-1px);
}
.site-give__icon { display: flex; }

/*
 * Narrow screens keep the heart and drop the words. The nav already tightens
 * twice on the way down and there is no room for two more.
 */
@media (max-width: 760px) {
  .site-give__text { display: none; }
  .site-give { padding: .375rem .5rem; }
}

/*
 * The header on a phone.
 *
 * It kept the desktop's 72px bar around controls less than half that tall,
 * which spent a tenth of the screen on empty band, while the links inside it
 * had shrunk to 12px to make room. So the bar comes down to 56px — the
 * height a phone's own app bars use — and what is in it grows into it: one
 * 36px size for both round buttons, a slightly smaller mark, and links that
 * are readable again. Only phones; nothing above 620px is touched.
 *
 * After every earlier phone rule for these controls, so it is the one that
 * applies.
 */
@media (max-width: 620px) {
  :root { --head-h: 56px; }
  .site-head__inner { gap: .375rem; }

  .logo { flex: none; margin-right: auto; }
  .logo__mark svg { width: 30px; height: 30px; }
  .logo__mark { border-radius: 9px; box-shadow: 0 2px 8px -3px rgb(244 85 60 / 50%); }

  .searchbar,
  .site-give {
    flex: none; justify-content: center;
    width: 36px; height: 36px; min-width: 0; padding: 0;
    border-radius: 50%;
  }
  .searchbar { margin-left: 0; }

  .site-nav { gap: 0; }
  .site-nav a {
    --nav-pad: .625rem;
    padding: .375rem var(--nav-pad);
    font-size: .875rem;
  }
  .site-nav a.is-current::after { bottom: .125rem; }
}
@media (max-width: 360px) {
  .site-nav a { --nav-pad: .5rem; font-size: .8125rem; }
}

/*
 * The stop control on a running job. Deliberately quiet — it sits beside a
 * progress bar somebody is watching, and a loud red button next to a working
 * conversion reads as "something is wrong" rather than "you may leave".
 */
.action__stop {
  margin-left: auto;
  flex: none;
  align-self: center;
}
.action--running { display: flex; align-items: center; gap: var(--s-6); flex-wrap: wrap; }
.action--running .action__text { min-width: 0; }

/* Reorder controls, shown only on the tools where sequence is the output. */
.file-row__move { display: inline-flex; flex-direction: column; gap: 2px; flex: none; }
.file-row__move .icon-btn {
  width: 22px; height: 18px; padding: 0;
  color: var(--ink-3);
}
.file-row__move .icon-btn svg { transform: rotate(180deg); }
.file-row__move .icon-btn.is-down svg { transform: none; }
.file-row__move .icon-btn:disabled { opacity: .3; cursor: default; }
.file-row__move .icon-btn:not(:disabled):hover { color: var(--brand); background: var(--brand-soft); }

/* -------------------------------------------- what the setting actually does */

.qpreview {
  margin-top: var(--s-8);
  padding: var(--s-8);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--surface-2);
}
.qpreview__pair {
  display: grid; gap: var(--s-6);
  grid-template-columns: 1fr 1fr;
}
.qpreview__fig { margin: 0; min-width: 0; }
.qpreview__fig img {
  display: block; width: 100%; aspect-ratio: 4 / 3;
  object-fit: contain;
  background: var(--surface-3);
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
}
.qpreview__fig figcaption {
  display: flex; justify-content: space-between; gap: var(--s-4);
  margin-top: var(--s-3);
  font-family: var(--mono); font-size: var(--fs-2);
  color: var(--ink-3);
}
.qpreview__fig figcaption b { color: var(--ink); font-weight: 700; }
.qpreview__note {
  margin-top: var(--s-6);
  font-size: var(--fs-2); color: var(--ink-3);
  font-family: var(--mono);
}
.qpreview__verdict { font-weight: 700; }
.qpreview__verdict.is-good { color: var(--ok); }
.qpreview__verdict.is-bad { color: var(--err); }

/* ---------------------------------------------- the prose the build wrote */

/*
 * Carried over from the served HTML rather than rendered by the app, so the
 * words survive hydration and the crawler sees what the reader sees.
 */
.prose {
  max-width: 68ch;
  margin: clamp(2.5rem, 5vw, 4rem) auto 0;
  color: var(--ink-2);
}
.prose section + section { margin-top: var(--s-12); }
.prose h2 {
  font-size: var(--fs-9);
  font-weight: 750;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0 0 var(--s-5);
}
.prose p { margin: 0 0 var(--s-6); line-height: 1.7; }
.prose ul, .prose ol { margin: 0 0 var(--s-6); padding-left: var(--s-10); line-height: 1.7; }
.prose li { margin-bottom: var(--s-3); }
.prose li::marker { color: var(--ink-4); }

/* The date the written part of the page last changed. */
.prose .updated {
  margin: 0 0 var(--s-6);
  font-size: var(--fs-3); color: var(--ink-4);
}
/*
 * The self-contained answer at the top of a page — the passage written to be
 * quoted whole. Set apart just enough to read as the summary it is.
 */
.prose .answer {
  margin: 0 0 var(--s-10);
  padding: var(--s-6) var(--s-8);
  border-radius: var(--r);
  border-left: 3px solid var(--brand);
  background: var(--surface-2);
  color: var(--ink);
}
.prose section > .answer { margin-bottom: var(--s-6); }

/* The per-platform "how to open this" table. */
.prose table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--s-6) 0 var(--s-8);
  font-size: var(--fs-3);
}
.prose caption {
  text-align: left;
  font-family: var(--mono);
  font-size: var(--fs-2);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-4);
  padding-bottom: var(--s-4);
}
.prose th, .prose td {
  text-align: left;
  padding: var(--s-5) var(--s-6) var(--s-5) 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.prose th[scope="row"] {
  width: 8.5rem;
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
}
.prose thead th { color: var(--ink-4); font-size: var(--fs-2); }

@media (max-width: 560px) {
  .prose th[scope="row"] { width: auto; display: block; border: 0; padding-bottom: var(--s-2); }
  .prose td { display: block; padding-top: 0; }
}

/* ------------------------------------------------------- written articles */

.article { padding-top: clamp(1.5rem, 4vw, 3rem); }
.article-head { max-width: 68ch; margin-inline: auto; }
.article-head__title {
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 800; letter-spacing: -0.04em; line-height: 1.05;
  margin: 0 0 var(--s-6);
}
.article-head__lede {
  font-size: var(--fs-7); line-height: 1.6; color: var(--ink-3); margin: 0;
}
.article .prose { margin-top: clamp(2rem, 4vw, 3rem); }



/* --------------------------------------------- what happens after a run */

.after {
  display: flex; flex-wrap: wrap; gap: var(--s-4);
  margin-top: var(--s-6);
}

/*
 * Chaining. Presented as its own block rather than as three more buttons in
 * the row above, because it is a different kind of offer: those act on the
 * file you have, this one starts the next job with it.
 */
.chain {
  margin-top: var(--s-8);
  padding-top: var(--s-7);
  border-top: 1px dashed var(--line-2);
}
.chain__label {
  font-size: var(--fs-3); color: var(--ink-3); margin: 0 0 var(--s-5);
}
.chain__label b { color: var(--ink); font-weight: 650; }
.chain__row { display: grid; gap: var(--s-4); }
.chain__go {
  display: flex; align-items: center; gap: var(--s-5);
  width: 100%; padding: var(--s-5) var(--s-6);
  font: inherit; font-size: var(--fs-4); font-weight: 650; color: var(--ink);
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  cursor: pointer;
  transition: transform .3s var(--ease), border-color .2s, background .2s;
}
.chain__go:hover {
  transform: translateX(3px);
  border-color: var(--brand);
  background: var(--brand-softer);
}
.chain__go:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.chain__ico {
  display: grid; place-items: center; flex: none;
  width: 30px; height: 30px; border-radius: 9px;
  background: var(--accent-fill, var(--fill-brand)); color: #fff;
}
.chain__name { flex: 1; min-width: 0; }
.chain__arrow {
  display: flex; color: var(--ink-4); flex: none;
  transition: transform .3s var(--ease), color .2s;
}
.chain__go:hover .chain__arrow { transform: translateX(3px); color: var(--brand); }

/* ------------------------------------------------------- the bill */

/*
 * The ask, as a second stub torn off the bottom of the receipt.
 *
 * It is below the download and never over it, and it is never a dialog:
 * interrupting somebody mid-download to ask for money would cost more in
 * abandoned conversions than it could raise. What it does instead is
 * itemise what they got, add it up, and land on zero.
 */
.bill {
  position: relative;
  margin: var(--s-10) calc(var(--s-8) * -1) calc(var(--s-8) * -1);
  padding: var(--s-12) var(--s-8) var(--s-8);
  background: var(--surface-2);
  border-top: 1px dashed var(--line-2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  overflow: hidden;
}
/* The torn edge, in the same zigzag the receipt head already uses. */
.bill::before {
  content: '';
  position: absolute; left: 0; right: 0; top: 0; height: 11px;
  background:
    conic-gradient(from 315deg at 50% 100%, var(--surface) 0 25%, transparent 0 100%)
    0 0 / 16px 11px repeat-x;
}

.bill__head {
  font-family: var(--mono); font-size: var(--fs-2); font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-4);
  margin: 0 0 var(--s-6);
}

.bill__lines { list-style: none; margin: 0 0 var(--s-8); padding: 0; }
.bill__line {
  display: flex; align-items: baseline; gap: var(--s-4);
  padding: var(--s-3) 0;
  font-size: var(--fs-3);
  color: var(--ink-2);
}
/* A leader of dots, so the eye tracks label to figure like a real bill. */
.bill__what { flex: none; }
.bill__line::after {
  content: '';
  flex: 1;
  height: 1px;
  margin-bottom: 3px;
  background-image: radial-gradient(circle, var(--ink-4) 0.5px, transparent 0.5px);
  background-size: 5px 1px;
  opacity: .5;
  order: 1;
}
.bill__val {
  order: 2; flex: none;
  font-family: var(--mono); font-weight: 650; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.bill__line.is-total {
  margin-top: var(--s-4); padding-top: var(--s-5);
  border-top: 1px solid var(--line-2);
  font-weight: 700;
}
.bill__line.is-total .bill__what { color: var(--ink); font-weight: 700; }
.bill__line.is-total .bill__val { font-size: var(--fs-7); color: var(--ok); }

/*
 * The stamp. The only decorative thing in the block, and the part anyone
 * will actually remember — which is the reason it is worth the eleven
 * lines it costs.
 */
.bill__stamp {
  /* Bottom right, where a paid stamp lands on a real receipt — and clear of
     the figures, which it used to sit on top of. */
  position: absolute; right: var(--s-8); bottom: var(--s-10);
  display: grid; gap: 1px; justify-items: center;
  padding: var(--s-4) var(--s-6);
  border: 2.5px solid color-mix(in srgb, var(--brand) 55%, transparent);
  border-radius: var(--r-sm);
  color: color-mix(in srgb, var(--brand) 75%, transparent);
  font-family: var(--mono); text-transform: uppercase;
  line-height: 1.15; text-align: center;
  transform: rotate(-7deg);
}
.bill__stamp b { font-size: var(--fs-2); font-weight: 700; letter-spacing: .12em; }
.bill__stamp span { font-size: var(--fs-4); font-weight: 700; letter-spacing: .04em; }

.bill__ask {
  font-size: var(--fs-3); line-height: 1.6; color: var(--ink-3);
  margin: 0 0 var(--s-6);
  /* Stops short of the stamp rather than running under it. */
  max-width: min(54ch, calc(100% - 9.5rem));
}
.bill__acts { display: flex; gap: var(--s-4); flex-wrap: wrap; }

/* The warmer variant, for somebody who has put real work through the site. */
.bill.is-warm { background: var(--brand-softer); }
.bill.is-warm::before {
  background:
    conic-gradient(from 315deg at 50% 100%, var(--surface) 0 25%, transparent 0 100%)
    0 0 / 16px 11px repeat-x;
}

/*
 * The arrival. The stub unrolls, the lines walk in, and the stamp lands
 * last with a small overshoot — the order a real receipt would be read in.
 */
@media (prefers-reduced-motion: no-preference) {
  .bill { animation: bill-unroll .5s var(--ease) both; }
  .bill__line { animation: bill-line .42s var(--ease) both; }
  .bill__line:nth-child(1) { animation-delay: .16s; }
  .bill__line:nth-child(2) { animation-delay: .24s; }
  .bill__line:nth-child(3) { animation-delay: .32s; }
  .bill__line:nth-child(4) { animation-delay: .40s; }
  .bill__line:nth-child(5) { animation-delay: .52s; }
  .bill__stamp { animation: bill-stamp .5s cubic-bezier(.2, 1.5, .4, 1) .78s both; }
  .bill__ask, .bill__acts { animation: bill-line .4s var(--ease) .86s both; }
}
@keyframes bill-unroll {
  from { opacity: 0; transform: translateY(-6px) scaleY(.94); transform-origin: top; }
  to   { opacity: 1; transform: none; }
}
@keyframes bill-line {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes bill-stamp {
  from { opacity: 0; transform: rotate(-26deg) scale(1.6); }
  to   { opacity: 1; transform: rotate(-7deg) scale(1); }
}

@media (max-width: 560px) {
  .bill__stamp { position: static; margin: 0 0 var(--s-7); align-self: flex-start; width: fit-content; }
  .bill__ask { max-width: none; }
  .bill { display: flex; flex-direction: column; }
  .bill__head { order: -2; } .bill__lines { order: -1; }
}

/* ================================================== the pre-hydration view

   Everything inside <main id="view"> is written by prerender.js, and it is
   what a visitor actually looks at until the module graph has loaded and
   the app takes over. On a first visit that is a real window — thirty-odd
   modules on a cold cache — and the markup used to spend it as raw
   full-bleed HTML with browser-default type, because the classes that
   style it were being attached by the very JavaScript we were waiting for.
   It read, fairly, as "the site has no CSS".

   So the served markup now carries its own classes and its own container,
   and these rules dress the parts that sit outside .prose. None of it
   survives hydration: route() replaces the whole of #view, wrapper and
   all, so nothing here can leak into the running app.
   ========================================================================= */

.seed { padding-block: 2.5rem 4rem; }

.seed h1 { font-size: clamp(2rem, 4vw, 2.6rem); line-height: 1.15; letter-spacing: -.02em; }
.seed h2 { font-size: 1.25rem; margin-top: 2.25rem; }
.seed h3 { font-size: 1rem; margin-top: 1.375rem; }
.seed p  { max-width: 68ch; margin-top: .75rem; color: var(--ink-2); }

.seed a {
  color: var(--brand-deep);
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--brand) 40%, transparent);
}

.seed ul, .seed ol { max-width: 68ch; margin-top: .75rem; padding-left: 1.25rem; color: var(--ink-2); }
.seed li { margin-top: .3125rem; }

.seed table { margin-top: 1rem; border-collapse: collapse; }
.seed th, .seed td { padding: .375rem 1.25rem .375rem 0; text-align: left; border-bottom: 1px solid var(--line); }
.seed th { color: var(--ink); font-weight: 600; }

/* The link blocks are long — a wrapped row of links reads faster than a
   column of forty bullets, and takes a fraction of the height. */
.seed nav { margin-top: 2.25rem; }
.seed nav ul { display: flex; flex-wrap: wrap; gap: .375rem 1.125rem; max-width: none; padding-left: 0; list-style: none; }
.seed nav li { margin-top: 0; }

/* .prose centres itself inside the app's narrower column; here it shares a
   container with the h1, so it lines up with it instead. */
.seed .prose { margin-inline: 0; padding-top: 0; }

/* ============================================================ the key page

   Twelve bars, one per pitch class, showing what the recording actually
   contains. It is the evidence behind the verdict rather than decoration:
   somebody who disagrees with the answer can look at this and see why it
   was given, which a single line of large type never allows.
   ========================================================================= */

.chroma {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  align-items: end;
  gap: var(--s-7);
  height: 190px;
  margin-top: var(--s-8);
  padding: var(--s-7) var(--s-7) 0;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--surface-2);
}

.chroma__col { display: flex; flex-direction: column; align-items: center; gap: var(--s-7); height: 100%; justify-content: flex-end; min-width: 0; }

.chroma__bar {
  width: 100%;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  background: var(--fill-brand);
  transition: height .5s var(--ease);
}

.chroma__name {
  font-family: var(--mono);
  font-size: var(--fs-1);
  color: var(--ink-3);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .chroma__bar { transition: none; }
}

@media (max-width: 560px) {
  .chroma { height: 150px; gap: 3px; padding: var(--s-7) 4px 0; }
  .chroma__name { font-size: 10px; }
}

/* ==================================================== how it works, shown

   Three panels, each a picture of the step it describes.

   The reference this grew from was dark-only and leaned on a gradient for
   its centre card. Neither is available here: the site has a cream light
   theme as well as a dark one, and gradients are out across the whole
   project. So the panels are built from the same surface tokens as
   everything else and the middle one earns its prominence with a solid
   brand fill, a ring and a lift instead — which survives both themes and
   reads as deliberate rather than as decoration.
   ========================================================================= */

.how {
  margin-top: clamp(5rem, 9vw, 8rem);
  padding: clamp(3.5rem, 8vw, 6rem) 0;
  background: var(--surface-2);
  border-top: 1px solid var(--line);
}
.how__inner { width: min(1240px, 100% - 2.5rem); margin-inline: auto; }

.how__head { text-align: center; }
.how__head .eyebrow { justify-content: center; }
.how__title {
  max-width: 18ch;
  margin: .875rem auto 0;
  font-size: clamp(2rem, 4.6vw, 3.1rem);
  font-weight: 800; letter-spacing: -0.045em;
  text-wrap: balance;
}
.how__lede {
  max-width: 56ch;
  margin: 1rem auto 0;
  color: var(--ink-3);
  font-size: clamp(1rem, 1.3vw, 1.0625rem);
  line-height: 1.65; text-wrap: pretty;
}

/* ----------------------------------------------------------------- grid */

.how__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.25rem, 2.4vw, 2rem);
  align-items: start;
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.step { display: grid; gap: 1.25rem; }

/*
 * The panel the mockup sits in. Fixed height so the three line up along
 * their bottom edge whatever is inside them — three mockups of different
 * natural heights left ragged would undo the whole point of a row.
 */
.step__mock {
  position: relative;
  display: grid; align-content: center;
  gap: .75rem;
  height: 330px;
  padding: clamp(1rem, 1.8vw, 1.375rem);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  background: var(--surface);
  overflow: hidden;
}

/* The middle one leads. Solid fill, a ring, and a lift — no gradient. */
.step--lead .step__mock {
  background: var(--fill-brand);
  border-color: transparent;
  box-shadow: 0 18px 44px -22px rgb(217 61 38 / 55%);
  transform: translateY(-10px);
}

.step__say { display: grid; gap: .5rem; }
.step__num {
  font-family: var(--mono); font-size: .6875rem; font-weight: 700;
  letter-spacing: .14em; color: var(--ink-4);
}
.step__title {
  font-size: clamp(1.25rem, 2vw, 1.5rem);
  font-weight: 700; letter-spacing: -0.03em;
}
.step__text { color: var(--ink-3); font-size: .9375rem; line-height: 1.65; }

/* =============================================== the mockups themselves */

.mock { display: grid; gap: .75rem; font-size: .8125rem; }

/* --- 1. intake ---------------------------------------------------------- */

.mock__drop {
  display: grid; justify-items: center; gap: .25rem;
  padding: 1.5rem 1rem;
  border: 1px dashed color-mix(in srgb, var(--brand) 45%, var(--line-2));
  border-radius: var(--r);
  background: var(--brand-soft);
  text-align: center;
}
.mock__droparrow {
  display: grid; place-items: center;
  width: 34px; height: 34px; margin-bottom: .375rem;
  border-radius: 50%;
  background: var(--fill-brand); color: #fff;
}
.mock__drop strong { font-size: .875rem; font-weight: 700; }
.mock__dropnote { color: var(--ink-4); font-size: .75rem; }

.mock__files { display: grid; gap: .5rem; }
.mock__file {
  display: flex; align-items: center; gap: .625rem;
  padding: .625rem .75rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.mock__filetype {
  display: grid; place-items: center;
  width: 28px; height: 28px; flex: none;
  border-radius: var(--r-chip); color: #fff;
}
.mock__filetype[data-kind="video"] { background: var(--video); }
.mock__filetype[data-kind="image"] { background: var(--fill-brand); }
.mock__filemeta { display: grid; gap: 1px; min-width: 0; }
.mock__filename {
  font-size: .8125rem; font-weight: 650;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mock__filesize { font-family: var(--mono); font-size: .6875rem; color: var(--ink-4); }
.mock__filesize em { font-style: normal; color: var(--ok); font-weight: 700; }
.mock__filedot {
  width: 7px; height: 7px; border-radius: 50%; margin-left: auto; flex: none;
  background: var(--ok);
}

/* --- 2. the picker (on the brand panel) --------------------------------- */

/*
 * Everything in here sits on a solid coral, so it takes its own palette:
 * white ink, and translucent whites for the surfaces rather than the page's
 * surface tokens, which are built for a page background.
 */
.mock--pick { color: #fff; }

.mock__slots {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: .625rem;
}
.mock__slot {
  display: grid; gap: .25rem; justify-items: center;
  padding: .75rem .5rem;
  border-radius: var(--r-sm);
  background: rgb(255 255 255 / 14%);
  border: 1px solid rgb(255 255 255 / 22%);
}
.mock__slot span {
  font-family: var(--mono); font-size: .625rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: rgb(255 255 255 / 72%);
}
.mock__slot strong { font-size: 1.0625rem; font-weight: 800; letter-spacing: .02em; }
.mock__slot--to { background: rgb(255 255 255 / 24%); border-color: rgb(255 255 255 / 40%); }
.mock__arrow { display: grid; place-items: center; color: rgb(255 255 255 / 80%); }

.mock__chips { display: flex; flex-wrap: wrap; gap: .3125rem; }
.mock__chip {
  padding: .25rem .5rem;
  border-radius: var(--r-xs);
  font-family: var(--mono); font-size: .625rem; font-weight: 700; letter-spacing: .06em;
  color: rgb(255 255 255 / 76%);
  border: 1px solid rgb(255 255 255 / 22%);
}
.mock__chip.is-on { background: #fff; color: var(--brand-deep); border-color: #fff; }

.mock__quality { display: grid; gap: .4375rem; }
.mock__qlabel {
  display: flex; justify-content: space-between;
  font-family: var(--mono); font-size: .625rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: rgb(255 255 255 / 72%);
}
.mock__qlabel b { color: #fff; }
.mock__track {
  position: relative; height: 5px; border-radius: 3px;
  background: rgb(255 255 255 / 26%);
}
.mock__fill {
  position: absolute; inset: 0 25% 0 0;
  border-radius: 3px; background: #fff;
}
.mock__knob {
  position: absolute; top: 50%; left: 75%;
  width: 14px; height: 14px; border-radius: 50%;
  translate: -50% -50%;
  background: #fff;
  box-shadow: 0 2px 6px -1px rgb(120 30 12 / 45%);
}

.mock__readout {
  display: flex; align-items: center; gap: .5rem;
  padding-top: .625rem;
  border-top: 1px solid rgb(255 255 255 / 24%);
  font-family: var(--mono); font-weight: 700;
}
.mock__from { font-size: .8125rem; color: rgb(255 255 255 / 62%); text-decoration: line-through; }
.mock__rarrow { display: grid; place-items: center; color: rgb(255 255 255 / 62%); }
.mock__to { font-size: 1rem; color: #fff; }
.mock__delta {
  margin-left: auto;
  padding: .1875rem .4375rem; border-radius: 5px;
  font-size: .6875rem;
  background: #fff; color: var(--brand-deep);
}

/* --- 3. results --------------------------------------------------------- */

.mock__head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding-bottom: .625rem; border-bottom: 1px solid var(--line-2);
}
.mock__head strong { font-size: .875rem; font-weight: 700; }
.mock__all { font-size: .75rem; font-weight: 650; color: var(--brand-deep); }

.mock__results { display: grid; gap: .5rem; }
.mock__result {
  display: flex; align-items: center; gap: .625rem;
  padding: .625rem .75rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.mock__ext {
  padding: .1875rem .375rem; border-radius: 5px; flex: none;
  font-family: var(--mono); font-size: .5625rem; font-weight: 700; letter-spacing: .06em;
  background: var(--brand-soft); color: var(--brand-deep);
}
.mock__resultname {
  font-size: .8125rem; font-weight: 650; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mock__resultsize {
  margin-left: auto; flex: none;
  font-family: var(--mono); font-size: .6875rem; color: var(--ink-4);
}
.mock__save {
  display: grid; place-items: center; flex: none;
  width: 24px; height: 24px; border-radius: var(--r-xs);
  background: var(--fill-brand); color: #fff;
}

.mock__ledger {
  display: grid; gap: .375rem;
  padding-top: .75rem; margin-top: .125rem;
  border-top: 1px dashed var(--line-2);
}
.mock__ledgerrow {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: .75rem; color: var(--ink-3);
}
.mock__ledgerrow b { font-family: var(--mono); font-size: .75rem; color: var(--ink-2); }
/* The line the whole site rests on, and the only one that can be zero. */
.mock__ledgerrow.is-zero { color: var(--ink-2); font-weight: 650; }
.mock__ledgerrow.is-zero b {
  font-size: 1rem; font-weight: 800;
  color: var(--ok);
}

/* ------------------------------------------------------------ responsive */

@media (max-width: 980px) {
  /*
   * Two up, in source order.
   *
   * Pulling the lead panel to the top and spanning it looked better and read
   * worse: the steps are numbered, and the eye met 02 before 01. A sequence
   * that argues with its own numbering is a worse problem than a lone third
   * card, so the lead keeps its place and simply stops floating above the
   * row it is no longer taller than.
   */
  .how__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .step--lead .step__mock { transform: none; }
}

@media (max-width: 640px) {
  .how__grid { grid-template-columns: minmax(0, 1fr); }
  .step--lead { order: 0; }
  .step__mock { height: auto; min-height: 280px; }
}

@media (prefers-reduced-motion: reduce) {
  .step--lead .step__mock { transform: none; }
}

/* ===================================================== families of five

   Tools and conversions share this layout on purpose. They answer two
   different questions — what can it do to my file, and what can it turn my
   file into — and making them siblings is what tells somebody those are two
   questions rather than one list printed twice.
   ========================================================================= */

.families {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem);
}

.family { display: grid; gap: .75rem; align-content: start; }

.family__head {
  display: flex; align-items: center; gap: .5rem;
  font-family: var(--mono); font-size: .6875rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3);
}
.family__dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--accent, var(--brand));
}

/* One block per family, subdivided by hairlines: three objects on the page
   rather than fifteen, which is the whole reason for grouping them. */
.family__list {
  display: grid;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--surface);
  overflow: hidden;
}

.pick {
  display: flex; align-items: center; gap: .625rem;
  padding: .75rem .875rem;
  text-decoration: none; color: inherit;
  border-top: 1px solid var(--line);
  transition: background var(--t-fast), color var(--t-fast);
}
.pick:first-child { border-top: 0; }
.pick:hover { background: var(--surface-2); }

.pick__icon {
  display: grid; place-items: center; flex: none;
  width: 26px; height: 26px; border-radius: 7px;
  color: #fff; background: var(--accent-fill, var(--fill-brand));
}
.pick__name {
  font-size: .875rem; font-weight: 650; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* A conversion reads as a pair, so it is set as one rather than as a title. */
.pick__from, .pick__to {
  font-family: var(--mono); font-size: .75rem; font-weight: 700; letter-spacing: .04em;
}
.pick__from { color: var(--ink-3); }
.pick__to { color: var(--ink); }
.pick__arrow { display: grid; place-items: center; color: var(--ink-4); flex: none; }

.pick__go {
  margin-left: auto; flex: none;
  display: grid; place-items: center; color: var(--ink-4);
  opacity: 0; transform: translateX(-4px);
  transition: opacity var(--t-fast), transform var(--t) var(--ease), color var(--t-fast);
}
.pick:hover .pick__go { opacity: 1; transform: none; color: var(--accent, var(--brand)); }

/*
 * The last row of a family: a way on rather than another destination.
 *
 * It reads quieter than the five above it — no icon block, brand ink, a
 * dashed rule instead of the hairline — so the eye reads five things and an
 * exit, not six things one of which is odd.
 */
.pick--more {
  border-top: 1px dashed var(--line-2);
  color: var(--accent, var(--brand-deep));
}
.pick--more .pick__name { font-weight: 650; font-size: .8125rem; }
.pick--more .pick__go { opacity: 1; transform: none; color: currentColor; }
.pick--more:hover { background: var(--accent-soft, var(--brand-soft)); }
.pick--more:hover .pick__go { transform: translateX(3px); }

@media (prefers-reduced-motion: reduce) {
  .pick, .pick__go { transition: none; }
  .pick:hover .pick__go { transform: none; }
}

@media (max-width: 900px) {
  .families { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .families { grid-template-columns: minmax(0, 1fr); }
  /* Touch has no hover, so the chevron would never arrive. */
  .pick__go { opacity: 1; transform: none; }
}

/* ================================================ the panels, on hover

   A hover that only lifts the card tells you the card noticed you. These
   ones perform the step they are describing instead, so the section demos
   the product rather than illustrating it.

     1  the dropzone lights up the way it does with a file over it
     2  the quality goes up, and the size and the saving follow it
     3  the two results save, one after the other

   Panel 2 is the one that earns its keep: it is the whole trade-off of the
   product in one gesture — push the quality up, watch the file get bigger.
   Nothing here is decoration for its own sake.
   ========================================================================= */

.step__mock { transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease); }
.step:hover .step__mock {
  transform: translateY(-6px);
  box-shadow: var(--sh-2);
}
.step--lead:hover .step__mock {
  transform: translateY(-16px);
  box-shadow: 0 26px 56px -22px rgb(217 61 38 / 62%);
}

/* --- 1. the dropzone, as if a file were over it ------------------------- */

.mock__drop {
  transition: border-color var(--t-fast), background var(--t-fast);
}
.mock__droparrow { transition: transform var(--t) var(--ease-soft); }
.step[data-step="add"]:hover .mock__drop {
  border-color: var(--brand);
  border-style: solid;
  background: color-mix(in srgb, var(--brand) 12%, var(--surface));
}
.step[data-step="add"]:hover .mock__droparrow { transform: translateY(-3px) scale(1.06); }

/* The queue answers too, a beat behind the zone, so it reads as a
   consequence of the drop rather than as four things twitching at once. */
.mock__file { transition: border-color var(--t-fast), transform var(--t) var(--ease); }
.step[data-step="add"]:hover .mock__file { border-color: var(--line-2); transform: translateX(3px); }
.step[data-step="add"]:hover .mock__file:nth-child(1) { transition-delay: .05s; }
.step[data-step="add"]:hover .mock__file:nth-child(2) { transition-delay: .11s; }

/* --- 2. the quality, and everything that follows from it ---------------- */

/*
 * Written here rather than in the markup precisely so they cannot drift:
 * one hover rule changes the slider, the number, the output size and the
 * saving in the same declaration block.
 */
.mock__qval::after { content: '75'; }
.mock__to::after { content: '12.4 KB'; }
.mock__delta::after { content: '−79%'; }

.mock__fill, .mock__knob { transition: all var(--t) var(--ease); }

.step[data-step="pick"]:hover .mock__qval::after { content: '92'; }
.step[data-step="pick"]:hover .mock__to::after { content: '21.6 KB'; }
.step[data-step="pick"]:hover .mock__delta::after { content: '−63%'; }
.step[data-step="pick"]:hover .mock__fill { inset: 0 8% 0 0; }
.step[data-step="pick"]:hover .mock__knob { left: 92%; }

/* The chosen format lifts a little, so the row reads as a control. */
.mock__chip { transition: transform var(--t) var(--ease); }
.step[data-step="pick"]:hover .mock__chip.is-on { transform: translateY(-2px); }

/* --- 3. the results, saving one after the other ------------------------- */

.mock__result { position: relative; overflow: hidden; }
/*
 * A sweep under each row rather than a spinner: a download that is already
 * finished should not be drawn as one in progress, and a wash that passes
 * once reads as "that one is done" without claiming to be a real measure.
 */
.mock__result::after {
  content: '';
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--ok) 16%, transparent);
  transform-origin: left;
  scale: 0 1;
  opacity: 0;
}
.step[data-step="save"]:hover .mock__result::after {
  animation: mock-saved .55s var(--ease) forwards;
}
.step[data-step="save"]:hover .mock__result:nth-child(2)::after { animation-delay: .14s; }

@keyframes mock-saved {
  0%   { scale: 0 1; opacity: 1; }
  70%  { scale: 1 1; opacity: 1; }
  100% { scale: 1 1; opacity: 0; }
}

.mock__save { transition: transform var(--t) var(--ease); }
.step[data-step="save"]:hover .mock__save { transform: translateY(2px); }
.step[data-step="save"]:hover .mock__save { transition-delay: .1s; }

/* The line that matters gets the last word. */
.mock__ledgerrow.is-zero b { transition: transform var(--t) var(--ease); }
.step[data-step="save"]:hover .mock__ledgerrow.is-zero b { transform: scale(1.14); }

/* ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .step__mock, .mock__drop, .mock__droparrow, .mock__file,
  .mock__fill, .mock__knob, .mock__chip, .mock__save,
  .mock__ledgerrow.is-zero b { transition: none; }
  .step:hover .step__mock,
  .step--lead:hover .step__mock { transform: none; }
  .step[data-step="add"]:hover .mock__droparrow,
  .step[data-step="add"]:hover .mock__file,
  .step[data-step="pick"]:hover .mock__chip.is-on,
  .step[data-step="save"]:hover .mock__save,
  .step[data-step="save"]:hover .mock__ledgerrow.is-zero b { transform: none; }
  .step[data-step="save"]:hover .mock__result::after { animation: none; }
}

/* Touch has no hover, so the panels simply keep their resting state — which
   is the state that reads correctly on its own. */
@media (hover: none) {
  .step:hover .step__mock,
  .step--lead:hover .step__mock { transform: none; box-shadow: none; }
  .step--lead .step__mock { box-shadow: 0 18px 44px -22px rgb(217 61 38 / 55%); }
}
