/* CINTI Portal Shell — shared CSS for every portal.
   Phase 1.2 of #44 Portal Modularization. Closes #1609.

   Portals include this stylesheet once. Design tokens come from CSS variables
   set on :root by portal-shell.js applyTokens() — themed portals (e.g. the
   projects portal with #7a88ff accent) override individual tokens via their
   manifest.json theme_overrides. This file ONLY uses var(--cinti-*) values
   so a token change is a one-place edit.

   What's in here:
     - Design-token variables (default values; JS overrides them on init)
     - Common typography baseline (Inter body, mono fallback)
     - Toast host + cards
     - Modal overlay + card + buttons
     - Avatar panel (right-side iframe drop-in)
     - Standard form input/select/textarea (matches the dev portal modal)
     - Skeleton-row loading state
     - Standard error / retry button

   What's NOT in here:
     - Portal-specific layout (each portal owns its own grid/tiles)
     - Portal-specific tile styling
     - Avatar-iframe internals (those live in the avatar app itself)
*/

/* ----- Design tokens (defaults; overridden by JS at runtime) -----
   CW Project #63 Wave 2 (2026-05-08): split into dark + light, plus a
   small bridge to the universal /portals/_shared/cinti-theme.css token
   layer. Portal-shell tokens (--cinti-bg / --cinti-text / --cinti-bg-card
   / etc.) override `cinti-theme.css` for portal pages; light mode adds
   matching values so the same shell renders on either palette. JS in
   portal-shell.js subscribes to CintiTheme.onChange and re-runs
   applyTokens() on theme flips so manifest theme_overrides survive. */
:root, [data-theme="dark"] {
  /* Literal, not var(--accent, …) — cinti-theme.css declares
     `--accent: var(--cinti-accent)`, so the alias was a cycle and both tokens
     computed to nothing wherever cinti-theme.css won the --accent declaration.
     See the long note on DEFAULT_TOKENS in portal-shell.js. (2026-09-07) */
  --cinti-accent:    #26bdd6;
  --cinti-accent-2:  #7ee2a8;
  --cinti-accent-3:  #ffb454;
  --cinti-accent-4:  #ff7a7a;
  --cinti-bg:        #0a0e1a;
  --cinti-bg-subtle: #080c1a;
  --cinti-bg-card:   #0e1420;
  --cinti-bg-input:  #0a0e1a;
  --cinti-border:    #1f2530;
  --cinti-border-2:  #1a1f2c;
  --cinti-text:      #cfd6e2;
  --cinti-text-dim:  #8a92a3;
  --cinti-text-mute: #5e6675;
  --cinti-font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --cinti-font-mono: 'SF Mono', 'JetBrains Mono', 'Monaco', monospace;
}

[data-theme="light"] {
  --cinti-accent:    #1a9ab5;
  --cinti-accent-2:  #00a050;
  --cinti-accent-3:  #d97500;
  --cinti-accent-4:  #d32f2f;
  --cinti-bg:        #f4f7fb;
  --cinti-bg-subtle: #ffffff;
  --cinti-bg-card:   #ffffff;
  --cinti-bg-input:  #eef2f7;
  --cinti-border:    rgba(26, 154, 181, 0.22);
  --cinti-border-2:  rgba(26, 154, 181, 0.12);
  --cinti-text:      #1a2332;
  --cinti-text-dim:  #5c6a82;
  --cinti-text-mute: #8a92a3;
}

/* ----- Toast ----- */
.cinti-toast-host {
  position: fixed;
  top: 64px;
  right: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 2000;
  pointer-events: none;
}
.cinti-toast {
  background: var(--cinti-bg-card);
  border: 1px solid var(--cinti-border);
  border-left: 3px solid var(--cinti-accent);
  padding: 10px 14px;
  border-radius: 4px;
  min-width: 260px;
  max-width: 380px;
  font-size: 12px;
  font-family: var(--cinti-font-body);
  color: var(--cinti-text);
  pointer-events: auto;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  animation: cinti-toast-in 0.18s ease-out;
}
.cinti-toast.success { border-left-color: var(--cinti-accent-2); }
.cinti-toast.error   { border-left-color: var(--cinti-accent-4); }
.cinti-toast.pending { border-left-color: var(--cinti-accent-3); }
.cinti-toast .t-title {
  font-weight: 600;
  margin-bottom: 2px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.cinti-toast .t-body  { color: var(--cinti-text-dim); }
.cinti-toast .t-body a { color: var(--cinti-accent); text-decoration: none; }
.cinti-toast .t-close {
  float: right;
  cursor: pointer;
  color: var(--cinti-text-mute);
  padding-left: 8px;
  background: transparent;
  border: none;
  font-size: 14px;
  line-height: 1;
}
.cinti-toast .t-close:hover { color: var(--cinti-text); }
@keyframes cinti-toast-in {
  from { transform: translateY(-8px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

/* ----- Modal ----- */
.cinti-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 26, 0.78);
  backdrop-filter: blur(4px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  font-family: var(--cinti-font-body);
}
.cinti-modal-overlay.open { display: flex; }
.cinti-modal-card {
  width: min(640px, 92vw);
  max-height: 88vh;
  background: var(--cinti-bg-card);
  border: 1px solid var(--cinti-border);
  border-radius: 8px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  display: flex;
  flex-direction: column;
}
.cinti-modal-head {
  padding: 16px 22px;
  border-bottom: 1px solid var(--cinti-border-2);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.cinti-modal-title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--cinti-text);
  letter-spacing: 0.3px;
}
.cinti-modal-close {
  background: none;
  border: none;
  color: var(--cinti-text-dim);
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  padding: 4px 8px;
}
.cinti-modal-close:hover { color: var(--cinti-text); }
.cinti-modal-body {
  padding: 18px 22px;
  overflow-y: auto;
  color: var(--cinti-text);
  font-size: 13px;
  line-height: 1.55;
}
.cinti-modal-foot {
  padding: 14px 22px;
  border-top: 1px solid var(--cinti-border-2);
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  align-items: center;
}

/* ----- Buttons (used in modal foot + ad-hoc) ----- */
.cinti-btn {
  font-family: inherit;
  font-size: 12px;
  border-radius: 4px;
  cursor: pointer;
  padding: 7px 16px;
  border: 1px solid var(--cinti-border);
  background: transparent;
  color: var(--cinti-text-dim);
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.cinti-btn:hover { color: var(--cinti-text); border-color: #2c3445; }
.cinti-btn.primary {
  background: var(--cinti-accent);
  color: var(--cinti-bg-subtle);
  border: none;
  font-weight: 600;
}
.cinti-btn.primary:hover { background: #3dcde0; }
.cinti-btn[disabled] { opacity: 0.6; cursor: not-allowed; }

/* ----- Form inputs (consumed by both modal forms and standalone) ----- */
.cinti-input,
.cinti-select,
.cinti-textarea {
  background: var(--cinti-bg-input);
  border: 1px solid var(--cinti-border);
  color: var(--cinti-text);
  font-family: inherit;
  font-size: 13px;
  padding: 8px 10px;
  border-radius: 4px;
  outline: none;
}
.cinti-input:focus,
.cinti-select:focus,
.cinti-textarea:focus { border-color: var(--cinti-accent); }
.cinti-textarea { min-height: 88px; resize: vertical; line-height: 1.5; }
.cinti-label {
  font-size: 11px;
  color: var(--cinti-text-dim);
  font-family: var(--cinti-font-mono);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

/* ----- Avatar panel (right-side iframe drop-in) -----
   ONE SOURCE FOR THE WIDTH. `--cinti-avatar-w` was read in two places with a
   440px fallback and never actually SET anywhere, while the panel itself
   carried a second hardcoded `width: 440px`. So the panel's width and the space
   the page reserved for it were two independent literals that could drift apart
   — and the variable the comment below calls "the one place" did nothing.

   It also stepped down only at 900px, where the reservation drops to zero.
   Between there and ~1300px the panel stayed a hard 440px: measured on
   Gruenberg at a 1024px viewport, that left the content column 488px against
   the avatar's 440 — the avatar taking nearly half the screen on a laptop.
   Two steps down so the reading column keeps the majority. */
:root { --cinti-avatar-w: 440px; }
@media (max-width: 1400px) { :root { --cinti-avatar-w: 380px; } }
@media (max-width: 1150px) { :root { --cinti-avatar-w: 320px; } }

.cinti-avatar-panel {
  position: fixed;
  /* THE PANEL SITS IN THE SAME BAND AS THE CONTENT, NOT A GUESSED ONE.
     These were `top: 56px` and `bottom: 0`, and 56 is not the height of any
     topbar we actually ship: Review's is 38px and Automations' is 36px, so the
     panel floated 18–20px below where the header ends. `bottom: 0` was worse —
     it ran the panel straight through the footer, by 28px on Review and 52px
     over the Automations taskbar. Emmanuel, 2026-09-08: "the avatars are
     slightly down not starting after the header ends but after a few - and not
     ending before the footer starts instead crossing by a bit."
     --topbar-h and --taskbar-h are admin/css/layout.css's own variables, which
     every one of these portals already declares, so the panel now tracks the
     same numbers the rest of the layout does. --cinti-avatar-bottom is the
     override for a portal whose footer sits INSIDE the content column rather
     than as a fixed rail (Review), where --taskbar-h is legitimately 0. */
  top: var(--topbar-h, 56px);
  right: 0;
  bottom: var(--cinti-avatar-bottom, var(--taskbar-h, 0px));
  width: var(--cinti-avatar-w, 440px);
  background: var(--cinti-bg-card);
  border-left: 1px solid var(--cinti-border);
  display: flex;
  flex-direction: column;
  transform: translateX(0);
  transition: transform 0.2s ease, width 0.2s ease, left 0.2s ease;
  z-index: 500;
}
.cinti-avatar-panel.collapsed {
  transform: translateX(calc(100% - 36px));
}
/* When collapsed, the 36px still on screen must read as ONE affordance, not as
   the sliced-off left edge of a chat panel. Everything inside is hidden and the
   reopen strip takes the whole height with the portal's name running up it —
   the same shape the bespoke Automations panel used, which is the one people
   here already know. (2026-09-08) */
.cinti-avatar-panel.collapsed > *:not(.cinti-avatar-reopen) {
  opacity: 0; pointer-events: none;
}
.cinti-avatar-reopen {
  display: none;
  position: absolute; left: 0; top: 0; bottom: 0; width: 36px;
  align-items: center; justify-content: center;
  writing-mode: vertical-rl; text-orientation: mixed;
  background: var(--cinti-bg-card); border: 0; border-right: 1px solid var(--cinti-border);
  color: var(--accent, #26bdd6); font: 600 11px/1 Inter, system-ui, sans-serif;
  letter-spacing: .04em; cursor: pointer; padding: 0;
}
.cinti-avatar-panel.collapsed .cinti-avatar-reopen { display: flex; }
.cinti-avatar-reopen:hover { background: rgba(var(--accent-rgb, 38, 189, 214), .10); }
/* An expanded or fullscreen panel is never also collapsed (the JS opens it
   first), but if a stored preference and a stored mode ever disagreed, being
   visible is the safer of the two states. */
.cinti-avatar-panel.expanded.collapsed,
.cinti-avatar-panel.fullscreen.collapsed { transform: translateX(0); }
.cinti-avatar-panel.expanded.collapsed > *,
.cinti-avatar-panel.fullscreen.collapsed > * { opacity: 1; pointer-events: auto; }
.cinti-avatar-panel.expanded .cinti-avatar-reopen,
.cinti-avatar-panel.fullscreen .cinti-avatar-reopen { display: none; }

/* THE PAGE HAS TO GIVE THE PANEL ITS 440px BACK.
   The panel is position:fixed, and nothing reserved room for it — because
   until now no portal actually mounted it (every caller passed
   mountAvatar:false). The first one that did, Review on 2026-09-08, drew the
   avatar straight over its own KPI cards and seat panel. mountAvatar() sets
   these classes on <body>; the width is a variable so the expand cycle can
   change it in one place. Padding rather than a margin, so a portal whose
   wrapper is a scroll container still scrolls its full height. */
body.cinti-has-avatar { padding-right: var(--cinti-avatar-w, 440px); }
body.cinti-has-avatar.cinti-avatar-collapsed { padding-right: 36px; }
/* ...and again for the canonical fixed content column. admin/css/layout.css
   pins `.desktop-wrapper { position: fixed; right: 0 }`, which ignores body
   padding entirely — that is why the first mount still drew over Review's KPI
   row. admin's own avatar is a FLEX CHILD of this wrapper, so it never needed
   this; the portal panel is fixed (its expand/fullscreen cycle depends on
   that), so the column is what moves. */
body.cinti-has-avatar .desktop-wrapper { right: var(--cinti-avatar-w, 440px); }
body.cinti-has-avatar.cinti-avatar-collapsed .desktop-wrapper { right: 36px; }
body.cinti-has-avatar.cinti-avatar-over .desktop-wrapper { right: 0; }
/* Expanded (70vw) and fullscreen are deliberate overlays — the reader is done
   with the page for the moment — so the page stops reserving anything. */
body.cinti-has-avatar.cinti-avatar-over { padding-right: 0; }
@media (max-width: 900px) {
  /* The panel is already full-width-ish on a small screen; reserving 440px
     would leave the page a sliver. */
  body.cinti-has-avatar,
  body.cinti-has-avatar.cinti-avatar-collapsed { padding-right: 0; }
  body.cinti-has-avatar .desktop-wrapper,
  body.cinti-has-avatar.cinti-avatar-collapsed .desktop-wrapper { right: 0; }
}
/* Expand cycle: normal → expanded (70%) → fullscreen → normal.
   Matches admin's .avatar-panel.expanded / .fullscreen behavior so any
   portal using portal-shell.js mountAvatar gets the same UX. */
.cinti-avatar-panel.expanded { width: 70vw; }
.cinti-avatar-panel.fullscreen {
  width: auto; left: 0; right: 0; top: 0; bottom: 0;
  border-left: none; z-index: 600;
}
.cinti-avatar-frame {
  flex: 1;
  border: none;
  width: 100%;
  background: var(--cinti-bg);
}

/* ----- Canonical avatar header (orb + name + button cluster) -----
   CW #1911 + CW #2103 (Emmanuel 2026-05-12). Host page renders this header
   around the iframe; buttons postMessage into the iframe. Keep tokens in
   sync with admin/css/layout.css — the two patterns must look identical
   on the screen, only the wrapping selectors differ (.cinti-avatar-* vs
   admin's .avatar-*). */
.cinti-avatar-header {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--cinti-border);
  background: var(--cinti-bg-subtle);
  flex: 0 0 auto;
}
.cinti-avatar-orb {
  width: 22px; height: 22px; border-radius: 50%;
  background: radial-gradient(circle at 40% 40%, rgba(var(--accent-rgb, 38, 189, 214),0.6), rgba(var(--accent-rgb, 38, 189, 214),0.15) 55%, transparent 70%);
  box-shadow: 0 0 10px rgba(var(--accent-rgb, 38, 189, 214),0.3);
  animation: cintiOrbPulse 3s ease-in-out infinite;
}
@keyframes cintiOrbPulse {
  0%, 100% { box-shadow: 0 0 10px rgba(var(--accent-rgb, 38, 189, 214),0.3); }
  50%      { box-shadow: 0 0 16px rgba(var(--accent-rgb, 38, 189, 214),0.5), 0 0 32px rgba(var(--accent-rgb, 38, 189, 214),0.15); }
}
/* ONE LINE EACH, ELLIPSIS IF IT WILL NOT FIT.
   The status line used to say "Online" and always fitted. It now carries what
   the agent is grounded in, and "Clio workflows + legal operations · grounded"
   needs 190px in a 153px column — so it WRAPPED to two lines and read as cut
   off (Emmanuel, 2026-09-08: "title gets cut off?"). A subtitle that reflows
   the header is worse than one that trims, and the full text is on the title
   attribute either way. The text block also needs min-width:0, or a flex child
   refuses to shrink below its content and pushes the button cluster out. */
.cinti-avatar-header > div { min-width: 0; }
.cinti-avatar-name,
.cinti-avatar-status {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.cinti-avatar-name   { font-size: 13px; font-weight: 600; color: var(--cinti-accent); }
.cinti-avatar-status { font-size: 9px;  color: var(--cinti-success, #10b981); }

.avatar-hdr-cluster {
  display: flex; align-items: center; gap: 4px;
  margin-left: auto;
}
.avatar-hdr-btn {
  background: none; border: none; color: var(--cinti-text-dim);
  cursor: pointer; padding: 0; transition: color 0.2s;
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 24px; box-sizing: border-box;
}
.avatar-hdr-btn:hover { color: var(--cinti-accent); }
.avatar-mute-btn.muted          { color: var(--cinti-danger, #ef4444); }
.avatar-mute-btn.muted:hover    { color: var(--bad-text,#ff7878); }
.avatar-cam-btn.active          { color: var(--cinti-accent); }
.avatar-cam-btn.preview         { color: var(--cinti-success, #10b981); }
.avatar-settings-btn.active     { color: var(--cinti-accent); }
/* Speed button is text-labeled (1x/1.25x/1.5x/2x). Mono+bold+tight tracking
   keeps it crisp at 13px so it visually matches the 16px SVGs next to it. */
.avatar-speed-btn {
  font-family: var(--cinti-font-mono);
  font-size: 13px; font-weight: 700; line-height: 1;
  letter-spacing: -0.04em;
}
.avatar-speed-btn:not(.baseline) { color: var(--cinti-accent); }
/* Mute icon's SVG geometry fills ~70% of its 24×24 viewBox — bump to 18px
   so its perceived weight matches the camera/settings icons. */
.avatar-mute-btn svg { width: 18px; height: 18px; }

/* ----- Skeleton loading row ----- */
.cinti-skeleton-row {
  height: 18px;
  background: linear-gradient(90deg, var(--cinti-bg-card) 0%, #161a22 50%, var(--cinti-bg-card) 100%);
  background-size: 200% 100%;
  border-radius: 3px;
  margin: 8px 0;
  animation: cinti-skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes cinti-skeleton-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

/* ----- Error state with retry ----- */
.cinti-error-state {
  text-align: center;
  padding: 40px 20px;
  color: var(--cinti-text-dim);
  font-size: 13px;
}
.cinti-error-state .reason {
  color: var(--cinti-text-mute);
  font-size: 11px;
  margin-top: 6px;
}
.cinti-error-state .cinti-btn { margin-top: 14px; }

/* ----- 2026-06-16 (Emmanuel) — global sticky table headers ---------------- */
/* Polish: when a table sits in a scrollable container, its <thead> rides
 * along as the user scrolls. position:sticky is a no-op for non-scrolling
 * tables, so this is safe to apply broadly. Pages that want to opt OUT
 * can set `.cinti-no-sticky-thead` on the table. Background uses theme
 * surface so the content underneath doesn't bleed through. (Polish-only:
 * does not change layout, sort, click behaviour, or column widths.) */
table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  /* If the table's own thead already paints a background (most do), this
   * inherits to it — otherwise fall back to the theme surface so sticky
   * mode doesn't reveal the rows underneath. */
  background: var(--cinti-surface, var(--surface, rgba(13, 20, 38, 0.97)));
}
[data-theme="light"] table thead th {
  background: var(--cinti-surface, var(--surface, rgba(255, 255, 255, 0.97)));
}
table.cinti-no-sticky-thead thead th { position: static; }

/* =============================================================================
   TILES (tile-loader.js + tile-registry.js)

   tile-loader.js has always emitted .cinti-tile / .cinti-tile-head /
   .cinti-tile-body markup, and NOTHING in the repo styled it — because nothing
   ever mounted a tile. Added with the tile registry (Gene 2026-09-03).

   Colours come from the shared theme variables (--accent, --panel-bg …), NOT
   from --cinti-*, so a portal's theme_overrides actually reach these. Light
   mode is handled by the shared keystone remapping those same variables.
   ============================================================================= */
.cinti-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.cinti-tile {
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 14px 16px;
  min-width: 0;                 /* let grid children shrink instead of overflowing */
}
.cinti-tile-head {
  display: flex; align-items: center; gap: 6px; margin-bottom: 10px;
}
.cinti-tile-icon { color: var(--accent); font-size: 12px; }
.cinti-tile-title {
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.5px; color: var(--dim);
}
.cinti-tile-status {
  margin-left: auto; font-size: 9px; text-transform: uppercase;
  letter-spacing: 0.4px; color: var(--dim); opacity: 0.75;
}
.cinti-tile-body { min-height: 40px; }
/* The ACTIVE tile-foot rule. tile-styles.css carries a fuller copy but no
   portal links it, so this is the one that renders — the gap fix has to live
   here. Without gap the two footer spans meet on a narrow tile and read as
   "8:40:00 AMevery 1m", which is what was reported from a live screen. */
.cinti-tile-foot {
  margin-top: 8px;
  display: flex;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.cinti-tile-loading,
.cinti-tile-empty { font-size: 11.5px; color: var(--dim); padding: 6px 0; }
.cinti-tile-error { font-size: 11.5px; color: var(--bad-text,#f44336); padding: 6px 0; }
.cinti-stat-value {
  font-size: 26px; font-weight: 200; color: var(--text); line-height: 1.2;
}
.cinti-stat-label { font-size: 11px; color: var(--dim); margin-top: 2px; }
.cinti-tile-table { width: 100%; border-collapse: collapse; font-size: 11.5px; margin-top: 10px; }
.cinti-tile-table th {
  text-align: left; font-size: 9px; text-transform: uppercase; letter-spacing: 0.4px;
  color: var(--dim); padding: 5px 6px; border-bottom: 1px solid var(--panel-border);
}
.cinti-tile-table td {
  padding: 6px; color: var(--text);
  border-bottom: 1px solid rgba(var(--accent-rgb, 38, 189, 214), 0.06);
}
.cinti-badge {
  font-size: 9px; padding: 1px 6px; border-radius: 10px;
  background: rgba(var(--accent-rgb, 38, 189, 214), 0.14); color: var(--accent);
}
.cinti-badge.ok  { background: rgba(76, 175, 80, 0.16);  color: var(--ok-text,#4caf50); }
.cinti-badge.bad { background: rgba(244, 67, 54, 0.16);  color: var(--bad-text,#f44336); }

/* A declared tile with no renderer. Deliberately visible rather than absent —
   a tile that silently fails to appear is how the gap stayed hidden. */
.cinti-tile-todo { border-style: dashed; }
.cinti-tile-todo-msg { font-size: 11px; color: var(--dim); line-height: 1.6; }

/* Three flashes when a tile is switched on in the admin editor, so it is
   findable on a busy dashboard. Animation rather than a JS timer loop, so a
   tile replaced mid-flash cannot get stuck highlighted. */
@keyframes cinti-tile-flash {
  0%, 100%   { box-shadow: 0 0 0 0 rgba(var(--accent-rgb, 38, 189, 214), 0); border-color: var(--panel-border); }
  15%, 48%, 81% { box-shadow: 0 0 0 3px rgba(var(--accent-rgb, 38, 189, 214), 0.55); border-color: var(--accent); }
  32%, 65%, 98% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb, 38, 189, 214), 0);      border-color: var(--panel-border); }
}
.cinti-tile-flash { animation: cinti-tile-flash 1.5s ease-in-out 1; }
@media (prefers-reduced-motion: reduce) {
  .cinti-tile-flash { animation: none; box-shadow: 0 0 0 3px rgba(var(--accent-rgb, 38, 189, 214), 0.55); }
}

/* ---------------------------------------------------------------------------
   TILE DRILL-DOWN (Emmanuel: "van these look better, have ddrill downs, and
   dont take up so much space")
   --------------------------------------------------------------------------- */

/* The affordance that replaced the big "N items" stat block. Deliberately a
   quiet footer link rather than a button-shaped control: nine tiles each with
   a filled button is louder than the data. */
.cinti-tile-drill {
  display: block;
  width: 100%;
  margin: 6px 0 0;
  padding: 5px 0 1px;
  background: none;
  border: 0;
  border-top: 1px solid var(--border, rgba(120,130,145,.22));
  color: var(--accent, #0b6b7d);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.cinti-tile-drill:hover { text-decoration: underline; }

/* A row you can open has to look like one. */
.cinti-row-click { cursor: pointer; }
.cinti-row-click:hover { background: var(--row-hover, rgba(120,130,145,.10)); }

/* The single-record view inside the modal. */
.cinti-kv {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border, rgba(120,130,145,.18));
}
.cinti-kv:last-child { border-bottom: 0; }
.cinti-kv-k { color: var(--muted, #5b6675); font-size: 12px; }
/* Dark and saturated in light mode — the standing contrast rule; a pale value
   against a white modal is the exact complaint this stylesheet already
   carries a keystone for. */
.cinti-kv-v { color: var(--text, #16202b); font-weight: 600; font-size: 12.5px; text-align: right; }
