/* 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;
}

/* A short tab drops the scrollbar and a long one brings it back, and the whole
   column jumped sideways by its width on every switch. Reserve it always. */
html:has(> body.enc) {
  scrollbar-gutter: stable;
}

/* ── 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: it's a crimson near
 * enough to the brand accent 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);
}

/* Empty, it keeps its line, so what is under it stays put (the list's count
   is left out over its first-run empty state). */
.enc .page-meta {
  min-height: 1lh;
  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-label);
  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 :is(.theme-switch, .lang-switch, .view-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 :is(.theme-switch, .lang-switch, .view-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 :is(.theme-switch, .lang-switch, .view-switch) button::before {
  content: '';
  position: absolute;
  inset: -8px;
}

.enc :is(.theme-switch, .view-switch) button svg {
  width: 16px;
  height: 16px;
}

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

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

/* Theme and language, one row: two clusters of the same shape. */
.enc .nav-prefs {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Two letters instead of an icon: the same square, the label in small caps
   weight so EN and RU read as marks, not words. */
.enc .lang-switch button {
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
}

/* 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 .nav-prefs {
  align-self: flex-start;
  margin-bottom: var(--space-1);
}

.enc .top-nav[data-nav-open] .nav-panel :is(.theme-switch, .lang-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='/projects.html']::before,
.enc .top-nav a[href='/record.html']::before,
.enc .top-nav a[href='/upload.html']::before,
.enc .top-nav a[href='/admin.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='/projects.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='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 .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");
}

.enc .top-nav a[href='/admin.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='M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z'/%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 "whisperwise") 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('/brand/logo.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 {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

/* A person, so a circle. The initial is the fallback: the photo sits over it and
   removes itself if it fails, which leaves the letter, never a broken image. */
.enc .user-avatar {
  position: relative;
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  overflow: hidden;
  border-radius: 50%;
  background: var(--bg-active);
  color: var(--text-primary);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-semibold);
  line-height: 1;
}

.enc .user-avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.enc .user-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 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;
}

/* The bar's view-transition-name makes it a stacking context, so the sheet's
   z-index stops at the bar: lifted only while open, or the search and the rows
   that come after it in the page paint over the sheet. */
.enc .top-nav[data-nav-open] {
  z-index: 30;
}

.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 {
    /* The column and the top bar are cards, ringed, this far off the window's
       edges and each other. What the column reserves is its 232px card and
       the gap on its left. */
    --shell-gap: var(--space-2);
    --sidebar-w: 240px;
    /* 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: var(--shell-gap) auto var(--shell-gap) var(--shell-gap);
    z-index: 20;
    flex-direction: column;
    align-items: stretch;
    width: calc(var(--sidebar-w) - var(--shell-gap));
    height: auto;
    margin: 0;
    padding: var(--space-5) var(--space-3);
    /* Its rows' corner plus the 12px it keeps round them. */
    border-radius: calc(var(--radius-field) + var(--space-3));
    box-shadow: inset 0 0 0 1px 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: var(--control-h);
    padding: 0 var(--space-3);
    border-radius: var(--radius-field);
  }

  /* The bar's card's height from the column's top, both cards starting the gap
     down, so the mark sits on the bar's centre line with its switches; the
     first row then starts on the page title's line. */
  .enc .top-nav .brand {
    width: 108px;
    height: calc(var(--topbar-h, 64px) - var(--shell-gap));
    margin: calc(-1 * var(--space-5)) var(--space-3) var(--space-5);
  }

  /* The column stretches its children; three icons do not need 208px. */
  .enc .top-nav .nav-prefs {
    align-self: flex-start;
  }

  /* Signed in as whom — photo and name on one row of the list; a long name
     ends in an ellipsis (.user-name) rather than wrapping in a column this
     narrow. */
  .enc .top-nav .user-email {
    color: var(--text-primary);
  }

  /* 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);
    border-radius: 0;
    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;
  }

  /* And the account block too: the switches and Sign in on the bar's line. */
  .enc .top-nav-public .nav-right {
    flex-direction: row;
    align-items: center;
    gap: var(--space-3);
    margin: 0 0 0 auto;
    padding: 0;
  }

  .enc .top-nav-public .nav-prefs {
    align-self: center;
  }
}

/* ── Shell: no paint before the nav ──
 * Every page marked data-shell builds its nav from /api/me, so its first paint
 * used to be the page without sidebar and top bar, which then jumped in a round
 * trip later — on every navigation, and plainest on a language switch, which
 * reloads. Nothing is painted until the nav exists, so the browser keeps the
 * previous page on screen (paint holding) and swaps in a finished one. The
 * animation is the safety net: if /api/me never answers, the page shows anyway.
 */
body.enc[data-shell]:not(:has(> .top-nav)) {
  visibility: hidden;
  animation: enc-shell-reveal 0s 2s forwards;
}

@keyframes enc-shell-reveal {
  to { visibility: visible; }
}

/* ── Shell: the top bar (topbar.js) ──
 * Page-wide controls — theme, language, the assistant — over the content and
 * right of the sidebar. Desktop only: below 1000px the nav bar is already the
 * top bar, so the switches sit in its panel and the assistant beside the burger.
 */
.enc .top-bar {
  display: none;
}

/* An icon button with the switches' height and glass, so the three read as one
   row of controls. Built the way a switch is — a 28px cell, 4px of padding,
   the border — so a hairline rounds the same on both at any pixel ratio. */
.enc .assistant-toggle {
  box-sizing: content-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: var(--space-1);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-surface);
  background: var(--glass-bg);
  color: var(--text-primary);
}

.enc .assistant-toggle svg {
  flex: none;
  color: var(--text-brand);
}

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

.enc .assistant-toggle[aria-expanded="true"] {
  background: var(--bg-active);
}

/* The fold is housekeeping, not a tool: a bare icon in the switches' ink, with
   no plate even under the pointer, at the bar's other end by the sidebar. The
   36px box puts the icon 21px off the edge, as near as the bar's 19px above it. */
.enc .sidebar-toggle {
  width: 36px;
  height: 36px;
  margin-right: auto;
  padding: 0;
  border-color: transparent;
  background: transparent;
  color: var(--text-muted-strong);
}

.enc .sidebar-toggle:is(:hover, :active) {
  background: transparent;
  color: var(--text-primary);
}

/* In the narrow bar: an icon the burger's size, just left of it. */
.enc .top-nav > .assistant-toggle {
  box-sizing: border-box;
  width: 40px;
  height: 40px;
  margin-left: auto;
  padding: 0;
  border-color: transparent;
  border-radius: var(--radius-field);
  background: transparent;
}

.enc .top-nav > .assistant-toggle + .nav-toggle {
  margin-left: 0;
}

@media (min-width: 1000px) {
  /* Read by what sticks to the window, so it sticks under the bar: its 56px
     card and the gap above it. A number, not a calc(): scripts read it. */
  body.enc:has(.top-bar) {
    --topbar-h: 64px;
  }

  /* The controls hold the bar's ends, not the content's measure: when the
     sidebar folds the column re-centres, and controls that followed it slid.
     Both ends sit 9px inside the card, what it keeps above and below the 38px
     switches, so its corner nests round theirs. The bar is the strip from the
     window's top, gap and all, so nothing shows through it on the way under;
     the card is drawn on it. */
  .enc .top-bar {
    position: sticky;
    top: 0;
    z-index: 21;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    height: var(--topbar-h);
    padding: var(--shell-gap) calc(var(--shell-gap) + 9px) 0;
    background: var(--bg-chrome);
  }

  .enc .top-bar::before {
    content: '';
    position: absolute;
    inset: var(--shell-gap) var(--shell-gap) 0;
    z-index: -1;
    border-radius: calc(var(--radius-surface) + 9px);
    box-shadow: inset 0 0 0 1px var(--border-hairline);
    pointer-events: none;
  }

  .enc .top-bar-end {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }
}

/* ── Shell: the folded sidebar (sidebar.js) ──
 * A 64px card is the 40px row plus the column's 12px each side, so every icon
 * keeps its place and only the edge moves: the labels are cut off by it rather
 * than faded out, and the wordmark gives way to its two w's. 72 with the gap.
 */
@media (min-width: 1000px) {
  body.enc[data-nav-collapsed] {
    --sidebar-w: 72px;
  }

  .enc .top-nav {
    overflow-x: hidden;
  }

  body.enc[data-nav-ready] {
    transition: padding var(--dur-base) var(--ease-out);
  }

  body.enc[data-nav-ready] .top-nav {
    transition: width var(--dur-base) var(--ease-out);
  }

  .enc .top-nav :is(a, .link-button) {
    overflow: hidden;
  }

  /* The label then starts 8px past the row's edge: right on it, the first
     letter's anti-aliasing showed as a sliver beside the active plate. */
  body.enc[data-nav-collapsed] .top-nav :is(a, .link-button)::before {
    margin-right: var(--space-4);
  }

  /* The avatar is 24px to the icons' 16: 4px less padding centres it on them,
     and its name starts past the edge as the labels do. */
  body.enc[data-nav-collapsed] .top-nav .user-email {
    gap: var(--space-4);
    padding-left: var(--space-2);
  }

  /* The same drawing at the same scale, so the letters do not change size: the
     wordmark is 132px for 591 units (brand.css), so the w's 173.6 take 38.8px.
     The 0.6px left centres them in the 40px column, on the icons' axis. */
  body.enc[data-nav-collapsed] .top-nav .brand {
    width: 38.8px;
    margin-inline: 0.6px 0;
    background-image: url('/brand/logo-ww.svg');
  }

  [data-theme="light"] body.enc[data-nav-collapsed] .top-nav .brand {
    background-image: url('/brand/logo-ww-ink.svg');
  }
}

@media print {
  .enc .top-bar {
    display: none;
  }
}

/* ── Shell: the minutes meter (plan-meter.js) ──
 * The foot of the column, over the account row: the plan, a bar of what is
 * left, the minutes in words. A reading, not a destination, so it keeps the
 * rows' 12px inset and none of their 40px height. Ink while there is room,
 * amber from 80% used, the words red once the month's minutes are gone (the
 * bar is empty by then). "Upgrade plan" is a row like the others, for the
 * owner only, in the brand's colour: it is the one way out of running low.
 */
.enc .plan-meter {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}

.enc .top-nav a.plan-meter-upgrade {
  color: var(--text-brand);
}

.enc .top-nav a.plan-meter-upgrade::before {
  content: '';
  flex: 0 0 16px;
  height: 16px;
  margin-right: var(--space-2);
  background-color: currentcolor;
  mask: 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%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='m16 12-4-4-4 4'/%3E%3Cpath d='M12 16V8'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
}

.enc .top-nav a.plan-meter-link {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  height: auto;
  padding-block: var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  line-height: var(--lh-tight);
  white-space: normal;
}

.enc .plan-meter-plan {
  color: var(--text-primary);
  font-weight: var(--fw-medium);
}

.enc .plan-meter-bar {
  display: block;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--border-hairline-strong);
  overflow: hidden;
}

.enc .plan-meter-bar > span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--text-primary);
}

.enc .plan-meter[data-state='low'] .plan-meter-bar > span {
  background: var(--amber);
}

.enc .plan-meter[data-state='out'] .plan-meter-text {
  color: var(--status-failed-text);
}

/* Folded, the bar alone stays, on the icons' 40px: the words are its title. */
@media (min-width: 1000px) {
  body.enc[data-nav-collapsed] .plan-meter :is(.plan-meter-plan, .plan-meter-text) {
    display: none;
  }

  body.enc[data-nav-collapsed] .top-nav a.plan-meter-link {
    padding-inline: var(--space-2);
  }
}

/* ── Shell: the team switcher (teams.js) ──
 * Which team the page is showing is the one fact every other row depends on,
 * so it heads the column, above the destinations, on a plate of its own — a
 * context, not one more place to go. The square sits 8px in, on the icons'
 * axis, so in the folded rail it is what stays, the way the avatar does.
 * Nested radii: the square is 4px, the plate's 8px of padding makes it 12.
 */
.enc .team-switch {
  margin-bottom: var(--space-3);
}

.enc .team-switch-btn {
  display: flex;
  justify-content: flex-start;
  gap: var(--space-2);
  width: 100%;
  height: 40px;
  padding: 0 var(--space-2);
  overflow: hidden;
  border: 0;
  border-radius: calc(4px + var(--space-2));
  background: transparent;
  box-shadow: inset 0 0 0 var(--stroke) var(--border-hairline);
  color: var(--text-primary);
  font-weight: var(--fw-semibold);
  text-align: left;
}

.enc .team-switch-btn:hover {
  background: var(--bg-raised);
}

.enc .team-switch-btn:active,
.enc .team-switch-btn[aria-expanded='true'] {
  background: var(--bg-active);
}

/* The button's line-height is the font size; clipped to it, the name would
   lose its descenders, so the line gets its own height. */
.enc .team-switch-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.5;
}

.enc .team-switch-btn > svg {
  color: var(--text-muted);
}

/* A team, so a square: a person is the circle below. */
.enc .team-mark {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 4px;
  background: var(--bg-brand);
  color: var(--white);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-semibold);
  line-height: 1;
}

@media (min-width: 1000px) {
  /* Folded, the plate would be a frame around a square; the name starts past
     the edge, as the labels do. */
  body.enc[data-nav-collapsed] .team-switch-btn {
    gap: var(--space-4);
    box-shadow: none;
  }
}

/* In the burger's panel: the full width and a finger's height. */
.enc .top-nav[data-nav-open] .team-switch {
  align-self: stretch;
}

.enc .top-nav[data-nav-open] .team-switch-btn {
  height: 44px;
}

/* The menu is a popover, in the top layer, placed by menu.js: the sidebar
   clips what overflows it. No `display` here — a closed popover is hidden by
   the browser's own `display: none`, which any author value would override.
   «Add meeting», the meeting's type and its project open the same card. */
.enc :is(.team-menu, .add-menu, .template-menu, .project-menu) {
  position: fixed;
  inset: auto;
  z-index: 40;
  margin: 0;
  padding: var(--space-2);
  overflow-y: auto;
  border: var(--stroke) solid var(--border-hairline);
  border-radius: calc(var(--radius-field) + var(--space-2));
  background: var(--surface-2);
  box-shadow: var(--shadow-lift);
  color: var(--text-primary);
  animation: enc-drop-in var(--dur-fast) var(--ease-out);
}

.enc .team-menu-label {
  padding: var(--space-2) var(--space-2) var(--space-1);
  color: var(--text-muted);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
}

.enc :is(.team-menu-sep, .template-menu-sep, .project-menu-sep) {
  height: var(--stroke);
  margin: var(--space-2) calc(-1 * var(--space-2));
  background: var(--border-hairline);
}

/* A team in the list: its square, its name, what you are in it and how many. */
.enc .team-item {
  display: flex;
  justify-content: flex-start;
  gap: var(--space-3);
  width: 100%;
  height: auto;
  padding: var(--space-2);
  border: 0;
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--text-primary);
  font-weight: var(--fw-regular);
  text-align: left;
}

.enc .team-item-text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.enc :is(.team-item-name, .team-item-meta) {
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.3;
}

.enc .team-item-name {
  font-weight: var(--fw-medium);
}

.enc .team-item-meta {
  color: var(--text-muted);
  font-size: var(--fs-app-xs);
}

.enc :is(.team-item-end, .template-item-end, .project-item-end) {
  flex: none;
  display: inline-flex;
  min-width: 16px;
  justify-content: flex-end;
  color: var(--text-brand);
}

.enc .team-item:not([aria-checked='true']) .team-item-end svg,
.enc .template-item:not([aria-checked='true']) .template-item-end svg,
.enc .project-item:not([aria-checked='true']) .project-item-end svg {
  visibility: hidden;
}

.enc .team-item-join {
  padding: 2px var(--space-2);
  border-radius: var(--radius-control);
  background: color-mix(in oklab, var(--text-brand) 16%, transparent);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-medium);
}

/* Settings, invite, create: rows with an icon on the squares' axis. Prefixed
   with .top-nav because the menu lives in it, and its links would otherwise
   take the sidebar's row and hover rules. */
.enc .top-nav .team-action {
  display: flex;
  justify-content: flex-start;
  gap: var(--space-2);
  width: 100%;
  height: var(--control-h);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-regular);
}

/* A row that leads where a sidebar row does (Activity) brings its own icon;
   the sidebar's glyph, attached by href, stays off it. */
.enc .top-nav .team-menu .team-action::before {
  content: none;
}

.enc .top-nav .team-menu :is(.team-item, .team-action):is(:hover, :focus-visible, :active) {
  background: var(--bg-menu-hover);
  color: var(--text-primary);
  outline-offset: -2px;
}

/* ── Shell: «Add meeting» (add-meeting.js) ──
 * The app's first action, under the team: the brand button, full width. The
 * plus sits 12px in, on the rows' icon axis, so in the folded rail the button
 * closes to a 40px square round it and the label is cut off by the edge, as the
 * rows' are.
 */
.enc .add-meeting {
  margin-bottom: var(--space-3);
}

/* The rows it stands in for (Record, Upload) are hidden by add-meeting.js; the
   nav's links are flex, which beats the bare `[hidden]`. */
.enc .top-nav a[hidden] {
  display: none;
}

.enc .add-meeting-btn {
  justify-content: flex-start;
  width: 100%;
  height: 40px;
  padding-inline: var(--space-3);
  overflow: hidden;
}

@media (min-width: 1000px) {
  body.enc[data-nav-collapsed] .add-meeting-btn {
    gap: var(--space-4);
  }
}

/* In the narrow bar: the plus alone, filled, left of the assistant and the
   burger's size, as the assistant is. */
.enc .top-nav > .add-meeting {
  margin: 0 var(--space-1) 0 auto;
}

.enc .top-nav > .add-meeting .add-meeting-btn {
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
}

.enc .top-nav > .add-meeting .add-meeting-label {
  display: none;
}

.enc .top-nav > .add-meeting + .assistant-toggle {
  margin-left: 0;
}

/* A way in: its icon in a 24px cell, so it sits on the team squares' axis,
   what it is, and what it takes on the line below. The menu lives in the nav,
   so the rows restate what the nav's links would otherwise lend them — height,
   pill, glyph; on a project's page, what a page's links and buttons lend. */
.enc :is(.top-nav, .project-add) .add-menu .add-item,
.enc .template-menu .template-item,
.enc .project-menu .project-item {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  gap: var(--space-3);
  width: 100%;
  height: auto;
  padding: var(--space-2);
  border: 0;
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-regular);
  text-align: left;
  text-decoration: none;
  white-space: normal;
}

.enc .top-nav .add-menu .add-item::before {
  content: none;
}

.enc :is(.top-nav, .project-add) .add-menu .add-item:is(:hover, :focus-visible, :active),
.enc .template-menu .template-item:is(:hover, :focus-visible, :active),
.enc .project-menu .project-item:is(:hover, :focus-visible, :active) {
  background: var(--bg-menu-hover);
  color: var(--text-primary);
  outline-offset: -2px;
}

.enc :is(.add-item-icon, .template-item-icon) {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 20px;
  color: var(--text-brand);
}

.enc :is(.add-item-text, .template-item-text, .project-item-text) {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.enc :is(.add-item-name, .template-item-name, .project-item-name) {
  font-weight: var(--fw-medium);
  line-height: 20px;
}

.enc :is(.add-item-meta, .template-item-meta, .project-item-meta) {
  color: var(--text-muted);
  font-size: var(--fs-app-xs);
  line-height: 1.4;
}

/* ── Teams: the create dialog ──
 * The share dialog's card, with a form inside: a sentence on what a team is,
 * one field, the way out on the left of the way forward. */
.enc .team-dialog-lead {
  margin: 0;
  color: var(--text-muted-strong);
  line-height: 1.5;
}

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

.enc .team-label {
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-medium);
}

.enc .team-field textarea {
  min-height: 88px;
  resize: vertical;
}

.enc :is(.team-hint, .team-error) {
  margin: 0;
  font-size: var(--fs-app-sm);
  line-height: 1.5;
}

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

.enc .team-error {
  color: var(--status-failed-text);
}

.enc .team-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* On a phone the buttons split the row between them. Two long labels that will
   not share it stack, and wrap-reverse puts the second — the action — on top,
   under the thumb, with the way out below it. */
@media (max-width: 640px) {
  .enc .team-dialog-actions {
    flex-wrap: wrap-reverse;
  }

  .enc .team-dialog-actions > button {
    flex: 1 1 0;
    min-width: max-content;
  }
}

/* ── Toasts (toast.js) ──
 * Centred on the content, not the window: with the sidebar open the window's
 * centre is 116px left of where the reader is looking. */
.enc .enc-toasts {
  position: fixed;
  bottom: var(--space-5);
  left: calc(var(--sidebar-w, 0px) + (100vw - var(--sidebar-w, 0px)) / 2);
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  width: max-content;
  max-width: calc(100vw - var(--space-5));
  translate: -50% 0;
  pointer-events: none;
}

.enc .enc-toast {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4) var(--space-3) var(--space-3);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--surface-2);
  box-shadow: var(--shadow-lift);
  color: var(--text-primary);
  font-size: var(--fs-app-sm);
  line-height: 1.4;
  pointer-events: auto;
  animation: enc-toast-in var(--dur-base) var(--ease-out);
  transition: opacity 240ms var(--ease-out), translate 240ms var(--ease-out);
}

/* Done in the status green: a check in the brand's red would read as a fault. */
.enc .enc-toast > svg {
  flex: none;
  color: var(--status-done);
}

.enc .enc-toast[data-tone='error'] > svg {
  color: var(--status-failed-text);
}

/* No connection: a wait, not a fault — the colour of things in progress. */
.enc .enc-toast[data-tone='offline'] > svg {
  color: var(--status-processing);
}

/* The way back — Undo — after the words, as Try again is under a paragraph:
   underlined in the line's colour, set apart by more than a word's space. */
.enc .enc-toast-action,
.enc .enc-toast-action:hover {
  flex: none;
  margin-left: var(--space-2);
  color: inherit;
  font-size: inherit;
  font-weight: var(--fw-medium);
  line-height: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.enc .enc-toast[data-leaving] {
  opacity: 0;
  translate: 0 4px;
}

@keyframes enc-toast-in {
  from { opacity: 0; translate: 0 8px; }
}

/* ── Team page (team.js) ──
 * A settings column: the team's square and name at the head, then one section
 * per question — how people get in, who is in, what it is called, the way out.
 * The square at the head is the switcher's at twice the size: 8px corners, the
 * same sixth of its side as the 4px on the 24px one. */
.enc .team-head {
  display: flex;
  align-items: center;
  gap: var(--space-16);
}

.enc .team-head .team-mark,
.enc .team-head .team-mark-skel {
  width: 48px;
  height: 48px;
  border-radius: 8px;
  font-size: var(--fs-app-lg);
}

.enc .team-head-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.enc .team-head h1 {
  overflow-wrap: anywhere;
}

.enc .team-head-text > .skel:first-child {
  height: 30px;
  margin-bottom: var(--space-2);
}

.enc .team-section {
  margin-top: var(--space-6);
  scroll-margin-top: calc(var(--topbar-h, 0px) + var(--space-4));
}

.enc .team-section-head {
  margin-bottom: var(--space-16);
}

.enc .team-section-head h2 {
  font-size: var(--fs-app-lg);
}

.enc .team-section-lead {
  max-width: 60ch;
  margin: var(--space-1) 0 0;
  color: var(--text-muted-strong);
}

/* People: one card, a row a person. The rows share the list's columns
   (subgrid), so "Active 2 days ago" and the roles line up down the card
   whatever the names' lengths; a row with nothing to remove keeps its cell. */
.enc .team-people {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  column-gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
  box-shadow: var(--shadow-tile);
}

.enc .team-person {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: center;
  min-height: 64px;
  padding: var(--space-3) var(--space-4);
}

.enc .team-person + .team-person {
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .team-person-who {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.enc .team-person-avatar {
  width: 32px;
  height: 32px;
  font-size: var(--fs-app-sm);
}

.enc .team-person-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.enc .team-person-name {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  font-weight: var(--fw-semibold);
  overflow-wrap: anywhere;
}

.enc .team-person-name .pill {
  display: inline-block;
  flex: none;
  font-weight: normal;
}

.enc .team-person-email {
  overflow: hidden;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.enc .team-person-seen {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  white-space: nowrap;
}

/* The toolbar's compact select, not the 44px form field: in a row of text a
   field that tall reads as a form to fill. One width, so Admin and Member
   line up; a role that is only read starts where the selects' words do. */
.enc .team-person-role select {
  width: 128px;
}

.enc .team-person-role-text {
  padding-left: calc(var(--space-3) + var(--stroke));
  color: var(--text-muted-strong);
}

.enc .team-person-end {
  justify-self: end;
}

/* Someone invited is not in yet: an envelope in a dashed ring, not a face. */
.enc .team-invite-avatar {
  border: var(--stroke) dashed var(--border-control);
  background: transparent;
  color: var(--text-muted);
}

/* Who, when it expires, what to do: the role is on the line under the email. */
.enc .team-invites {
  grid-template-columns: minmax(0, 1fr) auto auto;
}

.enc .team-invite-actions {
  display: flex;
  gap: var(--space-4);
}

/* ── Team page: inviting ──
 * One card, three ways in, hairlines between them. The email form ends on its
 * button at the right, where reading ends; the two open ways are settings, so
 * each is a row with its switch on the right edge. */
.enc .team-card {
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
  box-shadow: var(--shadow-tile);
}

.enc .team-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
}

.enc .team-block + .team-block {
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .team-block textarea {
  min-height: 88px;
  resize: vertical;
}

.enc :is(.team-invite-send, .team-link-foot) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
}

.enc .team-invite-send {
  justify-content: flex-end;
}

.enc .team-invite-as {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

.enc :is(.team-invite-send, .team-link-foot) select {
  width: 128px;
}

/* "New link" is a different act from the role beside it: it goes to the far
   end, so "Joins as Member" and "New link" never read as one phrase. */
.enc .team-link-foot .link-button {
  margin-left: auto;
}

.enc .team-switch-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  cursor: pointer;
}

.enc .team-switch-row:has(.switch:disabled) {
  cursor: default;
}

.enc .team-switch-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: 64ch;
}

.enc .team-switch-title {
  font-weight: var(--fw-medium);
}

.enc .team-switch-row + .share-link-row {
  margin-top: var(--space-2);
}

/* Copy stands as tall as the field it copies from, so the row has one edge. */
.enc .team-block .share-link-row button {
  height: var(--field-h);
}

/* The name: a field as wide as a long name, not as the page, with Save at its
   end; no card around a single field. */
.enc .team-name-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 32rem;
}

.enc .team-name-form button {
  height: var(--field-h);
}

/* The way out: what happens on the left, the act on the right, like a switch
   row. The controls never shrink; the words wrap instead. */
.enc .team-exit-row {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.enc .team-exit-controls {
  display: flex;
  flex: none;
  gap: var(--space-2);
}

.enc .team-exit-controls select {
  width: 200px;
}

/* A phone shows the link whole and puts the act under it, full width, where
   the thumb is; "Invite as" moves to the start of its line, above Send, and
   its role takes the rest of that line. */
@media (max-width: 640px) {
  .enc .team-block .share-link-row {
    flex-wrap: wrap;
  }

  .enc :is(.team-invite-send, .team-link-foot) select {
    flex: 1 1 auto;
    width: auto;
  }

  .enc .team-block .share-link-row :is(input, button),
  .enc .team-invite-send button[type='submit'] {
    flex: 1 1 100%;
  }

  .enc .team-invite-send {
    justify-content: flex-start;
  }

  .enc .team-exit-row {
    flex-direction: column;
    align-items: stretch;
  }

  .enc .team-exit-controls > * {
    flex: 1 1 0;
    min-width: 0;
  }
}

/* ── Team page: plan and minutes (billing-section.js) ──
 * The plan with its minutes, who used them, then every plan side by side. The
 * bar is the sidebar's, thicker, in the same colours. Four tiles in a row when
 * the column has room for them, two below that, one on a phone: by the
 * section's own width, so the sidebar folding changes nothing. The team's own
 * plan is outlined in ink, not filled, so its price still reads. */
.enc .billing-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.enc :is(.billing-body, .billing-tile) :is(h3, h4, p) {
  margin: 0;
}

.enc .billing-current {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  scroll-margin-top: calc(var(--topbar-h, 0px) + var(--space-4));
}

.enc .billing-current-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

/* A phone puts the plan's act under its name, the column wide. */
@media (max-width: 640px) {
  .enc .billing-current-head > button {
    flex: 1 1 100%;
  }
}

.enc .billing-plan-name {
  font-size: var(--fs-app-lg);
}

/* Focused after a change so a screen reader reads the new plan; text, not a
   control, so no ring. */
.enc .billing-plan-name:focus {
  outline: none;
}

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

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

.enc .billing-minutes-text {
  font-weight: var(--fw-medium);
}

.enc .billing-body .plan-meter-bar {
  height: 8px;
}

.enc .billing-minutes[data-state='low'] .plan-meter-bar > span {
  background: var(--amber);
}

.enc .billing-minutes[data-state='out'] .billing-minutes-text {
  color: var(--status-failed-text);
}

.enc .billing-note {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

.enc .billing-dates {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  color: var(--text-secondary);
}

.enc .billing-dates:empty {
  display: none;
}

.enc .billing-alert {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.enc .billing-alert p {
  flex: 1 1 24ch;
}

/* Who used the minutes: a name, a bar against the one who used most, the
   minutes, in three columns shared by every row. */
.enc .billing-usage-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(64px, 30%) auto;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.enc .billing-usage-list li {
  display: contents;
}

/* One line: an address broken mid-word reads as two. The whole one is its title. */
.enc .billing-usage-who {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A phone gives the name the row, the minutes beside it and the bar under both. */
@media (max-width: 640px) {
  .enc .billing-usage-list {
    grid-template-columns: minmax(0, 1fr);
  }

  .enc .billing-usage-list li {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: var(--space-2) var(--space-3);
  }

  .enc .billing-usage-list li .plan-meter-bar {
    grid-column: 1 / -1;
    grid-row: 2;
  }
}

.enc .billing-usage-minutes {
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.enc .billing-usage-list .plan-meter-bar {
  height: 4px;
}

.enc .billing-plans {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-2);
  container-type: inline-size;
}

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

/* Monthly | Yearly, shaped as the theme and language switches. */
.enc .billing-interval {
  display: inline-flex;
  gap: 1px;
  padding: var(--space-1);
  border: var(--stroke) solid var(--border-hairline);
  /* The buttons' corner plus the padding and border around them. */
  border-radius: calc(var(--radius-control) + var(--space-1) + var(--stroke));
  background: var(--bg-card);
}

.enc .billing-interval button,
.enc .billing-interval button:active {
  height: 32px;
  padding-inline: var(--space-3);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

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

.enc .billing-interval button[aria-checked='true'] {
  background: var(--bg-active);
  color: var(--text-primary);
}

.enc .billing-tiles {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

@container (min-width: 420px) {
  .enc .billing-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container (min-width: 760px) {
  .enc .billing-tiles {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* A tile's six parts sit on the row's lines (subgrid): names, prices, buttons
   and minutes line up across the plans whatever each part's length. */
.enc .billing-tile {
  display: grid;
  grid-row: span 6;
  grid-template-rows: subgrid;
  /* The tile's width, not its widest line's: nothing pushes into the padding. */
  grid-template-columns: minmax(0, 1fr);
  row-gap: var(--space-3);
  padding: var(--space-4);
}

.enc .billing-tile[data-current] {
  border-color: var(--text-primary);
}

.enc .billing-tile[data-pick] {
  border-color: var(--accent);
}

.enc .billing-tile-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.enc .billing-tile .billing-pick,
.enc .billing-save {
  border-color: transparent;
  background: var(--action-paint) border-box;
  color: var(--on-action);
  font-weight: var(--fw-medium);
}

.enc .billing-save {
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-app-xs);
}

.enc .billing-tile-for {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

.enc .billing-tile-minutes {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: var(--space-3);
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .billing-tile-figure {
  color: var(--text-primary);
  font-size: var(--fs-app-md);
  font-weight: var(--fw-semibold);
}

.enc .billing-tile-gains {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.enc .billing-tile-also {
  color: var(--text-primary);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-medium);
}

/* The plan a change is booked to: marked, but not as the one the team is on. */
.enc .billing-tile[data-next] {
  border-style: dashed;
  border-color: var(--text-muted);
}

.enc .billing-tile-name {
  font-size: var(--fs-app-md);
}

.enc .billing-tile-price {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.enc .billing-amount {
  color: var(--text-primary);
  font-size: var(--fs-app-xl);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
}

/* What the plan comes to for this team, read last: who, then the sum and the
   minutes it would pay for. */
.enc .billing-tile-who {
  margin-top: var(--space-2);
}

.enc .billing-tile-team {
  color: var(--text-primary);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-medium);
}

.enc .billing-notes {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* What a plan adds, each line ticked. */
.enc .billing-limits {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  color: var(--text-secondary);
  font-size: var(--fs-app-sm);
  list-style: none;
}

.enc .billing-limits li {
  display: flex;
  gap: var(--space-2);
}

.enc .billing-limits li::before {
  content: '';
  flex: 0 0 16px;
  height: 16px;
  margin-top: 1px;
  background-color: var(--text-muted);
  mask: 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='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
}

.enc .billing-tile-action {
  display: flex;
  align-items: center;
  justify-content: center;
}

.enc .billing-tile-action > button:not(.link-button) {
  width: 100%;
}

.enc .billing-tile-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-h);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-medium);
}

/* ── Settings (/account.html) ──
   Built from the team page's parts — sections, cards, rows — so the two pages
   of settings read as one place. */
.enc .settings-page :is(.tab-panel, .calendar-body, .telegram-body) > .team-section:first-child {
  margin-top: var(--space-2);
}

/* Who is signed in: the photo, the name, the email, as the sidebar shows them
   but larger. Long ones wrap; nothing is cut. */
.enc .settings-person {
  flex-direction: row;
  align-items: center;
  gap: var(--space-3);
}

.enc .settings-avatar {
  width: 48px;
  height: 48px;
  font-size: var(--fs-app-lg);
}

.enc .settings-person-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.enc .settings-person-name {
  font-weight: var(--fw-semibold);
  overflow-wrap: anywhere;
}

.enc .settings-person-email {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  overflow-wrap: anywhere;
}

.enc :is(.settings-row-title, .settings-label) {
  font-weight: var(--fw-medium);
}

.enc :is(.settings-you-form, .settings-token-form) :is(.team-hint, .team-error),
.enc .settings-google {
  margin: 0;
}

/* Out to Google: the arrow says the link leaves the app. */
.enc .settings-google a {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-weight: var(--fw-medium);
}

/* A setting a row: what it is on the left, its choices at the right edge. */
.enc .settings-row {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
}

/* The choices: native radios on the tab strip's figure — a trough, the chosen
   one a lighter pill. The radios stay in the tree (arrow keys, screen readers)
   and out of sight; the focus ring goes round the pill. */
.enc .settings-segments {
  display: inline-flex;
  gap: var(--space-1);
  padding: var(--space-1);
  border-radius: calc(var(--radius-control) + var(--space-1));
  background: var(--bg-canvas);
  box-shadow: inset 0 0 0 var(--stroke) var(--border-hairline);
}

.enc .settings-segments label {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tab-h);
  padding: 0 var(--space-3);
  border-radius: var(--radius-control);
  color: var(--text-muted-strong);
  font-weight: var(--fw-medium);
  text-align: center;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

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

.enc .settings-segments label:has(input:checked) {
  background: var(--bg-active);
  box-shadow: inset 0 0 0 var(--stroke) var(--border-hairline);
  color: var(--text-primary);
}

.enc .settings-segments label:has(input:focus-visible) {
  outline: var(--stroke-strong) solid var(--focus-ring);
  outline-offset: 2px;
}

.enc .settings-segments input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.enc .settings-segments svg {
  flex: none;
  width: 16px;
  height: 16px;
}

/* Tokens: the new one stands out until it is copied; the list is a card, a
   row a token, Revoke at the end of it. */
.enc .settings-token-form {
  margin-bottom: var(--space-4);
}

.enc .settings-new-token {
  margin-bottom: var(--space-4);
  border-color: var(--border-brand);
}

.enc .settings-new-token-title {
  margin: 0;
  font-weight: var(--fw-semibold);
}

.enc .settings-token-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The calendar's and Telegram's account is a row like a token's: who, since when, the way out. */
.enc :is(.settings-token, .calendar-account, .telegram-account) {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.enc .settings-token-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.enc .settings-token-name {
  font-weight: var(--fw-medium);
  overflow-wrap: anywhere;
}

.enc .settings-token-dates {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

.enc .settings-token-dates > span + span::before {
  content: ' · ';
}

.enc :is(.settings-token, .calendar-account, .telegram-account) button {
  flex: none;
}

.enc .settings-token-list .empty-line {
  color: var(--text-muted);
}

/* While Google refuses (calendar-section.js), or the calendar has no team
   (calendar-rule.js), the card opens with why, tinted as an error line, the way
   out at its end; the card clips the tint to its corners. */
.enc .team-card:has(> :is(.calendar-reconnect, .calendar-no-team)) {
  overflow: clip;
}

.enc :is(.calendar-reconnect, .calendar-no-team) {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  background: color-mix(in oklab, var(--status-failed) var(--status-tint), transparent);
  color: var(--text-primary);
}

.enc :is(.calendar-reconnect, .calendar-no-team) > svg {
  flex: none;
  color: var(--status-failed);
}

.enc :is(.calendar-reconnect, .calendar-no-team) > p {
  flex: 1 1 16rem;
  margin: 0;
}

.enc .calendar-reconnect > .team-error {
  flex-basis: 100%;
}

/* The team the meetings land in: a row like the team page's, with what went
   wrong under it, across. */
.enc .calendar-team {
  flex-wrap: wrap;
}

.enc .calendar-team > .team-error {
  flex-basis: 100%;
}

/* The meetings (calendar-events.js): a day's name over its card, a row a
   meeting. The days, their cards and the rows share the list's columns
   (subgrid), so the times, what the notetaker does and its switches line up
   down the week whatever the titles' lengths. */
.enc .calendar-days {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: var(--space-5) var(--space-4);
}

.enc .calendar-days > *,
.enc .calendar-day > * {
  grid-column: 1 / -1;
}

.enc .calendar-day {
  display: grid;
  grid-template-columns: subgrid;
  row-gap: 0;
}

.enc .calendar-day-title {
  margin: 0 0 var(--space-2);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-medium);
}

.enc .calendar-events {
  display: grid;
  grid-template-columns: subgrid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.enc .calendar-event {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: start;
  padding: var(--space-3) var(--space-4);
}

.enc .calendar-event + .calendar-event {
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .calendar-event-time {
  color: var(--text-muted-strong);
  font-variant-numeric: tabular-nums;
  line-height: 24px;
  white-space: nowrap;
}

.enc :is(.calendar-event-main, .calendar-event-end) {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.enc .calendar-event-end {
  align-items: flex-end;
  text-align: right;
}

.enc .calendar-event-title {
  font-weight: var(--fw-medium);
  line-height: 24px;
  overflow-wrap: anywhere;
}

.enc :is(.calendar-event-meta, .calendar-event-why) {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  line-height: 1.5;
}

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

/* Over: the time and the title step back; what came of it stays as it is. */
.enc .calendar-event.past :is(.calendar-event-time, .calendar-event-title) {
  color: var(--text-muted);
}

/* The notetaker's marks are the bot dialog's, a size down. */
.enc .calendar-event-status {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  line-height: 24px;
  white-space: nowrap;
}

.enc .calendar-event-status > svg {
  flex: none;
  color: var(--text-muted-strong);
}

.enc .calendar-event-status[data-tone='wait'] > svg { color: var(--status-processing); }
.enc .calendar-event-status[data-tone='done'] > svg { color: var(--status-done); }
.enc .calendar-event-status[data-tone='error'] > svg { color: var(--status-failed-text); }
.enc .calendar-event-status[data-tone='off'] > svg { color: var(--text-muted); }

.enc .calendar-event-status[data-tone='busy'] > svg {
  animation: enc-spin 0.9s linear infinite;
}

.enc .calendar-event-status > .bot-live-dot {
  flex: none;
  margin: 3px;
}

.enc a.calendar-event-open {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The switch on the title's line; a meeting over keeps the empty column. */
.enc .calendar-event-switch {
  display: flex;
  align-items: center;
  height: 24px;
}

.enc :is(.calendar-event-rule, .calendar-event-stop) {
  line-height: 24px;
}

.enc .calendar-event-error {
  margin: var(--space-1) 0 0;
}

/* Where the recording lands: a quiet control in the meeting's meta, a field
   on every row would outweigh the meetings. Its edge comes with `.enc
   select`'s own hover and focus. */
.enc .calendar-event-project {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-1);
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

.enc .calendar-event-project select {
  width: auto;
  min-width: 0;
  max-width: 280px;
  min-height: 28px;
  line-height: 26px;
  padding-left: var(--space-2);
  /* A badge's size: a badge's corner, not a capsule. */
  border-radius: var(--radius-sm);
  border-color: transparent;
  background-color: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

/* A phone: the time and the notetaker on the first line, the meeting under
   them across the card. */
@media (max-width: 640px) {
  .enc .calendar-days {
    display: flex;
    flex-direction: column;
  }

  .enc :is(.calendar-day, .calendar-events) {
    display: block;
  }

  .enc .calendar-event {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas: 'time end switch' 'main main main';
    gap: var(--space-1) var(--space-3);
  }

  .enc .calendar-event-time { grid-area: time; }
  .enc .calendar-event-main { grid-area: main; }
  .enc .calendar-event-end { grid-area: end; }
  .enc .calendar-event-switch { grid-area: switch; }

  /* A finger's field, across the card, as every control on a phone. */
  .enc .calendar-event-project select {
    flex: 1;
    max-width: none;
    min-height: var(--field-h);
    line-height: calc(var(--field-h) - 2px);
    border-radius: var(--radius-field);
    border-color: var(--border-control);
  }
}

/* The line heights above centre the closed native look's text; a base-select
   centres by flex, and a tall line sat its chevron on the line's baseline, as
   on the filter chips. */
@supports (appearance: base-select) {
  .enc .calendar-event-project select {
    line-height: 1;
  }
}

/* A phone: Reconnect takes the column, as every control there does. */
@media (max-width: 640px) {
  .enc .calendar-reconnect > button {
    flex: 1 1 100%;
  }
}

/* What didn't load, in the card it would have filled (the token list, the
   name): the card is the frame, so the box drops its own dashed one; under
   the person, the hairline the card's other rows have between them. */
.enc .settings-page .team-card .load-error {
  border: 0;
  border-radius: 0;
}

.enc .settings-page .team-card > .team-block + .load-error {
  border-top: var(--stroke) solid var(--border-hairline);
}

/* The flag to type, in the sentence: dressed as the block below it, and one
   piece, so a line never ends between «-F» and its value. */
.enc .settings-page .team-section-lead code {
  padding: 1px var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--surface-1);
  box-shadow: inset 0 0 0 var(--stroke) var(--border-hairline);
  white-space: nowrap;
}

/* Two tabs hug their labels; on a phone they take the column, as every strip
   does (the tabs' own phone rule). */
.enc .enc-tabs.settings-tabs {
  display: inline-flex;
  width: auto;
}

/* A phone: the choices take the row under their name, a third each, the icon
   over the word so «Системная» fits a third of 328px; the form's button goes
   under its field, as on the team page; a token's dates take a line each. */
@media (max-width: 640px) {
  .enc .settings-token-dates > span {
    display: block;
  }

  .enc .settings-token-dates > span + span::before {
    content: none;
  }

  .enc .settings-row {
    flex-direction: column;
    align-items: stretch;
  }

  .enc .settings-segments {
    display: flex;
  }

  .enc .settings-segments label {
    flex: 1 1 0;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    padding: var(--space-2) var(--space-1);
  }

  .enc :is(.settings-token-form, .settings-you-form) .share-link-row,
  .enc .settings-new-token .share-link-row {
    flex-wrap: wrap;
  }

  .enc :is(.settings-token-form, .settings-you-form) .share-link-row :is(input, button),
  .enc .settings-new-token .share-link-row :is(input, button) {
    flex: 1 1 100%;
  }
}

/* The switch. Outranks the plain checkbox rule further down (0,2,1), which
   would size it back to a 16px box. */
.enc input[type='checkbox'].switch {
  appearance: none;
  position: relative;
  flex: none;
  width: 36px;
  height: 20px;
  margin: 2px 0 0;
  border-radius: var(--radius-pill);
  --paint-from: var(--border-control);
  --paint-to: var(--border-control);
  background: linear-gradient(90deg, var(--paint-from), var(--paint-to));
  cursor: pointer;
  transition: --paint-from var(--dur-fast) var(--ease-out),
    --paint-to var(--dur-fast) var(--ease-out);
}

.enc input[type='checkbox'].switch::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--white);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
  transition: transform var(--dur-fast) var(--ease-out);
}

/* On, the paint runs backwards: the white knob sits at the red end, 3.1:1,
   where the amber would leave it at 2.1. */
.enc input[type='checkbox'].switch:checked {
  --paint-from: var(--action-to);
  --paint-to: var(--action-from);
}

.enc input[type='checkbox'].switch:checked::before {
  transform: translateX(16px);
}

.enc input[type='checkbox'].switch:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* A phone has no room for four columns: who on the whole first line, so the
   email is not cut for a button; when, then the role with Remove across from
   it, both under the name, where the eye already is. */
@media (max-width: 640px) {
  .enc .team-people {
    display: block;
  }

  .enc .team-person {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: 'who who' 'seen seen' 'role end';
    gap: var(--space-1) var(--space-3);
  }

  .enc .team-person-who { grid-area: who; }
  .enc .team-person-end { grid-area: end; }

  .enc :is(.team-person-seen, .team-person-role) {
    padding-left: calc(32px + var(--space-3));
  }

  .enc .team-person-seen {
    grid-area: seen;
    margin-top: calc(-1 * var(--space-1));
    white-space: normal;
  }

  .enc .team-person-role {
    grid-area: role;
    margin-top: var(--space-1);
  }

  .enc .team-person-role-text {
    padding-left: 0;
  }

  /* An invite has no role control to sit across from: its two actions start
     under the email instead. */
  .enc .team-invites .team-person-end {
    grid-column: 1 / -1;
    justify-self: start;
    margin-top: var(--space-1);
    padding-left: calc(32px + var(--space-3));
  }
}

/* ── Shell: the assistant panel (assistant.js) ──
 * The sidebar's mirror: a full-height column on the chrome colour, a hairline
 * on its inner edge. Where the page has room (≥1280px) it pushes the content
 * over rather than covering it, so a transcript stays readable beside the chat;
 * narrower desktops get it over the page; below 1000px it is the screen.
 * The slide waits for data-assistant-ready, so a page that opens with the panel
 * open draws it in place instead of sliding it in again.
 */
.enc .assistant {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  background: var(--bg-chrome);
  transform: translateX(100%);
  visibility: hidden;
}

body.enc[data-assistant-open] .assistant {
  transform: none;
  visibility: visible;
}

/* Shut: hidden once the slide is over, so it leaves the tab order and the
   accessibility tree. Open: visible at once, before it starts moving. */
body.enc[data-assistant-ready] .assistant {
  transition: transform var(--dur-base) var(--ease-out), visibility 0s linear var(--dur-base);
}

body.enc[data-assistant-ready][data-assistant-open] .assistant {
  transition: transform var(--dur-base) var(--ease-out), visibility 0s;
}

/* The top bar's height and hairline, so the two read as one line across. */
.enc .assistant-head {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--space-1);
  height: var(--topbar-h, 56px);
  padding-inline: var(--space-4) var(--space-2);
  box-shadow: inset 0 -1px 0 var(--border-hairline);
}

.enc .assistant-head h2 {
  flex: 1;
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-app-md);
  font-weight: var(--fw-semibold);
  letter-spacing: normal;
}

.enc .assistant-icon-btn {
  width: var(--control-h);
  padding: 0;
  border-color: transparent;
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--text-muted-strong);
}

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

.enc .assistant-icon-btn:active,
.enc .assistant-toggle:active {
  background: var(--bg-active);
}

.enc .assistant-icon-btn > svg {
  flex-basis: 18px;
  width: 18px;
  height: 18px;
}

.enc .assistant-empty {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-5);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-base);
  text-align: center;
}

/* `.enc button` and the flex above set a display, which beats the UA's
   `[hidden]`; New chat, Send/Stop and the empty state toggle by it. */
.enc .assistant-empty[hidden],
.enc .assistant [hidden] {
  display: none;
}

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

.enc .assistant-empty-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-2);
  border-radius: var(--radius-field);
  background: var(--bg-raised);
  color: var(--text-brand);
}

.enc .assistant-empty-title {
  color: var(--text-primary);
  font-size: var(--fs-app-md);
  font-weight: var(--fw-semibold);
}

.enc .assistant-log {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-4);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-4);
}

.enc .assistant-log:empty {
  display: none;
}

.enc .assistant-msg {
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.enc .assistant-msg[data-role="user"] {
  align-self: flex-end;
  max-width: 85%;
  padding: var(--space-3);
  border-radius: var(--radius-field);
  background: var(--bg-raised);
}

.enc .assistant-msg[data-error] {
  color: var(--red);
}

/* Three dots while the answer is on its way, one line of text tall. */
.enc .assistant-typing {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: 1lh;
}

.enc .assistant-typing > span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-muted-strong);
  animation: enc-typing 1s infinite ease-in-out;
}

.enc .assistant-typing > span:nth-child(2) { animation-delay: 0.15s; }
.enc .assistant-typing > span:nth-child(3) { animation-delay: 0.3s; }

@keyframes enc-typing {
  0%, 80%, 100% { opacity: 0.3; }
  40% { opacity: 1; }
}

/* The home page's Ask, in small: the button in the field's bottom corner at the
   field's inset, so its corner is the field's 16 − 8, and the field grows with
   the message up to a point. One line sits centred in the 44px. */
.enc .assistant-form {
  display: grid;
  flex: none;
  padding: var(--space-3) var(--space-4) var(--space-4);
}

.enc .assistant-form > * {
  grid-area: 1 / 1;
}

.enc .assistant-form textarea {
  max-height: 40vh;
  padding-block: calc((var(--field-h) - 2 * var(--stroke) - 1lh) / 2);
  padding-right: var(--field-h);
  resize: none;
  field-sizing: content;
}

.enc .assistant-send {
  align-self: end;
  justify-self: end;
  width: calc(var(--field-h) - 2 * var(--field-inset));
  height: calc(var(--field-h) - 2 * var(--field-inset));
  margin: var(--field-inset);
  padding: 0;
  border-radius: var(--radius-inset);
}

/* Nothing to send: a quiet square, not a faded brand one. */
.enc .assistant-send:disabled {
  opacity: 1;
  background: var(--bg-raised);
  color: var(--text-muted);
}

@media (min-width: 1000px) {
  body.enc {
    --assistant-w: 400px;
  }

  /* A card like the column, the gap off the window's edges: the 400 is its 392
     and the gap on its right. Each corner nests what sits in it, the close
     button 8px in at the top (12 + 8), the field 20px in at the foot (12 + 20).
     Shut, it slides the gap further too, so no sliver of it stays. */
  .enc .assistant {
    inset: var(--shell-gap) var(--shell-gap) var(--shell-gap) auto;
    width: calc(var(--assistant-w) - var(--shell-gap));
    overflow: hidden;
    border-radius:
      calc(var(--radius-field) + var(--space-2)) calc(var(--radius-field) + var(--space-2))
      calc(var(--radius-field) + var(--space-4)) calc(var(--radius-field) + var(--space-4));
    box-shadow: inset 0 0 0 1px var(--border-hairline), var(--shadow-lift);
    transform: translateX(calc(100% + var(--shell-gap)));
  }

  /* The card starts the gap down, so its line meets the foot of the bar's card. */
  .enc .assistant-head {
    height: calc(var(--topbar-h, 64px) - var(--shell-gap));
  }
}

@media (min-width: 1280px) {
  .enc .assistant {
    box-shadow: inset 0 0 0 1px var(--border-hairline);
  }

  body.enc[data-assistant-open] {
    padding-right: var(--assistant-w);
  }

  /* All of the padding: the folding sidebar moves the other side, and one rule
     wins over the other here. */
  body.enc[data-assistant-ready] {
    transition: padding var(--dur-base) var(--ease-out);
  }
}

@media print {
  .enc .assistant {
    display: none;
  }

  body.enc[data-assistant-open] {
    padding-right: 0;
  }
}

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

/* A heading the page focuses on its own (tabindex="-1") when the button the
   keyboard was on went away, after Try again or a late answer: a place to
   stand, not a control, so no ring. Controls out of the Tab order keep it. */
.enc :is(h1, h2, h3)[tabindex='-1']:focus {
  outline: none;
}

.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.
 *
 * The fill is the action's paint (tokens.css): a state moves its two ends, and
 * the ends are registered colours, so hover and press fade as a flat fill did.
 * A variant that sets its own `background` drops the paint whole.
 */
@property --paint-from {
  syntax: '<color>';
  inherits: false;
  initial-value: transparent;
}

@property --paint-to {
  syntax: '<color>';
  inherits: false;
  initial-value: transparent;
}

.enc button,
.enc .btn {
  --paint-from: var(--action-from);
  --paint-to: var(--action-to);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--control-h);
  padding-inline: var(--space-16);
  border: var(--stroke) solid transparent;
  border-radius: var(--radius-control);
  background: linear-gradient(135deg, var(--paint-from), var(--paint-to));
  color: var(--on-action);
  font-family: var(--font-body);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-control);
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
    --paint-from var(--dur-fast) var(--ease-out),
    --paint-to var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    scale var(--dur-fast) var(--ease-out);
}

.enc button:hover,
.enc .btn:hover {
  --paint-from: var(--action-from-hover);
  --paint-to: var(--action-to-hover);
  text-decoration: none;
}

.enc button:active,
.enc .btn:active {
  --paint-from: var(--action-from-active);
  --paint-to: var(--action-to-active);
}

/* Pressed state, continued. The rule above only moves the paint's ends, which
   a variant with its own background never shows. (When it set the fill itself,
   at 0,2,1 it outranked every quiet variant below, 0,2,0: `▸ more`, Delete, the
   mic, the ✕ and Sign out all flashed brand red under the finger.) 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;
}

/* `hidden` hides: the base above is flex, which beats the browser's bare
   `[hidden]` rule. */
.enc :is(button, .btn)[hidden] {
  display: 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 {
  border-color: var(--secondary-edge);
  background: var(--secondary-fill);
  color: var(--text-primary);
}

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

.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;
}

/* Admin "Remove". app.css colours .link-button.danger at (0,2,0), which ties
   the base rule above and loses on load order — restate at (0,3,0). */
/* `.enc button.danger` above colours the border at (0,2,1); `border: 0` keeps
   the word unboxed even if a later rule restates the whole border. */
.enc .link-button.danger { border: 0; color: var(--status-failed-text); }
.enc .link-button.danger:hover:not(:disabled) { background: transparent; }

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

.enc .btn-lg {
  height: calc(var(--control-h) + 4px);
  padding-inline: var(--space-24);
  font-size: var(--fs-app-md);
}

.enc .btn-icon {
  width: var(--control-h);
  height: var(--control-h);
  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: var(--field-h);
  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-field);
  line-height: calc(var(--field-h) - 2px);
  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: var(--focus-field-outline);
  outline-offset: 2px;
  border-color: var(--focus-field-edge);
  box-shadow: var(--focus-field-ring);
}

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

/* The chevron, like every icon in a field, is centred half the field's height
   in from its edge. */
.enc select {
  padding-right: calc(var(--field-h) / 2 + 8px + var(--space-2));
  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 calc((var(--field-h) - 16px) / 2) center;
  background-size: 16px;
  -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.
 */
/* Both icons sit on the field's own square: centred half its height in from
   the edge. Text starts a gap past the magnifier and stops at the clear's
   square, so a long query never runs under either. */
.enc .search-bar input[type='search'] {
  min-height: var(--control-h);
  padding-left: calc(var(--control-h) / 2 + 8px + var(--space-2));
  padding-right: var(--control-h);
  border-radius: var(--radius-field);
  box-shadow: none;
  /* A toolbar control, sized with the filters beside it, not with form fields. */
  font-size: var(--fs-app-base);
  line-height: calc(var(--control-h) - 2px);
}

/* app.css paints the focused search field #11151b and its hover edge #36404d at
   (0,3,1)/(0,4,1), above the generic field rules — on cream the typed query
   went dark-on-near-black. Restated here so the field keeps its own surface. */
.enc .search-bar input[type='search']:hover:not(:disabled) {
  border-color: var(--border-control-hover);
}

.enc .search-bar input[type='search']:focus {
  outline: var(--focus-field-outline);
  outline-offset: 2px;
  border-color: var(--focus-field-edge);
  background: var(--surface-1);
  box-shadow: var(--focus-field-ring);
}

.enc .search-bar::before {
  left: calc((var(--control-h) - 16px) / 2);
  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='8'/><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. */
/* Inset by the same gap on top, right and bottom, its corner the field's less
   that gap: one figure with the field, not a disc dropped on its edge. */
.enc .search-clear {
  display: none;
  right: var(--field-inset);
  width: calc(var(--control-h) - 2 * var(--field-inset));
  height: calc(var(--control-h) - 2 * var(--field-inset));
  padding: 0;
  border-color: transparent;
  border-radius: var(--radius-inset);
  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 > :last-child {
    margin-left: auto;
  }
}

.enc .filter-chip {
  width: auto;
  min-height: var(--control-h);
  line-height: calc(var(--control-h) - 2px);
  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 calc(var(--control-h) / 2 + 8px + var(--space-2)) 0 var(--space-3);
  background-position: right calc((var(--control-h) - 16px) / 2) center;
  background-size: 16px;
}

.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: transparent;
  background-color: transparent;
  background-image: var(--action-paint);
  background-origin: border-box;
  background-size: auto;
  color: var(--on-action);
}

/* The hover above is 0,4,0 and outranked this: the label went to ink and the
   edge to grey on the red fill. Hovered, the active chip just lifts. */
.enc .filter-chip.active:hover:not(:disabled) {
  border-color: transparent;
  background-image: linear-gradient(135deg, var(--action-from-hover), var(--action-to-hover));
  color: var(--on-action);
}

/* The chevron is drawn, not inherited, so it does not follow `color` onto the
   fill. Same path, restated in the paint's ink, over the paint. */
.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>"), var(--action-paint);
  background-size: 16px, auto;
  background-repeat: no-repeat;
}

.enc select.filter-chip.active:hover:not(:disabled) {
  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>"), linear-gradient(135deg, var(--action-from-hover), var(--action-to-hover));
}

/* ── 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: calc((var(--field-h) - 16px) / 2);
    background-image: none;
    line-height: 1;
    text-align: left;
  }

  /* line-height too: the chip's own (control height, for the closed native
     look) outranks `.enc select` above, sat the icon on the text baseline and
     stretched every option to a 38px line. */
  .enc select.filter-chip {
    padding-right: calc((var(--control-h) - 16px) / 2);
    line-height: 1;
  }

  /* The painted chevron is the fallback's; here it would be a second one. */
  .enc select.filter-chip.active {
    background-image: var(--action-paint);
    background-size: auto;
  }

  .enc select.filter-chip.active:hover:not(:disabled) {
    background-image: linear-gradient(135deg, var(--action-from-hover), var(--action-to-hover));
  }

  .enc select.filter-chip.active::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='%23FFFFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  }

  /* 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: 16px;
    height: 16px;
    transition: transform var(--dur-fast) var(--ease-out);
  }

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

  /* Every list that drops from a control is one figure: rows shaped like the
     control they came from, inside a panel whose corner is the row's plus the
     gap between them (the dictation menu and the Ask typeahead repeat it). */
  .enc select::picker(select) {
    min-width: anchor-size(width);
    max-height: 320px;
    /* Both sides: a list with no room below flips over its control. */
    margin-block: var(--space-1);
    /* Only then: the browser's own order takes the taller side, and a short
       list near the fold opened upward over what it was picked for. */
    position-try-order: normal;
    padding: var(--space-2);
    border: var(--stroke) solid var(--border-hairline);
    border-radius: calc(var(--radius-field) + var(--space-2));
    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: var(--control-h);
    padding: 0 var(--space-3);
    border-radius: var(--radius-field);
    background: transparent;
    color: var(--text-primary);
    font-size: var(--fs-app-base);
    line-height: 1.3;
    /* 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;
  }

  /* Off the list it opens, as without base appearance: the flex above
     outranks the browser's bare `[hidden]` rule. */
  .enc select option[hidden] {
    display: none;
  }

  /* One text edge for every row and group label: the caps label already splits
     the list into teams, and a second indent made three left edges. Chromium
     pads the group label 0.5em on its own, so the indent is short by that. */
  .enc select optgroup option {
    text-indent: 0;
  }

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

  .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);
    /* Lucide check, not the UA's ✓ glyph. */
    content: '';
    width: 16px;
    height: 16px;
    background-color: currentcolor;
    mask: 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 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
  }

  .enc select optgroup {
    padding: var(--space-3) 0 0;
    background: transparent;
    text-indent: calc(var(--space-3) - 0.5em);
    color: var(--text-muted);
    font-size: var(--fs-app-xs);
    font-weight: var(--fw-label);
    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-label);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

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

/* In a window the fields stand in one column, the project's as wide as the
   type's under it and the link's above it. */
.enc .team-dialog .picker-host {
  max-width: none;
}

/* upload.html: the project and the type as two rows of one form. The labels
   share a column, so both selects start on one line and are as wide; the
   type's note goes under its select. A row without its field takes no room. */
.enc .upload-fields {
  display: grid;
  grid-template-columns: max-content minmax(0, 340px);
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}

.enc .upload-fields > .upload-controls:not([hidden]) {
  display: contents;
}

.enc .upload-fields .team-hint {
  grid-column: 2;
  margin-top: calc(var(--space-1) * -1);
}

/* 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);
}

/* What came of connecting the calendar (calendar-outcome.js): the glyph, the
   sentence, the way on, the cross, on one line over the list. Back from
   Paddle (checkout-return.js), the same line over the plan. The plan's
   minutes and payment (plan-banner.js), the same line over the list. */
.enc .calendar-outcome,
.enc .checkout-outcome,
.enc .plan-banner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  color: var(--text-primary);
}

.enc .calendar-outcome > svg {
  flex: none;
  color: var(--status-done);
}

.enc .calendar-outcome.error > svg {
  color: var(--status-failed);
}

.enc .plan-banner > svg {
  flex: none;
  color: var(--amber);
}

.enc .plan-banner.error > svg {
  color: var(--status-failed);
}

/* The cross or the button sits at the banner's edge as far in as from the top. */
.enc .plan-banner {
  padding-inline-end: var(--space-3);
}

.enc .plan-banner > p {
  margin: 0;
}

/* A phone: the words take lines; the glyph and the cross stay at the first,
   the button goes under them, across. */
@media (max-width: 640px) {
  /* The button across takes the inset its corners need; every banner takes
     the same, so stacked ones start their glyphs and words in one line. */
  .enc .plan-banner {
    align-items: flex-start;
    padding-inline-start: var(--space-3);
  }

  .enc .plan-banner > svg {
    margin-top: calc((1lh - 16px) / 2);
  }

  .enc .plan-banner:has(> .plan-banner-close) > svg {
    margin-top: 8px;
  }

  .enc .plan-banner > .plan-banner-text {
    margin-top: calc((32px - 1lh) / 2);
  }

  .enc .plan-banner > .btn {
    flex: 1 1 100%;
  }
}

.enc .checkout-outcome > svg {
  flex: none;
  color: var(--text-muted-strong);
}

.enc .checkout-outcome[data-busy] > svg {
  color: var(--status-processing);
  animation: enc-spin 0.9s linear infinite;
}

.enc .calendar-outcome-text,
.enc .checkout-outcome-text,
.enc .plan-banner-text {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
}

.enc .calendar-outcome-action {
  flex: none;
}

.enc a.calendar-outcome-action,
.enc .plan-banner-text a {
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.enc .calendar-outcome-close,
.enc .plan-banner-close {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--text-muted);
}

.enc .calendar-outcome-close:hover,
.enc .plan-banner-close:hover {
  background: var(--bg-menu-hover);
  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(--action-paint-along);
  transition: width var(--dur-base) var(--ease-out);
}

/* ── Upload dialog (upload-dialog.js) ──
 * The dropzone in a window. It opens near the top, not in the middle: a native
 * modal re-centres on every change of height, and an error or the video note
 * would move the whole window, the button under the pointer with it.
 */
.enc .upload-dialog {
  margin-top: min(12vh, 112px);
  margin-bottom: auto;
}

.enc .upload-file {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* The target before a file and the file after it: the same three lines in the
   same place, so a choice moves nothing below. The cloud is an element here,
   not the dropzone's mask, so it can turn into the file. */
.enc .upload-drop {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  text-align: center;
  cursor: pointer;
}

.enc .upload-drop::before {
  content: none;
}

.enc .upload-drop-mark {
  display: flex;
  margin-bottom: var(--space-2);
  color: var(--text-muted-strong);
  transition: color var(--dur-fast) var(--ease-out);
}

.enc .upload-drop:is(:hover, .drag, .has-file) .upload-drop-mark {
  color: var(--text-primary);
}

/* Wrapped, never cut: a long file name is shortened in the middle by the
   script, so its extension stays; a name without spaces still breaks. */
.enc .upload-drop-title {
  max-width: 100%;
  font-weight: var(--fw-medium);
  overflow-wrap: anywhere;
  text-wrap: balance;
}

.enc .upload-drop-note {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
}

/* A chosen file is a thing, not a hole: the edge goes solid. */
.enc .upload-drop.has-file:not(.drag) {
  border-style: solid;
}

/* The ✕ sits over the box, not in it; pointing at it keeps the box lit. */
.enc .upload-drop:has(+ .upload-remove:hover) {
  border-color: var(--text-muted);
  background: var(--bg-raised);
}

/* While the file goes the box reports; it takes nothing. */
.enc .upload-drop.busy {
  pointer-events: none;
}

/* Focus lands on the input, out of sight: the ring goes round the box. */
.enc .upload-drop:has(.upload-input:focus-visible) {
  outline: var(--stroke-strong) solid var(--focus-ring);
  outline-offset: 2px;
}

.enc .upload-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The upload's own progress, along the foot of the box: it fills the padding,
   so the box keeps its height. */
.enc .upload-drop-bar {
  position: absolute;
  right: var(--space-5);
  bottom: var(--space-3);
  left: var(--space-5);
  height: 4px;
  overflow: hidden;
  border-radius: var(--radius-control);
  background: var(--bg-raised);
}

.enc .upload-drop-bar > span {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--action-paint-along);
  transition: width var(--dur-base) var(--ease-out);
}

/* The dialog's close mark, in the box's corner. */
.enc .upload-remove {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: grid;
  place-items: center;
  width: 32px;
  min-width: 0;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--text-muted-strong);
}

.enc .upload-remove[hidden] {
  display: none;
}

.enc .upload-remove:hover {
  background: var(--bg-active);
  color: var(--text-primary);
}

.enc .upload-error:empty {
  display: none;
}

/* A refusal's way through (enc/refusal.js): a link inside a sentence shows as one. */
.enc :is(.upload-error, .bot-error) a {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Beside «Cancel upload»: the room the Upload button leaves. The minutes
 * stand there while nothing goes. */
.enc :is(.upload-keep, .upload-minutes) {
  flex: 1;
  align-self: center;
}

/* A phone has no room beside the buttons: the line takes a row of its own, and
   order lifts it above them (the row wraps in reverse). */
@media (max-width: 640px) {
  .enc :is(.upload-keep, .upload-minutes) {
    flex-basis: 100%;
    order: 1;
  }

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

/* ── Notetaker dialog (bot-dialog.js) ──
 * Near the top, as the upload's: the line under the link changes. Once sent,
 * the notetaker's status takes the form's place — a card at the field's inset,
 * so its corners are the field's: a mark by tone, what it means, which call. */
.enc .bot-dialog {
  margin-top: min(12vh, 112px);
  margin-bottom: auto;
}

/* The form and the status swap by `hidden`; the fields and the card are flex,
   which beats the browser's bare `[hidden]` rule. */
.enc .bot-dialog [hidden] {
  display: none;
}

.enc .bot-link-line {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.enc .bot-link-line.found {
  color: var(--text-muted-strong);
}

.enc .bot-error:empty {
  display: none;
}

.enc .bot-link-line > svg {
  flex: none;
  color: var(--status-done);
}

.enc .bot-status {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
  background: var(--bg-raised);
}

.enc .bot-status-mark {
  display: grid;
  flex: none;
  place-items: center;
  width: 24px;
  height: 24px;
  color: var(--text-muted-strong);
}

.enc .bot-status[data-tone='wait'] .bot-status-mark { color: var(--status-processing); }
.enc .bot-status[data-tone='done'] .bot-status-mark { color: var(--status-done); }
.enc .bot-status[data-tone='error'] .bot-status-mark { color: var(--status-failed-text); }
.enc .bot-status[data-tone='off'] .bot-status-mark { color: var(--text-muted); }

.enc .bot-status[data-tone='busy'] .bot-status-mark > svg {
  animation: enc-spin 0.9s linear infinite;
}

.enc .bot-live-dot {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-full);
  background: var(--red);
  animation: enc-record-pulse 1.6s var(--ease-out) infinite;
}

.enc .bot-status-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.enc .bot-status-title {
  font-weight: var(--fw-medium);
  line-height: 24px;
}

.enc .bot-status-call {
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  line-height: 1.5;
  overflow-wrap: anywhere;
}

/* ── 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) {
  /* No app around the card, so no sidebar to keep clear of: without this the
     wide-screen padding pushes the card 116px right of the window's centre. */
  --sidebar-w: 0px;
  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 "whisperwise". 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('/brand/logo.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;
}

/* ── Accepting an invite (join.html) ──
 * The sign-in problem's card, for the other screen people reach from outside
 * the app. The team's square sits under the brand mark, so the card says whose
 * door this is before it says anything else; one act under it, and who is
 * signed in at the foot — that line is what decides which account joins.
 */
.enc .join-card :is(h1, .auth-reason) {
  /* A long team name or email address on a phone. */
  overflow-wrap: anywhere;
}

.enc .join-card .team-mark,
.enc .skel.join-skel-mark {
  display: grid;
  width: 48px;
  height: 48px;
  margin: 0 auto var(--space-3);
  border-radius: 8px;
  font-size: var(--fs-app-lg);
}

.enc .join-card .auth-reason:has(+ .join-note) {
  margin-bottom: var(--space-2);
}

.enc .join-note {
  max-width: 40ch;
  margin: 0 auto var(--space-24);
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

.enc .join-actions {
  display: flex;
  justify-content: center;
}

.enc .join-card .team-error {
  margin: var(--space-3) 0 0;
}

/* `p.` for the specificity of `.auth-card p:last-child` above, which would
   otherwise take the gap away: the foot is always the card's last line. */
.enc .join-card p.join-foot {
  margin: var(--space-4) 0 0;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  overflow-wrap: anywhere;
}

/* Underlined from the start: in a muted line of small type a link that only
   shows itself on hover reads as more of the sentence. */
.enc .join-foot :is(a, .link-button) {
  color: var(--text-primary);
  font: inherit;
  text-decoration: underline;
  text-decoration-color: var(--text-muted);
  text-underline-offset: 3px;
}

.enc .join-foot :is(a, .link-button):hover {
  text-decoration-color: currentColor;
}

/* The grey is the shape of an invite that is still good, the answer most links
   get: the same groups of lines at the same sizes, so the card does not jump
   when it arrives. On a phone the heading, the note and the foot wrap once
   more, and so does the grey. `.skel.` where a rule has to beat the bar's own
   height, roundness or display, which come later in this file. */
.enc .join-skeleton {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.enc .join-skeleton .skel-lines {
  width: 100%;
  align-items: center;
}

.enc .join-skel-title {
  margin-bottom: var(--space-2);
  font-size: var(--fs-app-xl);
  line-height: 1.2;
}

.enc .join-skel-reason {
  max-width: 40ch;
  margin-bottom: var(--space-2);
  font-size: var(--fs-app-md);
}

.enc .join-skel-note {
  max-width: 40ch;
  margin-bottom: var(--space-24);
  font-size: var(--fs-app-sm);
}

.enc .skel.join-skel-button {
  width: 11em;
  height: calc(var(--control-h) + 4px);
  border-radius: var(--radius-control);
}

.enc .join-skel-foot {
  margin-top: var(--space-4);
  font-size: var(--fs-app-sm);
}

.enc .skel.join-skel-wrap {
  display: none;
}

@media (max-width: 640px) {
  .enc .join-actions > * {
    flex: 1 1 auto;
  }

  .enc .skel.join-skel-wrap {
    display: inline-block;
  }
}

/* ── Signing in (signin.html) ──
 * The invite's card, one step at a time. Both ways in are full width, one
 * above the other: they are the same act by two roads, so neither is the
 * smaller button. The field's label and errors start where the field does —
 * centred, a two-line error would look like a caption of the whole card.
 */
.enc .signin-step {
  max-width: 400px;
}

.enc .signin-step :is(h1, .auth-reason) {
  overflow-wrap: anywhere;
}

.enc .signin-step .btn-lg {
  width: 100%;
}

.enc .signin-or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

.enc .signin-or::before,
.enc .signin-or::after {
  content: '';
  flex: 1 1 0;
  height: var(--stroke);
  background: var(--border-hairline);
}

.enc .signin-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-align: left;
}

.enc .signin-form .btn-lg {
  margin-top: var(--space-2);
}

/* `p.` for the specificity of `.auth-card p:last-child`, as the invite's foot. */
.enc :is(.signin-step, .welcome-step) p.signin-foot {
  margin: var(--space-4) 0 0;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

/* Six digits in six cells, checked against the letter digit by digit. The
   field that takes them lies over the cells, unseen; the cell for the next
   digit wears the field's focus. Proportional: the font's tabular 1 has a
   foot and reads as another face. */
.enc .signin-cells {
  position: relative;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-2);
}

/* 16px: a phone zooms into a smaller field. */
.enc .signin-cells input.signin-code {
  position: absolute;
  inset: 0;
  z-index: 1;
  min-height: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: transparent;
  -webkit-text-fill-color: transparent;
  caret-color: transparent;
  font-size: 16px;
  outline: none;
  box-shadow: none;
}

.enc .signin-cells input.signin-code::selection {
  background: transparent;
  color: transparent;
}

.enc .signin-cell {
  display: grid;
  place-items: center;
  height: 56px;
  border: var(--stroke) solid var(--border-control);
  border-radius: var(--radius-field);
  background: var(--surface-1);
  color: var(--text-primary);
  font-size: var(--fs-app-xl);
  transition: border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}

.enc .signin-cells:hover .signin-cell {
  border-color: var(--border-control-hover);
}

/* A code turned down: every cell says so until it is typed again. */
.enc .signin-form:has(> .team-error:not([hidden])) .signin-cell {
  border-color: var(--danger);
}

.enc .signin-cells .signin-cell[data-active] {
  outline: var(--focus-field-outline);
  outline-offset: 2px;
  border-color: var(--focus-field-edge);
  box-shadow: var(--focus-field-ring);
}

/* The code selected, to be typed over: its cells, as a selection. */
.enc .signin-cell[data-selected] {
  background: var(--bg-active);
}

/* The caret, drawn: the real one is in the unseen field. */
.enc .signin-cell[data-active]:empty::after {
  content: '';
  width: var(--stroke-strong);
  height: 1.1em;
  background: var(--text-primary);
  animation: signin-caret 1s steps(1) infinite;
}

@keyframes signin-caret {
  50% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .enc .signin-cell[data-active]:empty::after {
    animation: none;
  }
}

.enc .signin-step p.signin-sent:empty {
  display: none;
}

.enc .signin-step p.signin-sent {
  margin: var(--space-3) 0 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
}

/* The spam line and its two ways out read as one block. */
.enc .signin-step p.signin-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-2);
}

.enc :is(.signin-links, .welcome-step .signin-foot, .billing-dates) .link-button {
  font-size: inherit;
  text-decoration: underline;
  text-decoration-color: var(--text-muted);
  text-underline-offset: 3px;
}

.enc .signin-links .link-button:disabled {
  color: var(--text-muted);
  text-decoration: none;
  cursor: default;
}

/* ── First steps (welcome.html) ──
 * The sign-in card, a little wider: step 3 lays its ways side by side with
 * what each takes. Over the heading, where they are in three — the count for
 * reading, the bar for a glance.
 */
.enc .welcome-step {
  max-width: 480px;
}

.enc .welcome-step :is(h1, .auth-reason) {
  overflow-wrap: anywhere;
}

.enc .welcome-step .btn-lg {
  width: 100%;
}

.enc .welcome-progress {
  margin: 0 0 var(--space-4);
}

.enc .welcome-step p.welcome-count {
  margin: 0 0 var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
}

.enc .welcome-bar {
  display: flex;
  gap: var(--space-1);
  max-width: 120px;
  margin: 0 auto;
}

.enc .welcome-bar span {
  flex: 1 1 0;
  height: 4px;
  border-radius: var(--radius-control);
  background: var(--bg-raised);
}

/* The upload bar's paint: the same fill for how far along. */
.enc .welcome-bar span.is-done {
  background: var(--action-paint-along);
}

/* The Download dialog's rows: each use is two words, one to a line. The extra
   room above parts the question from the name field's hint; `fieldset.` to
   win over `.export-field`'s margin, which comes later in the file. */
.enc fieldset.welcome-uses {
  margin-top: var(--space-4);
}

.enc .welcome-uses > .team-hint {
  margin-top: var(--space-2);
}

/* Step 2's teams: the switcher's row — square, name, what it is to them — in
   a frame of its own, with the button that joins at its end. */
.enc .welcome-teams {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

.enc .welcome-team {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
}

.enc .welcome-team .team-mark {
  width: 32px;
  height: 32px;
  border-radius: 6px;
  font-size: var(--fs-app-sm);
}

.enc .welcome-team .team-item-meta {
  white-space: normal;
}

.enc :is(.welcome-teams, .welcome-ways) + .team-error {
  margin-top: var(--space-2);
  text-align: left;
}

/* Step 3's ways: the Add meeting menu's rows, each a frame as step 2's teams,
   the whole frame the button. The icon sits in the team square's place. */
.enc .welcome-ways {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.enc .welcome-way {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  height: auto;
  padding: var(--space-3);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
  background: transparent;
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  font-weight: var(--fw-regular);
  text-align: left;
  white-space: normal;
}

.enc .welcome-way:is(:hover, :focus-visible) {
  background: var(--bg-menu-hover);
}

.enc .welcome-way > svg {
  flex: none;
  color: var(--text-muted);
}

.enc .welcome-way-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  background: var(--bg-raised);
  color: var(--text-brand);
}

.enc .welcome-way .team-item-meta {
  white-space: normal;
}

/* Two full-width buttons in a form: the second is the way past it. */
.enc .welcome-step .signin-form .btn-lg + .btn-lg {
  margin-top: 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%;
}

/* Saying nothing, it is still a flex item: the gap before it took 8px off the
   select, short of the fields under it. */
.enc .project-picker .picker-meta:empty {
  display: none;
}

/* 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: var(--field-h);
  height: var(--field-h);
  flex: 0 0 var(--field-h);
  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;
  height: var(--field-h);
}

/* The mic is a mode switch on the field, not a second submit. Ghost at rest;
   the brand fill is what says "listening".
   Pinned from the field's top, not centred on the wrap: the wrap also holds the
   error line under the field, and 50% of that sat the mic 2px low. In a
   textarea the same inset puts it in the top corner, concentric with it. */
.enc .dictation-mic {
  top: var(--field-inset);
  right: var(--field-inset);
  width: calc(var(--field-h) - 2 * var(--field-inset));
  height: calc(var(--field-h) - 2 * var(--field-inset));
  transform: none;
  padding: 0;
  border-radius: var(--radius-inset);
  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(--action-paint);
  color: var(--on-action);
}

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

/* 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-2);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: calc(var(--radius-field) + var(--space-2));
  background: var(--surface-2);
  box-shadow: var(--shadow-lift);
}

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

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

/* Which language is on, said by colour and a trailing Lucide check — the same
   mark, in the same place, as the select popup. app.css's leading ✓ glyph and
   its spacer go, so every label starts at one x. */
.enc .dictation-lang-option.active {
  color: var(--text-primary);
}

.enc .dictation-lang-option::before {
  content: none;
}

.enc .dictation-lang-option.active::after {
  content: '';
  width: 16px;
  height: 16px;
  flex: none;
  margin-left: auto;
  background-color: var(--red);
  mask: 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 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* `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,
.enc textarea.dictation-field {
  padding-right: var(--field-h);
}

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

/* Empty, its margin still pushed the field 4px off the button beside it. */
.enc .dictation-error:empty {
  display: none;
}

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

.enc .project-ask-suggestions-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  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);
  padding: var(--space-2);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: calc(var(--radius-field) + var(--space-2));
  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%;
  height: auto;
  min-height: var(--control-h);
  padding-inline: var(--space-3);
  border: 0;
  border-radius: var(--radius-field);
  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-label);
  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: var(--field-h);
}

/* 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: linear-gradient(0deg, var(--action-from), var(--action-to));
  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 and shape are restated. The fill is the button's paint,
   ringed by its halo: the one thing on the page to press. */
.enc .record-btn {
  width: 96px;
  height: 96px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  box-shadow: 0 0 0 8px var(--action-halo), var(--shadow-red);
  transition: transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out),
    --paint-from var(--dur-fast) var(--ease-out),
    --paint-to var(--dur-fast) var(--ease-out);
}

/* `flex: none`: the button-icon rule's 16px basis outranked this width, so the
   mic drew at 16 in a 16×34 box — a speck in a 96px disc. */
.enc .record-btn svg {
  flex: none;
  width: 34px;
  height: 34px;
  stroke: currentcolor;
}

.enc .record-btn:hover:not(:disabled) {
  transform: scale(1.04);
  border: 0;
  box-shadow: 0 0 0 10px var(--action-halo), var(--shadow-red);
}

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

/* 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;
}

/* Lucide's square, filled and stroked in one colour: a ~22px stop, the size the
   old 12-in-24 rect drew at 44px. */
.enc .record-btn [data-icon-stop] {
  width: 26px;
  height: 26px;
  color: var(--red);
}

.enc .record-btn [data-icon-stop] rect {
  fill: currentcolor;
}

@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.
 *
 * The sentence is one box (`.line`) and the time another, a column on the
 * panel's right edge, set right so the times stack digit over digit, as in a
 * table. The glyph is absolute.
 */
.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 {
  display: grid;
  grid-template-columns: minmax(0, 1fr) max-content;
  column-gap: var(--space-4);
  align-items: baseline;
  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);
}

/* A phone has no room for a second column: the time trails the sentence. */
@media (max-width: 560px) {
  .enc .activity-list li {
    display: block;
  }

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

.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 the transcript's seek chip — a tinted, clickable plate (app.css, and
   `.enc .ts` below with its hover and light-theme tint). Here it is the time an
   entry happened, and nothing to click, so the plate comes off. Through the id:
   the chip's own rules come later at the same class weight. */
.enc #activity-list .ts {
  display: inline;
  height: auto;
  padding: 0;
  background: none;
  color: var(--text-muted);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
  line-height: inherit;
  text-decoration: none;
  white-space: nowrap;
  cursor: auto;
}

/* No rows: the panel goes, and the dashed card every empty list wears stands
   in its place (#empty-host), not a frame inside a frame. */
.enc .activity-list:empty {
  display: 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;
  }

  /* Share and Download split the row between them, as every control on a phone
     takes the column; Delete keeps its square at the end. */
  .enc .session-actions > button:not(.danger) {
    flex: 1 1 0;
  }

  /* Three words don't fit a phone in Russian: Delete keeps to its bin, a
     square as tall as the two beside it, at the row's far end, away from what
     is safe to tap. Its name stays for the screen reader (app.js). */
  .enc .session-actions button.danger {
    aspect-ratio: 1;
    margin-inline-start: auto;
    padding-inline: 0;
  }

  .enc .session-actions .delete-label {
    display: none;
  }
}

/* Under 360px "Поделиться" with its icon is wider than half the row and pushed
   the bin past the screen's edge: the two keep their words, not their icons. */
@media (max-width: 359px) {
  .enc .session-actions > button:not(.danger) {
    padding-inline: var(--space-3);
  }

  .enc .session-actions > button:not(.danger) svg {
    display: none;
  }
}

.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;
}

/* The meeting's project (session-project.js): a pill, and a button — it opens
   the menu of projects. A button's fill, weight and 16px glyph would make it
   the one loud pill in the row; it keeps the pills' look, and its folder says
   what the name is. Without a project the outline is dashed: a place to fill.
   A long name ends in an ellipsis rather than out of a phone's width. */
.enc .session-project {
  min-width: 0;
  max-width: 100%;
}

.enc button.session-project-btn {
  gap: var(--space-1);
  min-width: 0;
  max-width: 100%;
  font-weight: var(--fw-regular);
}

.enc button.session-project-btn:is(:hover, :focus-visible, :active) {
  background: var(--bg-raised);
}

.enc .session-project-btn > svg {
  flex: none;
  width: 12px;
  height: 12px;
  color: var(--text-muted);
}

.enc .session-project-btn > span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.enc .session-project-btn[data-empty] {
  border-style: dashed;
}

.enc .session-project-btn[aria-busy='true'] {
  opacity: 0.6;
  cursor: progress;
}

/* ── Transcript page: the player ──
 * Our own bar (enc/player.js) over the browser's engine. Native controls were
 * kept while the alternative meant a week of work to reach parity with
 * scrubbing, keyboard and captions; the bar does all three now, and the native
 * controls stay behind it as the fallback — until the module takes an element
 * over, or if it never does, `controls` is still on it, and `color-scheme`
 * (declared once per theme on the root) keeps it in the page's colours. The
 * wrap is the surface the bar sits on, and it sticks so a timestamp stays
 * clickable while reading further down.
 */
.enc .sticky-player {
  top: var(--topbar-h, 0px);
  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;
}

/* Taken over, audio is the bar alone; the element is only the engine.
   `!important` because app.js shows the element with an inline display. */
.enc .sticky-player[data-player='audio'] > audio {
  display: none !important;
}

/* A capsule. The 40px play disc sits 8px in from its end, so the outer radius
   is the inner one plus that gap — 20 + 8 = 28, half the height: fully round.
   Every control in it is a 40px circle for the same reason. */
.enc .player-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: 56px;
  padding-inline: var(--space-2) var(--space-4);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-full);
  background: var(--bg-card);
}

.enc .player-bar button {
  flex: none;
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: var(--radius-full);
}

/* The bar measures itself, not the window: beside the assistant it is narrow
   on a wide screen. */
.enc .player-bar {
  container-type: inline-size;
}

/* A jump: Lucide's turning arrow at 24px with its count of seconds set inside
   the turn, where the arc leaves room. */
.enc .player-skip {
  position: relative;
}

.enc .player-bar .player-skip > svg {
  flex-basis: 24px;
  width: 24px;
  height: 24px;
}

.enc .player-skip-n {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 9px;
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  pointer-events: none;
}

/* Speed: the app's select in the bar's quiet dress — a pill the size of the
   buttons, no edge, no chevron. One width for every value, so choosing 1.75×
   does not nudge the track. */
.enc .player-bar .player-speed {
  flex: none;
  width: 56px;
  min-height: 0;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
  line-height: 40px;
  text-align: center;
  text-align-last: center;
}

.enc .player-bar .player-speed:hover:not(:disabled) {
  background: var(--bg-raised);
  color: var(--text-primary);
}

/* A field's focus ring is for fields; this one is a button to the eye, and
   takes the buttons' outline, from the keyboard only. */
.enc .player-bar :is(.player-speed, input[type='range']):focus {
  border-color: transparent;
  outline: none;
  box-shadow: none;
}

.enc .player-bar .player-speed:focus-visible {
  outline: var(--stroke-strong) solid var(--focus-ring);
  outline-offset: 2px;
}

@supports (appearance: base-select) {
  .enc .player-bar .player-speed {
    justify-content: center;
    padding: 0;
  }

  .enc .player-bar .player-speed::picker-icon {
    display: none;
  }
}

.enc .player-volume {
  display: flex;
  flex: none;
  align-items: center;
}

.enc .player-bar .player-mute > svg {
  flex-basis: 20px;
  width: 20px;
  height: 20px;
}

/* The level is drawn in ink, not brand: the bar's one orange line is the one
   that says where you are in the meeting. */
.enc .player-level {
  width: 64px;
  height: 40px;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  appearance: none;
}

.enc .player-level::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: var(--radius-full);
  background: linear-gradient(to right, var(--text-primary) var(--level, 100%),
    color-mix(in oklab, var(--text-primary) 16%, transparent) 0);
}

.enc .player-level::-moz-range-track {
  height: 4px;
  border-radius: var(--radius-full);
  background: linear-gradient(to right, var(--text-primary) var(--level, 100%),
    color-mix(in oklab, var(--text-primary) 16%, transparent) 0);
}

.enc .player-level::-webkit-slider-thumb {
  width: 12px;
  height: 12px;
  margin-top: -4px;
  border: 0;
  border-radius: 50%;
  background: var(--text-primary);
  appearance: none;
}

.enc .player-level::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: 0;
  border-radius: 50%;
  background: var(--text-primary);
}

.enc .player-level:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px var(--bg-card), 0 0 0 5px var(--focus-ring);
}

.enc .player-level:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 3px var(--bg-card), 0 0 0 5px var(--focus-ring);
}

/* The file would not load: the controls give way to what happened and a way
   to try again. The mark sits in a 40px box where the play disc was, and the
   button 8px in from the far end, as the disc is from the near one. */
.enc .player-bar[data-state='error'] {
  padding-inline-end: var(--space-2);
}

.enc .player-bar[data-state='error'] > :not(.player-failure) {
  display: none;
}

.enc .player-failure {
  display: flex;
  flex: 1;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.enc .player-failure-mark {
  display: grid;
  flex: none;
  place-items: center;
  width: 40px;
  height: 40px;
  color: var(--red);
}

/* Never cut: where the row is short, the reason takes a second line. */
.enc .player-failure-text {
  flex: 1;
  min-width: 0;
  color: var(--text-primary);
  font-size: var(--fs-app-sm);
}

.enc .player-bar .player-retry {
  width: auto;
  padding-inline: var(--space-16);
}

/* Narrow — a phone, or the column beside the assistant: the jumps and the
   level go; play, where you are and the speed stay. */
@container (max-width: 559px) {
  .enc .player-skip,
  .enc .player-level {
    display: none;
  }

  /* Try again keeps to its glyph, a 40px disc like the rest, and the reason
     gets the room. Its name stays for the screen reader (player.js). */
  .enc .player-bar .player-retry {
    width: 40px;
    padding-inline: 0;
  }

  .enc .player-retry-label {
    display: none;
  }
}

/* No pointer that hovers: a phone or a tablet, whose volume is its own
   buttons — iOS ignores a page that sets it. */
@media (hover: none) {
  .enc .player-volume {
    display: none;
  }
}

/* While it plays, the audio bar's disc is ringed by its paint: 4px, half the
   bar's padding around it, so the ring stays inside the capsule. */
.enc .player-bar[data-playing] > .player-play:not(.btn-ghost) {
  box-shadow: 0 0 0 4px var(--action-halo);
}

/* Filled, not outlined: at 18px in a disc the stroke-only glyph reads thin. */
.enc .player-play > svg {
  flex-basis: 18px;
  width: 18px;
  height: 18px;
  fill: currentColor;
}

/* Waiting on data, the disc spins Lucide's loader — stroked, not filled like
   play: filled, the arc is a wedge. */
.enc .player-bar [data-busy] > svg {
  fill: none;
  animation: enc-spin 0.9s linear infinite;
}

@keyframes enc-spin {
  to { rotate: 360deg; }
}

/* Tabular figures and a width held to the longest the clock will show, so the
   track does not twitch every time a digit changes. */
.enc .player-time {
  flex: none;
  min-width: var(--time-ch, 4ch);
  color: var(--text-secondary);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* The track is 40px of hit area around a 4px rail. The rail is drawn by its own
   element, the range input lies over it, transparent but for the thumb: the
   input is what the keyboard, the screen reader and the finger talk to. */
.enc .player-track {
  position: relative;
  flex: 1;
  min-width: 0;
  height: 40px;
}

.enc .player-rail {
  position: absolute;
  inset-inline: 0;
  top: 50%;
  height: 4px;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: color-mix(in oklab, var(--text-primary) 16%, transparent);
  translate: 0 -50%;
  transition: height var(--dur-fast) var(--ease-out);
  pointer-events: none;
}

.enc .player-track:hover .player-rail {
  height: 6px;
}

.enc .player-buffered,
.enc .player-played {
  position: absolute;
  inset-block: 0;
  left: 0;
}

.enc .player-buffered {
  width: var(--buffered, 0%);
  background: color-mix(in oklab, var(--text-primary) 16%, transparent);
}

.enc .player-played {
  width: var(--played, 0%);
  background: var(--action-paint-along);
}

/* Chapter starts from the outline, cut into the rail in the bar's own colour:
   gaps, not marks, so the rail reads as the meeting in parts. */
.enc .player-cuts > i {
  position: absolute;
  inset-block: 0;
  width: 2px;
  background: var(--bg-card);
  translate: -1px 0;
}

/* Under the track: the bar sticks to the top of the page, and the space above
   it is the top bar's. Sized to its words, not to the room left of where it
   is pinned — near the track's end that room is a few pixels, and the chapter
   would shrink to an ellipsis. */
.enc .player-tip {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 2px;
  width: max-content;
  max-width: 280px;
  padding: var(--space-1) var(--space-2);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--bg-raised);
  box-shadow: var(--shadow-lift);
  font-size: var(--fs-app-sm);
  line-height: 1.3;
  white-space: nowrap;
  translate: -50% 0;
  pointer-events: none;
}

.enc .player-tip[hidden],
.enc .player-tip > [hidden] {
  display: none;
}

.enc .player-tip-chapter {
  max-width: 100%;
  overflow: hidden;
  color: var(--text-primary);
  font-weight: var(--fw-medium);
  text-overflow: ellipsis;
}

.enc .player-tip-at {
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.enc .player-scrub {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  appearance: none;
}

.enc .player-scrub:focus-visible {
  outline: none;
}

.enc .player-scrub::-webkit-slider-runnable-track {
  height: 4px;
  background: transparent;
}

.enc .player-scrub::-moz-range-track {
  height: 4px;
  background: transparent;
}

/* The thumb comes up under the pointer or the keyboard, and stays up on touch,
   where there is no hover to call it. */
.enc .player-scrub::-webkit-slider-thumb {
  width: 14px;
  height: 14px;
  margin-top: -5px;
  border: 0;
  border-radius: 50%;
  background: var(--action-to);
  scale: 0;
  transition: scale var(--dur-fast) var(--ease-out);
  appearance: none;
}

.enc .player-scrub::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: 0;
  border-radius: 50%;
  background: var(--action-to);
  scale: 0;
  transition: scale var(--dur-fast) var(--ease-out);
}

.enc .player-track:hover .player-scrub::-webkit-slider-thumb,
.enc .player-scrub:focus-visible::-webkit-slider-thumb,
.enc .player-scrub:active::-webkit-slider-thumb {
  scale: 1;
}

.enc .player-track:hover .player-scrub::-moz-range-thumb,
.enc .player-scrub:focus-visible::-moz-range-thumb,
.enc .player-scrub:active::-moz-range-thumb {
  scale: 1;
}

.enc .player-scrub:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px var(--bg-card), 0 0 0 5px var(--focus-ring);
}

.enc .player-scrub:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 3px var(--bg-card), 0 0 0 5px var(--focus-ring);
}

@media (hover: none) {
  .enc .player-scrub::-webkit-slider-thumb { scale: 1; }
  .enc .player-scrub::-moz-range-thumb { scale: 1; }
}

/* ── The player on a picture ──
 * A video gets the video sites' frame: the bar laid over the picture's foot on
 * a scrim, out of the way while the meeting plays and the pointer rests. A
 * picture is dark whatever the page's theme, and so is everything over it: the
 * frame sets its own ink rather than take the theme's. Not sticky — a 16:9
 * frame pinned to the top would cover what is being read.
 */
/* Static, not relative: the sticky rule's `top` would shift a relative wrap
   onto what follows it. */
.enc .sticky-player[data-player='video'] {
  position: static;
}

.enc .player-stage {
  --text-primary: var(--white);
  --text-secondary: rgb(255 255 255 / 0.8);
  --text-muted-strong: var(--white);
  --red: var(--crimson);
  --focus-ring: var(--crimson);
  --bg-card: #000;
  --bg-raised: rgb(255 255 255 / 0.16);
  --bg-active: rgb(255 255 255 / 0.24);
  --bg-menu-hover: rgb(255 255 255 / 0.12);
  --surface-2: rgb(24 24 24 / 0.94);
  --border-hairline: rgb(255 255 255 / 0.12);
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-field);
  background: black;
  color: var(--text-primary);
  color-scheme: dark;
  /* Captions are sized by the picture, not the window; so is the bar's layout. */
  container: player-stage / inline-size;
}

/* The column's width: app.css held the bare element to 480px, a size for the
   browser's own controls, not for a picture people watch. 16:9 before the file
   says otherwise, so the page does not jump when it does; a portrait recording
   is held inside it, not stretched. The id outranks app.css's. */
.enc #player-wrap .player-stage > video {
  max-width: none;
  margin: 0;
  aspect-ratio: 16 / 9;
  border-radius: 0;
  object-fit: contain;
}

.enc .player-stage:fullscreen {
  border-radius: 0;
}

/* The page under a full-screen frame is not on screen: its kept scrollbar
   gutter would stand as a strip down the picture's edge. */
html:has(.player-stage:fullscreen) {
  overflow: hidden;
  scrollbar-gutter: auto;
}

.enc #player-wrap .player-stage:fullscreen > video {
  height: 100%;
  max-height: none;
  aspect-ratio: auto;
}

/* Before the first play, a disc in the middle: the picture is a meeting to
   play, not a still. The triangle's weight sits left of its box; 2px right
   centres it to the eye. */
.enc .player-big {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 64px;
  height: 64px;
  margin: auto;
  padding: 0;
  border-radius: var(--radius-full);
  box-shadow: 0 0 0 6px var(--action-halo);
}

.enc .player-big > svg {
  flex-basis: 28px;
  width: 28px;
  height: 28px;
  fill: currentColor;
  translate: 2px 0;
}

.enc .player-stage[data-started] > .player-big,
.enc .player-stage:has(> .player-bar[data-state='error']) > .player-big,
.enc .player-stage:has(> .player-away:not([hidden])) > .player-big {
  display: none;
}

/* The picture gone to its own window: the frame says where. */
.enc .player-away {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  margin: 0;
  background: black;
  color: var(--text-secondary);
  font-size: var(--fs-app-md);
}

.enc .player-away[hidden] {
  display: none;
}

/* The rail across the full width, the controls under it. The scrim is what
   keeps white legible over a white slide. */
.enc .player-stage > .player-bar {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 2;
  flex-wrap: wrap;
  gap: 0 var(--space-1);
  height: auto;
  padding: var(--space-24) var(--space-3) var(--space-2);
  border: 0;
  border-radius: 0;
  background: linear-gradient(to top, rgb(0 0 0 / 0.8), rgb(0 0 0 / 0.5) 50%, transparent);
  transition: opacity var(--dur-base) var(--ease-out);
}

.enc .player-stage .player-track {
  flex: 1 0 100%;
  height: 24px;
}

/* Over the picture the tip rises above the rail: under it are the controls. */
.enc .player-stage .player-tip {
  top: auto;
  bottom: calc(100% + var(--space-1));
  background: var(--surface-2);
}

/* The speed list rises over the picture as well: below the bar is the page,
   and in full screen nothing at all. */
@supports (appearance: base-select) {
  .enc .player-stage select::picker(select) {
    position-area: block-start span-inline-end;
  }
}

.enc .player-stage .player-play > svg,
.enc .player-bar :is(.player-cc, .player-pip, .player-full) > svg,
.enc .player-close > svg {
  flex-basis: 20px;
  width: 20px;
  height: 20px;
}

/* The clock reads as one, 12:04 / 42:15. Where you are keeps the width of the
   longest it will show, set against the slash, so nothing twitches. */
.enc .player-stage .player-time {
  margin-inline-start: var(--space-2);
  text-align: end;
}

.enc .player-stage .player-time + .player-time {
  min-width: 0;
  margin-inline-start: 0;
  text-align: start;
}

.enc .player-stage .player-time + .player-time::before {
  content: '/';
  content: '/' / '';
  padding-inline: 0.4em;
}

.enc .player-gap {
  flex: 1;
}

/* A narrow frame — a phone, the corner, a column beside the assistant — has no
   row for the clock and every control once a meeting runs past the hour. The
   clock takes the rail's ends, as on the audio bar; an empty item after it
   breaks the line, and the controls keep a row of their own. */
@container player-stage (max-width: 639px) {
  .enc .player-stage > .player-bar::before {
    content: '';
    flex-basis: 100%;
    order: -1;
  }

  .enc .player-stage .player-time {
    order: -3;
    margin-inline-start: 0;
    text-align: start;
  }

  .enc .player-stage .player-track {
    order: -2;
    flex: 1;
    margin-inline: var(--space-1);
  }

  .enc .player-stage .player-time + .player-time {
    order: -2;
    min-width: var(--time-ch, 4ch);
    text-align: end;
  }

  .enc .player-stage .player-time + .player-time::before {
    content: none;
  }
}

/* A switch that is on — picture in picture, captions — says so with a short
   line under its glyph. */
.enc .player-stage [aria-pressed='true'] {
  position: relative;
}

.enc .player-stage [aria-pressed='true']::after {
  content: '';
  position: absolute;
  inset-inline: 10px;
  bottom: 6px;
  height: 2px;
  border-radius: var(--radius-full);
  background: var(--red);
}

/* At rest the bar steps aside, and the pointer with it. Out of sight, not out
   of reach: Tab still lands in it, and focus brings it back (player.js). */
.enc .player-stage[data-idle] {
  cursor: none;
}

.enc .player-stage[data-idle] > :is(.player-bar, .player-head) {
  opacity: 0;
  pointer-events: none;
}

.enc .player-stage[data-idle] .player-close {
  pointer-events: none;
}

/* No transcript, no captions: the switch is not offered. The bar's buttons
   are flex, which beats the bare `[hidden]`. */
.enc .player-cc[hidden] {
  display: none;
}

/* Captions: white on a solid plate, so a slide behind them never eats a
   word. Low on the picture while the bar rests; lifted over it while it
   shows, moved, not resized, so the words hold still to the eye. */
.enc .player-captions {
  --low: var(--space-16);
  position: absolute;
  inset-inline: 0;
  bottom: var(--low);
  z-index: 1;
  display: flex;
  justify-content: center;
  margin: 0;
  padding-inline: 5%;
  font-size: 18px;
  font-size: clamp(13px, 2.4cqi, 36px);
  line-height: 1.35;
  text-align: center;
  pointer-events: none;
  transition: translate var(--dur-base) var(--ease-out);
}

.enc .player-captions > span {
  max-width: 100%;
  padding: 0.2em 0.6em;
  border-radius: 0.35em;
  background: rgb(0 0 0 / 0.78);
  color: var(--white);
  font-weight: 500;
  white-space: pre-line;
  text-wrap: balance;
}

.enc .player-stage:not([data-idle]) > .player-captions {
  translate: 0 calc(var(--low) - var(--controls-h, 72px) - var(--space-2));
}

.enc .player-stage:fullscreen > .player-captions {
  --low: var(--space-5);
}

.enc .player-captions[hidden],
.enc .player-stage:not([data-started]) > .player-captions,
.enc .player-stage:has(> .player-bar[data-state='error']) > .player-captions,
.enc .player-stage:has(> .player-away:not([hidden])) > .player-captions {
  display: none;
}

/* Docked (player.js): read past while it plays, the frame keeps to the
   window's corner, clear of the assistant where that is open. Its place in
   the column is held at the size it left, black like the frame, so nothing
   below moves when it goes or comes back. */
.enc #player-wrap[data-docked]::before {
  content: '';
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 60vh;
  border-radius: var(--radius-field);
  background: black;
}

@keyframes enc-dock-in {
  from { opacity: 0; translate: 0 var(--space-4); }
}

.enc #player-wrap[data-docked] > .player-stage {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: 25;
  width: min(400px, 100vw - 2 * var(--space-4));
  box-shadow: var(--shadow-lift);
  animation: enc-dock-in var(--dur-base) var(--ease-out);
  transition: right var(--dur-base) var(--ease-out);
}

body.enc[data-assistant-open] #player-wrap[data-docked] > .player-stage {
  right: calc(var(--assistant-w, 0px) + var(--space-4));
}

/* No window of its own for the corner frame: it is one already, and the big
   frame and full screen still offer it. */
.enc #player-wrap[data-docked] .player-stage:not(:fullscreen) .player-pip {
  display: none;
}

/* The way to put it away, top right as on any small window: the bar's scrim
   turned over, so the cross reads on a white slide, resting with the bar.
   Not in full screen, where the frame is no longer the small one. */
.enc .player-head {
  display: none;
}

.enc #player-wrap[data-docked] .player-stage:not(:fullscreen) > .player-head {
  position: absolute;
  inset: 0 0 auto;
  z-index: 2;
  display: flex;
  justify-content: flex-end;
  padding: var(--space-2) var(--space-3) var(--space-24);
  background: linear-gradient(to bottom, rgb(0 0 0 / 0.6), transparent);
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

.enc .player-close {
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: var(--radius-full);
  pointer-events: auto;
}

/* ── Transcript page: the card stack ── */
.enc #ask-card,
.enc #summary-card,
.enc #template-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);
}

/* The suggestion chips close the list and space themselves with trailing
 * margins; the list gives those back, so the last chip meets the card's corner
 * at its padding, as everything else in the card does. The answers above keep
 * their width. */
.enc #ask-list:has(> .ask-suggestion) {
  margin-right: calc(-1 * var(--space-2));
  margin-bottom: calc(-1 * var(--space-2));
}

.enc #ask-list:has(> .ask-suggestion) > :not(.ask-suggestion) {
  margin-right: var(--space-2);
}

/* Prose: an LLM answer, the summary and a meeting type's notes 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,
.enc .template-md {
  color: var(--text-secondary);
  font-size: var(--fs-app-md);
  line-height: var(--lh-body);
}

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

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

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

.enc :is(.qa-answer, #summary, .template-md) :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, .template-md) pre {
  padding: var(--space-3);
  overflow: auto;
}

.enc :is(.qa-answer, #summary, .template-md) 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' 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;
}

/* On a phone the chips become a list: each spans the column (less the margin
   the list gives back), grows to two lines rather than running off, and is a
   thumb's 44 tall. A capsule two lines tall is a lozenge, so the row takes a
   control's corner. Read from the left, as a list is. */
@media (max-width: 640px) {
  .enc #ask-list .ask-suggestion {
    justify-content: flex-start;
    width: calc(100% - var(--space-2));
    height: auto;
    min-height: 44px;
    padding-block: var(--space-2);
    border-radius: var(--radius-control);
    line-height: var(--lh-heading);
  }
}

/* The questions asked before that didn't load (app.js): their box stands over
   the suggestions, and the first chip would sit on its frame. */
.enc #ask-list > .load-error:not(:last-child) {
  margin-bottom: var(--space-3);
}

/* ── 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;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* On a phone, while reading, the buttons take their own line under the heading
   and split it; a long label takes a row of its own. Editing lays the row out
   for itself (its phone rule below). */
@media (max-width: 640px) {
  .enc #transcript-card:not(.is-editing) .transcript-actions {
    flex-basis: 100%;
  }

  .enc #transcript-card:not(.is-editing) .transcript-actions > button {
    flex: 1 1 0;
    min-width: max-content;
  }
}

/* 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,
.enc .transcript-edit,
.enc .transcript-find-toggle {
  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),
.enc .transcript-edit:hover:not(:disabled),
.enc .transcript-find-toggle: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,
.enc .transcript-edit:active,
.enc .transcript-find-toggle:active {
  background: var(--bg-raised);
}

/* Find stays pressed while its panel is open: the same button closes it. */
.enc .transcript-find-toggle[aria-expanded='true'],
.enc .transcript-find-toggle[aria-expanded='true']:hover:not(:disabled) {
  background: var(--bg-raised);
  color: var(--text-primary);
}

/* Done is the way out of correcting, so it is the one filled button in the bar —
   at the height of the quiet ones it replaces, so the bar does not jump. */
.enc .transcript-done {
  height: 30px;
  padding: 0 var(--space-3);
  font-size: var(--fs-app-sm);
}

.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 {
  position: relative;
  margin: 0 0 var(--space-3);
  color: var(--text-secondary);
  font-size: var(--fs-app-base);
  line-height: var(--lh-body);
  transition: color var(--dur-base) var(--ease-out);
}

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

/* The line being spoken (follow.js): lit to full ink, with a rail in the
   card's margin, so it is found at a glance and nothing in the text moves. */
.enc .transcript-para.is-current {
  color: var(--text-primary);
}

@keyframes enc-current-in {
  from { opacity: 0; scale: 1 0.6; }
}

.enc .transcript-para.is-current::before {
  content: '';
  position: absolute;
  inset-block: 0.3em;
  left: calc(-1 * var(--space-3));
  width: 3px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--action-from), var(--action-to));
  translate: -50% 0;
  animation: enc-current-in var(--dur-base) var(--ease-out);
}

/* ── The transcript, being corrected (transcript-edit.js) ──
 * Every paragraph's words open at once, and a transcript is hundreds of them:
 * the dashed box the report draws round each of its few fields would turn this
 * read into a grid. So a paragraph shows it can be corrected under the
 * pointer, and the one being typed in is solid and lit, as in the report. All
 * of it is drawn outside the paragraph — outline and shadow, never padding — so
 * opening edit mode moves no word.
 */
.enc #transcript-card.is-editing .transcript-para {
  border-radius: var(--radius-field);
  outline: var(--stroke) dashed transparent;
  /* 4 and not the report's 6: paragraphs are 12px apart, and two rings of 6
     would meet between them. */
  outline-offset: 4px;
  cursor: text;
  scroll-margin-top: calc(var(--transcript-clear, var(--topbar-h, 0px)) + var(--space-3));
  transition: color var(--dur-base) var(--ease-out),
    outline-color var(--dur-fast) var(--ease-out);
}

.enc #transcript-card.is-editing .transcript-para:hover {
  outline-color: var(--border-control-hover);
}

/* The fill reaches the ring by a shadow of its own colour: padding would move
   the words. Without :has() the browser's own ring on the words stays. */
@supports selector(:has(*)) {
  .enc #transcript-card.is-editing .transcript-para:has(> .transcript-text:focus) {
    outline: var(--stroke-strong) solid var(--focus-ring);
    background: var(--bg-active);
    box-shadow: 0 0 0 4px var(--bg-active);
  }

  .enc #transcript-card.is-editing .transcript-text:is(:focus, :focus-visible) {
    outline: none;
  }
}

/* The heading gives way to how the words are corrected: the card is plainly
   the transcript, and the bar is the mode's now. Out of sight, not out of the
   page — it is still the card's heading to a screen reader. One row as
   before: the hint is about as long as the copy buttons it stands in for. */
.enc #transcript-card.is-editing .transcript-summary h2 {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Where the row is short the hint folds and Done stays at the end, where the
   hand left it; on a phone it is too short for both, and Done goes under. */
.enc .transcript-hint {
  flex: 1 1 16em;
  min-width: 0;
  margin: 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

/* Folded, it folds between its two sentences. */
.enc #transcript-keys:not([hidden]) {
  display: inline-block;
}

/* Every status in one cell, the live one over the others unseen: the slot is
   as wide as the longest, so saving moves nothing beside it. */
.enc .transcript-status-slot {
  display: inline-grid;
  justify-items: end;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  white-space: nowrap;
}

.enc .transcript-status-slot[hidden] {
  display: none;
}

.enc .transcript-status-slot > * {
  grid-area: 1 / 1;
}

.enc .transcript-status-slot > [aria-hidden] {
  visibility: hidden;
}

.enc .transcript-status.is-failed {
  color: var(--status-failed-text);
}

/* On a phone Edit sits alone at the left edge, so Done starts its row there,
   under the hint, and what is saving follows it rather than holding it off. */
@media (max-width: 640px) {
  .enc .transcript-hint {
    flex-basis: 100%;
  }

  .enc .transcript-status-slot {
    order: 1;
    justify-items: start;
  }

  /* Find at the row's other end, away from Done. */
  .enc #transcript-card.is-editing .transcript-actions {
    flex-grow: 1;
  }

  .enc .transcript-find-toggle {
    order: 2;
    margin-left: auto;
  }

  /* While finding the bar sticks (below), and the keyboard has half the
     screen: the hint gives its line to the transcript. */
  .enc #transcript-card.is-finding .transcript-hint {
    display: none;
  }

  /* For the same reason the player steps back into the page while finding:
     stuck above the bar and its panel it would leave the words a few lines.
     Where it was in the page it still is, so nothing below it moves. */
  .enc:has(#transcript-card.is-finding) .sticky-player {
    position: static;
  }
}

/* Down a long transcript, Done and what is saving stay in reach: the bar
   sticks under what already sticks (--transcript-pin, transcript-edit.js).
   Opaque, so the words pass under it: up to the card's top by a margin its
   padding cancels, so nothing moves, and out past both sides, for the card to
   cut at its own edges and corners — whatever padding and radius the brand
   gives the card, the bar meets them. A clip, not hidden: hidden would make
   the card the scroller the bar sticks in. On a phone only while finding —
   the field and the way to the next place found have to stay in reach — and
   not otherwise: there the keyboard has half the screen already. */
@supports (overflow: clip) {
  .enc #transcript-card.is-editing {
    overflow: clip;
  }

  .enc #transcript-card.is-editing .transcript-summary {
    position: relative;
    z-index: 2;
    margin-top: calc(-1 * var(--space-4));
    margin-inline: -100vw;
    border-inline: 100vw solid var(--bg-card);
    padding-top: var(--space-4);
    background: var(--bg-card);
  }

  /* The words fade in under the bar rather than being cut by its edge. Over
     the gap below it, so where the bar has not stuck it is not seen. */
  .enc #transcript-card.is-editing .transcript-summary::after {
    content: '';
    position: absolute;
    inset-inline: -100vw;
    top: 100%;
    height: var(--space-3);
    background: linear-gradient(var(--bg-card), transparent);
    pointer-events: none;
  }

  .enc #transcript-card.is-finding .transcript-summary {
    position: sticky;
    top: var(--transcript-pin, var(--topbar-h, 0px));
  }
}

@media (min-width: 641px) {
  @supports (overflow: clip) {
    .enc #transcript-card.is-editing .transcript-summary {
      position: sticky;
      top: var(--transcript-pin, var(--topbar-h, 0px));
    }
  }
}

/* ── Find and replace (transcript-find.js) ──
 * Under the bar's row, as wide as the card. The query and its replacement in
 * one column, so the two fields line up, and beside each what goes with it:
 * how the query is matched, then the buttons that replace. Where the card is
 * too narrow for both columns, one: each control under the one it goes with. */
.enc #transcript-card.is-editing .transcript-summary {
  container-type: inline-size;
}

.enc .transcript-find {
  display: grid;
  flex-basis: 100%;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-1);
}

.enc .transcript-find[hidden] {
  display: none;
}

@container (max-width: 600px) {
  .enc .transcript-find {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The field draws the edge and the ring, so the count and the arrows are
   inside it with the words. Its icon is centred on the field's own square, as
   the search bar's is; the arrows are inset from its end by the field's gap,
   their corner its corner less that gap. */
.enc .find-field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--control-h);
  padding-left: calc((var(--control-h) - 16px) / 2 - var(--stroke));
  padding-right: calc(var(--field-inset) - var(--stroke));
  border: var(--stroke) solid var(--border-control);
  border-radius: var(--radius-field);
  background: var(--surface-1);
  color: var(--text-muted-strong);
  cursor: text;
  transition: border-color var(--dur-fast) var(--ease-out);
}

.enc .find-field:hover {
  border-color: var(--border-control-hover);
}

.enc .find-field > svg {
  flex: none;
  width: 16px;
  height: 16px;
}

.enc .find-field > input[type='text'] {
  flex: 1 1 0;
  align-self: stretch;
  min-width: 0;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  font-size: var(--fs-app-base);
  line-height: normal;
}

@supports selector(:has(*)) {
  .enc .find-field:has(> input:focus) {
    outline: var(--focus-field-outline);
    outline-offset: 2px;
    border-color: var(--focus-field-edge);
    box-shadow: var(--focus-field-ring);
  }

  .enc .find-field > input[type='text']:focus {
    outline: none;
    box-shadow: none;
  }
}

.enc .find-count {
  flex: none;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Nothing to count yet, so no gap held for it. Kept in the tree, not hidden: it
   is a live region, and a hidden one is not read out when it changes. */
.enc .find-count:empty {
  margin-left: calc(-1 * var(--space-2));
}

.enc .find-step {
  flex: none;
  width: calc(var(--control-h) - 2 * var(--field-inset));
  height: calc(var(--control-h) - 2 * var(--field-inset));
  padding: 0;
  border-color: transparent;
  border-radius: var(--radius-inset);
  background: transparent;
  color: var(--text-muted-strong);
}

/* The two arrows are one control: nearer each other than to the count. */
.enc .find-step + .find-step {
  margin-left: calc(-1 * var(--space-1));
}

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

.enc .find-step:active {
  background: var(--bg-active);
}

/* The boxes hang their hover plate into the gap, so the box itself lines up
   with the Replace button under it. */
.enc .find-options {
  display: flex;
  flex-wrap: wrap;
  margin-left: calc(-1 * var(--space-2));
}

.enc .find-option {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--control-h);
  padding: 0 var(--space-2);
  border-radius: var(--radius-control);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.enc .find-option:hover,
.enc .find-option:has(> :checked) {
  color: var(--text-primary);
}

.enc .find-option:hover {
  background: var(--bg-raised);
}

.enc .find-option > input {
  flex: none;
  margin: 0;
}

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

/* A finger's height, and 16px in the fields so iOS does not zoom to them. */
@media (max-width: 760px) {
  .enc .find-field,
  .enc .find-option {
    height: 44px;
  }

  .enc .find-step {
    position: relative;
    width: calc(44px - 2 * var(--field-inset));
    height: calc(44px - 2 * var(--field-inset));
  }

  /* Under a finger each arrow takes the field's full height, and half the gap
     between the two: not the 44 square, which would put one over the other. */
  .enc .find-step::after {
    content: '';
    position: absolute;
    inset: calc(-1 * var(--field-inset)) calc(-1 * var(--space-1) / 2);
  }

  .enc .find-field > input[type='text'] {
    font-size: var(--fs-app-md);
  }
}

/* The places found, lit in the words (transcript-find.js): all of them in a
   wash of amber, the one stepped to in full — a marker pen, as in any find. */
::highlight(transcript-find) {
  background-color: color-mix(in oklab, var(--amber) 38%, transparent);
  color: var(--text-primary);
}

::highlight(transcript-find-current) {
  background-color: var(--amber);
  color: var(--ink);
}

/* High contrast keeps the system's own colours, so they are what lights them. */
@media (forced-colors: active) {
  ::highlight(transcript-find) {
    background-color: Mark;
    color: MarkText;
  }

  ::highlight(transcript-find-current) {
    background-color: Highlight;
    color: HighlightText;
  }
}

/* Under the words, in a note's size: why they did not save, or why they were
   put back. The icon hangs in the margin so a wrapped line starts under the
   first. */
.enc .transcript-note {
  position: relative;
  margin: var(--space-1) 0 0;
  padding-left: var(--space-4);
  font-size: var(--fs-app-sm);
  line-height: var(--lh-body);
}

.enc .transcript-note > svg {
  position: absolute;
  top: 3px;
  top: calc((1lh - 14px) / 2);
  left: 0;
}

.enc .transcript-note.is-error {
  color: var(--status-failed-text);
}

.enc .transcript-note.is-info {
  color: var(--text-muted-strong);
}

/* "Corrected" ends the words it is about: a quiet word, not a badge — on a
   page of corrections a badge per paragraph would be all there is to see. A
   space sets it off from them (transcript-edit.js), not a margin: at the start
   of a line a margin would indent it. */
.enc .transcript-corrected {
  gap: 2px;
  font-size: var(--fs-app-xs);
  vertical-align: baseline;
}

.enc :is(.transcript-corrected, .transcript-restore) > svg {
  flex-basis: 14px;
  width: 14px;
  height: 14px;
}

.enc .transcript-corrected > svg {
  transition: transform var(--dur-fast) var(--ease-out);
}

.enc .transcript-corrected[aria-expanded='true'] > svg {
  transform: rotate(180deg);
}

/* A finger gets the size of a tap, not of the word. */
@media (pointer: coarse) {
  .enc .transcript-corrected {
    position: relative;
  }

  .enc .transcript-corrected::after {
    content: '';
    position: absolute;
    inset: -12px -8px;
  }
}

/* What recognition heard, opened from "Corrected", with the way back to it. */
.enc .transcript-original {
  margin: var(--space-1) 0 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  line-height: var(--lh-body);
}

.enc .transcript-original q {
  color: var(--text-secondary);
}

.enc :is(.transcript-restore, .transcript-retry) {
  gap: var(--space-1);
  margin-left: var(--space-2);
  font-size: inherit;
  line-height: inherit;
  vertical-align: baseline;
}

/* Trying again is what the note is there for: a link to the eye from the
   start, in the note's colour. */
.enc .transcript-retry {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.enc .transcript-retry:hover {
  color: inherit;
}

/* The way back to it (follow.js), once the reader has scrolled it out of
   view: at the foot of the window, centred on the transcript's column, and
   lifted over the docked video where the two would meet. A plate, not the
   brand fill: it offers, it does not insist. `button.` to outrank the base
   button's hover. */
.enc button.follow-pill {
  position: fixed;
  bottom: calc(max(var(--space-5), var(--follow-lift, 0px) + var(--space-3)) + env(safe-area-inset-bottom, 0px));
  left: var(--follow-x, 50%);
  z-index: 24;
  height: 40px;
  padding: 0 var(--space-4) 0 var(--space-3);
  border-color: var(--border-hairline);
  border-radius: var(--radius-full);
  background: var(--surface-2);
  box-shadow: var(--shadow-lift);
  color: var(--text-primary);
  font-size: var(--fs-app-sm);
  font-weight: var(--fw-medium);
  opacity: 0;
  translate: -50% var(--space-2);
  visibility: hidden;
  transition: background var(--dur-fast) var(--ease-out),
    scale var(--dur-fast) var(--ease-out),
    opacity var(--dur-base) var(--ease-out),
    translate var(--dur-base) var(--ease-out),
    visibility 0s linear var(--dur-base);
}

.enc button.follow-pill[data-shown] {
  opacity: 1;
  translate: -50% 0;
  visibility: visible;
  transition-delay: 0s;
}

.enc button.follow-pill:hover {
  background: var(--surface-3);
}

.enc button.follow-pill:active {
  background: var(--bg-active);
}

.enc button.follow-pill > svg {
  transition: rotate var(--dur-base) var(--ease-out);
}

.enc button.follow-pill[data-dir='up'] > svg {
  rotate: 180deg;
}

@media print {
  .enc .follow-pill {
    display: none;
  }
}

/* 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),
    scale var(--dur-fast) var(--ease-out);
}

/* The chip is a button (format-time.js), so it takes off what every
   `.enc button` wears: the frame, which would shave 2px off the 20px and drop
   the label off centre, and the control weight, which would set a time bolder
   than the sentence it sits in. */
.enc button.ts {
  border: 0;
  font-weight: inherit;
}

.enc button.ts:hover {
  /* Painted, not a 34% mix: on cream that mix is a pale pink, and nothing on
     it reads. Filled, the hover is the action in both themes; the ends are
     the ones `.enc button:hover` moved. */
  background: linear-gradient(135deg, var(--paint-from), var(--paint-to));
  color: var(--on-action);
  text-decoration: none;
}

/* Pressed — by the mouse, or by Space without one — is a step deeper, in the
   paint's ink: `.enc button:active` moves the ends, and the tint's red would
   otherwise stay on the label. */
.enc button.ts:active {
  background: linear-gradient(135deg, var(--paint-from), var(--paint-to));
  color: var(--on-action);
}

/* With nothing to seek — a Meet transcript ingest has no media — the time is
   text (format-time.js): no plate, no pointer, nothing that offers a press. */
.enc span.ts {
  display: inline;
  height: auto;
  padding: 0;
  background: none;
  color: var(--text-muted);
  line-height: inherit;
  cursor: auto;
}

/* ── 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: calc(var(--radius-control) + var(--space-1));
  background: var(--bg-card);
  box-shadow: inset 0 0 0 var(--stroke) var(--border-hairline);
  /* One row, always — it scrolls when the labels outgrow it. See the phone
     rule after the tabs. */
  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;
}

.enc .enc-tab {
  flex: 0 0 auto;
  height: var(--tab-h);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-control);
  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: var(--tab-h);
  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);
}

/* A phone gives the strip the whole column, as it does every control. Tabs that
   fit share it out; six do not — they need ~440px and a phone gives 328 — so
   they keep their width and the strip scrolls, the tab cut off at its edge
   saying there are more. They never shrink: a squeezed tab wraps its label.
   tabs.js scrolls the open tab into view, so nothing is selected off-screen. */
@media (max-width: 640px) {
  .enc .enc-tabs,
  .enc .enc-tabs.settings-tabs {
    display: flex;
    width: 100%;
  }

  .enc .enc-tab {
    flex: 1 0 auto;
  }
}

/* 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: the meeting's type (enc/templates.js) ──
 * «Meeting type: [Sales call ▾]» above the summary for whoever may edit the
 * meeting, and the notes made for that type in a card under it for everyone.
 * The menu is «Add meeting»'s card with its rows; the notes are prose, as the
 * summary is, under a heading per section. The bar's `display` is the script's.
 */
.enc .template-bar {
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-3);
}

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

/* As wide as the button: menu.js measures the wrapper to place the menu. */
.enc .template-pick {
  display: inline-flex;
}

/* The bar holding the button's place until the types are in, shaped as it is. */
.enc .template-pick > .skel {
  border-radius: var(--radius-control);
}

.enc .template-btn-icon {
  display: inline-flex;
  color: var(--text-brand);
}

.enc .template-btn > svg {
  color: var(--text-muted);
}

/* The tick on the name's line, as the icon is, not halfway down a description. */
.enc :is(.template-item-end, .project-item-end) {
  height: 20px;
  align-items: center;
}

/* A phone wraps the descriptions: no word left alone on the second line. */
.enc .template-item-meta {
  text-wrap: pretty;
}

/* A refused pick, under the bar; a refused Try again, under the line that
   offered it. */
.enc .template-error {
  flex-basis: 100%;
  margin: 0;
  color: var(--status-failed);
  font-size: var(--fs-app-sm);
  line-height: var(--lh-body);
}

.enc #template-card .template-error {
  margin: calc(-1 * var(--space-3)) 0 var(--space-4);
}

/* Nothing under them (a first build that failed): the card's own padding is
   the step under the last line, as over the title. */
.enc .template-status:has(+ .template-error[hidden] + #template-notes:empty),
.enc #template-card .template-error:has(+ #template-notes:empty) {
  margin-bottom: 0;
}

/* What the notes wait on or are missing: the summary's notice, a quiet line and
   a quiet button. Focused when its own button goes, as a place to stand. */
.enc .template-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-4);
  color: var(--text-muted-strong);
  line-height: var(--lh-body);
}

.enc .template-status[hidden] {
  display: none;
}

.enc .template-status:focus {
  outline: none;
}

.enc .template-again {
  height: 30px;
  padding: 0 var(--space-3);
  font-size: var(--fs-app-sm);
}

.enc .template-section + .template-section {
  margin-top: var(--space-4);
}

/* A step under the card's title, a step over the points. */
.enc .template-section > h3 {
  margin-bottom: var(--space-2);
  color: var(--text-primary);
  font-size: var(--fs-app-md);
}

/* The points hang under their heading rather than 40px in from it. */
.enc .template-md :is(ul, ol) {
  padding-left: var(--space-4);
}

/* The bars where the first notes will be, on the prose's lines. */
.enc .template-section > .skel-lines {
  font-size: var(--fs-app-md);
  line-height: var(--lh-body);
}

.enc .template-none {
  margin: 0;
  color: var(--text-muted);
  line-height: var(--lh-body);
}

/* ── 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);
}

/* On a phone the strip is a small form: «Send to:» heads it, the team, the
   project and Apply each take the column — Apply as tall as the fields and with
   their corner, a capsule that wide being a bar — and Send fills what Select
   all leaves it, the spacer that pushed the two to the end having nothing to
   push. */
@media (max-width: 640px) {
  .enc .tasks-toolbar > .tasks-toolbar-label {
    flex-basis: 100%;
  }

  .enc :is(.tasks-team-select, .tasks-project-select, .tasks-apply-to-selected) {
    flex: 1 1 100%;
  }

  .enc .tasks-apply-to-selected {
    height: var(--field-h);
    border-radius: var(--radius-control);
  }

  .enc .tasks-toolbar > span:empty {
    display: none;
  }

  .enc .tasks-send-btn {
    flex: 1 1 auto;
  }
}

/* `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 field height, not a number: the date field is the only control here
   without a min-height of its own, so a fixed 36 left it 8px shorter than the
   selects beside it. No padding of its own either — the field shell's 12 for
   the text, and the chevron's and the calendar's for the icon.
   `background-color`, not the shorthand: the shorthand reset the chevron's
   repeat and size, and where the light theme repaints it (Safari) it tiled. */
.enc .tasks-team-select,
.enc .tasks-project-select,
.enc .task-linear-fields :is(select, input[type='text'], input[type='date']) {
  width: auto;
  height: var(--field-h);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
  background-color: 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 OS calendar glyph swapped for Lucide's, centred where the chevron is:
   half the field's height in from the edge. A mask, so it takes the theme's
   muted ink rather than the glyph's own black. */
.enc input[type='date'] {
  padding: 0 calc((var(--field-h) - 16px) / 2) 0 var(--space-3);
}

.enc input[type='date']::-webkit-calendar-picker-indicator {
  width: 16px;
  height: 16px;
  margin: 0 0 0 var(--space-2);
  padding: 0;
  background: var(--text-muted-strong);
  mask: 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='M8 2v3'/%3E%3Cpath d='M16 2v3'/%3E%3Crect x='3' y='3' width='18' height='18' rx='2'/%3E%3Cpath d='M3 9h18'/%3E%3C/svg%3E") center / contain no-repeat;
  cursor: pointer;
}

/* 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);
}

/* A phone has no room for a label column: an 82px label left a project's name
   186 to fit in, and it ran out of the card. Each label heads its field, close
   to it and a step from the field above, and every field takes the column. */
@media (max-width: 640px) {
  .enc .task-linear-fields {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-1);
  }

  .enc .task-linear-fields .label:not(:first-child) {
    margin-top: var(--space-2);
  }

  .enc .task-linear-fields :is(select, input[type='date']),
  .enc .task-linear-fields .task-assignee-list {
    justify-self: stretch;
    width: 100%;
  }
}

.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); }

/* The second try where the names didn't come: a line like the ones above it,
   not a button stretched across the box. */
.enc .task-assignee-list > button { align-self: start; 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 {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  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 {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: var(--fs-app-sm);
}

/* Why the task didn't go, at the top of its opened row, above the fields there
   are to change. */
.enc .task-send-error {
  margin: 0 0 var(--space-2);
  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;
}

/* On a phone each takes the column, one under another — the block row cannot
   share a line out, and Copy, filled, stays first. Generate, before there is a
   draft, spans it too. */
@media (max-width: 640px) {
  .enc :is(#recap-actions, #recap-empty) button {
    width: 100%;
    margin: 0 0 var(--space-2);
  }

  .enc #recap-actions button:last-child {
    margin-bottom: 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);
}

/* The email that didn't load, in Generate's place (app.js): the card is the
   frame, so the box drops its own dashed one. */
.enc #recap-card .load-error {
  border: 0;
  border-radius: 0;
}

/* ── 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);
}

/* On a phone the group takes its own line under the heading and the buttons
   split it; the one that will not fit takes a row of its own. */
@media (max-width: 640px) {
  .enc .report-actions {
    flex-basis: 100%;
  }

  .enc .report-actions > button {
    flex: 1 1 0;
    min-width: max-content;
  }
}

/* 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 {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  /* A flex item drops the separator's leading space; the gap stands in for it. */
  gap: 0.25em;
}

/* The letterhead's mark at footnote size: its cap height meets the line's. */
.enc .report-foot-mark {
  width: 64px;
}

.enc .report-foot-mark + span::before,
.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 sets
 * `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;
}

/* The admin user table is the same kind of list, so it gets the same panel. */
.enc :is(.transcription-list, .admin-users) {
  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 :is(.transcription-list, .admin-users) thead th:first-child {
  border-top-left-radius: var(--radius-surface);
}

.enc :is(.transcription-list, .admin-users) 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 :is(.transcription-list, .admin-users) :is(th, td):first-child {
  padding-left: var(--space-4);
}

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

/* Opaque, because rows scroll underneath it. */
.enc thead th {
  position: sticky;
  top: var(--topbar-h, 0px);
  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-label);
  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 :is(.transcription-list, .admin-users) tbody tr:last-child td:first-child {
  border-bottom-left-radius: var(--radius-surface);
}

.enc :is(.transcription-list, .admin-users) 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;
  /* A long list, not a card: tighter corners than the brand's panels. The
     header and last-row cells read the same token, so they follow. */
  --radius-surface: 16px;
}

.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;
}

/* ДЛИТЕЛЬНОСТЬ in caps is 116px of text; at 96 it ran into Uploader. */
.enc:lang(ru) .transcription-list th:nth-child(4) {
  width: 140px;
}

.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;
}

/* No projects yet, and no meeting in one (transcription-list.js): the Project
   column would be dashes only. Hidden, not left out, so the widths above still
   count it; the title takes its room. Three element steps outweigh the phone's
   label/value cells, which are flex. */
.enc .transcription-list.no-project thead tr th:nth-child(2),
.enc .transcription-list.no-project tbody tr td:nth-child(2) {
  display: none;
}

/* 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;
  gap: var(--space-1);
  color: var(--text-muted-strong);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-regular);
}

.enc .row-snippet-toggle svg {
  transition: transform var(--dur-fast) var(--ease-out);
}

.enc .row-snippet-toggle[aria-expanded='true'] svg {
  transform: rotate(90deg);
}

.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),
    background var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    scale 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);
}

/* ── 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;
}

/* The list before anything is in it (a new team, a new account): a start, not
   a dead end. What is missing, who will see it, and the two ways in. */
.enc .empty-start {
  gap: var(--space-2);
  padding: var(--space-6) var(--space-5);
}

.enc .empty-start svg {
  margin-bottom: var(--space-2);
}

.enc .empty-start :is(h2, h3) {
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-app-lg);
  font-weight: var(--fw-heading);
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.enc .empty-start > p {
  max-width: 46ch;
  text-wrap: balance;
}

.enc .empty-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.enc .empty-actions svg {
  margin: 0;
  opacity: 1;
}

.enc .empty-start :is(.empty-invite, .empty-note) {
  margin-top: var(--space-2);
  font-size: var(--fs-app-sm);
}

.enc .empty-start :is(.empty-invite, .empty-note) a {
  color: var(--text-primary);
  white-space: nowrap;
  text-decoration: underline;
  text-decoration-color: var(--text-muted);
  text-underline-offset: 3px;
}

.enc .empty-start :is(.empty-invite, .empty-note) a:hover {
  text-decoration-color: currentColor;
}

/* A phone: the two ways in as one column of equal buttons, like the page's own
   upload button above, not two widths stacked off-centre. */
@media (max-width: 640px) {
  .enc .empty-start {
    padding: var(--space-5) var(--space-4);
  }

  .enc .empty-actions {
    align-self: stretch;
  }

  .enc .empty-actions > * {
    flex: 1 1 100%;
  }
}

/* What didn't load, in the place it would have stood (enc/load-error.js): the
   empty state's frame, the failure's colour on its mark, the reason in plain
   words and the way on. */
.enc .load-error {
  gap: var(--space-2);
  padding: var(--space-6) var(--space-5);
}

.enc .load-error > svg {
  margin-bottom: var(--space-2);
  color: var(--status-failed-text);
  opacity: 1;
}

.enc .load-error h2 {
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-app-lg);
  font-weight: var(--fw-heading);
  line-height: 1.3;
}

.enc .load-error > p {
  max-width: 46ch;
  text-wrap: balance;
}

.enc .load-error > :is(button, .btn) {
  margin-top: var(--space-3);
}

/* The empty state fades its icons; the way on is a full-strength button. */
.enc .load-error > :is(button, .btn) svg {
  opacity: 1;
}

@media (max-width: 640px) {
  .enc .load-error {
    padding: var(--space-5) var(--space-4);
  }
}

/* ── 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;
  }
}

/* ── Admin users, API tokens ──
 * One field and its button. Bounded like the search field: at full width an
 * email input ran 1400px and read as a banner, not a control.
 */
/* Under the users table: which "admin" the page sets, and where the rest is. */
.enc .admin-note {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-app-md);
}

.enc :is(.admin-invite, .token-form) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.enc :is(.admin-invite, .token-form) input {
  flex: 1 1 280px;
  max-width: 420px;
}

/* A submit beside a field is part of the field's row: same height. */
.enc :is(.admin-invite, .token-form) button {
  height: var(--field-h);
}

/* Project access: a dropdown, not every project laid out in the row. The
   trigger wears the filter chip's shape, the panel the select list's (see "The
   dropdown list itself"); the checkboxes stay for the keyboard and the tests,
   and the trailing check says what is on. */
.enc .admin-projects {
  position: relative;
  width: 100%;
}

/* Fixed columns: the trigger relabels as boxes are ticked, and with automatic
   layout every tick re-sized all four columns. */
@media (min-width: 1000px) {
  .enc .admin-users {
    table-layout: fixed;
  }

  /* Wide enough for "Server admin" in capitals, Russian included. */
  .enc .admin-users th:nth-child(2) {
    width: 160px;
  }

  .enc .admin-users th:nth-child(3) {
    width: 320px;
  }

  .enc .admin-users th:nth-child(4) {
    width: 140px;
  }

  /* The trigger is a field's height, taller than a line: centre the row on it
     so the name, the box and Remove share its axis. */
  .enc .admin-users td {
    vertical-align: middle;
  }
}

.enc .admin-projects > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: var(--control-h);
  padding: 0 calc((var(--control-h) - 16px) / 2) 0 var(--space-3);
  border: var(--stroke) solid var(--border-control);
  border-radius: var(--radius-field);
  background: var(--surface-1);
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  white-space: nowrap;
  list-style: none;
  cursor: pointer;
}

.enc .admin-projects > summary > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.enc .admin-projects > summary::-webkit-details-marker {
  display: none;
}

.enc .admin-projects > summary:hover {
  border-color: var(--border-control-hover);
}

.enc .admin-projects > summary:focus-visible {
  outline: var(--focus-field-outline);
  outline-offset: 2px;
  border-color: var(--focus-field-edge);
  box-shadow: var(--focus-field-ring);
}

.enc .admin-projects > summary svg {
  flex: none;
  color: var(--text-muted-strong);
  transition: transform var(--dur-fast) var(--ease-out);
}

.enc .admin-projects[open] > summary svg {
  transform: rotate(180deg);
}

.enc .admin-projects-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 5;
  width: max-content;
  min-width: 100%;
  max-width: 400px;
  max-height: 320px;
  margin-top: var(--space-1);
  padding: var(--space-2);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: calc(var(--radius-field) + var(--space-2));
  background: var(--surface-2);
  box-shadow: var(--shadow-lift);
  overflow-y: auto;
}

.enc .admin-projects-menu .admin-team {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* The team is the group label, as an optgroup's. */
.enc .admin-projects-menu legend {
  padding: var(--space-3) var(--space-3) var(--space-1);
  color: var(--text-muted);
  font-size: var(--fs-app-xs);
  font-weight: var(--fw-label);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

.enc .admin-projects-menu label {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  margin: 0;
  padding: 0 var(--space-3);
  border-radius: var(--radius-field);
  color: var(--text-primary);
  font-size: var(--fs-app-base);
  line-height: 1.3;
  white-space: nowrap;
  cursor: pointer;
}

.enc .admin-projects-menu label:is(:hover, :has(input:focus-visible)) {
  background: var(--bg-menu-hover);
}

.enc .admin-projects-menu input[type='checkbox'] {
  position: absolute;
  width: 1px;
  height: 1px;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  appearance: none;
}

/* Lucide check, the select list's. */
.enc .admin-projects-menu label::after {
  content: '';
  flex: none;
  width: 16px;
  height: 16px;
  margin-left: auto;
  background-color: var(--red);
  mask: 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 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
  visibility: hidden;
}

.enc .admin-projects-menu label:has(input:checked)::after {
  visibility: visible;
}

/* Off while a change to the row saves: the box itself is hidden, so the row says it. */
.enc .admin-projects-menu label:has(input:disabled) {
  background: none;
  cursor: default;
  opacity: 0.5;
}

/* A command to copy, so it keeps its line breaks and scrolls rather than wraps. */
.enc .code-block {
  width: fit-content;
  max-width: 100%;
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-16);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-field);
  background: var(--surface-1);
  overflow-x: auto;
  font-size: var(--fs-app-sm);
  line-height: var(--lh-body);
}

.enc .code-block code {
  white-space: pre;
}

.enc .token-new {
  margin-bottom: var(--space-4);
}

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

.enc .token-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: var(--stroke) solid var(--border-hairline);
}

/* ── Cards ────────────────────────────────────────────────────────────────
   The list as a library: the same table drawn as tiles (list-host's
   data-view, the List | Cards switch in transcriptions.html). As on a phone,
   the DOM stays a table, so delete, the recount and the unit tests don't know
   which view is on. From 1000px: below that the rows are cards already, and
   the switch is hidden.

   A tile reads down: the project as its eyebrow, the title, the summary, and
   who, how long and when at the foot — pinned to the bottom, so a row of tiles
   lines up. The status only when it is news: "ready" on every tile is noise. */
@media (min-width: 1000px) {
  /* The height of the chips beside it: 30 + 4 + 4 + 1 + 1. Through .toolbar, to
     outrank the switches' 28 further down. */
  .enc .toolbar .view-switch {
    --seg: 30px;
  }

  .enc .toolbar .view-switch button {
    width: var(--seg);
    height: var(--seg);
  }

  .enc [data-view='cards'] .transcription-list {
    display: block;
    border: 0;
    border-radius: 0;
    background: none;
  }

  .enc [data-view='cards'] .transcription-list thead {
    display: none;
  }

  .enc [data-view='cards'] .transcription-list tbody {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-3);
  }

  /* 24 = the delete button's 12 + the 12 it sits in from the corner. */
  .enc [data-view='cards'] .transcription-list tbody tr {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, max-content) max-content max-content 1fr max-content;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      'project project project project project'
      'title title title title title'
      'uploader duration created . status';
    column-gap: var(--space-2);
    padding: var(--space-4);
    border: var(--stroke) solid var(--border-hairline);
    border-radius: 24px;
    background: var(--surface-1);
    transition: background var(--dur-fast) var(--ease-out);
  }

  .enc [data-view='cards'] .transcription-list tbody tr:hover {
    background: var(--bg-raised);
  }

  .enc [data-view='cards'] .transcription-list tbody td,
  .enc [data-view='cards'] .transcription-list tbody tr:hover td {
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
  }

  .enc [data-view='cards'] .transcription-list tbody td:nth-child(1) {
    grid-area: title;
  }

  /* Clear of the delete button in the corner. */
  .enc [data-view='cards'] .transcription-list tbody td:nth-child(2) {
    grid-area: project;
    margin-bottom: var(--space-2);
    padding-right: 40px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* No project: the dash keeps its line, so titles start level across a row. */
  .enc [data-view='cards'] .transcription-list tbody td:nth-child(2) > .muted {
    visibility: hidden;
  }

  /* No project column at all: the title is the top line, and clears the button. */
  .enc [data-view='cards'] .transcription-list.no-project tbody td:nth-child(1) {
    padding-right: 40px;
  }

  .enc [data-view='cards'] .transcription-list tbody td:nth-child(3) { grid-area: uploader; }
  .enc [data-view='cards'] .transcription-list tbody td:nth-child(4) { grid-area: duration; }
  .enc [data-view='cards'] .transcription-list tbody td:nth-child(5) { grid-area: created; }
  .enc [data-view='cards'] .transcription-list tbody td:nth-child(6) { grid-area: status; }

  .enc [data-view='cards'] .transcription-list tbody td:nth-child(n + 3):nth-child(-n + 6) {
    align-self: center;
    margin-top: var(--space-4);
    overflow: hidden;
    color: var(--text-muted-strong);
    font-size: var(--fs-app-sm);
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .enc [data-view='cards'] .transcription-list tbody td:nth-child(4)::before,
  .enc [data-view='cards'] .transcription-list tbody td:nth-child(5)::before {
    content: '·';
    margin-right: var(--space-2);
    color: var(--text-muted);
  }

  .enc [data-view='cards'] .transcription-list .status-ready {
    display: none;
  }

  .enc [data-view='cards'] .transcription-list tbody td.row-actions {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    z-index: 1;
  }

  /* The whole tile opens the meeting: the title's link stretched over it. The
     project link and the delete button stay above it. */
  .enc [data-view='cards'] .transcription-list a.row-title {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    font-size: var(--fs-app-md);
  }

  .enc [data-view='cards'] .transcription-list a.row-title::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
  }

  .enc [data-view='cards'] .transcription-list tbody td:nth-child(2) a {
    position: relative;
    z-index: 1;
  }

  /* Three lines and no «more»: the tile opens the whole meeting. */
  .enc [data-view='cards'] .row-snippet {
    margin-top: var(--space-2);
    max-width: none;
    -webkit-line-clamp: 3;
  }

  .enc [data-view='cards'] .row-snippet-toggle {
    display: none;
  }
}

/* Below 1000px the list is cards anyway. */
@media (max-width: 999px) {
  .enc .view-switch {
    display: none;
  }
}

/* ── 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.

   Each cell names itself in the reader's language (data-label, from the same
   list as the header row in transcription-list.js); this block shows the name. */
@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 {
    content: attr(data-label);
    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.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%);
  }
}

/* ── 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;
  }

  /* «Add meeting» is filled, so its 40px square shows: it keeps the bar's
     size, and its hit area grows to 44 round it, as the clamp toggle's does. */
  .enc .top-nav > .add-meeting .add-meeting-btn {
    position: relative;
    overflow: visible;
  }

  .enc .top-nav > .add-meeting .add-meeting-btn::after {
    content: '';
    position: absolute;
    inset: -2px;
  }

  /* 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;
  }

  /* The chips' row is the column, as the search above it: sized to its words
     it stopped short of the edge. */
  .enc .toolbar .filter-chips {
    flex: 1 1 100%;
  }

  .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);
}

/* On a phone the ways to send split the row, as a dialog's buttons do, and the
   one that will not fit takes a row of its own; «Replace link» spans the column
   too, still apart from them by the line. */
@media (max-width: 640px) {
  .enc .share-actions > button {
    flex: 1 1 0;
    min-width: max-content;
  }

  .enc .share-rotate {
    align-items: stretch;
  }
}

/* ── Download dialog ──────────────────────────────────────────────────────
   Share's card, labels and lit rows, one line to a row: a format is a name and
   what it is for, short enough to sit side by side, and five two-line rows made
   a dialog taller than most of what it opens over. */
.enc .export-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* A legend sits outside its fieldset's flex box, so the room under it is its
   own margin, not the gap. */
.enc .export-field > legend {
  margin-bottom: var(--space-2);
  padding: 0;
}

/* The sections are single words: two to a line, read across in the document's
   order. */
.enc .export-sections {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.enc .export-choice,
.enc .export-check {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-field);
  font-size: var(--fs-app-base);
  cursor: pointer;
}

.enc .export-choice:hover,
.enc .export-check:hover {
  background: var(--bg-raised);
}

/* As in Share: the format in force stays lit while the pointer is elsewhere. */
.enc .export-choice:has(:checked) {
  background: var(--bg-raised);
  outline: var(--stroke) solid var(--border-hairline);
}

.enc .export-check:has(:disabled) {
  background: none;
  cursor: default;
  opacity: 0.5;
}

.enc :is(.export-choice, .export-check) input {
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--red);
  cursor: inherit;
}

/* The names in a column of their own, so every hint starts on one line. */
.enc .export-choice-name {
  flex: 0 0 6.5em;
}

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

/* The file's name beside the button that makes it, so what lands in Downloads
   is no surprise. A long name wraps rather than losing its end: the
   extension and the recording's size are what a phone on data reads first. */
.enc .export-go {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.enc .export-filename {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm);
  overflow-wrap: anywhere;
}

.enc .export-go button {
  flex: none;
}

/* The recording is another thing from the notes above it: below a line, as
   Replace link is in Share. */
.enc .export-media {
  padding-top: var(--space-4);
  border-top: var(--stroke) solid var(--border-hairline);
}

/* ── Download: the PDF (export-print.js) ──
 * The notes as the Word file has them — its sizes, its grey, its indents — in
 * the page's one font, printed alone in black on white whatever the theme. It
 * is in the page only while it prints, and never on screen. Its own named page,
 * so the report's print keeps its margins. */
.enc .export-print {
  display: none;
}

@page export {
  margin: 20mm;
}

@media print {
  html[data-print='export'],
  html[data-print='export'] body {
    background: #fff;
  }

  html[data-print='export'] body > :not(.export-print) {
    display: none !important;
  }

  html[data-print='export'] .export-print {
    display: block;
    page: export;
    color: #000;
    font-family: var(--font-body);
    font-size: 11pt;
    line-height: 1.4;
  }

  .enc .export-print :is(h1, h2, h3, h4, h5, h6) {
    font-family: inherit;
    font-weight: var(--fw-bold);
    line-height: 1.2;
    break-after: avoid;
  }

  .enc .export-print h1 {
    margin: 0 0 3pt;
    font-size: 22pt;
    letter-spacing: var(--tracking-heading);
  }

  .enc .export-print h2 {
    margin: 18pt 0 6pt;
    font-size: 16pt;
    letter-spacing: normal;
  }

  .enc .export-print h3 {
    margin: 12pt 0 4pt;
    font-size: 13pt;
  }

  .enc .export-print :is(h4, h5, h6) {
    margin: 10pt 0 3pt;
    font-size: 11pt;
  }

  .enc .export-print :is(p, ul, ol, blockquote) {
    margin: 0 0 6pt;
  }

  /* Word's lists: the text half an inch in, each level half an inch deeper. */
  .enc .export-print :is(ul, ol) {
    padding-left: 0.5in;
  }

  .enc .export-print li {
    margin-bottom: 4pt;
    break-inside: avoid;
  }

  .enc .export-print li > :is(ul, ol) {
    margin: 4pt 0 0;
  }

  /* A box to tick on paper — drawn, so every system prints the same one. */
  .enc .export-print .export-print-task {
    list-style: none;
  }

  .enc .export-print .export-print-task > li {
    position: relative;
  }

  .enc .export-print .export-print-task > li::before {
    content: '';
    position: absolute;
    top: 0.3em;
    left: -0.25in;
    width: 0.75em;
    height: 0.75em;
    border: 1px solid currentColor;
    border-radius: 2px;
  }

  .enc .export-print .export-print-meta,
  .enc .export-print .export-print-quiet {
    color: #6b7280;
  }

  .enc .export-print .export-print-meta {
    margin-bottom: 6pt;
  }

  .enc .export-print .is-plain {
    white-space: pre-line;
  }

  .enc .export-print a {
    color: inherit;
    text-decoration: underline;
  }

  .enc .export-print blockquote {
    padding-left: 12pt;
    border-left: 2px solid #d1d5db;
  }
}

/* Where a sheet's margin can be carried to every page it runs over (Chromium
   130+, Firefox), the margin above and below is the sheet's and the page has
   none — so no room for the browser's own header and footer, which print the
   page's address: on a document made to be passed on, the link to the
   recording. */
@supports (box-decoration-break: clone) {
  @page export {
    margin-top: 0;
    margin-bottom: 0;
  }

  @media print {
    html[data-print='export'] .export-print {
      padding-block: 20mm;
      box-decoration-break: clone;
    }
  }
}

/* ── Motion ──
 * Light, and only where something changes under the reader's hand: a state
 * fades instead of snapping, a popup settles into place, a press gives a little.
 * The DS's two durations — --dur-fast for a control, --dur-base for a panel —
 * on its one ease-out curve; nothing bounces, nothing loops. */

/* Rows that light up under the pointer fade in like the buttons already do. */
.enc .top-nav a,
.enc .admin-projects-menu label,
.enc .ask-suggestion,
.enc :is(.qa-item, .task-item) summary,
.enc select option {
  transition: background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

/* A press gives — the plate, not the row: menu rows, tabs and segments stay
   put, and the record button has its own press. `scale`, not `transform`, so
   it adds to a control's own transform instead of replacing it. */
.enc :is(button, .btn):active:not(:disabled, [role='tab'], [role='radio'], .dictation-lang-option, .ask-suggestion, .link-button, .record-btn, [role^='menuitem']) {
  scale: 0.97;
}

/* A popup settles out of the control it hangs from: in from 4px nearer to it,
   fading up. Entry only — a menu being dismissed should be out of the way the
   moment it is, which is what dismissing it asked for. */
@keyframes enc-drop-in {
  from { opacity: 0; translate: 0 -4px; }
}

.enc .dictation-lang-menu,
.enc .admin-projects[open] .admin-projects-menu,
.enc .project-ask-typeahead {
  animation: enc-drop-in var(--dur-fast) var(--ease-out);
}

@supports (appearance: base-select) {
  .enc select:open::picker(select) {
    animation: enc-drop-in var(--dur-fast) var(--ease-out);
  }
}

/* The share dialog comes forward instead of appearing: up from 98% while the
   page dims behind it on the same beat. Entry only, like the popups — and the
   PDF button closes it straight into window.print(), which must not catch it
   halfway out. */
@keyframes enc-come-forward {
  from { opacity: 0; scale: 0.98; }
}

@keyframes enc-fade-in {
  from { opacity: 0; }
}

.enc .enc-dialog[open] {
  animation: enc-come-forward var(--dur-base) var(--ease-out);
}

.enc .enc-dialog[open]::backdrop {
  animation: enc-fade-in var(--dur-base) var(--ease-out);
}

/* An answer or a task unrolls rather than landing: its height eases from the
   summary line to its own. Both ways — a closing one has no print to dodge, and
   content-visibility waits out the fold so the rows don't vanish first. The clip
   margin keeps a focused field's ring inside; Chromium 131+ only, elsewhere the
   details open as before. */
@supports (interpolate-size: allow-keywords) {
  .enc :is(.qa-item, .task-item) {
    interpolate-size: allow-keywords;
  }

  .enc :is(.qa-item, .task-item)::details-content {
    block-size: 0;
    overflow: clip;
    overflow-clip-margin: 6px;
    transition: block-size var(--dur-base) var(--ease-out),
      content-visibility var(--dur-base) allow-discrete;
  }

  .enc :is(.qa-item, .task-item)[open]::details-content {
    block-size: auto;
  }
}

/* New content rises into its place: 4px under it, fading up. */
@keyframes enc-rise-in {
  from { opacity: 0; translate: 0 4px; }
}

/* A tab the reader picked (tabs.js marks it) — not the one the page opened on. */
.enc .tab-panel[data-switched] {
  animation: enc-rise-in var(--dur-base) var(--ease-out);
}

/* The theme and language switches: the "on" plate is one plate that slides to
   the choice, not a fill that jumps between squares. It sits under the buttons
   (isolate keeps it above the glass), one segment — a button and the 1px gap —
   per step. Only once the shell has settled (data-nav-ready, sidebar.js): a
   page opens with it already in place. */
.enc :is(.theme-switch, .lang-switch, .view-switch) {
  --seg: 28px;
  position: relative;
  isolation: isolate;
}

.enc .top-nav[data-nav-open] .nav-panel :is(.theme-switch, .lang-switch) {
  --seg: 44px;
}

.enc :is(.theme-switch, .lang-switch, .view-switch)::before {
  content: '';
  position: absolute;
  top: var(--space-1);
  left: var(--space-1);
  z-index: -1;
  width: var(--seg);
  height: var(--seg);
  border-radius: var(--radius-control);
  background: var(--bg-active);
  translate: calc(var(--at, 0) * (var(--seg) + 1px)) 0;
}

.enc :is(.theme-switch, .lang-switch, .view-switch):has(> button:nth-child(2)[aria-checked='true']) { --at: 1; }
.enc :is(.theme-switch, .lang-switch, .view-switch):has(> button:nth-child(3)[aria-checked='true']) { --at: 2; }

.enc :is(.theme-switch, .lang-switch, .view-switch):not(:has(> [aria-checked='true']))::before {
  opacity: 0;
}

body.enc[data-nav-ready] :is(.theme-switch, .lang-switch, .view-switch)::before {
  transition: translate var(--dur-base) var(--ease-out);
}

.enc :is(.theme-switch, .lang-switch, .view-switch) button[aria-checked='true'] {
  background: transparent;
}

/* A theme change crossfades the whole page (theme.js), on the panel beat. The
   switch itself sits out: its old picture would ghost under the sliding plate,
   so only the live one is drawn. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--dur-base);
  animation-timing-function: var(--ease-out);
}

.enc .theme-switch {
  view-transition-name: theme-switch;
}

::view-transition-old(theme-switch) {
  display: none;
}

::view-transition-new(theme-switch) {
  animation: none;
}

/* Between sections. Every section is its own page, so a nav click used to be a
   hard cut. Now the page crossfades on the theme's beat (the root rule above)
   while the chrome holds still: the sidebar, the bar and an open assistant are
   named, so each is drawn on its own and matches itself across the two pages
   instead of fading with them. Only push/back/forward navigations (a reload,
   the language switch, stays a cut), and reduced motion keeps every navigation
   a cut.

   The page itself gets the browser's plain crossfade on purpose. Old and new on
   one clock add up to full strength, so what both pages share — the title's
   line, the toolbar — stays put and solid; a rise made the title jolt down and
   back, and a quicker fade-out dimmed it mid-way. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: enc-page;
  }
}

.enc .top-nav { view-transition-name: enc-nav; }
.enc .top-bar { view-transition-name: enc-bar; }
.enc #enc-assistant { view-transition-name: enc-assistant; }

/* The chrome's own change — the active link moving — is a quick crossfade. */
html:active-view-transition-type(enc-page)::view-transition-group(*) {
  animation-duration: var(--dur-fast);
}

/* A message just sent, and the answer to it (assistant.js marks both). */
.enc .assistant-msg[data-new] {
  animation: enc-rise-in var(--dur-base) var(--ease-out);
}

/* ── Skeleton ──────────────────────────────────────────────────────────────
   What a page draws while its data is on the way: grey bars in the shape of the
   rows and cards to come, inside the same table or card the data will fill, so
   nothing around them moves when it lands (enc/skeleton.js builds them).

   One glint crosses them all. The gradient is fixed to the viewport, so every
   bar shows its own slice of the same band and the page shimmers as one sweep,
   left to right, rather than each bar on its own clock. The band is a third of
   a 300% layer: it crosses the screen in half the cycle and rests off it for
   the other half. Under reduced motion the one remaining iteration ends where
   it starts, off screen — plain still bars. */
.enc {
  --skel-base: var(--bg-raised);
  --skel-glint: rgb(255 255 255 / 0.06);
  --skel-fill: var(--skel-base) linear-gradient(100deg, transparent 40%, var(--skel-glint) 50%, transparent 60%) 100% 0 / 300% 100% fixed;
}

/* On cream the raised tone is a shade off the page itself: a bar straight on
   the background all but vanished. A tint of the ink reads the same on the page
   and on a white card. */
[data-theme='light'] .enc {
  --skel-base: color-mix(in oklab, var(--text-primary) 8%, transparent);
  --skel-glint: rgb(255 255 255 / 0.8);
}

@keyframes enc-shimmer {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}

.enc .skel {
  display: inline-block;
  vertical-align: middle;
  max-width: 100%;
  height: 0.75em;
  border-radius: var(--radius-full);
  background: var(--skel-fill);
  animation: enc-shimmer 2s linear infinite;
}

/* Lines of running text, one bar each, on the text's own line height. */
.enc .skel-lines {
  display: flex;
  flex-direction: column;
}

.enc .skel-lines > .skel {
  margin-block: calc((1lh - 0.75em) / 2);
}

/* A status pill or a chip, at the pill's height. */
.enc .skel.skel-pill {
  height: 1.5em;
}

/* A field or a chip, at the control height. */
.enc .skel.skel-control {
  height: var(--control-h);
  border-radius: var(--radius-field);
}

/* A form field, at the field height, on its own line as the field is. */
.enc .skel.skel-field {
  display: block;
  height: var(--field-h);
  border-radius: var(--radius-field);
}

/* The activity row's glyph, still a square. */
.enc .activity-list li[data-kind='skel']::before {
  border-radius: 4px;
  background: var(--skel-fill);
  animation: enc-shimmer 2s linear infinite;
}

/* A checkbox. */
.enc .skel.skel-box {
  width: 16px;
  height: 16px;
  border-radius: 4px;
}

/* The recording page's stand-in (share.html). The page it stands for waits
   hidden behind it, so no half-filled card shows on the way; and when the
   recording couldn't be opened, behind the box that says so. */
.enc :is(.page-skeleton, .page-failed) ~ * {
  display: none !important;
}

/* The pill row under the title, at #meta's spacing and the pills' height. */
.enc .skel-meta {
  display: flex;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
}

.enc .skel-meta > .skel {
  height: 24px;
}

/* The player bar (enc/player.js): a 56px capsule. */
.enc .skel.skel-player {
  display: block;
  height: 56px;
  border-radius: var(--radius-full);
}

/* 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;
  }

  /* The motion section's pseudo-elements, which `*` doesn't reach. One rule
     each: a browser that doesn't know one of them drops only that rule. */
  .enc *::backdrop {
    animation-duration: 0.01ms !important;
  }

  .enc *::details-content {
    transition-duration: 0.01ms !important;
  }

  .enc select::picker(select) {
    animation-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. At rest only:
   at the same weight as the hover, this later rule took the fill off it too,
   and the white label sat on pale pink. */
[data-theme="light"] .enc button.ts:not(:hover, :active) {
  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('/brand/logo-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('/brand/logo-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='8'/><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('/brand/logo-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: var(--print-accent);
  }

  /* 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: var(--print-accent);
    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;
}

/* As with the Download dialog's PDF: where the sheet can carry its margin to
   every page it runs over, the page has none above and below — no room for the
   browser's header and footer, which print the page's address: the link to the
   recording, on the document sent to the client. */
@supports (box-decoration-break: clone) {
  @page report {
    margin: 0 16mm;
  }

  @media print {
    .enc.has-report {
      page: report;
    }

    .enc.has-report .report-doc {
      padding-block: 18mm;
      box-decoration-break: clone;
    }
  }
}

/* Projects (projects.html): one card, a row a project, the whole row a link.
   Rows sit inset in the card, so the hover plate's corners follow the card's:
   inner radius = outer radius - inset. */
.enc .projects-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}

.enc .projects-head h1 {
  margin: 0;
  font-size: var(--fs-app-lg);
}

/* «Add meeting» and its menu: a flex box, so no line box adds height under
   the button. */
.enc .project-add {
  display: flex;
}

.enc .project-list {
  margin: 0;
  padding: var(--space-1);
  list-style: none;
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
  box-shadow: var(--shadow-tile);
}

.enc .project-list > li + li {
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .project-row {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  min-height: 64px;
  padding: var(--space-3);
  border-radius: calc(var(--radius-surface) - var(--space-1));
  color: inherit;
  text-decoration: none;
}

@media (hover: hover) {
  .enc a.project-row:hover {
    background: color-mix(in srgb, currentcolor 5%, transparent);
  }
}

.enc .project-row-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: color-mix(in srgb, currentcolor 7%, transparent);
  color: var(--text-muted-strong);
}

.enc .project-row-text {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}

.enc .project-row-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.enc .project-row-meta {
  color: var(--text-muted-strong);
  font-size: var(--fs-app-sm, 0.875rem);
}

.enc .project-row > svg {
  color: var(--text-muted-strong);
}

.enc .projects-archive {
  margin-top: var(--space-6);
}

.enc .projects-archive > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: fit-content;
  margin-bottom: var(--space-3);
  color: var(--text-muted-strong);
  list-style: none;
  cursor: pointer;
}

.enc .projects-archive > summary:hover {
  color: var(--text-primary);
}

/* Lucide's chevron in place of the browser's triangle; it turns down as the fold opens. */
.enc .projects-archive > summary::-webkit-details-marker {
  display: none;
}

.enc .projects-archive > summary svg {
  flex: none;
  transition: transform var(--dur-fast) var(--ease-out);
}

.enc .projects-archive[open] > summary svg {
  transform: rotate(90deg);
}

/* A project that is not there, or did not load: the words held to a reading
   width, the way to try again under them. An empty list is the dashed card
   (.empty-start), as on the list of all meetings. */
.enc .projects-empty {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  margin: var(--space-6) auto 0;
  text-align: center;
}

.enc .projects-empty h2,
.enc .projects-empty p {
  max-width: 44ch;
  margin: 0;
  text-wrap: balance;
}

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

/* A phone: "Try again" the column wide, as every act there. The grid centres
   its items, so it stretches here, not by align-self. */
@media (max-width: 640px) {
  .enc .projects-empty > button {
    justify-self: stretch;
  }
}

/* Back to the list, above the project's name. */
.enc .project-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
  color: var(--text-muted-strong);
  text-decoration: none;
}

@media (hover: hover) {
  .enc .project-back:hover {
    color: inherit;
  }
}

.enc .project-page .settings-tabs {
  margin-top: 0;
  margin-bottom: var(--space-4);
}

/* People tab: a person a row, remove is a 40px circle. The row is the list
   item itself, not a link with a hover: square, or the line between two
   rows bends at its ends. */
.enc .project-person {
  border-radius: 0;
}

.enc :is(.project-remove, .email-remove) {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
}

.enc .project-add-person {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* Add stands as tall as the picker beside it: the row has one edge. */
.enc .project-add-person button {
  height: var(--field-h);
}

/* Nobody picked yet: the first line asks, so it reads as a field's
   placeholder does, not as someone already chosen. */
.enc .project-add-person select:has(option[value=""]:checked) {
  color: var(--text-muted);
}

/* Under the choice, the list or what stands for it: as far below the last
   choice as the choices are from each other. */
.enc .project-page .team-section > .project-access + * {
  margin-top: var(--space-2);
}

/* The list didn't load: what went wrong, with the way to try again beside it. */
.enc .project-load-error {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.enc .project-load-error > p {
  margin: 0;
}

/* New project: who sees it, two choices with a line under each. */
.enc .project-access {
  margin: 0;
  padding: 0;
  border: 0;
}

.enc .project-choice {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2);
  align-items: start;
  padding: var(--space-2) 0;
  cursor: pointer;
}

.enc .project-choice input {
  margin: 3px 0 0;
  accent-color: var(--red);
}

.enc .project-access legend {
  padding: 0;
}

.enc .project-choice-text {
  display: grid;
  gap: 2px;
}

/* Settings tab: the team page's sections; a select is as wide as the name
   field above it, not as the page. */
.enc .project-page .tab-panel > .team-section:first-child {
  margin-top: var(--space-2);
}

.enc .project-page select.project-setting {
  max-width: 32rem;
}

/* Tasks: the tracker's project with Refresh list at its end, as wide as the
   name field above, and how fresh the list is under them. A phone puts the
   button under the list, so a long project name is read whole. */
.enc .project-tracker {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 32rem;
}

.enc .project-tracker select {
  flex: 1 1 auto;
  min-width: 0;
}

.enc .project-tracker button {
  height: var(--field-h);
}

@media (max-width: 640px) {
  .enc .project-tracker .share-link-row {
    flex-wrap: wrap;
  }

  .enc .project-tracker .share-link-row > * {
    flex: 1 1 100%;
  }
}

/* ── Notes by email: the team's choice (team.html) and a meeting's Email tab ── */

.enc .notify-chosen {
  margin: var(--space-1) 0 0;
  font-weight: 600;
}

.enc .email-choice {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.enc .email-choice select {
  flex: 0 1 auto;
  min-width: 0;
}

/* The select gives way, not the label: "Кому отправлять" stays on one line. */
.enc .email-choice-label {
  flex: none;
}

/* Where the letter stands, the panel's Send at the end of the same line. */
.enc .email-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-4);
}

.enc .email-state {
  flex: 1 1 280px;
  margin: 0;
  color: var(--text-muted-strong);
}

/* Focused only from code, after a Send that leaves nothing to press. */
.enc .email-state:focus {
  outline: none;
}

.enc .email-send-all {
  margin-left: auto;
}

/* Under the Send, why it is off for now. */
.enc .email-wait {
  margin: var(--space-2) 0 0;
  text-align: right;
}

.enc .email-why {
  margin: var(--space-3) 0 0;
  color: var(--status-failed-text);
}

/* The people: the projects' list, a status and the row's buttons at its end. */
.enc .email-people {
  margin: var(--space-4) 0 0;
  padding: var(--space-1);
  list-style: none;
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-surface);
  background: var(--bg-card);
}

.enc .email-people > li + li {
  border-top: var(--stroke) solid var(--border-hairline);
}

.enc .email-person {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-3);
  min-height: 64px;
  padding: var(--space-3);
}

.enc .email-person-who {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}

.enc .email-person-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.enc :is(.email-person-meta, .email-person-status) {
  font-size: var(--fs-app-sm);
}

.enc .email-person-meta {
  color: var(--text-muted-strong);
  overflow-wrap: anywhere;
}

.enc .email-person-status[data-tone='done'] { color: var(--status-done); }
.enc .email-person-status[data-tone='error'] { color: var(--status-failed-text); }
.enc .email-person-status[data-tone='muted'] { color: var(--text-muted); }

.enc .email-person-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
}

.enc .email-person-actions .link-button {
  line-height: 24px;
}

/* Typed in by hand, under the list: the address, a name, Add on their line. */
.enc .email-add {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

/* Grid beats the bare `[hidden]`: a panel that only reads has no form. */
.enc .email-add[hidden] {
  display: none;
}

.enc .email-add-title {
  font-size: var(--fs-app-md);
}

.enc .email-add-fields {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr) auto;
  align-items: end;
  gap: var(--space-3);
}

/* On a phone the status goes under the name, the buttons along the row's foot;
   the panel's Send and the form's fields take the whole width. */
@media (max-width: 640px) {
  .enc .email-choice {
    flex-direction: column;
    align-items: stretch;
  }

  .enc .email-send-all {
    flex: 1 1 100%;
  }

  .enc .email-wait {
    text-align: left;
  }

  .enc .email-add-fields {
    grid-template-columns: minmax(0, 1fr);
  }

  .enc .email-person {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2);
  }

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

  .enc .email-person-actions .email-remove {
    margin-left: auto;
  }
}

/* The letter before it goes: wider than a question, the letter as mail shows it.
   Both classes, so a brand's .enc-dialog width, loaded later, does not win. */
.enc .enc-dialog.email-preview-dialog {
  width: min(720px, calc(100vw - var(--gutter) * 2));
}

.enc .email-preview-body {
  display: grid;
  gap: var(--space-3);
}

.enc .email-preview-meta {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  margin: 0;
  font-size: var(--fs-app-sm);
}

.enc .email-preview-meta dt {
  color: var(--text-muted-strong);
}

.enc .email-preview-meta dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.enc .email-preview-note {
  margin: 0;
}

.enc .email-preview-frame {
  width: 100%;
  height: min(60vh, 560px);
  border: var(--stroke) solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: #fff;
}
