/* The application's own stylesheet, so the window on this page is the window.
 *
 * `src/css/01-base.css` … `05-later.css` concatenated in the import order of
 * `src/styles.css` — that order is load-bearing, see the note at the top of
 * that file — and mechanically prefixed with `.shot`, so the application's
 * rules cannot reach the page around them. Nothing else was changed: no rule
 * was rewritten, dropped or reordered.
 *
 * `:root` blocks are left global on purpose. The page is drawn from the same
 * tokens as the application, which is the whole idea.
 *
 * Regenerate whenever the application's CSS changes; the page has no other
 * copy of it.
 */
/* ==== src/css/01-base.css ==== */
/* Tokens, both palettes, the scrollbar, the header bar, buttons and the notice.
 *
 * Part 01 of styles.css. The number is the point: these files are
 * one stylesheet cut into pieces, and the order they are imported in is the
 * order the rules were in. Moving a rule between them can change the look
 * without changing a value. */

/* Přemysl — visual language taken from the supplied design system.
   Colours are carried over unchanged as raw HSL triples so the next version
   of the system can be pasted into one place. The shapes come from there too:
   controls are pills, cards have a large radius, section labels are small
   caps, numerals are tabular. */

:root {
  --ds-background: 220 13% 96%;
  --ds-foreground: 240 7% 11%;
  --ds-card: 0 0% 100%;
  --ds-muted: 220 10% 91%;
  --ds-muted-foreground: 225 5% 43%;
  --ds-accent: 220 12% 92%;
  --ds-border: 220 8% 91%;
  --ds-input: 220 8% 82%;
  --ds-destructive: 6 91% 57%;
  --ds-ring: 240 7% 11%;
  --ds-petrol: 218 100% 55%;
  --ds-amber: 32 59% 44%;
  --ds-grass: 101 51% 46%;

  --ground: hsl(var(--ds-background));
  --panel: hsl(var(--ds-card));
  --text: hsl(var(--ds-foreground));
  --text-quiet: hsl(var(--ds-muted-foreground));
  --line: hsl(var(--ds-border));
  --line-field: hsl(var(--ds-input));
  --muted: hsl(var(--ds-accent));
  --subtle: hsl(var(--ds-foreground) / 0.02);
  --accent: hsl(var(--ds-petrol));
  --accent-light: hsl(var(--ds-petrol) / 0.08);
  --accent-line: hsl(var(--ds-petrol) / 0.25);
  --danger: hsl(var(--ds-destructive));
  --danger-light: hsl(var(--ds-destructive) / 0.08);
  --success: hsl(var(--ds-grass));

  /* Three colours that carry text, where the brand value is tuned for the
     opposite job — being read *against* a neutral surface rather than under
     white, or written on its own tint. Each one below is the same hue and
     saturation taken to the lightness that reaches 4.5 : 1, and each is used
     only where that pair actually occurs. The brand tokens above do not move,
     so nothing that merely borrows the colour changes with them.

     --accent-solid   white on a filled accent bar: 4.47 : 1 light and
       3.29 : 1 dark, where the accent is lightened so it can be read as text.
       One value for both palettes, 4.76 : 1 — a filled bar has the same job
       whatever is behind it.
     --danger-solid white on a filled red surface: 3.69 / 3.50 before.
       44 % rather than 46 so the brightness(1.08) hover still clears 4.5
       (4.53 light, 4.82 dark) instead of falling to 4.19.
     --success-text   grass written on the grass/0.14 tint it always sits on:
       2.36 : 1 in the light palette. The tint is unchanged, only the ink —
       5.26 : 1 on a panel, 4.84 : 1 on the page background. The dark palette
       already clears it at 4.93 and keeps its own value. */
  --accent-solid: hsl(218 100% 53%);
  --danger-solid: hsl(6 91% 44%);
  --success-text: hsl(101 51% 29%);
  --highlight: hsl(var(--ds-amber) / 0.22);
  /* Notes are the one surface that is not part of the neutral chrome. Paper
     yellow says "written by hand" at a glance, without a label. */
  --note: hsl(46 88% 92%);
  --note-line: hsl(44 52% 78%);
  --note-field: hsl(48 70% 97%);
  --ring: 0 0 0 2px hsl(var(--ds-ring) / 0.4);
  --ring-field: 0 0 0 3px hsl(var(--ds-petrol) / 0.16);
  --hover-duration: 0.22s;
  --hover-ease: ease;
  --app-footer-height: 30px;

  /* The veil behind a dialog. Dark in both palettes on purpose — see
     `.dialog-overlay`. */
  --scrim: hsl(240 7% 11% / 0.35);
  --shadow: 0 1px 4px hsl(var(--ds-foreground) / 0.08);
  --shadow-high: 0 12px 32px hsl(var(--ds-foreground) / 0.14), 0 2px 8px hsl(var(--ds-foreground) / 0.06);

  /* Shapes: pills for controls, soft corners for surfaces */
  --r-pill: 999px;
  --r-card: 12px;
  --r-large: 16px;
  --r-small: 8px;

  /* Overwritten from Settings at runtime; this is only the default. */
  --font: "Geist Variable", "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif;
  --font-body: "Literata Variable", Georgia, "Times New Roman", serif;
  /* Paths and other fixed-pitch values. Not written from Settings — it was
     used through a fallback in two places and defined nowhere, so the two
     could have drifted apart without anything noticing. */
  --font-path: ui-monospace, "Cascadia Mono", "Consolas", monospace;
  --text-size: 17.5px;
  --line-height: 1.72;
}

/* The dark palette. Which one applies is decided in JavaScript and written to
   `data-theme` on the root element — following the system is one of the three
   choices in Settings, and a media query alone cannot be overridden by a
   person's decision. `main.tsx` writes the attribute before React mounts, from
   the choice remembered in local storage, so the window never opens light and
   then turn dark. */
:root[data-theme="dark"] {
  --ds-background: 240 5% 12%;
  --ds-foreground: 220 15% 94%;
  --ds-card: 240 4% 16%;
  --ds-muted: 240 4% 21%;
  --ds-muted-foreground: 220 6% 66%;
  --ds-accent: 240 4% 21%;
  --ds-border: 240 4% 23%;
  --ds-input: 240 4% 28%;
  --ds-destructive: 6 80% 60%;
  --ds-ring: 220 15% 94%;
  --ds-petrol: 218 100% 64%;
  --ds-amber: 33 60% 55%;
  --ds-grass: 101 45% 52%;

  --subtle: hsl(var(--ds-foreground) / 0.04);
  /* Same idea in the dark theme: warm enough to read as paper, dim enough
     not to glare beside the neutral panels. */
  --note: hsl(46 16% 19%);
  --note-line: hsl(46 14% 31%);
  --note-field: hsl(46 12% 15%);
  --accent-light: hsl(var(--ds-petrol) / 0.14);
  /* `--accent-solid` is deliberately the same in both palettes; the other two
     follow their own palette's hue and saturation. Grass at 52 % already
     reads 4.93 : 1 on its own tint here, so it keeps the brand value. */
  --danger-solid: hsl(6 80% 44%);
  --success-text: hsl(var(--ds-grass));
  --highlight: hsl(var(--ds-amber) / 0.28);
  --scrim: rgb(0 0 0 / 0.55);
  --shadow: 0 1px 4px rgb(0 0 0 / 0.3);
  --shadow-high: 0 12px 32px rgb(0 0 0 / 0.45), 0 2px 8px rgb(0 0 0 / 0.3);
}

.shot * { box-sizing: border-box; }

/* Custom scrollbar. The native one is needlessly wide and loud in an app window. */
.shot * {
  scrollbar-width: thin;
  scrollbar-color: hsl(var(--ds-foreground) / 0.22) transparent;
}
.shot ::-webkit-scrollbar { width: 12px; height: 12px; }
.shot ::-webkit-scrollbar-track { background: transparent; }
.shot ::-webkit-scrollbar-thumb {
  background: hsl(var(--ds-foreground) / 0.2);
  border-radius: var(--r-pill);
  /* a transparent border insets the thumb without narrowing the track */
  border: 3px solid transparent;
  background-clip: content-box;
}
.shot ::-webkit-scrollbar-thumb:hover { background: hsl(var(--ds-foreground) / 0.35); background-clip: content-box; }
.shot ::-webkit-scrollbar-corner { background: transparent; }
.shot,
.shot,
.shot { height: 100%; margin: 0; }

.shot {
  font-family: var(--font);
  background: var(--ground);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.shot button,
.shot input,
.shot select,
.shot textarea { font: inherit; color: inherit; }
.shot :focus-visible { outline: none; box-shadow: var(--ring); }

.shot .app { display: flex; flex-direction: column; height: 100%; }

/* The sidebar holds five cards on one page, and each one heads a list the
   reader works in. Upper case at 11 px labelled them; at that size five of
   them down one column read as fine print rather than as structure.
   This is the only rule that types this heading — the margin it once also
   carried further down the file is gone, because the card owns its spacing. */
.shot .sidebar h2,
.shot .sidebar [data-h="2"] {
  /* 680, the weight the archive's folder heading uses for the same job — a
     14 px label at the top of a list — and one the documented set allows. */
  font-size: 14px; font-weight: 680; text-transform: none;
  letter-spacing: -0.005em; color: var(--text);
}
/* A group label above a list in the wizard — deliberately below the 19 px of
   a card or a dialog heading, because it labels one part of a screen. This is
   the "wizard group label 17/700" of the type scale, and the only <h2> in the
   wizard is the one it describes: the group heading in the manual component
   list. `.manual h2` further down used to redeclare the type at the same
   specificity and win purely on file order, so this rule never drew anything;
   it now carries only that list's own margin. */
.shot .wizard h2,
.shot .wizard [data-h="2"] {
  font-size: 17px; font-weight: 700; color: var(--text);
  letter-spacing: -0.01em; text-transform: none;
}

/* ---------------------------------------------------------------- header bar */

/* Fixed height rather than derived from content. The mini player is two points taller
   than the buttons beside it, so closing it lifted the bar and everything
   below jumped. */
.shot .bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  height: 57px; padding: 0 18px; border-bottom: 1px solid var(--line);
  background: var(--panel); flex: 0 0 auto;
}
.shot .header-brand-mark {
  display: flex; align-items: center;
  padding: 4px 2px;
}
.shot .mark {
  background: none; border: 0; cursor: pointer;
  border-radius: var(--r-pill);
}
/* `.logotype` and `.header-brand-name` stood here — the isometric cube at 24 px
   and the wordmark beside it at 14.8, with 5.6 px between them and the wordmark
   dropped 1.9 px. Every one of those numbers was measured off the printed lockup
   in `src-tauri/nsis/header.bmp` so that the header would sit the way the
   installer sits, and every one of them described a pair. The header carries one
   drawing now, so there is no pair to keep in step and nothing left to measure
   against. The installer art still shows the cube and is the open half of this
   change.

   82 × 25 is the drawing's own viewBox at 1:1. The geometry of the closed state
   is not repeated here — `--wordmark-closed`, `--wordmark-shift` and
   `--wordmark-tm` arrive from `brand.ts`, which is where the outlines are, so a
   redrawn letterform cannot leave this file saying something else. */
/* 2 px down, and this is a correction that was here before and got deleted with
   the rest of the lockup. The old rule dropped the wordmark 1.9 px and said why:
   the ™ lifts the top of the drawing's box and the smile under the `o` drops its
   bottom, so a box centred in the bar does not put the letters where the eye
   expects them. The letters themselves run y 0.08 to 16.52 in a 25 tall box —
   their weight sits above the middle, and the mark reads as flying upward.

   That reasoning was never about the cube, so it survived the cube. Two, on the
   owner's word, where the artwork gave 1.9. */
.shot .wordmark {
  display: block; overflow: hidden;
  width: 82px; height: 25px;
  color: var(--text);
  /* `position`/`top`, not `transform: translateY`. The offset is the same, but a
     transform on this box makes it a compositing layer and hands everything
     inside it to the rasteriser; `top` moves it in layout and leaves the drawing
     a drawing. */
  position: relative; top: 2px;
  transition: width var(--wordmark-collapse) var(--wordmark-ease);
}
.shot .wordmark-svg { display: block; width: 82px; height: 25px; flex: 0 0 auto; }
.shot .wordmark-svg path { fill: currentColor; }
.shot .wordmark-shift,
.shot .wordmark-v,
.shot .wordmark-cal,
.shot .wordmark-tm {
  transition: transform var(--wordmark-collapse) var(--wordmark-ease),
              opacity var(--wordmark-collapse) var(--wordmark-ease);
}
.shot .wordmark.closed { width: calc(var(--wordmark-closed) * 1px); }
.shot .wordmark.closed .wordmark-shift { transform: translateX(calc(var(--wordmark-shift) * 1px)); }
.shot .wordmark.closed .wordmark-tm { transform: translateX(calc(var(--wordmark-tm) * 1px)); }
/* Variant B — the outer letters leave the frame instead of going out on the
   spot. Nothing fades: `v` drives off to the left and `cal` to the right, and
   the box clips them because it closes at the same time. The distances are what
   it takes to clear that box — `cal` also carries the group's own −12.45, so its
   44 is 31.5 of actual travel.

   Variant A stood here and is two lines: `opacity: 0` with a 7 unit drift on
   each. It is the quieter of the two, and this is the one that reads as
   machinery. */
.shot .wordmark.closed .wordmark-v { transform: translateX(-16px); }
.shot .wordmark.closed .wordmark-cal { transform: translateX(44px); }

/* Under the pointer it opens again, and quicker than it closed: the closing is
   staged and this is an answer to a hand.

   `:focus-visible` beside `:hover`, because this mark is a button — it goes back
   to the archive — and an affordance only a pointer can reach would leave every
   keyboard reader with a name they never see. `.ready` is what keeps a hand that
   happens to be resting here at start from cancelling the intro. */
.shot .wordmark.ready,
.shot .wordmark.ready .wordmark-shift,
.shot .wordmark.ready .wordmark-v,
.shot .wordmark.ready .wordmark-cal,
.shot .wordmark.ready .wordmark-tm {
  transition-duration: var(--wordmark-hover);
}
.shot .header-brand-mark:hover .wordmark.ready,
.shot .header-brand-mark:focus-visible .wordmark.ready { width: 82px; }
.shot .header-brand-mark:hover .wordmark.ready .wordmark-shift,
.shot .header-brand-mark:hover .wordmark.ready .wordmark-tm,
.shot .header-brand-mark:hover .wordmark.ready .wordmark-v,
.shot .header-brand-mark:hover .wordmark.ready .wordmark-cal,
.shot .header-brand-mark:focus-visible .wordmark.ready .wordmark-shift,
.shot .header-brand-mark:focus-visible .wordmark.ready .wordmark-tm,
.shot .header-brand-mark:focus-visible .wordmark.ready .wordmark-v,
.shot .header-brand-mark:focus-visible .wordmark.ready .wordmark-cal {
  transform: none; opacity: 1;
}
.shot .wordmark.still,
.shot .wordmark.still * { transition: none !important; }
.shot .bar-left { display: flex; align-items: center; gap: 6px; min-width: 0; }
.shot .bar-right { display: flex; align-items: center; gap: 4px; }
/* The pills sit at the right edge, before the named actions. Half the space
   comes from the group's own gap, half from here, so pill-to-pill and
   pill-to-button distances come out the same 8 px. */
.shot .bar-right > .mini-player { margin-right: 4px; }

.shot .app-status-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  height: var(--app-footer-height); padding: 0 20px; flex: 0 0 auto;
  border-top: 1px solid var(--line); background: var(--panel);
  color: var(--text-quiet); font-size: 11.5px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.shot .app-status-footer-group {
  display: flex; align-items: center; gap: 14px; min-width: 0;
}
.shot .app-status-footer-group:last-child:not(:first-child) { flex: 0 0 auto; }
.shot .app-status-item {
  display: inline-flex; align-items: center; gap: 5px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shot .app-status-item svg { flex: 0 0 auto; opacity: 0.8; }
.shot .app-status-item > span { overflow: hidden; text-overflow: ellipsis; }

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

.shot .button {
  display: inline-flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 15px;
  border: 1px solid var(--line-field); background: var(--panel);
  border-radius: var(--r-pill);
  font-size: 13.5px; font-weight: 600; line-height: 1;
  cursor: pointer; white-space: nowrap;
  transition: background var(--hover-duration) var(--hover-ease),
    border-color var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .button:not(:disabled):hover { background: var(--muted); }
.shot .button.primary {
  background: var(--text); border-color: var(--text); color: var(--ground);
}
.shot .button.primary:not(:disabled):hover {
  background: var(--text); border-color: var(--text); color: var(--ground);
  filter: brightness(1.25);
}
.shot .button.quiet {
  border-color: transparent; background: transparent; color: var(--text-quiet);
}
.shot .button.quiet:not(:disabled):hover { background: var(--muted); color: var(--text); }
.shot .button.danger { color: var(--danger); }
.shot .button.current { background: var(--text); border-color: var(--text); color: var(--ground); }
.shot .button.current:not(:disabled):hover {
  background: var(--text); border-color: var(--text); color: var(--ground);
}
.shot .button.button-small { height: 24px; padding: 0 10px; font-size: 12px; }
.shot .button:disabled { opacity: 0.45; cursor: default; }


/* ---------------------------------------------------------------- toast */

/* Two kinds of message, two colours. Red is reserved for something that needs
   attention; a confirmation gets the accent colour. Everything else about the
   bar stays the same, so the difference reads as tone rather than as two
   unrelated components. */
/* The lifetime arrives from App as `--notice-life`, because the ring has to
   empty over exactly the time the timer there waits. The value below is only a
   fallback for a bar rendered without it. */
/* **Positioned, so it can be seen over a dialog.** In normal flow the bar is
   painted under `.dialog-overlay` — `position: fixed; z-index: 60` — and every
   message raised while a dialog was open went behind the scrim. That is why the
   wizard drew a panel of its own; with this it does not need one, and the
   application has one place for saying something went wrong instead of two.

   `relative`, not `fixed`: the bar still takes its row under the header and
   still pushes the page down, which is the whole of its layout behaviour. Only
   the painting order changes. */
.shot .notice {
  position: relative; z-index: 70;
  --notice-life: 5s;
  display: flex; justify-content: space-between; gap: 16px; align-items: center;
  padding: 10px 18px; color: #fff; font-weight: 500;
  max-height: 120px; overflow: hidden; background: var(--accent-solid);
  transform-origin: top;
  animation: notice-enter 0.32s ease-out both;
}
.shot .notice.error { background: var(--danger-solid); }
/* The text takes the room the ring does not, so the close button keeps the
   right edge and the ring the left. */
.shot .notice > span { flex: 1 1 auto; }
.shot .notice-countdown { margin-right: -6px; }
.shot .notice .countdown-track { animation: odpocet var(--notice-life) linear forwards; }
/* A bar on its way out has nothing left to count down. */
.shot .notice.leaving .countdown-track { animation: none; }
.shot .notice.leaving {
  pointer-events: none;
  animation: notice-leave 0.28s ease-in-out both;
}
/* The chip darkens its backdrop rather than lightening it. A white veil is the
   obvious way to lift a control off a coloured bar, and it is the one thing
   that cannot be done here: it lightens the very surface the white label has to
   stand on. White on white/0.2 measured 3.48 : 1 over the accent bar and
   4.04 / 4.08 over the red one — all three short of 4.5, and the first short of
   the 4.76 the bare bar already gives, so the chip was the worst place on the
   bar to read. Black/0.18 takes the same three composites to 6.51, 7.04 and
   7.39. The bar's own colour is untouched; only the veil changed sign. */
.shot .notice button {
  background: rgb(0 0 0 / 0.18); border: 0; border-radius: var(--r-pill);
  padding: 5px 13px; cursor: pointer; color: #fff; font-weight: 600; font-size: 13px;
}
/* The way on, when a bar has one. Two chips of the same darkened veil would be
   two ways out; this one is the bar's own colour on white, which is how a
   primary button is drawn everywhere else — filled with the surface it sits
   against reversed. `--accent-solid` and `--danger-solid` are the two bar
   grounds, so the text takes whichever this bar is wearing. */
.shot .notice .notice-action {
  background: #fff; color: var(--accent-solid);
}
.shot .notice.error .notice-action { color: var(--danger-solid); }
/* No ring on a bar that waits, so nothing is reserving space for one: the gap
   is the flex row's own 16 and the text starts at the padding edge. */
@keyframes notice-enter {
  from { max-height: 0; padding-block: 0; opacity: 0; transform: translateY(-2px); }
  to { max-height: 120px; padding-block: 10px; opacity: 1; transform: none; }
}
/* The application's checkbox.
   An 18 px box that fills with the accent and shows a white tick. It was
   `.watch-folder-checkbox`, drawn for the list of files a watched folder
   offers; the clip dialog needs the same control for the same question —
   which of these do you want — so it moved here rather than being drawn a
   second time. The input beside it is hidden rather than styled, because no
   browser lets a native box be drawn to this system.

   5 px radius, not `--r-small`: on an 18 px box 8 would read as a circle, and
   a circle is what this application uses for a radio. And green is not an
   option: the grass circle with a tick means *done* or *installed* wherever it
   appears — a control the reader is choosing with must not borrow it. */
.shot .check-box {
  display: grid; place-items: center; width: 18px; height: 18px; flex: 0 0 auto;
  border: 1px solid var(--line-field); border-radius: 5px;
  background: var(--panel); color: transparent;
  transition: background var(--hover-duration) var(--hover-ease),
    border-color var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .check-box-input {
  position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none;
}
.shot .check-box-input:checked + .check-box {
  border-color: var(--accent); background: var(--accent); color: #fff;
}
.shot .check-box-input:focus-visible + .check-box { box-shadow: var(--ring-field); }
.shot .check-box-input:disabled ~ * { opacity: 0.55; }

@keyframes notice-leave {
  from { max-height: 120px; padding-block: 10px; opacity: 1; transform: none; }
  to { max-height: 0; padding-block: 0; opacity: 0; transform: translateY(-2px); }
}
@media (prefers-reduced-motion: reduce) {
  .shot .notice { animation: none; }
  .shot .notice.leaving { display: none; animation: none; }}
/* ==== src/css/02-library.css ==== */
/* The archive: the hero, the filter row, recording cards and folders.
 *
 * Part 02 of styles.css. The number is the point: these files are
 * one stylesheet cut into pieces, and the order they are imported in is the
 * order the rules were in. Moving a rule between them can change the look
 * without changing a value. */

/* ----------------------------------------------------------------- library */

.shot .library {
  flex: 1; min-height: 0; overflow-y: auto; overflow-anchor: none;
  display: block; padding: 0 20px;
  scrollbar-gutter: stable;
}
/* Centres the page content. **A `.dialog-overlay` must never become one of
   these children** — it is `position: fixed; inset: 0`, and a `max-width` on it
   shrinks the veil to this column and leaves the rest of the window undimmed,
   which is the defect the wizard shipped with; the whole reasoning is on
   `.wizard.wizard > *` in 04-settings.css. The archive's dialogs are rendered
   inside a row or up in `App`, so the rule is left exactly as it is: adding
   `:not(.dialog-overlay)` here would raise it to the specificity of the two
   overrides below and turn their win into a matter of file order. If a dialog
   ever is put at this level, exclude it here and say so there. */
.shot .library.library > * { max-width: 900px; margin-inline: auto; }
/* The hero and the filter row are pinned together, as one block, so the
   toolbar's offset is the wrapper's own edge rather than a copy of the
   compact hero's height that somebody has to keep in step with it.

   Everything the earlier entries say about a sticky header is still true —
   it slices whatever passes under it. What changed is the answer: the two
   layers below the block blur the passing content into it, so the boundary
   reads as an edge the list goes under rather than as a card cut in half.
   The block is opaque so nothing shows through it; only the strip below is
   translucent. */
.shot .library.library > .archive-sticky {
  position: sticky; top: 0; z-index: 20;
  width: 100%; max-width: none; margin-inline: 0;
  background: var(--ground);
}
.shot .archive-sticky > * { width: 100%; max-width: 900px; margin-inline: auto; }
/* Two layers, not one: the blur is stronger and shorter next to the block and
   weaker over a longer run below it, which is what makes it read as a
   gradient rather than as a frosted band with an edge of its own. Both are
   masked to nothing at their far end, so neither has a visible boundary.
   `pointer-events: none` — this is glass, not a surface to click. */
.shot .archive-sticky::before,
.shot .archive-sticky::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%;
  pointer-events: none;
  /* `backdrop-filter` inside a `position: sticky` block leaves the last frame
     it sampled behind when the block moves back up the scroller: rolling down
     re-samples continuously and looks right, rolling back up leaves a strip of
     a row that is no longer there. It is a compositing fault, not a layout one
     — the ghost sits exactly at `top: 100%`, which is where these two are.

     `will-change: transform` asks for a layer of their own, which is what makes
     the sample follow the scroll rather than stick to it. It is a hint and not
     a guarantee: if the ghost survives, the honest answer is to drop the blur
     and keep the colour fade below, which the comment on `::before` already
     says carries the effect on its own. */
  will-change: transform;
  /* Only while something is actually passing underneath. At the top of the
     list there is nothing to dissolve, and a strip that is always there sits
     over the first row — a folder's own name, blurred, in a folder with
     nothing in it. */
  opacity: 0; transition: opacity 0.16s ease;
}
.shot .archive-sticky.collapsed::before,
.shot .archive-sticky.collapsed::after { opacity: 1; }
.shot .archive-sticky::before {
  height: 42px;
  backdrop-filter: blur(3px);
  /* The colour fade is what carries the effect where `backdrop-filter` is not
     available; on its own it already reads as the list dissolving upward. */
  background: linear-gradient(to bottom, var(--ground) 0%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 15%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 15%, transparent 100%);
}
.shot .archive-sticky::after {
  height: 18px;
  backdrop-filter: blur(9px);
  mask-image: linear-gradient(to bottom, #000, transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent);
}
.shot .library.library > .archive-scroll-content {
  width: 100%; max-width: none; margin-inline: 0;
  padding: 4px 0 60px;
}
/* More specific than component rules such as `.list { margin: 0 }` below.
   Every archive block must share the drop zone's centred 900 px column. */
.shot .library .archive-scroll-content > * {
  width: 100%; max-width: 900px; margin-inline: auto;
}

/* Two banners, one frame, two meanings — the geometry is shared and only the
   colour differs.

   `.setup-notice` is setup that has not finished, where nothing is wrong at
   all: it takes the accent and points somewhere rather than sounding an alarm.
   `.warning` keeps the destructive palette and **nothing draws it at the
   moment**; it is left for whatever genuinely earns red.

   Both were one rule until 2026-08-16, when the archive's banner was found
   wearing the red one while the application had simply not downloaded its
   models yet. A third variant lived here for an hour on 17 August — the
   wizard's failed download, in neutral rather than red — and went with the
   panel itself when that message moved to the notice bar. */
.shot .warning,
.shot .setup-notice {
  display: flex; gap: 16px; align-items: flex-start; justify-content: space-between;
  border-radius: var(--r-large); padding: 15px 18px; margin-bottom: 20px;
}
/* The button sits on the middle of the text beside it, not at the top of it.
   `flex-start` is right for `.warning`, whose text is a heading over a list that
   can run to several lines — a button level with the last of them would float.
   This notice is two lines that never grow, so a button pinned to the first one
   reads as hanging off the corner. */
.shot .setup-notice { align-items: center; }
.shot .warning {
  border: 1px solid hsl(var(--ds-destructive) / 0.25);
  background: var(--danger-light);
}
.shot .setup-notice {
  border: 1px solid var(--accent-line);
  background: var(--accent-light);
}
.shot .setup-notice strong { font-weight: 700; }
/* The sentence sits under the heading the way it does in a dialog: 4 px, so the
   two read as one statement rather than as a heading with a paragraph. */
.shot .setup-notice p { margin: 4px 0 0; color: var(--text-quiet); line-height: 1.5; }
/* Standing above the hero rather than inside the list, the notice is the first
   thing under the application bar and needs the hero's own inset — 40 px. It
   keeps its 20 px below, which is what separates it from the hero's 40. */
.shot .library > .warning,
.shot .library > .setup-notice { margin-top: 40px; }
.shot .warning strong { font-weight: 700; }
.shot .warning ul { margin: 6px 0 0; padding-left: 18px; color: var(--text-quiet); }

.shot .watch-folder-notice {
  padding: 18px; margin-bottom: 18px;
  border: 1px solid var(--line); border-radius: var(--r-large);
  background: var(--panel); box-shadow: var(--shadow);
}
.shot .watch-folder-header {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px;
  align-items: center;
}
.shot .watch-folder-icon {
  display: grid; place-items: center; width: 34px; height: 34px;
  border-radius: 50%; background: var(--accent-light); color: var(--accent);
}
.shot .watch-folder-copy { min-width: 0; }
.shot .watch-folder-copy > strong { display: block; font-size: 15px; line-height: 1.35; }
.shot .watch-folder-copy > p { margin: 3px 0 0; color: var(--text-quiet); font-size: 13.5px; }
.shot .watch-folder-file-panel {
  margin-top: 16px; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--ground);
}
.shot .watch-folder-file-header {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 38px; padding: 0 12px;
  border-bottom: 1px solid var(--line); background: var(--subtle);
}
.shot .watch-folder-file-header > strong {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--text-quiet);
}
.shot .watch-folder-select-all {
  padding: 4px 0; border: 0; background: transparent; color: var(--accent);
  cursor: pointer; font-size: 12.5px; font-weight: 650;
}
.shot .watch-folder-select-all:hover { text-decoration: underline; text-underline-offset: 3px; }
.shot .watch-folder-select-all:disabled { opacity: 0.45; cursor: default; text-decoration: none; }
.shot .watch-folder-files {
  max-height: 188px; overflow-y: auto;
  list-style: none; margin: 0; padding: 0;
}
.shot .watch-folder-files li + li { border-top: 1px solid var(--line); }
/* The row is the label plus its dismiss button; the label takes what is left
   after the button, so a long file name ellipsizes rather than pushing it out. */
/* The surface belongs to the row, not to the label inside it — otherwise the
   strip behind the dismiss button is a different colour and the separator
   stops short of it. */
.shot .watch-folder-files li { display: flex; align-items: center; background: var(--panel); }
.shot .watch-folder-files li > .watch-folder-file { flex: 1 1 auto; min-width: 0; }
/* Visible from the start. Revealing it on hover would have kept the list
   calmer, but a control nobody can see is a control nobody uses — and this one
   is the answer to "this file, no". Quiet colour instead of hiding: it is
   present, it just does not lead. */
.shot .watch-folder-dismiss {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 28px; height: 28px; margin-right: 8px; padding: 0;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text-quiet); cursor: pointer;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .watch-folder-dismiss:hover { background: var(--danger-light); color: var(--danger); }
.shot .watch-folder-dismiss:disabled { cursor: default; }
.shot .watch-folder-file {
  position: relative; display: flex; align-items: center; gap: 10px;
  min-height: 42px; padding: 7px 12px; cursor: pointer;
  background: none;
}
.shot .watch-folder-files li {
  transition: background var(--hover-duration) var(--hover-ease);
}
.shot .watch-folder-files li:hover { background: var(--muted); }
.shot .watch-folder-file input:disabled ~ * { opacity: 0.55; }
.shot .watch-folder-file-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13.5px; font-weight: 600;
}
/* The note is about the list, not part of it. It had no gap at all: the rule
   below said 12 px, but `.settings-info-note.compact { margin: 0 }` is written
   later at the same specificity and won, so the note sat straight on the
   panel's border. Naming `.compact` here takes the class back. */
.shot .watch-folder-notice > .settings-info-note.compact {
  margin-top: 20px; font-size: 12.5px;
}
.shot .watch-folder-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line);
}
.shot .watch-folder-selection { color: var(--text-quiet); font-size: 12.5px; }
.shot .watch-folder-actions { display: flex; gap: 8px; }
/* Below the window's own 1000 px minimum (tauri.conf.json), so this cannot
   fire today. Kept as a net, like the 900 px rule for the header pills, in
   case the minimum ever drops or the interface is opened in a browser.
   650 px: the notice's footer would stack. */
@media (max-width: 650px) {
  .shot .watch-folder-footer { align-items: flex-start; flex-direction: column; }
  .shot .watch-folder-actions { align-self: stretch; justify-content: flex-end; }}

  .shot .library-bar {
    display: grid;
    grid-template-columns: minmax(220px, 360px) 138px 160px minmax(64px, 1fr);
  position: relative; z-index: 10;
  align-items: center; justify-content: start; gap: 12px; margin-bottom: 0; padding: 8px 0 18px;
  background: var(--ground);
}
.shot .library-bar .search { min-width: 0; margin-bottom: 0; }
.shot .archive-filter-select { position: relative; min-width: 0; }
.shot .archive-filter-select .select-trigger {
  height: 40px; padding-left: 38px; padding-right: 13px;
  background: var(--panel); font-size: 13.5px;
}
.shot .archive-filter-icon {
  position: absolute; left: 13px; top: 50%; z-index: 2;
  display: grid; place-items: center; transform: translateY(-50%);
  color: var(--text-quiet); pointer-events: none;
}
.shot .archive-filter-select .select-menu { min-width: 100%; }
/* 40 px is not a second size for this control: 32px buttons + 2×3 px padding
   + 2×1 px border is exactly the height of .archive-filter-select beside it in
   the toolbar. The same pill inside a dialog stays 36 px. */
.shot .archive-view-toggle {
  height: 40px; padding: 3px;
  border: 1px solid var(--line); background: var(--muted);
  justify-self: end;
}
.shot .archive-view-toggle button { width: 32px; height: 32px; min-height: 32px; padding: 0; }
.shot .archive-view-toggle button svg { flex: 0 0 auto; }
.shot .archive-calendar-popover {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 50;
  width: 292px; padding: 14px;
  border: 1px solid var(--line); border-radius: var(--r-large);
  background: var(--panel); box-shadow: var(--shadow-high);
  animation: calendar-open 0.12s ease-out;
}
@keyframes calendar-open {
  from { opacity: 0; transform: translateY(-3px); }
  to { opacity: 1; transform: none; }
}
.shot .archive-calendar-header {
  display: grid; grid-template-columns: 32px minmax(0, 1fr) 32px;
  align-items: center; gap: 8px; margin-bottom: 10px;
}
.shot .archive-calendar-header strong {
  overflow: hidden; text-align: center; text-overflow: ellipsis; white-space: nowrap;
  font-size: 14px; font-weight: 680;
}
.shot .archive-calendar-nav {
  display: grid; place-items: center; width: 32px; height: 32px; padding: 0;
  border: 1px solid transparent; border-radius: 50%;
  background: transparent; color: var(--text-quiet); cursor: pointer;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .archive-calendar-nav:hover { background: var(--muted); color: var(--text); }
.shot .archive-calendar-weekdays,
.shot .archive-calendar-days {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
}
.shot .archive-calendar-weekdays { margin-bottom: 5px; }
.shot .archive-calendar-weekdays span {
  display: grid; place-items: center; height: 24px;
  color: var(--text-quiet); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.shot .archive-calendar-day,
.shot .archive-calendar-empty { width: 34px; height: 34px; }
.shot .archive-calendar-day {
  position: relative; display: grid; place-items: center; padding: 0;
  border: 1px solid transparent; border-radius: 50%;
  background: transparent; color: var(--text); cursor: pointer;
  font-size: 13px; font-variant-numeric: tabular-nums;
  transition: background var(--hover-duration) var(--hover-ease),
    border-color var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .archive-calendar-day:hover { background: var(--muted); }
.shot .archive-calendar-day.today:not(.selected) {
  border-color: var(--accent-line); color: var(--accent); font-weight: 700;
}
.shot .archive-calendar-day.selected,
.shot .archive-calendar-day.selected:hover {
  border-color: var(--accent); background: var(--accent); color: #fff;
  font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
  .shot .archive-calendar-popover { animation: none; }}
.shot .archive-filter-empty {
  margin: 22px auto 0; color: var(--text-quiet); text-align: center;
}
/* Below the window's own 1000 px minimum (tauri.conf.json), so this cannot
   fire today. Kept as a net, like the 900 px rule for the header pills, in
   case the minimum ever drops or the interface is opened in a browser.
   760 px: search takes its own row above the two filters. */
@media (max-width: 760px) {
    .shot .library-bar {
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  }
  .shot .library-bar .search { grid-column: 1 / -1; }}

/* Toggle per the design system: 36 x 20 track, 16 knob.
   Same dimensions as h-5 w-9 in their notation. */
.shot .switch {
  display: flex; align-items: center; gap: 9px;
  cursor: pointer; flex: 0 0 auto; user-select: none;
  /* the hidden checkbox anchors here, not to some arbitrary ancestor */
  position: relative;
}
.shot .switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.shot .switch-track {
  position: relative; width: 36px; height: 20px; flex: 0 0 auto;
  background: var(--line-field); border-radius: var(--r-pill);
  transition: background 0.18s ease;
}
.shot .switch-track::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  /* Black rather than --shadow: this is a white knob lifted off its own track,
     the one shadow that must not follow the palette. 25 % at a 3 px blur. */
  background: #fff; box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
  transition: transform 0.18s ease;
}
.shot .switch input:checked + .switch-track { background: var(--accent); }
.shot .switch input:checked + .switch-track::after { transform: translateX(16px); }
.shot .switch input:focus-visible + .switch-track { box-shadow: var(--ring); }
.shot .switch-label { font-size: 13px; font-weight: 600; color: var(--text-quiet); white-space: nowrap; }
.shot .switch input:checked ~ .switch-label { color: var(--text); }

/* stands in for the player while transcription runs */

.shot .search { margin-bottom: 18px; }
.shot .search input {
  width: 100%; height: 40px;
  border: 1px solid var(--line-field); background: var(--panel);
  border-radius: var(--r-pill); padding: 0 18px; outline: none;
  font-size: 14px;
}
.shot .search input:focus { border-color: var(--accent); box-shadow: var(--ring-field); }

/* 12 px between cards, not 8: at 85 px tall with a border of their own they
   need the room to read as separate blocks rather than as one ruled table.
   The compact list keeps a tighter 8 px — density is what it is for. */
.shot .list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.shot .list.compact { gap: 8px; }
.shot .list.compact .row { padding: 8px 12px 8px 8px; border-radius: var(--r-card); }
.shot .list.compact .row-main { align-items: center; gap: 10px; }
/* 6 px against the roomy tile's 7: the same proportion on a 38 x 38 px mark.
   Both are commented where `.recording-calendar` is defined. */
.shot .list.compact .recording-calendar {
  width: 38px; border-radius: 6px;
}
.shot .list.compact .recording-calendar-month { padding: 2px 0; font-size: 7.5px; }
.shot .list.compact .recording-calendar-day { margin-top: 2px; font-size: 16px; }
.shot .list.compact .recording-calendar-year { display: none; }
.shot .list.compact .row-text {
  flex: 1 1 auto; flex-direction: row; align-items: center; gap: 14px;
}
.shot .list.compact .row-title { flex: 1 1 210px; }
.shot .list.compact .recording-metadata {
  flex: 0 1 auto; flex-wrap: nowrap; margin-top: 0; gap: 4px 10px;
  font-size: 11.5px;
}
/* A failure explains itself in a whole sentence, which is right in the roomy
   list and does not fit on one compact line — it wrapped and pushed the row
   taller than every other one. Here it is cut where it runs out of room; the
   sentence is whole in the tooltip and whole in the accessible name. */
.shot .list.compact .recording-metadata { min-width: 0; }
/* The short facts hold their size; the sentence is the one that gives way, so
   the squeeze lands entirely on it instead of clipping a duration. */
.shot .list.compact .recording-metadata-item { flex: 0 0 auto; }
.shot .list.compact .recording-metadata-item.error { flex: 0 1 auto; min-width: 0; }
.shot .list.compact .recording-metadata-item.error > span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Below the window's own 1000 px minimum (tauri.conf.json), so this cannot
   fire today. Kept as a net, like the 900 px rule for the header pills, in
   case the minimum ever drops or the interface is opened in a browser.
   720 px: the compact row's metadata drops below its title. */
@media (max-width: 720px) {
  .shot .list.compact .row-text { flex-direction: column; align-items: flex-start; gap: 1px; }
  .shot .list.compact .recording-metadata { flex-wrap: wrap; }}
/* interpolate-size allows animating a content-derived height */
@supports (interpolate-size: allow-keywords) {
  .shot { interpolate-size: allow-keywords; }
  .shot .row {
    transition: border-color var(--hover-duration) var(--hover-ease),
      background var(--hover-duration) var(--hover-ease),
      box-shadow var(--hover-duration) var(--hover-ease),
      height 0.16s ease-out;
  }}

.shot .row {
  border: 1px solid var(--line); background: var(--panel);
  border-radius: var(--r-large); padding: 14px 16px;
  display: grid; grid-template-columns: 1fr auto; gap: 8px;
  /* The rename input is taller than the title. Without a transition the row
     would jolt on every switch. */
  transition: border-color var(--hover-duration) var(--hover-ease),
    background var(--hover-duration) var(--hover-ease),
    box-shadow var(--hover-duration) var(--hover-ease);
}
.shot .row-text { transition: none; }
.shot .row:hover,
.shot .row:focus-within { border-color: var(--line-field); box-shadow: var(--shadow); }
.shot .row.running { border-color: var(--accent-line); background: var(--accent-light); }

/* The calendar occupies the card's left edge; the status dot now belongs to
   the title itself, where it also appears on transcript Detail. */
.shot .row-main {
  display: flex; align-items: flex-start; gap: 12px;
  background: none; border: 0; text-align: left; cursor: pointer;
  padding: 0; min-width: 0;
}
.shot .list:not(.compact) .row-main { align-items: center; }
.shot .row-main:disabled { cursor: default; opacity: 0.75; }

/* The offset puts the dot on the optical centre of the first title line. */
.shot .status-mark {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  background: var(--text-quiet); margin-top: 7px;
}
.shot .status-mark.done { background: var(--success); }
.shot .status-mark.transcribing { background: var(--accent); box-shadow: 0 0 0 4px hsl(var(--ds-petrol) / 0.2); animation: puls 1.6s ease-in-out infinite; }
.shot .status-mark.failed { background: var(--danger); }
.shot .status-mark.new { background: transparent; border: 1.5px solid var(--line-field); }
@keyframes puls { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }

/* The height of whatever stands at a row's left edge — the calendar on a
   transcript, the drawer on a folder. It is what makes a row 85 px, so the
   two kinds of block are the same height by construction rather than by two
   numbers that happen to agree today. */
.shot .list { --row-thumb: 55px; }
.shot .list.compact { --row-thumb: 38px; }
.shot .recording-calendar {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; align-items: center;
  /* 7 px, and 6 on the compact tile above: the radius is proportional to a
     48 x 55 px tile, the same reasoning as the archive calendar popover. */
  width: 48px; height: var(--row-thumb); flex: 0 0 auto;
  border: 1px solid var(--line-field); border-radius: 7px;
  background: var(--panel); color: var(--text);
  box-shadow: var(--shadow);
  transition: box-shadow var(--hover-duration) var(--hover-ease);
}
.shot .recording-calendar-month {
  width: 100%; padding: 4px 0 3px;
  background: var(--muted); color: var(--text-quiet); text-align: center;
  font-size: 9px; font-weight: 700; line-height: 1;
  letter-spacing: 0.08em;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .row:hover .recording-calendar-month,
.shot .row:focus-within .recording-calendar-month { background: var(--accent); color: #fff; }
.shot .row:hover .recording-calendar,
.shot .row:focus-within .recording-calendar { box-shadow: none; }
.shot .recording-calendar-day {
  margin-top: 3px; font-size: 20px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.shot .recording-calendar-year {
  margin-top: 2px; color: var(--text-quiet);
  font-size: 8.5px; font-weight: 650; line-height: 1;
  font-variant-numeric: tabular-nums;
}

.shot .row-text { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.shot .row-title {
  display: flex; align-items: baseline; gap: 7px;
  font-weight: 600; font-size: 14.5px; min-width: 0;
}
.shot .list:not(.compact) .row-title { position: relative; top: -1px; font-size: 16px; }
.shot .row-status-icon {
  display: grid; place-items: center; width: 14px; height: 14px; flex: 0 0 auto;
  align-self: center;
}
.shot .row-status-icon .status-mark { margin-top: 0; }
.shot .row-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shot .recording-metadata {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 5px 13px; margin-top: 3px;
  color: var(--text-quiet); font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.shot .list:not(.compact) .recording-metadata { margin-top: 5px; }
.shot .recording-metadata-item {
  display: inline-flex; align-items: center; gap: 5px;
  min-width: 0; line-height: 1.25; font-weight: 500;
}
.shot .recording-metadata-item > svg { flex: 0 0 auto; opacity: 0.72; }
.shot .recording-metadata-item.error { color: var(--danger); }
/* A language heard but not written in. Amber, not red: nothing is broken —
   there is simply more of the recording to be had. The icon keeps its full
   strength here, where every other one is dimmed, because the mark is the
   point of the item. */
.shot .recording-metadata-item.missing { color: hsl(var(--ds-amber)); }
.shot .recording-metadata-item.missing > svg { opacity: 1; }
/* Buttons centred in the block, not pinned to the top. */
.shot .row-actions { display: flex; gap: 4px; align-items: center; }

.shot .action-menu { position: relative; }
.shot .action-menu-list {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  min-width: 190px; padding: 6px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-large); box-shadow: var(--shadow-high);
  display: flex; flex-direction: column;
}
.shot .action-menu-list button {
  display: flex; align-items: center; gap: 10px;
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: 8px 12px; border-radius: var(--r-card);
  font-size: 13.5px; font-weight: 500; white-space: nowrap;
}
.shot .menu-icon { color: var(--text-quiet); flex: 0 0 auto; }
.shot .action-menu-list button:hover .menu-icon { color: var(--text); }
.shot .action-menu-list button.destructive-item .menu-icon { color: inherit; }
/* the second level has no icons; indentation aligns its text with the level above */
.shot .action-menu-list button:not(:has(.menu-icon)):not(.menu-back) {
  padding-left: 42px;
}
.shot .action-menu-list button:hover { background: var(--muted); }
/* The label takes the remaining space so the arrow always meets the right edge
   and it is obvious at a glance that the item leads somewhere. */
.shot .menu-label { flex: 1 1 auto; display: flex; flex-direction: column; gap: 1px; }
/* The second line under a menu entry: what choosing it leads to. Quiet and one
   size down, so the entry still reads as one line with a note under it rather
   than as two entries. */
.shot .menu-hint { font-size: 11.5px; color: var(--text-quiet); font-weight: 500; }
.shot .menu-arrow { color: var(--text-quiet); flex: 0 0 auto; margin-left: 18px; }
.shot .action-menu-list button:hover .menu-arrow { color: var(--text); }
.shot .menu-back {
  display: flex; align-items: center; gap: 6px;
  color: var(--text-quiet);
  /* The divider is its own line under the button, not the button's bottom border —
     that would force square corners and the hover highlight would be boxy. */
  position: relative; margin-bottom: 7px;
}
.shot .menu-back::after {
  content: ""; position: absolute; left: 6px; right: 6px; bottom: -4px;
  height: 1px; background: var(--line);
}
.shot .action-menu-list button.destructive-item { color: var(--danger); }
.shot .action-menu-list button.destructive-item:hover { background: var(--danger-light); }

.shot .progress { grid-column: 1 / -1; margin-top: 10px; }
.shot .progress-bar {
  height: 5px; border-radius: var(--r-pill); overflow: hidden;
  /* derived from the text colour, not a fixed grey, or it vanishes on a tinted ground */
  background: hsl(var(--ds-foreground) / 0.12);
}
.shot .progress-fill { height: 100%; background: var(--accent); border-radius: var(--r-pill); transition: width 0.4s ease; }
.shot .progress-label {
  display: flex; justify-content: space-between; margin-top: 5px;
  font-size: 12px; font-weight: 600; color: var(--text-quiet);
  font-variant-numeric: tabular-nums;
}
.shot .progress-ai { margin-top: 8px; }
.shot .progress-ai .progress-fill {
  background: linear-gradient(90deg, var(--accent), hsl(var(--ds-petrol) / 0.65));
}

.shot .live-text {
  margin-top: 10px; padding: 10px 14px;
  background: var(--panel); border-radius: var(--r-card);
  max-height: 110px; overflow: hidden;
  font-family: var(--font-body); color: var(--text-quiet); font-size: 14px;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 40%);
  mask-image: linear-gradient(to bottom, transparent, #000 40%);
}
.shot .live-text p { margin: 0 0 4px; }

/* Not sticky, and that is the decision rather than an omission. A pinned
   header means everything else passes underneath it and gets cut through the
   middle. It is `.archive-sticky` above that is pinned now, with the hero and
   the filter row inside it as one block; this element only collapses. Do not
   make it sticky again — two pinned layers in one scroller is what used to
   cover the first card's top edge. */
.shot .archive-drop-zone {
  position: relative; z-index: 1;
  width: 100%;
  /* 40 px above the hero and 28 below it. The number to look at is not this
     one alone but this one against the block it introduces: the hero is a
     dashed panel over 460 px tall, so 24 px above it read as a seam rather
     than as a margin. The compact state keeps its own 20/16. */
  padding: 40px 0 28px; background: var(--ground);
  transition: padding 0.18s ease;
}
  .shot .archive-drop-zone-surface {
    position: relative; display: grid; grid-template-columns: 1fr;
    grid-template-areas: "mark" "copy" "actions";
  justify-items: center; align-items: center; gap: 14px;
  text-align: center;
  /* 22 px, not --r-large: a 320 px dashed panel, the largest surface in the
     window. The drag overlay repeats the amount so the two read as one. */
  border: 1.5px dashed var(--line-field); border-radius: 22px;
  min-height: 320px; padding: 56px 32px 52px; background: var(--subtle);
  transition: min-height 0.18s ease, padding 0.18s ease, border-radius 0.18s ease,
    background 0.18s ease, box-shadow 0.18s ease;
}
/* 63 px, not the cube's 56, and no `opacity: 0.9` with it. The height is the
   2.4× the owner settled on at the drawing's own 26.2 unit box; the cube was
   dimmed because a three-colour solid at full strength shouted over the sentence
   under it, and a black outline with nothing in it has no such problem.

   The face is written rather than placed, so its two layers and the pen between
   them live here. The outline is the same line colour as the dashed panel around
   it — it belongs to the empty state and not to the mark yet. */
.shot .archive-drop-zone-mark { grid-area: mark; margin-bottom: 4px; }
.shot .archive-drop-zone-mark svg {
  height: 63px; width: auto; display: block;
  transition: height 0.18s ease;
}
.shot .olo-face { display: block; }
.shot .olo-face-guide path { fill: var(--line-field); }
.shot .olo-face-ink path { fill: var(--text); }
/* A flat front, not a round one. A round cap is a disc of half the stroke's
   width, and on a ring whose centreline is 4.35 across that disc reaches parts
   of the letter the pen has not got to yet — black appears where the pen is not.
   A flat front advances as a clean radial cut and may be wider than the shape,
   because the letterform clips whatever spills.

   `100 200` and a resting offset of 101, not `100` and `100`: the browser
   rescales the real curve onto the hundred units `pathLength` declares, and at
   exactly 100 the resting state sits on the boundary between the drawn and
   undrawn halves of the pattern. Rounding then leaves a sliver at the start of
   each stroke — on a six-unit-wide pen, a visible tick on the outline before
   anything has begun. One unit of slack puts the rest inside the undrawn part.
   It costs 1 % of a stroke's time drawing nothing. */
.shot .olo-pen {
  fill: none; stroke: #fff;
  stroke-linecap: butt; stroke-linejoin: round;
  stroke-dasharray: 100 200; stroke-dashoffset: 101;
  transition-property: stroke-dashoffset;
  transition-timing-function: var(--olo-ease);
  transition-duration: calc(var(--olo-writing) * var(--pen-duration));
  transition-delay: calc(var(--olo-writing) * var(--pen-start));
}
.shot .olo-pen-1,
.shot .olo-pen-3 { stroke-width: 6; }
.shot .olo-pen-2,
.shot .olo-pen-4 { stroke-width: 5; }
.shot .olo-face.drawn .olo-pen { stroke-dashoffset: 0; }
.shot .olo-face.still .olo-pen { transition: none; }

/* The face blinks: the whole `o` squashed to a slit, not just the counter
   inside it. The quieter version was built — outline standing, only the
   counters closing, done with a mask so the holes stayed transparent — and
   rejected at the screen in favour of this one.

   `transform-box: fill-box` is what makes it work at all. Without it
   `transform-origin: center` is measured from the viewBox, so both eyes squash
   toward a single point between them instead of each closing where it stands.

   `.08` rather than 0: a closed eye leaves a slit. Zero turns the `o` into a
   solid dot, which reads as a full stop rather than a blink. */
.shot .olo-eye {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform var(--olo-blink) cubic-bezier(.4, 0, .6, 1);
}
.shot .olo-face.blinking .olo-eye { transform: scaleY(.08); }
.shot .olo-face.still .olo-eye { transition: none; transform: none; }

/* While a transcript runs the mark turns on its side and stops being a face:
   the two `o` are rollers and the stem is sheet metal passing between them.
   Nothing is drawn for it — the same four shapes from another side.

   `scale(.84)` is what keeps the rollers inside the frame, and the first attempt
   at it was wrong. 26.2 / 30.41 — the frame's height over the drawing's length —
   assumes the drawing is centred in the frame, and it is not: the turn is about
   (27.655, 12.13) while the frame runs −1 to 25.2, leaving 13.13 above and
   **13.07 below**. Against a reach of 15.206 that allows 0.8595, and .861 ran
   past it by 0.022 of a unit.

   Twenty-two thousandths, and it showed. At the extreme of a circle the outline
   is tangent to the frame, so a sliver of overrun takes a wide flat bite out of
   the curve — measured at 0.05 px and reported as one or two. .84 leaves 0.3 of
   a unit, which is 0.7 px at the size the drop zone draws it. */
.shot .olo-rot {
  transform-origin: 27.655px 12.13px;
  transition: transform var(--olo-turn) cubic-bezier(.65, 0, .35, 1);
}
.shot .olo-face.working .olo-rot { transform: rotate(90deg) scale(.84); }

/* The sheet travels along y in unturned coordinates, and it is the parent's
   rotation that makes the journey horizontal. Along x it would run lengthwise
   through the rollers rather than between them. */
@keyframes olo-pass {
  from { transform: translateY(26px); }
  to { transform: translateY(-26px); }
}
.shot .olo-face.working .olo-sheet {
  animation: olo-pass var(--olo-pass) linear infinite;
  animation-delay: calc(var(--olo-turn) * var(--olo-lead));
}

/* The smile turns about the centre of its own arc — (27.66, 13.81), radius
   8.81 — so it slides along the curve it is itself made of rather than moving
   in a straight line. It cannot leave by turning alone, being always the same
   distance from that centre, so the turn carries the movement and the fade
   carries the departure.

   The delay sits on the resting rule rather than on `.working`, so it applies
   only to the return: the smile leaves at once and comes back after the mark
   has turned upright. The machine becomes a face again, and the smile arrives
   last. */
.shot .olo-smile {
  transform-origin: 27.66px 13.81px;
  transition: opacity calc(var(--olo-turn) * .7) ease,
              transform calc(var(--olo-turn) * .9) cubic-bezier(.34, 1.25, .64, 1);
  transition-delay: var(--olo-turn);
}
.shot .olo-face.working .olo-smile {
  transition-delay: 0ms;
  opacity: 0;
  transform: rotate(var(--olo-smile-turn));
}
/* Reduced motion keeps the face a face. Not a slower mill: none. */
.shot .olo-face.still .olo-rot,
.shot .olo-face.still .olo-smile { transition: none; transform: none; }
.shot .olo-face.still .olo-sheet { animation: none; }
.shot .archive-drop-zone-copy { grid-area: copy; min-width: 0; }
.shot .archive-drop-zone-copy h1,
.shot .archive-drop-zone-copy [data-h="1"] {
  font-size: 26px; font-weight: 700; margin: 0;
  letter-spacing: -0.02em; transition: font-size 0.18s ease;
}
.shot .archive-drop-zone-copy p {
  color: var(--text-quiet); margin: 8px 0 0; max-width: 44ch;
  max-height: 44px; overflow: hidden; opacity: 1;
  transition: max-height 0.16s ease, opacity 0.12s ease, margin 0.16s ease;
}
  .shot .archive-drop-zone-actions {
    grid-area: actions; display: flex; flex-direction: column;
    align-items: center; gap: 10px;
  }
  .shot .archive-drop-zone-actions .archive-automatic-toggle {
    position: absolute; right: 24px; bottom: 20px;
  }
  .shot .archive-automatic-toggle .switch-label {
    order: 1; color: var(--text-quiet); font-size: 12.5px; font-weight: 600;
  }
  .shot .archive-automatic-toggle .switch-track { order: 2; }
  .shot .archive-drop-zone.compact { padding: 20px 0 16px; }
  .shot .archive-drop-zone.compact .archive-drop-zone-surface {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "mark copy actions";
  justify-items: stretch; gap: 12px; min-height: 85px; padding: 14px 16px;
}
.shot .archive-drop-zone.compact .archive-drop-zone-mark { margin: 0; align-self: center; }
.shot .archive-drop-zone.compact .archive-drop-zone-mark svg { height: 38px; }
.shot .archive-drop-zone.compact .archive-drop-zone-copy {
  align-self: center; text-align: left;
}
.shot .archive-drop-zone.compact .archive-drop-zone-copy h1,
.shot .archive-drop-zone.compact .archive-drop-zone-copy [data-h="1"] { font-size: 15px; }
  .shot .archive-drop-zone.compact .archive-drop-zone-copy p {
    max-height: 44px; margin: 2px 0 0; opacity: 1;
  }
  .shot .archive-drop-zone.compact .archive-drop-zone-actions {
    flex-direction: row-reverse; justify-self: end; gap: 14px;
  }
  .shot .archive-drop-zone.compact .archive-drop-zone-actions .archive-automatic-toggle {
    position: static;
  }
@media (prefers-reduced-motion: reduce) {
  .shot .archive-drop-zone,
.shot .archive-drop-zone-surface,
.shot .archive-drop-zone-mark svg,
.shot .archive-drop-zone-copy h1,
.shot .archive-drop-zone-copy [data-h="1"],
.shot .archive-drop-zone-copy p { transition: none; }}
.shot .small-text { font-size: 13px; color: var(--text-quiet); }
.shot .note { font-size: 13px; color: var(--text-quiet); margin-top: 20px; }

.shot .results { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.shot .results button {
  width: 100%; text-align: left; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 11px 14px; cursor: pointer; display: flex; flex-direction: column; gap: 2px;
}
.shot .results button:hover { border-color: var(--accent-line); background: var(--accent-light); }
.shot .result-where { font-size: 12px; font-weight: 600; color: var(--text-quiet); font-variant-numeric: tabular-nums; }
.shot .result-text { font-family: var(--font-body); font-size: 15px; }
/* 4 px, not --r-small: an inline highlight is about 20 px tall, and 8 would
   round it into a pill. Same amount as `.word`, which is the same idea. */
.shot mark { background: var(--highlight); color: inherit; padding: 0 2px; border-radius: 4px; }
.shot .empty-result { color: var(--text-quiet); }
/* ==== src/css/03-detail.css ==== */
/* The transcript screen: the reader, the sidebar and searching inside it.
 *
 * Part 03 of styles.css. The number is the point: these files are
 * one stylesheet cut into pieces, and the order they are imported in is the
 * order the rules were in. Moving a rule between them can change the look
 * without changing a value. */

/* ---------------------------------------------------------------- detail */

.shot .detail { flex: 1; display: flex; flex-direction: column; min-height: 0; }

.shot .detail-header {
  display: flex; align-items: center; gap: 10px;
  height: 57px; padding: 0 18px; flex: 0 0 auto;
  border-bottom: 1px solid var(--line); background: var(--panel);
}
.shot .detail-header h1,
.shot .detail-header [data-h="1"] {
  font-size: 15px; font-weight: 700; margin: 0; min-width: 0;
}
.shot .detail-header-left {
  display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0;
}
.shot .detail-mark { flex: 0 0 auto; margin-right: 1px; }
.shot .detail-back-button { flex: 0 0 auto; }
.shot .detail-title { display: flex; align-items: center; gap: 7px; flex: 0 1 auto; }
.shot .detail-status { align-self: center; margin-top: 0; }
.shot .detail-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shot .detail-title-menu { flex: 0 0 auto; }
.shot .detail-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.shot .icon-button.header-icon-button {
  width: 34px; height: 34px;
}
.shot .ai-edit-button.ready { color: var(--accent); border-color: var(--accent-line); }
.shot .ai-edit-button svg { flex: 0 0 auto; }

.shot .detail-progress-bubble {
  position: fixed; right: 22px;
  bottom: calc(var(--app-footer-height) + 22px); z-index: 50;
  display: flex; align-items: center; gap: 12px;
  width: min(340px, calc(100vw - 44px)); padding: 12px 12px 12px 14px;
  border: 1px solid var(--accent-line); border-radius: var(--r-large);
  background: linear-gradient(var(--accent-light), var(--accent-light)), var(--panel);
  box-shadow: var(--shadow-high); color: var(--text);
  animation: detail-progress-appears 0.16s ease-out;
}
.shot .detail-progress-icon {
  display: grid; place-items: center; flex: 0 0 auto;
  color: var(--accent); font-size: 18px; line-height: 1;
}
.shot .detail-progress-icon svg { display: block; }
.shot .detail-progress-body { min-width: 0; flex: 1 1 auto; }
.shot .detail-progress-row {
  display: flex; justify-content: space-between; gap: 12px;
  margin-bottom: 5px; font-size: 12.5px; font-weight: 600;
}
.shot .detail-progress-row span:first-child {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shot .detail-progress-row span:last-child { flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.shot .detail-progress-bubble .progress-bar { height: 4px; }
.shot .detail-progress-bubble .progress-fill {
  background-color: var(--accent);
  background-image: linear-gradient(
    100deg,
    transparent 20%,
    rgba(255, 255, 255, 0.5) 45%,
    transparent 70%
  );
  background-size: 220% 100%;
  animation: detail-progress-flow 1.25s linear infinite;
}
@keyframes detail-progress-flow {
  from { background-position: 130% 0; }
  to { background-position: -90% 0; }
}
@keyframes detail-progress-appears {
  from { opacity: 0; transform: translateY(6px) scale(0.98); }
  to { opacity: 1; transform: none; }
}
/* 28 px, not 32: the bubble is a 340 px card and its close control must not
   set the height of the row the phase description sits on. */
.shot .detail-progress-cancel {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 28px; height: 28px; padding: 0;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text-quiet); cursor: pointer;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .detail-progress-cancel:hover { background: var(--muted); color: var(--text); }
@media (prefers-reduced-motion: reduce) {
  .shot .detail-progress-bubble,
.shot .detail-progress-bubble .progress-fill { animation: none; }}

/* auxiliary icon button — must not compete with the primary actions */
.shot .icon-button {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; flex: 0 0 auto;
  border: 0; background: none; border-radius: 50%;
  color: var(--text-quiet); cursor: pointer;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .icon-button:hover { background: var(--muted); color: var(--text); }
.shot .icon-button[aria-pressed="true"] { color: var(--text); }

.shot .player {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 18px; border-bottom: 1px solid var(--line); background: var(--panel);
}
.shot .play {
  /* Both layers share one grid cell, so they centre on each other. 44 px,
     not the 32 of an icon button: this is the transport's primary control
     and the only circular button that leads a row. */
  display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: 50%;
  border: 0; background: none; padding: 0; line-height: 0;
  cursor: pointer; flex: 0 0 auto;
  color: var(--text);
}
/* The button surface reaches the ring: the ring is its border, not
   a ring standing off it. Diameter 38 = a radius-20 track minus the stroke. */
.shot .play-mark {
  grid-area: 1 / 1; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--panel);
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
/* On hover only the glyph takes colour. Any fill behind the button
   would overpower both the ring and the whole row. */
.shot .play:hover .play-mark { color: var(--accent); }
.shot .play-ring {
  grid-area: 1 / 1; display: block;
  width: 44px; height: 44px; transform: rotate(-90deg);
}
/* The ring track doubles as the button border, so it must stay visible
   even at zero progress, or the button looks like it has no border at all. */
.shot .play-track { fill: none; stroke: var(--line-field); stroke-width: 2; }
.shot .play-progress {
  fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round;
  transition: stroke-dashoffset 0.1s linear;
}
/* The range control is transparent except for its thumb and played portion.
   Fixed frequency bands animate underneath it. */
.shot .slider {
  flex: 1; margin: 0; padding: 0;
  -webkit-appearance: none; appearance: none;
  background: none; border: 0; cursor: pointer;
  height: 18px;
}
/* No track at all. Played position is shown by the equalizer bars behind it,
   which are painted in the accent colour up to the handle — one signal instead
   of a line and a row of bars saying the same thing twice. */
.shot .slider::-webkit-slider-runnable-track {
  height: 4px; border: 0; background: none;
}
.shot .slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; margin-top: -4.5px;
  border: 0; border-radius: 50%;
  /* The accent colour, matching the bars it leads. */
  background: var(--accent);
  /* A soft shadow separates the thumb from the equalizer underneath. Black
     rather than --shadow, and at 30 % rather than the token's 8: it has to hold
     against the accent-coloured bars in both palettes. */
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.3);
}
.shot .slider:focus-visible::-webkit-slider-thumb { box-shadow: var(--ring); }

/* Frequency bands stay fixed horizontally. The measured left position and
   width match the slider track exactly. */
.shot .waves {
  position: absolute; top: 0; bottom: 0; z-index: 0;
  pointer-events: none;
  color: hsl(var(--ds-foreground) / 0.12);
  transition: color 0.4s ease;
}
.shot .waves:not(.playing) { color: hsl(var(--ds-foreground) / 0.075); }
.shot .time { font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 600; color: var(--text-quiet); flex: 0 0 auto; }
.shot .speeds { display: flex; gap: 2px; }
.shot .player-end {
  display: flex; align-items: center; gap: 10px; flex: 0 0 auto;
}

/* the shortcuts strip: visible, but never louder than the transcript */
/* A label so the row reads as an offer of help rather than a status line, and
   a cross, because after the tenth recording nobody needs to be told what the
   space bar does. Settings brings it back. */
.shot .shortcuts-title {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--text-quiet);
}
.shot .shortcuts-title::after { content: ":"; }
.shot .shortcuts-hide {
  /* 20 px, not 32: the strip is 35 px tall on its 12 px line, and an
     ordinary 32 px icon button would take it to 47 — the strip would be set
     by its own dismiss control, which is the one thing it must not be. */
  margin-left: auto; flex: 0 0 auto;
  display: grid; place-items: center;
  width: 20px; height: 20px; padding: 0;
  border: 0; background: none; cursor: pointer;
  border-radius: 50%; color: var(--text-quiet);
}
.shot .shortcuts-hide:hover { background: hsl(var(--ds-foreground) / 0.1); color: var(--text); }

/* A language the transcript is missing.
   Sits above the shortcuts strip and takes the same 7/18 padding and the same
   bottom rule, so the two read as one band when both are up. What it does not
   share is the quiet colour: this one is news, and news the reader is meant to
   act on, so it carries the accent line the strip deliberately does not. */
.shot .second-language {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 18px;
  /* The notice bar's own ground (Jakub's ask): this is a notice about the
     transcript, and the reader already knows what that blue means. Same
     white on `--accent-solid` as `.notice` in 01-base.css. */
  background: var(--accent-solid);
  font-size: 13px; font-weight: 500; color: #fff;
}
.shot .second-language-text { flex: 1; min-width: 220px; }
/* The two buttons follow the notice bar's pair as well: the way on is the
   bar's colour on white, the way out a darkened veil. */
.shot .second-language .button {
  background: #fff; color: var(--accent-solid); border: 0;
}
.shot .second-language .button.quiet {
  background: rgb(0 0 0 / 0.18); color: #fff;
}
/* Choosing what to take away — from a marked passage, or from a whole
   recording. One list for both, because it is one question: the audio for the
   podcast, the subtitles for the video and the text for the quote come out of
   the same minutes at the same moment. A row of five buttons said "pick one",
   which is what it was wrong about. */
.shot .clip-dialog,
.shot .save-dialog { width: 520px; }
/* The frame comes from `.about-panel`, which both lists carry as a class: the
   bordered, rounded box with a rule between rows that the About page and the
   backup summary already wear. Asked for on 2026-09-02 by pointing at one.
   Only the list part is the choice's own — a row here is a box, a name and a
   line about it, where `.about-row` is a label and a value. */
.shot .clip-shapes,
.shot .save-choices { list-style: none; padding: 0; }
.shot .clip-shapes li + li,
.shot .save-choices li + li { border-top: 1px solid var(--line); }
.shot .clip-shape,
.shot .save-choice {
  /* `position: relative` because the real input is taken out of the flow and
     has to anchor here rather than on some ancestor. */
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  column-gap: 11px;
  /* The panel's own 14 px sides; 10 above and below, because a row here holds
     two lines where `.about-row` holds one. */
  padding: 10px 14px;
  cursor: pointer;
}
/* The box takes the place `.about-mark` has in that panel, so the names line
   up on the same left edge as the labels there. */
.shot .clip-shape .check-box,
.shot .save-choice .check-box { margin: 1px 0; }
/* The row lights up under the pointer, the same way the rows of files a
   watched folder offers do — the other list in this application made of things
   to tick. `--muted` over the panel's `--subtle` ground is one step, which is
   all a row that is merely pickable should take.

   Not a row that cannot be picked: a text format over a recording with no
   transcript stays where it is, or the highlight would promise a click that
   does nothing. */
.shot .clip-shape,
.shot .save-choice { transition: background var(--hover-duration) var(--hover-ease); }
.shot .clip-shape:hover,
.shot .save-choice:has(input:not(:disabled)):hover { background: var(--muted); }
.shot .clip-shape .check-box,
.shot .save-choice .check-box { grid-row: span 2; align-self: center; }
.shot .clip-shape-name,
.shot .save-choice-name { font-size: 14px; font-weight: 600; color: var(--text); }
/* The line under each name says what it is for, so nobody has to know what VTT
   stands for to choose it. */
.shot .clip-shape-note,
.shot .save-choice-note { font-size: 12.5px; color: var(--text-quiet); }
/* There is no finished state to draw: a fill that is done is said in the
   notice bar like every other confirmation, and this bar simply goes. */

/* The strip used to borrow the sidebar's small-caps rule and then undo every
   part of it with `!important`. It has its own settings now; only the label
   is in small caps, as headings are everywhere else. */
.shot .shortcuts {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 7px 18px; border-bottom: 1px solid var(--line);
  background: var(--subtle);
  font-size: 12px; font-weight: 500; color: var(--text-quiet);
}
.shot .shortcuts kbd {
  font-family: inherit; font-size: 11px; font-weight: 700;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-small); padding: 2px 6px; margin-right: 5px;
}
.shot .shortcut-emphasis { color: var(--text); }
.shot .shortcut-emphasis kbd { border-color: var(--accent-line); color: var(--accent); }

/* 320 px, and it does not shrink with the window.
   It was 300, dropping to 250 on a narrower one — and 250 is where the panel
   stopped working: after padding it leaves 214 px, while a section heading and
   its action want about 270 together. Everything in there then fought for room
   and the headings lost, which is how `Mlu… Kontr… Opra…` happened. Narrowing
   the one column whose contents have a fixed minimum was the wrong economy.
   The transcript can give it up: at the window's own 1000 px minimum that still
   leaves 680, and the text is capped at 760 anyway. */
/* One number for the panel's width: the grid below sizes it and the find bar
   keeps clear of it. Two literals would drift the first time it changes. */
:root { --sidebar-width: 320px; }
.shot .detail-body {
  flex: 1; display: grid; grid-template-columns: 1fr var(--sidebar-width);
  min-height: 0;
}
.shot .detail-body.no-panel { grid-template-columns: 1fr; }

.shot .transcript { overflow-y: auto; padding: 26px 32px 120px; }
/* Centres the transcript's own blocks. **A `.dialog-overlay` must never become
   one of these children** — it is `position: fixed; inset: 0`, and a `max-width`
   on it shrinks the veil to this column and leaves the rest of the window
   undimmed; the reasoning is on `.wizard.wizard > *` in 04-settings.css. This
   screen's dialogs are rendered beside the transcript rather than inside it, so
   the rule stands as it is. */
.shot .transcript.transcript > * { max-width: 760px; margin-inline: auto; }
.shot .live-transcript { font-family: var(--font-body); font-size: var(--text-size); line-height: var(--line-height); color: var(--text-quiet); }

.shot .speaker-header {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.09em; margin: 22px 0 6px;
}

.shot .segment {
  position: relative;
  display: grid; grid-template-columns: 52px 1fr; gap: 12px;
  padding: 3px 10px 3px 0; border-radius: var(--r-small);
}
/* Note: `content-visibility: auto` does not belong here, tempting as it is.
   The browser then only estimates the height of off-screen segments and
   computes the real one when scrolling reaches them. During a smooth jump
   across half the transcript those estimates get corrected one after
   another, the content below shifts, and the target keeps moving away — it
   looks as if the page were hunting for it. Transcript speed comes from
   stable handlers and `memo` on the segments, not from this. */
.shot .segment.current,
.shot .segment.editing { background: var(--accent-light); }

.shot .time-mark {
  background: none; border: 0; cursor: pointer;
  color: var(--text-quiet); font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 4px 0 0; text-align: right; opacity: 0.5;
  border-radius: var(--r-small);
}
.shot .segment:hover .time-mark { opacity: 1; }
.shot .time-mark:hover { color: var(--accent); }

.shot .segment-text { margin: 0; font-family: var(--font-body); font-size: var(--text-size); line-height: var(--line-height); }
.shot .word {
  cursor: pointer;
  /* The highlight is drawn around the text line, not the full line box —
     otherwise the boxes of adjacent lines collide and form steps.
     4 px, not --r-small: on a line-height-tall inline box 8 would be a pill.
     Same amount as `mark`. */
  border-radius: 4px;
  box-decoration-break: clone; -webkit-box-decoration-break: clone;
  padding: 1px 1px;
  margin: 0 -1px;
  transition: background var(--hover-duration) var(--hover-ease);
}
.shot .word:hover { background: var(--highlight); }
.shot .segment.current .word.sounded { color: var(--accent); }
.shot .segment.uncertain .segment-text {
  text-decoration: underline dotted var(--danger);
  text-underline-offset: 4px;
}
/* A word somebody put right. Dashed rather than the dotted line an uncertain
   block wears, and quiet rather than the warning colour: an uncertain place is
   a question, a correction is an answer. The two never meet on one block —
   correcting a segment marks it checked — so the decorations cannot stack.
   Underline rather than a background: the transcript is set to be read, and a
   highlight down a paragraph would fight the text for attention. */
.shot .word.corrected {
  text-decoration: underline dashed var(--text-quiet);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}
/* The same mark in the sidebar's list of corrections, at its own size. */
.shot .correction-after .corrected {
  text-decoration: underline dashed var(--text-quiet);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.shot .edited-mark { color: var(--text-quiet); font-size: 11px; margin-left: 6px; }

.shot .segment.editing textarea {
  width: 100%; border: 1px solid var(--accent); border-radius: var(--r-small);
  padding: 6px 10px; resize: none; overflow: hidden;
  font-family: var(--font-body); font-size: var(--text-size); line-height: var(--line-height);
  background: var(--panel); outline: none;
}

/* ---------------------------------------------------------------- sidebar */

.shot .sidebar {
  /* White in the light theme. `--panel` rather than a literal white so the dark
     theme gets its card colour instead of a glaring slab. */
  border-left: 1px solid var(--line); background: var(--panel);
  /* 12 px at the sides, not 18. The one thing kept from the version that
     stood these cards on the transcript's ground: that arrangement was
     rejected, and the extra six pixels each side it gave the cards were the
     part of it worth having. The lists inside are rows that need the width.
     18 px of vertical inset, because a surface this tall reads tighter at the
     top than at the sides. */
  overflow-y: auto; padding: 18px 12px 60px;
}

.shot .speaker-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
/* ------------------------------------------------- find inside a transcript */
/* A panel over the reading column, with the dialog surface and shadow: it is
   temporary and floats above the text rather than pushing it down. */
/* Over the transcript, never over the panel: the bar belongs to the text it
   searches, and the panel beside it is a different thing that stays readable. */
.shot .search-transcript {
  /* 26 above and 26 from the side — the transcript's own top padding, so the
     bar starts on the line the first block of text does. */
  position: absolute; z-index: 6; top: 26px;
  right: calc(var(--sidebar-width) + 26px);
  display: flex; align-items: center; gap: 4px;
  /* A full pill, not a rounded rectangle: the row is one line of circular
     buttons around a pill field, so anything else would be a fourth corner
     radius on a strip that has none. */
  padding: 6px; border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--panel); box-shadow: var(--shadow-high);
}
.shot .search-transcript input {
  width: 200px; height: 32px; padding: 0 12px;
  border: 1px solid transparent; border-radius: var(--r-pill);
  background: var(--ground); color: var(--text); font: inherit; font-size: 13.5px;
}
.shot .search-transcript input:focus {
  border-color: var(--accent); box-shadow: var(--ring-field); outline: 0;
}
.shot .search-count {
  min-width: 52px; padding: 0 4px; text-align: center;
  color: var(--text-quiet); font-size: 12.5px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* The 26 px circular icon button, as in `.segment-actions`: this row is one line
   of type tall and the standard 32 would make it taller than its own field. */
.shot .search-transcript button {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 26px; height: 26px; padding: 0;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text-quiet); cursor: pointer;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .search-transcript button:hover:not(:disabled) {
  background: var(--muted); color: var(--text);
}
.shot .search-transcript button:disabled { opacity: 0.35; cursor: default; }
/* Every match is tinted; the one being stood on takes the accent. Deliberately
   a background here and not the underline the corrections use — this is a
   transient state the eye must find at a glance, not a permanent mark on the
   text. */
.shot .word.hit { background: var(--accent-light); border-radius: var(--r-small); }
.shot .segment.found .word.hit { background: var(--accent); color: var(--panel); }
.shot .detail-body { position: relative; }
.shot .detail-body.no-panel .search-transcript { right: 26px; }

.shot .speaker-list li { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* A column of the shared 38 px fields — the group has one question above it,
   so the fields carry only a placeholder each and no label of their own. */
/* The language of the run under a speaker's name, where the transcript holds
   two — set as a superscript, which is what a mark qualifying a name is.
   It rides the heading's own colour at half strength rather than taking one of
   its own: the name says who, this says in what, and neither should compete
   with the words below.

   `top` rather than the browser's own raise: `vertical-align: super` on a
   heading with `line-height` this tight pushes the line box open and moves the
   whole row. Positioned instead, so the name stays where it was. */
.shot .speaker-header-language {
  position: relative; top: -0.45em;
  margin-left: 3px;
  font-size: 9.5px; font-weight: 600; letter-spacing: 0.06em;
  vertical-align: baseline;
  opacity: 0.55;
  /* Out of anything the reader drags over. Dragging across the transcript is
     how a passage is quoted and how a clip is marked, and `PAUL BARTLETTEN`
     landing in the middle of a quotation is exactly the kind of thing nobody
     notices until it is in a document. The written files never carry it —
     every export is built in Rust from the blocks themselves — so this is the
     one place it could leak. */
  user-select: none; -webkit-user-select: none;
}

.shot .speaker-names { display: flex; flex-direction: column; gap: 8px; }
/* The shortlist drops onto its own line under the row it belongs to, so the
   sample button, the field and the share keep the geometry they always had. */
.shot .speaker-name-chips {
  flex: 0 0 100%; display: flex; flex-wrap: wrap; gap: 6px;
  /* Under the field, not under the play button that precedes it. */
  padding: 2px 0 4px 26px;
}
.shot .dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
.shot .speaker-list input {
  flex: 1; min-width: 0; border: 1px solid transparent; background: transparent;
  border-radius: var(--r-pill); padding: 5px 10px; font-weight: 600; font-size: 13.5px;
}
.shot .speaker-list input:hover { border-color: var(--line); }
.shot .speaker-list input:focus {
  border-color: var(--accent); outline: none;
  background: var(--ground); box-shadow: var(--ring-field);
}
/* The merge control is the shared Select now, so it needs no colours of its
   own — only a width, or it would stretch and push the name into two lines. */
.shot .speaker-list .select { flex: 0 0 auto; width: 132px; }
.shot .speaker-list .select button { min-height: 30px; font-size: 12px; }
/* Takes a person off the list. 26 px for the reason `.search-transcript button`
   gives — a row one line of type tall, where the standard 32 would be taller
   than the field beside it. Grey until pointed at: one of these sits in every
   row, and a column of red cans would read as a warning about the recording. */
.shot .speaker-remove {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 26px; height: 26px; padding: 0;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text-quiet); cursor: pointer;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .speaker-remove:hover { background: var(--danger-light); color: var(--danger); }
/* The notes section's "+ Přidat" exactly, with 7 px of side padding instead of
   9: this heading is the only one carrying two actions, and the card is 283 px
   wide. Those 4 px are what keeps "+ Přidat  Rozpoznat mluvčí" on one line. */
.shot .speaker-add { padding: 0 7px; }
/* The two actions the heading carries. No gap of its own — each button's
   padding already holds them apart, and 8 more would push the pair off the
   card's edge. */
.shot .speaker-actions { display: flex; align-items: center; flex: 0 0 auto; }

.shot .menu {
  position: fixed; left: 50%;
  bottom: calc(var(--app-footer-height) + 24px); transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: var(--shadow-high); border-radius: var(--r-large);
  padding: 11px 16px; z-index: 20; font-size: 13.5px; font-weight: 500;
}
/* ==== src/css/04-settings.css ==== */
/* Settings, the wizard, dialogs and the recorder.
 *
 * Part 04 of styles.css. The number is the point: these files are
 * one stylesheet cut into pieces, and the order they are imported in is the
 * order the rules were in. Moving a rule between them can change the look
 * without changing a value. */

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

.shot .settings { flex: 1; overflow-y: auto; padding: 34px 24px 96px; }
/* 720 px, and the wizard's by-hand listing is now the same column — see
 * `.wizard.wizard-listing` further down this file, where the reasoning for both
 * this width and the exclusion the wizard's rule carries is written out.
 *
 * **A `.dialog-overlay` must never become one of these children.** It is
 * `position: fixed; inset: 0`, and a `max-width` on it shrinks the veil to this
 * column and leaves the rest of the window undimmed — the defect the wizard
 * shipped with. Settings renders its dialogs inside the cards that raise them
 * rather than at this level, so the rule stands as it is; if one is ever put
 * here, exclude it the way the wizard's rule does.
 *
 * The doubled class is a specificity fight that nothing in the history explains
 * — it predates the English rename. It is left doubled rather than reduced
 * blind: it beats any single-class rule landing on a direct child, which is a
 * fight it can only lose by being made weaker. */
.shot .settings.settings > * { max-width: 720px; margin-inline: auto; }
.shot .settings-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; margin-bottom: 18px;
}
.shot .settings h1,
.shot .settings [data-h="1"] {
  font-size: 28px; line-height: 1.15; font-weight: 700;
  margin: 0; letter-spacing: -0.025em;
}
/* Settings save themselves; this is only a quiet confirmation.
   Fixed to the window rather than sitting beside the title: most settings are
   changed further down the page, where a pill up in the header is off screen
   and the confirmation might as well not exist. It floats in the corner
   instead, so it is in the same place whatever was just changed. */
.shot .saved {
  position: fixed; right: 22px; bottom: 22px; z-index: 50;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 13px 6px 10px; border-radius: var(--r-pill);
  background: hsl(var(--ds-grass) / 0.14); color: var(--success-text);
  border: 1px solid hsl(var(--ds-grass) / 0.3);
  box-shadow: var(--shadow-high);
  font-size: 12.5px; font-weight: 700;
  opacity: 0; transform: translateY(6px);
  transition: opacity 0.18s ease, transform 0.18s ease;
  pointer-events: none;
}
.shot .saved.visible { opacity: 1; transform: none; }
/* The ring drains over however long its owner is shown for; the surface sets
   the duration, the ring only knows how to empty. The dash array is the
   circle's circumference, so the offset runs from nothing to all of it. */
.shot .countdown { flex: 0 0 auto; }
.shot .countdown-track { stroke-dasharray: 40.2; stroke-dashoffset: 0; }
@keyframes odpocet {
  from { stroke-dashoffset: 0; }
  to { stroke-dashoffset: 40.2; }
}
.shot .saved.visible .countdown-track { animation: odpocet 1.4s linear forwards; }
@media (prefers-reduced-motion: reduce) {
  .shot .saved { transition: opacity 0.18s ease; transform: none; }
  .shot .countdown-track { animation: none; }}
.shot .settings-tabs {
  display: flex; align-items: center; gap: 4px;
  margin-bottom: 18px; padding: 2px 0;
}
/* Navigation between views: no border at all, the chosen one raised as a white
   pill on the grey ground. One of the application's three button jobs, and the
   only one that applies to a row of views — a bordered pill is a standalone
   action, a dark pill is a value that has been set. The document preview's
   tabs are the same job and share this rule; do not give either a border. */
.shot .settings-tabs button,
.shot .ai-document-tabs button,
.shot .ai-output-options button {
  position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 36px; padding: 0 14px;
  border: 0; border-radius: var(--r-pill); background: transparent;
  color: var(--text-quiet); cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 650; white-space: nowrap;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease),
    box-shadow var(--hover-duration) var(--hover-ease);
}
/* The rule is not "a white pill" but "the chosen one is filled, one step off
   its own ground" — white cannot be that step on white.
   On grey: raised in white. Settings' page, and the strip that sits on the
   document inside the preview dialog. */
.shot .settings-tabs button:hover,
.shot .ai-output-options button:hover { background: var(--muted); color: var(--text); }
.shot .settings-tabs button.active,
.shot .ai-output-options button.active {
  background: var(--panel); color: var(--text); box-shadow: var(--shadow);
}
/* On white: filled dark, which is the same rule taken to its end — the chosen
   one is the ground's opposite. It is also what the playback speeds do, on
   white, for the same job: one of a row, chosen. `--muted` was tried and is
   an 8 % step off white, too little to mark anything. */
.shot .ai-document-tabs button:hover { background: var(--muted); color: var(--text); }
.shot .ai-document-tabs button.active { background: var(--text); color: var(--ground); }
.shot .settings-tab-alert {
  width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%;
  background: var(--danger);
}
.shot .settings-panels {
  display: flex; flex-direction: column; gap: 16px;
}
/* Přepis reads what comes out of a recording, in the order somebody wants it:
   the model and the language, then the speakers, then the language editing,
   then the corrections, and last everything folded away. What is downloaded
   used to stand between the last two; it is about the machine rather than the
   transcript and is on Nástroje now.
   The JSX may declare them in any order it likes; this is the reading order. */
.shot .settings-card-transcription { order: 1; }
/* The recording's language stands between the model that reads it and the
   people in it: still about the recording, and the last thing on this tab that
   is. Everything below is about what is done with the words afterwards. */
.shot .settings-card-language { order: 2; }
.shot .settings-card-speakers { order: 3; }
.shot .settings-card-second-language { order: 4; }
.shot .settings-card-language-edit { order: 5; }
.shot .settings-card-dictionary { order: 6; }
.shot .settings-card-advanced { order: 7; }
/* Výkon holds one card, and it still gets a number: without a rule it takes
   `order: 0` and the next card put beside it jumps above whatever asked for 1.
   That is the defect this whole block exists to prevent, and a tab with one
   card is exactly where somebody would think the rule is not needed. */
.shot .settings-card-compute { order: 1; }
/* Nástroje: whether what is needed is downloaded, then where it is kept, then
   what was found where. The compute card led this tab until `Výkon` was given
   one of its own; these three were always the rest of it and their order is
   unchanged — only their numbers moved up. */
.shot .settings-card-modules { order: 1; }
.shot .settings-card-locations { order: 2; }
/* `settings-card-diagnostics` was 3 here and is gone with the block it ordered.
   Two cards left on this tab and no gap. */
/* Every card on a tab needs a number here, and the recordings folder had none:
   without a rule it takes `order: 0` and sorts above cards that asked for 1, so
   it rendered over the portable-mode banner no matter where the JSX put it.
   That includes the two tabs that hold a single card each — alone the number
   changes nothing, and it is what stops the next card added beside them from
   silently jumping to the top. */
/* Jazyk a vzhled: the two halves the tab is named for, in the order it names
   them. The language card was the appearance card's first field until the tab
   stopped being called `Vzhled`. */
.shot .settings-card-app-language { order: 1; }
.shot .settings-card-appearance { order: 2; }
.shot .settings-card-updates { order: 1; }
/* Složky a zálohy: the banner first when there is one, because it says what
   kind of installation everything under it belongs to; then where recordings
   are put, where new ones are looked for, and last the copies of the archive
   and of the whole application, which are what you go to when something has
   already gone wrong. */
.shot .settings-card-portable { order: 1; }
.shot .settings-card-recordings { order: 2; }
.shot .settings-card-watch-folder { order: 3; }
.shot .settings-card-backups { order: 4; }
.shot .settings-card-portable-copy { order: 5; }
/* Informace: what it is, what it does, what it stands on. */
.shot .settings-card-about { order: 1; }
.shot .settings-card-abilities { order: 2; }
.shot .settings-card-credits { order: 3; }
/* The card whose whole content is one folded-away block — `Pokročilé` at the
   foot of `Přepis`. Its disclosure carries the 14 px rule that separates it from
   whatever stood above it inside a card, and here nothing does, so the rule
   would be drawn along the card's own top edge, 26 px in from both sides and
   parallel to the border. Zeroed, and only here: everywhere else that rule is
   doing its job. `Technické podrobnosti` was the second card on this selector
   until 14 August 2026. */
.shot .settings-card-advanced > .settings-disclosure {
  margin-top: 0; padding-top: 0; border-top: 0;
}
.shot .settings section {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-large); padding: 26px 28px;
}
.shot .settings h2,
.shot .settings [data-h="2"] {
  margin: 0; font-size: 19px; line-height: 1.25; font-weight: 700;
  letter-spacing: -0.018em;
}
/* `anywhere` and not `break-all`, and the difference is the whole rule.
   `break-all` breaks between any two letters at the end of every line, whether
   or not the word would have fitted on the next one — which is how
   `Volocal.exe` came to be printed as `Vo` and `local.exe`. `anywhere` breaks
   inside a word only when the word cannot fit on a line by itself, so the file
   name moves down whole and the long install path, which fits nowhere, still
   breaks rather than running out of its card. */
.shot .settings code {
  font-size: 12px; background: var(--muted); padding: 2px 7px;
  border-radius: var(--r-small); overflow-wrap: anywhere;
}

.shot .settings-section-description {
  max-width: 62ch; margin: 8px 0 24px;
  color: var(--text-quiet); font-size: 14px; line-height: 1.55;
}
/* A card's first field under a heading with no sentence between them.
   `.settings-toggle:not(.section)` was on this selector for an hour, for
   `Rozlišení mluvčích` under a `Mluvčí` heading; that arrangement is gone —
   the phrase is the heading now and the switch is on it — and the selector is
   narrow again rather than left waiting for a caller that no longer exists. */
.shot .settings section > h2 + .field,
.shot .settings section > [data-h="2"] + .field { margin-top: 24px; }
/* A card whose content follows its heading with no introductory sentence
   between them takes the same 24 px that a field does. Without this the list
   sat on the heading, because `h2` carries no bottom margin of its own — the
   description normally provides the gap. */
.shot .settings section > h2 + .about-abilities,
.shot .settings section > [data-h="2"] + .about-abilities { margin-top: 24px; }
.shot .settings-info-note {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 8px 0 0; color: var(--text-quiet);
  font-size: 13.5px; line-height: 1.5;
}
.shot .settings-info-note.compact { margin: 0; }
.shot .settings-info-note-icon { flex: 0 0 auto; margin-top: 2px; color: var(--accent); }
.shot .settings-toggle {
  display: flex; align-items: center; justify-content: space-between;
  gap: 32px;
}
.shot .settings-toggle-copy { min-width: 0; flex: 1 1 auto; }
.shot .settings-toggle.section .settings-info-note { max-width: 62ch; }
.shot .settings-toggle:not(.section) {
  margin: 0; padding: 0;
}
.shot .settings-toggle:not(.section) .settings-info-note { margin-top: 8px; }
.shot .settings-toggle-title {
  display: block; font-size: 15px; line-height: 1.35; font-weight: 680;
}
/* A plain switch below the controls it qualifies, on the divider between them —
   `Automaticky` under the two cards on `Výkon`. This is the pairing's own rule,
   not the section variant's borrowed: a column of choice cards is a block of
   controls exactly as a field is, so `.choices` joins the selector that already
   places a plain switch after a field. `.settings-toggle.separated` is 22/20 and
   is the other case — a switch after free-flowing text, as on `Aktualizace`. */
.shot .field + .settings-toggle:not(.section),
.shot .choices + .settings-toggle:not(.section),
.shot /* …and after the live sample at the foot of the appearance card, which is the
   block the shortcut-strip switch follows. A framed sample is a block of
   controls in the way that matters here — the switch is the next thing after
   it, and it takes the same divider a switch after a field takes. */
.preview-font + .settings-toggle:not(.section) {
  margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--line);
}
.shot .settings-toggle:not(.section) + .field {
  margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--line);
}
.shot .settings-toggle.separated {
  margin: 22px 0 0; padding: 20px 0 0;
  border-top: 1px solid var(--line);
}
.shot .settings-toggle-control {
  margin: 0 -7px 0 0; padding: 7px;
  border-radius: var(--r-pill);
}
.shot .settings-toggle-control:has(input:disabled) { opacity: 0.45; cursor: default; }
.shot .settings-toggle.section + .field,
.shot /* One dependent switch under another — watching a folder, and transcribing
   what it finds. They belong together, so no divider: only the room a control
   needs to stop reading as a second line of the one above it. */
.settings-toggle:not(.section) + .settings-toggle:not(.section) { margin-top: 20px; }
/* Choice cards revealed by a section switch sit on the same divider as a
   revealed field, so turning a section on always looks the same. */
.shot .settings-toggle.section + .choices {
  margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--line);
}
/* 14 px everywhere else, 24 under the wizard's two cards. Above it there stands
   a block of framed cards rather than a paragraph, and at fourteen the note sat
   close enough to their bottom edge to read as a caption of the card on the
   left — *ta info věta nalepená na kartu*. */
.shot .choices + .settings-info-note.compact { margin-top: 14px; }
.shot .setup-choices + .settings-info-note.compact { margin-top: 24px; }
.shot .settings-action-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  font-size: 13.5px;
}
.shot .settings-action-row.separated {
  margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line);
}
/* The same distance without the rule, for the cards whose content above is
   already framed — the module tiles, the backup summary. A rule under a
   bordered panel draws the same boundary twice, and on a card that also ends
   in a folded-away footer it made two rules a few lines apart with one row of
   text between them. The rule that stays is the one below this row, which
   separates the card from the thing folded away at its foot. */
.shot .settings-action-row.spaced { margin-top: 22px; }
.shot .settings-action-row > .settings-info-note { margin: 0; }
.shot .settings-action-row > .button { flex: 0 0 auto; }
/* Two buttons at the right-hand end, as one group.

   The row is `space-between`, which is right for a note and a button and wrong
   the moment there are two: it would put one button at each end and strand the
   note in the middle of them. Export and import are one choice with two
   directions and have to sit as one. 8 px was `.diagnostics-actions`' gap, the
   other place two buttons stood together until that block was removed. */
.shot .settings-action-row-buttons { display: flex; gap: 8px; flex: 0 0 auto; }
.shot .settings-success { color: var(--success); }
.shot .field { margin-bottom: 24px; }
.shot .settings section > .field:last-child { margin-bottom: 0; }
.shot .field > .small-text {
  max-width: 62ch; margin: 8px 0 0; line-height: 1.5;
}
/* the value beside a slider belongs to the label, not to a sentence */
.shot .value { font-style: normal; color: var(--text); font-variant-numeric: tabular-nums; }

/* One disclosure pattern for advanced controls and technical diagnostics. */
.shot .settings-disclosure {
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
}
/* A divider needs the same room on both sides, or the block between two of
   them is not in the middle of what the eye reads as one block. The disclosure
   carries its own tighter 14/14 rhythm — right after a field, wrong after a
   switch, which sits on 22. Left at 14 the row had 22 above and 14 below, and
   because the switch is centred on the row it was the switch that looked
   dropped: 5.5 px below the midpoint of the two lines, measured. */
.shot .settings-toggle:not(.section) + .settings-disclosure { margin-top: 22px; }
.shot .settings-action-row + .settings-disclosure {
  margin-top: 12px; padding-top: 0; border-top: 0;
}
/* Unless it is the card's last band, which is where a folded-away extra
   belongs: the rarest thing on the card, under everything that is not.

   Then it takes the rule, at the action row's own 22/20. This is the card's
   only rule, which is the point — the action row above it gives its own up
   (`.spaced`) precisely so that this one is not the second of two a few lines
   apart with a single row of text between them. One line, and it falls where
   the card stops being about its main business. */
.shot .settings-action-row + .settings-disclosure.card-footer {
  margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line);
}
.shot .settings-disclosure > summary {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 0; border: 0; background: none; cursor: pointer;
  list-style: none;
  font: inherit; font-size: 13.5px; font-weight: 600; color: var(--text-quiet);
}
.shot .settings-disclosure > summary::-webkit-details-marker { display: none; }
.shot .settings-disclosure > summary:hover { color: var(--text); }
.shot .settings-disclosure > summary:focus-visible { box-shadow: var(--ring); border-radius: var(--r-small); }
.shot .settings-disclosure-chevron {
  display: grid; place-items: center; line-height: 0;
  transition: transform 0.16s ease;
}
.shot .settings-disclosure[open] .settings-disclosure-chevron { transform: rotate(90deg); }
/* No rule down the side, and no indent to carry one.

   It was drawn to say "this belongs to the summary above", which the summary
   already says by being the thing you opened. What it actually did was add a
   second border beside every panel put inside it — there was already an
   exception for `.check` admitting as much — and indent the content away from
   everything else on the card for no reason the eye can use. */
.shot .settings-disclosure-content {
  margin-top: 8px;
  display: flex; flex-direction: column; gap: 2px;
}
.shot .settings-disclosure-content > .small-text:first-child { margin-top: 0; }
/* Below the window's own 1000 px minimum (tauri.conf.json), so this cannot
   fire today. Kept as a net, like the 900 px rule for the header pills, in
   case the minimum ever drops or the interface is opened in a browser.
   560 px: Settings loses its outer padding and the tabs scroll. */
@media (max-width: 560px) {
  .shot .settings { padding: 24px 12px 72px; }
  .shot .settings section { padding: 22px 20px; }
  .shot .settings-tabs { overflow-x: auto; scrollbar-width: none; }
  .shot .settings-tabs::-webkit-scrollbar { display: none; }
  .shot .settings-toggle,
.shot .settings-action-row { gap: 16px; }
  .shot .input-row { flex-wrap: wrap; }
  .shot .input-row input { flex-basis: 100%; }}
/* The toggle is a <label> too, but inline. Without :not() this rule would
   switch it to block, the track would lose its width and the knob would end
   up inside the text. */
.shot .field > label:not(.switch) {
  display: block; margin-bottom: 8px;
  color: var(--text); font-size: 13.5px; line-height: 1.35; font-weight: 650;
}
.shot .field input[type="text"],
.shot .field input:not([type]),
.shot .field input[type="url"],
.shot .field select,
.shot .field input[type="number"] {
  width: 100%; height: 38px;
  border: 1px solid var(--line-field); background: var(--ground);
  border-radius: var(--r-pill); padding: 0 15px; outline: none; font-size: 14px;
}
/* The rule above is for one-line controls and fixes their height at 38 px. An
   instruction to a language model is a sentence or three, so it gets a
   textarea, which cannot share that declaration and repeats the border, the
   ground and the padding rather than inventing its own. Two differences, both
   because it is a box and not a line: the card radius, since a pill crops the
   corners of the first and last line inside it, and a height it can be dragged
   taller from. Everything a `.field` textarea needs is here — a second one
   added later belongs to this rule, not beside it. */
.shot .field textarea {
  /* 84 px is three lines at 14/1.5 with the padding, which is what an
     instruction to a model actually is; 160 is six, and it is a ceiling rather
     than a preference. The handle may not be able to resize the dialog around
     it: 160 keeps the tallest dialog holding one of these inside the smallest
     window the application allows (660 px, `tauri.conf.json`), so the footer
     cannot be dragged off the bottom of the screen. Past six lines the box
     scrolls, the way the release notes do at 240. */
  width: 100%; height: 84px; min-height: 84px; max-height: 160px;
  resize: vertical;
  border: 1px solid var(--line-field); background: var(--ground);
  border-radius: var(--r-card); padding: 9px 14px; outline: none;
  font-size: 14px; line-height: 1.5;
}
/* A field lights up when it is being filled in. A slider is not filled in,
   it is grabbed — so dragging one does not light anything up (Jakub's ask).
   `:focus-visible` further down still gives it a ring on keyboard focus, which
   is the only way to see where the focus stands without a pointer. */
.shot .field input:not([type="range"]):focus,
.shot .field select:focus,
.shot .field textarea:focus {
  border-color: var(--accent); box-shadow: var(--ring-field);
}
.shot .field input[type="range"] { width: 100%; accent-color: hsl(var(--ds-foreground)); height: auto; }
/* ---------------------------------------------------------------- custom menu
   On Windows the native <select> is painted by the OS: square corners and
   foreign colours. This is its replacement in the design system's language. */

.shot .select { position: relative; }
.shot .select-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; height: 38px; padding: 0 16px;
  border: 1px solid var(--line-field); background: var(--ground);
  border-radius: var(--r-pill); cursor: pointer;
  font-size: 14px; text-align: left;
  transition: border-color var(--hover-duration) var(--hover-ease),
    background var(--hover-duration) var(--hover-ease);
}
.shot .select-trigger:hover { border-color: hsl(var(--ds-foreground) / 0.35); }
.shot .select-trigger.expanded {
  border-color: var(--accent); background: var(--panel);
  box-shadow: var(--ring-field);
}
.shot .select-trigger:disabled { opacity: 0.45; cursor: default; }
.shot .select-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shot .select-arrow { flex: 0 0 auto; color: var(--text-quiet); transition: transform 0.15s; }
.shot .select-trigger.expanded .select-arrow { transform: rotate(180deg); }

.shot .select-menu {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 40;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-large); box-shadow: var(--shadow-high);
  padding: 6px; max-height: 320px; overflow-y: auto;
}
.shot .select-group {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--text-quiet);
  padding: 10px 12px 5px;
}
.shot .select-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 9px 12px; border: 0; background: none; cursor: pointer;
  border-radius: var(--r-card); text-align: left; font-size: 14px;
}
.shot .select-item.highlighted { background: var(--muted); }
.shot .select-item.picked { font-weight: 600; }
.shot .select-label { flex: 1; min-width: 0; }
.shot .select-note { font-size: 12px; color: var(--text-quiet); white-space: nowrap; }
.shot .select-tick { flex: 0 0 auto; color: var(--accent); align-self: center; }

.shot .input-row { display: flex; gap: 8px; }
.shot .input-row input { flex: 1; min-width: 0; }
.shot .field .input-row > .button { height: 38px; }

.shot .portable-info {
  border-color: var(--accent-line) !important;
  background: var(--accent-light) !important;
}
.shot .portable-info p { margin: 0 0 8px; }

/* `.check` and its four children stood here — the found-paths table inside
   `Technické podrobnosti`, three columns of mark, name and path. Deleted with
   that block on 14 August 2026: it was the only thing that ever drew them, and
   a rule kept for a screen nobody can reach is a rule the next person has to
   work out the absence of. The circular tick lives on in `.downloaded-mark`,
   which is where it came from. */
.shot .problems { color: var(--danger); font-size: 13px; padding-left: 18px; }

/* `.benchmark` stood here — the list of measured backends under `Změřit
   rychlost`. Both are gone: `benchmark_compute` in Rust is the application's
   only instrument for timing a backend and has no caller, and until somebody
   runs it there is nothing measured to list. The card on `Nástroje` chooses
   between the processor and the card and then says, from `.about-panel`, which
   build actually ran — the panel of facts the About page and the archive's
   backups already share. */
/* A `.about-panel` rule stood here, for a `Používá se` row under the two cards.
   The row is gone: under a card already drawn as chosen it said the same thing
   a third time, and where it had something to say — a pick that could not be
   met — a label and a value were too quiet for it. What is under the cards now
   is one sentence chosen by the state, and `.choices + .settings-info-note.
   compact` and `.settings-action-row.spaced` already place both shapes. */

.shot .preview-font {
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 18px 20px; background: var(--ground); margin-top: 4px;
}
.shot .preview-font p { font-family: var(--font-body); font-size: var(--text-size); line-height: var(--line-height); margin: 0; }
.shot .preview-speakers {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--accent); margin-bottom: 6px;
}
.shot .preview-label {
  font-size: 13px !important; line-height: 1.6 !important;
  color: var(--text-quiet); margin-top: 14px !important;
  font-family: var(--font) !important;
}

/* ---------------------------------------------------------------- wizard */

/* The wizard's column, and the one screen in it that is a different width.
 *
 * **`:not(.dialog-overlay)` is a defect fix, not tidiness.** `ConfirmationDialog`
 * renders at the end of this `<main>`, so the scrim was one of the children this
 * rule centres — and `max-width` on a `position: fixed; inset: 0` box does not
 * bounce off it: the box shrinks to 620 px and `margin-inline: auto` centres it.
 * The veil covered a strip down the middle of the window and left the rest of
 * the screen undimmed, which is the *divný šedý pruh* on both sides of the
 * owner's screenshot. Nothing was wrong with the scrim's colour and nothing
 * behind it was two surfaces; it simply was not covering the window. The other
 * three screens that centre their children do **not** carry the exclusion, and
 * that is a decision rather than an oversight: none of them renders a dialog at
 * that level, and `:not()` adds its argument's specificity — on the archive it
 * would lift the rule to what `.library.library > .archive-sticky` and
 * `.archive-scroll-content` have, turning two specificity wins into a matter of
 * file order. Each of the three carries a comment saying what must never become
 * one of its children, which costs nothing and cannot go stale.
 *
 * **The width rides on a variable** rather than on a second `max-width` rule,
 * so there is one declaration of the column and nothing new has to win a
 * cascade fight to change it. 720 px is the settings column's own width, on the
 * owner's ask — the listing is reached from Settings and is a table, and a
 * column that moved 100 px under the reader between the two screens was a
 * change nobody could see a reason for. The guided run keeps 620: it is one
 * question with two cards, which is prose and choices, and prose does not want
 * the width a table does.
 *
 * The doubled class is a specificity fight and is left as it stands: nothing in
 * the history says what it was written to beat, and it beats any single-class
 * rule that lands on a direct child of this screen — which is a fight it can
 * only lose by being reduced. */
.shot .wizard { flex: 1; overflow-y: auto; padding: 36px 20px 40px; --wizard-column: 620px; }
.shot .wizard.wizard-listing { --wizard-column: 720px; }
.shot .wizard.wizard > :not(.dialog-overlay) {
  /* The fallback is not decoration: an undefined `var()` drops the declaration
     silently, and this one decides the shape of every screen in the wizard. */
  max-width: var(--wizard-column, 620px); margin-inline: auto;
}

/* `.steps`, `.step-number`, `.step-intro`, `.step-outro` and its two children
   stood here — the three-segment bar, the `KROK 1 ZE 3` above the heading, the
   quiet sentence under it, and the centred conclusion. All six are deleted with
   the screen they were the furniture of: the guided run is a dialog over the
   archive now, and a dialog has its own rhythm — 19 px heading, 8 px to its
   sentence, 18 px to the content, which `.dialog h2`, `.dialog p` and
   `.dialog h2 + p` already own. A bar counting three screens inside the one
   thing being counted was ceremony, and centred prose in a dialog reads as a
   certificate. `.step` and `.step h1` stay: the by-hand listing is still a page
   and still wants a 28 px heading. */
.shot .step h1,
.shot .step [data-h="1"] { font-size: 28px; line-height: 1.15; margin: 0 0 8px; letter-spacing: -0.025em; font-weight: 700; }

.shot .step-footer {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--line);
}
.shot .step-footer .button.primary { margin-left: auto; }

/* `.detected` and its three children stood here — a two-panel grid on the
   wizard's one question, saying which graphics driver was found and where the
   transcription would run. Deleted with the block: the sentence directly above
   it already told the reader the estimates were for the graphics card in this
   computer, and two panels restating the line above them were the most words on
   the screen with the fewest. The 11/700/0.09em micro-label lives on in
   `.detected-label`'s twin elsewhere in the visual system, so nothing was lost
   from the type scale either. */

.shot .choices { display: flex; flex-direction: column; gap: 8px; }
/* ---------------------------------------------- the wizard's two cards

   **The one place a choice card is not the shared row**, and the exception is
   argued rather than assumed. `.choice` is one rule for every card of its kind
   in the application — the three sources in `Nový přepis`, the transcription
   models, the language-editing tiers — and those all sit in a column of eight
   things somebody scrolls. This is the whole of the first run: one question,
   two answers, and the answer is awkward to take back, because changing the
   model later means transcribing everything again.

   Beside one another two things are compared; below one another they are read.
   So the axis turns, and with it what a card holds. */
.shot .setup-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.shot .setup-choices .choice { flex-direction: column; gap: 0; padding: 0; overflow: hidden; }
/* `flex: 1` on the body is what pins the foot to the bottom of the card rather
   than to the end of the text. Both feet then sit on one axis however many
   lines a description takes — and that alignment is what turns two cards into a
   comparison rather than two cards. */
.shot .setup-choices .choice-top {
  padding: 17px 16px 16px; display: flex; flex-direction: column; flex: 1 1 auto;
}
/* `justify-content` musí být uvedeno, ne zděděno: sdílené `.choice-head` má
   `space-between` is named rather than inherited: elsewhere on a choice card
   the name stands left and the size right, and with nothing on the right here
   that rule pushed the heading to the far edge. */
.shot .setup-choices .choice-head {
  display: flex; justify-content: flex-start; align-items: center; gap: 10px;
}
.shot .setup-choices .choice-icon { width: 32px; height: 32px; }
.shot .setup-choices .choice-title { font-size: 16px; flex-wrap: wrap; row-gap: 4px; }
.shot .setup-choices .choice-top .small-text { margin-top: 12px; line-height: 1.5; }
/* The foot is set into `--subtle`, the token that exists for exactly this: a
   quiet inset inside a surface. No rule across the top — a band that has its own
   ground separates itself, and a line there would be a line a gap already made. */
.shot .setup-choices .choice-foot {
  background: var(--subtle); padding: 13px 16px;
  display: flex; flex-direction: column; gap: 8px;
}
.shot .setup-choices .choice.chosen .choice-foot { background: hsl(var(--ds-petrol) / 0.06); }
/* The number first and the words after it, and the line may not wrap. Written
   the other way round — *Hodinová nahrávka za 4 minuty* — it filled 175 px of a
   198 px column: two pixels of slack, so a longer figure broke one card's foot
   and not the other's, and the alignment everything above rests on went with
   it. */
.shot .setup-choices .choice-stat {
  display: flex; align-items: baseline; gap: 7px; white-space: nowrap;
  font-size: 12.5px; color: var(--text-quiet); font-variant-numeric: tabular-nums;
}
.shot .setup-choices .choice-stat svg { flex: 0 0 auto; opacity: 0.7; align-self: center; }
.shot .setup-choices .choice-stat b { font-weight: 600; color: var(--text); font-size: 13px; }

/* One frame for every row of this kind. They carry different content but must
   read as one family, so the padding, the radius and the vertical rhythm live
   here and nowhere else. */
.shot .choice {
  border: 1px solid var(--line); background: var(--panel);
  border-radius: var(--r-large); padding: 12px 14px;
}
.shot .choice {
  display: flex; flex-direction: column; gap: 4px; width: 100%;
  text-align: left; cursor: pointer;
  transition: border-color var(--hover-duration) var(--hover-ease),
    background var(--hover-duration) var(--hover-ease),
    box-shadow var(--hover-duration) var(--hover-ease);
}
/* Under the pointer a choice card lifts, exactly as an archive row does — the
   shared low `--shadow`, not a shadow chosen for these cards. The keyboard gets
   the same lift: the focus ring says where you are, the shadow says the thing
   under it is pickable, and that is the same statement in both directions.
   One rule for every choice card in the application — the three sources in
   `Nový přepis`, the transcription models, the acceleration and the
   language-editing tiers, the wizard's quality cards, the AI dialog's modes. */
.shot .choice:hover,
.shot .choice:focus-visible { border-color: var(--line-field); box-shadow: var(--shadow); }
.shot .choice.chosen { border-color: var(--accent-line); background: var(--accent-light); }
.shot .choice-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.shot .choice-title { font-weight: 600; font-size: 15px; display: flex; align-items: center; gap: 8px; }
/* Every size in this application is one pair: 13 px, 500, `--text-quiet`,
   `tabular-nums`. *Velikost těch souborů nedávej tučně.*

   500 is chosen against its two neighbours rather than picked off the scale.
   Prose beside it is 400 — `.small-text` — and the name above it is 600, and a
   figure somebody scans down a column belongs between them: heavier than a
   sentence so the column reads as a column, lighter than the name so it
   supports the thing it belongs to instead of competing with it. At 600 it was
   the name's own weight, which is what made it loud.

   No exception for the cards, and it was considered: on a `.choice` the size is
   part of the decision — pressing that card fetches those gigabytes — while in
   a listing it is reference. But on a card the figure is already privileged by
   having its own column at the right edge and by being the only number there.
   Weight is not what makes it findable, and at 600 beside a 15/600 title the
   two read as equals. */
.shot .choice-size { font-size: 13px; font-weight: 500; color: var(--text-quiet); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* What a choice costs, as a line inside the card rather than a figure in a
   column at its edge. The wizard's two quality cards are the only cards that
   need it: a size alone answers *how many megabytes*, and what somebody
   choosing a transcription model is weighing is minutes against megabytes,
   which is one sentence and not two columns.

   It is `.choice-size`'s own type — 13 px, 500, `--text-quiet`, `tabular-nums`,
   the pair documented above — and deliberately not that class, for the one
   property it must not take: `.choice-size` is `nowrap`, which is right for a
   figure in a column and wrong for a sentence that has to wrap at 520 px in
   whatever language it is read in. */
.shot .choice-cost { font-size: 13px; font-weight: 500; color: var(--text-quiet); font-variant-numeric: tabular-nums; }
/* Three points on top of `.choice-body`'s own three, so the card reads as a
   name over its detail and then two statements of a different kind. At the
   plain 3 px all four lines sat in one block: the description and the cost are
   both 13 px and both quiet, and 500 against 400 is the whole of what tells
   them apart within a line. Six is what separates them as blocks. */
.shot .choice-cost,
.shot .choice-reason { margin-top: 3px; }
/* Why this card is the one for this computer. It replaced a `doporučeno` badge,
   and the colour is the badge's accent kept: this is still the application
   pointing at one of two cards, and losing the accent would have made the
   recommendation quieter than the sentence above it, which is the wrong way
   round. 600 rather than the 700 a badge carries — it is a sentence now, and
   700 over three lines of it reads as shouting. */
.shot .choice-reason { font-size: 13px; font-weight: 600; color: var(--accent); line-height: 1.45; }

.shot .badge {
  font-style: normal; font-size: 11px; font-weight: 700;
  background: hsl(var(--ds-petrol) / 0.14); color: var(--accent);
  padding: 2px 9px; border-radius: var(--r-pill); letter-spacing: 0.02em;
}
.shot .badge.required { background: var(--danger-light); color: var(--danger); }
.shot .badge.complete { background: hsl(var(--ds-grass) / 0.14); color: var(--success-text); }
/* A badge that is the card's action, not its state. The whole card is the
   button, so this must read as something that happens, not as a label. */
.shot .badge.actions { background: var(--accent-light); color: var(--accent); }
.shot .choice.missing .choice-icon { background: var(--danger-light); color: var(--danger); }
.shot .choice.missing:hover .badge.actions { background: var(--accent); color: white; }
.shot .badge.quiet { background: var(--muted); color: var(--text-quiet); }

.shot .summary { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: var(--r-large); overflow: hidden; }
.shot .summary li {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 10px 16px; background: var(--panel); font-size: 14px; font-weight: 500;
  border-bottom: 1px solid var(--line);
}
.shot .summary li:last-child { border-bottom: 0; }
.shot .summary li.done { color: var(--text-quiet); }
.shot .summary li.done span:last-child { color: var(--success); font-weight: 600; }
.shot .summary li.selhala span:last-child { color: var(--danger); }

/* The wizard's download listing: a read-only row that is one line or two, and
   neither of the two things it resembles.

   Not `.summary`, which is one line and cannot hold a sentence. Not
   `.component`, which is a control — a checkbox, a hit area, a pointer — and
   nothing on this screen can be ticked. So the container is `.summary`'s, line
   for line, and only the row differs: a two-column grid instead of a flex pair,
   because the name and its sentence are one block against the size rather than
   two things at opposite ends.

   `align-items: center` and no margins anywhere: the size sits against the whole
   row in both views, which is the only way it stays right when the row grows a
   second line. */
/* `.download-list` stood here and went with the markup that used it: the
   wizard's own list is `.components` now, the one the module listing draws.
   Two lists for the same five rows meant a component was `hotovo` in a dialog
   and a tick on a page, and only one of those says the state at a glance. */

/* The switch above it, at the right-hand end where the archive's own sits.
   10 px is the gap `.detected` took to what followed it on this screen, and it
   is now the only rule placing it: `.manual > div { margin-bottom: 16px }` used
   to catch the switch as one of the by-hand list's children, and the override
   that beat it went with it when the group spacing moved above the headings. */
.shot .download-view { display: flex; justify-content: flex-end; margin-bottom: 10px; }
/* Icon-only, so the button is square. 30 px in the 36 px track is the plain
   segmented control's own geometry — the archive's 32-in-40 is matched to the
   filter select standing beside it there and has no counterpart on either of
   these screens, so the shape is carried over and the measurement is not. */
.shot .listing-view-toggle button { width: 30px; min-height: 30px; padding: 0; }
.shot .listing-view-toggle button svg { flex: 0 0 auto; }

.shot .progress-large { margin-bottom: 20px; }
.shot .progress-large .progress-bar { height: 6px; }
.shot .progress-large .progress-label { font-size: 13px; margin-top: 7px; }

/* 20 px under the whole list, which is what the last group's own 16 plus this
   4 used to come to. The block spacing moved above the headings and the outer
   distance did not change with it. */
.shot .manual { margin-bottom: 20px; }
/* **The space belongs above a heading, not below it.** A heading names what
   follows, so the gap that separates it from the group before is what makes the
   listing read as sections; a gap underneath detaches it from the rows it
   names. It was 16 px between blocks against 8 under the heading — close enough
   that a heading floated between two groups and belonged to neither.
   24 px is the amount this stylesheet already separates the bands inside a card
   by; 16 is what it puts between cards, and a group boundary inside one listing
   is the larger of those two kinds of gap. It is more than half a row's height
   in either listing view, so the boundary reads whether the rows are one line
   or two.
   `+` and not a plain rule: the first group must sit against the view switch at
   the 10 px that control keeps, or the list opens with a hole. */
.shot .manual-group + .manual-group { margin-top: 24px; }
/* Only the gap under the label. The type comes from `.wizard h2` — see the
   note there; this rule used to restate it and beat it on file order. */
.shot .manual h2,
.shot .manual [data-h="2"] { margin: 0 0 8px; }

/* The by-hand list. The same row as `.download-list` above, plus the two things
   this one has: a mark that is also the action, and a bin.

   **One ground for the whole list.** The group sat in a container while each row
   carried its own shade, so the rows and the thing holding them competed —
   *tady to pozadí u modelu je špatně*. Installed rows had been the grey ones,
   which put the most contrast on what needs the least attention. The container
   draws the border and every row inside it is `--panel`; what is installed is
   said by the green mark, which is the column that exists to say it. */
/* The dictionary's saved entries join these four rules rather than copying
   their values — *použijme na slovník stejné řádky jako mají položky v kartě
   modely*. It is a list of items that grows, with a bin at the right, which is
   this row's geometry and not the two-fact summary panel's. Its own file is
   `05-later.css`, which only sets what genuinely differs: the columns, because
   a dictionary row carries a pair of fields and an arrow where a component row
   carries a mark, a name and a size. Everything else — the ground, the
   hairlines, the 44 px, the 16 px edge, the bin — is one declaration for both,
   so neither can drift. */
.shot .components,
.shot .dictionary-list {
  list-style: none; margin: 0; padding: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-large);
  background: var(--panel);
}
.shot .component,
.shot .dictionary-list li { border-bottom: 1px solid var(--line); }
.shot .component:last-child,
.shot .dictionary-list li:last-child { border-bottom: 0; }
/* Four columns, each fixed or shared so the eye runs down any of them: the mark,
   the text, a size column with one right edge, and the bin — whose 26 px is
   reserved on every row whether or not there is a bin in it. */
.shot .component-row,
.shot .dictionary-list li {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) auto 26px; gap: 12px;
  align-items: center; min-height: 44px; padding: 9px 16px;
  font-size: 14px;
}
/* The mark, at the bin's diameter so the row is bracketed by two circles of one
   size. Three states share it and none of them changes its geometry, which is
   what keeps the text edge still as a row goes from offer to progress to fact. */
/* No `position: relative` any more: it was here for the punched-out middle of
   the old conic ring, and the ring the mini player lends is an SVG in the same
   grid cell as the glyph. */
/* The wizard's copy of this listing has no bin, so it has three columns rather
   than four. On the Settings page the fourth is reserved on every row whether or
   not that row can be removed — that is what keeps the bins on one axis down a
   long table. In a first-run dialog nothing can be removed at all, and the
   reserved column plus its gap were 38 px of nothing between the size and the
   right edge. */
.shot .setup-list .component-row { grid-template-columns: 26px minmax(0, 1fr) auto; }

.shot .component-mark {
  display: grid; place-items: center; width: 26px; height: 26px; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .component-mark.static { cursor: default; }
/* Not here yet: the accent, because fetching it is the ordinary thing to do on
   this screen and the accent is what this application uses for the ordinary
   thing to do. */
.shot .component-mark.get { background: var(--accent-light); color: var(--accent); }
.shot .component-mark.get:hover { background: var(--accent); color: #fff; }
/* Here: the grass tint the tick has worn since it was `.downloaded-mark`. */
.shot .component-mark.have { background: hsl(var(--ds-grass) / 0.14); color: var(--success-text); }
.shot .component-mark.have:hover:not(.static) { background: var(--accent-light); color: var(--accent); }
/* The tick gives way to the turning arrow under the pointer. Both are drawn and
   one is hidden, rather than swapped in JavaScript: a mark that re-renders on
   hover flickers, and the pair has to cross-fade to read as one control
   changing rather than two controls trading places. */
.shot .component-mark .mark-hover { display: none; }
.shot .component-mark.have:hover:not(.static) .mark-rest { display: none; }
.shot .component-mark.have:hover:not(.static) .mark-hover { display: block; }
/* Running: the ring fills as the bytes arrive, and the glyph is a stop square.
   A ring rather than a spinner, because the number is known — a spinner would
   say *something is happening* where the row can say how much.

   **The ring is the mini player's**, weight and technique both: two SVG circles
   with a 2 px stroke, `.mini-track` and `.mini-progress` reused rather than
   copied, and the arc shortened with `stroke-dashoffset`. It was a 3 px conic
   band running edge to edge with the middle punched out — *moc silný* — and the
   answer to how thin came from the owner as *stejně, jako je v mini playeru*:
   the application already had a ring for how far through something is, and a
   second weight would have been two languages for one idea. The disc stays
   `--accent-light` under it, so the running row is still one of the three
   filled 26 px circles this column is built from. */
.shot .component-mark.running { background: var(--accent-light); color: var(--accent); }
/* Both children in the same grid cell, the way `.mini-play` stacks its own ring
   and glyph — without it the grid would give them a row each and the circle
   would grow. The quarter turn starts the arc at twelve o'clock. */
.shot .component-mark > svg { grid-area: 1 / 1; }
.shot .component-ring { transform: rotate(-90deg); }
.shot .component-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* The name leads and the sentence supports it. Both sat within a point of each
   other, so fifteen rows of two lines had no first thing to read. */
.shot .component-name {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-weight: 600;
}
.shot .component-text .small-text { line-height: 1.45; }
/* A column, rather than a value that happens to sit on the right. */
.shot .component-size {
  min-width: 58px; text-align: right;
  color: var(--text-quiet); font-size: 13px; font-weight: 500;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* The bin, drawn always: hover-to-reveal is undiscoverable for a destructive
   action, and revealing it moved the size column as the pointer travelled.
   Quiet by default, the danger colour under the pointer where the intent is
   already formed. */
/* 26 px, and the dictionary's bin is the same button: it stood at 28 because
   it ended a 34 px field row, and it does not end one any more — it ends a
   44 px listing row, which is where 26 comes from. Two bins one row apart at
   two diameters is the drift this selector exists to prevent. */
.shot .component-remove,
.shot .dictionary-remove {
  display: grid; place-items: center; width: 26px; height: 26px; padding: 0;
  border: 0; border-radius: var(--r-card); background: none; cursor: pointer;
  color: var(--text-quiet);
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .component-remove:hover,
.shot .dictionary-remove:hover { background: var(--danger-light); color: var(--danger); }
/* The lock, in the bin's own column and at the bin's own 26 px, so a row that
   cannot be deleted lines up with the rows that can. Quiet rather than red: it
   states a fact, it does not warn. No hover and no pointer — it is not a
   button, and anything suggesting it could be pressed would be a lie; the
   tooltip on it is the whole of what it has to offer. */
.shot .component-lock {
  display: grid; place-items: center; width: 26px; height: 26px;
  color: var(--text-quiet);
}
/* Still needed: a row with nothing installed has neither a bin nor a lock, and
   the column has to keep its width or the size column moves. */
.shot .component-remove-space { display: block; width: 26px; }

/* ---------------------------------------------------------------- dialog */

.shot .dialog-overlay {
  position: fixed; inset: 0; z-index: 60;
  /* A slight blur under the veil. The scrim alone darkens the window and leaves
     every edge behind the dialog as sharp as the dialog's own, so two planes
     compete for the same focus; taking the detail out of the far one is what
     makes it read as far.

     3 px, which is the amount the archive's own dissolve uses against its
     sticky header — the same effect at the same strength rather than a second
     number for the same idea. This is a `position: fixed` element covering the
     whole window, so it has none of the compositing trouble that rule carries
     a warning about: nothing here moves under it. */
  backdrop-filter: blur(3px);
  /* A veil is dark in both palettes. It used to be `--ds-foreground / 0.35`,
     which is the *text* colour — dark on the light palette, and near-white on
     the dark one, where it lightened the page by 35 % behind a dialog that is
     darker than its own scrim. The depth cue inverted: the dialog read as a
     hole punched under a haze. */
  background: var(--scrim);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  animation: veil-settles var(--hover-duration) var(--hover-ease);
}
/* **The veil arrives, it does not appear.** Blur at full strength on the first
   frame is a cut, and a cut behind a dialog that eases in reads as two events
   — *ať to není jen skok, ať ten blur zesílí*.

   `from` only, with no `to`: the end of the animation is whatever the rule
   itself sets, so the radius and the scrim are written once. Two rules holding
   one value is the thing this file keeps warning about, and here it would be a
   blur that ramps to a number the veil no longer uses.

   `--hover-duration`, 0.22s, which is already this application's length for
   *something changes while you are looking at it*. Longer than the dialog's own
   0.14 s on purpose: an opacity fade is read from its endpoints, a blur is read
   from the radii in between, so the same duration would leave the veil looking
   instant beside a dialog that visibly moves.

   `background-color` rather than the `background` shorthand — a shorthand is
   not interpolated. The drag overlay has no background at all, so for it this
   half of the keyframe is transparent to transparent and only the blur ramps,
   which is why one keyframe serves both. */
@keyframes veil-settles {
  /* 0.02px, not 0. `blur(0)` is minified to `blur()` — legal by the letter of
     the filter-effects grammar, where the length is optional, and exactly the
     kind of bet this evening was spent losing: a stylesheet shipping something
     an engine may or may not accept, with nothing between the source and the
     screen to notice. A hundredth of a pixel is invisible, means the same
     thing, and no minifier touches it. */
  from { backdrop-filter: blur(0.02px); background-color: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .shot .dialog-overlay,
.shot .drag-overlay { animation: none; }}
/* The dialog owns its size, and the last of those numbers is the one that says
   so: `calc(100vh - 48px)` is the window less the overlay's own 24 px on each
   side, so no dialog can reach past the screen it is centred on whatever ends
   up inside it. It scrolls instead — which puts the footer at the end of a
   scroll rather than under the bottom edge, where it cannot be pressed at all.
   The reading window sets its own height above this and never uses the scroll;
   it is the ceiling for everything else. */
.shot .dialog {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-large); box-shadow: var(--shadow-high);
  padding: 24px 26px; width: 100%; max-width: 420px;
  max-height: calc(100vh - 48px); overflow-y: auto;
  animation: dialog-nabehne 0.14s ease-out;
}
@keyframes dialog-nabehne {
  from { opacity: 0; transform: translateY(6px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
/* One rhythm for every dialog, owned by the dialog rather than by whatever
   happens to sit inside it: 19 px heading, 8 px to its sentence, 18 px from
   there to the content, whatever the content is. Content blocks therefore carry
   no margin-top of their own: they used to, each with its own number
   (18/18/18/20/16), and the two dialogs that had none left their first label
   2 px under the sentence.

   **It was 4 px until 2026-08-16**, and the reason given for it was that the
   sentence should read as the heading's own. That is exactly what was wrong
   with it: at four the sentence reads as the heading's *second line*, one
   block in two sizes. Eight makes them two things — a name and an explanation
   — which is what they are. Judged at the screen on two dialogs at once, a
   long sentence over a confirmation and a one-liner over a field, because the
   number is one rule for every dialog in the application and cannot be settled
   on a single sample.

   Descendant, not child: in the online view of Přidat nahrávku the heading and
   the sentence sit inside a <form>, so `.dialog > h2 + p` would miss them. */
.shot .dialog h2,
.shot .dialog [data-h="2"] {
  font-size: 19px; font-weight: 700; margin: 0 0 8px;
  letter-spacing: -0.015em; text-transform: none; color: var(--text);
  line-height: 1.25;
}
.shot .dialog p { margin: 0; color: var(--text-quiet); line-height: 1.5; }
.shot .dialog h2 + p,
.shot .dialog [data-h="2"] + p { margin-bottom: 18px; }
/* A field is the dialog's last block; the footer owns the space above it. */
.shot .dialog .field { margin-bottom: 0; }
.shot .dialog-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 22px; }
/* A note carrying the dialog's opening sentence, in place of the plain `<p>`
   the family's rhythm was written for. `.dialog h2 + p` owns the 18 px down to
   the content; the note has to own the same, or the first row of a list sits
   against the sentence explaining it — which is what the save dialogs did.

   Eight above it when it follows the sentence rather than replacing it: the
   two lines are one introduction and read as a pair, not as two blocks. */
.shot .dialog > .settings-info-note { margin-bottom: 18px; }
.shot .dialog h2 + .settings-info-note,
.shot .dialog [data-h="2"] + .settings-info-note { margin-top: 0; }
.shot .dialog p + .settings-info-note { margin-top: 8px; }
/* And where the note follows the sentence, the sentence gives up its own gap:
   the two are one introduction, and 18 between them would read as two. */
.shot .dialog h2 + p:has(+ .settings-info-note),
.shot .dialog [data-h="2"] + p:has(+ .settings-info-note) { margin-bottom: 0; }
/* What is in an update, in the dialog that offers it. No margin of its own:
   `.dialog h2 + p` owns the gap above, like every other block in a dialog.
   240 px is about eight items — beyond that the box scrolls, so a talkative
   release cannot push the footer off the bottom of a laptop screen. */
/* This dialog's lead line gets 24 px under it rather than the family's 18. What
   follows it is a column of marks, not a paragraph, and at 18 the first tick
   sat close enough to the sentence to read as part of it. The dialog still owns
   the gap, as every dialog does — the list carries no margin of its own. */
.shot .release-notes-dialog h2 + p,
.shot .release-notes-dialog [data-h="2"] + p { margin-bottom: 24px; }
/* The gap between blocks belongs to the column, not to margins on what is in
   it. `.release-notes ul` sets `margin: 0` and outranks any `* + *` rule, so a
   margin-top here was overruled and the introducing sentence sat on the first
   tick however large the number was.

   14 px, which is what this application already gives an explanation belonging
   to a whole group rather than to the line under it — the same relationship a
   sentence has to the list it introduces. 22, the dialog's footer gap, was
   tried and read as two separate blocks. */
.shot .release-notes {
  max-height: 240px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 14px;
}
/* No bullets and no left inset: each line carries its own mark, and the marks
   are what the column lines up on. 8 px between rows rather than 6 — the mark
   is taller than the type it sits beside, so the rows need the room. */
.shot .release-notes ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
.shot .release-notes li { display: flex; align-items: center; gap: 9px; }
/* The tick from `.check-mark`, at 16 px. There it marks a row of a checklist
   40 px tall; here it stands beside one line of type, and 22 would be a badge
   rather than a mark. Same grass tint, same text colour. */
.shot .release-notes-mark {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 16px; height: 16px; border-radius: 50%;
  background: hsl(var(--ds-grass) / 0.14); color: var(--success-text);
}
/* Full-strength text, unlike `.dialog p`: the sentence above is the framing and
   may be quiet, but this is the thing the reader opened the dialog to read. */
.shot .release-notes li,
.shot .release-notes p { color: var(--text); font-size: 14px; line-height: 1.5; }
/* Two widths: 420 px for a question with a field, 520 px for a column of
   choice cards. 520 is the wider of the two former numbers because the cards
   in the AI dialog carry a badge in a third column (.choice.with-icon > .badge),
   and the narrower dialog took that width out of their descriptions.

   The first-run wizard is the third of them and takes the same 520 for the same
   reason: two stacked cards, each holding a name, a sentence about the model, a
   line of cost and — on one of them — the reason it is recommended here. It was
   a 620 px screen, and the 100 px it gives up is width those cards never used:
   they were a column already, but each was one line of text with a size pinned
   at the far edge, so the extra width went to the gap between them. */
.shot .ai-edit-dialog,
.shot .add-recording-dialog { max-width: 520px; }
/* 560, not the 520 the other two take. That number is for a *column* of choice
   cards; two of them side by side is a different shape of content, and at 520 a
   card comes out 230 px wide — the width at which the text just fits, which is
   no width at all: the first longer figure wraps one card and not the other. */
.shot .setup-dialog { max-width: 560px; }
/* The failure banner, when the first-run dialog is drawing one. It stands
   between the dialog's sentence and its content, and it owns the 18 px below
   itself rather than above: `.dialog h2 + p` has already placed the gap above
   it, and no content block in a dialog carries a `margin-top`. 18, not the 20
   `.warning` gives itself in the archive, because in here the number belongs to
   the dialog's rhythm and not to the banner. */
.shot .setup-dialog > .setup-notice { margin-bottom: 18px; }
.shot .ai-edit-note {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 14px !important;
}
/* The instruction field stands between the cards and that note, so it takes
   the same 14 px the note takes above itself, and gives up the 24 px a field
   carries in Settings — the note below brings its own. Adjacency, like
   `.choices + .settings-info-note.compact`: a dialog's content blocks do not
   carry a margin of their own, they say what follows what. */
.shot .ai-edit-dialog .choices + .field { margin: 14px 0 0; }
.shot .ai-edit-note-icon { flex: 0 0 auto; margin-top: 1px; color: var(--accent); }

.shot .add-recording-options .small-text { margin: 0; }
.shot .add-recording-back {
  display: inline-flex; align-items: center; gap: 6px;
  margin: -4px 0 14px; padding: 4px 0;
  border: 0; background: none; color: var(--text-quiet);
  font-size: 13px; font-weight: 600; cursor: pointer;
}
.shot .add-recording-back:hover { color: var(--text); }
.shot .add-recording-back:disabled { opacity: 0.45; cursor: default; }
.shot .add-recording-url { margin: 0; }
.shot .field.add-recording-url input:disabled { opacity: 0.65; }
/* ------------------------------------------------- microphone recording */
/* One surface holds the whole state of the take: the capsule icon, what is
   happening, the clock, and a live level bar. The bar is the honest part —
   a timer proves the application is counting, the level proves it hears. */
.shot .mic-stage {
  display: grid; grid-template-columns: auto 1fr; align-items: center;
  column-gap: 14px; row-gap: 12px;
  margin: 0; padding: 16px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--subtle);
}
.shot .mic-stage.running .mic-mark {
  background: var(--danger-light); color: var(--danger);
  animation: mic-tep 1.6s ease-in-out infinite;
}
/* In preview the capsule becomes the take's play button — the same circle,
   now an instruction instead of a picture. */
.shot button.mic-play {
  border: 0; cursor: pointer;
  background: var(--accent-light); color: var(--accent);
  transition: background var(--hover-duration) var(--hover-ease);
}
.shot button.mic-play:hover:not(:disabled) { background: hsl(var(--ds-petrol) / 0.22); }
.shot button.mic-play:disabled { cursor: default; opacity: 0.6; }
@keyframes mic-tep {
  0%, 100% { box-shadow: 0 0 0 0 hsl(var(--ds-destructive) / 0.25); }
  50% { box-shadow: 0 0 0 6px hsl(var(--ds-destructive) / 0); }
}
@media (prefers-reduced-motion: reduce) {
  .shot .mic-stage.running .mic-mark { animation: none; }}
.shot .mic-readout { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
/* One line saying what a control is doing right now. The recorder had the only
   one until the About page grew a check for updates; both say the same kind of
   thing, so they are one rule rather than two that drift apart. The About one
   is stacked under its buttons rather than sitting in a row, so it carries the
   gap the row already provides for the recorder. */
.shot .mic-status,
.shot .about-status { font-size: 13.5px; font-weight: 600; }
.shot .mic-status.failed,
.shot .about-status.failed { color: var(--danger); }
.shot .about-status { margin: 12px 0 0; }
/* `.settings section > h2 + .about-updates` stood here with 24 px, for the day
   this block followed the card's heading directly. It follows the card's
   opening sentence now, and `.settings-section-description` already ends with
   the same 24 — one amount from one declaration, rather than a rule that can
   only ever be right while nothing is said between the two. */
/* The two facts, then the errand.

   22 px under the panel and no rule with it, which is `.settings-action-row.
   spaced` exactly — its comment is written for this case: content under
   something already framed, where a hairline would draw the panel's own
   boundary a second time.

   The ⓘ line that used to stand between them is gone — it claimed the
   application only looks when asked, which the switch at the foot of this card
   makes untrue whenever it is on. Its own 22 above and 12 below went with it,
   and the panel now spaces the buttons directly at the same 22. */
.shot .about-updates > .about-panel + .about-updates-actions { margin-top: 22px; }
.shot .about-updates-actions { display: flex; gap: 10px; }
/* The explanation belongs to the whole block above it, not to one button. */
.shot .about-updates .settings-section-description { margin: 14px 0 0; }
.shot .mic-time {
  color: var(--text-quiet); font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* Under both columns: the spectrum belongs to the take, not to the icon.
   The same bars the transport pill draws, so the recorder and the player
   speak one language. Quiet while the microphone merely listens; the accent
   arrives with the recording, the same way the pill lights up on play. */
.shot .mic-spectrum-wrap { grid-column: 1 / -1; position: relative; }
/* No colour transition here, deliberately: the bars are drawn onto a canvas,
   which samples this colour only when it repaints. During a transition the
   drawing takes whatever value the colour happens to hold at that instant, and
   when the spectrum stops updating — exactly when the take ends — the canvas
   keeps that half-way colour for good. The recorder's strip therefore stayed
   accent-blue after stopping. */
.shot .mic-spectrum {
  display: block; height: 38px;
  color: hsl(var(--ds-foreground) / 0.22);
}
.shot .mic-spectrum.running { color: var(--accent); }
/* The take's timeline, laid over the spectrum. The bars are the track, so
   the control paints only the shared `.slider` thumb, vertically centred
   on the 38 px strip. */
.shot .mic-slider { position: absolute; inset: 0; height: 100%; }
.shot .mic-slider:disabled { cursor: default; }
.shot .mic-slider::-webkit-slider-runnable-track { background: none; height: 100%; }
/* The dot's centre sits exactly on the line the bars grow from, per Jakub.
   The offset is measured, not derived: Chromium places this thumb from its
   own track box: the thumb's *top* lands on the margin, so its centre is
   `margin-top + 6.5`. The bars' minimum stubs paint rows 36–37 of the 38 px
   canvas, centre 36.5 — hence 30. The first attempt read 32.5 off a
   screenshot whose blue blob included the thumb's own drop shadow, which
   pulled the reading two pixels down; the shadow is switched off while
   measuring now. Re-measure rather than re-reason if the strip changes. */
.shot .mic-slider::-webkit-slider-thumb { margin-top: 30px; }
/* Room under the strip for the dot's lower half, so the control's own box
   contains it instead of letting it hang over whatever follows. */
.shot .mic-spectrum-wrap { padding-bottom: 7px; }

/* The take minimised into the header — the mini player's sibling. Sized to
   its content rather than the player's fixed width; the live spectrum runs
   behind the text exactly as the stored envelope does next door. The dot is
   the record mark, pulsing on the same beat as the dialog's capsule. */
/* Both class names, deliberately: the pill's shared rule later in this file
   sets 290 px at the same single-class specificity, and a plain
   `.mini-recorder` loses to it by order — which is exactly what happened,
   and the recorder rode at the player's width without anyone deciding it. */
.shot .mini-player.mini-recorder { width: 200px; }
.shot .mini-recorder > .mini-waves { inset: 0 34px 0 10px; color: hsl(var(--ds-destructive) / 0.3); }
/* A waiting take: the dot stands still and dims, the bars are already flat
   (the spectrum hook zeroes while suspended). Movement means recording. */
.shot .mini-recorder.paused .mini-recorder-dot { animation: none; opacity: 0.45; }
.shot .mini-recorder-dot {
  width: 10px; height: 10px; margin-left: 12px; flex: 0 0 auto;
  border-radius: 50%; background: var(--danger);
  animation: mic-tep 1.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .shot .mini-recorder-dot { animation: none; }}

/* The one place a red dot is not a warning: it is the record mark, and it
   keeps the primary button's own surface. 10 px is the mini recorder's dot,
   so the button and the pill that appears once it is running agree. */
.shot .mic-start { gap: 9px; }
.shot .mic-start-dot {
  width: 10px; height: 10px; flex: 0 0 auto; border-radius: 50%;
  background: var(--danger);
}
.shot .mic-start:disabled .mic-start-dot { opacity: 0.5; }
/* The stop mark, answering the record dot: same 10 px and the same place, so
   the two buttons read as one pair. A rounded square rather than a circle
   because that is what a transport's stop is everywhere, and `currentColor`
   rather than red — red is destructive here, and stopping a take destroys
   nothing. 2 px of radius, enough to lose the hard corner at 10 px without
   reading as a circle. */
.shot .mic-stop-square {
  width: 10px; height: 10px; flex: 0 0 auto; border-radius: 2px;
  background: currentColor;
}

.shot .add-recording-note {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 14px !important;
}
.shot .add-recording-note svg { flex: 0 0 auto; margin-top: 1px; color: var(--accent); }
.shot .add-recording-progress { margin-top: 18px; }
.shot .add-recording-error {
  margin-top: 14px !important; padding: 10px 12px;
  border: 1px solid hsl(var(--ds-destructive) / 0.25);
  border-radius: var(--r-card); background: var(--danger-light);
  color: var(--danger) !important; font-size: 12.5px;
  white-space: pre-line; max-height: 130px; overflow-y: auto;
}

.shot .ai-preview-dialog {
  max-width: 840px; height: min(760px, calc(100vh - 48px));
  display: flex; flex-direction: column; padding: 0;
}
.shot .ai-preview-header {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 20px 26px 14px;
}
.shot .ai-preview-header h2 + p,
.shot .ai-preview-header [data-h="2"] + p { margin-bottom: 0; }
.shot .ai-preview-warning {
  margin: 0 26px 14px; padding: 10px 12px;
  border: 1px solid hsl(var(--ds-destructive) / 0.25);
  border-radius: var(--r-card); background: var(--danger-light);
  color: var(--text); font-size: 12.5px; line-height: 1.45;
}
/* Segmented controls use the same pill geometry as the rest of the app.
   The generic class is shared by document navigation and compact choices. */
.shot .segmented-control {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 3px; border-radius: var(--r-pill); background: var(--muted);
}
.shot .segmented-control button {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 30px; padding: 0 11px;
  border: 0; border-radius: var(--r-pill); background: transparent;
  color: var(--text-quiet); cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 650; white-space: nowrap;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease),
    box-shadow var(--hover-duration) var(--hover-ease);
}
.shot .segmented-control button:hover { color: var(--text); }
.shot .segmented-control button.active {
  background: var(--panel); color: var(--text); box-shadow: var(--shadow);
}

.shot .sidebar-count {
  display: inline-grid; place-items: center;
  min-width: 19px; height: 19px; padding: 0 5px;
  border-radius: var(--r-pill); background: hsl(var(--ds-foreground) / 0.08);
  /* 11 px, the badge size `.badge` already uses and the documented
     micro-label. 10.5 belonged to the archive calendar's weekday, not here. */
  color: var(--text-quiet); font-size: 11px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* Each section is a card that opens, not a row on a shared sheet. The card is
   what makes the whole width clickable without inventing anything: it has an
   edge, so there is no question where the target ends. */
.shot .sidebar .sidebar-section {
  /* `--r-large`, like every other bordered card on `--panel` in this
     application. `--r-card` is the inner radius here: menu items, list rows,
     the inset data panel. No `overflow: hidden` — the focus ring is an outer
     shadow and the toggle is flush with three of these edges, so hiding the
     overflow would clip it. The toggle rounds its own corners instead. */
  margin: 0; border: 1px solid var(--line); border-radius: var(--r-large);
  background: var(--panel);
  transition: border-color var(--hover-duration) var(--hover-ease),
    box-shadow var(--hover-duration) var(--hover-ease);
}
.shot .sidebar .sidebar-section + .sidebar-section { margin-top: 8px; }
/* Pickable, exactly as a choice card and an archive row say it — the shared
   low `--shadow` and `--line-field`. Open is not exempt: `.choice.chosen` and
   `.row.running` both keep their lift, and a state that suppressed it would
   make this the only surface in the application that does. */
.shot .sidebar .sidebar-section:hover,
.shot .sidebar .sidebar-section:focus-within {
  border-color: var(--line-field); box-shadow: var(--shadow);
}
.shot .sidebar .sidebar-section h2,
.shot .sidebar .sidebar-section [data-h="2"] { margin: 0; line-height: 1.3; }
/* The whole card width, because the action moved inside the body and nothing
   competes with it on this row any more. Every arrangement before this one was
   a way around a button that could not contain another button. */
.shot .sidebar-section-toggle {
  display: flex; align-items: center; gap: 8px; width: 100%;
  /* Square, 12 px all round. It was `8px 12px`, chosen when the rule of the
     day was that a card's horizontal inset must be at least its vertical one
     — which this satisfied and then some, at the price of a header that sat
     tight against its own top and bottom edges while the sides breathed.
     Equal is the plainer answer and the one that looks right. */
  padding: 12px;
  /* One less than the card's 16, for the 1 px border it sits inside. Squared
     off at the bottom once the body is showing. */
  border: 0; border-radius: 15px; background: transparent;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
  transition: background var(--hover-duration) var(--hover-ease);
}
.shot .sidebar-section.open .sidebar-section-toggle { border-radius: 15px 15px 0 0; }
/* `--ground`, the transcript's own ground, like the rows inside the card.
   Deliberately not `--muted`, which the icon's circle already uses: on that
   ground the circle would dissolve into its own background. */
.shot .sidebar-section-toggle:hover { background: var(--ground); }
/* The mark leads the words under the pointer, having been quieter at rest. */
.shot .sidebar-section-toggle:hover .sidebar-section-icon { color: var(--text); }
/* A circle, like every other leading icon in this application — module tiles,
   choice cards, the folder card, the About rows. There is no bare one. It was
   tried and it was wrong twice over: the header kept its height while losing
   its object, so five identical text lines behind hairlines read as one grey
   slab; and `Library.tsx` reuses this rule for the archive's folder heading,
   so a second screen changed with it.
   30/17 rather than the 38/22 of the larger surfaces — the reduced variant the
   About rows already use for exactly this reason. */
.shot .sidebar-section-icon {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--muted); color: var(--text-quiet);
  transition: color var(--hover-duration) var(--hover-ease);
}
/* Takes the free width, so the arrow is pushed to the end of the row and the
   count sits just before it. Never gives way to an ellipsis: every heading
   here is one short word and it is the only thing the row has to say. */
.shot .sidebar-section-title { flex: 1 1 auto; }
.shot .sidebar-section-chevron {
  flex: 0 0 auto; color: var(--text-quiet);
  transition: transform var(--hover-duration) var(--hover-ease);
}
.shot .sidebar-section.open .sidebar-section-chevron { transform: rotate(180deg); }
/* The card owns the gap under its heading, so every kind of body starts on the
   same line. Left to themselves the five sections began at 2, 2, 4 and 12 px —
   four rhythms under five identical headers, because each list carried a top
   margin of its own. Those are zeroed; this is the one number. */
.shot .sidebar-section-body {
  /* 12 at the sides and the bottom, with the header above it. 6 at the top
     rather than the old 10: the header's own bottom inset grew from 8 to 12,
     and the two together are what separates its text from the body — 18 px,
     which is what it was and what it should stay. */
  min-width: 0; padding: 6px 12px 12px;
  animation: sidebar-section-open 180ms var(--hover-ease);
}
.shot .sidebar-section-body > :first-child { margin-top: 0; }
/* And the same number again where an explanation is followed by the list it
   explains — the only card that has both. Left flush, the first row's
   highlight began exactly where the sentence ended, so the gap above the body
   and the gap inside it disagreed by ten pixels in a card small enough to see
   both at once. One number, used twice. */
.shot .sidebar-section-body > .sidebar-empty + * { margin-top: 10px; }
/* Bottom right of the card, under what it acts on. */
.shot .sidebar-section-action { display: flex; justify-content: flex-end; margin-top: 10px; }
@keyframes sidebar-section-open {
  from { opacity: 0; transform: translateY(-3px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .shot .sidebar-section-body { animation: none; }
  .shot .sidebar-section-chevron { transition: none; }}
.shot .sidebar-text-action {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 0 auto; min-height: 30px; padding: 0 9px;
  border: 0; border-radius: var(--r-pill); background: transparent;
  color: var(--accent); cursor: pointer; font: inherit;
  font-size: 12.5px; font-weight: 650;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .sidebar-text-action:hover { background: var(--accent-light); }
.shot .sidebar-text-action:disabled { color: var(--text-quiet); cursor: default; opacity: 0.65; }
.shot .sidebar-text-action:disabled:hover { background: transparent; }
.shot .sidebar-empty {
  margin: 0; color: var(--text-quiet); font-size: 13px; line-height: 1.45;
}

/* Notes are cards, not table rows. The four-column grid they used to sit in
   left the text about 190 px, so anything longer than a phrase wrapped five
   times. A closed card gives the text the whole panel and clamps it; opening
   one turns it into an editor. */
.shot .stickies {
  display: flex; flex-direction: column; gap: 8px;
  margin: 0; padding: 0; list-style: none;
}
.shot .sticky {
  padding: 11px 12px; border-radius: var(--r-large);
  background: var(--note); border: 1px solid transparent;
  transition: border-color var(--hover-duration) var(--hover-ease);
}
.shot .sticky:hover { border-color: var(--note-line); }
.shot .sticky.open { border-color: var(--accent-line); }
.shot .sticky-body { min-width: 0; display: flex; flex-direction: column; gap: 8px; }
/* The note keeps its paper colour while being written; only the writing
   area lightens, the way a pad does. */
/* The closed note is one big target, so it is a button; the text inside keeps
   the paragraph's own left alignment and wrapping. */
.shot .sticky-text {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; width: 100%; margin: 0; padding: 0;
  border: 0; background: transparent; cursor: pointer;
  color: var(--text); font: inherit; font-size: 13.5px; line-height: 1.45;
  text-align: left; white-space: pre-wrap; overflow-wrap: anywhere;
  /* The same three lines the clamp shows, said in a length so it can be eased
     to and from. Keep the two in step: 3 × the line height above. */
  max-height: calc(3 * 1.45em);
  /* Closing. The clamp is discrete, so it is held until the height has
     finished shrinking; without `allow-discrete` it snaps back instead, which
     is what an older WebView will do. */
  transition: max-height 190ms var(--hover-ease), -webkit-line-clamp 0s 190ms;
  transition-behavior: allow-discrete;
}
/* Reading a long note should not cost a click.
   Pointing at a clamped note unrolls it; opening it is still a click and still
   means editing. Keyboard focus does the same, so the note is readable without
   a mouse. A note that already fits does not move, because its measured height
   is the height it already has.
   The short delay is deliberate: sweeping the pointer down the list on the way
   somewhere else must not set every note in it moving. */
.shot .sticky:hover .sticky-text,
.shot .sticky:focus-within .sticky-text {
  -webkit-line-clamp: unset;
  max-height: var(--sticky-full, 40em);
  transition: max-height 220ms var(--hover-ease) 90ms, -webkit-line-clamp 0s 90ms;
  /* The shorthand resets it, and it is wanted in this direction too: the
     ellipsis should go when the note starts moving, not before. */
  transition-behavior: allow-discrete;
}
@media (prefers-reduced-motion: reduce) {
  .shot .sticky-text,
.shot .sticky:hover .sticky-text,
.shot .sticky:focus-within .sticky-text { transition: none; }}
.shot .sticky-editor {
  width: 100%; min-width: 0; padding: 7px 8px;
  border: 1px solid var(--note-line); border-radius: var(--r-small);
  outline: 0; resize: none; overflow: hidden; field-sizing: content;
  background: var(--note-field); color: var(--text); font: inherit;
  font-size: 13.5px; line-height: 1.45;
}
.shot .sticky-editor::placeholder { color: var(--text-quiet); }
.shot .sticky-editor:focus { border-color: var(--accent); box-shadow: var(--ring-field); }
.shot .sticky-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.shot .sticky-time,
.shot .sticky-pin,
.shot .sticky-quiet,
.shot .sticky-confirm,
.shot .sticky-danger {
  border: 0; border-radius: var(--r-pill); cursor: pointer;
  font: inherit; font-size: 11.5px; font-weight: 700;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .sticky-time {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 9px 4px 8px;
  background: var(--accent-light); color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.shot .sticky-time:disabled { cursor: default; }
.shot .sticky-pin,
.shot .sticky-quiet,
.shot .sticky-danger {
  padding: 4px 8px; background: transparent; color: var(--text-quiet);
  font-weight: 650;
}
.shot .sticky-pin:hover:not(:disabled),
.shot .sticky-quiet:hover { background: hsl(var(--ds-foreground) / 0.08); color: var(--text); }
.shot .sticky-pin:disabled { opacity: 0.45; cursor: default; }
.shot .sticky-danger { margin-left: auto; }
.shot .sticky-danger:hover { background: var(--danger-light); color: var(--danger); }
.shot .sticky-confirm { margin-left: auto; padding: 4px 11px; background: var(--accent); color: white; }
.shot .sticky-confirm:disabled { opacity: 0.4; cursor: default; }
.shot .sticky-time-input {
  width: calc(6ch + 12px); height: 25px; margin: 0; padding: 3px 5px;
  border: 1px solid var(--note-line); border-radius: var(--r-small); outline: 0;
  background: var(--note-field); color: var(--text); font: inherit;
  font-size: 11.5px; font-weight: 650; text-align: center;
  font-variant-numeric: tabular-nums;
}
.shot .sticky-time-input:focus { border-color: var(--accent); box-shadow: var(--ring-field); }
.shot .sticky-time-input[aria-invalid="true"] {
  border-color: hsl(var(--ds-destructive) / 0.45);
  background: var(--danger-light); color: var(--danger);
}
.shot .speaker-count-choices {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 0 14px;
}
.shot .speaker-count-choices .button { min-width: 46px; justify-content: center; }
/* 34 px and the button surface are not a third field size: this input stands
   in one flex row with four .button and behaves as the fifth choice. */
.shot .speaker-count-input {
  width: 84px; height: 34px; padding: 0 10px;
  border: 1px solid var(--line-field); border-radius: var(--r-pill);
  background: var(--panel); color: var(--text); font: inherit;
  font-size: 13px; font-weight: 600; text-align: center;
  /* No spinner. The browser puts a pair of arrows inside every number field,
     and they are for walking a value one step at a time — here the reader
     already has 1 to 4 as buttons beside this and types the exception. Two
     arrows in a pill 84 px wide are furniture that does nothing. */
  appearance: textfield; -moz-appearance: textfield;
}
.shot .speaker-count-input::-webkit-outer-spin-button,
.shot .speaker-count-input::-webkit-inner-spin-button {
  appearance: none; -webkit-appearance: none; margin: 0;
}
.shot .speaker-count-input:focus {
  outline: 0; border-color: var(--accent); box-shadow: var(--ring-field);
}
/* Which of the three documents to read. Stays on the dialog's white with the
   heading it belongs to, on the 26 px inset every dialog shares. */
.shot .ai-document-tabs {
  display: flex; gap: 4px; flex: 0 0 auto;
  padding: 0 26px 14px; overflow-x: auto;
}
.shot .ai-output-options { display: flex; gap: 4px; flex: 0 0 auto; }
.shot .ai-document-tabs button svg {
  width: 15px; height: 15px; flex: 0 0 auto;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
/* The sparkle is a filled shape, the other three are drawn with a line. It
   used to be selected as `button:first-child`, which stopped being true when
   the tab row began leaving the improved transcript out; the icon says which
   it is (`DocumentViewIcon`). */
.shot .ai-document-tabs button svg.solid { fill: currentColor; stroke: none; }
/* This row belongs to the document, not to the dialog: it chooses a version,
   a length, a language — of the thing being read, not which thing to read.
   So it lives on the grey with the document and carries the edge between the
   two. The tabs above it choose the view and stay with the heading. */
/* 8 px under the controls where there were none, so the first line of the
   document clears them by 24 rather than by the 16 its own padding gave. The
   row is a set of pills on the same grey as the text, with nothing between
   them, and at 16 the reading started against the buttons. */
.shot .ai-output-toolbar {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  padding: 14px 26px 8px; background: var(--ground);
  border-top: 1px solid var(--line);
}
/* The document dissolves into the controls above it rather than sliding under
   a hard edge — the archive's own fade under its sticky header, with the same
   two layers and the same amounts: a short strong blur against the block and a
   longer weak one below it, each masked to nothing at its far end so neither
   has a boundary of its own. One drawing for scrolled-away content, wherever
   the application shows it.

   Only while something is actually passing underneath: at the top of a document
   there is nothing to dissolve, and a permanent strip would sit blurring the
   first line of every one of them. */
.shot .ai-output-toolbar::before,
.shot .ai-output-toolbar::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%;
  pointer-events: none;
  opacity: 0; transition: opacity 0.16s ease;
}
.shot .ai-preview-dialog.scrolled .ai-output-toolbar::before,
.shot .ai-preview-dialog.scrolled .ai-output-toolbar::after { opacity: 1; }
.shot .ai-output-toolbar::before {
  height: 42px;
  backdrop-filter: blur(3px);
  background: linear-gradient(to bottom, var(--ground) 0%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 15%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 15%, transparent 100%);
}
.shot .ai-output-toolbar::after {
  height: 18px;
  backdrop-filter: blur(9px);
  mask-image: linear-gradient(to bottom, #000, transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent);
}
.shot .ai-output-toolbar .select { width: 190px; }
/* The instruction stands in that same strip, where the other tabs keep their
   length and their language: it is what this document was asked for. A column
   rather than a row, because it is a field with a label and a sentence under
   it, so the strip closes its own 14 px at the bottom instead of leaving that
   to the text below. */
.shot .ai-custom-bar { display: block; padding-bottom: 14px; }
.shot .ai-custom-bar .field { margin-bottom: 0; }
/* 36, the height of the tabs beside it — this strip held three different
   heights until every control in it became the same one. */
.shot .ai-output-toolbar .select-trigger { height: 36px; background: var(--panel); font-size: 13px; }
.shot .ai-preview-text {
  flex: 1 1 auto; min-height: 0; overflow: auto;
  /* No border of its own: the row above it is already on this grey and carries
     the edge. 26 on the sides, the dialog's own line. */
  padding: 16px 26px 24px; background: var(--ground);
  font-family: var(--font-body); font-size: var(--text-size);
  line-height: var(--line-height); white-space: pre-wrap;
}
.shot .ai-preview-text p { margin: 0 0 1.1em; }
.shot .ai-preview-empty {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 40px 24px; text-align: center; background: var(--ground);
}
.shot .ai-preview-empty-icon {
  display: grid; place-items: center; width: 44px; height: 44px;
  margin-bottom: 14px; border-radius: 50%;
  background: var(--accent-light); color: var(--accent);
}
.shot .ai-preview-empty-icon svg {
  width: 21px; height: 21px; fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
.shot .ai-preview-empty h3,
.shot .ai-preview-empty [data-h="3"] { margin: 0 0 6px; font-size: 16px; }
.shot .ai-preview-empty p { max-width: 440px; margin: 0 0 18px; color: var(--text-quiet); }
.shot .ai-preview-actions {
  flex: 0 0 auto; margin: 0; padding: 14px 22px;
  border-top: 1px solid var(--line);
}
.shot .ai-preview-actions .button.quiet:first-child { margin-right: auto; }

.shot .button.destructive {
  background: var(--danger-solid); border-color: var(--danger-solid); color: #fff;
}
.shot .button.destructive:hover { filter: brightness(1.08); background: var(--danger-solid); }

/* ---------------------------------------------------------------- drag and drop */

/* **Only blurred, and over everything — the same as a dialog.**

   It used to tint the window as well, `--ds-petrol / 0.12`, and the tint was
   the fault: a colour laid over everything says *this whole window is now a
   state*, when what is true is that the archive under the pointer will take the
   file. Blur alone says it, and says it about the content rather than about the
   application.

   It covered everything, then briefly stopped below the header — on the
   argument that a dialog is modal and a drag is not, so the header stays
   available. **That is a distinction a designer knows and a reader cannot
   see**: both are something lying over the window, and answering the same
   situation two ways is not a nuance. So the header blurs here too.

   3 px, which is `.dialog-overlay`'s. Two amounts for one idea is the thing
   this file keeps warning about; what tells the two states apart is what stands
   on the veil — a dialog, or the panel that says the file will land. */
.shot .drag-overlay {
  position: fixed; inset: 0; z-index: 50;
  backdrop-filter: blur(3px);
  /* **No background, and that stands.** A `--scrim` was put here for an hour on
     17 August, as a floor for machines that cannot blur — written while the
     packaged application appeared unable to blur anything, which turned out to
     be a stylesheet shipping a property Blink does not implement. There was no
     such machine. The premise went and the veil went with it, because blur
     alone is what was chosen, on the argument that a colour laid over
     everything says *the window is a state* where the blur says it about the
     content.
     If a reader ever turns up on software rendering, this is one line back. */
  display: flex; align-items: center; justify-content: center; pointer-events: none;
  /* The dialog's arrival, shared. See `veil-settles`. Here it is the blur
     alone, and it is the whole of what the window does when a file comes over
     it — so a cut is even more of a jolt than it is behind a dialog. */
  animation: veil-settles var(--hover-duration) var(--hover-ease);
}
/* No dashed border on the panel. The drop zone below it is dashed because a
   dashed frame is what says *put something here* about an empty area waiting;
   this panel is not waiting, it is the answer to something already happening,
   and it is the only sharp thing on a blurred window — which is more than
   enough to be seen. */
.shot .overlay-content {
  background: var(--panel); border: 1px solid var(--line);
  /* 22 px: the drop zone's amount, because it is the same panel at the same
     moment, only answering rather than inviting. */
  border-radius: 22px; padding: 40px 60px; text-align: center;
  box-shadow: var(--shadow-high);
}
.shot .overlay-icon { font-size: 34px; color: var(--accent); line-height: 1; margin-bottom: 8px; }
.shot .overlay-content p { margin: 0; font-size: 17px; font-weight: 600; }

/* The panel always stays a right-hand column, at one width. It used to move
   below the text in a narrow window, which meant speakers and notes suddenly
   vanished somewhere below; then it narrowed instead, which is what broke it —
   see the note on `.detail-body`. It keeps its 320 px now, and if that is too
   much on a small screen the user closes it, which is one click and has been
   there all along.
   Only the padding still gives way, and only a little. */
@media (max-width: 1180px) {
  .shot .sidebar { padding: 16px 10px 60px; }}





/* `.diagnostics-actions` stood here — `Zkopírovat údaje` and `Otevřít log`
   side by side at the foot of `Technické podrobnosti`, and gone with that block.
   Its 8 px gap is still quoted by `.settings-action-row-buttons` above as the
   other place two buttons stand together; that reference is now to a rule that
   was, which is why this note keeps the number. */
/* And a button standing on its own inside a folded block — `Zpět na výchozí`
   at the foot of `Pokročilé` — for exactly the same reason as the row above.
   The disclosure is a flex column, so left alone it stretches to the full width
   of the card, which no other button in Settings does. It stretched before this
   rule existed, when it was one short block's own reset; at the foot of a block
   this long it is the last thing the eye lands on. */
.shot .settings-disclosure-content > .button { align-self: flex-start; }

/* The backups, one row each: when it was taken, how large it is, and the way
   back. The date leads because it is the only thing anybody chooses by; the
   size is there to tell an empty archive from a full one at a glance. */
/* Everything about the surface is `.check`'s, deliberately: two lists on the
   same screen doing the same job — a column of things with a value beside
   each — had no reason to be a bordered panel in one place and bare rules in
   the other. Same border, radius, ground, 14 px inset, hairline and 13 px.
   Only the row is taller, because this one carries a 34 px button. */
.shot .backup-list {
  list-style: none; margin: 14px 0 0; padding: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--subtle); font-size: 13px;
}
/* Taller than `.check`'s 40, because this row carries two things that one
   does not: a calendar leaf and a button. 8 px around a 38 px leaf makes 54,
   which leaves the standalone 34 px button room to be itself rather than
   filling the row it sits in. */
.shot .backup-list li {
  display: flex; align-items: center; gap: 14px;
  min-height: 58px; padding: 10px 14px;
}
/* The same answer the archive gives when a row is pointed at: the leaf's
   header takes the accent and the leaf stops standing off the page. Shared
   with `.row` in the library rather than invented here — one gesture, one
   meaning, wherever the leaf appears. */
.shot .backup-list li:hover .recording-calendar-month,
.shot .backup-list li:focus-within .recording-calendar-month {
  background: var(--accent); color: #fff;
}
.shot .backup-list li:hover .recording-calendar,
.shot .backup-list li:focus-within .recording-calendar { box-shadow: none; }
.shot .backup-list li + li { border-top: 1px solid var(--line); }
/* The archive's own leaf at its compact size: 38 px wide, no year. A backup
   is chosen by its day, and the year of one taken this week says nothing. */
.shot .backup-list .recording-calendar {
  width: 38px; height: 38px; border-radius: 6px;
}
.shot .backup-list .recording-calendar-month { padding: 2px 0; font-size: 7.5px; }
.shot .backup-list .recording-calendar-day { margin-top: 2px; font-size: 16px; }
.shot .backup-list .recording-calendar-year { display: none; }
/* No tabular figures on the date. They are for numbers read down a column,
   and these are sentences of digits: monospaced, "12. 8. 2026 10:58" falls
   apart into widely spaced characters. The size keeps them — it is a column. */
/* The hour takes the room, so what the backup holds sits against the button
   at the right — read as one block with it, the way the archive's own
   metadata sits at the end of a row. */
.shot .backup-when {
  flex: 1 1 auto; font-weight: 600; line-height: 1.35;
  font-variant-numeric: tabular-nums;
}
.shot .backup-holds { flex: 0 0 auto; }
/* ==== src/css/05-later.css ==== */
/* Sections added after the sheet already had its shape. They stay here, in this order, because moving a rule changes which one wins.
 *
 * Part 05 of styles.css. The number is the point: these files are
 * one stylesheet cut into pieces, and the order they are imported in is the
 * order the rules were in. Moving a rule between them can change the look
 * without changing a value. */

/* ------------------------------------------------- list of uncertain spots */
.shot .uncertain-places,
.shot .corrections,
.shot .unassigned {
  /* Wider than the text, but not out to the card's edge. These rows are menu
     items and a menu item's highlight spans its whole menu — inset by the
     body's full 12 px it read as a floating filled cell with the row's own
     12 px inside it, two insets doing one job. Pulled out the whole 12 px it
     went the other way: the highlight ran into the card's border and its
     rounded corner, which reads as something that failed to stop rather than
     something that was placed. 6 px is the half measure, and it is a half
     measure on purpose. */
  list-style: none; margin: 0 -6px; padding: 0;
  display: flex; flex-direction: column; gap: 2px;
  /* A longer recording can have dozens. The panel must not grow forever. */
  max-height: 280px; overflow-y: auto;
}
.shot .uncertain-places button,
.shot .corrections button,
.shot .unassigned .interjection {
  /* Play mark, time, text. The mark says the row sounds rather than merely
     travels — which it now does, in all three lists. */
  display: grid; grid-template-columns: auto auto 1fr; align-items: baseline;
  gap: 8px;
  width: 100%; text-align: left; cursor: pointer;
  border: 0; background: none;
  /* These rows are menu items, not pills: a list you run down picking one, the
     same gesture as the action menus. So they take the menu item's shape —
     `--r-card` and 12 px of side padding, exactly `.action-menu-list
     button`. A full pill was tried and is wrong: a lozenge reads as one
     standalone control, and these are a column of siblings. */
  padding: 6px 12px; border-radius: var(--r-card);
}
/* The surrounding row paints the highlight now; a second one inside it would
   only show up as a rectangle within a rectangle. */
.shot .uncertain-places button:hover,
.shot .corrections button:hover,
.shot .unassigned .interjection:hover,
.shot .uncertain-places button.current,
.shot .corrections button.current,
.shot .unassigned .interjection.current { background: none; }
.shot .uncertain-time,
.shot .correction-time {
  font-size: 12px; font-weight: 600; color: var(--text-quiet);
  font-variant-numeric: tabular-nums;
}
/* Quiet at rest, accent once the row is pointed at or sounding — the mark is
   there to say the row plays, not to compete with the words. */
.shot .row-play {
  display: grid; place-items: center; color: var(--text-quiet);
  transition: color var(--hover-duration) var(--hover-ease);
}
.shot li:hover > button > .row-play,
.shot button.current > .row-play { color: var(--accent); }
.shot .uncertain-text {
  font-size: 13px; color: var(--text); line-height: 1.45;
  /* two lines are enough to recognise the spot; the rest is in the editor */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.shot .uncertain-places button.current .uncertain-time,
.shot .unassigned .interjection.current .uncertain-time,
.shot .corrections button.current .correction-time { color: var(--accent); }
/* Being edited, the row keeps the time in the same column; only the text
   becomes a field. The field is the application's own — the same one the
   transcript opens on a double-click, and the same one a note is written in:
   `--r-small` corners, a one-pixel border in the accent, the focus ring every
   other field in this interface wears. Nothing here invents a shape. */
.shot .uncertain-actions {
  display: grid; grid-template-columns: auto 1fr; align-items: start; gap: 9px;
  width: 100%; padding: 6px 8px;
}
/* Border (1) plus the field's padding (7): puts the time's first line on the
   first line inside the field. */
.shot .uncertain-actions > .uncertain-time { padding-top: 8px; }
.shot .uncertain-actions textarea {
  width: 100%; min-width: 0; padding: 7px 10px;
  border: 1px solid var(--accent); border-radius: var(--r-small);
  background: var(--panel); color: var(--text);
  font: inherit; font-size: 13px; line-height: 1.45;
  outline: none; resize: none; overflow: hidden;
  box-shadow: var(--ring-field);
}

/* A correction reads as a small sentence: what was there, an arrow, what is
   there now. The before is quiet because it no longer exists; the after takes
   the text colour because it is what the transcript says. Three lines is the
   ceiling — past that the row is a paragraph and the transcript is the place
   to read it. */
.shot .correction-change {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 13px; line-height: 1.45;
}
.shot .correction-before { color: var(--text-quiet); }
.shot .correction-arrow { margin: 0 6px; color: var(--text-quiet); }
.shot .correction-after { color: var(--text); }

/* ------------------------------------------------------- mini player */
.shot .mini-player {
  display: flex; align-items: center; gap: 8px;
  /* Equal gap on both sides. The two outer buttons share a diameter,
     so the inset from the pill's edge comes out optically even. */
  height: 36px; padding: 0 2px 0 3px;
  /* Same colour as the app surface, so the pill reads as a cut-out in the
     surface rather than an element stuck on top of it. */
  background: var(--ground); border-radius: var(--r-pill);
  width: 290px;
}
/* Without :not() this rule would beat `flex` on the title: it comes earlier
   but has higher specificity. The title would not grow, the content would
   collapse to the left and a gap would be left on the right. */
.shot .mini-player button:not(.mini-label) {
  border: 0; background: none; cursor: pointer; padding: 0;
  display: grid; place-items: center; flex: 0 0 auto;
}
/* Button and ring share one grid cell; the icon sits in the middle. */
.shot .mini-play { position: relative; width: 30px; height: 30px; color: var(--text); }
.shot .mini-play > svg:last-child { grid-area: 1 / 1; }
.shot .mini-ring { grid-area: 1 / 1; transform: rotate(-90deg); }
/* These two are also worn by the module listing's download ring — see
   `.component-mark.running` in 04-settings.css. One ring means one weight, so
   the stroke is set here once; anything changed here changes both, which is the
   point of the sharing rather than an accident of it. */
.shot .mini-track {
  fill: none; stroke: hsl(var(--ds-foreground) / 0.15); stroke-width: 2;
}
.shot .mini-progress {
  fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round;
  transition: stroke-dashoffset 0.1s linear;
}
.shot .mini-play:hover { color: var(--accent); }

.shot .mini-label {
  flex: 1 1 auto; min-width: 0;
  border: 0; background: none; cursor: pointer; padding: 0;
  text-align: left; font-size: 12.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shot .mini-time {
  margin-right: 0;
  font-size: 11.5px; font-weight: 600; color: var(--text-quiet);
  font-variant-numeric: tabular-nums; flex: 0 0 auto;
}
/* 30 px, not 32: the compact pill's 83 px width is arithmetic built on two
   30 px buttons, and resizing this one moves a measured layout. */
.shot .mini-close {
  width: 30px; height: 30px; border-radius: 50%;
  color: var(--text-quiet);
}
.shot .mini-close:hover { background: hsl(var(--ds-foreground) / 0.10); color: var(--text); }

/* In a narrow header the pills give up their words (Jakub's call): the
   player keeps play and the cross, the recorder keeps its dot, the clock and
   stop — a recording without a visible clock would look stalled. The bars
   and the title are the first things a squeezed header can live without.

   Three thresholds, because the squeeze depends on what shares the bar, not
   on the window alone — and the window cannot go below the 1000 px minimum
   in tauri.conf.json, which is why the first version's single 900 px rule
   never fired at all: it asked for a width the window cannot have. The
   archive header holds only the wordmark and always fits; the 900 px rule
   stays purely as a net in case the minimum ever drops. The detail header
   also carries the recording's title and the document actions, so its
   player compacts at 1020 px — and with a recording running beside it, two
   full pills plus the actions need ~1400 px, which is a perfectly ordinary
   window. That is the case in Jakub's screenshot. */
@media (max-width: 900px) {
  .shot .mini-player { width: auto; }
  .shot .mini-player > .mini-waves,
.shot .mini-player > .mini-label,
.shot .mini-player > .mini-time { display: none; }
  .shot .mini-recorder { padding-left: 10px; }
  .shot .mini-recorder > .mini-time { display: block; }}
/* The player pill compacts by measurement, not by a guessed window width:
   Detail hands it `compact` the moment the recording's name starts losing
   letters, and takes it away once the name is whole with room to spare. The
   change eases — width and gap shrink while the words fold and fade —
   instead of jumping between two layouts.

   Compact is play · three equalizer bars · cross, at Jakub's ask: two icons
   with dead air between them read as a big pill doing nothing. 83 px is the
   pill's own arithmetic — 3 + 30 play + three 6 px gaps (the folded words
   keep their zero-width slots) + 30 cross + 2 — and those three gaps become
   one 18 px window where the bars say whether sound is running. */
.shot .mini-player:not(.mini-recorder) {
  transition: width var(--hover-duration) var(--hover-ease),
    gap var(--hover-duration) var(--hover-ease);
}
.shot .mini-player:not(.mini-recorder) > .mini-label,
.shot .mini-player:not(.mini-recorder) > .mini-time {
  overflow: hidden;
  transition: max-width var(--hover-duration) var(--hover-ease),
    opacity var(--hover-duration) var(--hover-ease);
}
.shot .mini-player:not(.mini-recorder) > .mini-label { max-width: 180px; }
.shot .mini-player:not(.mini-recorder) > .mini-time { max-width: 64px; }
.shot .mini-player.dense { width: 83px; gap: 6px; }
.shot .mini-player.dense > .mini-label,
.shot .mini-player.dense > .mini-time {
  flex: 0 1 0; min-width: 0; max-width: 0; opacity: 0;
}
/* The bars leave the full-bleed backdrop and become the middle of the pill.
   Louder than the decorative backdrop, because here they are the only thing
   saying the sound is moving. */
.shot .mini-player.dense > .mini-waves {
  inset: 11px 32px 11px 33px;
  color: hsl(var(--ds-foreground) / 0.32);
}
.shot .mini-player.dense > .mini-waves.playing { color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
  .shot .mini-player:not(.mini-recorder),
.shot .mini-player:not(.mini-recorder) > * { transition: none; }}

/* --------------------------------------------- signing off a doubtful segment */
/* The actions sit on the first line of the segment, opposite the timestamp —
   not on a row of their own. As their own grid row they added some thirty
   points of height to every uncertain segment, and with a one-line sentence
   that doubled the block for two icons.

   Absolutely positioned rather than given a grid column: they only appear on
   hover, and a reserved column would narrow the text of every segment in the
   transcript for something almost never shown. */
.shot .segment-actions {
  position: absolute; top: 1px; right: 4px;
  display: flex; gap: 2px;
  opacity: 0; transition: opacity 0.15s ease;
}
/* Room for them, so a long first line does not run underneath. Only on
   uncertain segments — the rest have nothing there. */
.shot .segment.uncertain .segment-text { padding-right: 58px; }
.shot .segment:hover .segment-actions,
.shot .segment.current .segment-actions,
.shot .segment-actions:focus-within { opacity: 1; }
.shot .segment-actions button {
  display: grid; place-items: center;
  width: 26px; height: 26px; padding: 0;
  border: 0; background: none; cursor: pointer;
  border-radius: var(--r-card); color: var(--text-quiet);
}
/* Inside a highlighted segment grey on blue would be invisible, hence a
   layer derived from the text colour works on both grounds. */
.shot .segment-actions button:hover {
  background: hsl(var(--ds-foreground) / 0.10);
  color: var(--text);
}
.shot .segment-actions button:first-child:hover { color: var(--accent); }

/* The row is the item, so the row is what lights up — the tick belongs to it
   and used to sit outside the highlight, which made a hovered row look cut
   off before its own action. */
.shot .uncertain-places li,
.shot .corrections li {
  display: flex; align-items: center; gap: 2px;
  border-radius: var(--r-card);
  transition: background var(--hover-duration) var(--hover-ease);
}
.shot .uncertain-places li { padding-right: 4px; }
/* `--ground` — the transcript's own ground, the same faintly blue grey. On a
   white card it is one step, not the two `--muted` takes: that token is a
   hover on `--ground` itself, so used here it lands darker than everything
   around it and reads as a filled cell rather than a row being pointed at.
   What is sounding keeps the heavier `--muted`. */
.shot .uncertain-places li:hover,
.shot .corrections li:hover,
.shot .unassigned li:hover { background: var(--ground); }
.shot .uncertain-places li:has(> button.current),
.shot .corrections li:has(> button.current),
.shot .unassigned li:has(.interjection.current) { background: var(--muted); }
.shot .uncertain-places li > button:first-child,
.shot .corrections li > button:first-child { flex: 1 1 auto; min-width: 0; }
.shot .uncertain-places .confirm {
  /* The general rule above turns buttons into a two-column grid (time + text).
     Without cancelling the columns the icon would land in the first one
     instead of in the middle. */
  grid-template-columns: none;
  /* 28 px, not 32: it shares a row with one line of text in a 300 px panel,
     and at 32 the row would be taller than the line it belongs to. */
  flex: 0 0 auto; display: grid; place-items: center;
  width: 28px; height: 28px; padding: 0;
  border: 0; background: none; cursor: pointer;
  border-radius: 50%; color: var(--text-quiet);
}
/* On its own the tick was a bare glyph, and its hover surface at 10 % white
   was invisible on a row that is already tinted. It takes the accent pair the
   rest of the application uses for a circular icon surface, which also says
   what the click does: mark this spot correct. */
.shot .uncertain-places .confirm:hover {
  background: var(--accent-light);
  color: var(--accent);
}

/* ------------------------------------------------------- modules in settings */
/* The tiles are gone with the tab they stood on: four rows that could not be
   pressed, each repeating a value chosen a card or two higher up. What is left
   of that card is one line saying whether anything is missing, and the red it
   says it in. */
.shot .warning-row { color: var(--danger); }

/* --------------------------------------------------- model choice cards */
.shot .model-choices { display: grid; gap: 8px; }
/* Centred rather than aligned to the top: the icon stands level with the whole
   block, not with its first line. */
.shot .choice.with-icon { flex-direction: row; align-items: center; gap: 13px; }
/* A circle. A rounded square next to round buttons and round status marks read
   as a third shape for no reason. */
.shot .choice-icon {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--muted); color: var(--text-quiet);
}
.shot .choice.chosen .choice-icon { background: hsl(var(--ds-petrol) / 0.14); color: var(--accent); }
/* Two lines, three points apart. */
.shot .choice-body {
  display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto;
}
.shot .choice.with-icon .choice-head { align-items: center; justify-content: flex-start; gap: 9px; }
/* The badge belongs at the right edge. Inline after the title it moved with the
   length of the name, so no two rows had it in the same place. The size on the
   model and language-editing cards in Settings is the same kind of thing and
   takes the same place; no card carries both, so both may claim the free space
   with `margin-left: auto` without having to split it between them.

   The wizard's two quality cards were the third user and are not any more:
   their size moved into the card as part of a sentence about what the choice
   costs on this machine — see `.choice-cost`. Nothing else changed for them,
   and the rule is left as it is because the Settings cards still need it. */
.shot .choice.with-icon > .badge,
.shot .choice.with-icon > .choice-size { flex: 0 0 auto; margin-left: auto; }

/* ------------------------------------- audio visualisation in the mini player */
.shot .mini-player { position: relative; overflow: hidden; }
.shot .mini-player > *:not(.mini-waves) { position: relative; z-index: 1; }
.shot .mini-waves {
  /* **The whole pill, under everything.** It used to start at 38 px, on the
     argument that the play button needs room — and the button does not need
     room, it stands on its own layer: `.mini-player > *:not(.mini-waves)` is at
     `z-index: 1` and this is at 0, which is the arrangement that makes the bars
     a backdrop rather than a neighbour. The inset made them a neighbour that
     began somewhere, and the eye reads the start of a pattern as an edge.

     `.mini-recorder` had it right all along — *tak, jak to má i recorder mini*
     — and the comment on `.dense` has been calling this the full-bleed backdrop
     since it was written, describing a state it did not have. */
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  /* The visualisation is decoration, not data — it must not outshout the title. */
  color: hsl(var(--ds-foreground) / 0.085);
  transition: color 0.4s ease;
}
/* When paused the waveform should not vanish, only go quiet. */
.shot .mini-waves:not(.playing) { color: hsl(var(--ds-foreground) / 0.05); }

/* ------------------------------------------- time preview on the timeline */
/* The wrapper spans the full height of the block so the waveform fits
   envelope; the slider inside stays vertically centred. */
.shot .axis-wrap { position: relative; flex: 1; display: flex; align-items: center; }
.shot .slider-preview {
  position: absolute; bottom: calc(50% + 14px); z-index: 30;
  transform: translateX(-50%);
  padding: 4px 9px; border-radius: var(--r-pill);
  background: hsl(var(--ds-foreground)); color: var(--ground);
  font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums;
  white-space: nowrap; pointer-events: none;
  box-shadow: var(--shadow-high);
}
/* the tip pointing at the spot on the axis */
.shot .slider-preview::after {
  content: ""; position: absolute; top: 100%; left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top-color: hsl(var(--ds-foreground));
}

/* the source file has gone from disk */
.shot .player-missing { justify-content: space-between; gap: 16px; }
.shot .player-missing > span { font-size: 13.5px; color: var(--text-quiet); }
.shot .mini-time.destructive-item { color: var(--danger); }

/* `.field-prompt` stood here — a red panel with a sentence and a button, and
   its only user was `Jazyková úprava` saying its model was not downloaded yet.
   That is an optional feature nobody has asked for, not a fault, and the red
   said otherwise. It is a plain `settings-action-row` now, which is what this
   application already uses for a sentence and a button. */

/* Nothing may clip the time bubble, hence no `overflow: hidden` here —
   the waveform stays inside because it is absolutely positioned. */
.shot .player { position: relative; }
.shot .player > *:not(.waves) { position: relative; z-index: 1; }

/* The canvas fills its wrapper. Without an explicit size it would keep its
   own and ignore the wrapper's insets. */
.shot .waves > canvas,
.shot .mini-waves > canvas,
.shot .mic-spectrum > canvas { display: block; width: 100%; height: 100%; }

/* The waveform fades at the edges so it does not end in a vertical cut. The
   mask sits on the wrapper, not the canvas: the canvas repaints every frame. */
.shot .mini-waves {
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 7%,
    #000 93%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 7%,
    #000 93%,
    transparent 100%
  );
}

/* ------------------------------------------------- facts in the sidebar */
/* The three facts about the backup — when, how many, where — as one panel.
   Loose rows with 6 px between them read as three separate remarks; a rule
   between each says they belong to one thing and lines the values up. */
/* The About page lists what the application is made of in the same shape —
   a name on the left, one fact on the right — so the two panels are one rule
   rather than two that drift. */
.shot .backup-summary,
.shot .about-panel {
  margin: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--subtle); font-size: 13px;
}
.shot .backup-row,
.shot .about-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: center; gap: 16px;
  min-height: 40px; padding: 6px 14px;
}
.shot .backup-row + .backup-row,
.shot .about-row + .about-row { border-top: 1px solid var(--line); }
.shot .backup-row dt,
.shot .about-row dt { color: var(--text-quiet); white-space: nowrap; }
.shot .backup-row dd,
.shot .about-row dd {
  min-width: 0; margin: 0;
  font-weight: 600; text-align: right; font-variant-numeric: tabular-nums;
}

/* The publisher's mark beside its name, on the row that answers who made this.
   `dd` sets `text-align: right`, which does nothing to a flex box, so the row
   is pushed to its right edge from here instead.

   14 px tall, which is the cap height of the 14 px name beside it rather than
   the name's line box — the mark is three solid shapes and would out-weigh the
   type at the same nominal size. 9 px between them: the amount a label and its
   field take everywhere else, and the two are that same kind of pair. */
.shot .about-author {
  display: flex; align-items: center; justify-content: flex-end; gap: 9px;
}
/* Nothing draws this at the moment — the publisher's mark is out of the `Autor`
   row for now, not for good. The rule is kept with its measurements so putting
   the mark back is one line in `Settings.tsx` rather than a re-derivation. */
.shot .znackarna-mark { display: block; height: 14px; width: auto; flex: 0 0 auto; }

/* -------------------------------------------------------------- about */
/* What the application can do, one line each. The 30 px circle and the 17 px
   icon are the sidebar headings' geometry, so a row here weighs the same as
   a heading there. */
.shot .about-abilities { display: grid; gap: 14px; margin: 0; padding: 0; list-style: none; }
.shot .about-abilities li {
  display: grid; grid-template-columns: 30px minmax(0, 1fr);
  align-items: center; gap: 12px; font-size: 14px; line-height: 1.5;
}
.shot .about-mark {
  display: grid; place-items: center; width: 30px; height: 30px;
  border-radius: 50%; background: var(--accent-light); color: var(--accent);
}
/* A panel whose labels carry that circle. 46 px rather than the 40 px every
   other row of this kind takes: the circle is 30 and the row's own 6 px of
   padding leaves it 2 px from both edges, which is a mark wedged into a row
   rather than sitting in one. 8 px above and below it is the same air the
   abilities list gives the same circle. 11 px to the label is `.about-row`'s
   own 16 px gap brought in, because half of that gap is now the circle's
   whitespace. Only this panel: the licence list beside it has no marks. */
/* The one value on this panel that is a control. It is drawn as a link and not
   as a button: every other value in every `.about-panel` is plain text at the
   right edge, and one of them wearing a border and a rounded corner reads as an
   input somebody left in — *ten divný obdélníček*. The element underneath is
   still a `<button>`, because there is no href for the webview to follow, so
   the whole of what makes it a button has to be taken off it by hand.

   Colour and an underline on hover, which is what says *this goes somewhere*
   without drawing a box. The weight and the right edge come from `.about-row
   dd` above, so it sits on the same line as the values beside it. */
.shot .about-link {
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--accent); font: inherit; text-align: right;
}
.shot .about-link:hover { text-decoration: underline; }
.shot .about-panel-marked .about-row { min-height: 46px; }
.shot .about-panel-marked .about-row dt { display: flex; align-items: center; gap: 11px; }
.shot .about-group + .about-group { margin-top: 22px; }
.shot .about-group-label {
  margin: 0 0 8px;
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--text-quiet);
}

/* ------------------------------------------------------------- folders */

/* Folders sit under the transcripts, at Jakub's ask: recent work keeps the
   top of the list, the drawers are below it. */
.shot .folder-block { margin-top: 26px; }
.shot .folder-block-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 10px;
  font-size: 14px; font-weight: 680; color: var(--text);
}
.shot .folder-block-title { display: flex; align-items: center; gap: 8px; }

/* The same block a transcript gets, in the paper yellow that already marks
   notes. The two never share a screen, so one colour can mean "not chrome"
   in both places without a second token. */
.shot .folder-row {
  background: var(--note); border-color: var(--note-line);
}
.shot .folder-row:hover,
.shot .folder-row:focus-within { border-color: var(--note-line); background: var(--note); }
/* A folder block is a transcript block with the drawer where the calendar
   stands: the same height, the same title, the same distance down to what is
   written under it. The mark is a circle rather than a card, so it does not
   fill the slot — the slot's own height is what keeps the two rows equal, and
   the mark is centred in it. Its width matches the calendar's, so the text
   column starts on the same pixel in both. */
.shot .folder-row .row-main { min-height: var(--row-thumb); }
.shot .folder-mark {
  display: grid; place-items: center; width: 48px; height: 48px;
  flex: 0 0 auto; border-radius: 50%;
  background: var(--note-field); color: var(--text-quiet);
}
.shot .list.compact .folder-mark { width: 38px; height: 38px; }
.shot .list.compact .folder-mark svg { width: 21px; height: 21px; }

/* Where you are, once a folder is open. */
.shot .folder-crumb {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 14px;
}
/* The folder's mark is also the way out of it: at rest the drawer, under the
   pointer or the keyboard an arrow back to the archive. The two are stacked in
   one grid cell rather than swapped in JavaScript, so neither can shift the
   row by a pixel as it fades. */
.shot .folder-crumb-back {
  /* 30 px, not 32: the same 30 px circle a sidebar section heading uses for
     its mark, because at rest that is what this is. */
  display: grid; flex: 0 0 auto; place-items: center;
  width: 30px; height: 30px; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--muted); color: var(--text-quiet);
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
/* Both layers fill the button and centre their own drawing. A bare span is
   an inline box, so its svg sat on a text baseline and the drawer came out
   above and left of the circle's middle — which is exactly what Jakub saw. */
.shot .folder-crumb-back > * {
  grid-area: 1 / 1; display: grid; place-items: center;
  width: 100%; height: 100%;
  transition: opacity var(--hover-duration) var(--hover-ease);
}
.shot .folder-crumb-back-arrow { opacity: 0; }
.shot .folder-crumb-back:hover,
.shot .folder-crumb-back:focus-visible { background: var(--accent-light); color: var(--accent); }
.shot .folder-crumb-back:hover .folder-crumb-back-folder,
.shot .folder-crumb-back:focus-visible .folder-crumb-back-folder { opacity: 0; }
.shot .folder-crumb-back:hover .folder-crumb-back-arrow,
.shot .folder-crumb-back:focus-visible .folder-crumb-back-arrow { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .shot .folder-crumb-back,
.shot .folder-crumb-back > * { transition: none; }}

.shot .folder-crumb-name { font-size: 16px; font-weight: 680; }
.shot .folder-crumb-count { margin-left: 2px; }
/* The menu sits with the name, not at the end of the row: it acts on the
   folder, and the facts after it are about the folder's contents. */
/* 30 px, not 32: it sits beside the 30 px mark at the head of the same row. */
.shot .folder-crumb-menu > .icon-button { width: 30px; height: 30px; }

/* a recording with no transcript */
.shot .player-prompt { justify-content: space-between; gap: 16px; }
.shot .player-prompt > span { font-size: 13.5px; color: var(--text-quiet); }

/* The untranscribed transcript area: what this space is for, and the one
   action that fills it. Same visual language as the summary/translation
   empty states — the accent circle, a heading, a primary action. Centred in
   the area's content box; the 120 px bottom padding of `.transcript` biases it
   slightly above the middle, which is where an empty state wants to sit. */
.shot .transcript-empty {
  min-height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
}
.shot .transcript-empty-mark {
  display: grid; place-items: center; width: 44px; height: 44px;
  margin-bottom: 14px; border-radius: 50%;
  background: var(--accent-light); color: var(--accent);
}
.shot .transcript-empty h2,
.shot .transcript-empty [data-h="2"] { margin: 0 0 18px; font-size: 16px; font-weight: 680; }

/* ------------------------------------------------------- save as */
.shot .save { position: relative; }
.shot .save > .button { gap: 7px; }
.shot .save-arrow { color: var(--text-quiet); margin-left: 2px; }
.shot .save-list {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  min-width: 210px; padding: 6px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-large); box-shadow: var(--shadow-high);
  display: flex; flex-direction: column;
}
.shot .document-save-menu.opens-above {
  top: auto; bottom: calc(100% + 6px);
}
.shot .save-menu-group {
  display: block; padding: 9px 12px 5px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--text-quiet);
}
.shot .save-menu-group:not(:first-child) { margin-top: 4px; border-top: 1px solid var(--line); }
.shot .save-list button {
  display: grid; grid-template-columns: 46px 1fr; align-items: baseline;
  gap: 10px; width: 100%; text-align: left; cursor: pointer;
  border: 0; background: none; padding: 8px 12px;
  border-radius: var(--r-card);
}
.shot .save-list button:hover { background: var(--muted); }
.shot .save-format {
  font-size: 12px; font-weight: 700; letter-spacing: 0.04em;
  color: var(--text);
}
.shot .save-label {
  font-size: 13px; color: var(--text-quiet);
  white-space: nowrap;
}

/* Path to the backup folder — and the way into it. Nobody reads a path for
   its own sake; they read it to go there, so it is a button. Monospace and
   toned down, because it is a location, not a sentence. `direction: rtl`
   drops the ellipsis at the beginning: the end of a path is what says which
   folder this is. `bdi` keeps the text itself reading left to right. */
.shot .backup-path {
  max-width: 100%; padding: 4px 8px; margin: -4px -8px -4px 0;
  border: 0; border-radius: var(--r-pill); background: transparent;
  color: var(--text-quiet); cursor: pointer;
  font-family: var(--font-path);
  font-size: 12px; font-weight: inherit;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  direction: rtl;
  transition: background var(--hover-duration) var(--hover-ease),
    color var(--hover-duration) var(--hover-ease);
}
.shot .backup-path:hover { background: var(--muted); color: var(--text); }
.shot .backup-path:focus-visible { outline: none; box-shadow: var(--ring); }

/* Speakers in the sidebar: a sample of the voice, the name, the share of the
   talking. The row is „play — name — how much they said", which is exactly the
   order somebody decides in whether this cluster is a real person. */
.shot .speaker-sample {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  cursor: pointer;
  padding: 0;
  transition:
    filter var(--hover-duration) var(--hover-ease),
    box-shadow var(--hover-duration) var(--hover-ease);
}
.shot .speaker-sample svg {
  margin-left: 1px; /* opticky stred trojuhelniku */
}
.shot .speaker-sample:hover {
  filter: brightness(1.08);
  /* A halo, not a drop shadow, so it takes the foreground rather than black:
     8 % black around a coloured button is invisible in the dark palette. */
  box-shadow: 0 0 0 3px hsl(var(--ds-foreground) / 0.08);
}
.shot .speaker-sample:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}
.shot .speaker-share {
  flex: 0 0 auto;
  min-width: 38px;
  text-align: right;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-quiet);
}
/* 14, not 8: the system's 8 px is for a note about the one field above it, and
   this one is about the whole list of speakers. */
.shot .speaker-hint {
  margin: 14px 0 0;
}

/* ------------------------------------------------------------- dictionary */
/* The card is a table and a button under it. Nothing here has spacing of its
   own — the numbers are the ones Settings uses everywhere else: the opening
   sentence's own 24 above the table, and `.settings-action-row.spaced`'s 22
   under it. */
/* **The columns.** Two halves of equal width and then the bin's 26 px, which
   is the listing's own so both lists end on the same right edge. It is also
   the one thing a dictionary row does not share with a component row, which
   has a mark, a name and a size instead.

   It was written for three bands at once — the composer, the headings and the
   rows — because three of them claiming the same two columns is how they came
   to have three different left edges. Both of the others are gone and only the
   rows are left, so the left edge is the panel's own again and there is nothing
   left to keep in step with it. */
.shot .dictionary-list li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 26px;
  align-items: center; gap: 12px;
}
/* `--dictionary-inset: 17px` stood here — the panel's hairline plus the 16 px
   `.component-row` pads a row by, a number the bands *outside* the panel needed
   in order to start where the rows inside it start. Nothing is outside the
   panel now. */
/* A mark and the field it belongs to are one cell, not two columns. Two columns
   would put the same gap between a mark and its own field as between that field
   and the next mark, and the reader would have to work out which way each mark
   faces. 8 px inside the pair against the grid's 12 between them: the amount
   the design system already gives a label and the field under it. */
.shot .dictionary-pair {
  display: flex; align-items: center; gap: 8px; min-width: 0;
}
.shot .dictionary-pair > input { flex: 1 1 auto; }
/* 22 px, and neither of the two circles it could have copied.

   26 is the component listing's mark, and there it is the row's only one,
   sharing a column with the bin at the far end — a dictionary row carries two
   of them and a bin, and at 26 the row reads as three circles with words
   between them rather than as a pair of words. 16 is the release notes' tick,
   drawn beside a line of prose rather than in front of a field. 22 stays under
   both, which is the point: the mark is smaller than the thing it marks and
   smaller than the row's one action. It did not grow with the field — a mark
   that matched the 38 px box beside it would be a third control on the row.

   Both circles are the application's own pairs — the tick is the green the
   listing's installed mark and the release notes' tick already sit on, the
   cross is the red the bin turns under the pointer. No colour is invented and
   both come from `:root`. */
.shot .dictionary-mark {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 22px; height: 22px; border-radius: 50%;
}
.shot .dictionary-mark.wrong { background: var(--danger-light); color: var(--danger); }
.shot .dictionary-mark.right {
  background: hsl(var(--ds-grass) / 0.14); color: var(--success-text);
}
/* `.dictionary-mark-space` stood here — a spacer the width of a mark, put in
   the heading row so each label sat over its *field* rather than over its
   column. That was the wrong edge: the mark is part of what the heading names,
   and indenting past it left the circle hanging outside the column it belongs
   to. The headings start at the column now, and the spacer had nothing to do. */
/* `.field:has(> .dictionary-row) > label` and `.dictionary-row > .button`
   stood here — the composer's label and its `Přidat`, the second given a line
   of its own across all three columns because a button 82 px wider than the
   bin under it made the entry row's halves a different width from every other
   row's. The composer is gone and both went with it; the card's one button is
   `.settings-action-row.spaced` under the table now, which is the pattern for
   an action below a bordered panel and needs nothing of its own. */
/* `.dictionary-arrow` stood here — the → between the two halves, in both the
   composer and the saved rows. It is a mark in front of each half now, on the
   owner's word: an arrow states the relation once, in the gap, and leaves the
   direction to be read; the pair states it on both fields at once. */
/* `.dictionary-row-actions` stood here — a flex box pushing the cross to the
   right end of a 108 px column it was sharing with the composer's button. The
   saved rows have a 26 px column of their own now, so the bin already ends
   where the row does and had nothing left to push it. */
/* The 24 above, the 22 of padding and the hairline that stood here separated
   the table from the composer above it. There is no composer: the sentence
   under the heading is what the table now follows, and
   `.settings-section-description` already ends with 24 of its own. A rule
   drawn a few pixels under that sentence would be dividing a card from its own
   opening line. */
/* Saved rows are edited in place, exactly like speaker names: no box until the
   pointer or the keyboard arrives. That is what lets a row of two fields read
   as a row of a listing and still be edited where it stands — and it is why
   the pair stayed two inputs rather than becoming text with a pencil. */
/* No size of its own any more: 13.5 px was set against a bare row, and the row
   is a listing row at 14 now. `font: inherit` takes it from there, which is how
   a component row's own text is sized.

   **38 px, and the row 56 with it** — *dejme tabulce vyšší řádek*. It was 26,
   which is the component listing's mark and made the row that listing's 44 px
   exactly. That was the right answer while a composer stood above the table
   holding the fields somebody actually typed in; with the composer gone, these
   *are* those fields, and a field in this application is 38 px. So the number
   is not invented for the ask — it is the one the design system already gives
   the thing this row has become, and the row is that plus the listing's own
   9 px above and below.

   The height belongs on the input rather than the row because the input is
   what a reader aims at: a taller row with a 26 px box inside it would look
   easier to hit and not be. */
.shot .dictionary-list input {
  min-width: 0; height: 38px; padding: 0 11px;
  border: 1px solid transparent; border-radius: var(--r-pill);
  background: transparent; color: var(--text); font: inherit;
}
/* Same specificity as `.component-row, .dictionary-list li` in
   `04-settings.css` and later in the load order, which is the whole reason it
   wins. Named here so a move of either block is known to change the height:
   this is the one measurement a dictionary row no longer shares with a
   component row, and everything else about the two still comes from that one
   declaration. */
.shot .dictionary-list li { min-height: 56px; }
.shot .dictionary-list input:hover { border-color: var(--line); }
.shot .dictionary-list input:focus {
  border-color: var(--accent); outline: none;
  background: var(--ground); box-shadow: var(--ring-field);
}
/* `.dictionary-head` stood here — `Co přepis slyší` and `Jak to má být`, 12/600
   in `--text-quiet` with 8 px under them. The row's own two marks say which
   half is which, per row and without a word, so the headings were saying it a
   second time. Both names live on as the inputs' `aria-label`, which needs no
   rule. */
/* `.dictionary-remove` is declared with `.component-remove` in
   `04-settings.css`: same bin, same 26 px, same two colours. Nothing is left
   here for it. */
/* Below the window's own 1000 px minimum (tauri.conf.json), so this cannot
   fire today. Kept as a net, like the 900 px rule for the header pills, in
   case the minimum ever drops or the interface is opened in a browser.
   560 px: a dictionary row drops to two columns. */
@media (max-width: 560px) {
  /* A row stacks its two pairs and keeps the bin beside them, spanning both
     lines, so it still ends on the same right edge it does at full width. */
  .shot .dictionary-list li { grid-template-columns: minmax(0, 1fr) 26px; }
  .shot .dictionary-list li > .dictionary-pair { grid-column: 1; }
  .shot .dictionary-list li > .dictionary-remove { grid-column: 2; grid-row: 1 / span 2; }}

/* ------------------------------------------------------------- tooltip */
/* The application's own bubble, replacing the one Windows draws. Same surface
   language as the action menus — panel colour, a hairline, the high shadow —
   because a tooltip is the same kind of thing: a small sheet that appears over
   the interface and is gone again. */
.shot .bubble {
  position: fixed; left: 0; top: 0; z-index: 200; visibility: hidden;
  max-width: 288px; padding: 7px 11px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--panel); color: var(--text);
  box-shadow: var(--shadow-high);
  font-size: 12.5px; font-weight: 500; line-height: 1.4;
  /* A hint about a control must never stand between the pointer and it. */
  pointer-events: none;
  animation: bublina-prichod 0.12s var(--hover-ease);
}
@keyframes bublina-prichod {
  from { opacity: 0; transform: translateY(-2px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .shot .bubble { animation: none; }}

/* --------------------------------------------------------- context menu */
/* The application's own menu surface, put where the pointer is. Fixed rather
   than absolute: the transcript scrolls, and a menu that scrolls with the text
   would drift away from the word it belongs to. It closes on scroll anyway,
   but the two must not disagree in the meantime. */
.shot .context-menu {
  position: fixed; z-index: 60;
}
.shot .context-menu .action-menu-list {
  position: static; min-width: 208px;
}

/* ------------------------------------------------ blocks with no speaker */
/* The same row as the two lists above it, with the answers underneath. One
   click per answer: a menu would be three, and there are only ever a handful
   of voices to choose from. */
/* Two parts to a row — the interjection, then who to credit it to — so this
   list stacks where its two siblings above are one line each. */
.shot .unassigned { gap: 6px; }
.shot .unassigned li {
  display: flex; flex-direction: column; gap: 2px;
  /* Matches the 6 px the row button puts above the text, so the highlight sits
     evenly around the pair rather than hugging the chips. */
  padding-bottom: 6px; border-radius: var(--r-card);
  transition: background var(--hover-duration) var(--hover-ease);
}
/* Flush with the time, not indented under the text. Lining them up with the
   words they are about read better and cost 39 px of a 283 px card — half a
   chip, on a row that has two of them and a name of unknown length. */
.shot .unassigned-voices { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 12px; }
.shot .voice-choice {
  border: 1px solid; border-radius: var(--r-pill); background: transparent;
  padding: 2px 9px; font: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer; opacity: 0.85;
  /* A long name gives way rather than pushing its neighbour out of the panel. */
  max-width: 100%; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
  transition: background var(--hover-duration) var(--hover-ease),
    opacity var(--hover-duration) var(--hover-ease);
}
.shot .voice-choice:hover { background: var(--muted); opacity: 1; }