/* Object/traits world — the generic renderer (Portal.object) + this module's
   shell. Intentionally plain for now; views grow as traits do. */

.obj-shell { display: flex; flex-direction: column; gap: 0.8rem; flex: 1; min-height: 0; }
/* A lone pane wears no chrome: a title bar only earns its space once there are panes to tell
   apart (and to click between). So a single card — or just the bucket — is full-bleed, exactly
   as the grid was before there were panes at all. Pure CSS; the layout never has to know. */
/* A LONE PANE IS THE PAGE — no frame around the whole screen, no title bar stating what the one
   visible thing already is, and nothing clipped (the console floats past its edges when the
   results are short). Framing starts when there is something to tell apart. */


/* #obj-root is the positioning context for the layer, and holds the workspace beneath it. */
.obj-shell { position: relative; }

/* The IDE cell (`layout = {cols: {bucket editor}}`): the workspace as t-exp text. Monospace,
   fills its pane, no resize grabber — the pane's own split decides its size, which is the point of
   having a layout language at all.
   Written as `.po-text.po-ws-editor` on purpose: .po-text sets min-height:12rem and
   resize:vertical LATER in this file, and at equal specificity later wins — a plain .po-ws-editor
   rule would silently lose both, leaving an editor that cannot shrink into its pane. */
.po-text.po-ws-editor {
  flex: 1; min-height: 0; resize: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem; line-height: 1.5;
}
.po-text.po-ws-editor.po-invalid { border-color: #dc2626; }
.po-ws-msg { flex: none; color: #c0392b; font-size: 0.78rem; padding: 0.3rem 0 0; }
.po-ws-msg:empty { display: none; }

/* Which pane the keyboard acts on: an accent edge, NOT a border — a border would put back the
   frame that made a tiled workspace look like stacked modals, and it would move the content by a
   pixel as focus travels. Silent when a pane has no siblings: "the focused one" is not information
   when there is nothing to contrast it with. */
.po-pane.is-active { border-color: var(--accent); box-shadow: inset 2px 0 0 var(--accent); }

.obj-invite {
  display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
  padding: 0.5rem 0.7rem; border: 1px solid var(--accent); border-radius: 7px; background: var(--card-bg);
}
.obj-open { min-height: 0; }
.obj-loading, .obj-empty { color: var(--muted); }
.obj-error { color: #c0392b; }

/* Cumulative trait cue: a CONSTANT-WIDTH bar split into one coloured segment per trait
   (alphabetical) — composable and never-growing (two traits = halves, four = quarters),
   and out of the text line so it doesn't disturb reading. Hover a segment for the name.
   Infinitely extensible: a new trait = a new colour, no code. */
.po-traitbar {
  display: flex;
  width: 100%;
  border-radius: 3px;
  overflow: hidden;
  flex: none;
}
.po-tseg {
  flex: 1;
  min-width: 0; /* let the label ellipsis inside a narrow segment */
  background: var(--tc, var(--muted));
  color: #fff;
  font-size: 0.62rem;
  line-height: 1.5;
  text-align: center;
  padding: 0.05rem 0.25rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); /* legible on any segment colour */
}
.po-tseg:hover { filter: brightness(1.15); cursor: pointer; } /* a segment opens its trait's view */
.po-tseg[data-trait='note'] { --tc: #94a3b8; }
.po-tseg[data-trait='collection'] { --tc: #3b82f6; }
.po-tseg[data-trait='user'] { --tc: #22c55e; }
.po-tseg[data-trait='group'] { --tc: #14b8a6; }
.po-tseg[data-trait='organization'] { --tc: #a855f7; }
.po-tseg[data-trait='ticket'] { --tc: #ef4444; }
.po-tseg[data-trait='shareable'] { --tc: #eab308; }
/* The bar spans the full object width — bottom of a tile, bottom of a list row. */
.po-tile .po-traitbar { margin-top: 0.4rem; }
.po-list-row { display: flex; flex-direction: column; align-items: stretch; gap: 0.35rem; }

/* Portal.object renderer */
.po { display: flex; flex-direction: column; gap: 0.5rem; }
.po-bar { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
.po-views { display: flex; gap: 0.3rem; flex-wrap: wrap; }
.po-view-name { color: var(--muted); font-size: 0.85rem; font-weight: 600; user-select: none;
  background: none; border: none; padding: 0; cursor: pointer; font-family: inherit; }
.po-view-name:hover { color: var(--fg); }
/* View bar: a segmented control (analog of the trait colour bar) showing every view; the active
   segment is lit. Click a segment or Alt+V to cycle. */
.po-viewbar { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.po-vseg { border: none; border-right: 1px solid var(--border); background: var(--card-bg); color: var(--muted);
  font: inherit; font-size: 0.76rem; padding: 0.2rem 0.6rem; cursor: pointer; }
.po-vseg:last-child { border-right: none; }
.po-vseg:hover { color: var(--fg); }
.po-vseg-on { background: var(--accent); color: #fff; }
.po-actions { display: flex; gap: 0.3rem; flex-wrap: wrap; }
.po-action-danger { color: #c0392b; }
/* Message buttons (trait-card methods): greyed = the can* guard says not from this state. */
.po-method:disabled { opacity: 0.4; cursor: default; }
.po-import { display: flex; flex-direction: column; gap: 0.5rem; min-width: min(70vw, 34rem); }
.po-import .po-text { min-height: 16rem; }
.po-import-file { font-size: 0.8rem; color: var(--muted); }
.po-form { display: flex; flex-direction: column; gap: 0.6rem; min-width: min(80vw, 22rem); }
.po-form-row { display: flex; flex-direction: column; gap: 0.2rem; }
.po-form-lbl {
  font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted);
}
.po-loading { color: var(--muted); font-size: 0.85rem; }
.po-error { color: #c0392b; font-size: 0.85rem; }

.po-fields { display: flex; flex-direction: column; }
.po-field {
  display: grid;
  grid-template-columns: 8rem 1fr;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.3rem 0.4rem;
  border-bottom: 1px solid var(--border);
}
.po-label {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  word-break: break-all;
}
.po-val { font-size: 0.85rem; white-space: pre-wrap; word-break: break-word; }
/* _shared faker rows: derived from the ACL (not editable data) — muted + italic to read as such. */
.po-field-shared { opacity: 0.75; }
.po-field-shared .po-val { font-style: italic; }
.po-input, .po-text {
  width: 100%;
  padding: 0.3rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 0.85rem;
}
.po-text { min-height: 12rem; line-height: 1.6; resize: vertical; }
.po-input:focus, .po-text:focus { outline: none; border-color: var(--accent); }
/* Auto-save feedback: editors save themselves, so instead of a Save button they show a small
   status line, and go red while their content is invalid / was rejected (and held, not saved). */
.po-editor-status { font-size: 0.78rem; min-height: 1.1em; margin: 0.25rem 0 0.1rem; color: var(--muted); }
/* Read-only tail of the source view: the same document continuing — same font and highlight,
   just a slightly darker background. Not editable; selectable for copying. */
.po-json-ghost {
  margin: 0; padding: 0.5rem 0.65rem;
  font: 0.82rem/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: rgba(0, 0, 0, 0.07);
  border-radius: 0 0 6px 6px;
  white-space: pre-wrap; user-select: text;
}
.po-draft-drop { margin-top: 0.35rem; }
/* Message buttons (trait methods) read differently from plain actions: accent-tinted. */
.po-method { border-color: var(--accent); color: var(--accent); }
.po-method:not(:disabled):hover { background: var(--accent); color: #fff; }
.po-status-err { color: #dc2626; }
.po-status-mod { color: #d97706; } /* amber: an unsaved (dirty) edit, distinct from error-red */
.po-status-ok { color: #16a34a; }
.po-invalid .po-json-input, .po-invalid.po-json { outline: 1px solid #dc2626; outline-offset: -1px; }
.po-input.po-invalid, .po-text.po-invalid { border-color: #dc2626; }

/* Syntax-highlighted JSON editor (buildSource): a transparent <textarea> over a highlighted
   <pre> that mirrors it. Both MUST share the exact box model so the tokens sit under the caret. */
.po-json { position: relative; }
.po-json-hl, .po-json-input {
  margin: 0; box-sizing: border-box;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.82rem; line-height: 1.5;
  padding: 0.6rem 0.7rem; border: 1px solid var(--border); border-radius: 6px;
  white-space: pre; overflow: auto; tab-size: 2;
}
.po-json-hl { position: absolute; inset: 0; pointer-events: none; color: var(--fg); background: var(--card-bg); }
.po-json-input {
  position: relative; display: block; width: 100%; min-height: 14rem; resize: vertical;
  background: transparent; color: transparent; caret-color: var(--fg);
}
.po-json-input:focus { outline: none; border-color: var(--accent); }
.tok-key { color: #2563eb; }
.tok-str { color: #16a34a; }
.tok-num { color: #d97706; }
.tok-kw { color: #a855f7; }
.tok-punct { color: var(--muted); }

/* Generic syntax-highlight overlay (text.edit with opts.highlight): a transparent <textarea>
   over a highlighted <pre> mirror. Prose-oriented (pre-wrap), unlike the code-oriented JSON one;
   both layers MUST share the exact box model so the tokens sit under the caret. */
.po-hl { position: relative; }
.po-hl-pre, .po-hl-input {
  margin: 0; box-sizing: border-box; font: inherit; font-size: 0.85rem; line-height: 1.6;
  padding: 0.3rem 0.45rem; border: 1px solid var(--border); border-radius: 5px;
  white-space: pre-wrap; overflow-wrap: break-word; overflow: auto;
}
.po-hl-pre { position: absolute; inset: 0; pointer-events: none; color: var(--fg); background: var(--bg); }
/* The mirror's text lives in a <code>, which the UA styles monospace — while the textarea above it
   uses the inherited proportional font. Different glyph widths drift the tokens away from the caret
   and wrap the two layers differently. (The JSON overlay escapes this only because it forces
   monospace on both layers.) Inheriting here is what keeps the caret over its own text. */
.po-hl-pre code { font: inherit; }
.po-hl-input {
  position: relative; display: block; width: 100%; min-height: 12rem; resize: vertical;
  background: transparent; color: transparent; caret-color: var(--fg);
}
.po-hl-input:focus { outline: none; border-color: var(--accent); }
.po-plain { white-space: pre-wrap; overflow-wrap: break-word; }
/* markdown SOURCE tokens (editor overlay — a cue, not a render) */
.mdh-head { color: #2563eb; font-weight: 600; }
.mdh-strong { color: #b45309; font-weight: 600; }
.mdh-em { color: #b45309; font-style: italic; }
.mdh-code { color: #16a34a; }
.mdh-quote { color: var(--muted); font-style: italic; }
.mdh-mark { color: #a855f7; }
.mdh-link { color: #0891b2; }

/* Children (collection items) — inline-expand tree. */
.po-children { margin-top: 0.5rem; }
.po-children-head {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  margin-bottom: 0.3rem;
}
.po-child { border-top: 1px solid var(--border); }
.po-child:first-of-type { border-top: none; }
.po-child-head { display: flex; align-items: baseline; gap: 0.4rem; padding: 0.3rem 0; cursor: pointer; }
.po-disclose { flex: none; width: 1.1rem; border: none; background: none; color: var(--muted); cursor: pointer; font-size: 0.8rem; padding: 0; }
.po-child-label { font-size: 0.88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.po-child-body { margin-left: 1.1rem; padding-left: 0.5rem; border-left: 2px solid var(--border); }

/* Collection search — live filter above Grid/Table (Portal.search semantics).
   The search box is a fixed header; the RESULTS are the only scroll region beneath it,
   so the console stays visible while arrow-navigating a long list without ever covering
   content. The flex chain (.po-host → .po → .po-view → .po-search → results) is bounded
   inside a dialog and inert in the page-scrolling root. */
/* The search console sits flush at the top — drop the dialog body's top padding above it
   (space-efficient, and the box reads as a real header). Side/bottom padding stay. */
.dialog-body:has(.po-search), .po-pane-body:has(.po-search) { padding-top: 0; }
.po-search { position: relative; display: flex; flex-direction: column; gap: 0.6rem; flex: 1; min-height: 0; }
.po-search-box { display: flex; align-items: center; gap: 0.4rem; flex: none; }
.po-search-results { flex: 1; min-height: 0; overflow-y: auto; }
/* The command line + chips ride together. In a dialog: a normal in-flow column (unchanged look). */
.po-cmdbar { display: flex; flex-direction: column; gap: 0.6rem; }
.po-host { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.po-host > .po { flex: 1; min-height: 0; }
/* The view body fills + scrolls (long card/markdown). A COLLECTION view instead lets its
   own results scroll (search box stays put), so its .po-view is a non-scrolling column. */
.po-host .po-view { flex: 1; min-height: 0; overflow-y: auto; }
.po-host .po-view:has(.po-search) { display: flex; flex-direction: column; overflow: hidden; }
.po-search-field { position: relative; flex: 1; }
.po-search-add {
  flex: none;
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--accent);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}
.po-search-add:hover { border-color: var(--accent); }
.po-sort, .po-sort-dir {
  flex: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--muted);
  font: inherit;
  font-size: 0.8rem;
  padding: 0.35rem 0.4rem;
  cursor: pointer;
}
.po-sort-dir { width: 2rem; text-align: center; }
.po-sort:hover, .po-sort-dir:hover { border-color: var(--accent); }
.po-search-input {
  width: 100%;
  padding: 0.4rem 1.9rem 0.4rem 0.55rem; /* room on the right for the × */
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 0.85rem;
}
/* No accent highlight on the (always-focused) console — the text cursor shows focus. */
.po-search-input:focus { outline: none; }
.po-search-input::placeholder { color: var(--muted); }
/* The command bar is a drop-down OVERLAY on its OWN pane — shown in COMMAND mode, slid up out of
   view in NAV (Tab toggles). It floats rather than sitting in flow so the results underneath use
   the pane's full height, which is the behaviour the grid was tuned for.
   It is anchored to .po-pane (position:relative), NOT to the shell: a console belongs to one
   surface. It used to be lifted into the shell header, which made it global — and a global console
   has to be reasoned about against every other surface, which is how the tiling attempt collapsed
   into interlocking visibility rules. Dialog consoles keep their in-flow bar (unscoped below). */
.po-pane .po-cmdbar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  /* see the flush/full-bleed rules below for the first pane */
  background: var(--header-bg); padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--header-border);
  /* A hairline, not a lift. The heavy drop shadow made this read as a floating panel, which is why
     panes without one looked like a different kind of surface — it, not the panes, was the odd one
     out. Enough shadow to separate it from the rows scrolling under it, and no more. */
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  transition: transform 0.15s ease, opacity 0.15s ease;
}

/* The command bar spans its own pane, edge to edge. It used to need negative offsets to cancel
   #content's gutter; #content has none now, so left/right:0 (from .po-pane .po-cmdbar above) is
   already the full width and there is nothing left to correct for. */
/* Hide the native type=search clear — we render our own × (only when there's text). */
.po-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.po-search-clear {
  position: absolute;
  right: 0.35rem;
  top: 50%;
  transform: translateY(-50%);
  border: none;
  background: none;
  color: var(--muted);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.25rem;
}
.po-search-clear:hover { color: var(--fg); }
.po-search-pin { flex: none; }
/* Pinned filter chips (the AND-ed filter stack), shown above the results. */
.po-filter-stack { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.4rem 0 0.1rem; }
.po-chip { display: inline-flex; align-items: center; max-width: 100%; background: var(--accent); color: #fff; border-radius: 999px; font-size: 0.78rem; }
/* A long filter must not blow out the row: the text ellipsizes (full text on hover), the × stays. */
.po-chip-text { border: none; background: none; color: inherit; font: inherit; cursor: pointer; padding: 0.15rem 0.2rem 0.15rem 0.6rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.po-chip-x { border: none; background: none; color: inherit; cursor: pointer; font-size: 1rem; line-height: 1; padding: 0 0.5rem 0 0.25rem; opacity: 0.85; flex: none; }
.po-chip-x:hover { opacity: 1; }
.po-chip-clear { background: none; border: 1px solid var(--border); color: var(--muted); padding: 0.15rem 0.6rem; cursor: pointer; }
.po-chip-clear:hover { border-color: var(--accent); color: var(--fg); }
/* Chip provenance: mount-contributed chips are colored by their mount (user chips keep --accent). */
.po-chip-m-app { background: #7c5cbf; } /* the app lens */
.po-chip-m-org { background: #2e7d5b; } /* the org place */
/* The chip loaded into the search line for editing (↑/↓ recall): dimmed + dashed to read as
   "lifted, being edited in the prompt" — it's excluded from the filter until you commit (Enter). */
.po-chip-editing { opacity: 0.5; outline: 1px dashed currentColor; outline-offset: -1px; }
/* A malformed sort chip: visibly broken and ignored (the validation layer for `sort = …`). */
.po-chip-invalid { background: var(--bg); color: #c0392b; border: 1px solid #c0392b; }
mark { background: var(--accent); color: #fff; border-radius: 2px; padding: 0 1px; }

/* Collection Grid view — board of clickable tiles. */
.po-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.6rem;
}
.po-tile {
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 0.6rem 0.7rem;
  background: var(--card-bg); /* a distinct surface vs the page/search bg; the gap separates neighbours */
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.12s, box-shadow 0.12s;
}
/* Zebra for the grid too. On a wrapping grid this stripes by index, so it reads as a checkerboard
   rather than as rows — which is the point: it breaks a field of identical cards into something the
   eye can count along.
   The step is a MIX TOWARDS THE TEXT COLOUR rather than the page colour: --bg was too close to
   --card-bg in this theme to separate one tile from the next, and mixing towards --fg moves in the
   contrasting direction whichever theme is on. Background only, so the hover lift and the cursor
   tint still land on top. */
.po-tile { border-color: color-mix(in srgb, var(--fg) 22%, transparent); }
.po-tile:nth-child(even) { background: color-mix(in srgb, var(--fg) 10%, var(--card-bg)); }
.po-tile:hover { border-color: var(--accent); box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08); }
.po-list { display: flex; flex-direction: column; gap: 0.3rem; }
.po-queue { display: flex; flex-direction: column; gap: 0; }
.po-queue-row {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  column-gap: 0.7rem; align-items: baseline;
  border-bottom: 1px solid var(--border); padding: 0.5rem 0.7rem;
  cursor: pointer; font-size: 0.9rem; transition: background 0.1s;
}
.po-queue-row:hover { background: var(--hover-bg, var(--bg)); }
.po-queue-row[data-status="opened"]   { box-shadow: inset 3px 0 0 #2196f3; }
.po-queue-row[data-status="assigned"] { box-shadow: inset 3px 0 0 #ffc107; }
.po-queue-row[data-status="closed"]   { box-shadow: inset 3px 0 0 #2ea043; }
.po-queue-row[data-status="canceled"] { box-shadow: inset 3px 0 0 rgba(248,81,73,0.7); }
/* ZEBRA — measured, not guessed. Every stripe in this file used to alternate --card-bg / --bg, and
   in the DEFAULT theme those are #ffffff and #f7fafc: a 2% difference, invisible. It only ever read
   in Solarized, where the pair happens to be yellow against white. So stripes mix towards --fg
   instead — a reference that is contrasting BY DEFINITION in every theme, because it is the text
   colour. Queue rows sit on the layer with no surface of their own, so the stripe is translucent. */
.po-queue-row:nth-child(even) { background: color-mix(in srgb, var(--fg) 10%, transparent); }
.po-queue-col-sector  { border-bottom: 2px solid #14b8a6; }
.po-queue-col-contact { border-bottom: 2px solid #eab308; }
.po-queue-meta { grid-column: 1; grid-row: 1 / 3; display: flex; flex-direction: column; align-items: flex-end; gap: 0.2rem; min-width: 6rem; }
.po-queue-title { grid-column: 2; grid-row: 1; font-weight: 600; }
.po-queue-excerpt { grid-column: 2; grid-row: 2; font-size: 0.8rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.po-queue-sector  { font-size: 0.75rem; color: var(--accent); font-weight: 600; }
.po-queue-contact { font-size: 0.75rem; color: var(--muted); }
.po-queue.po-queue-app { display: grid; }
.po-queue-inline { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; column-gap: 0.5rem; }
.po-queue-inline > .po-traitbar { display: none; }
.po-queue-body { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 0.85rem; border-left: 1px solid var(--border); padding-left: 0.5rem; }
.po-queue-field-ref { background: none; border: none; padding: 0.1rem 0.35rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; color: var(--accent); cursor: pointer; }
.po-queue-field-ref:hover { background: var(--hover-bg, #f0f0f0); }
.po-list-row {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.45rem 0.7rem;
  background: var(--card-bg); /* a distinct surface vs the page/search bg; the gap separates neighbours */
  cursor: pointer;
  font-size: 0.9rem;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.po-list-row:hover { border-color: var(--accent); box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08); }
/* Zebra: see the queue's note above. A list row IS a card, so its stripe mixes INTO --card-bg and
   stays opaque, rather than going translucent and losing the surface. */
.po-list-row:nth-child(even) { background: color-mix(in srgb, var(--fg) 10%, var(--card-bg)); }
.po-img { max-width: 100%; max-height: 16rem; border-radius: 6px; display: block; }
.po-tile-title { font-weight: 600; font-size: 0.9rem; margin-bottom: 0.35rem; }
.po-tile-body {
  font-size: 0.82rem;
  color: var(--muted);
  max-height: 7.5rem;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 70%, transparent);
  mask-image: linear-gradient(180deg, #000 70%, transparent);
}

/* Collection Table view — rows × union of child fields. */
.po-rows { border-collapse: collapse; width: 100%; font-size: 0.85rem; }
.po-rows th {
  text-align: left;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  padding: 0.35rem 0.6rem;
  border-bottom: 2px solid var(--border);
  white-space: nowrap;
}
.po-rows td {
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.po-rows tbody tr { cursor: pointer; }
.po-rows tbody tr:hover { background: var(--bg); }
.po-row-label { font-weight: 600; }

/* Organization trait — Overview view. */
.po-org { display: flex; flex-direction: column; gap: 0.5rem; }
.po-org-row { display: flex; gap: 0.6rem; align-items: baseline; }
.po-org-invite { margin-top: 1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.po-org-invites { display: flex; flex-direction: column; gap: 0.3rem; }
.po-org-k { min-width: 7rem; opacity: 0.6; font-size: 0.85rem; }
.po-org-v { font-weight: 600; }
/* Org-card group rows: the name is a button (expand members in place); the member line is muted. */
.po-org-grp { background: none; border: none; padding: 0; cursor: pointer; text-align: left; color: var(--fg); font: inherit; }
.po-org-grp:hover { text-decoration: underline; }
.po-org-grp-members { font-size: 0.8rem; color: var(--muted); padding: 0.1rem 0 0.3rem 7rem; }

/* Views-by-role cell: picked view chips + an add-picker, wrapping. */
.po-views-cell { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; flex: 1; min-width: 0; }
.po-views-cell .po-picker { flex: 0 0 auto; width: 9rem; }
.po-views-all { font-size: 0.8rem; color: var(--muted); font-style: italic; }

/* Collapsible, filterable section (Portal people lists: invites/members/managers). The header
   is a full-width button; a chevron shows open/closed; the body (filter box + list) hides when
   collapsed. A reusable pattern — any list view can adopt it. */
.po-section { border-top: 1px solid var(--border); margin-top: 0.6rem; }
.po-section-head {
  display: flex; align-items: baseline; gap: 0.4rem; width: 100%;
  background: none; border: none; padding: 0.5rem 0.1rem; cursor: pointer;
  font: inherit; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); text-align: left;
}
.po-section-head::before { content: '\25B8'; flex: none; transition: transform 0.12s; }
.po-section.is-open .po-section-head::before { transform: rotate(90deg); }
.po-section-head:hover { color: var(--fg); }
.po-section-count { font-weight: 400; text-transform: none; letter-spacing: 0; }
.po-section-body { display: none; flex-direction: column; gap: 0.3rem; padding-bottom: 0.4rem; }
.po-section.is-open .po-section-body { display: flex; }
.po-section-body .po-search-input { margin-bottom: 0.15rem; }

/* Shared trait — Members/Share view. */
.po-share { display: flex; flex-direction: column; gap: 0.8rem; }
.po-members { display: flex; flex-direction: column; }
.po-member {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.2rem;
  border-bottom: 1px solid var(--border);
}
.po-member-who { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.po-member-name { font-size: 0.88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.po-member-email { font-size: 0.72rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The role cell is now a WRAPPER around the enum primitive's control, not the control itself —
   so the box styling moves inward, or the span would draw a second border around the select. */
.po-member-role { display: inline-flex; flex: none; }
.po-member-role .po-input {
  width: auto;
  padding: 0.2rem 0.4rem;
  font-size: 0.8rem;
}
.po-member-rolelabel { font-size: 0.78rem; color: var(--muted); text-transform: capitalize; }
.po-member-remove { color: #c0392b; }
/* Section header within the group view (Sub-groups / Members). */
.po-members-head { font-weight: 600; font-size: 0.78rem; color: var(--muted); margin: 0.6rem 0 0.15rem; text-transform: uppercase; letter-spacing: 0.03em; }
/* A sub-group name rendered as a button that drills into the tree. */
.po-member-link { flex: 1; min-width: 0; text-align: left; background: none; border: none; padding: 0; font: inherit; color: var(--accent); cursor: pointer; }
.po-member-link:hover { text-decoration: underline; }
.po-picker { position: relative; display: flex; flex-direction: column; gap: 0.3rem; }
/* Results float as an overlay dropdown (below the input) so opening one in a field row never
   shoves the surrounding layout. The input's .po-picker keeps position:relative as the anchor. */
.po-picker-results {
  display: flex; flex-direction: column; gap: 0.2rem;
  position: absolute; top: 100%; left: 0; right: 0; z-index: 30;
  max-height: 16rem; overflow-y: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 0.25rem; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
}
.po-picker-results[hidden] { display: none; }
.po-cand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.05rem;
  text-align: left;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  cursor: pointer;
  margin-top: 0.2rem;
}
.po-cand:hover { border-color: var(--accent); }

/* Ticket view (the `ticket` trait) — status, assignment, timeline. */
.po-ticket { display: flex; flex-direction: column; gap: 0.7rem; }
.po-ticket-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.po-ticket-lbl {
  font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); width: 4.5rem; flex: none;
}
.po-ticket-agent { font-size: 0.88rem; }
.po-ticket-note { font-size: 0.82rem; font-style: italic; color: var(--muted); }
.po-pill {
  font-size: 0.75rem; font-weight: 600; padding: 0.1rem 0.5rem; border-radius: 999px;
  text-transform: capitalize; border: 1px solid var(--border); color: var(--fg);
}
.po-pill-opened { background: rgba(33, 150, 243, 0.18); }
.po-pill-assigned { background: rgba(255, 193, 7, 0.22); }
.po-pill-closed { background: rgba(46, 160, 67, 0.20); }
.po-pill-canceled { background: rgba(248, 81, 73, 0.18); color: var(--muted); }
/* Search-hit meta: a status pill + the created date, under the title (grid tile / list row). */
/* Mount ON the card: a mountable app offers its verb without opening the card first. Violet to
   match the app mount chip in the nav (.nav-mount-app), so the affordance and its result agree. */
.po-tile-mount {
  display: inline-block;
  margin: 0.3rem 0 0.1rem;
  padding: 0.15rem 0.5rem;
  border: 1px solid #7c5cbf;
  border-radius: 999px;
  background: transparent;
  color: #7c5cbf;
  font-size: 0.72rem;
  font-weight: 600;
  cursor: pointer;
}
.po-tile-mount:hover { background: #7c5cbf; color: #fff; }
.po-tile-meta { display: flex; align-items: center; gap: 0.5rem; margin: 0.25rem 0; flex-wrap: wrap; }
.po-tile-meta .po-pill { font-size: 0.68rem; padding: 0.05rem 0.4rem; }
.po-tile-date { font-size: 0.72rem; color: var(--muted); }
.po-timeline { margin-top: 0.3rem; }
.po-tl-list { display: flex; flex-direction: column; }
.po-tl-item {
  display: flex; gap: 0.6rem; align-items: baseline;
  padding: 0.3rem 0.2rem; border-bottom: 1px solid var(--border); font-size: 0.85rem;
}
.po-tl-when { font-size: 0.72rem; color: var(--muted); white-space: nowrap; flex: none; min-width: 9rem; }
.po-tl-what { word-break: break-word; flex: 1; }
.po-tl-who { font-size: 0.72rem; color: var(--muted); white-space: nowrap; flex: none; } /* the doer */
.po-reply { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.5rem; }
.po-reply .po-text { width: 100%; resize: vertical; }
.po-reply .btn-small { align-self: flex-end; }

/* Trash view — a flat list; the label grows so the Restore / Delete-forever buttons sit at the end. */
.obj-trash-title { font-size: 1.05rem; margin: 0 0 0.7rem; }
.po-trash-row { flex-direction: row; align-items: center; gap: 0.5rem; cursor: default; }
.po-trash-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.btn-danger { color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: #fff; border-color: var(--danger); }

/* Card History view — revisions newest-first, each with a Restore affordance. */
.po-history { display: flex; flex-direction: column; gap: 0.6rem; }
.po-rev { border: 1px solid var(--border); border-radius: 7px; overflow: hidden; }
.po-rev-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.35rem 0.6rem;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.po-rev-when { font-size: 0.75rem; color: var(--muted); }
.po-rev-body { padding: 0.4rem 0.5rem; max-height: 22rem; overflow: auto; }
.po-diff { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.78rem; line-height: 1.5; }
.po-diff-line { white-space: pre-wrap; word-break: break-word; padding: 0 0.4rem; border-radius: 2px; }
.po-diff-add { background: rgba(46, 160, 67, 0.18); }
.po-diff-del { background: rgba(248, 81, 73, 0.18); }
.po-diff-eq { color: var(--muted); }
.po-diff-gap { color: var(--muted); text-align: center; }
/* Revealed whitespace: tabs faint inline, trailing whitespace on an amber chip. */
.po-ws { color: var(--muted); opacity: 0.6; }
.po-ws-trail { background: rgba(255, 193, 7, 0.35); color: var(--muted); border-radius: 2px; }

/* Rendered markdown (Portal.markdown cls:'po'); base.css zeroes margins. */
.po-md { padding: 0.2rem 0.1rem; }
.po-empty { color: var(--muted); }
.po-h1 { font-size: 1.2rem; font-weight: 700; margin: 0.5rem 0 0.4rem; }
.po-h2 { font-size: 1.02rem; font-weight: 600; margin: 0.8rem 0 0.35rem; }
.po-h3 { font-size: 0.9rem; font-weight: 600; margin: 0.7rem 0 0.3rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.po-p { margin: 0 0 0.6rem; }
.po-hr { border: none; border-top: 1px solid var(--border); margin: 0.8rem 0; }
.po-ul { margin: 0.3rem 0 0.6rem 1.3rem; }
.po-ul li { margin-bottom: 0.2rem; }
.po-code { background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 0.6rem 0.7rem; overflow-x: auto; font-size: 0.82rem; margin: 0.5rem 0; white-space: pre; }
.po-inline { background: var(--bg); border: 1px solid var(--border); border-radius: 3px; padding: 1px 5px; font-size: 0.87em; }
.po-table { border-collapse: collapse; width: 100%; margin: 0.5rem 0; font-size: 0.85rem; }
.po-table th { text-align: left; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); padding: 0.35rem 0.6rem; border-bottom: 2px solid var(--border); }
.po-table td { padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--border); vertical-align: top; }
.po-table tr:last-child td { border-bottom: none; }

/* Traits editor in the source view — owner-only; changing traits changes what the object is. */
.po-traits-edit { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.5rem; }
.po-traits-lbl { font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); flex: none; }
.po-traits-edit .po-input { flex: 1; }

/* New-object assistant (Portal.dialog body). */
.po-new { display: flex; flex-direction: column; gap: 0.6rem; min-width: min(85vw, 26rem); }
.po-new-traits { display: flex; align-items: center; gap: 0.4rem; }
.po-new-traits .po-input { flex: 1; }
.po-new-resolved { font-size: 0.8rem; color: var(--muted); }
.po-new-fields { display: flex; flex-direction: column; gap: 0.5rem; }

/* ── Doc rendering (the `docs-*` markdown namespace) ─────────────────────────────────────────
   Moved here from the deleted docs module. Portal.markdown({cls:'docs'}) stamps these classes,
   and the doc trait's viewer (buildDoc) renders into .docs-view — so the styling has to live with
   the module that owns the viewer, not with a page that no longer exists. The old .docs-nav /
   .docs-nav-btn rules are dropped with the page they styled. */
.docs-view { max-width: 760px; line-height: 1.7; }
.docs-view a { color: var(--accent); }
.docs-loading { color: var(--muted); }

.docs-h1 { font-size: 1.5rem; font-weight: 700; margin: 1.5rem 0 0.75rem; letter-spacing: -0.02em; }
.docs-h2 { font-size: 1.1rem; font-weight: 600; margin: 1.8rem 0 0.6rem; letter-spacing: -0.01em; border-bottom: 1px solid var(--border); padding-bottom: 0.3rem; }
.docs-h3 { font-size: 0.95rem; font-weight: 600; margin: 1.2rem 0 0.4rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }

.docs-p  { margin-bottom: 0.9rem; }
.docs-hr { border: none; border-top: 1px solid var(--border); margin: 1.5rem 0; }

.docs-ul { margin: 0.5rem 0 0.9rem 1.4rem; }
.docs-ul li { margin-bottom: 0.25rem; }

.docs-code {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 0.9rem 1rem;
  overflow-x: auto;
  font-size: 0.84rem;
  margin: 0.75rem 0 1rem;
  white-space: pre;
}

.docs-inline {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 1px 5px;
  font-size: 0.87em;
}

.docs-table { border-collapse: collapse; width: 100%; margin: 0.75rem 0 1rem; font-size: 0.88rem; }
.docs-table th {
  text-align: left;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  padding: 0.4rem 0.75rem;
  border-bottom: 2px solid var(--border);
}
.docs-table td { padding: 0.45rem 0.75rem; border-bottom: 1px solid var(--border); vertical-align: top; }
.docs-table tr:last-child td { border-bottom: none; }

/* The ticket's own request text, above the timeline. Bounded so a long request scrolls inside its
   own block instead of pushing the timeline and reply box off-screen. */
.po-ticket-body { margin-bottom: 0.8rem; }
.po-ticket-body .po-text, .po-ticket-body .po-hl { max-height: 22rem; }

/* A one-argument message drops its candidates under its own button (assign → who). Same anatomy
   as the nav doors' menus; anchored to the button's slot so it tracks the action bar. */
.po-method-slot { position: relative; display: inline-block; }
.po-method-menu {
  position: absolute; top: 100%; left: 0; z-index: 40; min-width: 12rem; max-height: 16rem;
  overflow-y: auto; display: flex; flex-direction: column; gap: 2px; padding: 4px; margin-top: 2px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
}
.po-method-menu button {
  background: none; border: none; color: var(--fg); text-align: left; padding: 6px 10px;
  border-radius: 4px; cursor: pointer; font-size: 0.85rem; font-family: inherit;
}
.po-method-menu button:hover { background: var(--accent); color: #fff; }
.po-method-empty { color: var(--muted); font-size: 0.85rem; padding: 6px 10px; }

/* An invite the relay refused: shown, not hidden — the manager needs to see the typo and the
   reason. Red on the value (the relay's words), so it reads as a dead end rather than a state. */
.po-invite-failed .po-org-v { color: #dc2626; }

/* ── Panes: #content is a column, every card is a pane ───────────────────────────────────────
   A pane is a card frame in FLOW — no overlay, no backdrop, no z-index. It owns a definite
   height (set by the layout) and its body is the bounded flex box the .po-host chain needs to
   scroll internally, exactly as .dialog-box.dialog-card provides one inside a dialog. */
/* NO frame and NO surface. Every pane looks like the lone bucket, because the lone bucket is what
   a pane IS — one surface type, one appearance, and not a pixel spent on borders. Both halves were
   learned the hard way: --card-bg breaks row zebra and hover (they are tuned against the page),
   and a border costs screen while making a tiled workspace read as stacked cards. What separates
   cells is the gap; what identifies them is the title; what marks the focused one is an accent
   edge. overflow:hidden stays so each pane clips its own floating console. */
/* FRAMED — and this is now consistent, which it was not before. The earlier objection was that the
   console pane wore a frame and object panes did not; the console is a LAYER now, so every pane in
   the workspace is an object and framing them all says one thing. A border and radius make a cell
   feel like a distinct object, which a background wash could not do — and the background stays
   transparent because row zebra and hover are tuned against the page. */
.po-pane {
  display: flex; flex-direction: column; min-height: 0;
  position: relative;
  /* A NEUTRAL wash, not --card-bg. It lifts the pane off the page so the frame reads as a surface
     rather than an outline — but it must not be either colour the rows alternate between, or every
     second row inside would vanish into it. Grey at 6% is invisible as a hue and works in both
     themes, so it accentuates the frame without touching the zebra. */
  background: rgba(128, 128, 128, 0.06);
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
.po-pane:focus { outline: none; }
/* A CAPTION, not a title bar. The border and the 0.4rem padding were the last thing framing a
   pane — they cost a visible strip per cell and made a tiled workspace look unlike the lone bucket,
   which has no head at all. A pane needs to say which one it is and offer its ✕; it does not need
   a bar to do it in. */
.po-pane-head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
  flex: none; padding: 0.1rem 0.15rem 0.25rem;
}
.po-pane-title {
  font-size: 0.7rem; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.05em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.po-pane.is-active > .po-pane-head > .po-pane-title { color: var(--accent); }
.po-pane-close, .po-pane-rotate {
  flex: none; background: none; border: none; color: var(--muted);
  font-size: 0.85rem; line-height: 1; cursor: pointer; padding: 0 0.2rem;
}
.po-pane-close:hover, .po-pane-rotate:hover { color: var(--fg); }
/* Padding lives HERE, not on #content: inside the pane it belongs to the content, scrolls with it,
   and costs nothing at the screen edges. */
.po-pane-body { padding: 0 0 0.4rem; }

/* The body is the definite-height flex box; exactly one thing inside it scrolls. It is also a
   CONTAINER: the same card can be the whole page in one workspace and a third of it in another, so
   what it responds to is its PANE, not the viewport. A media query would be answering the wrong
   question — the window did not change size, the layout did. */
.po-pane-body { flex: 1; min-height: 0; display: flex; flex-direction: column; container-type: inline-size; }

/* Narrow pane: the field grid drops its fixed 8rem label column and stacks label over value. That
   fixed column is fine across a full-width card and squeezes the value to nothing beside a search
   pane. The markdown/JSON editors give back their 12rem floor for the same reason. */
@container (max-width: 30rem) {
  .po-field { grid-template-columns: 1fr; gap: 0.15rem; align-items: start; }
  .po-text { min-height: 8rem; }
}

/* ── The stack strip ─────────────────────────────────────────────────────────────────────────
   An HP-style register strip, not a row of tags: one line per entry, numbered, the top of the
   stack nearest the entry line. The number is the point — it is what makes a line ADDRESSABLE
   instead of something you walk to. */
.po-filter-stack { flex-direction: column; align-items: stretch; gap: 0.15rem; }
.po-filter-stack .po-chip { justify-content: flex-start; border-radius: 4px; }
.po-chip-n {
  flex: none; min-width: 1.2rem; text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 0.7rem; font-weight: 700; color: var(--muted);
  border-right: 1px solid var(--border); padding-right: 0.35rem; margin-right: 0.1rem;
}
.po-filter-stack .po-chip-text { text-align: left; }
/* The entry line rejecting an operator it cannot apply (too few operands, a directive). */
.po-search-input.po-invalid { border-color: #dc2626; }

/* ── The roving cursor, LAST on purpose ──────────────────────────────────────────────────────
   Drawn with OUTLINE, not box-shadow: row decoration already owns box-shadow (the queue status
   stripe, the tile/row hover lift), so a shadow ring loses the cascade on exactly the rows that
   carry a status or the pointer. The TINT needs the same care for a different reason — zebra
   striping is `.po-list-row:nth-child(even)`, which is two-specificity and appears later in this
   file, so a one-class background would silently lose on every other row. Hence `.po-navitem`
   compounded, and hence this block living at the very end: the cursor must outrank everything
   that decorates a row, whatever gets added next. */
.po-navitem.po-nav-cursor {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 16%, var(--card-bg));
  position: relative;   /* paint above the neighbouring rows' edges */
  z-index: 1;
}

/* …and the other half of making the cursor obvious: DIM ITS NEIGHBOURS. Brightening one row can
   only go so far before it fights the accent used everywhere else; pushing everything else back
   costs the cursor nothing — but it was TRIED AND REMOVED. Every gate for it was wrong in a
   different way: always-on is just a darker app, mode-gated is invisible when you are looking,
   and keyboard-gated needed an Esc rung that fought the Esc ladder. The ring carries the cursor on
   its own. Kept as a note so it is not re-invented a fourth time. */

/* Pane-level dimming was tried and REMOVED. Rows and panes are not the same problem: picking one
   row out of fifty needs the other forty-nine pushed back, while a workspace holds two or three
   panes and every one of them contains something you are reading. Greying a card you are looking
   at costs more than it signals — and the focused pane already says so twice, with its accent edge
   and its accent caption. */

/* ── SIZES ───────────────────────────────────────────────────────────────────────────────────
   A list row, a grid tile and an open card are ONE object at three sizes, so raising something is
   a CSS class on the item that was already there — not a move to another surface. Which is why
   there is no workspace: there was never anything for it to hold. */
.po-card {
  display: flex; flex-direction: column; min-width: 0; min-height: 0;
  background: rgba(128, 128, 128, 0.06);
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
/* max fills the row it lands in; span takes the whole width of the grid — the thing nested splits
   cannot express, and the reason the arrangement is a grid at all. */
.po-size-max { min-height: 24rem; }
.po-size-span { grid-column: 1 / -1; min-height: 24rem; }
.po-list .po-size-span, .po-queue .po-size-span { width: 100%; }
.po-card .po-host { flex: 1; min-height: 0; }

/* A raised cell that matched nothing: a MAYBE, not a silence. Muted and dashed so it reads as an
   absence rather than content — you should be able to tell at a glance that nothing is there,
   without having to read it. It is still a real cell: cursor-navigable, collapsible, movable. */
.po-none { border-style: dashed; opacity: 0.8; display: flex; flex-direction: column; gap: 0.4rem;
           justify-content: center; padding: 0.8rem; }
.po-none-title { font-weight: 600; color: var(--muted); }
.po-none-why { font-size: 0.85rem; color: var(--muted); word-break: break-word; }
