/* Encore component sheet.
 *
 * Composition, sizing and spacing follow shadcn/ui; colour, type and radius come
 * from the Encore design system. Plain CSS — this app has no bundler, so there is
 * no Tailwind to compile and no Radix to mount.
 *
 * Every rule is scoped to `.enc`, which pages opt into with <body class="enc">.
 * That is what makes the migration safe: a page without the class keeps app.css
 * exactly as it was, and `.enc .card` (0,2,0) still outranks app.css's `.card`
 * (0,1,0) without needing @layer — which would not work here anyway, since
 * unlayered CSS beats every layer and app.css is unlayered.
 *
 * Requires tokens.css.
 */

/* ── Page ── */
body.enc {
  margin: 0;
  padding: 0;
  background: var(--bg-canvas);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
  letter-spacing: normal;
  -webkit-font-smoothing: antialiased;
}

/* ── The fork's palette, pointed at DS values ──
 * app.css defines five colour variables on :root, and two kinds of rule read
 * them where the layer cannot reach: inline styles set from JS (record.js paints
 * the picker label `color: var(--muted)`) and app.css rules the layer has no
 * reason to restate. Both resolve these names at use time, so redefining the
 * names on `body.enc` converts them all at once — and converts nothing on a page
 * that has not opted in. `--accent` is the important one: #E4002B is a crimson
 * near enough to the DS red to read as a mistake rather than a second colour,
 * and it paints waveform bars, card links and focus rings. `--mono` has no DS
 * equivalent — numbers get tabular figures from Inter instead.
 */
body.enc {
  --muted: var(--text-muted);
  --accent: var(--red);
  --accent-fg: var(--white);
  --danger: var(--status-failed);
  --border: var(--border-hairline);
  --card: var(--bg-card);
  --mono: var(--font-body);
}

/* Fluid, never fixed: full width up to the DS content maximum, with the DS
   gutter held on both edges at every size. */
.enc .container {
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.enc main.container {
  padding-block: var(--space-5) var(--space-7);
}

/* Task pages — upload, record — are a column of controls, not a data grid. The
   full width only spreads a dropzone thin and drags the eye across a line of
   copy nobody needs to read that far. */
.enc .container-measure {
  max-width: calc(860px + var(--gutter) * 2);
}

/* ── Type ── */
.enc h1 {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--fs-app-2xl);
  font-weight: var(--fw-heading);
  line-height: var(--lh-heading);
  letter-spacing: var(--tracking-heading);
}

.enc h2 {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--fs-app-xl);
  font-weight: var(--fw-heading);
  line-height: var(--lh-heading);
  letter-spacing: var(--tracking-heading);
}

.enc h3 {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--fs-app-lg);
  font-weight: var(--fw-heading);
  line-height: var(--lh-heading);
}

/* Title on the left, the page's one primary action on the right, the count
   underneath. Baseline-aligned so the button sits on the title's baseline
   rather than floating against the ascenders. */
.enc .page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

/* A line of copy under the title, where `.page-meta` carries counts. */
.enc .page-header .muted {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-app-md);
}

.enc .page-meta {
  margin: var(--space-1) 0 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
}

/* Column headers, eyebrows: the DS caps treatment. */
.enc .enc-label {
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted-strong);
}

.enc .muted {
  color: var(--text-muted-strong);
}

/* Figures that sit in a column — durations, counts, dates — line up digit for
   digit. The DS has no real monospace face (--font-mono is Inter), and this is
   what that token was actually for. */
.enc .enc-numeric {
  font-variant-numeric: tabular-nums;
}

/* ── Links ── */
.enc a {
  color: var(--text-primary);
  text-decoration: none;
}

.enc a:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

/* ── Shell ──
 * On a desktop this is a left sidebar (see the `min-width: 1000px` block below);
 * narrower than that it stays a top bar. Both are the same markup: render-nav.js
 * is shared by every page and pinned by ten tests, so it is not touched — the
 * whole shell is CSS, which also means one class on <body> still reverts it.
 *
 * Base = the top bar. app.css caps the nav at 820px and centres it, which on a
 * fluid page leaves it floating unaligned above the content; here it spans the
 * full width and its inner edges line up with the container's.
 */
.enc .top-nav {
  max-width: none;
  height: 56px;
  margin: 0 0 var(--space-5);
  padding: 0 var(--gutter);
  background: var(--bg-chrome);
  /* A hairline drawn inset, per the DS Navbar, rather than a border — a border
     would add to the 56px and make the bar taller than the design says. */
  border-bottom: 0;
  box-shadow: inset 0 -1px 0 var(--border-hairline);
  font-size: var(--fs-app-base);
}

.enc .top-nav .nav-left,
.enc .top-nav .nav-right {
  gap: var(--space-1);
}

.enc .top-nav .nav-right {
  gap: var(--space-3);
}

/* ── Theme switch ──
 * Three states, not a toggle: "system" is a real choice, and a two-way switch
 * makes it unreachable once the reader has touched it. Clustered on glass so the
 * three icon buttons read as one control rather than three loose marks in the
 * bar — the same shape the CRM's switch has. */
.enc .theme-switch {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  padding: var(--space-1);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-surface);
  background: var(--glass-bg);
}

.enc .theme-switch button {
  width: 28px;
  height: 28px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--text-muted-strong);
  /* The bar is a pointer-first surface at this width; below 1000px the switch
     moves into the panel and grows to a touch target there. */
  position: relative;
}

.enc .theme-switch button::before {
  content: '';
  position: absolute;
  inset: -8px;
}

.enc .theme-switch button svg {
  width: 16px;
  height: 16px;
}

.enc .theme-switch button:hover {
  background: var(--bg-raised);
  color: var(--text-primary);
}

.enc .theme-switch button[aria-checked="true"] {
  background: var(--bg-active);
  color: var(--text-primary);
}

/* In the burger panel the switch is a row of its own, and the account block
   below it is a column — so it stands on its own line, at full touch size. */
.enc .top-nav[data-nav-open] .nav-panel .theme-switch {
  align-self: flex-start;
  margin-bottom: var(--space-1);
}

.enc .top-nav[data-nav-open] .nav-panel .theme-switch button {
  width: 44px;
  height: 44px;
}

/* Nav links are targets, not text: padded, so the active pill has something to
   enclose and the hit area is a comfortable 32px. */
.enc .top-nav a {
  padding: 0 var(--space-3);
  height: 32px;
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-control);
  color: var(--text-muted-strong);
}

.enc .top-nav a:hover:not(.brand) {
  background: var(--bg-raised);
  color: var(--text-primary);
  text-decoration: none;
}

/* Where you are, stated once and unmistakably. */
.enc .top-nav a.active {
  background: var(--bg-active);
  color: var(--text-primary);
  font-weight: var(--fw-medium);
}

/* A label per destination is not enough on a shell you look at every day — the
   eye finds a shape faster than it reads a word. The glyph is attached by href
   because render-nav.js is off limits: no markup change, so all ten nav tests
   still pass. Same Lucide data as icons.js, inlined here because CSS cannot
   import a JS module, and masked rather than drawn so the colour is the link's
   own and hover/active need no second rule. */
.enc .top-nav a[href='/transcriptions.html']::before,
.enc .top-nav a[href='/activity.html']::before,
.enc .top-nav a[href='/record.html']::before,
.enc .top-nav a[href='/upload.html']::before {
  content: '';
  flex: 0 0 16px;
  height: 16px;
  margin-right: var(--space-2);
  background-color: currentcolor;
  mask-position: center;
  mask-repeat: no-repeat;
  mask-size: 16px 16px;
}

.enc .top-nav a[href='/transcriptions.html']::before {
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z'/%3E%3Cpath d='M14 2v4a2 2 0 0 0 2 2h4'/%3E%3Cpath d='M10 9H8'/%3E%3Cpath d='M16 13H8'/%3E%3Cpath d='M16 17H8'/%3E%3C/svg%3E");
}

.enc .top-nav a[href='/activity.html']::before {
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2'/%3E%3C/svg%3E");
}

.enc .top-nav a[href='/record.html']::before {
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 19v3'/%3E%3Cpath d='M19 10v2a7 7 0 0 1-14 0v-2'/%3E%3Crect x='9' y='2' width='6' height='13' rx='3'/%3E%3C/svg%3E");
}

.enc .top-nav a[href='/upload.html']::before {
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='17 8 12 3 7 8'/%3E%3Cline x1='12' x2='12' y1='3' y2='15'/%3E%3C/svg%3E");
}

/* Sign out is a <button>, so it gets its glyph the same way the links get
   theirs. In a list of rows with icons, the one row without one reads as
   unfinished. */
.enc .top-nav .link-button::before {
  content: '';
  flex: 0 0 16px;
  height: 16px;
  margin-right: var(--space-2);
  background-color: currentcolor;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'/%3E%3Cpolyline points='16 17 21 12 16 7'/%3E%3Cline x1='21' x2='9' y1='12' y2='12'/%3E%3C/svg%3E");
  mask-position: center;
  mask-repeat: no-repeat;
  mask-size: 16px 16px;
}

/* A row in the shell, not a word in a sentence: it gets the same hover fill the
   links get, and it does not underline. */
.enc .top-nav .link-button:hover {
  background: var(--bg-raised);
  color: var(--text-primary);
  text-decoration: none;
}

/* The brand slot carries the mark, not a typeset word. app.css sets the text in
   a white-to-accent gradient clip, which at 14px reads as a broken font, and a
   hand-typed wordmark next to the real one is the thing that made this look
   borrowed rather than ours. The DS ships the mark as an asset, so the text is
   pushed out of its own box (the link keeps its accessible name — a screen
   reader still hears "encore-wise") and the asset is painted in its place. The
   `white` pair — white badge, ink letters — is the DS's own choice for dark
   surfaces; `blue` is broken upstream (Logo.jsx references a --blue-700 that
   colors.css does not define). */
.enc .top-nav .brand {
  width: 92px;
  height: 30px;
  margin-right: var(--space-4);
  padding: 0;
  border-radius: 0;
  background: url('/enc/logo-encore.svg') left center / contain no-repeat;
  overflow: hidden;
  text-indent: 110%;
}

/* Only the opacity moves. Restating the `background` shorthand here — which is
   what the generic `.top-nav a:hover` fill forced before it was excluded above —
   makes Safari re-resolve the SVG on first hover, and the mark blinks out for a
   frame while it does. */
.enc .top-nav .brand:hover,
.enc .top-nav .brand:active {
  opacity: 0.85;
}

.enc .top-nav .user-email {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

/* A wrapped nav item breaks the 56px bar; below 760px the strip scrolls
   instead (see the narrow-screen block at the end of this file). */
.enc .top-nav a,
.enc .top-nav .brand {
  white-space: nowrap;
}

/* ── Shell: the burger ──
 * Five destinations, an email and Sign out do not fit across a phone, and the
 * previous answer — let the strip scroll behind a fade — hides them behind a
 * gesture nobody is told about. A burger says there is a menu and where it is.
 *
 * The panel is built by enc/shell.js, which moves the links and the account
 * block into one element after render-nav.js has run; that element is this
 * drop-down here and the sidebar's column further down. Closed by default, so a
 * page that does not load shell.js is unaffected.
 */
.enc .top-nav {
  position: relative;
}

.enc .nav-toggle {
  width: 40px;
  height: 40px;
  margin-left: auto;
  padding: 0;
  border-color: transparent;
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--text-primary);
}

.enc .nav-toggle::before {
  content: '';
  width: 20px;
  height: 20px;
  background-color: currentcolor;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='4' x2='20' y1='6' y2='6'/%3E%3Cline x1='4' x2='20' y1='12' y2='12'/%3E%3Cline x1='4' x2='20' y1='18' y2='18'/%3E%3C/svg%3E");
  mask-position: center;
  mask-repeat: no-repeat;
  mask-size: 20px 20px;
}

.enc .nav-toggle:hover {
  background: var(--bg-raised);
}

.enc .nav-toggle:active {
  background: var(--bg-active);
}

/* Open, the button is the way back out, so it becomes the close affordance. */
.enc .top-nav[data-nav-open] .nav-toggle::before {
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E");
}

.enc .top-nav .nav-panel {
  display: none;
}

/* Except when signed out: the bar is then a brand and one link, and a burger
   over one link hides the page's only call to action behind a button nobody has
   a reason to press. The link stays in the open and the toggle goes. */
.enc .top-nav-public .nav-toggle {
  display: none;
}

.enc .top-nav-public .nav-panel {
  display: flex;
  margin-left: auto;
}

/* A sheet dropped from the bar, not a strip inside it: full width, so every row
   is a full-width target, and on the chrome colour so it reads as part of the
   bar rather than as a card floating over the page. */
.enc .top-nav[data-nav-open] .nav-panel {
  position: absolute;
  top: 100%;
  right: 0;
  left: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-1);
  padding: var(--space-2) var(--gutter) var(--space-3);
  background: var(--bg-chrome);
  box-shadow: inset 0 -1px 0 var(--border-hairline), var(--shadow-lift);
}

/* 44px, not the bar's 32: a row in a touch menu is a touch target. */
.enc .top-nav[data-nav-open] .nav-panel a,
.enc .top-nav[data-nav-open] .nav-panel .link-button {
  justify-content: flex-start;
  height: 44px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-field);
  font-size: var(--fs-app-md);
}

/* The account block is not a destination; a rule says so. */
.enc .top-nav[data-nav-open] .nav-panel .nav-right {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-1);
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  box-shadow: inset 0 1px 0 var(--border-hairline);
}

.enc .top-nav[data-nav-open] .nav-panel .user-email {
  padding: var(--space-1) var(--space-3);
}

/* ── Shell: the desktop sidebar ──
 * Five destinations laid across the top of a 1440px page spend a whole band of
 * the screen saying almost nothing, and they read as a website's menu rather
 * than as an app's frame. Standing them up in a column costs 232px of width the
 * content does not need — the table's own measure is narrower than the page —
 * and buys the two things the top bar could not have: the destination list is
 * scannable as a list, and the account block sits at the bottom, out of the way
 * of the work, which is where an app puts it.
 *
 * Vertical below 1000px is the wrong trade: a phone's height is the scarce axis,
 * and 232px of a 375px viewport is most of its width. So the top bar stays the
 * base and the sidebar is the wide-screen enhancement — and it is still the same
 * markup, so no page's JS changes.
 */
@media (min-width: 1000px) {
  body.enc {
    --sidebar-w: 232px;
    /* The bar is fixed, so it is out of the flow; the padding is what keeps the
       content clear of it, and it leaves `.container` centred in what is left
       rather than centred in the window and overlapped on one side. */
    padding-left: var(--sidebar-w);
  }

  .enc .top-nav {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 20;
    flex-direction: column;
    align-items: stretch;
    width: var(--sidebar-w);
    height: auto;
    margin: 0;
    padding: var(--space-5) var(--space-3);
    box-shadow: inset -1px 0 0 var(--border-hairline);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* Wide enough for the column, so there is nothing to hide behind a button. */
  .enc .nav-toggle {
    display: none;
  }

  .enc .top-nav .nav-left {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
  }

  /* The panel is the column. It takes the rest of the height so the account
     block at its foot can be pushed to the bottom of the screen. */
  .enc .top-nav .nav-panel {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
  }

  /* The account block is not a destination, so it does not sit among them. */
  .enc .top-nav .nav-right {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
    margin-top: auto;
    padding-top: var(--space-4);
  }

  /* A row, not a chip: the label starts at the icon, and the hit area is the
     full width of the column, which is what makes a list feel like one. */
  .enc .top-nav a,
  .enc .top-nav .link-button {
    justify-content: flex-start;
    height: 36px;
    padding: 0 var(--space-3);
    border-radius: var(--radius-field);
  }

  .enc .top-nav .brand {
    width: 108px;
    height: 34px;
    margin: 0 var(--space-3) var(--space-5);
  }

  /* Signed in as whom — one line, and the answer is the local part; a column
     this narrow would otherwise wrap a long address onto three. */
  .enc .top-nav .user-email {
    padding: 0 var(--space-3) var(--space-1);
    overflow: hidden;
    font-size: var(--fs-app-xs);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Signed out, the nav is a brand and one link (render-nav.js marks it
     `top-nav-public`). A 232px column for that is a frame around nothing, and
     it puts Sign in at the far bottom of the screen, away from the card that
     asks for it. So the public nav stays a bar at every width — which means the
     page must not reserve the column either. */
  body.enc:has(.top-nav-public) {
    padding-left: 0;
  }

  .enc .top-nav-public {
    position: static;
    flex-direction: row;
    align-items: center;
    width: auto;
    height: 56px;
    padding: 0 var(--gutter);
    box-shadow: inset 0 -1px 0 var(--border-hairline);
  }

  .enc .top-nav-public .nav-left {
    flex-direction: row;
    align-items: center;
  }

  .enc .top-nav-public .brand {
    margin: 0;
  }

  /* The column rules above turn the panel vertical; here it is a bar. */
  .enc .top-nav-public .nav-panel {
    flex-direction: row;
    align-items: center;
  }

  .enc .top-nav-public .nav-right {
    margin: 0 0 0 auto;
    padding: 0;
  }
}

/* ── Focus ── */
.enc :focus-visible {
  outline: var(--stroke-strong) solid var(--focus-ring);
  outline-offset: 2px;
}

.enc ::selection {
  background: var(--red);
  color: var(--white);
}

/* ── Button ──
 * shadcn's geometry (36px tall, 16px inline padding, 8px gap, 16px icons) with
 * the DS pill radius and red. The variant names are the ones app.css already
 * uses — `secondary`, `danger`, `btn-lg` — so no existing markup has to change.
 *
 * The DS Button sets `text-transform: uppercase`. Not inherited here: it turns
 * `Delete` in a table row into shouting. Kept for `.btn-cta` only, which is
 * where the DS actually meant it.
 */
.enc button,
.enc .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 36px;
  padding-inline: var(--space-16);
  border: var(--stroke) solid transparent;
  border-radius: var(--radius-control);
  background: var(--red);
  color: var(--white);
  font-family: var(--font-body);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.enc button:hover,
.enc .btn:hover {
  background: var(--red-bright);
  text-decoration: none;
}

.enc button:active,
.enc .btn:active {
  background: var(--red-600);
}

/* Pressed state, continued. `:active` adds a pseudo-class, so the rule above
   (0,2,1) outranks every quiet variant declared below it (0,2,0): under the
   finger, `▸ more`, Delete, the mic, the ✕ and Sign out all flashed brand red.
   Worst on `▸ more`, whose padding is 0 — the fill landed straight on the words.
   Anything that overrides the base fill has to restate it here, at 0,3,0.
   Pressed is one step past hover, so quiet controls that lift on hover go to
   --bg-active, and the ones that are text to the eye stay text. */
.enc button.secondary:active,
.enc .btn-outline:active,
.enc .btn-ghost:active,
.enc .search-clear:active,
.enc .dictation-mic:active,
.enc .project-ask-pill:active,
.enc .project-ask-ta-item:active {
  background: var(--bg-active);
}

.enc .link-button:active,
.enc .btn-link:active,
.enc .row-snippet-toggle:active {
  background: transparent;
}

.enc button.danger:active {
  background: color-mix(in oklab, var(--status-failed) 26%, transparent);
}

.enc button:disabled,
.enc .btn[aria-disabled='true'] {
  opacity: 0.5;
  pointer-events: none;
}

/* Icons never shrink and never stretch the control. */
.enc button > svg,
.enc .btn > svg {
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
}

.enc button.secondary {
  background: var(--bg-raised);
  color: var(--text-primary);
}

.enc button.secondary:hover {
  background: var(--bg-active);
}

.enc .btn-outline {
  background: transparent;
  border-color: var(--border-hairline);
  color: var(--text-primary);
}

.enc .btn-outline:hover {
  background: var(--bg-raised);
  border-color: var(--border-hairline);
}

.enc .btn-ghost {
  background: transparent;
  color: var(--text-muted-strong);
}

.enc .btn-ghost:hover {
  background: var(--bg-raised);
  color: var(--text-primary);
}

/* Destructive reads as outline, not as a filled red block: a row-level delete
   should not be the loudest thing on the screen, and filled red is the brand. */
/* The border is the only thing that says "button" once the fill is gone, so it
   is held to 1.4.11's 3:1 rather than treated as decoration: 45% of the darker
   red was 1.71:1, effectively invisible. 75% of the text red is 3.70:1, and
   hover goes to full strength (5.61:1) so the state change still reads. */
.enc button.danger {
  background: transparent;
  border-color: color-mix(in oklab, var(--status-failed-text) 75%, transparent);
  color: var(--status-failed-text);
}

.enc button.danger:hover {
  background: color-mix(in oklab, var(--status-failed) var(--status-tint), transparent);
  border-color: var(--status-failed-text);
}

/* Text-only affordances: nav sign-out, record-card retry/dismiss, snippet
   toggles. Buttons in the DOM, links to the eye. */
.enc .link-button,
.enc .btn-link {
  height: auto;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text-muted-strong);
  font-weight: var(--fw-regular);
}

.enc .link-button:hover,
.enc .btn-link:hover {
  background: transparent;
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── Button sizes ── */
.enc .btn-sm {
  height: 32px;
  padding-inline: var(--space-3);
  font-size: var(--fs-app-sm);
}

.enc .btn-lg {
  height: 40px;
  padding-inline: var(--space-24);
  font-size: var(--fs-app-md);
}

.enc .btn-icon {
  width: 36px;
  height: 36px;
  padding: 0;
}

.enc .btn-icon.btn-sm {
  width: 32px;
  height: 32px;
}

/* The one place the DS uppercase belongs. */
.enc .btn-cta {
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

/* ── Field ──
 * Inputs, textareas and selects share one 36px shell at the 6px field radius —
 * shadcn's proportions. Not the pill: a pill-shaped text input hides where the
 * text begins and ends, which is why shadcn keeps buttons and fields apart.
 *
 * The chevron and magnifier are inline data URIs, so their stroke cannot be a
 * custom property. They carry the literal --rose (#CC9D9D), which is what
 * --text-secondary resolves to — not a new colour.
 */
.enc input[type='text'],
.enc input[type='search'],
.enc input[type='email'],
.enc input[type='password'],
.enc input[type='url'],
.enc textarea,
.enc select {
  width: 100%;
  min-height: 36px;
  padding: 0 var(--space-3);
  /* --border-control, not the DS hairline: a field's edge is what tells you
     there is a field, so it owes 1.4.11 3:1. See tokens.css. */
  border: var(--stroke) solid var(--border-control);
  border-radius: var(--radius-field);
  background: var(--surface-1);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--fs-app-base);
  line-height: 34px;
  transition: border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}

.enc textarea {
  padding: var(--space-2) var(--space-3);
  line-height: var(--lh-body);
  resize: vertical;
}

.enc input::placeholder,
.enc textarea::placeholder {
  color: var(--text-muted);
}

.enc input:hover:not(:disabled),
.enc textarea:hover:not(:disabled),
.enc select:hover:not(:disabled) {
  border-color: var(--border-control-hover);
}

/* One focus treatment for every field: DS border plus a soft brand ring. The
   ring app.css draws here is blue, left over from before the rebrand. */
.enc input:focus,
.enc textarea:focus,
.enc select:focus {
  outline: none;
  border-color: var(--border-brand);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--red) 22%, transparent);
}

.enc input:disabled,
.enc textarea:disabled,
.enc select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.enc select {
  padding-right: var(--space-5);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23C9A3A3' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 14px;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  /* `appearance: none` hands the control's text back to normal layout, and long
     project names wrapped to two lines inside a 36px box. A closed select is one
     line by definition — if the name does not fit, it gets cut, not stacked. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The native dropdown list is drawn by the OS and inherits nothing, so its
   colours have to be restated or it renders as light-on-light. */
.enc select optgroup,
.enc select option {
  background: var(--surface-1);
  color: var(--text-primary);
}

/* ── Search field ──
 * Same shell, with room for the leading magnifier and the trailing clear.
 */
.enc .search-bar input[type='search'] {
  padding-left: var(--space-5);
  padding-right: var(--space-5);
  border-radius: var(--radius-field);
  box-shadow: none;
}

.enc .search-bar input[type='search']:focus {
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--red) 22%, transparent);
}

.enc .search-bar::before {
  left: var(--space-3);
  width: 16px;
  height: 16px;
  opacity: 0.75;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23C9A3A3' stroke-width='2' stroke-linecap='round'><circle cx='11' cy='11' r='7'/><path d='M21 21l-4.3-4.3'/></svg>");
}

.enc .search-bar:focus-within::before {
  opacity: 1;
}

/* The button base fills with brand red; the clear affordance must not, or the
   loudest thing in the toolbar becomes a ✕.
   `display` has to be restated: app.css hides this at `.search-clear` (0,1,0)
   and toolbar.js only ever toggles `.visible`, so `.enc button`'s (0,1,1)
   inline-flex would otherwise pin the ✕ on screen over an empty field. */
.enc .search-clear {
  display: none;
  right: var(--space-1);
  width: 28px;
  height: 28px;
  padding: 0;
  border-color: transparent;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text-muted-strong);
}

.enc .search-clear.visible {
  display: inline-flex;
}

.enc .search-clear:hover {
  background: var(--bg-raised);
  color: var(--text-primary);
}

/* ── Filter chip ──
 * A native <select> wearing shadcn's select-trigger proportions. Native because
 * the project filter carries <optgroup>s and is keyboard- and screen-reader-
 * correct for free; `appearance: none` is all it takes to restyle.
 */
/* One row: search, then the filters. Stacked full-width the search field ran
   1344px on a desktop, which reads as a page-wide banner rather than a control
   and leaves the filters stranded on their own line. */
.enc .toolbar {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

/* Wide enough for a real query, bounded so it stays a control. */
.enc .toolbar .search-bar {
  flex: 1 1 280px;
  max-width: 420px;
}

.enc .filter-chips {
  gap: var(--space-2);
}

/* Sort is not a filter: the first two chips change which rows exist, the last one
   only changes their order. Given the room, it separates from the filter pair and
   sits over the table it reorders. Below that width the toolbar wraps and the
   auto margin would strand it on a line of its own. */
@media (min-width: 1000px) {
  .enc .filter-chips {
    flex: 1 1 auto;
  }

  .enc .filter-chips .filter-chip:last-child {
    margin-left: auto;
  }
}

.enc .filter-chip {
  width: auto;
  background-color: var(--surface-1);
  border-color: var(--border-control);
  border-radius: var(--radius-field);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-base);
  padding: 0 var(--space-5) 0 var(--space-3);
  background-position: right var(--space-3) center;
  background-size: 14px;
}

.enc .filter-chip:hover:not(:disabled) {
  border-color: var(--border-control-hover);
  color: var(--text-primary);
}

/* Active means "this filter is narrowing the list" — worth the brand, since it
   explains why rows are missing.
   Solid fill, not a tint with a brand border: on the v2 ramp a 12% tint sits
   1.09:1 from the page and the border reads 2.81:1 against the fill it encloses,
   so neither edge nor field carried the state. Filled, the chip clears 1.4.11 on
   its own outline (3.06:1) and the label rides --on-brand at 6.2:1. */
.enc .filter-chip.active {
  border-color: var(--red);
  background-color: var(--red);
  color: var(--on-brand);
}

/* The chevron is drawn, not inherited, so it does not follow `color` onto the
   fill. Same path, restated in the on-brand ink. */
.enc select.filter-chip.active {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23FFFFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
}

/* ── The dropdown list itself ──
 * `appearance: base-select` takes the popup away from the OS and hands it to
 * CSS, which is the only way to style an open <select> without replacing it
 * with a div-and-JS listbox — and the <select> has to stay: it carries the
 * <optgroup>s, it is keyboard- and screen-reader-correct for free, and ten
 * tests in project-picker.test.ts are written against it.
 *
 * Chromium 135+ only, so this is an enhancement, not the design: where it is
 * missing the trigger above still looks right and the list stays OS-drawn.
 */
@supports (appearance: base-select) {
  .enc select,
  .enc select::picker(select) {
    appearance: base-select;
  }

  /* base appearance turns the trigger into a flex container with a real
     ::picker-icon, so the background-image chevron and the padding that made
     room for it both come off. */
  .enc select {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding-right: var(--space-3);
    background-image: none;
    line-height: 1;
    text-align: left;
  }

  .enc select.filter-chip {
    padding-right: var(--space-3);
  }

  /* The default picker icon is the OS triangle. Same chevron the closed trigger
     used, restated here as content because a pseudo-element can't inherit a
     background-image from the rule it replaced. */
  .enc select::picker-icon {
    content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23C9A3A3' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
    width: 14px;
    height: 14px;
    transition: transform var(--dur-fast) var(--ease-out);
  }

  .enc select:open::picker-icon {
    transform: rotate(180deg);
  }

  .enc select::picker(select) {
    min-width: anchor-size(width);
    max-height: 320px;
    margin-top: var(--space-1);
    padding: var(--space-1);
    border: var(--stroke) solid var(--border-hairline);
    border-radius: var(--radius-field);
    background: var(--surface-2);
    box-shadow: var(--shadow-lift);
    overflow-y: auto;
  }

  .enc select option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 32px;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-primary);
    font-size: var(--fs-app-base);
    /* An option inherits from its optgroup, and the group label below is caps
       with tracking. Values are not labels. */
    letter-spacing: normal;
    text-transform: none;
  }

  /* Indented under their group label, so a two-team list reads as two lists. */
  .enc select optgroup option {
    padding-left: var(--space-3);
  }

  .enc select option:hover,
  .enc select option:focus {
    outline: none;
    background: var(--bg-raised);
  }

  .enc select option:checked {
    font-weight: var(--fw-medium);
  }

  /* Once options aren't painted as filled rows, the checkmark is the only thing
     saying which value is current. Sent to the trailing edge so every label
     starts at the same x whether or not anything is checked. */
  .enc select option::checkmark {
    order: 1;
    margin-left: auto;
    color: var(--red);
  }

  .enc select optgroup {
    padding: var(--space-2) var(--space-2) var(--space-1);
    background: transparent;
    color: var(--text-muted);
    font-size: var(--fs-app-xs);
    font-weight: var(--fw-medium);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
  }
}

/* ── Card, separator ──
 * Per the DS: a card is a surface plus a hairline, never a glow. The glow
 * recipe is reserved for heroes and marks; behind a stack of cards it turns the
 * whole page into a gradient.
 */
.enc .card {
  padding: var(--space-4) var(--space-24);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
  box-shadow: var(--shadow-tile);
}

.enc hr,
.enc .separator {
  height: var(--stroke);
  margin: 0;
  border: 0;
  background: var(--border-hairline);
}

/* ── The two glow surfaces ──
 * The DS puts the red glow behind heroes and marks and nowhere else. In this app
 * that is exactly two places: the sign-in card, which is a screen with nothing
 * else on it, and the ask hero, which is the main page's one purpose. Both get
 * the recipe's two layers — the ember gradient as the background, the red radial
 * over it — and both have to lift their content above the radial.
 */
.enc .signin-card,
.enc .ask-lead {
  position: relative;
  overflow: hidden;
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--glow-ember);
  box-shadow: var(--shadow-lift);
}

.enc .signin-card::before,
.enc .ask-lead::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--glow-red);
  opacity: 0.7;
  pointer-events: none;
}

/* Without this the radial paints over the copy. */
.enc .signin-card > *,
.enc .ask-lead > * {
  position: relative;
  z-index: 1;
}

/* ── Sign-in card ──
 * app.css paints it `--card`, a blue-grey from the old palette that reads as a
 * different product next to the DS surfaces.
 *
 * 480px: three lines of copy and a button. Full-bleed, the button ended up alone
 * in the middle of 1110px of card with nothing to relate to.
 */
.enc .signin-card {
  max-width: 480px;
  margin: var(--space-7) auto;
  /* The DS spacing ramp is sized for the landing (`--space-8` is 120px), so app
     surfaces stay on the low end of it. */
  padding: var(--space-5);
  text-align: center;
}

.enc .signin-card h2 {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-app-xl);
}

.enc .signin-card .muted {
  max-width: 40ch;
  margin: 0 auto var(--space-24);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-md);
}

.enc .signin-card p:last-child {
  margin: 0;
}

/* Google's button is Google's: their mark on white is the identity people look
   for, and a brand-red pill with a Google mark on it reads as a phishing page.
   This is the one control on the page, so it keeps the brand's shape and drops
   the brand's colour. */
.enc .btn-google {
  border-color: var(--white);
  background: var(--white);
  color: #1f1f1f;
}

.enc .btn-google:hover,
.enc .btn-google:active {
  border-color: var(--white);
  background: rgba(255, 255, 255, 0.86);
  color: #1f1f1f;
}

/* app.css sizes the mark with `background-size: contain` and no position, which
   in a taller enc button leaves it cropped at the top left. */
.enc .btn-google::before {
  background-position: center;
}

/* ── Project row ──
 * Which project this page is about, on the main page and on upload. app.css
 * stacks a sentence-case label over a select at the full width of the page — a
 * form field, for what is one setting. Label, control and status share a line,
 * the label is an eyebrow, and the select is as wide as project names are long.
 */
.enc .upload-controls {
  flex-direction: row;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}

.enc .upload-controls label {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

.enc .picker-host {
  flex: 1 1 auto;
  max-width: 340px;
}

/* app.css reserves 12ch for the sync status whether there is any or not, which
   on one line is a hole between the select and nothing. */
.enc .picker-meta {
  min-width: 0;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

/* ── Dropzone ──
 * app.css draws a 2px dashed box in the old palette, with the copy floating in
 * the middle of it. Dashed is the right idea — it says "put something here" in a
 * way a card cannot — but it needs to be visible, it needs a target to aim at
 * before the sentence is read, and it needs to answer the pointer: the same
 * surface as a card at rest, raised on hover, brand-edged while a file is over
 * it.
 */
.enc .dropzone {
  padding: var(--space-5);
  border: var(--stroke-strong) dashed var(--border-hairline-strong);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
  transition: border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.enc .dropzone::before {
  content: '';
  display: block;
  width: 28px;
  height: 28px;
  margin: 0 auto var(--space-3);
  background-color: var(--text-muted-strong);
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 13v8'/%3E%3Cpath d='M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242'/%3E%3Cpath d='m8 17 4-4 4 4'/%3E%3C/svg%3E");
  mask-position: center;
  mask-repeat: no-repeat;
  mask-size: 28px 28px;
}

.enc .dropzone:hover {
  border-color: var(--text-muted);
  background: var(--bg-raised);
}

.enc .dropzone.drag {
  border-color: var(--red);
  background: var(--bg-raised);
}

.enc .dropzone.drag::before,
.enc .dropzone:hover::before {
  background-color: var(--text-primary);
}

.enc .dropzone p {
  margin: 0 0 var(--space-1);
}

.enc .dropzone p:last-of-type {
  margin: 0;
}

/* The file types and the size limit are the small print of the sentence above
   them, not a second sentence. */
.enc .dropzone .muted {
  font-size: var(--fs-app-sm);
}

/* Why the dropzone is inert. It belongs to the zone above it, not to the page. */
.enc #project-hint {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-app-sm);
}

/* ── Banner ──
 * An upload that half-failed says so here. app.css colours it from the old
 * palette's `--danger`; the DS has no semantic states, so this is the status
 * ramp's failure colour, tinted the same way the status badges are.
 */
/* The base class carries no state: app.js's progress messages ("Preparing
   transcript…") pass an empty kind, so without a surface here they rendered as
   loose text floating above the cards. */
.enc .banner {
  padding: var(--space-3) var(--space-4);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
  background: var(--bg-raised);
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
}

.enc .banner.error {
  border: var(--stroke) solid color-mix(in oklab, var(--status-failed) 50%, transparent);
  background: color-mix(in oklab, var(--status-failed) var(--status-tint), transparent);
  color: var(--text-primary);
}

/* ── Progress ──
 * The bar is the only thing on screen during an upload, so it is the brand's
 * red — the DS one, not app.css's older `--accent` — and it is a pill, because
 * every other rounded thing here is.
 */
.enc .progress {
  height: 8px;
  margin-top: var(--space-4);
  border-radius: var(--radius-control);
  background: var(--bg-raised);
}

.enc .progress > div {
  border-radius: var(--radius-control);
  background: var(--red);
  transition: width var(--dur-base) var(--ease-out);
}

/* ── Ask hero ──
 * The main page has one job: pick a project, ask one question about it. As a
 * page title, a line of copy, a bare `Project` label, a select and a card, all
 * flush to the top-left of a 1440px canvas, it read as a form somebody left
 * unfinished. So the title, the copy and the project control become one object —
 * the hero — and the composer sits directly under it as the thing the eye lands
 * on.
 *
 * The column is capped: a question is text being written and an answer is text
 * being read, and neither wants 1112px. This is a measure, not the old fixed
 * `.container` width — the page still fills the window, the reading column does
 * not.
 */
.enc .ask-main {
  max-width: 860px;
  margin-inline: auto;
  /* Centred in the window, not pinned to the top of it. A hero and a one-line
     composer at the top of a 900px screen left two thirds of the page empty and
     read as content that had failed to load; sat in the middle, the emptiness is
     the composition — the shape of a page you type one thing into. `auto`
     margins rather than `justify-content: center` because they yield: once an
     answer arrives and the stack is taller than the window, they collapse to 0
     and nothing is pushed above the top of the scroll area. */
  margin-block: auto;
}

/* The container becomes the frame that centring measures against. The sign-in
   card is the same kind of screen, so it centres the same way. */
/* The window's height is claimed by the body, not the container, so the top bar
   counts as part of it: `min-height: 100dvh` on the container itself pushed the
   page 56px taller than the window and put a scrollbar on a screen with one card
   on it. */
body.enc:has(.ask-lead) {
  display: flex;
  min-height: 100dvh;
  flex-direction: column;
}

body.enc:has(.ask-lead) .container {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding-block: var(--space-6);
}

body.enc:has(.ask-lead) .signin-card {
  margin-block: auto;
}

/* `ask-lead`, not `ask-hero`: app.css already owns `.ask-hero` for the Ask card
   on the transcript page (share.html), which is a card in a column of cards and
   must not inherit the glow this hero carries. Two different things needed two
   different names. */
.enc .ask-lead {
  margin-bottom: var(--space-4);
  padding: var(--space-5);
}

.enc .ask-lead h1 {
  margin: 0 0 var(--space-1);
}

.enc .ask-lead .muted {
  max-width: 56ch;
  margin: 0 0 var(--space-4);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-md);
}

.enc .ask-lead .upload-controls {
  margin: 0;
}

/* ── Sign-in problem (auth-error.html) ──
 * Where the OAuth round trip drops you when it fails. app.css paints the reason
 * as a full-width red banner under a gradient-clipped wordmark, which makes a
 * cancelled sign-in look like an outage. It is one card: the mark, what went
 * wrong, and the way back. Centred in the window like the sign-in card, because
 * it is the same kind of screen — nothing else on it — but without the glow: the
 * glow says "this is the thing you came for", and a failure is not that.
 */
body.enc:has(.auth-card) {
  display: flex;
  min-height: 100dvh;
  flex-direction: column;
}

body.enc:has(.auth-card) .container {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding-block: var(--space-5);
}

.enc .auth-card {
  /* `width: 100%` because the auto margins below suppress the flex stretch, and
     without it the card is shrink-to-fit — "Sign-in cancelled." got a 242px card
     and the domain-mismatch reason got a 440px one, on the same screen. */
  width: 100%;
  /* Narrower than the sign-in card: one sentence and one button, and a 480px
     line of copy under a 40px mark reads as a form with its fields missing. */
  max-width: 440px;
  /* `auto` on both sides, so the card yields rather than clipping if the reason
     runs to three lines on a short window. */
  margin: auto;
  padding: var(--space-5);
  text-align: center;
}

/* The DS ships the mark as an asset, so the word is pushed out of its own box
   and the asset painted in its place — the link keeps its accessible name, a
   screen reader still hears "encore-wise". Same trick as the nav brand, one
   size up: here it is the only mark on the screen. */
.enc .auth-mark,
.enc .auth-mark:link,
.enc .auth-mark:visited,
.enc .auth-mark:hover,
.enc .auth-mark:active {
  display: block;
  width: 124px;
  height: 40px;
  margin: 0 auto var(--space-4);
  background: url('/enc/logo-encore.svg') center / contain no-repeat;
  overflow: hidden;
  text-indent: 110%;
  white-space: nowrap;
}

.enc .auth-card h1 {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-app-xl);
}

/* The reason is the content of the page, not a caption under a heading, so it
   reads at the same size the sign-in card's line does. */
.enc .auth-reason {
  max-width: 40ch;
  margin: 0 auto var(--space-24);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-md);
}

.enc .auth-card p:last-child {
  margin: 0;
}

/* The status line reports on the row; it does not compete with it. app.css puts
   select (`flex: 1`, so basis 0 and free to shrink), refresh and status on one
   line, and the moment the status says anything long — "Network error loading
   projects" is the longest — the select gave up its width and the project name
   wrapped to two lines inside the control.
   `:not(:empty)` and not always: the span is in the markup permanently and says
   nothing most of the time, and an always-wrapping line would add a row gap
   under every picker in the app for a message that is not there. */
.enc .project-picker {
  flex-wrap: wrap;
}

.enc .project-picker .picker-meta:not(:empty) {
  flex: 1 0 100%;
}

/* Refresh is maintenance, not the page's action — a quiet icon square. Restated
   at 0,3,0 for hover and press: `.enc button:active` is 0,2,1 and would
   otherwise repaint it brand red under the finger. */
.enc .project-picker .refresh-btn {
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  padding: 0;
  border-color: var(--border-hairline);
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-md);
}

.enc .project-picker .refresh-btn:hover:not(:disabled) {
  border-color: var(--border-hairline);
  background: var(--bg-raised);
  color: var(--text-primary);
}

.enc .project-picker .refresh-btn:active:not(:disabled) {
  border-color: var(--border-hairline);
  background: var(--bg-active);
  color: var(--text-primary);
}

/* Under the hero, the card's own heading would be the third line in a row
   saying "ask across a project". It stops being a heading and becomes a label —
   it still earns its place, because once a project is picked it is what names
   the project being asked. */
body.enc:has(.ask-lead) .project-ask-heading {
  color: var(--text-muted-strong);
  font-family: var(--font-body);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-medium);
  letter-spacing: normal;
}

/* ── Ask-this-project composer ──
 * The button primitives paint every <button> as a filled brand pill, which in
 * this card meant five red objects — mic, Ask, and three suggestions — where
 * exactly one is the action. Only Ask keeps the fill; the rest step down.
 */
.enc .project-ask {
  margin-bottom: var(--space-4);
  padding: var(--space-4);
}

.enc .project-ask-heading {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-app-md);
}

/* The form spans the card. Capping it at 880px left a third of the card empty to
   the right of Ask, which read as an unfinished layout; a single-line input has
   no measure to protect — the 60–75-character rule is about text being read, and
   the only long text here is the answer, which is capped where it is styled. */
.enc .project-ask-form {
  align-items: center;
  gap: var(--space-2);
}

.enc .project-ask-input {
  width: 100%;
}

.enc .project-ask-btn {
  min-width: 72px;
}

/* The mic is a mode switch on the field, not a second submit. Ghost at rest;
   the brand fill is what says "listening". */
.enc .dictation-mic {
  top: 50%;
  right: var(--space-1);
  width: 28px;
  height: 28px;
  padding: 0;
  border-color: transparent;
  background: transparent;
  color: var(--text-muted-strong);
}

.enc .dictation-mic:hover {
  background: var(--bg-raised);
  color: var(--text-primary);
}

.enc .dictation-mic.listening {
  background: var(--red);
  color: var(--white);
}

.enc .dictation-mic svg {
  width: 15px;
  height: 15px;
}

/* The dictation language menu (right-click / long-press on the mic). app.css
   paints its options `background: transparent` at 0,1,0 — which loses to
   `.enc button` at 0,1,1, so every option in the menu was a filled brand pill
   and the menu read as five buttons stacked in a box. Same shape as the project
   picker's popup: a surface panel with a hairline, and rows inside it. */
.enc .dictation-lang-menu {
  top: calc(100% + var(--space-1));
  padding: var(--space-1);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
  background: var(--surface-2);
  box-shadow: var(--shadow-lift);
}

.enc .dictation-lang-option {
  gap: var(--space-2);
  justify-content: flex-start;
  height: 32px;
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-regular);
}

.enc .dictation-lang-option:hover,
.enc .dictation-lang-option:active {
  background: var(--bg-raised);
  color: var(--text-primary);
}

/* Which language is on, said by weight and colour; the ✓ comes from app.css. */
.enc .dictation-lang-option.active {
  color: var(--text-primary);
}

/* `input.` and not `.dictation-field`: the field shell above is
   `.enc input[type='text']` (0,2,1), which a two-class selector loses to. */
.enc input.dictation-field {
  padding-right: 36px;
}

.enc .dictation-error {
  margin-top: var(--space-1);
  color: var(--status-failed-text);
  font-size: var(--fs-app-sm);
}

.enc .project-ask-suggestions {
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.enc .project-ask-suggestions-label {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

/* Suggestions are shortcuts, not calls to action: a question already answered
   costs nothing to replay, so it should read as quiet. */
.enc .project-ask-pill {
  height: 32px;
  padding-inline: var(--space-3);
  /* Clickable, so its edge is a control boundary, not a hairline. */
  border-color: var(--border-control);
  background: var(--surface-1);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-regular);
}

.enc .project-ask-pill:hover {
  border-color: var(--border-brand);
  background: var(--bg-raised);
  color: var(--text-primary);
}

.enc .project-ask-typeahead {
  margin-top: var(--space-1);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
  background: var(--surface-1);
}

.enc .project-ask-ta-item {
  /* app.css lays these out as `display: block`, which `.enc button` outranks;
     restated so the row is a flex line and `justify-content` below applies. */
  display: flex;
  justify-content: flex-start;
  width: 100%;
  padding-inline: var(--space-3);
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text-primary);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-regular);
}

.enc .project-ask-ta-item:hover {
  background: var(--bg-raised);
}

.enc .project-ask-sources {
  margin-top: var(--space-3);
  max-width: 68ch;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

/* ── Recorder ──
 * One button, one purpose. The panel is a card and not a hero: the 96px red
 * circle is already the loudest object on the page, and the glow recipe behind it
 * would put two reds within 200px of each other. app.css paints this section with
 * a blue-grey gradient left over from the fork's palette — this is the DS surface
 * instead.
 */
.enc .record-section {
  gap: var(--space-4);
  padding: var(--space-6) var(--space-4);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
  box-shadow: var(--shadow-tile);
  margin-bottom: var(--space-4);
}

/* Picking a project is the precondition, so it reads as one line above the
   button rather than as a form. */
.enc .record-picker-wrap {
  max-width: 380px;
}

/* Centred, because everything else in this card is: app.css gives the wrapper a
   fixed 380px and the row inside it only fills ~265, so left-aligned content sat
   58px off the axis the mic button, its label and the hint all share. */
.enc .record-picker-wrap .upload-controls {
  justify-content: center;
  /* Not `center`: the row beside this label grows a second line when there is a
     status to report, and centring the label against a two-line block floated it
     into the gap between the select and the message. Pinned to the top instead,
     and given the control's height as its leading, so it sits on the select's
     line in both states. */
  align-items: flex-start;
  gap: var(--space-3);
  margin: 0;
}

/* The same eyebrow the other two pickers get. It needs saying twice because
   record.js builds this row as a <label> wrapping a <span>, where upload.html and
   index.html use a <div> wrapping a <label> — so `.upload-controls label` misses
   it, and "Project" was rendering as 14px body copy next to the control it
   labels. Not restyled into a <label for>: wrapping the select is what associates
   the two here, and that works. */
.enc .record-picker-wrap .upload-controls > span {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  /* The control's height, so the eyebrow's own line box centres on the select. */
  line-height: 36px;
}

/* The status line, when there is one, sits under the row and on its axis rather
   than hanging off the right of a centred group. */
.enc .record-picker-wrap .project-picker {
  justify-content: center;
}

.enc .record-picker-wrap .picker-meta {
  text-align: center;
}

.enc .record-center {
  gap: var(--space-3);
}

/* The button first, above the clock and the waveform, whatever order the markup
   is in. Those two appear only while recording, and above the button they pushed
   it 60px down the instant it was tapped — the target moving out from under the
   finger that just hit it. Everything that changes between the two states now
   happens below the button, so the button does not move at all. */
.enc .record-center .record-btn-wrap {
  order: -1;
}

/* Exo for the clock: it is chrome, never user text, and its digits are the one
   thing on the screen that changes every second. Tabular figures because the
   DS ships no monospaced face and a proportional 1 would make the timer twitch. */
.enc .record-timer {
  font-family: var(--font-heading);
  font-size: var(--fs-h6);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-heading);
  font-variant-numeric: tabular-nums;
}

.enc .record-waveform {
  gap: var(--space-1);
  height: 44px;
}

.enc .record-waveform .bar {
  width: 3px;
  border-radius: var(--radius-full);
  background: var(--red);
  transition: height var(--dur-fast) var(--ease-out);
}

/* The one place in the app where a control is a circle rather than a pill: it is
   a mic, and a mic reads as a target. `.enc button` would otherwise make it a
   96x40 pill, so size, shape and fill are all restated — and again at 0,3,0 for
   press, where `.enc button:active` (0,2,1) would repaint it. */
.enc .record-btn {
  width: 96px;
  height: 96px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--red);
  color: var(--white);
  box-shadow: var(--shadow-red);
  transition: transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.enc .record-btn svg {
  width: 34px;
  height: 34px;
  stroke: currentcolor;
}

.enc .record-btn:hover:not(:disabled) {
  transform: scale(1.04);
  border: 0;
  background: var(--red-bright);
  box-shadow: var(--shadow-red), var(--ring-brand);
}

.enc .record-btn:active:not(:disabled) {
  transform: scale(0.98);
  background: var(--red-600);
}

/* Recording inverts it: the fill drops out, the ring stays, and the pulse says
   the microphone is live. The square is the stop affordance. */
.enc .record-btn.recording,
.enc .record-btn.recording:hover:not(:disabled) {
  border: var(--stroke-strong) solid var(--red);
  background: var(--bg-raised);
  box-shadow: none;
  animation: enc-record-pulse 1.6s var(--ease-out) infinite;
}

/* A square inside a circle reads smaller than it measures, so the stop icon is
   drawn a size up from the mic. */
.enc .record-btn [data-icon-stop] {
  width: 44px;
  height: 44px;
}

.enc .record-btn [data-icon-stop] rect {
  fill: var(--red);
}

@keyframes enc-record-pulse {
  0% {
    box-shadow: 0 0 0 0 color-mix(in oklab, var(--red) 35%, transparent);
  }
  70% {
    box-shadow: 0 0 0 18px transparent;
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
}

.enc .record-label {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-base);
}

.enc .record-label-active {
  color: var(--red);
  font-weight: var(--fw-medium);
}

.enc .record-hint {
  max-width: 42ch;
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

/* ── Recording cards ──
 * A recording in flight is a row, not a document: title, where it went, how long
 * it is, and what is happening to it. The status vocabulary is the table's, so a
 * recording that starts here and finishes in the list looks like the same thing
 * in both places.
 */
.enc .record-cards {
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.enc .record-card {
  padding: var(--space-3) var(--space-4);
}

.enc .record-card .record-card-row {
  align-items: center;
}

.enc .record-card-title,
.enc .record-card-title-link {
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-medium);
}

.enc .record-card-title-link:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.enc .record-card-meta {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
}

/* Work in progress: the same pill as the table, with the spinner taking the
   dot's place — a dot and a spinner side by side would say it twice. */
.enc .status-pill {
  gap: var(--space-2);
  height: 22px;
  padding: 0 var(--space-2);
  border: var(--stroke) solid color-mix(in oklab, currentcolor 35%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in oklab, currentcolor var(--status-tint), transparent);
  color: var(--status-processing);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
}

.enc .spinner {
  width: 9px;
  height: 9px;
  border-color: currentcolor;
  border-top-color: transparent;
}

/* Not-yet-uploaded is a sentence, not a state to scan for, so it stays text with
   a cloud glyph rather than becoming a 300px pill. */
.enc .record-card-offline {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

.enc .record-card-offline::before {
  content: '';
  flex: 0 0 15px;
  width: 15px;
  height: 15px;
  background-color: currentcolor;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m2 2 20 20'/%3E%3Cpath d='M5.782 5.782A7 7 0 0 0 9 19h8.5a4.5 4.5 0 0 0 1.307-.193'/%3E%3Cpath d='M21.532 16.5A4.5 4.5 0 0 0 17.5 10h-1.79A7.008 7.008 0 0 0 10 5.07'/%3E%3C/svg%3E");
  mask-repeat: no-repeat;
  mask-size: contain;
}

.enc .record-card-error {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--status-failed);
  font-size: var(--fs-app-sm);
}

/* Retry is the recovery path and gets the emphasis; dismiss is the quiet one. */
.enc .record-card-retry {
  color: var(--text-primary);
  font-weight: var(--fw-medium);
}

.enc .record-card-dismiss {
  color: var(--text-muted);
  font-size: var(--fs-app-md);
}

/* ── Activity feed ──
 * One sentence per row: who, what they did, what they did it to, when. The rows
 * are hairline-separated on a panel, the same shape as the transcription list, so
 * the two list pages read as the same kind of page.
 *
 * Each row is a sentence in normal flow, not a grid: the markup is a span, a bare
 * text node, a link and a span, so any display that boxes children would make the
 * text node its own column and break the sentence apart. The glyph is absolute
 * and the time trails the line.
 */
.enc .activity-list {
  margin: 0;
  padding: 0 var(--space-4);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
  box-shadow: var(--shadow-tile);
  list-style: none;
}

.enc .activity-list li {
  position: relative;
  padding: var(--space-3) 0 var(--space-3) var(--space-24);
  border-bottom: var(--stroke) solid var(--border-hairline);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-base);
}

.enc .activity-list li:last-child {
  border-bottom: 0;
}

/* The family of the action, drawn once per row: projects, transcriptions,
   questions. Cheaper to scan than reading eight verbs. */
.enc .activity-list li::before {
  content: '';
  position: absolute;
  top: 15px;
  left: 0;
  width: 15px;
  height: 15px;
  background-color: var(--text-muted);
  mask-repeat: no-repeat;
  mask-size: contain;
}

.enc .activity-list li[data-kind='project']::before {
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'/%3E%3C/svg%3E");
}

.enc .activity-list li[data-kind='transcription']::before {
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z'/%3E%3Cpath d='M14 2v4a2 2 0 0 0 2 2h4'/%3E%3Cpath d='M10 9H8'/%3E%3Cpath d='M16 13H8'/%3E%3Cpath d='M16 17H8'/%3E%3C/svg%3E");
}

.enc .activity-list li[data-kind='qa']::before {
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.9 20A9 9 0 1 0 4 16.1L2 22Z'/%3E%3C/svg%3E");
}

.enc .activity-list .actor {
  color: var(--text-primary);
}

/* The subject is what the eye is looking for, so it carries the weight. Links
   here stay text-coloured: eight red links in a column is a list of warnings.
   Selected through the id because app.css reaches these links through one too
   (`#activity-list a`), and a class chain cannot outrank an id. */
.enc #activity-list a {
  color: var(--text-primary);
  font-weight: var(--fw-medium);
  text-decoration: none;
}

.enc #activity-list a:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* `.ts` is app.css's transcript-seek timestamp — mono, dotted underline, made to
   look clickable. Here it is the time an entry happened, and nothing to click. */
.enc .activity-list .ts {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  white-space: nowrap;
}

.enc .activity-list .ts::before {
  content: ' · ';
}

/* The empty row is the panel's only content, so it drops the dashed frame the
   standalone empty state wears — the panel is already the frame — and the glyph,
   which would otherwise paint as a bare square (no `data-kind`, no mask-image). */
.enc .activity-list .empty-state {
  padding: var(--space-6) var(--space-4);
  border: 0;
  color: var(--text-muted);
}

.enc .activity-list .empty-state::before {
  content: none;
}

.enc #pager-host {
  margin-top: var(--space-4);
}

/* ── Transcript page: the shell ──
 * The one screen that is a document rather than a list: a player, a title, and
 * then a stack of cards — Ask, Summary, Outline, Decisions, Tasks, Follow-up,
 * Transcript. Seven headings of the same weight in a column is a table of
 * contents nobody asked for, so the cards keep the DS surface and the headings
 * drop a step: one page title, then section titles under it.
 */
/* Title and its pills on the left, Delete at the far end of the title's line —
   the same shape `.page-header` gives every other page. `flex-end` so the button
   sits on the last line of a title that wrapped, not against its ascenders. */
.enc .session-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.enc .session-head {
  /* Without this a long title refuses to wrap and pushes Delete off the
     gutter. */
  min-width: 0;
}

/* Share and Delete, in that order, on the title's line. `flex: none` so the pair
   keeps its width and the title is what gives way. */
.enc .session-actions {
  display: flex;
  flex: none;
  gap: var(--space-2);
}

/* On a phone the two buttons take half the row and the title wraps to three
   lines to fit around them. The title is why anyone is on this page, so below
   560px it gets the full width and the buttons drop underneath it. */
@media (max-width: 560px) {
  .enc .session-header {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }

  .enc .session-actions {
    justify-content: flex-start;
  }
}

.enc .session-title {
  font-size: var(--fs-app-2xl);
}

/* app.css pulls the editable title 0.2rem left of its own column so its hover
   background can bleed past the text (`padding: 0 .2rem; margin-left: -.2rem`).
   The bleed is right, the shift is not: it puts the page's biggest line 3.2px
   left of everything under it, which is the kind of thing you cannot unsee.
   Same affordance, drawn with an outset shadow instead — it does not occupy
   layout, so the text stays on the column. */
.enc .session-title.editable {
  margin-left: 0;
  padding-inline: 0;
  border-radius: var(--radius-sm);
}

.enc .session-title.editable:hover,
.enc .session-title.editable:focus {
  background: var(--bg-raised);
  box-shadow: 0 0 0 var(--space-1) var(--bg-raised);
}

/* The pills are what the recording *is* — language, length, project — so they
   sit on one line under the title. */
.enc #meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
  flex-wrap: wrap;
}

/* The pill itself is a component and lives in the Badge block below — it was
   written twice, once here and once there, and the later copy silently won. */

.enc .pill.editable:hover,
.enc .pill.editable:focus {
  border-color: color-mix(in oklab, var(--red) 55%, transparent);
  color: var(--text-primary);
  outline: none;
}

/* Delete is destructive and rarely wanted: a quiet outline that only commits to
   the failure colour on hover. */
.enc button.danger {
  border: var(--stroke) solid color-mix(in oklab, var(--status-failed) 45%, transparent);
  background: transparent;
  color: var(--status-failed);
}

.enc button.danger:hover:not(:disabled) {
  border-color: var(--status-failed);
  background: color-mix(in oklab, var(--status-failed) var(--status-tint), transparent);
  color: var(--status-failed);
}

/* ── Transcript page: the player ──
 * Native controls, deliberately: a hand-built player would be a week of work to
 * reach parity with scrubbing, keyboard and captions. What makes them belong is
 * `color-scheme`, and it is declared once per theme on the root — pinning it to
 * `dark` here left the player a black slab in the middle of a cream page. The
 * wrap is the surface it sits on, and it sticks so a timestamp stays clickable
 * while reading further down.
 */
.enc .sticky-player {
  z-index: 20;
  margin-bottom: var(--space-4);
  padding: var(--space-2) 0;
  border-bottom: 0;
  background: var(--bg-deep);
}

.enc .sticky-player audio,
.enc .sticky-player video {
  width: 100%;
  border-radius: var(--radius-field);
}

.enc .sticky-player video {
  max-height: 60vh;
  background: black;
}

/* ── Transcript page: the card stack ── */
.enc #ask-card,
.enc #summary-card,
.enc #outline-card,
.enc #decisions-card,
.enc #tasks-card,
.enc #recap-card,
.enc #transcript-card {
  margin-bottom: var(--space-4);
}

/* A section title inside a card, not a page title: one step down from the
   heading above the stack, and the same size in all seven cards. */
.enc .card > h2 {
  margin-bottom: var(--space-3);
  font-size: var(--fs-app-lg);
}

/* ── Transcript page: Ask ──
 * Same job as the main page's composer but inside a card: type a question, get
 * an answer, and see the ones already asked. The textarea is the widest thing
 * in the card and the Ask button sits directly under it, because the eye leaves
 * the box at its bottom-left corner.
 */
.enc #ask-card #q {
  min-height: 84px;
  font-size: var(--fs-app-md);
}

.enc #ask-card #ask-input p {
  margin: var(--space-3) 0 0;
}

.enc #ask-list {
  margin-top: var(--space-4);
}

/* Prose: an LLM answer and the summary are the only places in the app with real
   paragraphs, lists and code in them, so they get a reading size and leading
   rather than the 14px the rest of the UI runs at. */
.enc .qa-answer,
.enc #summary {
  color: var(--text-secondary);
  font-size: var(--fs-app-md);
  line-height: var(--lh-body);
}

.enc :is(.qa-answer, #summary) :is(p, ul, ol) {
  margin: var(--space-2) 0;
}

.enc :is(.qa-answer, #summary) > :first-child { margin-top: 0; }
.enc :is(.qa-answer, #summary) > :last-child { margin-bottom: 0; }

.enc :is(.qa-answer, #summary) strong {
  color: var(--text-primary);
  font-weight: var(--fw-semibold);
}

.enc :is(.qa-answer, #summary) :is(pre, code) {
  border-radius: var(--radius-field);
  background: var(--bg-raised);
  font-family: var(--font-body);
  font-size: var(--fs-app-base);
}

.enc :is(.qa-answer, #summary) pre {
  padding: var(--space-3);
  overflow: auto;
}

.enc :is(.qa-answer, #summary) code {
  padding: 1px 4px;
}

/* A question already asked is a row you can open, so it reads like a row: the
   hairline separates it from the next one and the chevron is the DS glyph rather
   than app.css's `▸` character, which sat a few pixels off the text's baseline
   at every size. */
.enc .qa-item {
  padding: var(--space-3) 0;
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .qa-item summary {
  gap: var(--space-2);
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-medium);
}

.enc .qa-item summary:hover {
  color: var(--text-primary);
}

.enc .qa-item summary::before,
.enc .task-item summary::before {
  content: '';
  width: 14px;
  height: 14px;
  flex: none;
  background-color: var(--text-muted);
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
  mask-repeat: no-repeat;
  mask-size: contain;
  transition: transform var(--dur-fast) var(--ease-out);
}

.enc .qa-item[open] summary::before,
.enc .task-item[open] summary::before {
  content: '';
  transform: rotate(90deg);
}

.enc .qa-answer {
  padding: var(--space-2) 0 0 var(--space-16);
}

/* The divider marks where answers stop and suggestions start. */
.enc .ask-list-divider {
  margin: var(--space-3) 0;
  border-top: var(--stroke) solid var(--border-hairline);
}

/* Suggestions are prompts, not the card's action: a quiet chip that shapes to
   its text. Left as a full-width red pill (which is what the base button gives
   them) two of them read as the two most important buttons on the page. The
   emoji app.css prepends becomes the DS lightbulb — nothing else in the app is
   drawn by the system emoji font. */
.enc #ask-list .ask-suggestion {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  width: auto;
  height: 30px;
  margin: 0 var(--space-2) var(--space-2) 0;
  padding: 0 var(--space-3);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-pill);
  background: var(--bg-raised);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-regular);
  text-align: left;
  white-space: normal;
}

.enc #ask-list .ask-suggestion:hover {
  border-color: color-mix(in oklab, var(--red) 55%, transparent);
  background: var(--bg-raised);
  color: var(--text-primary);
}

.enc #ask-list .ask-suggestion::before {
  content: '';
  width: 13px;
  height: 13px;
  flex: none;
  background-color: currentcolor;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5'/%3E%3Cpath d='M9 18h6'/%3E%3Cpath d='M10 22h4'/%3E%3C/svg%3E");
  mask-repeat: no-repeat;
  mask-size: contain;
}

/* ── Transcript page: the transcript itself ──
 * The card's header carried the heading with the two copy buttons absolutely
 * positioned over it, which is why app.css reserves 17rem of padding on the right
 * — a fixed reservation that runs out of room the moment the viewport is narrow.
 * A flex row instead: the buttons sit in normal flow, pushed to the end, and wrap
 * when there is no room.
 */
.enc .transcript-summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  /* Same reason as the report bar: the buttons are pushed to the end by the
     container, not by an auto margin of their own, so the wrapped row starts at
     the heading's left edge instead of 22px in from it. */
  justify-content: space-between;
  margin-bottom: var(--space-3);
  padding-right: 0;
}

.enc .transcript-summary h2 {
  font-size: var(--fs-app-lg);
}

.enc .transcript-actions {
  position: static;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* Copying the transcript out is a utility, not the page's action — and there are
   two of them side by side, which is exactly the shape that must not be two
   brand-red buttons. `copied` confirms in the done colour rather than the brand
   one, so the feedback reads as "it worked", not as "now this is important". */
.enc .transcript-copy {
  height: 30px;
  padding: 0 var(--space-3);
  border: var(--stroke) solid var(--border-hairline);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

.enc .transcript-copy:hover:not(:disabled) {
  border-color: color-mix(in oklab, var(--red) 55%, transparent);
  background: transparent;
  color: var(--text-primary);
}

/* Restated because `.enc button:active` carries a pseudo-class and would
   otherwise put the brand fill back under the cursor. */
.enc .transcript-copy:active {
  background: var(--bg-raised);
}

.enc .transcript-copy.copied,
.enc .transcript-copy.copied:hover {
  border-color: color-mix(in oklab, var(--status-done) 45%, transparent);
  background: color-mix(in oklab, var(--status-done) var(--status-tint), transparent);
  color: var(--status-done);
}

.enc #transcript {
  margin-top: var(--space-3);
}

/* A transcript is the longest read in the app: paragraph rhythm and body leading
   rather than the tighter list spacing the extracted cards use. */
.enc .transcript-para {
  margin: 0 0 var(--space-3);
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
}

.enc .transcript-para .speaker {
  margin-right: var(--space-1);
  color: var(--text-muted-strong);
  font-weight: var(--fw-medium);
}

/* The retry sits inside a muted notice; a brand-filled button there would be the
   loudest thing on the card. */
.enc #summary-retry-btn {
  height: 30px;
  padding: 0 var(--space-3);
  border: var(--stroke) solid var(--border-hairline);
  background: transparent;
  color: var(--text-primary);
  font-size: var(--fs-app-sm);
}

.enc #summary-retry-btn:hover:not(:disabled) {
  background: var(--bg-raised);
}

.enc #summary-retry-btn:active {
  background: var(--bg-active);
}

/* ── Transcript page: the seek timestamp ──
 * `0:41` next to a claim is the page's one hyperlink into the media: click it and
 * the player jumps there.
 *
 * A chip, not a link. There are dozens of these down a transcript, and as brand
 * red with app.css's dotted underline they were the loudest thing on a page of
 * prose — a hundred small alarms in the margin. A quiet raised chip reads as an
 * object you can press without competing with the words around it, and it is the
 * pointer and the hover that say it is clickable.
 *
 * `inline-block` with height and line-height equal: the chip's own text sits on
 * the baseline of the sentence it interrupts, so a paragraph's leading does not
 * change where a chip appears in it. Brackets are gone from the label (app.js,
 * task-row.js, format-time.js) — the chip is the boundary.
 */
.enc .ts {
  display: inline-block;
  box-sizing: border-box;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--radius-sm);
  /* Tinted with the brand rather than neutral: a grey chip is furniture, and this
     one is the only thing in a paragraph that does something. The tint is 18% —
     enough to read as warm against the card, far short of the solid red that made
     the old link shout. The label is the same red lifted toward white so it
     carries the tint instead of sitting on it as plain grey. */
  background: color-mix(in oklab, var(--red) 18%, transparent);
  color: var(--text-on-tint);
  font-family: var(--font-body);
  font-size: var(--fs-app-xs);
  font-variant-numeric: tabular-nums;
  line-height: 20px;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.enc .ts:hover {
  /* Solid, not a 34% mix: on cream that mix is a pale pink, and white on it is
     unreadable. Filled, the hover is the brand in both themes. */
  background: var(--red);
  color: var(--on-brand);
  text-decoration: none;
}

/* ── Transcript page: the card tabs ──
 * Everything a recording produced used to stack into one scroll — summary,
 * outline, decisions, tasks, the follow-up draft, the whole transcript — and the
 * page's answer to "what were the tasks" was "keep scrolling". The cards are
 * grouped behind a segmented control instead (enc/tabs.js moves them; the ids and
 * the JS that fills them are untouched).
 *
 * A trough in the card material with a lighter pill on top: the same figure the
 * DS uses for a selected nav link, so a tab reads as a place you are rather than
 * a button you press. It hugs its labels instead of stretching — four tabs spread
 * across 1344px would each be a target the size of a paragraph.
 */
.enc .enc-tabs {
  display: inline-flex;
  max-width: 100%;
  gap: var(--space-1);
  margin: 0 0 var(--space-4);
  padding: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--bg-card);
  box-shadow: inset 0 0 0 var(--stroke) var(--border-hairline);
  /* One row, always — it scrolls when six labels outgrow the viewport. See the
     note on the narrow case below. */
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* How much of each end the fade below covers. Also the scroll padding: a tab
     scrolled into view has to land clear of the fade, or the tab that was just
     opened is the one half dissolved into the edge. Chrome counts a tab inside
     this inset as out of view, which is what makes `nearest` move for it. */
  --enc-tabs-fade: 28px;
  scroll-padding-inline: var(--enc-tabs-fade);
  scrollbar-width: none;
  -ms-overflow-style: none;
}

/* A scrollbar across a 34px pill is thicker than the gap between the tabs it
   would sit under. The strip is short enough to swipe and tabs.js keeps the open
   tab in view, so the bar has no job here. */
.enc .enc-tabs::-webkit-scrollbar {
  display: none;
}

/* What replaces the scrollbar: whichever edge still has tabs behind it fades out,
   so the strip says which way it goes. tabs.js sets the attributes — it is the
   only thing that knows the scroll position, and a rule cannot ask.
   The fade takes the trough with it, which is the point: a pill that stops in a
   hard vertical line is a pill that ended, and this one has not. */
.enc .enc-tabs[data-fade-end] {
  mask-image: linear-gradient(to right, #000 calc(100% - var(--enc-tabs-fade)),
    transparent);
}

.enc .enc-tabs[data-fade-start] {
  mask-image: linear-gradient(to right, transparent, #000 var(--enc-tabs-fade));
}

.enc .enc-tabs[data-fade-start][data-fade-end] {
  mask-image: linear-gradient(to right, transparent, #000 var(--enc-tabs-fade),
    #000 calc(100% - var(--enc-tabs-fade)), transparent);
}

/* The UA hides `[hidden]` with `display: none`, which any author `display` beats —
   and both the strip and its tabs set one, so tabs.js hiding an empty tab would
   have had no effect at all without this. */
.enc .enc-tabs[hidden],
.enc .enc-tab[hidden] {
  display: none;
}

/* Six labels need ~440px and a phone gives 328, so on a phone the strip is a
   scroller. It wrapped into two rows before, which turned a segmented control
   into a paragraph of pills and stopped reading as one control at all.

   The strip spans the column here rather than hugging its labels: a scroller has
   to have an edge to clip against, and half of a tab cut off at that edge is what
   says there is more of them. tabs.js scrolls the open tab into view, so nothing
   is ever selected off-screen. */
@media (max-width: 560px) {
  .enc .enc-tabs {
    display: flex;
    width: 100%;
  }
}

.enc .enc-tab {
  flex: 0 0 auto;
  height: 32px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  box-shadow: none;
  color: var(--text-muted-strong);
  font-family: var(--font-body);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-medium);
  /* Centred by line-height: the DS button's own padding would fight the height. */
  line-height: 32px;
  letter-spacing: normal;
  text-transform: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.enc .enc-tab:hover {
  background: var(--bg-raised);
  color: var(--text-primary);
}

/* `.enc button:active` is 0,2,1 and outranks a 0,2,0 variant, so the selected tab
   has to restate the pressed case or it flashes back to the unselected fill. */
.enc .enc-tab:active {
  background: var(--bg-raised);
  transform: none;
}

.enc .enc-tab.active,
.enc .enc-tab.active:hover,
.enc .enc-tab.active:active {
  background: var(--bg-active);
  box-shadow: inset 0 0 0 var(--stroke) var(--border-hairline);
  color: var(--text-primary);
}

/* One card per panel is the common case, and the card already carries the gap. */
.enc .tab-panel > .card:last-child {
  margin-bottom: 0;
}

/* ── Transcript page: outline and decisions ── */
.enc .outline-item {
  margin: 0 0 var(--space-2);
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
}

.enc .outline-item strong {
  color: var(--text-primary);
  font-weight: var(--fw-medium);
}

.enc .decision-item {
  padding: var(--space-3) 0;
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .decision-title {
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-medium);
}

.enc .decision-detail {
  margin-top: var(--space-1);
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
}

/* Who decided it is provenance, not content. */
.enc .decision-by {
  margin-top: var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

/* ── Transcript page: tasks ──
 * The one card with a form in it: pick a team and project, tick the tasks, send
 * them to the CRM. app.css hangs the toolbar off the card's padding with
 * negative margins and paints it a blue-grey from the old palette; here it is a
 * raised strip inside the card, which is what it is — a group of controls that
 * act on the list below it.
 */
.enc .tasks-toolbar {
  gap: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border: 0;
  border-radius: var(--radius-field);
  background: var(--bg-raised);
}

.enc .tasks-toolbar-label,
.enc .tasks-select-all-label {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

/* The send button is the card's action and keeps the brand fill; everything else
   in the strip is a quiet control. A `flex: 1` spacer in task-row.js already
   pushes select-all and send to the far end — no auto margins needed here. */
.enc .tasks-apply-to-selected,
.enc .tasks-langview-btn {
  height: 30px;
  padding: 0 var(--space-3);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

.enc .tasks-apply-to-selected:hover,
.enc .tasks-langview-btn:hover {
  border-color: color-mix(in oklab, var(--red) 55%, transparent);
  background: transparent;
  color: var(--text-primary);
}

.enc .tasks-langview-btn.active {
  border-color: transparent;
  background: var(--bg-active);
  color: var(--text-primary);
}

/* `width: auto`, against the layer's own full-width default for form controls:
   these sit in a row of controls, not in a stacked form, and a select stretched
   to the card's width reads as the card's main input. The date field's native
   calendar icon follows `color-scheme`, which the root sets per theme. */
.enc .tasks-team-select,
.enc .tasks-project-select,
.enc .task-linear-fields :is(select, input[type='text'], input[type='date']) {
  width: auto;
  /* 36, matching the layer's own field height. It reads as a spare number, but
     the date field is the only control here without a min-height of its own, so
     a smaller value here left it 6px shorter than the selects beside it. */
  height: 36px;
  padding: 0 var(--space-2);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
  background: var(--bg-card);
  color: var(--text-primary);
  font-size: var(--fs-app-sm);
}

/* Grid items stretch by default, which is why `width: auto` alone leaves a
   priority select as wide as the card. `justify-self: start` sizes each control
   to its content instead. */
.enc .task-linear-fields :is(select, input[type='date']) {
  justify-self: start;
}

/* The assignee box is the third kind of control in this column and stretched
   like the others used to, which left a hand's width of empty card to the right
   of two names. It gets the same treatment, with a floor: sized to "Bob" alone
   it would be narrower than its own label. */
.enc .task-linear-fields .task-assignee-list {
  justify-self: start;
  min-width: 10rem;
}

/* The title is the one field that is prose, so it keeps the full column. Wrapped
   in `:is()` to match the specificity of the rule above it. */
.enc .task-linear-fields :is(input[type='text']) {
  width: 100%;
}

.enc .task-item {
  padding: var(--space-3) 0;
  border-top: var(--stroke) solid var(--border-hairline);
}

/* app.css aligns the row on `baseline` and nudges the checkbox down 0.2rem to
   compensate; centred, that nudge is what makes it look off. */
.enc .task-item summary {
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
  /* The badges are sent to the end from here rather than by an auto margin on
     themselves — the title eats the free space while they share a line, and once
     the badges wrap they start at the row's left edge instead of 92px into it. */
  justify-content: space-between;
}

.enc .task-select {
  margin-top: 0;
}

/* The title takes the row and the metadata trails it; on a narrow screen the
   metadata's own width pushes it onto a second line instead of squeezing the
   title down to one word per line. No media query — the flex basis is the
   breakpoint. */
.enc .task-title {
  flex: 1 1 55%;
  min-width: 0;
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-medium);
}

/* Owner, date, and the timestamp it was mentioned at: the metadata of the task,
   read after the title, never instead of it. */
.enc .task-badges {
  flex: none;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
}

/* Checkboxes are the one native control the DS has no token for. `accent-color`
   is enough: the box keeps the platform's shape and hit area and takes the
   brand's red when ticked. */
.enc input[type='checkbox'] {
  accent-color: var(--red);
  width: 16px;
  height: 16px;
  cursor: pointer;
}

.enc .task-body {
  padding: var(--space-3) 0 0 var(--space-24);
}

.enc .task-desc {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-field);
  background: var(--bg-raised);
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
  outline: var(--stroke) solid transparent;
}

.enc .task-desc:focus {
  background: var(--bg-raised);
  outline-color: var(--red);
}

.enc .task-linear-fields {
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}

.enc .task-linear-fields .label {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

.enc .task-assignee-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-height: 7.5rem;
  overflow-y: auto;
  padding: var(--space-1) var(--space-2);
  background: var(--bg-card);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
}
.enc .task-assignee-option { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; font-size: var(--fs-app-sm); }
.enc .task-assignee-empty { color: var(--text-muted); font-size: var(--fs-app-sm); }

/* Sent is a terminal, good state — the status ramp's done colour, tinted the way
   every other status in the app is. */
.enc .task-sent-badge {
  padding: 2px var(--space-2);
  border: var(--stroke) solid color-mix(in oklab, var(--status-done) 35%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--status-done) var(--status-tint), transparent);
  color: var(--status-done);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
}

/* The identifier links out to the CRM, so it stays the accent — same signal the
   seek timestamps carry. */
.enc .task-sent-link {
  font-size: var(--fs-app-sm);
}

.enc .task-send-error {
  color: var(--status-failed);
  font-size: var(--fs-app-sm);
}

/* ── Transcript page: the follow-up email ──
 * A drafted email is text somebody will copy into a mail client, so it is shown
 * as what it is: a block of plain text on a raised surface, not prose the app
 * has styled opinions about.
 */
.enc #recap-body {
  margin-top: var(--space-2);
  padding: var(--space-4);
  border-radius: var(--radius-field);
  background: var(--bg-raised);
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
}

/* app.js reveals this row with an inline `display: block`, which no class can
   outrank — so the buttons are spaced with margins rather than a flex gap.
   Copying the draft out is the one thing you came here to do; Download and
   Regenerate are alternates and go quiet, or the card ends up with three
   equally loud primaries. Selected by id because app.js gives them no class. */
.enc #recap-actions button {
  margin: 0 var(--space-2) 0 0;
}

.enc #recap-download-btn,
.enc #recap-regen-btn {
  border: var(--stroke) solid var(--border-hairline);
  background: transparent;
  color: var(--text-primary);
}

.enc #recap-download-btn:hover:not(:disabled),
.enc #recap-regen-btn:hover:not(:disabled) {
  background: var(--bg-raised);
}

.enc #recap-download-btn:active,
.enc #recap-regen-btn:active {
  background: var(--bg-active);
}

.enc #recap-error {
  color: var(--status-failed);
  font-size: var(--fs-app-sm);
}

/* ── Transcript page: the client report ──
 * The follow-up email is a draft you paste into a mail client. This is the other
 * deliverable: a document you forward as a PDF. So it is drawn as a document and
 * not as another card of app furniture — a sheet inside the card, headed with
 * what it is, which meeting it came from and when, then the agreements and the
 * next steps. Measure is capped at 68ch: a client reads this end to end.
 */
.enc .report-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  /* Not `margin-left: auto` on the buttons: an auto margin is still an auto margin
     after the row wraps, so on a phone the button row dropped to its own line and
     that margin pushed it 71px off the heading's left edge — a toolbar floating in
     the middle of nowhere. `space-between` is the same layout while the two share
     a line, and a line holding one item puts it at the start. */
  justify-content: space-between;
  margin-bottom: var(--space-3);
}

/* Wraps inside itself, not only inside the bar: edit mode puts three controls in
   here, and on a phone they are wider than the screen. The bar can only wrap the
   whole group onto its own line — without this, the last button ran off the
   right edge of the card. */
.enc .report-actions {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* The heading and its state travel together, so the bar keeps the two children
   `space-between` was written for — the badge is part of what the heading says,
   not a third item competing for the middle of the row. */
.enc .report-heading {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/* Download PDF keeps the brand fill — sending the client a file is the reason
   this card exists. Copy and Edit are alternates and go quiet, through
   `.btn-outline` rather than through rules of their own: three controls hand-
   rolling the same variant is how variants drift apart. */

/* Says the document has been corrected, in the card and never in the sheet.
   Quiet on purpose: it is a fact about the file, not a warning. */
.enc .report-badge {
  padding: 0 var(--space-2);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-control);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  line-height: 20px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* A failed save, above the sheet and never inside it. The edits are still on
   screen when this shows — the line says what to do, not what was lost. */
.enc .report-edit-error {
  /* 20px, not 12: the caption underneath pulls itself up by 8px to sit under a
     heading, and against this line that would leave the two touching. */
  margin: 0 0 var(--space-4);
  color: var(--status-failed);
  font-size: var(--fs-app-sm);
  line-height: var(--lh-body);
}

/* One line under a card's heading saying what the card is for. The report and the
   follow-up carry one each: they hold the two halves of sending a client the
   outcome of a call, and side by side without a word of explanation they look
   like the same feature built twice. */
.enc .card-hint {
  margin: calc(-1 * var(--space-2)) 0 var(--space-4);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

/* The pull-up above is measured against a heading: 4px under it, so the line reads
   as the heading's caption. The report's heading shares its bar with two 44px
   buttons, and the bar needs ~355px to hold both — under that the buttons take a
   line of their own and the caption ends up 4px beneath them, wedged between a
   button and the sheet. It gets the bar's own 12px there instead. */
@media (max-width: 480px) {
  .enc .report-bar + .card-hint {
    margin-top: 0;
  }
}

.enc .report-doc {
  max-width: 68ch;
  /* Centred inside the card: at 1440 the measure is ~600px of a 1344px card, and
     hard against the left edge it reads as a layout that ran out rather than as a
     sheet of paper. */
  margin-inline: auto;
  padding: var(--space-5) var(--space-5) var(--space-4);
  border-radius: var(--radius-field);
  background: var(--bg-raised);
}

/* Mark left, what the document is right, brand rule under both. The rule is a
   border and not a background: borders print, backgrounds do not. */
.enc .report-brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 2px solid var(--border-brand);
}

.enc .report-logo {
  width: 88px;
  height: auto;
  flex: none;
}

.enc .report-brand .report-kicker { margin-left: auto; }

.enc .report-kicker {
  margin: 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.enc .report-head { margin-top: var(--space-4); }

.enc .report-title {
  margin: 0;
  color: var(--text-primary);
  font-family: var(--font-display);
  font-size: var(--fs-app-lg);
  font-weight: var(--fw-semibold);
  line-height: 1.25;
}

.enc .report-meta {
  margin: var(--space-1) 0 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
}

/* A rule between sections, not a card each: one document, read top to bottom. */
.enc .report-section {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .report-section-title {
  margin: 0 0 var(--space-3);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.enc .report-para {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
}

/* Numbered because a client refers to them by number in the reply. */
.enc .report-decisions {
  margin: 0;
  padding-left: 1.6em;
  list-style: decimal;
}

/* The one place brand red is allowed inside the sheet: the numbers a reply will
   quote. Body copy stays ink — a document is not a landing page. */
.enc .report-decision::marker {
  color: var(--text-brand);
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
}

.enc .report-decision + .report-decision { margin-top: var(--space-3); }

.enc .report-decision-title {
  margin: 0;
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-medium);
  line-height: var(--lh-body);
}

.enc .report-decision-detail {
  margin: var(--space-1) 0 0;
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
}

.enc .report-decision-by {
  margin: var(--space-1) 0 0;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

.enc .report-tasks {
  margin: 0;
  padding: 0;
  list-style: none;
}

.enc .report-task + .report-task {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .report-task-title {
  margin: 0;
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-medium);
  line-height: var(--lh-body);
}

.enc .report-task-detail {
  margin: var(--space-1) 0 0;
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
}

/* Owner and date on one line, separated by the same middot the meta rows use. */
.enc .report-task-meta {
  margin: var(--space-1) 0 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
}

.enc .report-task-meta span + span::before {
  content: ' · ';
  color: var(--text-muted);
}

/* "not assigned" / "no date" is the absence of a fact, not a fact. */
.enc .report-task-meta .is-missing { color: var(--text-muted); }

.enc .report-foot {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: var(--stroke) solid var(--border-hairline);
  color: var(--text-muted);
  font-size: var(--fs-app-xs);
}

.enc .report-foot-mark {
  color: var(--text-muted-strong);
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.1em;
}

.enc .report-foot span + span::before {
  content: ' · ';
  color: var(--text-muted);
}

/* ── The report, being corrected ──
 * The extraction gets owners, dates and wording wrong often enough that a sender
 * was retyping the document elsewhere before forwarding it. So it is edited in
 * place: same sheet, same typography, same line breaks — what is on screen is
 * what prints. Everything below is scoped to `.is-editing`, which
 * enc/report-edit.js puts on the sheet, so the read-only document is untouched
 * byte for byte.
 *
 * The slots read as fields only while they are fields: a dashed hairline, no
 * fill, no radius bigger than the text. A boxed input per value would turn a
 * document into a form, and the sender is proofreading, not filling one in.
 */
.enc .report-doc.is-editing [data-field] {
  /* Negative margin against the padding, so opening edit mode does not move a
     single word: the outline grows outwards from where the text already sat. */
  margin-inline: -6px;
  padding: 2px 6px;
  border-radius: var(--radius-field);
  /* --border-hairline-strong and not --border-hairline: a hairline is drawn as one
     continuous line, and at that strength a dashed one breaks up into nothing.
     The whole affordance is this outline — invisible is not an option. */
  outline: var(--stroke) dashed var(--border-hairline-strong);
  outline-offset: 0;
}

.enc .report-doc.is-editing [data-field]:hover {
  outline-color: var(--border-control-hover);
}

/* The one being typed in is solid and lit — a dashed box means "you may edit
   this", a solid one means "you are". */
.enc .report-doc.is-editing [data-field]:focus {
  outline: var(--stroke-strong) solid var(--focus-ring);
  background: var(--bg-active);
}

/* An empty slot has to say what belongs in it: a missing owner or date is the
   most common thing a sender opens this to fix, and an empty box says nothing.
   Drawn from data-ph by CSS and never as text, so a placeholder can never be
   saved as somebody's name (see enc/report.js). */
.enc .report-doc.is-editing [data-field]:empty::before {
  content: attr(data-ph);
  color: var(--text-muted);
  font-style: italic;
}

/* Owner and date share a line, and while editing they are two boxes on it rather
   than one string with a middot in the middle. The middot is generated content
   inside the second slot, and Chrome's innerText reads generated content back —
   which would have saved the separator as part of the date. A flex gap says the
   same thing and is not text. */
.enc .report-doc.is-editing .report-task-meta {
  display: flex;
  flex-wrap: wrap;
  /* 20px, because each slot reaches 6px past its text on both sides. */
  gap: var(--space-4);
}

.enc .report-doc.is-editing .report-task-meta span + span::before {
  content: none;
}

/* Room at the end of the item's first line for the remove button, so a long
   title wraps before it reaches it instead of running underneath. */
.enc .report-doc.is-editing .report-decision,
.enc .report-doc.is-editing .report-task {
  position: relative;
  padding-right: var(--space-5);
}

/* Deleting an item is a small, quiet, reversible thing — Cancel drops it, and
   Restore original brings back the whole extraction. It gets an icon button at
   the item's edge, not a red bar across the row. */
.enc .report-item-remove {
  position: absolute;
  top: 0;
  right: 0;
  width: 24px;
  height: 24px;
  padding: 0;
  border-color: transparent;
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--text-muted);
}

.enc .report-item-remove:hover {
  background: color-mix(in oklab, var(--status-failed) 18%, transparent);
  color: var(--status-failed);
}

.enc .report-item-remove:active {
  background: color-mix(in oklab, var(--status-failed) 26%, transparent);
}

.enc .report-item-remove > svg {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
}

/* The touch target is 44px even though the button draws 24: a delete you can
   hit by accident on a phone is worse than one you have to aim at. */
.enc .report-item-remove::after {
  content: '';
  position: absolute;
  inset: -10px;
}

/* Adding a line to the document is not an action on the document — the sheet the
   client reads has no buttons on it. So: ghost weight, dashed like the field
   outlines it belongs with, sitting under its list where the next row will land. */
.enc .report-add {
  margin-top: var(--space-3);
  padding: 0 var(--space-3);
  height: 32px;
  gap: var(--space-2);
  border: var(--stroke) dashed var(--border-hairline-strong);
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-medium);
  text-transform: none;
  letter-spacing: normal;
}

.enc .report-add:hover {
  border-color: var(--border-hairline-strong);
  background: var(--bg-raised);
  color: var(--text-primary);
}

.enc .report-add > svg {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
}

/* 44px on a 32px control, the same trade the remove button makes. */
@media (max-width: 760px) {
  .enc .report-add { height: 44px; }
}

/* ── Badge ──
 * One fixed height for every pill, and the text centred by line-height rather
 * than by flex alignment: app.js and session-project.js both set
 * `display: inline-block` inline on these, which no selector can outrank, so
 * `align-items: center` never had anything to centre — the text sat wherever a
 * 12px line landed in a 22px box, which is what made the row look slipped.
 * 24px outside, 22px of line between the two hairlines.
 */
.enc .pill {
  box-sizing: border-box;
  height: 24px;
  padding: 0 var(--space-2);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-pill);
  background: var(--bg-raised);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-variant-numeric: tabular-nums;
  line-height: 22px;
}

/* ── Status badge ──
 * The dot carries the colour and the word carries the meaning, so the badge
 * stays legible in a greyscale print or to a red-green colourblind reader.
 *
 * Every session status the server can emit is mapped below; `.status` alone is
 * the neutral fallback, so a status added upstream degrades to a readable grey
 * pill rather than to unstyled text.
 */
.enc .status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: 22px;
  padding: 0 var(--space-2) 0 6px;
  border: var(--stroke) solid color-mix(in oklab, currentcolor 35%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in oklab, currentcolor var(--status-tint), transparent);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  /* Statuses arrive from the database as bare lower-case words. Capitalising
     here leaves the DOM text — which the API and the tests treat as data —
     exactly as it was. */
  text-transform: capitalize;
}

.enc .status::before {
  content: '';
  flex: 0 0 6px;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: currentcolor;
}

.enc .status-ready,
.enc .status-imported {
  color: var(--status-done);
}

.enc .status-created,
.enc .status-pending,
.enc .status-preparing,
.enc .status-transcribing {
  color: var(--status-processing);
}

.enc .status-error,
.enc .status-failed {
  color: var(--status-failed-text);
  /* The badge normally mixes its fill out of `currentcolor`, but red is the
     darkest hue in the set: a fill strong enough to read as a fill lifts the
     backdrop by as much as the lighter text gained, so 18% of currentcolor
     lands back at 4.3:1. Mixing from the darker --status-failed at a lower
     strength keeps it legible both at rest (5.1:1) and on a hovered row
     (4.6:1), where --bg-raised is already lighter. */
  background: color-mix(in oklab, var(--status-failed) 10%, transparent);
}

.enc .status-archived_drive,
.enc .status-none {
  color: var(--status-info);
}

/* Work in progress reads as alive. Killed by the reduced-motion block below. */
.enc .status-pending::before,
.enc .status-preparing::before,
.enc .status-transcribing::before {
  animation: enc-pulse 1.6s var(--ease-out) infinite;
}

@keyframes enc-pulse {
  50% {
    opacity: 0.3;
  }
}

/* ── Table ──
 * shadcn's table proportions on a DS surface: the list is the page's one piece
 * of content, so it reads as a panel — surface-1, one hairline, tile radius —
 * rather than rows floating on the canvas with 100px of dead space either side.
 *
 * `separate` and not `collapse`: collapsed borders can't be rounded per cell, and
 * the usual fix (`overflow: hidden` on the table) would kill the sticky header,
 * which is worth more on a 73-row list than a corner. Spacing stays 0, so the
 * per-cell bottom borders still read as one continuous rule.
 *
 * app.css styles no table at all, so this is the whole treatment.
 */
.enc table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--fs-app-base);
  text-align: left;
}

.enc .transcription-list {
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--surface-1);
}

/* The corners the surface needs, applied to the cells that sit in them. */
.enc .transcription-list thead th:first-child {
  border-top-left-radius: var(--radius-surface);
}

.enc .transcription-list thead th:last-child {
  border-top-right-radius: var(--radius-surface);
}

/* Content clears the panel edge by the same 20px the header row uses, so the
   title column starts on the same optical line as everything above it. */
.enc .transcription-list th:first-child,
.enc .transcription-list td:first-child {
  padding-left: var(--space-4);
}

.enc .transcription-list th:last-child,
.enc .transcription-list td:last-child {
  padding-right: var(--space-4);
}

/* Opaque, because rows scroll underneath it. */
.enc thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-1);
  border-bottom: var(--stroke) solid var(--border-hairline);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  white-space: nowrap;
}

.enc tbody td {
  padding: var(--space-3);
  border-bottom: var(--stroke) solid var(--border-hairline);
  /* Top, not middle: a row with an expanded snippet is several lines tall, and
     centring its neighbours against that looks like a rendering accident. */
  vertical-align: top;
}

/* The highlight goes on the cells, not the row: a row background would paint a
   square corner over the panel's rounded one on the last row. */
.enc tbody td {
  transition: background var(--dur-fast) var(--ease-out);
}

.enc tbody tr:hover td {
  background: var(--bg-raised);
}

.enc tbody tr:last-child td {
  border-bottom: 0;
}

.enc .transcription-list tbody tr:last-child td:first-child {
  border-bottom-left-radius: var(--radius-surface);
}

.enc .transcription-list tbody tr:last-child td:last-child {
  border-bottom-right-radius: var(--radius-surface);
}

/* Column widths.
 *
 * Fixed layout, so the columns don't jump between renders and a long Russian
 * title can't squeeze `Duration` into two lines. Everything except the title is
 * sized to its content — dates and durations are known widths — and the title
 * absorbs the remainder, which is where the reader's eye goes anyway.
 */
.enc .transcription-list {
  table-layout: fixed;
}

.enc .transcription-list th:nth-child(2) {
  width: 12%;
}

.enc .transcription-list th:nth-child(3) {
  width: 14%;
}

.enc .transcription-list th:nth-child(4) {
  width: 96px;
}

.enc .transcription-list th:nth-child(5) {
  width: 104px;
}

.enc .transcription-list th:nth-child(6) {
  width: 132px;
}

/* Just the icon button plus the cell's own padding. */
.enc .transcription-list th:nth-child(7) {
  width: 56px;
}

/* Fixed layout won't break a long unspaced token on its own. */
.enc .transcription-list td {
  overflow-wrap: break-word;
}

/* The title is the only full-contrast thing in a row — that is what makes a
   list of 73 scannable. app.css paints it accent-red (0,2,0); .enc wins at
   (0,3,0) without touching that rule. */
.enc .transcription-list a.row-title {
  color: var(--text-primary);
  font-weight: var(--fw-medium);
}

.enc .transcription-list a.row-title:hover {
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Two lines, not app.css's three, and measured at 62ch. A summary is a hint that
   the row is the right one, not the thing to read — three 90-character lines per
   row turned a 73-row grid into a page of paragraphs, and it made row heights
   swing between 57 and 106px, which is what stops the eye scanning a column.
   Anything longer than two lines now gets the `▸ more` toggle, which is what
   that toggle was written for. */
.enc .row-snippet {
  margin-top: var(--space-1);
  max-width: 62ch;
  -webkit-line-clamp: 2;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

/* `▾ less` had the third line printed across it. app.css lifts the clamp on
   expand, but `.row-snippet.expanded` and `.enc .row-snippet` are both (0,2,0)
   and this sheet loads second — so the rule above kept winning, the box stayed
   two lines tall, and app.css's `overflow: visible` let the rest of the summary
   spill out over the button underneath it. The layer that raised the clamp is the
   layer that has to lower it — including the overflow, so this does not depend on
   app.css still being there. */
.enc .row-snippet.expanded {
  -webkit-line-clamp: none;
  overflow: visible;
}

/* The clamp toggle is a <button>, so the primitives above would paint it as a
   filled brand pill — louder than the title it belongs to. Two classes (0,2,0)
   to outrank `.enc button` (0,1,1). */
.enc .row-snippet-toggle {
  height: auto;
  min-height: 0;
  margin-top: var(--space-1);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-regular);
}

.enc .row-snippet-toggle:hover {
  background: none;
  color: var(--text-primary);
}

/* Project, uploader — supporting detail, one step down in weight. */
.enc .transcription-list tbody td:nth-child(2),
.enc .transcription-list tbody td:nth-child(3) {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

/* The project cell holds a link, and a link is full-contrast by default — which
   put a second white object in every row, competing with the title. It keeps the
   cell's quiet colour and earns emphasis on hover instead. */
.enc .transcription-list tbody td:nth-child(2) a {
  color: inherit;
}

.enc .transcription-list tbody td:nth-child(2) a:hover {
  color: var(--text-primary);
}

/* Duration, created — figures, so they align digit for digit and never wrap. */
.enc .transcription-list tbody td:nth-child(4),
.enc .transcription-list tbody td:nth-child(5) {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Durations are the one column a reader compares rather than reads: 42:15 next
   to 1:12:40 only tells you which is longer if the units line up, and left
   alignment puts minutes under hours. Right-aligned with the header. */
.enc .transcription-list th:nth-child(4),
.enc .transcription-list tbody td:nth-child(4) {
  text-align: right;
}

/* Row actions stay out of the way until the row is pointed at or tabbed into.
   Hidden with opacity rather than display, so the button keeps its place in the
   tab order and the row doesn't reflow on hover.
   Faded on the button and not on the cell: `.row-actions` IS the <td>, and fading
   it took its bottom border with it — which is why the row rule stopped ~56px
   short of the panel edge. It also took a delete error with it the moment the
   pointer left the row. */
.enc .row-actions .row-delete {
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}

.enc tbody tr:hover .row-actions .row-delete,
.enc .row-actions .row-delete:focus-visible {
  opacity: 1;
}

/* Icon-only and quiet by default: destructive, but not the loudest thing in a
   row. Written with both classes so it beats `.enc button.danger` on
   specificity rather than on source order. */
.enc button.danger.row-delete {
  border-color: transparent;
  color: var(--text-muted-strong);
}

.enc button.danger.row-delete:hover {
  border-color: transparent;
  background: color-mix(in oklab, var(--status-failed) var(--status-tint), transparent);
  color: var(--status-failed-text);
}

.enc .row-delete-error {
  margin-top: var(--space-1);
  color: var(--status-failed-text);
  font-size: var(--fs-app-xs);
}

/* ── Empty state ── */
.enc .empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
  border: var(--stroke) dashed var(--border-hairline);
  border-radius: var(--radius-surface);
  color: var(--text-muted-strong);
  text-align: center;
}

.enc .empty-state p {
  margin: 0;
}

.enc .empty-state svg {
  opacity: 0.6;
}

/* ── Search results ──
 * A different shape from the table on purpose: a hit is a passage, not a row of
 * fields, so it gets a stacked title / snippet / provenance block.
 */
.enc .search-result {
  padding: var(--space-3) 0;
  border-bottom: var(--stroke) solid var(--border-hairline);
}

.enc .search-result:last-child {
  border-bottom: 0;
}

.enc .search-results a.row-title {
  color: var(--text-primary);
  font-size: var(--fs-app-md);
  font-weight: var(--fw-medium);
}

.enc .search-result-meta {
  margin-top: var(--space-1);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-variant-numeric: tabular-nums;
}

/* The matched words. Amber rather than brand red: red on this palette is
   already the colour of buttons and errors, and a highlight has to read as
   "here" and nothing else. */
.enc mark {
  padding: 0 2px;
  border-radius: 3px;
  background: color-mix(in oklab, var(--amber) 32%, transparent);
  color: var(--text-primary);
}

/* No hover on a touch screen, so there would be no way to reveal them. */
@media (hover: none) {
  .enc .row-actions .row-delete {
    opacity: 1;
  }
}

/* ── Narrow screens ───────────────────────────────────────────────────────
   Seven columns need about 1000px: below that the fixed widths above squeeze
   TITLE — the one column that carries the answer — down to under 100px, so
   each row becomes a card instead. The DOM stays a table (the unit tests query
   `tbody tr` and count `td`s); only the display types change.

   The cells carry no data-label attribute, so the labels live here as
   nth-child()/::before pairs. That couples this block to the column order in
   renderTranscriptionList(); the header row it mirrors sits right above it. */
@media (max-width: 999px) {
  .enc .transcription-list,
  .enc .transcription-list tbody,
  .enc .transcription-list tr,
  .enc .transcription-list td {
    display: block;
    width: auto;
  }

  /* Each row becomes its own panel below, so the panel around the table would be
     a second frame drawn around the first. */
  .enc .transcription-list {
    border: 0;
    border-radius: 0;
    background: none;
  }

  .enc .transcription-list thead {
    display: none;
  }

  .enc .transcription-list tbody tr {
    position: relative;
    margin-bottom: var(--space-3);
    padding: var(--space-3);
    border: var(--stroke) solid var(--border-hairline);
    border-radius: var(--radius-surface);
    background: var(--surface-1);
  }

  .enc .transcription-list tbody tr:hover td {
    background: none;
  }

  .enc .transcription-list tbody td {
    padding: 0;
    border-bottom: 0;
  }

  /* The panel's inner gutter belongs to the panel; here the card's own padding
     does that job. */
  .enc .transcription-list th:first-child,
  .enc .transcription-list td:first-child {
    padding-left: 0;
  }

  .enc .transcription-list th:last-child,
  .enc .transcription-list td:last-child {
    padding-right: 0;
  }

  /* The title cell is the card heading; the action button overlaps its
     top-right corner, so it keeps that space clear. */
  .enc .transcription-list tbody td:first-child {
    margin-bottom: var(--space-2);
    padding-right: 40px;
  }

  .enc .transcription-list a.row-title {
    font-size: var(--fs-app-md);
  }

  /* Columns 2–6 become label/value pairs. The cap keeps label and value in the
     same glance on a 960px card instead of at opposite ends of it. */
  .enc .transcription-list tbody td:nth-child(n + 2):nth-child(-n + 6) {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    max-width: 420px;
    padding: var(--space-1) 0;
    font-size: var(--fs-app-sm);
  }

  .enc .transcription-list tbody td:nth-child(n + 2):nth-child(-n + 6)::before {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: var(--fs-app-xs);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
  }

  .enc .transcription-list tbody td:nth-child(2)::before {
    content: 'Project';
  }

  .enc .transcription-list tbody td:nth-child(3)::before {
    content: 'Uploader';
  }

  .enc .transcription-list tbody td:nth-child(4)::before {
    content: 'Duration';
  }

  .enc .transcription-list tbody td:nth-child(5)::before {
    content: 'Created';
  }

  .enc .transcription-list tbody td:nth-child(6)::before {
    content: 'Status';
  }

  .enc .transcription-list tbody td.row-actions {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
  }

  .enc .transcription-list tbody td.row-actions .row-delete {
    opacity: 1;
  }

  /* The one tap target that stays small: it sits in the card's top-right corner,
     and 44px of red-tinted button there would outweigh the title next to it. So
     the hit area grows and the button does not — a transparent 44px square
     centred on a 32px icon, which is exactly what "extend the hit area beyond
     the visual bounds" means. */
  .enc .transcription-list .row-delete {
    position: relative;
  }

  .enc .transcription-list .row-delete::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }

  .enc .row-delete-error {
    position: absolute;
    top: 100%;
    right: 0;
    width: max-content;
    max-width: 60vw;
  }
}

/* ── Phones ─────────────────────────────────────────────────────────────── */
@media (max-width: 760px) {
  /* 48px of gutter is a sixth of a 375px viewport. */
  body.enc {
    --gutter: var(--space-16);
  }

  /* ── Touch targets ──
     36px is a mouse measurement. A finger needs 44 (Apple) / 48 (Material), and
     everything in this app that is tapped rather than clicked is one of these
     four things. Only inside this query: on a desktop 44px controls look
     oversized, and the 36px shell is shadcn's proportion, which is what the rest
     of the sheet is built to.

     Fields get 16px with the height. Anything smaller and iOS zooms the page in
     when the field takes focus — the viewport jumps, and the user has to pinch
     back out to see the rest of the form. */
  .enc button,
  .enc .btn,
  .enc .btn-icon {
    height: 44px;
  }

  .enc .btn-icon {
    width: 44px;
  }

  /* Two classes (0,2,0) each, because the desktop rules that size these are
     two classes too and the block above is only (0,1,1). */

  /* The burger is the only way to the nav on a phone, so it is the last control
     that should be under the finger minimum. */
  .enc .nav-toggle {
    width: 44px;
    height: 44px;
  }

  /* The clamp toggle cannot grow: it is a line of text under the snippet, and a
     44px button there would push the card's rows apart. The hit area grows
     instead — a transparent band centred on the word, 44px tall and never
     narrower than 44px. */
  .enc .row-snippet-toggle {
    position: relative;
  }

  .enc .row-snippet-toggle::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 0;
    min-width: 44px;
    width: 100%;
    height: 44px;
    transform: translateY(-50%);
  }

  /* `date` rides here and nowhere else below: it opens a picker rather than a
     keyboard, so it needs the finger's height without the typing field's
     leading or 16px. Its own rule sets `height: 30px`, and min-height outranks
     height in layout no matter which selector is stronger. */
  .enc input[type='text'],
  .enc input[type='search'],
  .enc input[type='email'],
  .enc input[type='password'],
  .enc input[type='url'],
  .enc input[type='date'],
  .enc textarea,
  .enc select {
    min-height: 44px;
  }

  /* Each option is one tap target, so it takes the finger's height, and the box
     is re-cut to hold three whole rows — 7.5rem over 44px rows ended mid-row,
     which reads as the bottom of the list rather than as more below. */
  .enc .task-assignee-option {
    min-height: 44px;
  }

  .enc .task-assignee-list {
    max-height: 9.375rem;
  }

  /* 16px only where iOS actually zooms — a field you type into. A select opens
     the system picker instead of a keyboard, so it never triggers the zoom, and
     16px there costs the project name room it does not have at 375. */
  .enc input[type='text'],
  .enc input[type='search'],
  .enc input[type='email'],
  .enc input[type='password'],
  .enc input[type='url'],
  .enc textarea {
    font-size: var(--fs-app-md);
  }

  /* The shell centres its text with line-height, so the taller shell needs the
     taller line. Not textarea — it wraps, and 42px leading between two wrapped
     lines is not a paragraph. */
  .enc input[type='text'],
  .enc input[type='search'],
  .enc input[type='email'],
  .enc input[type='password'],
  .enc input[type='url'] {
    line-height: 42px;
  }

  /* A select is not an input: the engine lays its own text box out inside the
     control, and a 42px line there made the box 85px tall instead of 44. Fixed
     height plus the engine's own centring, which is what a native select does
     when you leave its leading alone. */
  .enc select {
    height: 44px;
    line-height: normal;
  }

  .enc .project-picker .refresh-btn {
    width: 44px;
    height: 44px;
    flex-basis: 44px;
  }

  /* Follows the taller control. */
  .enc .record-picker-wrap .upload-controls > span {
    line-height: 44px;
  }

  /* The tab strip is how this page is navigated, so its tabs are tap targets
     before they are labels. Grown rather than hit-expanded: they sit side by side
     in two wrapped rows, where invisible 44px boxes would overlap each other. */
  .enc .enc-tab {
    height: 44px;
  }

  /* Same trade as the row delete: a 44px ✕ inside a 44px search field would be
     the loudest thing in the toolbar, so only its hit area grows. The dialog's
     close ✕ is the same case — a quiet mark next to a title, and the one control
     a mis-tap is most annoying on. */
  /* Only the dialog's: .search-clear is already absolutely positioned inside the
     search bar, and making it relative would drop it out of its own corner. */
  .enc .enc-dialog-close {
    position: relative;
  }

  .enc .search-clear::after,
  .enc .enc-dialog-close::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }

  /* The title and its action stop fitting on one line well before they
     collide, so stack them and let the button span the column. */
  .enc .page-header {
    flex-direction: column;
    align-items: stretch;
  }

  .enc .toolbar .search-bar {
    max-width: none;
  }

  /* "AGREEMENTS REPORT" beside a 88px mark in a 294px sheet wraps to two ragged
     lines. Under the mark it is one line again. */
  .enc .report-brand {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }

  .enc .report-brand .report-kicker {
    margin-left: 0;
  }

  .enc .toolbar .filter-chip {
    flex: 1 1 auto;
  }

  /* "Saved locally — will upload when connected" is a phrase, not a word: on one
     line with the title it overlaps it. The status moves under the title. */
  .enc .record-card .record-card-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }

  /* Side by side, the field is down to ~200px — narrower than the question it
     takes. Ask moves under it and spans the card. */
  .enc .project-ask-form {
    flex-wrap: wrap;
  }

  .enc .project-ask-form .dictation-wrap,
  .enc .project-ask-btn {
    flex: 1 1 100%;
  }
}

/* ── Share dialog ─────────────────────────────────────────────────────────
   The first native <dialog> in the app. No JS positioning, no focus trap of our
   own, no scroll lock: `showModal()` is all four, and enc/share-dialog.js checks
   for it before it offers the button at all.

   A card, not a new surface: same background, hairline and radius as every other
   card on the page, one shadow step up because it floats over them. */
.enc .enc-dialog {
  width: min(480px, calc(100vw - var(--gutter) * 2));
  padding: 0;
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
  color: var(--text-primary);
  box-shadow: var(--shadow-lift);
}

/* Dark enough to say the page underneath is out of reach, blurred enough to say
   the dialog is in front of it rather than replacing it. */
.enc .enc-dialog::backdrop {
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(3px);
}

.enc .enc-dialog-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-24) var(--space-24);
}

.enc .enc-dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.enc .enc-dialog-title {
  margin: 0;
  font-size: var(--fs-app-lg);
}

/* Ghost, square, and not red: Esc already closes the dialog, so this is the
   affordance that says so — not an action competing with the ones below. */
.enc .enc-dialog-close {
  flex: none;
  width: 32px;
  min-width: 0;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-md);
  line-height: 32px;
}

.enc .enc-dialog-close:hover {
  background: var(--bg-raised);
  color: var(--text-primary);
}

.enc .share-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.enc .share-label {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* The field takes the row and Copy keeps its width: a Copy button that grows
   with the dialog is a 200px target for a two-word label. */
.enc .share-link-row {
  display: flex;
  gap: var(--space-2);
}

.enc .share-link-row input {
  flex: 1 1 auto;
  min-width: 0;
}

.enc .share-link-row button {
  flex: none;
}

/* Radios, not a switch. A switch has to be labelled with one of the two modes
   and then reads as on/off for a decision that has no off state. */
.enc .share-choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-field);
  cursor: pointer;
}

.enc .share-choice:hover {
  background: var(--bg-raised);
}

/* The chosen mode stays visible while the pointer is somewhere else — hover
   alone would leave the row that is actually in force looking like the row that
   is not. */
.enc .share-choice:has(:checked) {
  background: var(--bg-raised);
  outline: var(--stroke) solid var(--border-hairline);
}

.enc .share-choice:has(:disabled) {
  cursor: default;
  opacity: 0.5;
}

/* Same treatment the DS has no token for as the task checkbox: the platform's
   shape and hit area, the brand's red when chosen. */
.enc .share-choice input[type='radio'] {
  accent-color: var(--red);
  width: 16px;
  height: 16px;
  margin: 2px 0 0;
  cursor: inherit;
}

.enc .share-choice-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--fs-app-base);
}

.enc .share-choice-hint {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

.enc .share-note {
  margin: 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  line-height: var(--lh-body);
}

/* A note about the choice above it, not a third choice: the extra 4px on top of
   the field's own gap is what keeps it from reading as part of the last row. */
.enc .share-field .share-note {
  margin-top: var(--space-1);
}

.enc .share-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Below the line, after everything else, with the consequence spelled out under
   the button: replacing the link is the one thing in here that cannot be undone.
   The room around it is the point. Sharing is what this dialog is for and this is
   the opposite of sharing, so the line, the 32px above it and the gap between
   the button and its warning are all there to stop the two from reading as one
   block of controls.
   20px and not 16: the warning's line box carries ~4px of half-leading above the
   glyphs, so a 16px gap measures 16 and reads 12. */
.enc .share-rotate {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: flex-start;
  margin-top: var(--space-2);
  padding-top: var(--space-5);
  border-top: var(--stroke) solid var(--border-hairline);
}

/* The DS ships no reduced-motion handling; everything animated here is
   decorative, so it can all stop. */
@media (prefers-reduced-motion: reduce) {
  .enc *,
  .enc *::before,
  .enc *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ── Print: the client report as a PDF ─────────────────────────────────────
   The PDF the sender attaches to a message. There is no server-side renderer and
   no new dependency for one: the browser's own print pipeline produces the file,
   and this block is what it prints.

   Scoped to `.has-report`, which enc/report.js sets on <body> only once the card
   has content — without it a print falls through to the browser's default
   rendering of the page rather than to a blank sheet where the report would be.

   Everything that is the application goes: navigation, the player, the tab strip,
   the session header (the sheet carries the title and the date itself), and the
   report's own toolbar — a paper "Download PDF" button is a joke at the reader's
   expense. Whatever tab was open, what prints is the report: the id selector
   below outranks both the blanket hide and tabs.js's `[hidden]`. */
/* ── Light theme, the two things a token cannot reach ──
 * Everything else on this sheet flips through tokens.css. These two do not: a
 * data URI has no access to var(), and Google's white button was drawn against
 * a near-black page. */

/* The chip's tint is the brand at 18%. Crimson on near-black lands as a dark
   wine that sits under the sentence; Blood Rust on cream at the same strength
   is a pink highlighter and the timestamps end up louder than the titles they
   belong to. Half the strength puts them back under the text. */
[data-theme="light"] .enc .ts {
  background: color-mix(in oklab, var(--red) 9%, transparent);
}

/* The mark is a knockout: white letters around one black counter-form. On
   cream — and on the sign-in card's white — the letters vanish and only the
   counter-form is left, a photographic negative of the wordmark. The DS ships
   the same drawing inked the other way round, so light gets the file, not a
   filter. Both places the mark appears are listed. */
[data-theme="light"] .enc .top-nav .brand,
[data-theme="light"] .enc .top-nav .brand:hover,
[data-theme="light"] .enc .top-nav .brand:active,
[data-theme="light"] .enc .auth-mark,
[data-theme="light"] .enc .auth-mark:link,
[data-theme="light"] .enc .auth-mark:visited,
[data-theme="light"] .enc .auth-mark:hover,
[data-theme="light"] .enc .auth-mark:active {
  background-image: url('/enc/logo-encore-ink.svg');
}

/* The third place the mark appears, and the one that is an <img> rather than a
   background — the report has to survive a print dialog with background
   graphics off. `content` swaps the drawing the element paints while leaving it
   a real image, so the PDF still carries it. */
[data-theme="light"] .enc .report-logo {
  content: url('/enc/logo-encore-ink.svg');
}

/* The sheet is paper, and paper is the lightest thing in the frame. It reads
   --bg-raised, which on cream deliberately goes *darker* than a card because
   that token's day job is hover and selection — so the document came out sunk
   into the card holding it. Lifted the way this theme lifts everything else:
   a lighter tint with a hairline, not a darker fill. */
[data-theme="light"] .enc .report-doc {
  background: var(--surface-2);
  border: var(--stroke) solid var(--border-hairline);
}

/* The drawn glyphs, restated in the light theme's rose: the dark theme's
   %23C9A3A3 measures 2.0:1 on cream, under the 3:1 a control's own icon owes.
   Guarded, because this one is the painted-on chevron: where base appearance is
   available the select has a real `::picker-icon`, recoloured further down, and
   painting the background as well drew a second chevron beside it. */
@supports not (appearance: base-select) {
  [data-theme="light"] .enc select {
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237A5B57' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  }
}

[data-theme="light"] .enc .search-bar::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237A5B57' stroke-width='2' stroke-linecap='round'><circle cx='11' cy='11' r='7'/><path d='M21 21l-4.3-4.3'/></svg>");
}

[data-theme="light"] .enc select::picker-icon {
  content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237A5B57' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
}

/* White on cream is 1.13:1 — the sign-in button loses its edge entirely. The
   fill stays white (it is Google's, not the theme's); only the edge is drawn. */
[data-theme="light"] .enc .btn-google,
[data-theme="light"] .enc .btn-google:hover,
[data-theme="light"] .enc .btn-google:active {
  border-color: var(--border-control);
}

@media print {
  .enc.has-report .top-nav,
  .enc.has-report .nav-panel,
  .enc.has-report .nav-toggle,
  .enc.has-report #status-banner,
  .enc.has-report .session-header,
  .enc.has-report #player-wrap,
  .enc.has-report .enc-tabs,
  .enc.has-report .report-bar,
  /* "Attach the PDF" is an instruction to the sender, printed inside the thing
     being sent. */
  .enc.has-report .card-hint,
  /* Both belong to correcting the document, not to the document. A print
     started from inside edit mode still has to produce the sheet. */
  .enc.has-report .report-edit-error,
  .enc.has-report .report-item-remove,
  .enc.has-report .report-add,
  .enc.has-report .tab-panel {
    display: none;
  }

  .enc.has-report #tab-panel-report {
    display: block;
  }

  /* app.js reveals the player and the banner with an inline `display`, which no
     selector can outrank — the two places on this sheet that need the flag, or
     the PDF opens with a video control bar across the top of page one. */
  .enc.has-report #player-wrap,
  .enc.has-report #status-banner {
    display: none !important;
  }

  /* Ink, not screen: the dark surfaces would print as a solid red-black block,
     and the rose body text is unreadable on white. The document goes back to
     black on white and keeps only its structure. */
  .enc.has-report,
  .enc.has-report .container,
  .enc.has-report #report-card,
  .enc.has-report .report-doc {
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: #fff;
    box-shadow: none;
    color: #000;
  }

  /* The mark is a knockout — white letters around a black counter-form — and
     paper is white whichever theme printed it. Without this the client opens a
     PDF whose letterhead is the counter-form alone. */
  .enc.has-report .report-logo {
    content: url('/enc/logo-encore-ink.svg');
  }

  /* The app's 1440px cap goes; the sheet's measure does not. 68ch on A4 inside
     16mm margins is the page — wider, and the reader loses the line. */
  .enc.has-report,
  .enc.has-report .container,
  .enc.has-report #report-card {
    max-width: none;
  }

  .enc.has-report .report-kicker,
  .enc.has-report .report-section-title {
    color: #555;
  }

  .enc.has-report .report-title { color: #000; }

  .enc.has-report .report-meta,
  .enc.has-report .report-decision-by,
  .enc.has-report .report-task-meta {
    color: #444;
  }

  .enc.has-report .report-para,
  .enc.has-report .report-decision-title,
  .enc.has-report .report-decision-detail,
  .enc.has-report .report-task-title,
  .enc.has-report .report-task-detail {
    color: #111;
  }

  .enc.has-report .report-task-meta .is-missing,
  .enc.has-report .report-task-meta span + span::before {
    color: #777;
  }

  /* The numbers are the whole point of numbering them, and on screen they take a
     translucent white that leaves nothing behind on paper. Brand red, because a
     text colour prints even with "Background graphics" off — which is the default
     in the dialog, and the reason nothing here leans on a fill. */
  .enc.has-report .report-decision::marker {
    color: #790000;
  }

  /* The mark and its rule are the only branding on the sheet, so both have to
     come through. The logo is an <img> (see report.js) and the rule is a border;
     neither depends on background printing. */
  .enc.has-report .report-brand {
    border-bottom-color: #790000;
    break-after: avoid;
  }

  .enc.has-report .report-foot,
  .enc.has-report .report-foot span + span::before {
    border-top-color: #ddd;
    color: #777;
  }

  .enc.has-report .report-foot-mark { color: #444; }

  /* The hairlines survive as rules on paper, a shade heavier than on screen —
     10% white over a dark surface becomes invisible over ink. */
  .enc.has-report .report-section,
  .enc.has-report .report-task + .report-task {
    border-top-color: #ddd;
  }

  /* An agreement split across a page break is an agreement someone misreads. */
  .enc.has-report .report-decision,
  .enc.has-report .report-task,
  .enc.has-report .report-head {
    break-inside: avoid;
  }

  .enc.has-report .report-section-title { break-after: avoid; }

  /* A print started while editing prints the document, not the editor: the field
     outlines and the room reserved for the remove buttons come off the sheet. */
  .enc.has-report .report-doc.is-editing [data-field] {
    margin-inline: 0;
    padding: 0;
    outline: none;
    background: none;
  }

  .enc.has-report .report-doc.is-editing .report-decision,
  .enc.has-report .report-doc.is-editing .report-task {
    padding-right: 0;
  }

  /* Owner and date go back to one line with the middot between them, and a slot
     the sender left empty prints empty rather than printing its prompt. */
  .enc.has-report .report-doc.is-editing .report-task-meta {
    display: block;
  }

  .enc.has-report .report-doc.is-editing .report-task-meta span + span::before {
    content: ' · ';
  }

  .enc.has-report .report-doc.is-editing [data-field]:empty::before {
    content: none;
  }

  /* Editing draws every slot, including the ones the extraction left blank, and on
     paper a blank slot is a blank line, a dangling "Agreed by:" or a lone middot.
     The empty ones come off the sheet, so printing mid-edit prints the document
     that leaving edit mode would have printed. */
  .enc.has-report .report-doc.is-editing .report-title:empty,
  .enc.has-report .report-doc.is-editing .report-para:empty,
  .enc.has-report .report-doc.is-editing .report-decision-detail:empty,
  .enc.has-report .report-doc.is-editing .report-task-detail:empty,
  .enc.has-report .report-doc.is-editing .report-decision-by:has([data-field]:empty),
  .enc.has-report .report-doc.is-editing .report-task-meta:has([data-field="owner"]:empty):has([data-field="due"]:empty) {
    display: none;
  }

  /* One of owner and date filled and the other not: no separator beside nothing. */
  .enc.has-report .report-doc.is-editing .report-task-meta:has([data-field]:empty) span + span::before {
    content: none;
  }

  /* Editing shows both lists even when one is empty, so it can be added to. On
     paper an empty one is a heading with nothing under it. */
  .enc.has-report .report-doc.is-editing .report-section:has(> .report-decisions:empty),
  .enc.has-report .report-doc.is-editing .report-section:has(> .report-tasks:empty) {
    display: none;
  }
}

/* The margin is the sheet's, not the app's — the report is the only thing this
   project prints, so the page box is set once here. */
@page {
  margin: 18mm 16mm;
}
