/* ============================================================
   EMPIRE OS — App shell layout (layout.css)
   Rail · Topbar · Content · Palette · Modals · Toasts
   Stays 100% consistent with tokens / base / components.
   ============================================================ */

/* ---------------------------------------------------------------
   App grid
--------------------------------------------------------------- */
.app {
  position: relative; z-index: 1;
  display: grid;
  /* Two-column shell: rail + content. The persistent AI dock was removed;
     the assistant now lives entirely on the AI Chat route. */
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  grid-template-rows: var(--topbar-h) minmax(0, 1fr);
  grid-template-areas:
    "rail topbar"
    "rail content";
  height: 100vh; height: 100dvh;
  transition: grid-template-columns var(--t-med) var(--ease-out);
}
body.rail-collapsed .app { grid-template-columns: var(--rail-w-collapsed) minmax(0, 1fr); }

/* ---------------------------------------------------------------
   Shell icon-button safety net
   Some shell/view markup uses `.btn-icon` without the base `.btn`
   class. Without `.btn`, the box model (height/radius/flex-center)
   was missing, so hover painted a flat, square, un-rounded slab
   behind the glyph — the "random squares" bug. This guarantees every
   shell icon button is a properly sized, centered, rounded hit area
   in all themes, with or without `.btn`.
--------------------------------------------------------------- */
.rail .btn-icon,
.topbar .btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0;
  border-radius: var(--r-sm);
  transition: background var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out);
}
.rail .btn-icon.btn-sm,
.topbar .btn-icon.btn-sm { width: 32px; height: 32px; border-radius: var(--r-xs); }
.rail .btn-icon svg,
.topbar .btn-icon svg { display: block; }
/* keyboard focus ring parity across the chrome */
.topbar .btn-icon:focus-visible,
.rail .btn-icon:focus-visible,
.global-search:focus-visible {
  outline: none; box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent-ring);
}

/* ===============================================================
   RAIL
=============================================================== */
.rail {
  grid-area: rail;
  display: flex; flex-direction: column;
  background: var(--surface-1);
  border-right: 1px solid var(--border);
  z-index: var(--z-rail);
  min-height: 0;
  position: relative;
  /* Inter optical refinements for the chrome: contextual alternates +
     disabled common ligatures keep nav labels crisp at small sizes. */
  font-feature-settings: "cv05" 1, "cv08" 1, "ss03" 1;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}
.rail::after { content: none; }

.rail-head {
  display: flex; align-items: center; justify-content: space-between;
  height: var(--topbar-h); padding: 0 14px 0 16px; flex-shrink: 0;
  border-bottom: 1px solid var(--border-soft);
}
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; text-decoration: none; }
.brand-mark {
  width: 23px; height: 23px; flex-shrink: 0;
  display: grid; place-items: center;
}
.brand-mark-img { width: 23px; height: 23px; object-fit: contain; grid-area: 1 / 1; }
.brand-word-img { height: 17px; width: auto; object-fit: contain; }
/* Theme-aware Nova ink: white on dark, black on light.
   These rules OWN the visibility of every brand image (mark + wordmark)
   so the per-element sizing rules above never need a `display` value
   (which previously fought these and double-rendered the wordmark). */
.brand-img-light { display: none; }
.brand-img-dark { display: block; }
[data-theme="light"] .brand-img-dark { display: none; }
[data-theme="light"] .brand-img-light { display: block; }

/* mark + wordmark lockup with a small muted instance label */
.brand-lockup { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.brand-wordmark { display: block; height: 17px; line-height: 0; }
.brand-instance {
  font-size: var(--fs-2xs); font-weight: 600; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--text-500); white-space: nowrap; line-height: 1;
}
.rail-collapse { color: var(--text-400); }

/* nav */
.rail-nav { flex: 1; min-height: 0; padding: 14px 12px 12px; overflow-y: auto; overflow-x: hidden; }
.rail-group { margin-top: 14px; }
.rail-group:first-child { margin-top: 4px; }
.rail-group-label {
  font-size: var(--fs-2xs); font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-500); padding: 0 10px 7px; white-space: nowrap;
}
.rail-item {
  position: relative; display: flex; align-items: center; gap: 11px;
  height: 38px; min-height: 38px; flex-shrink: 0;       /* never compress the row */
  padding: 0 10px; border-radius: var(--r-sm); margin-bottom: 2px;
  color: var(--text-300); font-size: var(--fs-sm); font-weight: 500;
  letter-spacing: -0.006em;
  transition: color var(--t-fast), background var(--t-fast);
}
.rail-item:hover { background: var(--surface-2); color: var(--text-100); }
.rail-ico { display: flex; color: var(--text-400); flex-shrink: 0; transition: color var(--t-fast); }
.rail-item:hover .rail-ico { color: var(--text-200); }
/* label takes the room, truncates with ellipsis — never wraps or compresses */
.rail-label { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* count badge sits flush-right, never crushing or being crushed by the label */
.rail-item .badge { flex-shrink: 0; margin-left: auto; }

.rail-item.active {
  color: var(--text-100); font-weight: 600;
  background: var(--surface-3);
}
.rail-item.active .rail-ico { color: var(--text-100); }
.rail-item.active::before {
  content: ""; position: absolute; left: -12px; top: 8px; bottom: 8px; width: 2px;
  border-radius: 0 2px 2px 0; background: var(--accent);
}

/* rail footer / user chip */
.rail-foot { padding: 10px 12px 14px; border-top: 1px solid var(--border-soft); flex-shrink: 0; }
.user-chip {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px;
  border-radius: var(--r-md); transition: background var(--t-fast); text-align: left;
}
.user-chip:hover { background: var(--surface-2); }
.user-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; line-height: 1.25; }
.user-name { font-size: var(--fs-sm); font-weight: 600; letter-spacing: -0.008em; color: var(--text-100); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-role { font-size: var(--fs-2xs); color: var(--text-400); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-gear { color: var(--text-400); display: flex; flex-shrink: 0; }
.user-chip:hover .user-gear { color: var(--text-200); }

/* ===============================================================
   COLLAPSED RAIL  (--rail-w-collapsed = 64px)
   Goal: a calm, intentional icon strip. Every icon sits in a
   consistent 40px square hit-area, centered in the rail, with a
   clear filled active chip + accent. Group labels collapse to a
   hairline divider so vertical rhythm reads as grouped, not random.
=============================================================== */
body.rail-collapsed .brand-lockup,
body.rail-collapsed .rail-label,
body.rail-collapsed .rail-item .badge,
body.rail-collapsed .rail-group-label,
body.rail-collapsed .user-meta,
body.rail-collapsed .user-gear { display: none; }

/* head: brand mark centered, collapse toggle centered below it isn't
   possible in the same row — keep the toggle but center the mark. */
body.rail-collapsed .rail-head { padding: 0; justify-content: center; position: static; }
body.rail-collapsed .rail { position: relative; }
body.rail-collapsed .brand { justify-content: center; gap: 0; }
/* the collapse/expand toggle is a small round handle centered on the rail's
   right edge (mid-height), half-overlapping the divider — discoverable, not noisy */
body.rail-collapsed .rail-collapse {
  position: absolute; right: -13px; top: 50%; transform: translateY(-50%);
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--surface-1); border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-md); z-index: 5; color: var(--text-300);
  opacity: 0; pointer-events: none; transition: opacity var(--t-fast) var(--ease-out);
}
/* reveal the expand handle only while interacting with the rail, so it never
   sits over the content column otherwise */
body.rail-collapsed .rail:hover .rail-collapse,
body.rail-collapsed .rail:focus-within .rail-collapse,
body.rail-collapsed .rail-collapse:focus-visible { opacity: 1; pointer-events: auto; }
body.rail-collapsed .rail-collapse:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--text-100); }
body.rail-collapsed .rail-collapse svg { transform: rotate(180deg); width: 14px; height: 14px; }

/* nav: tighten the horizontal padding so the 40px squares center cleanly */
body.rail-collapsed .rail-nav { padding: 12px 0; }
body.rail-collapsed .rail-group { display: flex; flex-direction: column; align-items: center; margin-top: 0; gap: 4px; }
/* hairline divider between groups (replaces hidden text labels) */
body.rail-collapsed .rail-group + .rail-group { margin-top: 10px; padding-top: 14px; position: relative; }
body.rail-collapsed .rail-group + .rail-group::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 24px; height: 1px; background: var(--border);
}

/* the square icon button */
body.rail-collapsed .rail-item {
  justify-content: center; padding: 0; gap: 0;
  width: 40px; height: 40px; min-height: 40px; margin: 0 auto;
  border-radius: var(--r-md);
}
body.rail-collapsed .rail-item .rail-ico { margin: 0; }
/* kill the expanded-state left accent bar; collapsed uses a filled chip */
body.rail-collapsed .rail-item.active::before { content: none; }
body.rail-collapsed .rail-item.active {
  background: var(--accent); color: var(--accent-fg);
}
body.rail-collapsed .rail-item.active .rail-ico { color: var(--accent-fg); }
body.rail-collapsed .rail-item:not(.active):hover { background: var(--surface-2); }

/* footer / user chip collapses to just a centered avatar */
body.rail-collapsed .rail-foot { padding: 10px 0 14px; }
body.rail-collapsed .user-chip { justify-content: center; padding: 0; width: 40px; height: 40px; margin: 0 auto; border-radius: var(--r-md); gap: 0; }

/* mobile rail toggle — hidden on desktop (override the .topbar .btn-icon
   inline-flex safety net, which has higher specificity than a bare class) */
.topbar .rail-toggle-m { display: none; }

/* ===============================================================
   COLLAPSED-RAIL FLYOUT TOOLTIP  (rendered in a body-level layer
   by app.js so it escapes the rail's overflow + the viewport edge)
   Appears to the RIGHT of the hovered icon, vertically centered.
=============================================================== */
.rail-tip-layer { position: fixed; inset: 0; pointer-events: none; z-index: var(--z-toast); }
.rail-flyout {
  position: fixed; left: 0; top: 0;
  display: inline-flex; align-items: center;
  padding: 6px 11px; max-width: 220px;
  font-size: var(--fs-xs); font-weight: 600; line-height: 1.2; letter-spacing: -0.004em;
  color: var(--text-100); white-space: nowrap;
  background: var(--raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-lg);
  pointer-events: none;
  opacity: 0; transform: translateX(-4px);
  transition: opacity var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
.rail-flyout.in { opacity: 1; transform: translateX(0); }
/* a small pointer notch on the left edge, pointing back at the icon */
.rail-flyout::before {
  content: ""; position: absolute; left: -4px; top: 50%; margin-top: -4px;
  width: 8px; height: 8px; transform: rotate(45deg);
  background: var(--raised);
  border-left: 1px solid var(--border-strong);
  border-bottom: 1px solid var(--border-strong);
}
.rail-flyout .rail-flyout-badge {
  margin-left: 8px; padding: 1px 6px; border-radius: var(--r-pill);
  font-size: var(--fs-2xs); font-weight: 600; line-height: 1.4;
  color: var(--text-300); background: var(--surface-2); border: 1px solid var(--border);
}
@media (prefers-reduced-motion: reduce) {
  .rail-flyout { transition: opacity var(--t-fast) linear; transform: none; }
  .rail-flyout.in { transform: none; }
}

/* ===============================================================
   TOPBAR
=============================================================== */
.topbar {
  grid-area: topbar;
  display: flex; align-items: center; gap: var(--sp-4);
  height: var(--topbar-h); padding: 0 var(--sp-5);
  background: var(--surface-glass);
  backdrop-filter: blur(16px) saturate(140%);
  border-bottom: 1px solid var(--border);
  z-index: var(--z-topbar);
  font-feature-settings: "cv05" 1, "cv08" 1, "ss03" 1;
  -webkit-font-smoothing: antialiased;
}
.topbar-left { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; flex: 0 0 auto; }
.breadcrumb { display: flex; align-items: center; gap: 6px; min-width: 0; }
.crumb { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-sm); font-weight: 500; letter-spacing: -0.006em; color: var(--text-200); white-space: nowrap; }
.crumb-root { color: var(--text-100); font-weight: 600; letter-spacing: -0.01em; }
.crumb-root svg { color: var(--text-300); }
.crumb-sep { display: inline-flex; color: var(--text-500); }
.crumb-sep svg { width: 14px; height: 14px; }

/* global search trigger */
.global-search {
  flex: 1; max-width: 560px; margin: 0 auto;
  display: flex; align-items: center; gap: 10px; height: 36px; padding: 0 12px;
  border-radius: var(--r-pill); background: var(--surface-1); border: 1px solid var(--border);
  color: var(--text-400); transition: background var(--t-fast) var(--ease-smooth), border-color var(--t-fast) var(--ease-smooth), color var(--t-fast) var(--ease-smooth), box-shadow var(--t-fast) var(--ease-smooth), transform var(--t-fast) var(--ease-smooth);
}
.global-search:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--text-300); }
.global-search:active { transform: scale(0.995); }
.global-search svg { color: var(--text-400); flex-shrink: 0; }
.gs-placeholder { font-size: var(--fs-sm); font-weight: 500; letter-spacing: -0.006em; flex: 1; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gs-kbd { display: inline-flex; gap: 3px; flex-shrink: 0; }
kbd {
  font-family: var(--font-mono); font-size: 0.66rem; line-height: 1; color: var(--text-300);
  background: var(--surface-3); border: 1px solid var(--border); border-bottom-width: 2px;
  border-radius: 5px; padding: 3px 5px; min-width: 17px; text-align: center;
}

.topbar-right { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.topbar-right .btn-icon { color: var(--text-300); }
.topbar-right .btn-icon.active { color: var(--text-100); background: var(--surface-3); }
.notif-btn { position: relative; }
.notif-dot { position: absolute; top: 7px; right: 8px; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 2px var(--bg); }

/* ---- Topbar tooltips MUST open DOWNWARD (never clip above y=0) ----
   The global [data-tip] rule (components.css) opens upward, which clips above
   the viewport for topbar triggers. Override so topbar tips drop below the
   trigger; the right-most tip right-aligns so it never spills past the edge. */
.topbar [data-tip]:hover::after {
  bottom: auto; top: calc(100% + 7px);
}
.topbar-right > [data-tip]:last-child:hover::after {
  left: auto; right: 0; transform: none;
}

/* ===============================================================
   CONTENT
=============================================================== */
.content { grid-area: content; overflow: hidden; min-height: 0; position: relative; }
#view {
  height: 100%; overflow-y: auto; overflow-x: hidden;
  padding: var(--sp-6) var(--sp-7) var(--sp-10);
}
#view > .view, .view { max-width: 1320px; margin: 0 auto; }

/* ---------------------------------------------------------------
   ONE clean view transition (replaces the per-view cascade on nav)
   The first load keeps the rich .rise/.stagger reveal. After that,
   `body.nav-ready` neutralizes those per-element animations so tabs
   switch crisply — only #view does a fast 120ms opacity/translate
   fade. No cascade re-trigger, no flash, no layout jump.
--------------------------------------------------------------- */
@keyframes eos-view-enter { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
body.nav-ready #view.view-enter { animation: eos-view-enter 120ms var(--ease-out) both; }
/* Gate the per-element cascade to first load only — our CSS wins even
   if a view re-adds .rise/.stagger on every navigation. */
body.nav-ready #view .rise,
body.nav-ready #view .stagger > * {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}
@media (prefers-reduced-motion: reduce) {
  body.nav-ready #view.view-enter { animation: none; }
}

/* standard view header */
.view-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-5);
  margin-bottom: var(--sp-6); flex-wrap: wrap;
}
.view-head .view-title-wrap { min-width: 0; }
.view-head .eyebrow { margin-bottom: 7px; }
.view-head h1 { font-family: var(--font-display); font-size: var(--fs-display); font-weight: 600; letter-spacing: -0.022em; line-height: 1.1; }
.view-head .view-sub { color: var(--text-400); margin-top: 9px; font-size: var(--fs-body); max-width: 60ch; }
.view-head .view-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; flex-shrink: 0; }
.view-body { margin-top: var(--sp-6); }

/* router fallback */
.coming-online {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 10px; min-height: 60vh; padding: var(--sp-8);
}
.coming-online-ring {
  width: 72px; height: 72px; border-radius: 50%; display: grid; place-items: center;
  color: var(--text-300); margin-bottom: var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  animation: eos-pulse 2.6s var(--ease-in-out) infinite;
}
.coming-online h2 { font-size: 2rem; }
.coming-online .eyebrow { margin-bottom: 2px; }

/* ===============================================================
   ASSISTANT MESSAGE PRIMITIVES
   The persistent right dock was removed — the assistant lives on the
   AI Chat route (ai-chat.js owns its own bubble layout). These message
   primitives remain only because shared helpers (action cards) and any
   legacy chat markup may still reference them.
=============================================================== */
.msg-stack { display: flex; flex-direction: column; gap: 8px; min-width: 0; max-width: 84%; }
.msg-avatar { width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center; flex-shrink: 0; margin-top: 2px; }
.msg-avatar.ai { color: var(--text-100); background: var(--surface-3); border: 1px solid var(--border); }
.msg-bubble {
  padding: 10px 13px; border-radius: var(--r-lg); font-size: var(--fs-sm); line-height: 1.5;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-200);
  max-width: 100%; white-space: pre-wrap; overflow-wrap: anywhere;
}
/* assistant suggestion chip — shared by the AI Chat composer */
.sugg-chip {
  font-size: var(--fs-xs); font-weight: 500; color: var(--text-300);
  padding: 6px 11px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--border);
  transition: background var(--t-fast) var(--ease-smooth), color var(--t-fast) var(--ease-smooth), border-color var(--t-fast) var(--ease-smooth), transform var(--t-fast) var(--ease-smooth); white-space: nowrap;
}
.sugg-chip:hover { background: var(--surface-3); color: var(--text-100); }
.sugg-chip:active { transform: scale(0.97); }
.sugg-chip:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }

/* ===============================================================
   SHARED DRAG-AND-DROP VISUAL SYSTEM  (SortableJS)
   Used by EVERY board/list across the OS — do NOT rename these classes.

   The canonical SortableJS option set now lives in code as HOUSE_DND
   in assets/js/dnd.js — that file is the single source of truth. Wire
   any board/list with makeSortable(el, { group, handle, filter, ... });
   it spreads HOUSE_DND (animation 180, easing cubic-bezier(.22,1,.36,1),
   ghostClass dnd-ghost / chosenClass dnd-chosen / dragClass dnd-drag,
   forceFallback, fallbackOnBody, fallbackTolerance 4, swapThreshold 0.65,
   emptyInsertThreshold 16, scroll + scrollSensitivity 80 / scrollSpeed 12)
   and adds/removes body.dnd-scrolling around the drag. The classes below
   are the shared look those options reference — keep them here, and do
   NOT re-declare per view.
=============================================================== */
/* placeholder gap left where the card will drop — a CLEAR drop zone so you can
   see exactly where the card lands (the old accent-ring @14% dashed line + 6%
   fill was nearly invisible in dark mode). Uses color-mix on --accent so it
   reads in BOTH themes (accent = near-white dark / near-black light). */
.dnd-ghost {
  position: relative;
  border: 2px dashed color-mix(in srgb, var(--accent) 55%, transparent) !important;
  background: color-mix(in srgb, var(--accent) 11%, transparent) !important;
  box-shadow: none !important;
  border-radius: var(--r-lg, 12px) !important;
  opacity: 1;
  min-height: 46px;
}
.dnd-ghost > * { visibility: hidden; }   /* hide contents, keep the shape/radius */

/* Highlight the drop target the card is currently over — the "show me the zone
   I'm dropping into" cue. Every sortable container is tagged .dnd-list by the
   shared makeSortable() helper, so this works IDENTICALLY on ALL boards (CRM,
   Tasks, Projects, Priorities, Content…) regardless of their own column class.
   On boards with a .board-col wrapper we lift the WHOLE column for a nicer look;
   otherwise the list container itself lights up. Only active during a drag. */
body.dnd-scrolling .dnd-list:has(.dnd-ghost) {
  outline: 2px solid color-mix(in srgb, var(--accent) 34%, transparent);
  outline-offset: -2px;
  border-radius: var(--r-lg, 12px);
  box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(--accent) 5%, transparent);
  transition: box-shadow .12s ease, outline-color .12s ease;
}
body.dnd-scrolling .board-col:has(.dnd-ghost) {
  outline: 2px solid color-mix(in srgb, var(--accent) 46%, transparent);
  outline-offset: -2px;
  border-radius: var(--r-lg, 12px);
  box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(--accent) 8%, transparent);
  transition: box-shadow .12s ease, outline-color .12s ease;
}
/* when the whole column lifts, don't also double-outline the inner list */
body.dnd-scrolling .board-col:has(.dnd-ghost) .dnd-list:has(.dnd-ghost) {
  outline: none; box-shadow: none;
}
/* an empty-column "Drop here" affordance lights up as an active target.
   Every board's empty-column placeholder carries one of these classes, so the
   affordance fires identically on CRM, Content, Projects, Tasks (board + plan)
   and Priorities — not just CRM. */
body.dnd-scrolling .dnd-list:has(.dnd-ghost) :is(.crm-col-empty, .ct-col-empty, .pj-empty-col, .tk-empty, .tk-col-empty, .plan-empty, .v-pri-empty),
body.dnd-scrolling .board-col:has(.dnd-ghost) :is(.crm-col-empty, .ct-col-empty, .pj-empty-col, .tk-empty, .tk-col-empty, .plan-empty, .v-pri-empty) {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent) !important;
  color: var(--text-200) !important;
}
/* the original card while it's being dragged */
.dnd-chosen { cursor: grabbing; opacity: 0.5; }
/* the floating clone that follows the cursor — lifted + tinted so the drag
   feels physical. The lift reads via shadow + accent border/outline (NOT a CSS
   transform): with forceFallback, Sortable repositions this clone by writing an
   inline `transform: translate3d(...)` on EVERY pointer move, so any CSS
   transform here is overwritten after one frame (the old scale/rotate only
   flashed once, then vanished — "sometimes I see the lift, sometimes I don't").
   Crucially, `transition: none !important` kills ANY transition on transform —
   both a local one and the one the cloned card inherits from its own base class
   (e.g. .kard has `transition: … transform …`). Without this the clone eases
   toward each new position over 150ms and visibly lags/wobbles behind the
   cursor (the "super slow, doesn't look good"). The clone must be glued to the
   pointer, so it gets no transition at all. */
.dnd-drag {
  cursor: grabbing;
  background: var(--surface-0);
  box-shadow: var(--shadow-elevated), 0 22px 40px -12px rgba(0,0,0,0.35) !important;
  border-color: var(--accent-ring) !important;
  outline: 1px solid var(--accent-ring); outline-offset: -1px;
  opacity: 1 !important;
  transition: none !important;
  /* CRITICAL: kill any inherited keyframe ANIMATION on the clone. Cards carry an
     entrance reveal (mo-enter → animation: mo-slide-up, which animates transform
     translateY(8px)→none). SortableJS's forceFallback positions the clone by
     writing an inline `transform: translate3d(...)` on every pointer move — but a
     running CSS animation OVERRIDES inline styles (per spec), so the reveal's
     transform wins and the clone stays pinned near its origin instead of tracking
     the cursor (the "dragging really sucks / card doesn't follow" bug). Cloning
     the card restarts that animation, so it must be forced off here. `transition:
     none` (above) alone was NOT enough — that only covers transitions. */
  animation: none !important;
  will-change: transform; /* composite the moving clone on the GPU for a smooth drag */
}
/* soft settle when a card lands — a spring overshoot that resolves to rest.
   Views add .dnd-settle to evt.item on drop (see settle() helpers). */
@keyframes dnd-settle-house {
  0%   { transform: scale(1.045); box-shadow: var(--shadow-elevated); }
  55%  { transform: scale(0.992); }
  100% { transform: scale(1); box-shadow: var(--shadow-card); }
}
.dnd-settle { animation: dnd-settle-house var(--t-slow) var(--ease-spring); }
/* while a drag is in progress: stop text selection + smooth the cursor */
body.dnd-scrolling { cursor: grabbing; user-select: none; }
body.dnd-scrolling * { cursor: grabbing !important; }
/* anything draggable should advertise it */
[data-draggable], .dnd-handle { cursor: grab; }

/* ===============================================================
   COMMAND PALETTE
=============================================================== */
.palette-backdrop {
  position: fixed; inset: 0; z-index: var(--z-palette);
  background: rgba(0,0,0,0.32); backdrop-filter: blur(4px);
  display: flex; align-items: flex-start; justify-content: center; padding-top: 13vh;
  opacity: 0; pointer-events: none; transition: opacity var(--t-fast);
}
body.palette-open .palette-backdrop { opacity: 1; pointer-events: auto; }
.palette {
  width: min(640px, 92vw); max-height: 64vh; display: flex; flex-direction: column;
  background: var(--raised);
  border: 1px solid var(--border-strong); border-radius: var(--r-xl);
  box-shadow: var(--shadow-xl); overflow: hidden;
  transform: translateY(-12px) scale(0.98); opacity: 0;
  transition: transform var(--t-med) var(--ease-out), opacity var(--t-med);
}
body.palette-open .palette { transform: none; opacity: 1; }
.palette-input-row { display: flex; align-items: center; gap: 11px; padding: 15px 17px; border-bottom: 1px solid var(--border); }
.palette-input-row > svg { color: var(--text-400); flex-shrink: 0; }
.palette-input { flex: 1; background: none; border: none; outline: none; font-size: 1.02rem; letter-spacing: -0.012em; color: var(--text-100); }
.palette-input::placeholder { color: var(--text-500); }
.palette-results { flex: 1; min-height: 0; overflow-y: auto; padding: 8px; }
.palette-group { font-size: var(--fs-2xs); font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-500); padding: 10px 12px 5px; }
.palette-row { display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 12px; border-radius: var(--r-sm); text-align: left; transition: background var(--t-fast); }
.palette-row.sel { background: var(--surface-3); }
.palette-row-ico { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; background: var(--surface-2); border: 1px solid var(--border); color: var(--text-300); flex-shrink: 0; }
.palette-row-ico.gold { color: var(--text-100); background: var(--accent-soft); border-color: var(--border-strong); }
.palette-row.sel .palette-row-ico { color: var(--text-100); }
.palette-row-label { flex: 1; font-size: var(--fs-body); color: var(--text-100); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.palette-row-hint { font-size: var(--fs-2xs); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-500); flex-shrink: 0; }
.palette-foot { display: flex; align-items: center; gap: var(--sp-4); padding: 10px 16px; border-top: 1px solid var(--border); font-size: var(--fs-2xs); color: var(--text-500); }
.palette-foot span { display: inline-flex; align-items: center; gap: 5px; }
.palette-foot kbd { font-size: 0.6rem; padding: 2px 4px; }

/* ===============================================================
   POPOVERS (notifications, menus)
=============================================================== */
.popover {
  position: fixed; z-index: var(--z-modal);
  background: var(--ink-600); border: 1px solid var(--border-strong);
  border-radius: var(--r-md); box-shadow: var(--shadow-xl);
  opacity: 0; transform: translateY(-6px) scale(0.98);
  transition: opacity var(--t-fast), transform var(--t-fast) var(--ease-out);
}
.popover.in { opacity: 1; transform: none; }
.notif-pop { width: 360px; max-width: 92vw; padding: 6px; }
.popover-head { display: flex; align-items: center; justify-content: space-between; padding: 9px 10px 9px 12px; }
.popover-title { font-weight: 600; color: var(--text-100); font-size: var(--fs-sm); }
.notif-list { max-height: 60vh; overflow-y: auto; }
.notif-item { display: flex; gap: 11px; padding: 11px 10px; border-radius: var(--r-sm); transition: background var(--t-fast); cursor: default; }
.notif-item:hover { background: var(--surface-2); }
.notif-item.unread { background: var(--accent-soft); }
.notif-ico { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; flex-shrink: 0; background: var(--surface-3); color: var(--text-300); border: 1px solid var(--border); }
.notif-ico.gold { color: var(--text-100); background: var(--accent-soft); border-color: var(--border); }
.notif-ico.green { color: var(--green-400); background: var(--green-bg); }
.notif-ico.blue { color: var(--blue-400); background: var(--blue-bg); }
.notif-ico.red { color: var(--red-400); background: var(--red-bg); }
.notif-ico.violet { color: var(--violet-400); background: var(--violet-bg); }
.notif-body { min-width: 0; flex: 1; }
.notif-title { font-size: var(--fs-sm); font-weight: 600; color: var(--text-100); }
.notif-detail { font-size: var(--fs-xs); line-height: 1.45; margin-top: 2px; }
.notif-time { font-size: var(--fs-2xs); margin-top: 5px; display: block; }
.menu-item.active { color: var(--text-100); }
.menu-item.active svg { color: var(--text-100); }

/* ===============================================================
   MODALS
=============================================================== */
.modal-backdrop {
  position: fixed; inset: 0; z-index: var(--z-modal);
  background: rgba(0,0,0,0.4); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; padding: var(--sp-5);
  opacity: 0; transition: opacity var(--t-med);
}
.modal-backdrop.in { opacity: 1; }
.modal-backdrop.out { opacity: 0; }
.modal {
  width: min(520px, 94vw); max-height: 88vh; display: flex; flex-direction: column;
  background: var(--raised);
  border: 1px solid var(--border-strong); border-radius: var(--r-xl);
  box-shadow: var(--shadow-xl);
  transform: translateY(14px) scale(0.97); opacity: 0;
  transition: transform var(--t-med) var(--ease-out), opacity var(--t-med);
}
.modal-backdrop.in .modal { transform: none; opacity: 1; }
.modal-wide { width: min(820px, 95vw); }
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: 18px 18px 14px; border-bottom: 1px solid var(--border); }
.modal-ico { width: 32px; height: 32px; border-radius: var(--r-md); display: grid; place-items: center; color: var(--text-100); background: var(--surface-3); border: 1px solid var(--border); flex-shrink: 0; }
.modal-title { font-family: var(--font-display); font-size: 1.2rem; font-weight: 600; letter-spacing: -0.012em; color: var(--text-100); }
.modal-body { padding: 18px; overflow-y: auto; flex: 1; min-height: 0; }
.modal-foot { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2); padding: 14px 18px; border-top: 1px solid var(--border); }

/* guardrail variant */
.modal-guardrail { border-color: var(--amber-400); box-shadow: var(--shadow-xl); }
.modal-guardrail .modal-head { border-bottom-color: var(--border); }
.guardrail { text-align: center; padding: var(--sp-4) var(--sp-3) var(--sp-2); }
.guardrail-shield { width: 60px; height: 60px; border-radius: 50%; margin: 0 auto var(--sp-4); display: grid; place-items: center; color: var(--amber-400); background: var(--amber-bg); border: 1px solid var(--amber-400); }
.guardrail-action { font-family: var(--font-display); font-size: 1.15rem; color: var(--text-100); font-weight: 600; letter-spacing: -0.012em; }
.guardrail-detail { font-size: var(--fs-sm); line-height: 1.55; margin: 10px auto 0; max-width: 46ch; }

/* Mobile: every modal becomes a bottom sheet — CONSOLIDATED here so every surface
   behaves identically. Views must NOT redeclare this in their own media queries. */
@media (max-width: 680px) {
  .modal-backdrop { align-items: flex-end; padding: 0; }
  .modal { width: 100vw; max-height: 92vh; border-radius: var(--r-xl) var(--r-xl) 0 0; transform: translateY(100%); }
  .modal-backdrop.in .modal { transform: none; }
}

/* ===============================================================
   TOASTS
=============================================================== */
.toast-wrap { position: fixed; right: 20px; bottom: 88px; z-index: var(--z-toast); display: flex; flex-direction: column; gap: 10px; align-items: flex-end; pointer-events: none; }
.toast {
  display: flex; align-items: center; gap: 11px; padding: 12px 15px; min-width: 240px; max-width: 380px;
  border-radius: var(--r-md); font-size: var(--fs-sm); color: var(--text-100); pointer-events: auto; cursor: pointer;
  background: var(--raised);
  border: 1px solid var(--border-strong); box-shadow: var(--shadow-lg);
  transform: translateX(120%); opacity: 0; transition: transform var(--t-med) var(--ease-out), opacity var(--t-med);
}
.toast.in { transform: none; opacity: 1; }
.toast.out { transform: translateX(120%); opacity: 0; }
.toast-ico { width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; flex-shrink: 0; background: var(--surface-3); color: var(--text-300); }
.toast-msg { line-height: 1.4; }
.toast-success .toast-ico { color: var(--green-400); background: var(--green-bg); }
.toast-error .toast-ico { color: var(--red-400); background: var(--red-bg); }
.toast-ai .toast-ico { color: var(--text-100); background: var(--accent-soft); }
.toast-ai { border-color: var(--border-strong); }
.toast-warn .toast-ico { color: var(--amber-400); background: var(--amber-bg); }

/* ===============================================================
   ACTION CARDS (assistant tool results — AI Chat)
=============================================================== */
.action-cards { display: flex; flex-direction: column; gap: 8px; width: 100%; }
.action-card {
  display: flex; align-items: flex-start; gap: 10px; width: 100%;
  padding: 11px 12px; border-radius: var(--r-md);
  background: var(--surface-1); border: 1px solid var(--border);
  animation: eos-rise var(--t-med) var(--ease-out) both;
}
.action-card.ac-guard { flex-direction: column; gap: 11px; border-color: var(--amber-400); background: var(--amber-bg); }
.ac-ico { width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center; flex-shrink: 0; border: 1px solid var(--border); }
.ac-ico.ok { color: var(--green-400); background: var(--green-bg); border-color: color-mix(in srgb, var(--green-400) 30%, transparent); }
.ac-ico.err { color: var(--red-400); background: var(--red-bg); border-color: color-mix(in srgb, var(--red-400) 30%, transparent); }
.ac-ico.guard { color: var(--amber-400); background: color-mix(in srgb, var(--amber-400) 14%, transparent); border-color: color-mix(in srgb, var(--amber-400) 38%, transparent); }
.ac-body { min-width: 0; flex: 1; }
.ac-summary { font-size: var(--fs-sm); color: var(--text-100); line-height: 1.45; }
.ac-link {
  display: inline-flex; align-items: center; gap: 4px; margin-top: 6px;
  font-size: var(--fs-xs); font-weight: 600; color: var(--text-200);
  padding: 3px 8px; border-radius: var(--r-pill); background: var(--surface-3); border: 1px solid var(--border);
  transition: color var(--t-fast) var(--ease-smooth), border-color var(--t-fast) var(--ease-smooth), background var(--t-fast) var(--ease-smooth), transform var(--t-fast) var(--ease-smooth);
}
.ac-link:hover { color: var(--text-100); border-color: var(--border-strong); }
.ac-link:active { transform: scale(0.97); }
.ac-guard-top { display: flex; align-items: flex-start; gap: 10px; width: 100%; }
.ac-guard-label { font-size: var(--fs-2xs); font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--amber-400); display: inline-flex; align-items: center; gap: 5px; margin-bottom: 3px; }
.ac-guard-foot { display: flex; align-items: center; gap: 8px; justify-content: flex-end; width: 100%; padding-top: 9px; border-top: 1px solid color-mix(in srgb, var(--amber-400) 24%, transparent); }
.ac-approving { font-size: var(--fs-xs); color: var(--text-300); display: inline-flex; align-items: center; gap: 6px; font-weight: 600; }
.ac-approving svg { animation: eos-spin 1s linear infinite; }
.ac-approving.err { color: var(--red-400); }
.ac-args { font-family: var(--font-mono); font-size: var(--fs-xs); line-height: 1.5; color: var(--text-200); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 10px 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
@keyframes eos-spin { to { transform: rotate(360deg); } }

/* palette result rows with subtitles + spinner */
.palette-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.palette-row-main .palette-row-label { flex: none; }
.palette-row-sub { font-size: var(--fs-2xs); color: var(--text-400); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.palette-spinner { display: inline-flex; color: var(--text-400); }
.palette-spinner svg { animation: eos-spin 1s linear infinite; }
.palette .empty { text-align: center; padding: var(--sp-7) var(--sp-4); color: var(--text-400); }
.palette .empty .ico { display: inline-flex; color: var(--text-500); margin-bottom: var(--sp-3); }

/* user-chip / generic menu */
.popover.menu { min-width: 210px; padding: 5px; }
.menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; border-radius: var(--r-sm); font-size: var(--fs-sm); font-weight: 500; letter-spacing: -0.006em; color: var(--text-200); text-align: left; transition: background var(--t-fast), color var(--t-fast); }
.menu-item svg { color: var(--text-400); flex-shrink: 0; }
.menu-item:hover { background: var(--surface-2); color: var(--text-100); }
.menu-item:hover svg { color: var(--text-200); }
.menu-item.danger { color: var(--red-400); }
.menu-item.danger svg { color: var(--red-400); }
.menu-item.danger:hover { background: var(--red-bg); color: var(--red-400); }

/* notif empty */
.notif-empty { text-align: center; padding: var(--sp-7) var(--sp-4); }
.notif-empty .ico { display: inline-flex; color: var(--text-500); margin-bottom: var(--sp-2); }

/* shortcut cheat-sheet */
.shortcut-grid { display: flex; flex-direction: column; gap: 2px; }
.shortcut-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); padding: 9px 6px; border-bottom: 1px solid var(--border-soft); }
.shortcut-row:last-child { border-bottom: none; }
.shortcut-keys { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; }
.shortcut-desc { font-size: var(--fs-sm); color: var(--text-200); text-align: right; }

/* mobile drawer backdrop (created lazily by app.js) */
.rail-backdrop {
  position: fixed; inset: 0; z-index: calc(var(--z-rail) - 1);
  background: rgba(0,0,0,0.42); backdrop-filter: blur(2px);
  opacity: 0; pointer-events: none; transition: opacity var(--t-med);
}
body.rail-open .rail-backdrop { opacity: 1; pointer-events: auto; }

/* ===============================================================
   RESPONSIVE
=============================================================== */
/* ---- TABLET (901–1100px): rail collapses to an icon strip in-grid ---- */
@media (max-width: 1100px) and (min-width: 901px) {
  .app { grid-template-columns: var(--rail-w-collapsed) minmax(0, 1fr); }
  .rail .brand-lockup, .rail-label, .rail-item .badge,
  .rail-group-label, .user-meta, .user-gear, .rail-collapse { display: none; }
  .rail-item { justify-content: center; padding: 0; }
  .user-chip { justify-content: center; }
  .rail-group { align-items: center; }
  .global-search { max-width: none; }
}

/* ---- MOBILE (≤900px): rail becomes a TRUE off-canvas drawer ----
   The grid collapses to a single content column so the rail can never
   overlay content. The rail is taken out of the grid, fixed at full
   width, and translated off-screen until body.rail-open. */
@media (max-width: 900px) {
  /* drop .app's own stacking context so the fixed rail can sit above the
     body-level backdrop (otherwise the rail is trapped at .app's z-index:1
     and the backdrop, a body child, paints over it). */
  .app { z-index: auto; }
  .app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "topbar"
      "content";
  }

  .rail {
    position: fixed; top: 0; bottom: 0; left: 0;
    width: min(86vw, var(--rail-w));
    transform: translateX(-100%);
    transition: transform var(--t-med) var(--ease-out);
    box-shadow: var(--shadow-xl);
    z-index: var(--z-modal);   /* above the backdrop (z-rail) at body level */
  }
  body.rail-open .rail { transform: none; }

  /* the drawer always shows the FULL labelled rail (never the icon strip) */
  body.rail-collapsed .rail .brand-lockup, body.rail-collapsed .rail-label,
  body.rail-collapsed .rail-item .badge, body.rail-collapsed .rail-group-label,
  body.rail-collapsed .user-meta, body.rail-collapsed .user-gear { display: flex; }
  .rail-collapse { display: none; }

  .topbar .rail-toggle-m { display: inline-flex; }
  .global-search { max-width: none; }

  /* content is full-width; no left offset, no overlap */
  .content { grid-area: content; }

  /* Tap targets ≥ 40px for in-body / sheet controls on mobile (CONTEXT
     mandate). Mobile-only (whole block is @media ≤900px) — desktop density is
     untouched. min-height overrides the fixed heights on .btn (36) / .btn-sm
     (30) / segmented + sub-tab buttons (~27-30) without changing their width,
     so no toolbar re-wraps. The rail hamburger (.rail-toggle-m, a
     btn-icon.btn-sm 30×30) is squared up to 40×40. */
  .btn,
  .btn-sm,
  .segmented button,
  .subtabs button { min-height: 40px; }
  .topbar .rail-toggle-m { min-width: 40px; min-height: 40px; }
}

/* ---- PHONE (≤760px): compress topbar + content padding ---- */
@media (max-width: 760px) {
  #view { padding: var(--sp-5) var(--sp-4) var(--sp-9); }
  .view-head h1 { font-size: 1.9rem; }
  .topbar { gap: var(--sp-2); padding: 0 var(--sp-3); }
  .global-search .gs-placeholder { display: none; }
  .global-search { flex: 0 0 auto; width: 38px; height: 38px; padding: 0; justify-content: center; margin: 0 auto 0 0; }
  .gs-kbd { display: none; }
  .breadcrumb .crumb:not(.crumb-root), .breadcrumb .crumb-sep { display: none; }

  /* tap targets ≥ 40px */
  .topbar-right .btn-icon, .rail-toggle-m, .global-search { min-width: 40px; min-height: 40px; }
  .rail-item { height: 44px; }
}
