/* =====================================================================
   office-workspace.css — chrome for the Office editors.

   Replaces office-ribbon.css, which styled a nine-tab copy of the Word /
   Excel / PowerPoint ribbon (and set `font-family: Calibri` on the whole
   shell). Nothing here imitates a desktop suite: one toolbar, a command
   palette, a side rail.

   Owns no palette. Every colour, radius and shadow comes from the app
   design system via the --o-* alias layer declared in office2.css, so the
   editors follow the active light / dark theme like every other route.
   ===================================================================== */

/* ── Shell ──────────────────────────────────────────────────────────── */
/* UBICONS glyphs in this route are outlines. They carry class="ofc-glyph"
   instead of the shared "icon" class precisely so app.css's
   `.icon { fill: var(--text) }` cannot fill them in — see ico() in
   office-commands.js. Shapes that want a fill set it per-element. */
.ofc-glyph {
  fill: none;
  flex: 0 0 auto;
}

/* Column flex rather than fixed grid rows: the context bar is only present for
   some editors, and with positional `grid-template-rows` its absence shifted
   every later row up a track — the body took the `auto` track and the status bar
   took the `1fr`, so a presentation's shell stopped short of the viewport. */
.ofc-shell {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  width: 100%;
  overflow: hidden;
  position: relative;
  background: var(--o-bg);
  color: var(--o-text);
}
.ofc-top,
.ofc-bar,
.ofc-ctx,
.ofc-status { flex: 0 0 auto; }
.ofc-shell * { box-sizing: border-box; }

/* ── Top bar ────────────────────────────────────────────────────────── */
.ofc-top {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  height: 52px;
  border-bottom: 1px solid var(--o-line);
  background: var(--o-card);
  background-blend-mode: var(--blend);
}

.ofc-icon-btn {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border: 1px solid transparent;
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-text);
  cursor: pointer;
  transition: background .14s ease, border-color .14s ease;
}
.ofc-icon-btn:hover { background: var(--o-soft); }
.ofc-icon-btn[aria-pressed="true"] {
  background: color-mix(in oklab, var(--ofc-accent) 14%, transparent);
  border-color: color-mix(in oklab, var(--ofc-accent) 34%, transparent);
  color: var(--ofc-accent);
}
.ofc-icon-btn svg { width: 18px; height: 18px; }

.ofc-type-badge {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  border-radius: 8px;
  color: var(--ofc-accent);
  background: color-mix(in oklab, var(--ofc-accent) 13%, transparent);
}
.ofc-type-badge svg { width: 17px; height: 17px; }

.ofc-title-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  flex: 1 1 auto;
}
.ofc-title {
  min-width: 0;
  max-width: 420px;
  flex: 0 1 auto;
  padding: 5px 8px;
  border: 1px solid transparent;
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-text);
  font: inherit;
  font-size: 14.5px;
  font-weight: 650;
  letter-spacing: -0.01em;
  text-overflow: ellipsis;
}
.ofc-title:hover { background: var(--o-soft); }
.ofc-title:focus {
  outline: none;
  background: var(--o-bg);
  border-color: color-mix(in oklab, var(--ofc-accent) 45%, transparent);
}

.ofc-saved {
  flex: 0 0 auto;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--o-muted);
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--o-soft);
  white-space: nowrap;
}
.ofc-saved[data-state="saving"] { color: var(--ofc-accent); }
.ofc-saved[data-state="error"] {
  color: #ef4444;
  background: color-mix(in oklab, #ef4444 12%, transparent);
}

.ofc-top-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  margin-left: auto;
}

.ofc-ghost-btn,
.ofc-donna-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 11px;
  border-radius: var(--o-radius-sm);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background .14s ease, border-color .14s ease;
}
.ofc-ghost-btn {
  border: 1px solid var(--o-line);
  background: transparent;
  color: var(--o-muted);
}
.ofc-ghost-btn:hover { background: var(--o-soft); color: var(--o-text); }
.ofc-ghost-btn kbd {
  font: inherit;
  font-size: 10.5px;
  padding: 1px 5px;
  border-radius: 5px;
  background: var(--o-soft);
  color: var(--o-muted);
}
.ofc-donna-btn {
  border: 1px solid color-mix(in oklab, var(--ofc-accent) 32%, transparent);
  background: color-mix(in oklab, var(--ofc-accent) 12%, transparent);
  color: var(--ofc-accent);
}
.ofc-donna-btn:hover { background: color-mix(in oklab, var(--ofc-accent) 20%, transparent); }
.ofc-ghost-btn svg,
.ofc-donna-btn svg { width: 15px; height: 15px; flex: 0 0 auto; }

/* ── Toolbar ────────────────────────────────────────────────────────── */
.ofc-bar {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 5px 12px;
  height: 44px;
  overflow: hidden;
  border-bottom: 1px solid var(--o-line);
  background: var(--o-surface);
}

.ofc-tb {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 32px;
  min-width: 32px;
  padding: 0 6px;
  flex: 0 0 auto;
  border: 1px solid transparent;
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-text);
  cursor: pointer;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.ofc-tb:hover:not(:disabled) { background: var(--o-soft); }
.ofc-tb:disabled { opacity: .38; cursor: default; }
.ofc-tb[aria-pressed="true"] {
  background: color-mix(in oklab, var(--ofc-accent) 15%, transparent);
  border-color: color-mix(in oklab, var(--ofc-accent) 32%, transparent);
  color: var(--ofc-accent);
}
.ofc-tb svg { width: 18px; height: 18px; flex: 0 0 auto; }
.ofc-caret { display: inline-flex; opacity: .5; }
.ofc-caret svg { width: 12px; height: 12px; }

.ofc-tb-select {
  padding: 0 8px;
  justify-content: space-between;
  border-color: var(--o-line);
  font-size: 12.5px;
  font-weight: 600;
}
.ofc-tb-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Group separators. `--o-line` against `--o-surface` is a hairline you cannot
   actually see at this height, so sixteen controls read as one undifferentiated
   strip. Taller, and mixed from the text colour so it survives both themes. */
.ofc-sep {
  width: 1px;
  height: 24px;
  margin: 0 8px;
  flex: 0 0 auto;
  background: color-mix(in oklab, var(--o-text) 16%, transparent);
}

.ofc-tb-more { margin-left: auto; }

/* ── Context bar (the sheet formula bar lives here) ─────────────────── */
.ofc-ctx {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 12px;
  height: 38px;
  border-bottom: 1px solid var(--o-line);
  background: var(--o-surface);
}

/* ── Body ───────────────────────────────────────────────────────────── */
.ofc-body {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
  overflow: hidden;
}
.ofc-main {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  overflow: hidden;
}

/* ── Status bar ─────────────────────────────────────────────────────── */
.ofc-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 14px;
  height: 30px;
  border-top: 1px solid var(--o-line);
  background: var(--o-surface);
  font-size: 11.5px;
  color: var(--o-muted);
}
.ofc-status-left { display: flex; align-items: center; gap: 14px; }
.ofc-status-left b { font-weight: 650; color: var(--o-text); }

.ofc-zoom { display: flex; align-items: center; gap: 2px; }
.ofc-zoom-btn,
.ofc-zoom-val {
  border: none;
  background: transparent;
  color: var(--o-muted);
  cursor: pointer;
  border-radius: 6px;
  font: inherit;
  font-size: 11.5px;
  display: grid;
  place-items: center;
}
.ofc-zoom-btn { width: 24px; height: 22px; }
.ofc-zoom-val { min-width: 44px; height: 22px; font-weight: 600; font-variant-numeric: tabular-nums; }
.ofc-zoom-btn:hover,
.ofc-zoom-val:hover { background: var(--o-soft); color: var(--o-text); }
.ofc-zoom-btn svg { width: 15px; height: 15px; }

/* ── Anchored panels (swatches, table size) ─────────────────────────── */
.ofc-panel {
  position: fixed;
  z-index: 9600;
  padding: 10px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--ui-surface-raised, var(--bg));
  box-shadow: var(--o-shadow), 0 12px 34px rgba(0, 0, 0, .18);
  animation: ofc-pop .12s ease;
}
@keyframes ofc-pop {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}

.ofc-swatch-grid {
  display: grid;
  grid-template-columns: repeat(6, 22px);
  gap: 6px;
}
.ofc-swatch {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  border: 1px solid color-mix(in oklab, var(--o-text) 16%, transparent);
  background: var(--sw);
  cursor: pointer;
  padding: 0;
  transition: transform .1s ease;
}
.ofc-swatch:hover { transform: scale(1.14); }
.ofc-swatch-none,
.ofc-swatch-custom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 5px 7px;
  border-radius: 7px;
  border: 1px solid var(--o-line);
  background: transparent;
  color: var(--o-text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.ofc-swatch-none { margin-bottom: 8px; }
.ofc-swatch-custom { margin-top: 8px; }
.ofc-swatch-custom input { width: 30px; height: 20px; padding: 0; border: none; background: none; }

.ofc-ts-grid {
  display: grid;
  grid-template-columns: repeat(10, 17px);
  gap: 3px;
}
.ofc-ts-cell {
  width: 17px;
  height: 17px;
  padding: 0;
  border-radius: 3px;
  border: 1px solid var(--o-line);
  background: transparent;
  cursor: pointer;
}
.ofc-ts-cell.on {
  background: color-mix(in oklab, var(--ofc-accent, var(--primary)) 45%, transparent);
  border-color: var(--ofc-accent, var(--primary));
}
.ofc-ts-label {
  margin-top: 8px;
  text-align: center;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--o-muted);
  font-variant-numeric: tabular-nums;
}

/* ── Command palette ────────────────────────────────────────────────── */
.ofc-palette-scrim {
  position: absolute;
  inset: 0;
  z-index: 9500;
  display: flex;
  justify-content: center;
  padding-top: 12vh;
  background: color-mix(in oklab, var(--o-text) 22%, transparent);
  backdrop-filter: blur(2px);
  animation: ofc-fade .12s ease;
}
@keyframes ofc-fade { from { opacity: 0; } to { opacity: 1; } }

.ofc-palette {
  width: min(560px, calc(100% - 40px));
  max-height: 62vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius);
  background: var(--ui-surface-raised, var(--bg));
  box-shadow: 0 24px 60px rgba(0, 0, 0, .28);
}

.ofc-palette-input {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--o-line);
  flex: 0 0 auto;
}
.ofc-palette-input svg { width: 18px; height: 18px; color: var(--o-muted); flex: 0 0 auto; }
.ofc-palette-input input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: none;
  color: var(--o-text);
  font: inherit;
  font-size: 14.5px;
}
.ofc-palette-input input:focus { outline: none; }
.ofc-palette-input kbd {
  font: inherit;
  font-size: 10.5px;
  padding: 2px 6px;
  border-radius: 5px;
  background: var(--o-soft);
  color: var(--o-muted);
}

.ofc-palette-list { overflow-y: auto; padding: 6px; }
.ofc-palette-group {
  padding: 8px 10px 4px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--o-muted);
}
.ofc-palette-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-text);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
}
.ofc-palette-item.is-cur { background: var(--o-soft); }
.ofc-palette-ico { display: grid; place-items: center; width: 20px; color: var(--o-muted); }
.ofc-palette-ico svg { width: 16px; height: 16px; }
.ofc-palette-label { flex: 1 1 auto; min-width: 0; }
.ofc-palette-item kbd {
  font: inherit;
  font-size: 10.5px;
  padding: 2px 6px;
  border-radius: 5px;
  background: var(--o-soft);
  color: var(--o-muted);
}
.ofc-palette-empty { padding: 26px 16px; text-align: center; color: var(--o-muted); font-size: 13px; }

/* ── Side rail ──────────────────────────────────────────────────────── */
.ofc-rail-host {
  flex: 0 0 336px;
  width: 336px;
  min-height: 0;
  border-left: 1px solid var(--o-line);
  background: var(--o-surface);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ofc-rail-host[hidden] { display: none; }
.ofc-rail { display: flex; flex-direction: column; min-height: 0; height: 100%; }

.ofc-rail-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 8px 8px 10px;
  border-bottom: 1px solid var(--o-line);
  flex: 0 0 auto;
}
.ofc-rail-tabs { display: flex; gap: 2px; flex: 1 1 auto; min-width: 0; }
.ofc-rail-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border: none;
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-muted);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.ofc-rail-tab:hover { background: var(--o-soft); color: var(--o-text); }
.ofc-rail-tab.is-on {
  background: color-mix(in oklab, var(--ofc-accent) 14%, transparent);
  color: var(--ofc-accent);
}
.ofc-rail-tab svg { width: 15px; height: 15px; flex: none; }
/* Only the active tab carries its label. Five icon-and-label tabs do not fit in
   a 336px rail — Comments and History pushed Access off the end entirely — and
   the alternatives all cost more: scrolling hides tabs behind a gesture, and
   wrapping steals a row from the panel on every document. The name is still on
   the button as aria-label and title, so nothing is lost to a screen reader or
   to a hover. */
.ofc-rail-tab > span { display: none; }
.ofc-rail-tab.is-on > span { display: inline; }
.ofc-rail-close {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--o-muted);
  cursor: pointer;
}
/* Dialled back at rest. This is the least important control in the rail and it
   was competing with the tabs for attention; it only needs to be findable once
   you have decided to look for it. */
.ofc-rail-close { opacity: .55; }
.ofc-rail-close:hover { background: var(--o-soft); color: var(--o-text); opacity: 1; }

.ofc-rail-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

.ofc-rail-section { padding: 14px 14px 4px; }
.ofc-rail-section-title {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--o-muted);
  margin-bottom: 8px;
}
.ofc-rail-note { font-size: 12.5px; color: var(--o-muted); margin: 0; }
.ofc-rail-loading { padding: 22px 14px; font-size: 12.5px; color: var(--o-muted); }

.ofc-rail-empty {
  padding: 30px 20px;
  text-align: center;
  color: var(--o-muted);
  font-size: 13px;
  line-height: 1.55;
}
.ofc-rail-empty-ico { color: var(--ofc-accent); opacity: .8; margin-bottom: 10px; }
.ofc-rail-empty p { margin: 0 0 14px; }

.ofc-kv { margin: 0; display: flex; flex-direction: column; gap: 7px; }
.ofc-kv > div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.ofc-kv dt { font-size: 12.5px; color: var(--o-muted); margin: 0; }
.ofc-kv dd {
  margin: 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--o-text);
  text-align: right;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Donna panel */
.ofc-donna { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.ofc-donna-thread { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.ofc-donna-chips { display: flex; flex-direction: column; gap: 7px; }
.ofc-chip {
  padding: 8px 11px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-text);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.ofc-chip:hover { background: var(--o-soft); border-color: color-mix(in oklab, var(--ofc-accent) 35%, transparent); }

.ofc-bubble { max-width: 100%; }
.ofc-bubble-text { font-size: 13px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
.ofc-bubble.is-user {
  align-self: flex-end;
  max-width: 88%;
  padding: 8px 11px;
  border-radius: 13px 13px 3px 13px;
  background: color-mix(in oklab, var(--ofc-accent) 14%, transparent);
}
.ofc-bubble.is-donna { padding: 2px 1px; }
.ofc-bubble.is-donna.is-streaming .ofc-bubble-text::after {
  content: '▍';
  animation: ofc-caret-blink 1s steps(2) infinite;
  color: var(--ofc-accent);
}
@keyframes ofc-caret-blink { 50% { opacity: 0; } }
.ofc-bubble.is-error .ofc-bubble-text { color: #ef4444; }

.ofc-bubble-actions { display: flex; gap: 6px; margin-top: 8px; }
.ofc-bubble-actions button {
  padding: 4px 9px;
  border: 1px solid var(--o-line);
  border-radius: 7px;
  background: transparent;
  color: var(--o-muted);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
}
.ofc-bubble-actions button:hover { background: var(--o-soft); color: var(--o-text); }

.ofc-donna-form {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 10px 12px 12px;
  border-top: 1px solid var(--o-line);
}
.ofc-donna-form textarea {
  flex: 1 1 auto;
  min-width: 0;
  resize: none;
  max-height: 140px;
  padding: 8px 10px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-bg);
  color: var(--o-text);
  font: inherit;
  font-size: 13px;
  line-height: 1.5;
}
.ofc-donna-form textarea:focus {
  outline: none;
  border-color: color-mix(in oklab, var(--ofc-accent) 50%, transparent);
}
.ofc-donna-send {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border: none;
  border-radius: var(--o-radius-sm);
  background: var(--ofc-accent);
  color: #fff;
  cursor: pointer;
}
.ofc-donna-send svg { width: 16px; height: 16px; }

/* Access panel */
.ofc-share-list { display: flex; flex-direction: column; gap: 6px; }
.ofc-share-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
}
.ofc-share-who { flex: 1 1 auto; min-width: 0; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ofc-share-perm {
  flex: 0 0 auto;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--o-muted);
}
.ofc-share-revoke {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--o-muted);
  cursor: pointer;
}
.ofc-share-revoke:hover { background: color-mix(in oklab, #ef4444 14%, transparent); color: #ef4444; }

.ofc-share-form { display: grid; grid-template-columns: 1fr auto; gap: 8px; }
.ofc-share-form input,
.ofc-share-form select {
  padding: 7px 9px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-bg);
  color: var(--o-text);
  font: inherit;
  font-size: 12.5px;
}
.ofc-share-form input { grid-column: 1 / -1; }
.ofc-share-form input:focus,
.ofc-share-form select:focus { outline: none; border-color: color-mix(in oklab, var(--ofc-accent) 50%, transparent); }
.ofc-share-form button {
  padding: 7px 14px;
  border: none;
  border-radius: var(--o-radius-sm);
  background: var(--ofc-accent);
  color: #fff;
  font: inherit;
  font-size: 12.5px;
  font-weight: 650;
  cursor: pointer;
}

/* =====================================================================
   Editor surfaces

   These were three near-identical <style> blocks that docs/engine.js,
   sheets/engine.js and slides/engine.js each appended to document.head on
   every mount — so moving between documents leaked a fresh copy each time.
   They are static rules; they belong in a stylesheet.
   ===================================================================== */

/* ── Document ───────────────────────────────────────────────────────── */
/* The canvas the page sits on.

   It used to be the page's own surface mixed 4% towards the TEXT colour, which
   left barely a step of value between paper and desk — the page read as a
   background region with a shadow on it rather than as an object. Worse, that
   direction is theme-dependent: mixing towards the text lightens the desk under
   a dark theme, so the desk crept UP towards the paper exactly where the paper
   had least contrast to spare.

   Mixing towards black instead moves the same way in both themes, which is what
   "the desk sits behind the paper" actually means. The accent wash from the top
   lights the page rather than merely outlining it. */
.ofc-doc {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  /* Column, because the authorship band stacks above the scroller. */
  flex-direction: column;
  background:
    radial-gradient(120% 70% at 50% -10%,
      color-mix(in oklab, var(--ofc-accent) 10%, transparent), transparent 62%),
    color-mix(in oklab, var(--o-bg) 82%, #000);
}
.ofc-doc-scroll {
  flex: 1 1 auto;
  overflow: auto;
  display: flex;
  justify-content: center;
  /* Generous bottom room so the page has a visible END. A page whose last edge
     is flush with the scroll floor never reads as a sheet of paper. */
  padding: 28px 24px 120px;
}
.ofc-doc-paper {
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: transform .15s ease;
}
.ofc-doc-ruler {
  display: flex;
  height: 18px;
  margin-bottom: 6px;
  padding: 0 2px;
  font-size: 8.5px;
  color: var(--o-muted);
  user-select: none;
}
.ofc-doc-ruler[hidden] { display: none; }
.ofc-tick {
  flex: 1 1 0;
  border-left: 1px solid color-mix(in oklab, var(--o-text) 18%, transparent);
  padding-left: 2px;
  line-height: 18px;
}
.ofc-tick.is-major { border-left-color: color-mix(in oklab, var(--o-text) 38%, transparent); }

/* The page.

   `position: relative` is load-bearing, not tidiness: the margin gutter is
   absolutely positioned against this box, and against the page rather than the
   body wrap because it lives in the MARGIN, which is the page's padding.

   The shadow is four layers on purpose — a hairline edge so the sheet has a
   boundary of its own at any zoom, a tight contact shadow, and two wide soft
   ones. One 34px blur, which is what this had, reads as a card in a list. */
.ofc-doc-page {
  position: relative;
  width: 21cm;
  min-height: 29.7cm;
  padding: var(--doc-margin, 2.54cm);
  display: flex;
  flex-direction: column;
  background: var(--o-surface);
  color: var(--o-text);
  border-radius: 2px;
  box-shadow:
    /* A hairline so the sheet keeps an edge of its own at any zoom, then a
       contact shadow and two soft ones. The single 34px blur this had read as
       a card in a list rather than as a sheet on a desk. */
    0 0 0 1px color-mix(in oklab, var(--o-text) 15%, transparent),
    0 1px 1px rgba(0, 0, 0, .08),
    0 10px 22px -8px rgba(0, 0, 0, .30),
    0 34px 64px -28px rgba(0, 0, 0, .45);
}
/* A lit top edge. Cheap, and it is most of what makes a rectangle read as a
   physical sheet rather than as a panel. */
.ofc-doc-page::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: color-mix(in oklab, #fff 22%, transparent);
  pointer-events: none;
}
.ofc-doc-running {
  font-size: 11px;
  color: var(--o-muted);
  outline: none;
  padding-bottom: 6px;
  margin-bottom: 22px;
  border-bottom: 1px dashed color-mix(in oklab, var(--o-line) 55%, transparent);
}
.ofc-doc-running.is-footer {
  margin: auto 0 0;
  padding: 6px 0 0;
  border-bottom: none;
  border-top: 1px dashed color-mix(in oklab, var(--o-line) 55%, transparent);
  text-align: center;
}
/* .ofc-model-body carries what is true of ANY body the document model
   projects into — the tables, quotes, code blocks, tracked runs and bound
   values it emits. Both the document page and the note paper wear it, because
   both are rendered by doc-model.js and a run that looks like an insertion on
   one surface and like ordinary text on the other is not a review tool. What
   stays on .ofc-doc-body below is only what belongs to the PAGE: its height in
   centimetres, and the stacking it needs against the overlays around it. */
.ofc-doc-body {
  flex: 1 1 auto;
  outline: none;
  min-height: 18cm;
  line-height: 1.65;
  /* Positioned so it can be ordered against the overlays around it. Without
     this the body is in-flow content, which paints BELOW any positioned
     sibling — so the authorship washes, which are meant to sit behind the
     glyphs like a highlighter, would cover them instead. */
  position: relative;
  z-index: 1;
}
.ofc-model-body > :first-child { margin-top: 0; }
.ofc-model-body table.doc-table {
  width: 100%;
  border-collapse: collapse;
  margin: 14px 0;
}
.ofc-model-body table.doc-table td,
.ofc-model-body table.doc-table th {
  border: 1px solid color-mix(in oklab, var(--o-text) 24%, transparent);
  padding: 7px 9px;
  min-width: 40px;
}
.ofc-model-body img { max-width: 100%; height: auto; }
.ofc-model-body blockquote {
  margin: 12px 0;
  padding-left: 14px;
  border-left: 3px solid color-mix(in oklab, var(--ofc-accent) 55%, transparent);
  color: var(--o-muted);
}
.ofc-model-body pre {
  padding: 10px 12px;
  border-radius: 8px;
  background: color-mix(in oklab, var(--o-text) 7%, transparent);
  overflow-x: auto;
}

/* ══════════════════════════════════════════════════════════════════════════
   The margin gutter — assets/js/office/docs/gutter.js

   Comments and pending edits live in the page's right margin, on the line they
   are about, instead of in a drawer that makes you hold two positions in your
   head. A mark is a dot; hover or focus blooms it into a card.

   The card grows from the gutter's left edge OUTWARD, past the page edge onto
   the canvas. That direction is deliberate: it never covers a word of the text
   you are reading it about, which is the failure of every popover anchored to
   the selection.
   ══════════════════════════════════════════════════════════════════════════ */

.ofc-doc-gutter {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  /* Set from the page's real right padding in applyPage(), because the margin
     is 1.27cm, 2.54cm or 5.08cm depending on page setup. */
  width: var(--doc-gutter, 2.54cm);
  /* Above the paper, below any dialog. Marks bloom outside this box. */
  z-index: 2;
}

.ofc-gm {
  position: absolute;
  right: 10px;
  height: 24px;
  cursor: pointer;
  outline: none;
  /* One hue per author, from a hash of the name — see authorHue() in gutter.js. */
  --au-solid: oklch(62% .16 var(--au));
  --au-wash: oklch(62% .16 var(--au) / .16);
}

/* A stale live field is not somebody's opinion, so it does not take an author's
   hue: "this no longer matches the record" means the same thing whichever
   record it is, and a hashed colour per record would make one document's
   warnings green and another's blue. */
.ofc-gm.is-binding {
  --au-solid: var(--ofc-del);
  --au-wash: color-mix(in oklab, var(--ofc-del) 16%, transparent);
}

/* The dot carries the KIND — comment, edit, suggestion, out-of-date field —
   because two letters of a name are not legible at this size and every mark
   ended up looking the same. Who it is comes from the colour and the card. */
.ofc-gm-dot {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--au-wash);
  box-shadow: inset 0 0 0 1.5px var(--au-solid);
  color: var(--au-solid);
  transition: transform .16s ease, box-shadow .16s ease;
}
.ofc-gm-dot svg { width: 13px; height: 13px; }

/* Initials, on the card rather than on the dot. */
.ofc-gm-ini {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--au-wash);
  color: var(--au-solid);
  font-size: 8.5px;
  font-weight: 750;
  letter-spacing: .02em;
}

.ofc-gm:hover .ofc-gm-dot,
.ofc-gm:focus-visible .ofc-gm-dot,
.ofc-gm.is-pinned .ofc-gm-dot {
  transform: scale(1.12);
  box-shadow: inset 0 0 0 2px var(--au-solid), 0 2px 8px var(--au-wash);
}

/* Deletions and resolved-ish items sit back so the things awaiting a decision
   are the ones that catch the eye. */
.ofc-gm.is-muted .ofc-gm-dot { opacity: .62; }
.ofc-gm.is-muted:hover .ofc-gm-dot { opacity: 1; }

/* The leader.

   A mark that had to be pushed down to avoid its neighbour is no longer level
   with its own text, so it draws a line back up to where it belongs. Height is
   the push distance, which the gutter measures — zero push, no line. */
.ofc-gm-leader {
  position: absolute;
  right: 29px;
  bottom: 12px;
  width: 1px;
  height: var(--drop, 0px);
  background: linear-gradient(to top, var(--au-solid), transparent);
  opacity: 0;
  transition: opacity .16s ease;
  pointer-events: none;
}
.ofc-gm:hover .ofc-gm-leader,
.ofc-gm:focus-visible .ofc-gm-leader,
.ofc-gm.is-pinned .ofc-gm-leader { opacity: .8; }

/* The card. */
.ofc-gm-card {
  position: absolute;
  top: -6px;
  /* Starts at the mark and extends away from the text, over the margin and out
     onto the canvas. */
  left: 0;
  width: 272px;
  padding: 10px 11px 9px;
  border-radius: var(--o-radius-sm);
  border: 1px solid var(--o-line);
  border-left: 3px solid var(--au-solid);
  background: var(--o-surface);
  color: var(--o-text);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .10), 0 16px 40px -12px rgba(0, 0, 0, .34);
  transform-origin: left center;

  /* `display: none` at rest rather than `visibility: hidden`, and this is not
     interchangeable: a hidden-but-laid-out card still extends the scroll
     container's width, so every closed card in the document would add a
     permanent horizontal scrollbar to the page. Discrete-property transitions
     plus @starting-style are what let it animate anyway. */
  display: none;
  opacity: 0;
  transform: translateX(-6px) scale(.97);
  transition:
    opacity .16s ease,
    transform .16s ease,
    display .16s allow-discrete;
}
.ofc-gm:hover .ofc-gm-card,
.ofc-gm:focus-within .ofc-gm-card,
.ofc-gm.is-pinned .ofc-gm-card {
  display: block;
  opacity: 1;
  transform: translateX(0) scale(1);
}
@starting-style {
  .ofc-gm:hover .ofc-gm-card,
  .ofc-gm:focus-within .ofc-gm-card,
  .ofc-gm.is-pinned .ofc-gm-card {
    opacity: 0;
    transform: translateX(-6px) scale(.97);
  }
}
/* A pinned card outranks a hovered one, or moving the pointer across the
   margin to reach a button flickers through every mark on the way. */
.ofc-gm.is-pinned { z-index: 3; }

/* The other direction — the spreadsheet's margin.
   A page's gutter sits inside the paper with the desk beyond it, so its cards
   open outward over the desk and never cover a word. A grid has no such
   outside: its gutter IS the last column of the viewport, and a card opening
   right would open into the window edge. Same rule, mirrored. */
.ofc-gutter.is-bloom-left .ofc-gm-card {
  left: auto;
  right: 0;
  transform-origin: right center;
  transform: translateX(6px) scale(.97);
}
.ofc-gutter.is-bloom-left .ofc-gm:hover .ofc-gm-card,
.ofc-gutter.is-bloom-left .ofc-gm:focus-within .ofc-gm-card,
.ofc-gutter.is-bloom-left .ofc-gm.is-pinned .ofc-gm-card {
  transform: translateX(0) scale(1);
}
@starting-style {
  .ofc-gutter.is-bloom-left .ofc-gm:hover .ofc-gm-card,
  .ofc-gutter.is-bloom-left .ofc-gm:focus-within .ofc-gm-card,
  .ofc-gutter.is-bloom-left .ofc-gm.is-pinned .ofc-gm-card {
    transform: translateX(6px) scale(.97);
  }
}
/* The accent edge moves to the side the card grows from, or it points at
   nothing. */
.ofc-gutter.is-bloom-left .ofc-gm-card {
  border-left: 1px solid var(--o-line);
  border-right: 3px solid var(--au-solid);
}

.ofc-gm-card header {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 6px;
}
.ofc-gm-who {
  font-size: 12px;
  font-weight: 700;
  color: var(--au-solid);
}
.ofc-gm-verb {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--o-muted);
}
.ofc-gm-quote {
  margin: 0 0 6px;
  padding-left: 8px;
  border-left: 2px solid color-mix(in oklab, var(--o-text) 14%, transparent);
  font-size: 12px;
  line-height: 1.45;
  color: var(--o-muted);
}
.ofc-gm-text {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  word-break: break-word;
}
.ofc-gm.is-change .ofc-gm-text,
.ofc-gm.is-suggestion .ofc-gm-text {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 11.5px;
  padding: 6px 8px;
  border-radius: 6px;
  background: var(--o-soft);
}
.ofc-gm.is-muted .ofc-gm-text { text-decoration: line-through; color: var(--o-muted); }

.ofc-gm-card footer {
  display: flex;
  gap: 6px;
  margin-top: 9px;
}
.ofc-gm-card footer button {
  flex: 1 1 0;
  padding: 5px 9px;
  border-radius: 7px;
  border: 1px solid var(--o-line);
  background: transparent;
  color: var(--o-muted);
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
}
.ofc-gm-card footer button:hover { background: var(--o-soft); color: var(--o-text); }
.ofc-gm-card footer button.is-primary {
  border-color: transparent;
  background: var(--au-solid);
  color: #fff;
}
.ofc-gm-card footer button.is-primary:hover { filter: brightness(1.07); }

/* ══════════════════════════════════════════════════════════════════════════
   Authorship — assets/js/office/docs/authorship.js, model/provenance.js

   Blame for prose. The op log the collaboration sequencer already keeps says
   who wrote every character; this draws it.
   ══════════════════════════════════════════════════════════════════════════ */

.ofc-prov-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--o-line);
  background: var(--o-surface);
  font-size: 12px;
}
.ofc-prov-bar[hidden] { display: none; }

.ofc-prov-legend {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}
.ofc-prov-note { color: var(--o-muted); }

.ofc-prov-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px 3px 7px;
  border-radius: 999px;
  background: var(--o-soft);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}
.ofc-prov-chip i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: oklch(62% .16 var(--au));
}
.ofc-prov-chip b { color: var(--o-muted); font-weight: 650; font-variant-numeric: tabular-nums; }
/* The share of the text nobody in the window wrote. Deliberately drawn as a
   ring rather than a fill: it is an absence of information, not a fifth
   collaborator with a colour of their own. */
.ofc-prov-chip.is-unknown i {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px color-mix(in oklab, var(--o-text) 32%, transparent);
}

.ofc-prov-time {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}
.ofc-prov-time-label {
  color: var(--o-muted);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  min-width: 92px;
  text-align: right;
}
/* The "since" control is a native <input type="range"> in the markup, and it is
   NOT the thing you see: Bits.Slider.upgradeAll swaps every range input in the
   app for a bits slider, hides the native one at 1×1 and forwards its events
   back onto it. That is the house control layer and this defers to it rather
   than opting out with data-native — the rail's <select data-native> is the
   exception, not the pattern.

   The one thing worth saying here is how wide it should be. Bits carries an
   explicit inline width across the upgrade, so the width goes on the wrap the
   upgrade produces; styling the input itself paints a hidden 1px element, which
   is exactly the mistake that left this band with an invisible control. */
.ofc-prov-time .bits-slider-wrap { width: 190px; flex: 0 0 auto; }

/* The washes.

   Behind the glyphs (first child of the body wrap) so this reads as a
   highlighter rather than a film laid over the text. A wash carries a stronger
   line along its bottom edge, because a flat tint at the alpha that keeps body
   text readable is too weak to separate two authors on adjacent lines. */
.ofc-prov-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}
.ofc-prov-wash {
  position: absolute;
  border-radius: 2px;
  background: oklch(62% .16 var(--au) / .17);
  box-shadow: inset 0 -2px 0 -1px oklch(62% .16 var(--au) / .55);
}

/* ══════════════════════════════════════════════════════════════════════════
   The document map — assets/js/office/docs/map.js

   Below 50% the page stops pretending to be a page. Each block becomes a bar,
   shaded by how recently it was touched, with the things waiting for a
   decision beside it. Not a minimap — a picture of the state.
   ══════════════════════════════════════════════════════════════════════════ */

.ofc-map-host {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 22px 0 60px;
}
.ofc-map-host[hidden] { display: none; }

.ofc-map-head,
.ofc-map-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: min(760px, 88%);
  margin: 0 auto;
}
.ofc-map-head { padding-bottom: 12px; }
.ofc-map-title { font-size: 12px; font-weight: 700; color: var(--o-muted); }
.ofc-map-totals { display: flex; gap: 7px; }
.ofc-map-total {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--o-soft);
  font-size: 11.5px;
  font-weight: 650;
}
.ofc-map-total svg { width: 13px; height: 13px; }
.ofc-map-total.is-comment { color: var(--ofc-accent); }
.ofc-map-total.is-change { color: var(--ofc-ins); }
.ofc-map-total.is-binding { color: var(--ofc-del); }
.ofc-map-total.is-clear { color: var(--o-muted); }

.ofc-map-body {
  width: min(760px, 88%);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 14px 16px;
  border-radius: var(--o-radius);
  background: var(--o-surface);
  box-shadow:
    0 0 0 1px color-mix(in oklab, var(--o-text) 10%, transparent),
    0 10px 22px -8px rgba(0, 0, 0, .20);
}

.ofc-map-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 2px 6px 2px calc(6px + var(--depth, 0) * 14px);
  border: none;
  border-radius: 5px;
  background: transparent;
  cursor: pointer;
  text-align: left;
}
.ofc-map-row:hover { background: var(--o-soft); }

.ofc-map-bar {
  flex: 0 0 auto;
  height: 6px;
  border-radius: 3px;
  /* The resting colour is a document nobody has touched recently: present, but
     not asking for anything. Recency lifts it towards the accent. */
  background: color-mix(in oklab, var(--o-text) 17%, transparent);
  transition: background .18s ease;
}
.ofc-map-row.is-heading .ofc-map-bar {
  height: 11px;
  border-radius: 4px;
  background: color-mix(in oklab, var(--o-text) 42%, transparent);
}
.ofc-map-row.is-divider .ofc-map-bar { height: 2px; opacity: .5; }
.ofc-map-row.is-image .ofc-map-bar,
.ofc-map-row.is-table .ofc-map-bar {
  height: 14px;
  border-radius: 4px;
  background: repeating-linear-gradient(45deg,
    color-mix(in oklab, var(--o-text) 20%, transparent) 0 4px,
    transparent 4px 8px);
}

/* Recency. Three coarse steps, because this is a glance and a continuous
   gradient across a month of edits reads as noise. */
.ofc-map-row.is-today .ofc-map-bar { background: color-mix(in oklab, var(--ofc-accent) 78%, transparent); }
.ofc-map-row.is-week .ofc-map-bar { background: color-mix(in oklab, var(--ofc-accent) 44%, transparent); }
.ofc-map-row.is-month .ofc-map-bar { background: color-mix(in oklab, var(--ofc-accent) 22%, transparent); }
.ofc-map-row.is-today.is-heading .ofc-map-bar { background: var(--ofc-accent); }

.ofc-map-badges { display: inline-flex; gap: 4px; flex: 0 0 auto; }
.ofc-map-badges i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
}
.ofc-map-badges i.is-comment { background: var(--ofc-accent); }
.ofc-map-badges i.is-change { background: var(--ofc-ins); }
.ofc-map-badges i.is-binding { background: var(--ofc-del); }

.ofc-map-foot {
  justify-content: center;
  padding-top: 14px;
  font-size: 11.5px;
  color: var(--o-muted);
}

/* ══════════════════════════════════════════════════════════════════════════
   Live record bindings — assets/js/office/docs/bindings.js

   A bound value is text that knows which record it came from. It must read as
   ordinary prose — a document full of chips is a form, not a document — so the
   only permanent cue is a hairline under the value. Staleness is reported in
   the MARGIN, not by making the number look broken in the middle of a sentence
   somebody is about to send to a client.
   ══════════════════════════════════════════════════════════════════════════ */

.ofc-model-body span[data-bind] {
  /* Dotted, and in the text's own colour at low alpha: present when you look
     for it, invisible when you are reading. Not the accent, and not solid —
     both are the link affordance. */
  border-bottom: 1px dotted color-mix(in oklab, var(--o-text) 38%, transparent);
  cursor: default;
}

/* The picker. */
.ofc-bind-scrim {
  position: absolute;
  inset: 0;
  z-index: 9550;
  display: grid;
  place-items: start center;
  padding-top: 12vh;
  background: color-mix(in oklab, var(--o-text) 22%, transparent);
  backdrop-filter: blur(2px);
}
.ofc-bind {
  width: min(560px, 92%);
  max-height: 70vh;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius);
  background: var(--o-surface);
  box-shadow: var(--o-shadow), 0 24px 60px -16px rgba(0, 0, 0, .45);
  overflow: hidden;
}
.ofc-bind header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 13px 14px 11px;
  border-bottom: 1px solid var(--o-line);
}
.ofc-bind header span { display: inline-flex; align-items: center; gap: 9px; font-size: 13.5px; }
.ofc-bind header b { font-weight: 700; }
.ofc-bind header button {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--o-muted);
  cursor: pointer;
  opacity: .6;
}
.ofc-bind header button:hover { background: var(--o-soft); color: var(--o-text); opacity: 1; }

.ofc-bind-note {
  margin: 0;
  padding: 10px 14px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--o-muted);
}
.ofc-bind-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.ofc-bind-step { padding: 4px 14px 14px; }

.ofc-bind-group {
  margin: 12px 0 7px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--o-muted);
}
.ofc-bind-grid { display: flex; flex-wrap: wrap; gap: 6px; }
.ofc-bind-pill {
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--o-line);
  background: transparent;
  color: var(--o-text);
  font-size: 12px;
  font-weight: 600;
  text-transform: capitalize;
  cursor: pointer;
}
.ofc-bind-pill:hover {
  background: color-mix(in oklab, var(--ofc-accent) 14%, transparent);
  border-color: color-mix(in oklab, var(--ofc-accent) 45%, transparent);
}

.ofc-bind-search {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 12px 0 10px;
  padding: 8px 11px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  color: var(--o-muted);
}
.ofc-bind-search input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: none;
  color: var(--o-text);
  font: inherit;
  font-size: 13px;
  outline: none;
}

.ofc-bind-list { display: flex; flex-direction: column; gap: 2px; }
.ofc-bind-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-text);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.ofc-bind-row:hover { background: var(--o-soft); }
.ofc-bind-row > span { text-transform: capitalize; min-width: 0; }
.ofc-bind-row b {
  flex: 0 0 auto;
  max-width: 45%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--o-muted);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.ofc-bind-chosen {
  margin: 12px 0 10px;
  padding: 8px 11px;
  border-radius: var(--o-radius-sm);
  background: var(--o-soft);
  font-size: 13px;
  font-weight: 650;
}
.ofc-bind-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 12px;
  padding: 5px 9px 5px 5px;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--o-muted);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.ofc-bind-back:hover { background: var(--o-soft); color: var(--o-text); }

/* The author under the caret, in the status bar. */
.ofc-status-prov {
  padding-left: 14px;
  border-left: 1px solid var(--o-line);
  color: var(--ofc-accent);
  font-weight: 600;
}

/* ── Notebook ───────────────────────────────────────────────────────── */
/* Same paper metaphor as the document, and since Part G the same editor —
   but flowing rather than paginated. A note has no page box to honour, so
   nothing here sets a height in centimetres; everything about how the model
   PROJECTS lives on .ofc-model-body, shared with the document page. */
.ofc-note-root { height: 100%; }
.ofc-note {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  background: color-mix(in oklab, var(--o-text) 4%, var(--o-bg));
}
.ofc-note-scroll {
  flex: 1 1 auto;
  overflow: auto;
  display: flex;
  justify-content: center;
  padding: 28px 24px 80px;
}
.ofc-note-paper {
  /* Wider than it was: the margin is carved out of this, not added beside it,
     so the measure of the text itself stays where it was. */
  --nb-gutter: 13rem;
  position: relative;
  width: min(100%, 59rem);
  min-height: 60vh;
  padding: 36px 40px 60px;
  background: var(--o-surface);
  color: var(--o-text);
  border-radius: 3px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .1), 0 12px 34px rgba(0, 0, 0, .12);
  font-size: calc(1rem * var(--nb-zoom, 1));
  transition: font-size .15s ease;
}
/* ── The note's margin ──────────────────────────────────────────────────
   Same interface as the document's, with one difference that matters: a page
   has margins because it is paper, and a note does not. Reserving the space
   permanently would be inventing a page the note never had — so the reserve
   only appears when something wants to sit in it, decided in the engine from
   data rather than from measured marks (see reserveMargin). No transition on
   the padding: animating it would drag every mark across the reflow it causes. */
.ofc-note-paper[data-margin="on"] {
  padding-right: var(--nb-gutter);
}
.ofc-note-gutter {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: var(--nb-gutter);
  /* Above the paper, below any dialog. Marks bloom outside this box. */
  z-index: 2;
  /* Nothing to catch clicks while the margin is empty — the paper underneath
     is still text somebody is trying to select. */
  pointer-events: none;
}
.ofc-note-paper[data-margin="on"] .ofc-note-gutter { pointer-events: auto; }

.ofc-note-body {
  outline: none;
  min-height: 40vh;
  line-height: 1.7;
}

/* ── The commitments map — assets/js/office/notebooks/map.js ─────────────
   Below 50% the note is replaced by the NOTEBOOK, because a notebook is the
   first surface whose unit of work is not the file: what you cannot see from
   inside one note is the other twenty-nine. Two panes — the notes, and every
   unticked box in any of them. */
.ofc-nbmap.ofc-map-host {
  padding: 22px 24px 60px;
}
.ofc-nbmap-panes {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 22px;
  align-items: start;
  margin-top: 14px;
}
.ofc-nbmap-notes {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ofc-nbmap-note {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid transparent;
  border-radius: var(--o-radius);
  background: var(--o-surface);
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ofc-nbmap-note:hover { border-color: var(--o-line); }
.ofc-nbmap-note.is-open-note {
  border-color: color-mix(in oklab, var(--ofc-accent) 60%, var(--o-line));
  background: color-mix(in oklab, var(--ofc-accent) 6%, var(--o-surface));
}
.ofc-nbmap-note.is-drop {
  border-color: var(--ofc-accent);
  border-style: dashed;
}
.ofc-nbmap-note-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.ofc-nbmap-note-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--o-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Recency, as a rule down the leading edge rather than a tint: a tinted card
   competes with the open-note highlight, and the two mean different things. */
.ofc-nbmap-note.is-today { box-shadow: inset 3px 0 0 -1px var(--ofc-ins); }
.ofc-nbmap-note.is-week { box-shadow: inset 3px 0 0 -1px color-mix(in oklab, var(--ofc-ins) 60%, transparent); }
.ofc-nbmap-note.is-month { box-shadow: inset 3px 0 0 -1px color-mix(in oklab, var(--ofc-ins) 30%, transparent); }

/* The shape strip: one bar per block, so a note reads as an outline without
   any of it being legible — which is the whole point at this distance. */
.ofc-nbmap-shape {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ofc-nbmap-blk {
  height: 3px;
  border-radius: 2px;
  background: color-mix(in oklab, var(--o-text) 16%, transparent);
}
.ofc-nbmap-blk.is-heading {
  height: 5px;
  background: color-mix(in oklab, var(--o-text) 42%, transparent);
}
.ofc-nbmap-blk.is-listItem { background: color-mix(in oklab, var(--o-text) 22%, transparent); }
.ofc-nbmap-blk.is-divider { background: color-mix(in oklab, var(--o-text) 10%, transparent); }
.ofc-nbmap-blk.is-attachment,
.ofc-nbmap-blk.is-image { background: color-mix(in oklab, var(--ofc-accent) 40%, transparent); }
.ofc-nbmap-blk.has-comment { background: var(--ofc-accent); }
.ofc-nbmap-blk.has-change { background: var(--ofc-ins); }
.ofc-nbmap-blk.has-binding { background: var(--ofc-del); }

/* The commitments pane. */
.ofc-nbmap-commit h4 {
  margin: 0 0 10px;
  font-size: 13px;
  font-weight: 700;
  color: var(--o-text);
}
.ofc-nbmap-empty {
  margin: 0;
  font-size: 12px;
  color: var(--o-muted);
}
.ofc-nbmap-todos {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ofc-nbmap-todo {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 8px 11px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius);
  background: var(--o-surface);
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ofc-nbmap-todo:hover { border-color: color-mix(in oklab, var(--ofc-accent) 55%, var(--o-line)); }
/* The same empty box the note draws, so the two read as the same thing. */
.ofc-nbmap-box {
  width: 13px;
  height: 13px;
  border: 1.5px solid color-mix(in oklab, var(--o-text) 38%, transparent);
  border-radius: 4px;
}
.ofc-nbmap-todo-text {
  font-size: 13px;
  color: var(--o-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ofc-nbmap-todo-note {
  /* Which note it came from is half the value of the line — an item with no
     note beside it is a to-do list, and the point here is that it is not one.
     Given a floor in ch so it never truncates to three characters. */
  flex: 0 0 auto;
  min-width: 9ch;
  max-width: 18ch;
  font-size: 11px;
  color: var(--o-muted);
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ofc-map-total.is-open { color: var(--ofc-accent); }

/* ── The checklist ──────────────────────────────────────────────────────
   A list item that says `data-checked`, drawn with a box in its own left
   padding. Deliberately NOT an <input>: the body is a projection the model
   rewrites on every edit, so a real form control would be swapped out from
   under a click — and inside contenteditable it is also selectable, deletable
   and carried into every copy. The box is a pseudo-element, the state is an
   attribute, and the click is a geometric hit test in the engine. */
.ofc-model-body ul[data-list="check"] {
  list-style: none;
  padding-left: 0;
  margin: 10px 0;
}
.ofc-model-body ul[data-list="check"] li {
  position: relative;
  padding-left: 1.75em;
  margin: 3px 0;
}
.ofc-model-body ul[data-list="check"] li::before {
  content: "";
  position: absolute;
  left: 0;
  /* Centred on the first LINE, not on the item: a checklist entry that wraps to
     three lines must not put its box halfway down the paragraph. */
  top: calc(0.85em - 0.5em);
  width: 1em;
  height: 1em;
  border: 1.5px solid color-mix(in oklab, var(--o-text) 38%, transparent);
  border-radius: 4px;
  background: var(--o-surface);
  cursor: pointer;
}
.ofc-model-body ul[data-list="check"] li[data-checked="true"]::before {
  border-color: var(--ofc-accent);
  background: var(--ofc-accent);
}
/* The tick, drawn rather than typed — a glyph would be a font dependency for
   the one mark on the page that has to be unambiguous. */
.ofc-model-body ul[data-list="check"] li[data-checked="true"]::after {
  content: "";
  position: absolute;
  left: 0.32em;
  top: calc(0.85em - 0.33em);
  width: 0.3em;
  height: 0.58em;
  /* The paper colour, which is the one thing guaranteed to contrast with a
     filled box in both themes — the fill is the accent, and an accent light
     enough to need a dark tick is exactly what #dfdfdf used to be. */
  border: solid var(--o-surface);
  border-width: 0 2.5px 2.5px 0;
  transform: rotate(42deg);
  pointer-events: none;
}
.ofc-model-body ul[data-list="check"] li[data-checked="true"] {
  color: var(--o-muted);
  text-decoration: line-through;
  text-decoration-color: color-mix(in oklab, var(--o-text) 30%, transparent);
}

/* ── The attachment card ────────────────────────────────────────────────
   A link, because the one thing that must work everywhere — the exported
   .docx, the PDF, an email quoting the note — is a link to the file. The card
   is what that link looks like on the page, not a different element. */
.ofc-model-body a[data-block="attachment"] {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 12px 0;
  padding: 9px 12px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius);
  background: color-mix(in oklab, var(--o-text) 3%, transparent);
  color: var(--o-text);
  text-decoration: none;
  font-size: 13px;
  font-weight: 600;
}
.ofc-model-body a[data-block="attachment"]::before {
  content: "";
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  background: currentColor;
  opacity: .6;
  /* A paperclip, inline: this markup is also what gets exported and reopened,
     so the card cannot depend on an icon element being injected into it. */
  -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2024%2024'%20fill%3D'none'%20stroke%3D'black'%20stroke-width%3D'1.8'%20stroke-linecap%3D'round'%20stroke-linejoin%3D'round'%3E%3Cpath%20d%3D'M21.4%2011.05%2012.25%2020.2a5.5%205.5%200%200%201-7.78-7.78l9.2-9.2a3.67%203.67%200%200%201%205.18%205.18l-9.2%209.2a1.83%201.83%200%201%201-2.6-2.6l8.5-8.48'%2F%3E%3C%2Fsvg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2024%2024'%20fill%3D'none'%20stroke%3D'black'%20stroke-width%3D'1.8'%20stroke-linecap%3D'round'%20stroke-linejoin%3D'round'%3E%3Cpath%20d%3D'M21.4%2011.05%2012.25%2020.2a5.5%205.5%200%200%201-7.78-7.78l9.2-9.2a3.67%203.67%200%200%201%205.18%205.18l-9.2%209.2a1.83%201.83%200%201%201-2.6-2.6l8.5-8.48'%2F%3E%3C%2Fsvg%3E") center / contain no-repeat;
}
.ofc-model-body a[data-block="attachment"]:hover {
  border-color: color-mix(in oklab, var(--ofc-accent) 55%, var(--o-line));
}

/* An image attaches as a figure — for an image the file IS the content, so it
   shows rather than being named. Same block, same data-* attributes. */
.ofc-model-body figure[data-block="attachment"] {
  margin: 14px 0;
}
.ofc-model-body figure[data-block="attachment"] img {
  display: block;
  border-radius: var(--o-radius);
  border: 1px solid var(--o-line);
}
.ofc-model-body figure[data-block="attachment"] figcaption {
  margin-top: 6px;
  font-size: 12px;
  color: var(--o-muted);
}
.ofc-model-body figure[data-block="attachment"] figcaption a { color: inherit; }

.ofc-missing {
  display: grid;
  place-items: center;
  gap: 6px;
  min-height: 40vh;
  text-align: center;
  color: var(--o-muted);
}
.ofc-missing h3 { margin: 0; color: var(--o-text); }

/* ── Presentation mode ──────────────────────────────────────────────────
   The nine-tab ribbon shipped a full `.slv-present*` block that no
   JavaScript ever rendered; it was deleted as dead CSS. These rules back a
   `deck.present` command that actually exists, and reuse the same
   `.slv-theme-*` / `.slv-l-*` classes as the editor so a slide looks
   identical presented and edited. */
.ofc-present {
  position: fixed;
  inset: 0;
  z-index: 2147483000;
  display: flex;
  flex-direction: column;
  background: #000;
  color: #fff;
}
.ofc-present-stage {
  flex: 1 1 auto;
  display: grid;
  place-items: center;
  padding: 2vmin;
  min-height: 0;
}
.ofc-present-slide {
  width: min(100%, calc((100vh - 12vmin) * var(--present-ratio, 1.7778)));
  aspect-ratio: var(--present-ratio, 1.7778);
  max-height: calc(100vh - 12vmin);
  overflow: hidden;
  border-radius: 6px;
  box-shadow: 0 20px 70px rgba(0, 0, 0, .6);
}
.ofc-present-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 10px 16px;
  background: rgba(255, 255, 255, .06);
  border-top: 1px solid rgba(255, 255, 255, .12);
  opacity: .35;
  transition: opacity .18s ease;
}
.ofc-present:hover .ofc-present-bar,
.ofc-present-bar:focus-within { opacity: 1; }
.ofc-present-btn {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 8px;
  background: transparent;
  color: #fff;
  cursor: pointer;
}
.ofc-present-btn:hover { background: rgba(255, 255, 255, .14); }
.ofc-present-btn:disabled { opacity: .35; cursor: default; }
.ofc-present-count {
  min-width: 74px;
  text-align: center;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, .8);
}
.ofc-present-notes {
  max-width: 46ch;
  max-height: 3.2em;
  overflow: auto;
  font-size: 12px;
  line-height: 1.35;
  color: rgba(255, 255, 255, .62);
  white-space: pre-wrap;
}

[contenteditable][data-ph]:empty::before {
  content: attr(data-ph);
  color: var(--o-muted);
  opacity: .55;
  pointer-events: none;
}

/* ── Spreadsheet ────────────────────────────────────────────────────── */
.ofc-namebox {
  flex: 0 0 auto;
  min-width: 56px;
  padding: 4px 8px;
  text-align: center;
  font-size: 12px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  border: 1px solid var(--o-line);
  border-radius: 6px;
  background: var(--o-soft);
}
.ofc-fx {
  flex: 0 0 auto;
  width: 26px;
  height: 24px;
  border: 1px solid var(--o-line);
  border-radius: 6px;
  background: transparent;
  color: var(--o-sheet, var(--ofc-accent));
  font: inherit;
  font-size: 12px;
  font-style: italic;
  font-weight: 700;
  cursor: pointer;
}
.ofc-fx:hover { background: var(--o-soft); }
.ofc-formula {
  flex: 1 1 auto;
  min-width: 0;
  height: 26px;
  padding: 0 10px;
  border: 1px solid var(--o-line);
  border-radius: 6px;
  background: var(--o-bg);
  color: var(--o-text);
  font: inherit;
  font-size: 12.5px;
}
.ofc-formula:focus {
  outline: none;
  border-color: color-mix(in oklab, var(--ofc-accent) 55%, transparent);
}

.ofc-sheet {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ofc-grid-scroll {
  flex: 1 1 auto;
  overflow: auto;
  background: color-mix(in oklab, var(--o-text) 2%, var(--o-bg));
}
.ofc-grid {
  /* The table was auto-width, so a sheet with five populated columns painted
     cell backgrounds across a third of the window and left the rest as bare
     scroll-container background — it read as an unfinished render. The width
     goes to the viewport edge and a single spacer column absorbs the
     remainder, so real column widths stay exactly as authored. */
  min-width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--o-surface);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.ofc-grid .ofc-filler {
  width: 100%;
  min-width: 0;
  border-right: none;
  background: var(--o-surface);
  cursor: default;
}
.ofc-grid thead .ofc-filler {
  background: color-mix(in oklab, var(--o-text) 6%, var(--o-surface));
}
.ofc-grid th,
.ofc-grid td {
  border-right: 1px solid var(--o-line);
  border-bottom: 1px solid var(--o-line);
  padding: 4px 8px;
  height: 26px;
  min-width: 92px;
  max-width: 320px;
  overflow: hidden;
  white-space: nowrap;
}
.ofc-grid th {
  position: sticky;
  z-index: 2;
  background: color-mix(in oklab, var(--o-text) 6%, var(--o-surface));
  font-weight: 600;
  font-size: 11.5px;
  color: var(--o-muted);
  text-align: center;
  user-select: none;
  cursor: pointer;
}
.ofc-grid thead th { top: 0; }
.ofc-grid .ofc-rowhdr,
.ofc-grid .ofc-corner { left: 0; min-width: 46px; width: 46px; }
.ofc-grid .ofc-corner { z-index: 3; top: 0; }
.ofc-grid .ofc-rowhdr { position: sticky; }
.ofc-grid th.is-sel {
  background: color-mix(in oklab, var(--ofc-accent) 18%, var(--o-surface));
  color: var(--ofc-accent);
}
.ofc-grid td { outline: none; cursor: cell; }
.ofc-grid td.is-active {
  outline: 2px solid var(--ofc-accent);
  outline-offset: -2px;
  background: color-mix(in oklab, var(--ofc-accent) 7%, transparent);
}

.ofc-sheet-tabs {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px;
  height: 34px;
  border-top: 1px solid var(--o-line);
  background: var(--o-surface);
}
.ofc-sheet-tab-list { display: flex; gap: 4px; overflow-x: auto; }
.ofc-sheet-tab {
  padding: 4px 12px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--o-muted);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.ofc-sheet-tab:hover { background: var(--o-soft); color: var(--o-text); }
.ofc-sheet-tab.is-on {
  background: color-mix(in oklab, var(--ofc-accent) 14%, transparent);
  border-color: color-mix(in oklab, var(--ofc-accent) 30%, transparent);
  color: var(--ofc-accent);
}
.ofc-sheet-add {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  border: 1px dashed var(--o-line);
  border-radius: 6px;
  background: transparent;
  color: var(--o-muted);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.ofc-sheet-add:hover { background: var(--o-soft); color: var(--o-text); }

/* ══════════════════════════════════════════════════════════════════════════
   Part E — the design pass on the spreadsheet.

   The document's floor problem was that the page did not read as paper. The
   grid's is different and worse: it read as a table element on a background,
   with no edge of its own, nothing to say which cells came from anywhere, and
   no margin at all — so every capability would have had to become a panel,
   which is the failure Part D exists to undo.
   ══════════════════════════════════════════════════════════════════════════ */

/* The desk. The same treatment the page sits on, and mixing towards BLACK for
   the same reason: mixing towards the text lightens the surround under a dark
   theme, so the desk creeps up towards the sheet exactly where the sheet has
   least contrast to spare. */
.ofc-sheet {
  background:
    radial-gradient(120% 60% at 50% -12%,
      color-mix(in oklab, var(--ofc-accent) 9%, transparent), transparent 60%),
    color-mix(in oklab, var(--o-bg) 86%, #000);
}

/* The stage is the SHEET: the grid and its margin together, one object lying on
   the desk. Two things follow from making it an object rather than a full-bleed
   table, and both are the point of the floor pass.

   It is inset, so the desk is visible around it — a table that runs to all four
   edges of the window has no boundary and reads as a background region with
   lines on it. And the margin is inside that boundary, which is what makes it a
   margin at all rather than a strip of chrome parked next to the data. */
.ofc-sheet-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: stretch;
  margin: 10px 12px 0;
  border-radius: var(--o-radius-sm) var(--o-radius-sm) 0 0;
  /* NOT clipped: a gutter card near the bottom of the sheet opens past this
     box, and `overflow: hidden` here would slice it off. The children carry the
     rounding instead. */
  box-shadow:
    0 0 0 1px color-mix(in oklab, var(--o-text) 13%, transparent),
    0 1px 1px rgba(0, 0, 0, .10),
    0 12px 26px -10px rgba(0, 0, 0, .40);
}
.ofc-sheet-stage[hidden] { display: none; }
.ofc-sheet-stage .ofc-grid-scroll {
  min-width: 0;
  border-radius: var(--o-radius-sm) 0 0 0;
}

.ofc-sheet-gutter {
  /* Wide enough for a 24px mark with air either side, and no wider: this is a
     margin, not a panel. */
  flex: 0 0 var(--sheet-gutter, 52px);
  position: relative;
  border-left: 1px solid var(--o-line);
  border-radius: 0 var(--o-radius-sm) 0 0;
  /* The same surface as the grid, because it is part of the same sheet. A
     different fill here would read as a second panel, which is the thing this
     design exists to remove. */
  background: var(--o-surface);
  /* NOT clipped, for the reason the stage is not: `overflow: hidden` would trap
     the cards, which have to open back across the sheet. Marks for rows that
     are off screen are never drawn in the first place — the engine filters them
     against the scroller's viewport. */
  overflow: visible;
  z-index: 4;
}

/* ── What a cell knows ──────────────────────────────────────────────────── */

/* A bound cell. Dotted, in the text's own colour at low alpha: present when you
   look for it, invisible when you are reading. A text decoration rather than a
   border, so it cannot disturb the grid lines — and not the accent and not
   solid, because both of those are the link affordance. */
.ofc-grid td.is-bound {
  text-decoration: underline dotted
    color-mix(in oklab, var(--o-text) 42%, transparent);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

/* A bound cell whose record has moved on. The tint is drawn as an inset shadow
   rather than a background, and that is load-bearing: cell fill colours are
   written as an inline `background`, which would win outright. An inset shadow
   paints above the background, so a cell somebody has coloured yellow can still
   report that it is out of date. */
/* The wash percentages here are all higher than the equivalents in prose, and
   that is not carelessness: a tint spread over a paragraph has a lot of area to
   register in, where a cell is one 26px line and a 12% wash on it reads as a
   rendering artefact. The stripe down the leading edge is what actually carries
   it at a glance across a full grid. */
.ofc-grid td.is-stale {
  box-shadow:
    inset 3px 0 0 0 var(--ofc-del),
    inset 0 0 0 100px color-mix(in oklab, var(--ofc-del) 18%, transparent);
  text-decoration-color: color-mix(in oklab, var(--ofc-del) 80%, transparent);
}

/* A change waiting for a decision. A wash and a line along the baseline, the
   same shape language as a tracked insertion in prose — and deliberately NOT
   the 2px accent outline, which is how this grid draws the cursor. */
.ofc-grid td.is-pending {
  /* No line along the baseline, and no full-cell box. This sheet's accent and
     the tracked-insertion green are close enough that a boxed cell reads as the
     CURSOR, which is the hyperlink mistake one layer over: the cursor is a 2px
     outline on all four sides, so a pending cell has to be a stripe on one. */
  box-shadow:
    inset 3px 0 0 0 var(--ofc-ins),
    inset 0 0 0 100px color-mix(in oklab, var(--ofc-ins) 13%, transparent);
}
.ofc-grid td.is-pending.is-stale {
  /* Both at once. The pending edit is the nearer decision, so it takes the
     wash; the staleness takes the stripe. */
  box-shadow:
    inset 3px 0 0 0 var(--ofc-del),
    inset 0 0 0 100px color-mix(in oklab, var(--ofc-ins) 13%, transparent);
}

/* The comment indicator: the corner wedge every spreadsheet has used for forty
   years. Worth keeping precisely because it is the one convention here that
   needs no explaining. */
.ofc-grid td.has-comment { position: relative; }
.ofc-grid td.has-comment::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  border: 5px solid transparent;
  border-top-color: var(--ofc-accent);
  border-right-color: var(--ofc-accent);
  pointer-events: none;
}

/* Authorship. A hue per author down the leading edge, plus a wash faint enough
   to keep figures readable. Both inset, for the reason is-stale gives. */
.ofc-grid td.is-authored {
  box-shadow:
    inset 3px 0 0 0 oklch(62% .16 var(--au)),
    inset 0 0 0 100px oklch(62% .16 var(--au) / .13);
}
.ofc-grid td.is-authored.is-pending {
  box-shadow:
    inset 3px 0 0 0 oklch(62% .16 var(--au)),
    inset 0 0 0 100px color-mix(in oklab, var(--ofc-ins) 12%, transparent),
    inset 0 -2px 0 -1px color-mix(in oklab, var(--ofc-ins) 70%, transparent);
}

/* The cursor is an outline, so it survives every wash above it. */
.ofc-grid td.is-active { position: relative; z-index: 1; }

.ofc-status-stale { color: var(--ofc-del); font-weight: 650; }

/* ── The workbook map — assets/js/office/sheets/map.js ──────────────────── */
/* Below 50% the grid stops pretending to be a grid. Every sheet in the file is
   drawn at once, every cell as a tile coloured by what KIND of thing it is.
   Not a minimap — a picture of the state. */

/* The head and foot come from the document's map, which is a single column of
   blocks and sized for prose. A workbook is laid out side by side and needs the
   width, so all three are widened together — leaving the head at the document's
   760px put the title over empty desk while the sheets started further left. */
.ofc-smap.ofc-map-host .ofc-map-head,
.ofc-smap.ofc-map-host .ofc-map-foot,
.ofc-smap-body {
  width: min(1100px, 94%);
  margin: 0 auto;
}
.ofc-smap-body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 16px;
}

.ofc-smap-sheet {
  flex: 0 1 auto;
  /* A four-column sheet must not render as a postage stamp beside a wide one —
     the card has to stay readable as a card, whatever it contains. */
  min-width: 190px;
  max-width: 100%;
  padding: 12px 14px 14px;
  border-radius: var(--o-radius);
  background: var(--o-surface);
  box-shadow:
    0 0 0 1px color-mix(in oklab, var(--o-text) 10%, transparent),
    0 10px 22px -8px rgba(0, 0, 0, .20);
}
/* Recency, on the sheet rather than on the cell: at this distance the question
   is which SHEET somebody has been working in. Three coarse steps, because a
   continuous gradient across a month of edits reads as noise. */
.ofc-smap-sheet.is-today {
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--ofc-accent) 55%, transparent),
    0 10px 22px -8px rgba(0, 0, 0, .20);
}
.ofc-smap-sheet.is-week {
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--ofc-accent) 32%, transparent),
    0 10px 22px -8px rgba(0, 0, 0, .20);
}
.ofc-smap-sheet.is-month {
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--ofc-accent) 18%, transparent),
    0 10px 22px -8px rgba(0, 0, 0, .20);
}

.ofc-smap-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 9px;
}
.ofc-smap-name { font-size: 12.5px; font-weight: 700; }
.ofc-smap-name.is-on { color: var(--ofc-accent); }
.ofc-smap-meta { font-size: 11px; color: var(--o-muted); font-variant-numeric: tabular-nums; }
.ofc-smap-empty { margin: 0; font-size: 11.5px; color: var(--o-muted); }

.ofc-smap-grid {
  display: grid;
  /* Fixed tiles, not to scale. A true-scale thumbnail of a 400-row sheet is a
     smear; a fixed tile means two sheets are comparable at a glance and a sheet
     is as big as it has content. */
  grid-template-columns: repeat(var(--cols, 10), 11px);
  gap: 3px;
}
.ofc-smap-grid i {
  width: 11px;
  height: 11px;
  border-radius: 2px;
  cursor: pointer;
}
.ofc-smap-grid i:hover { outline: 1.5px solid var(--ofc-accent); outline-offset: 1px; }
/* An empty cell is a gap, not a pale tile: the SHAPE of the gap is what tells
   you where the table ends. */
.ofc-smap-grid i.is-empty { background: transparent; cursor: default; }
.ofc-smap-grid i.is-empty:hover { outline: none; }
.ofc-smap-grid i.is-text { background: color-mix(in oklab, var(--o-text) 22%, transparent); }
.ofc-smap-grid i.is-number { background: color-mix(in oklab, var(--o-text) 48%, transparent); }
.ofc-smap-grid i.is-formula {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px color-mix(in oklab, var(--ofc-accent) 85%, transparent);
}
.ofc-smap-grid i.is-bound { background: color-mix(in oklab, var(--ofc-accent) 62%, transparent); }
.ofc-smap-grid i.is-stale { background: var(--ofc-del); }
.ofc-smap-grid i.is-pending { background: var(--ofc-ins); }
.ofc-smap-grid i.has-comment { box-shadow: inset 0 0 0 1.5px var(--ofc-accent); }

/* ══════════════════════════════════════════════════════════════════════════
   Part F — the design pass on the presentation.

   The document's floor problem was that the page did not read as paper; the
   grid's was that a full-bleed table had no edge of its own. The deck's is
   different again and it is about ATTENTION: a presentation shows you one slide
   out of forty and hides the other thirty-nine behind a 196px filmstrip, so
   every capability that is about the deck rather than about the slide on screen
   has nowhere to live except a panel — which is the failure Part D exists to
   undo, arriving here by a different road.

   So the filmstrip stops being a navigation strip and becomes the deck's INDEX:
   it carries state, it carries the margin, and it is where the marks are.
   ══════════════════════════════════════════════════════════════════════════ */

.ofc-slides {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.ofc-slide-body {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  display: flex;
}
.ofc-slide-body[hidden] { display: none; }

/* The rail is the filmstrip AND its margin, one object — the same move the
   spreadsheet's stage makes, for the same reason: a margin has to be INSIDE the
   thing it is a margin of, or it is a strip of chrome parked beside the data. */
.ofc-slide-rail {
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
  border-right: 1px solid var(--o-line);
  background: var(--o-surface);
  /* NOT clipped: a gutter card opens rightward past this edge, and
     `overflow: hidden` here would slice it off. The strip carries its own
     scrolling instead. */
  overflow: visible;
  z-index: 4;
}
.ofc-slide-thumbs {
  flex: 0 0 196px;
  width: 196px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  overflow-y: auto;
  overflow-x: hidden;
}
.ofc-thumb {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  text-align: left;
  position: relative;
}
.ofc-thumb-no {
  flex: 0 0 auto;
  width: 14px;
  padding-top: 2px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--o-muted);
  font-variant-numeric: tabular-nums;
}
.ofc-thumb-canvas {
  flex: 1 1 auto;
  min-width: 0;
  display: block;
  padding: 7px;
  border-radius: 5px;
  border: 2px solid transparent;
  overflow: hidden;
  font-size: 6px;
  pointer-events: none;
}
.ofc-thumb.is-on .ofc-thumb-canvas { border-color: var(--ofc-accent); }
.ofc-thumb-canvas .slv-s-title { font-size: 9px; }
.ofc-thumb-canvas .slv-s-body { font-size: 6px; }

/* A slide carrying more text than anyone can read from the back of a room. The
   cue is on the THUMBNAIL rather than on the canvas, because the canvas is
   where you are working and a warning there would be scolding you mid-sentence;
   the strip is where you review the deck as a deck. See slides/fields.js. */
.ofc-thumb-canvas.is-w-heavy {
  box-shadow: inset 0 -3px 0 0 color-mix(in oklab, var(--ofc-del) 62%, transparent);
}

/* The state marks on a thumbnail: the same three colours the margin and both
   other maps use, so the whole suite says "waiting", "under review" and "out of
   date" in one language. Dots rather than glyphs — at this size an icon is a
   smudge, and the mark's job here is only to say WHICH SLIDE. */
.ofc-thumb-marks {
  position: absolute;
  right: 3px;
  bottom: 3px;
  display: flex;
  gap: 3px;
  pointer-events: none;
}
.ofc-thumb-marks i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  box-shadow: 0 0 0 1.5px var(--o-surface);
}
.ofc-thumb-marks i.is-comment { background: var(--ofc-accent); }
.ofc-thumb-marks i.is-change { background: var(--ofc-ins); }
.ofc-thumb-marks i.is-binding { background: var(--ofc-del); }

/* Authorship on the filmstrip. This is the half of the layer neither of the
   other two surfaces has: a hue per author down the leading edge of the
   thumbnail, so "which of these forty slides are mine" is answered for slides
   that are not on screen. One colour per slide — a striped 196px thumbnail is a
   texture, not information. See slides/authorship.js. */
.ofc-thumb.is-authored .ofc-thumb-canvas {
  box-shadow: inset 4px 0 0 0 oklch(62% .16 var(--au));
}
.ofc-thumb.is-authored .ofc-thumb-canvas.is-w-heavy {
  box-shadow:
    inset 4px 0 0 0 oklch(62% .16 var(--au)),
    inset 0 -3px 0 0 color-mix(in oklab, var(--ofc-del) 62%, transparent);
}

/* Dragging to reorder. The indicator is a line on the card being hovered rather
   than a gap element, for the reason slides/map.js gives: a gap re-measures the
   whole flow on every pointer move. */
.ofc-thumb.is-dragging { opacity: .4; }
.ofc-thumb.is-drop-before::before,
.ofc-thumb.is-drop-after::after {
  content: '';
  position: absolute;
  left: 21px;
  right: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--ofc-accent);
}
.ofc-thumb.is-drop-before::before { top: -6px; }
.ofc-thumb.is-drop-after::after { bottom: -6px; }

/* The deck's margin.

   Beside the FILMSTRIP, not beside the canvas — the reasoning is at
   paintGutter() in slides/engine.js and it is the biggest departure in Part F.
   Wide enough for a 24px mark with air either side, and no wider: this is a
   margin, not a panel. */
.ofc-slide-gutter {
  flex: 0 0 var(--deck-gutter, 52px);
  position: relative;
  border-left: 1px solid var(--o-line);
  /* The same surface as the strip, because it is part of the same object. A
     different fill would read as a second panel, which is the thing this design
     exists to remove. */
  background: var(--o-surface);
  /* NOT clipped, for the reason the rail is not: overflow would trap the cards,
     which open rightward over the stage. Marks for thumbnails off the frame are
     never drawn — the engine filters them against the strip's viewport. */
  overflow: visible;
}

.ofc-slide-stage {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 24px;
  overflow: auto;
  /* The desk, mixing towards BLACK — the same treatment the page and the sheet
     lie on, and the same correction Part E made. This mixed towards --o-text,
     which lightens the surround under a dark theme, so the desk crept up
     towards the slide exactly where a slide has least contrast to spare. */
  background:
    radial-gradient(120% 60% at 50% -12%,
      color-mix(in oklab, var(--ofc-accent) 9%, transparent), transparent 60%),
    color-mix(in oklab, var(--o-bg) 86%, #000);
}
.ofc-slide-canvas {
  width: min(100%, calc(820px * var(--slide-zoom, 1)));
  flex: 0 0 auto;
  padding: 42px;
  border-radius: 10px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .12), 0 14px 40px rgba(0, 0, 0, .16);
  position: relative;
  overflow: hidden;
}
.ofc-slide-canvas .slv-s-title { font-size: 36px; outline: none; }
.ofc-slide-canvas .slv-s-body { font-size: 17px; line-height: 1.5; outline: none; }

.ofc-slide-guides { position: absolute; inset: 0; pointer-events: none; }
.ofc-slide-guides i { position: absolute; background: var(--ofc-accent); opacity: .4; }
.ofc-slide-guides .is-h { left: 0; right: 0; top: 50%; height: 1px; }
.ofc-slide-guides .is-v { top: 0; bottom: 0; left: 50%; width: 1px; }

.ofc-slide-notes {
  width: min(100%, 820px);
  flex: 0 0 auto;
  display: block;
}
.ofc-slide-notes > span {
  display: block;
  margin-bottom: 5px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--o-muted);
}
.ofc-slide-notes textarea {
  width: 100%;
  height: 60px;
  padding: 8px 11px;
  resize: vertical;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-surface);
  color: var(--o-text);
  font: inherit;
  font-size: 12.5px;
  outline: none;
}
.ofc-slide-notes textarea:focus { border-color: color-mix(in oklab, var(--ofc-accent) 50%, transparent); }

/* ── What a field knows ─────────────────────────────────────────────────── */

/* Five states, and no two share a shape — the rule Part E set and the reason it
   set it: a reader has to be able to tell "this is where my caret is" from
   "this is waiting for you" without learning a colour code first.

   The cues are drawn as INSET SHADOWS rather than backgrounds, and on a deck
   that is load-bearing in a way it is not even on the grid: a slide sits inside
   a `.slv-theme-*` block that owns the surface colour, so a wash written as a
   background would either be painted over by the theme or would paint over it —
   and a deck's themes include one that is nearly black. An inset shadow
   composites above whatever the theme laid down, in all four. */

/* The caret's field. A soft outline on all four sides, which is the shape the
   other two surfaces reserve for the cursor. */
.ofc-slide-canvas [data-field]:focus {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--ofc-accent) 46%, transparent);
  border-radius: 3px;
}

/* A bound field. Dotted, in the text's own colour at low alpha: present when
   you look for it, invisible from the third row. A text decoration rather than
   a border, so it cannot move the text — and not solid and not the accent,
   because both of those are the link affordance. */
.ofc-slide-canvas [data-field].is-bound,
.ofc-slide-notes textarea.is-bound {
  text-decoration: underline dotted currentColor;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: color-mix(in oklab, currentColor 42%, transparent);
}

/* A bound field whose record has moved on. A stripe down the leading edge,
   never a box — a boxed field would read as the caret's field, which is the
   hyperlink mistake from D0 and the cursor mistake from E0, one surface over. */
.ofc-slide-canvas [data-field].is-stale,
.ofc-slide-notes textarea.is-stale {
  box-shadow:
    inset 3px 0 0 0 var(--ofc-del),
    inset 0 0 0 100vmax color-mix(in oklab, var(--ofc-del) 12%, transparent);
}

/* A change waiting for a decision. Same shape, the insertion colour. */
.ofc-slide-canvas [data-field].is-pending,
.ofc-slide-notes textarea.is-pending {
  box-shadow:
    inset 3px 0 0 0 var(--ofc-ins),
    inset 0 0 0 100vmax color-mix(in oklab, var(--ofc-ins) 11%, transparent);
}
/* Both at once. The pending edit is the nearer decision, so it takes the wash;
   the staleness takes the stripe. */
.ofc-slide-canvas [data-field].is-pending.is-stale {
  box-shadow:
    inset 3px 0 0 0 var(--ofc-del),
    inset 0 0 0 100vmax color-mix(in oklab, var(--ofc-ins) 11%, transparent);
}

/* Authorship, per field on the canvas. The rail carries the per-slide answer;
   this is the one you need while you are editing. */
.ofc-slide-canvas [data-field].is-authored,
.ofc-slide-notes textarea.is-authored {
  box-shadow:
    inset 3px 0 0 0 oklch(62% .16 var(--au)),
    inset 0 0 0 100vmax oklch(62% .16 var(--au) / .12);
}

/* An open comment. A dot at the field's leading corner rather than the
   spreadsheet's forty-year-old corner wedge: a wedge works on a cell because a
   cell is a box with a corner, and a slide field is text on a themed surface
   with no corner to speak of. */
.ofc-slide-canvas [data-field].has-comment { position: relative; }
.ofc-slide-canvas [data-field].has-comment::after {
  content: '';
  position: absolute;
  top: .45em;
  /* Out in the canvas's own padding, not tucked against the first character —
     at -7px it sat on the text's left edge and read as a bullet on the line. */
  left: -18px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ofc-accent);
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--o-bg) 70%, transparent);
  pointer-events: none;
}

/* The weight readout in the status bar. Only the top band is coloured: three
   coloured chips would make "this slide is fine" look like a warning too. */
.ofc-status-weight { color: var(--o-muted); font-variant-numeric: tabular-nums; }
.ofc-status-weight.is-heavy { color: var(--ofc-del); font-weight: 650; }

/* ══════════════════════════════════════════════════════════════════════════
   The deck map — assets/js/office/slides/map.js

   Below 50% the deck stops pretending to be one slide at a time. Every slide is
   a card coloured by what is TRUE of it, the whole deck's text weight is one
   strip across the top, and — the part neither of the other two maps can have —
   this is where the deck is rearranged, because a deck is the one artefact of
   the three where the order is the content.
   ══════════════════════════════════════════════════════════════════════════ */

/* The head and foot come from the document's map, which is a single column of
   blocks and sized for prose. A deck is laid out as a grid of cards and needs
   the width, so all three widen together — leaving the head at 760px would put
   the title over empty desk while the cards started further left. */
.ofc-dmap.ofc-map-host .ofc-map-head,
.ofc-dmap.ofc-map-host .ofc-map-foot,
.ofc-dmap-rhythm,
.ofc-dmap-body {
  width: min(1100px, 94%);
  margin: 0 auto;
}
.ofc-map-total.is-heavy { color: var(--ofc-del); }

/* The rhythm strip: the whole deck's weight, in order, as one picture. Heights
   are absolute — measured against the point a slide stops being readable, not
   against the heaviest slide in the file. See rhythm() in slides/map.js for why
   the relative version drew a solid slab that said nothing.

   EVERY SLIDE GETS A LANE, and that is what makes the absolute scale legible.
   Drawn as bare bars, a readable deck is a row of 3px hairlines sitting on a
   baseline — correct, and unreadable as a picture. A faint full-height lane
   with the weight filled in from the bottom gives "this deck is fine" a shape
   of its own: a full row of lanes, mostly empty. */
.ofc-dmap-rhythm {
  display: flex;
  align-items: stretch;
  gap: 2px;
  height: 34px;
  margin-bottom: 14px;
  padding: 0 2px;
  border-bottom: 1px solid var(--o-line);
}
.ofc-dmap-rhythm i {
  flex: 1 1 0;
  min-width: 2px;
  border-radius: 2px;
  --bar: color-mix(in oklab, var(--o-text) 30%, transparent);
  background: linear-gradient(
    to top,
    var(--bar) 0 var(--h, 6%),
    color-mix(in oklab, var(--o-text) 15%, transparent) var(--h, 6%));
}
.ofc-dmap-rhythm i.is-sparse { --bar: color-mix(in oklab, var(--ofc-accent) 55%, transparent); }
.ofc-dmap-rhythm i.is-full { --bar: var(--ofc-accent); }
.ofc-dmap-rhythm i.is-heavy { --bar: var(--ofc-del); }
.ofc-dmap-rhythm i.is-on { outline: 1px solid var(--o-text); outline-offset: 1px; }

.ofc-dmap-body {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
}

.ofc-dmap-slide {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 11px 9px;
  border-radius: var(--o-radius-sm);
  background: var(--o-surface);
  box-shadow:
    0 0 0 1px color-mix(in oklab, var(--o-text) 10%, transparent),
    0 8px 18px -8px rgba(0, 0, 0, .22);
  cursor: pointer;
  position: relative;
  text-align: left;
}
.ofc-dmap-slide:hover { box-shadow: 0 0 0 1px color-mix(in oklab, var(--ofc-accent) 45%, transparent), 0 10px 22px -8px rgba(0, 0, 0, .28); }
.ofc-dmap-slide.is-on { box-shadow: 0 0 0 2px var(--ofc-accent), 0 10px 22px -8px rgba(0, 0, 0, .28); }
.ofc-dmap-slide:focus-visible { outline: 2px solid var(--ofc-accent); outline-offset: 2px; }

/* A slide carrying too much text, said the same way the filmstrip says it. */
.ofc-dmap-slide.is-heavy { box-shadow: 0 0 0 1px color-mix(in oklab, var(--ofc-del) 45%, transparent), inset 0 -3px 0 0 var(--ofc-del); }

/* Authorship, when the layer is up. The leading edge, as everywhere else. */
.ofc-dmap-slide[style*='--au'] { box-shadow: 0 0 0 1px color-mix(in oklab, var(--o-text) 10%, transparent), inset 4px 0 0 0 oklch(62% .16 var(--au)); }

.ofc-dmap-slide header {
  display: flex;
  align-items: center;
  gap: 6px;
}
.ofc-dmap-no {
  font-size: 11px;
  font-weight: 700;
  color: var(--o-muted);
  font-variant-numeric: tabular-nums;
}
.ofc-dmap-badges { display: inline-flex; gap: 4px; margin-left: auto; }
.ofc-dmap-badges svg { width: 12px; height: 12px; }
.ofc-dmap-badges .is-comment { color: var(--ofc-accent); }
.ofc-dmap-badges .is-change { color: var(--ofc-ins); }
.ofc-dmap-badges .is-binding { color: var(--ofc-del); }
/* Notes coverage, on every card whether or not there are any. An absence is the
   answer here — a deck where two slides in the middle have no speaker notes is
   a deck somebody stopped working on halfway through. */
.ofc-dmap-notes { color: var(--o-muted); opacity: .3; margin-left: auto; }
.ofc-dmap-notes.is-on { opacity: 1; color: var(--ofc-accent); }
.ofc-dmap-badges + .ofc-dmap-notes { margin-left: 0; }
.ofc-dmap-notes svg { width: 12px; height: 12px; }

.ofc-dmap-title {
  font-size: 12.5px;
  font-weight: 650;
  line-height: 1.3;
  color: var(--o-text);
}
.ofc-dmap-title i { color: var(--o-muted); font-weight: 500; }
.ofc-dmap-body-text {
  margin: 0;
  font-size: 11px;
  line-height: 1.4;
  color: var(--o-muted);
}
.ofc-dmap-slide footer {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: auto;
  padding-top: 4px;
  font-size: 10.5px;
  color: var(--o-muted);
}
.ofc-dmap-slide.is-heavy .ofc-dmap-weight { color: var(--ofc-del); font-weight: 650; }
.ofc-dmap-who { display: inline-flex; align-items: center; gap: 4px; }
.ofc-dmap-who i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: oklch(62% .16 var(--au));
}
.ofc-dmap-when { margin-left: auto; }

/* Shading by recency, as on both other maps. */
.ofc-dmap-slide.is-today .ofc-dmap-no { color: var(--ofc-accent); }

.ofc-dmap-slide.is-dragging { opacity: .4; }
.ofc-dmap-slide.is-drop-before::before,
.ofc-dmap-slide.is-drop-after::after {
  content: '';
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 3px;
  border-radius: 3px;
  background: var(--ofc-accent);
}
.ofc-dmap-slide.is-drop-before::before { left: -8px; }
.ofc-dmap-slide.is-drop-after::after { right: -8px; }

/* ══════════════════════════════════════════════════════════════════════════
   The presenter's pre-flight — slides/engine.js: preflight()

   The one thing in Part F that is not a port of D or E. A stale figure in a
   document is found by a reader, quietly, later; a stale figure on a slide is
   found by a room, while you are standing in front of it. This is the last
   moment the answer is still useful, so it goes here — once, on the way in.
   ══════════════════════════════════════════════════════════════════════════ */

.ofc-preflight-scrim {
  position: fixed;
  inset: 0;
  /* Above everything except the presenter it gates, which is 2147483000. */
  z-index: 2147482900;
  display: grid;
  place-items: center;
  background: rgba(0, 0, 0, .55);
  backdrop-filter: blur(3px);
}
.ofc-preflight {
  width: min(520px, 92%);
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius);
  background: var(--o-surface);
  color: var(--o-text);
  box-shadow: 0 24px 60px -16px rgba(0, 0, 0, .55);
  overflow: hidden;
}
.ofc-preflight header {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 14px 16px 12px;
  border-bottom: 1px solid var(--o-line);
  font-size: 13px;
  color: var(--ofc-del);
}
.ofc-preflight header b { font-weight: 700; }
.ofc-preflight-list {
  margin: 0;
  padding: 8px 16px;
  list-style: none;
  max-height: 40vh;
  overflow-y: auto;
  font-size: 12px;
}
.ofc-preflight-list li {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: baseline;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid color-mix(in oklab, var(--o-line) 60%, transparent);
}
.ofc-preflight-list li:last-child { border-bottom: none; }
.ofc-preflight-where { color: var(--o-muted); }
/* The old value struck through and the new one beside it: the reader has to be
   able to see the SIZE of the correction, not just that there is one. */
.ofc-preflight-was { text-decoration: line-through; color: var(--o-muted); font-variant-numeric: tabular-nums; }
.ofc-preflight-now { font-weight: 700; color: var(--ofc-ins); font-variant-numeric: tabular-nums; }
.ofc-preflight-more { color: var(--o-muted); display: block; }
.ofc-preflight footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 16px 14px;
  border-top: 1px solid var(--o-line);
}
.ofc-preflight footer button {
  padding: 7px 13px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-text);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.ofc-preflight footer button:hover { background: var(--o-soft); }
.ofc-preflight footer button.is-primary {
  border-color: transparent;
  background: var(--ofc-accent);
  color: #fff;
}
.ofc-preflight footer button.is-primary:hover { filter: brightness(1.07); }

/* ══════════════════════════════════════════════════════════════════════════
   Comments and version history — Part C of docs/office-decloning/PLAN.md.
   Tokens only: --o-* and --ofc-accent are defined in office2.css, which
   inherits them from app.css. Nothing here introduces a :root token.
   ══════════════════════════════════════════════════════════════════════════ */

/* The body and its highlight overlay share a stacking context. The wrapper
   takes the flex the body used to hold, so the page layout is unchanged. */
.ofc-doc-body-wrap,
.ofc-note-body-wrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Highlights are painted OVER the text, never into it — the body's DOM is a
   projection the model rewrites, so a decoration inside it would not survive
   the next keystroke. pointer-events stay off so the layer cannot eat a click
   meant for the caret. */
.ofc-comment-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}
.ofc-comment-hl {
  position: absolute;
  border-radius: 2px;
  background: color-mix(in oklab, var(--ofc-accent) 22%, transparent);
  box-shadow: inset 0 -1.5px 0 color-mix(in oklab, var(--ofc-accent) 60%, transparent);
}

/* ── Rail buttons shared by both panels ── */
.ofc-rail-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 12px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-surface);
  color: var(--o-text);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.ofc-rail-btn:hover:not(:disabled) { background: var(--o-soft); }
.ofc-rail-btn:disabled { opacity: .5; cursor: not-allowed; }
.ofc-rail-btn.is-primary {
  width: 100%;
  justify-content: center;
  border-color: color-mix(in oklab, var(--ofc-accent) 45%, transparent);
  background: color-mix(in oklab, var(--ofc-accent) 12%, transparent);
  color: var(--ofc-accent);
}
.ofc-rail-hint { margin: 8px 0 0; font-size: 11.5px; color: var(--o-muted); }
.ofc-rail-hint:empty { display: none; }

/* ── Comments ── */
.ofc-comments { display: flex; flex-direction: column; gap: 10px; }
.ofc-comments.is-resolved { opacity: .62; }

.ofc-comment {
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-card);
  padding: 10px 11px;
}
.ofc-comment > header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.ofc-comment-author { font-size: 12.5px; font-weight: 700; color: var(--o-text); }
.ofc-comment > header time { font-size: 11px; color: var(--o-muted); }

.ofc-comment blockquote {
  margin: 0 0 7px;
  padding: 4px 0 4px 9px;
  border-left: 2px solid color-mix(in oklab, var(--ofc-accent) 55%, transparent);
  font-size: 12px;
  color: var(--o-muted);
  cursor: pointer;
  /* One line, clipped — the quote identifies the anchor, it is not the text. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ofc-comment blockquote:hover { color: var(--o-text); }

.ofc-comment-body { margin: 0; font-size: 13px; line-height: 1.5; color: var(--o-text); white-space: pre-wrap; }
.ofc-comment > footer { display: flex; gap: 6px; margin-top: 9px; }
.ofc-comment > footer button {
  border: 0;
  background: transparent;
  padding: 3px 6px;
  border-radius: 6px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--o-muted);
  cursor: pointer;
}
.ofc-comment > footer button:hover { background: var(--o-soft); color: var(--o-text); }

/* An anchor whose text was deleted. Flagged rather than hidden: the comment is
   still someone's remark, it just no longer has anything to point at. */
.ofc-comment.is-orphan { border-style: dashed; }
.ofc-comment.is-orphan blockquote { border-left-color: var(--o-line); text-decoration: line-through; cursor: default; }
.ofc-comment-orphan { margin: 0 0 7px; font-size: 11.5px; color: var(--o-muted); font-style: italic; }

.ofc-comment-draft { margin-top: 9px; }
.ofc-comment-draft textarea {
  width: 100%;
  padding: 8px 10px;
  resize: vertical;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-surface);
  color: var(--o-text);
  font: inherit;
  font-size: 12.5px;
  outline: none;
}
.ofc-comment-draft textarea:focus { border-color: color-mix(in oklab, var(--ofc-accent) 50%, transparent); }
.ofc-comment-draft-actions { display: flex; justify-content: flex-end; gap: 7px; margin-top: 7px; }

/* ── Version history ── */
.ofc-versions { display: flex; flex-direction: column; gap: 7px; }
.ofc-version {
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-card);
  overflow: hidden;
}
.ofc-version > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 11px;
  cursor: pointer;
}
.ofc-version > header:hover { background: var(--o-soft); }
.ofc-version > header svg { color: var(--o-muted); transition: transform .16s ease; flex: none; }
.ofc-version.is-open > header svg { transform: rotate(180deg); }
.ofc-version-when { font-size: 12.5px; font-weight: 600; color: var(--o-text); }
.ofc-version-sum { font-size: 11.5px; color: var(--o-muted); margin-top: 2px; }
.ofc-version-body { padding: 0 11px 11px; }

.ofc-diff { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.ofc-diff-row {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  padding: 5px 7px;
  border-radius: 6px;
  font-size: 12px;
  line-height: 1.45;
}
.ofc-diff-row > span { flex: none; font-weight: 700; opacity: .75; }
.ofc-diff-row p { margin: 0; min-width: 0; overflow-wrap: anywhere; }
/* Green/red would be the obvious choice and the wrong one — it is unreadable
   for the commonest form of colour blindness with no other cue. The sign in the
   gutter carries the meaning; the tint only groups the rows. */
.ofc-diff-row.is-add { background: color-mix(in oklab, var(--ofc-accent) 12%, transparent); }
.ofc-diff-row.is-del { background: var(--o-soft); }
.ofc-diff-row.is-del p { text-decoration: line-through; color: var(--o-muted); }
.ofc-diff-row.is-mod { background: var(--o-soft); }
.ofc-diff-row ins { text-decoration: none; background: color-mix(in oklab, var(--ofc-accent) 20%, transparent); border-radius: 2px; }
.ofc-diff-row del { color: var(--o-muted); }

/* ══════════════════════════════════════════════════════════════════════════
   Track changes — Part C3.
   ══════════════════════════════════════════════════════════════════════════ */

/* Tracked text in the document itself.

   This USED to be an accent-coloured, 2px-underlined run — which is character
   for character how the app draws a hyperlink, so every insertion read as a
   link and every link read as somebody's pending edit. It was the single most
   misleading thing in the editor.

   An insertion is now a tinted wash with a DOTTED baseline: still obviously
   added, impossible to confuse with a link, and it leaves the text its own
   colour so a tracked run that is also a real link still looks like one. A
   deletion keeps the strike, because a strike is unambiguous.

   Shape carries the meaning in both cases — dotted versus struck — and hue is
   only ever the second cue, for anyone who cannot separate the two. */
.ofc-model-body ins[data-t] {
  text-decoration: none;
  padding-bottom: 1px;
  border-radius: 2px;
  background: color-mix(in oklab, var(--ofc-ins) 14%, transparent);
  box-shadow: inset 0 -2px 0 -1px color-mix(in oklab, var(--ofc-ins) 65%, transparent);
  /* A dotted rule rather than a solid one: solid is the link affordance. */
  border-bottom: 1.5px dotted color-mix(in oklab, var(--ofc-ins) 75%, transparent);
}
.ofc-model-body del[data-t] {
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  text-decoration-color: color-mix(in oklab, var(--ofc-del) 85%, transparent);
  border-radius: 2px;
  color: color-mix(in oklab, var(--o-text) 52%, transparent);
  background: color-mix(in oklab, var(--ofc-del) 9%, transparent);
}

/* ── The Changes panel ── */
.ofc-track-toggle {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  font-weight: 600;
  color: var(--o-text);
  cursor: pointer;
}
.ofc-track-toggle input { accent-color: var(--ofc-accent); width: 15px; height: 15px; cursor: pointer; }
.ofc-track-bulk { display: flex; gap: 7px; margin-bottom: 10px; }
.ofc-track-bulk .ofc-rail-btn { flex: 1 1 0; justify-content: center; }

.ofc-changes { display: flex; flex-direction: column; gap: 8px; }
.ofc-change {
  border: 1px solid var(--o-line);
  border-left-width: 3px;
  border-radius: var(--o-radius-sm);
  background: var(--o-card);
  padding: 9px 11px;
}
.ofc-change.is-ins { border-left-color: var(--ofc-accent); }
.ofc-change.is-del { border-left-color: var(--o-muted); }
/* A grid has a third case prose does not: a cell that was replaced rather than
   filled in or cleared. It gets its own edge because "3 → 5" is a different
   decision from "5 appeared". */
.ofc-change.is-edit { border-left-color: var(--ofc-ins); }
.ofc-change > header {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-bottom: 5px;
}
.ofc-change-kind {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--o-muted);
}
.ofc-change-text {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--o-text);
  overflow-wrap: anywhere;
}
.ofc-change.is-del .ofc-change-text { text-decoration: line-through; color: var(--o-muted); }
.ofc-change > footer { display: flex; gap: 6px; margin-top: 8px; }
.ofc-change > footer button {
  border: 0;
  background: transparent;
  padding: 3px 6px;
  border-radius: 6px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--o-muted);
  cursor: pointer;
}
.ofc-change > footer button:hover { background: var(--o-soft); color: var(--o-text); }
.ofc-change > footer [data-accept]:hover { color: var(--ofc-accent); }

/* ══════════════════════════════════════════════════════════════════════════
   Notebook attachments — Part F1.
   An attachment is a row in the unified file store rendered inline, so this
   styles a reference to a file rather than an embedded copy of one.
   ══════════════════════════════════════════════════════════════════════════ */
.nb-attach { margin: 6px 0; }

.nb-attach-pick {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 13px;
  border: 1px dashed var(--o-line);
  border-radius: var(--o-radius-sm);
  background: transparent;
  color: var(--o-muted);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .14s ease, color .14s ease, background .14s ease;
}
.nb-attach-pick:hover {
  border-color: color-mix(in oklab, var(--ofc-accent) 45%, transparent);
  background: color-mix(in oklab, var(--ofc-accent) 7%, transparent);
  color: var(--ofc-accent);
}
.nb-attach-pick svg { width: 17px; height: 17px; flex: 0 0 auto; }

.nb-attach-card {
  display: flex;
  align-items: center;
  gap: 11px;
  max-width: 420px;
  padding: 9px 12px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-card);
  color: inherit;
  text-decoration: none;
  transition: border-color .14s ease, background .14s ease;
}
.nb-attach-card:hover {
  border-color: color-mix(in oklab, var(--ofc-accent) 40%, transparent);
  background: var(--o-soft);
}
.nb-attach-ico {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border-radius: 8px;
  color: var(--ofc-accent);
  background: color-mix(in oklab, var(--ofc-accent) 12%, transparent);
}
.nb-attach-ico svg { width: 17px; height: 17px; }
.nb-attach-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.nb-attach-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--o-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nb-attach-meta { font-size: 11.5px; color: var(--o-muted); }

.nb-attach-thumb {
  display: block;
  max-width: 420px;
  max-height: 300px;
  margin-bottom: 7px;
  border-radius: var(--o-radius-sm);
  border: 1px solid var(--o-line);
}

.nb-attach-progress {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 9px 12px;
  border: 1px solid var(--o-line);
  border-radius: var(--o-radius-sm);
  background: var(--o-card);
  max-width: 420px;
}
.nb-attach.is-busy .nb-attach-meta { color: var(--ofc-accent); }
.nb-attach.is-error .nb-attach-progress { border-color: color-mix(in oklab, #ef4444 45%, transparent); }
.nb-attach.is-error .nb-attach-meta { color: #ef4444; }
.nb-attach-retry {
  margin-top: 6px;
  border: 0;
  background: transparent;
  padding: 3px 6px;
  border-radius: 6px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ofc-accent);
  cursor: pointer;
}
.nb-attach-retry:hover { background: var(--o-soft); }

/* ══════════════════════════════════════════════════════════════════════════
   Collaborators' carets — Part C4.
   Drawn in an overlay above the paper, never inside the editable body: the
   body's DOM is a projection the model rewrites on every edit, so anything
   written into it is erased by the next render and read back as document
   text by the IME reconciliation path.
   ══════════════════════════════════════════════════════════════════════════ */
.ofc-peer-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 3;
}

.ofc-peer-caret {
  position: absolute;
  width: 2px;
  background: var(--peer, var(--ofc-accent));
  border-radius: 1px;
  transition: left .12s ease, top .12s ease;
}

/* The name rides above the caret and stays out of the way of the text. It is
   only legible on hover-free surfaces at rest, which is the intent: a label
   that competes with the writing is worse than one you have to look for. */
.ofc-peer-name {
  position: absolute;
  left: -1px;
  bottom: 100%;
  margin-bottom: 2px;
  padding: 1px 5px;
  border-radius: 4px 4px 4px 0;
  background: var(--peer, var(--ofc-accent));
  color: #fff;
  font-size: 10.5px;
  font-weight: 650;
  line-height: 1.5;
  white-space: nowrap;
  opacity: .92;
}
