/* INT-1757 — Logged-in dashboard widget controls.
   Enhances the existing ReefMind app shell; does not replace dashboard.html. */
/* INT-2578 (Meghann): the toolbar is ACTIONS ONLY — the "Curated dashboard"
   heading and its explanatory paragraph are gone, so the row is compact and
   right-aligned instead of a headline/actions split. */
.dashboard-widget-controls{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin:0 0 14px;padding:8px 12px;border:1px solid rgba(0,212,170,.18);border-radius:16px;background:rgba(0,212,170,.06);box-shadow:0 4px 16px rgba(0,0,0,.10)}
.dashboard-widget-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}
.dashboard-widget-actions .btn{white-space:nowrap;min-height:44px}
.dashboard-widget-card{position:relative}
/* renderHealthScore replaces the card's class list during hydration, so keep
   the painted card itself as the absolute-positioning root for its menu. */
.health-score-card{position:relative}
.dashboard-widget-hidden-by-user{display:none!important}

/* ── The ONE edit rail (INT-2578 second remediation) ─────────────────────────
   There is no per-widget menu anchoring any more. Placement modes — a menu
   flexed into the section's own `.sec-hdr` title row, or absolutely inset into
   the top-right corner of a root that paints its own card — were exactly the
   two divergent anatomies Meghann rejected, and the corner one dropped a 44px
   grip and a 44px kebab straight onto Captain Chris's own "All Good" badge and
   "Preparing…" status line.

   Every managed widget now carries one identical rail, emitted by JS as the
   FIRST child of the managed wrapper and laid out IN FLOW above the widget's
   own title/content surface. Because it consumes layout space it cannot cover
   anything — no offsets, no exceptions, no z-order race.

   Outside edit mode JS sets `hidden` on it, so it collapses to display:none and
   viewing mode is byte-for-byte the clean card it always was. */
.dashboard-widget-card > .sec-hdr{display:flex;align-items:center;gap:8px;min-width:0}
.dashboard-widget-rail{position:relative;z-index:8;display:flex;align-items:center;justify-content:flex-end;gap:8px;margin:0 0 10px}
.ai-widget{position:relative}
/* One restrained circle for both rail buttons — a hairline outline over a
   dark-translucent disc, matching the Captain Chris card menu. Not a bright
   filled button. */
.dashboard-widget-menu-btn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:999px;border:1px solid rgba(255,255,255,.12);background:rgba(4,12,20,.68);color:var(--text,#e5eef7);cursor:pointer;-webkit-tap-highlight-color:transparent}
.dashboard-widget-menu-btn:hover,.dashboard-widget-menu-btn:focus-visible{background:rgba(10,24,38,.85);border-color:rgba(255,255,255,.2)}
.dashboard-widget-popover{position:absolute;right:0;top:calc(100% + 6px);min-width:220px;max-width:min(280px, calc(100vw - 24px));padding:9px;border:1px solid rgba(255,255,255,.14);border-radius:12px;background:rgba(8,16,28,.98);box-shadow:0 18px 40px rgba(0,0,0,.35);z-index:40}
/* Flip up when the trigger sits near the bottom of the viewport. */
.dashboard-widget-popover--up{top:auto;bottom:calc(100% + 6px)}
.dashboard-widget-note{margin:0 0 8px;color:var(--text-dim,rgba(226,232,240,.68));font-size:.72rem;line-height:1.35}
.dashboard-widget-move-up,.dashboard-widget-move-down{display:block;width:100%;min-height:44px;padding:8px 10px;margin-top:6px;border:1px solid rgba(255,255,255,.14);border-radius:10px;background:rgba(255,255,255,.06);color:var(--text,#e5eef7);font-weight:700;cursor:pointer;text-align:left}

.dashboard-widget-library{position:fixed;inset:0;z-index:1000;overflow:auto;background:rgba(2,6,12,.64);backdrop-filter:blur(10px);padding:18px}
.dashboard-widget-library[hidden]{display:none}
.dashboard-widget-library-panel{position:relative;max-width:860px;margin:6vh auto;padding:22px;border:1px solid rgba(255,255,255,.14);border-radius:22px;background:var(--bg-card,#08111f);box-shadow:0 16px 48px rgba(0,0,0,.38)}
.dashboard-widget-library-close{display:inline-flex;align-items:center;justify-content:center;position:absolute;top:14px;right:14px;width:44px;height:44px;border-radius:999px;border:1px solid rgba(255,255,255,.16);background:rgba(255,255,255,.06);color:var(--text,#e5eef7);cursor:pointer}
.dashboard-widget-library h2{margin:0 52px 6px 0;color:var(--text-bright,#f8fafc);font-size:1.4rem}
.dashboard-widget-library p{color:var(--text-dim,rgba(226,232,240,.68));line-height:1.45}
.dashboard-widget-library label{display:block;margin-top:14px;margin-bottom:6px;font-size:.78rem;font-weight:800;color:var(--text,#e5eef7)}
#dashboard-widget-search{width:100%;max-width:520px;margin-bottom:16px;padding:11px 12px;border-radius:12px;border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.06);color:var(--text-bright,#f8fafc)}
.dashboard-widget-library-heading{margin:18px 0 8px;color:var(--text-bright,#f8fafc);font-size:.9rem;font-weight:800}
/* #2552 follow-up: the explicit Save changes / Reset to default row, plus the
   live region that tells the truth about cross-device persistence. */
.dashboard-widget-manager-footer{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin:12px 0 4px}
.dashboard-widget-manager-footer button{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 16px;border-radius:12px;font-weight:800;cursor:pointer}
.dashboard-widget-manager-footer button:disabled{cursor:progress;opacity:.6}
.dashboard-widget-save-status{display:flex;align-items:center;gap:10px;flex:1 1 220px;margin:0;min-width:0;font-size:.78rem;line-height:1.4;color:var(--text-dim,rgba(226,232,240,.68));overflow-wrap:anywhere}
/* #2553: while Save is disabled mid-flight, focus is placed on this live region
   rather than dropped on <body>, so it needs a visible focus indicator. */
.dashboard-widget-save-status:focus{outline:2px solid var(--teal,#00d4aa);outline-offset:3px;border-radius:8px}
.dashboard-widget-save-status--saved{color:var(--teal,#00d4aa)}
.dashboard-widget-save-status--error{color:#fecaca}
.dashboard-widget-save-retry{flex:0 0 auto;min-height:44px;padding:0 12px;border-radius:12px;border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.06);color:var(--text-bright,#f8fafc);font-weight:800;cursor:pointer}

.dashboard-widget-library-banner{margin:0 0 12px;padding:9px 12px;border:1px solid rgba(0,212,170,.25);border-radius:12px;background:rgba(0,212,170,.07);color:var(--text,#e5eef7);font-size:.78rem;line-height:1.4}
.dashboard-widget-library-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}
.dashboard-widget-library-card{display:flex;flex-direction:column;gap:8px;min-width:0;padding:14px;border:1px solid rgba(255,255,255,.12);border-radius:16px;background:rgba(255,255,255,.045);overflow-wrap:anywhere}
.dashboard-widget-card-kicker{color:var(--teal,#00d4aa);font-size:.68rem;text-transform:uppercase;letter-spacing:.04em;font-weight:800}
.dashboard-widget-library-card h3{margin:0;color:var(--text-bright,#f8fafc);font-size:1rem}
.dashboard-widget-library-card p{margin:0;color:var(--text-dim,rgba(226,232,240,.68));font-size:.8rem;line-height:1.45}
/* INT-2578 (Meghann: "Add/move that to the add widgets screen"): what the widget
   DOES is the card's own always-visible prose — never a `?` the reefer has to
   find and hover. Setup guidance is a SECOND, visually distinct line, so
   availability adds to the description instead of replacing it. */
.dashboard-widget-library-setup{display:flex;gap:6px;color:#fbbf24;font-size:.74rem;line-height:1.4}
.dashboard-widget-library-setup::before{content:"⚑";flex:0 0 auto;opacity:.9}
.dashboard-widget-add-option{display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;width:100%;min-width:0;max-width:100%;margin-top:auto;min-height:44px;padding:9px 11px;border-radius:999px;border:1px solid rgba(0,212,170,.3);background:rgba(0,212,170,.10);color:var(--teal,#00d4aa);font-weight:800;cursor:pointer}
.dashboard-widget-catalog-remove{border-color:rgba(248,113,113,.3);background:rgba(248,113,113,.08);color:#fecaca}
.dashboard-widget-add-option:disabled{cursor:not-allowed;opacity:.68;border-color:rgba(255,255,255,.12);background:rgba(255,255,255,.06);color:var(--text-dim,rgba(226,232,240,.68))}
.dashboard-widget-toast{position:fixed;left:50%;bottom:20px;z-index:1200;transform:translateX(-50%);display:flex;align-items:center;gap:12px;max-width:min(680px,calc(100vw - 24px));padding:11px 13px;border:1px solid rgba(0,212,170,.28);border-radius:14px;background:rgba(6,15,27,.98);color:var(--text-bright,#f8fafc);box-shadow:0 18px 60px rgba(0,0,0,.45);font-size:.82rem}
.dashboard-widget-toast button{border:0;background:transparent;color:var(--teal,#00d4aa);font-weight:900;cursor:pointer}
/* #2553: an actionable toast raised while the aria-modal widget dialog is open is
   mounted INSIDE that dialog so keyboard/SR users can reach its Undo. The dialog
   is a fixed, full-viewport layer, so the toast keeps the same on-screen position;
   this only guarantees it paints above the dialog's own panel. */
.dashboard-widget-toast--in-dialog{z-index:1201}
.dashboard-widget-toast--in-dialog button:focus-visible{outline:2px solid var(--teal,#00d4aa);outline-offset:2px;border-radius:8px}
.dashboard-widget-library-card-gated{opacity:.72;border-style:dashed}
.dashboard-widget-library-card-gated .dashboard-widget-card-kicker{color:#fbbf24}

/* ── INT-2578 user-chosen card width ───────────────────────────────────────
   Three responsive modes the reefer picks per widget — never pixel resizing.
   grid-auto-flow stays `row`: dense packing would visually reorder cards away
   from the order the user saved, and they already control order. A card with no
   explicit mode (and any non-widget dashboard child) keeps a safe full row, so
   nothing can fall into an implicit column or overflow its track. Explicit
   classes override legacy widget-span-* geometry without removing it. */
html #tab-dashboard.active{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-flow:row;column-gap:16px}
html #tab-dashboard.active > .dashboard-widget-card--compact{grid-column:span 1;min-width:0}
html #tab-dashboard.active > .dashboard-widget-card--standard{grid-column:span 2;min-width:0}
html #tab-dashboard.active > .dashboard-widget-card--full{grid-column:1 / -1;min-width:0}
html #tab-dashboard.active > .dashboard-widget-card:not(.dashboard-widget-card--compact):not(.dashboard-widget-card--standard):not(.dashboard-widget-card--full){grid-column:1 / -1;min-width:0}
html #tab-dashboard.active > :not(.dashboard-widget-card){grid-column:1 / -1;min-width:0}

/* The width chooser inside the existing kebab popover — a labelled radio group
   next to Move/Remove, not a new toolbar. 44px targets; the current choice is
   both announced (aria-checked) and visible (tint + check glyph). */
.dashboard-widget-menu-group{margin:10px 0 4px;color:var(--text-dim,rgba(226,232,240,.68));font-size:.66rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em}
.dashboard-widget-size-group{display:flex;flex-direction:column}
.dashboard-widget-size{display:flex;align-items:center;gap:8px;width:100%;min-height:44px;padding:8px 10px;margin-top:6px;border:1px solid rgba(255,255,255,.14);border-radius:10px;background:rgba(255,255,255,.06);color:var(--text,#e5eef7);font-weight:700;text-align:left;cursor:pointer}
.dashboard-widget-size:hover,.dashboard-widget-size:focus-visible{background:rgba(10,24,38,.85);border-color:rgba(255,255,255,.24)}
.dashboard-widget-size-check{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:14px;height:14px;color:var(--teal,#00d4aa)}
.dashboard-widget-size[aria-checked="true"]{border-color:rgba(0,212,170,.4);background:rgba(0,212,170,.12);color:var(--teal,#00d4aa)}

/* ── INT-2578 even rows (Meghann, Discord 1532185538961084606) ───────────────
   "the widgets are all uneven even though they are the same size". Two causes,
   both structural, and both fixed by CONTAINMENT rather than by measuring
   anything:

     1. the grid declared `column-gap` only, so vertical rhythm came from each
        card's OWN margins (.controller-widget-card's 20px/10px, the .sec margins
        the adopted sections bring). Cards in one row therefore started at
        different y by exactly their margin difference — a row cannot align tops
        it does not own. `row-gap` moves that rhythm to the grid, and the cards
        give up their vertical margins;
     2. every card was display:block. Grid already stretches the ITEM to the row
        height, but a block card leaves its painted content surface at natural
        height inside that stretched box — so the edges a reefer actually SEES
        stayed ragged. The card becomes a flex column and its content surface
        takes the leftover height, so the painted box IS the row box.

   No fixed height, no height cap, no clipped overflow: a card with more content
   still grows its own row track, and rows size independently, so an unrelated
   row is never dragged to another row's height. `min-height:0` keeps a long
   flex child from refusing to shrink and overflowing its track.

   Scoped to DIRECT children of the grid — a nested managed widget (Skimmer lives
   inside Live cameras) keeps its own in-card rhythm untouched. Horizontal
   margins are left alone; only the vertical ones fight the row. */
html #tab-dashboard.active{row-gap:16px}
html #tab-dashboard.active > .dashboard-widget-card{display:flex;flex-direction:column;align-self:stretch;min-height:0;margin-top:0;margin-bottom:0}
/* The rail and the title lane keep their intrinsic height; the LAST content
   element absorbs the difference between this card's natural height and its row's
   height, so the painted surface bottom IS the row bottom. (ensureResizeHandle
   inserts the absolutely-positioned edge handle next to the rail precisely so
   `:last-child` here is content and never chrome.) */
html #tab-dashboard.active > .dashboard-widget-card > .dashboard-widget-rail,
html #tab-dashboard.active > .dashboard-widget-card > .sec-hdr{flex:0 0 auto}
html #tab-dashboard.active > .dashboard-widget-card > :last-child{flex:1 1 auto;min-height:0}
/* One uniform title LANE, so the painted surfaces below it start on the same
   line across a row. Without it the lane is as tall as whatever each widget puts
   in it — a plain uppercase label on one card, the taller provenance pill on the
   controller-scoped one — and the surfaces below inherit that difference. This is
   a min, not a height: a title that wraps still grows its own lane rather than
   being clipped, and it is one text row + the pill's padding/border, not a
   card-height guess. */
html #tab-dashboard.active > .dashboard-widget-card > .sec-hdr{min-height:1.5rem}
/* Textbook containment: the content stack's OUTER vertical margins belong to the
   row now, not to the widget. Only the first and last content elements are
   touched (the edge handle and the rail are the only things that can precede
   content), so each card keeps its own internal rhythm. Without this, Captain
   Chris's `.ai-widget` 8px margin and a `.sec-hdr` 20px margin re-introduce
   exactly the per-card offsets row-gap was brought in to replace. */
html #tab-dashboard.active > .dashboard-widget-card > .dashboard-widget-resize-handle + *,
html #tab-dashboard.active > .dashboard-widget-card > .dashboard-widget-rail + :not(.dashboard-widget-resize-handle){margin-top:0}
html #tab-dashboard.active > .dashboard-widget-card > :last-child{margin-bottom:0}
/* The drag proxy is position:fixed against its captured rect, so row containment
   must not also stretch it while it is following the pointer. */
html #tab-dashboard.active > .dashboard-widget-card--dragging{align-self:start}

/* ── INT-2578 (Meghann): the dedicated "Edit widgets" mode ──────────────────
   Viewing is clean; editing is an explicit, obviously-exitable mode. Every
   per-widget affordance is emitted by JS but carries `hidden` outside edit mode,
   so these rules only ever paint for a reefer who asked to edit. Every geometry
   rule stays within the dashboard widget surface. */
.dashboard-widget-menu[hidden],.dashboard-widget-drag-handle[hidden],.dashboard-widget-resize-handle[hidden]{display:none}
/* A quiet, structural marker for "this grid is editable" — a hairline inset rail,
   not a colour wash, and never colour alone: the handles themselves are the
   primary signal. */
html #tab-dashboard.active.dashboard-widgets-editing{outline:1px dashed rgba(0,212,170,.28);outline-offset:10px;border-radius:18px}
html .dashboard-widget-editable{position:relative}
.dashboard-widget-edit-toggle[aria-pressed="true"]{border-color:rgba(0,212,170,.45)}
.dashboard-widget-drag-handle{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;width:44px;height:44px;border-radius:999px;border:1px solid rgba(255,255,255,.12);background:rgba(4,12,20,.68);color:var(--text-dim,rgba(226,232,240,.68));cursor:grab;touch-action:none;-webkit-tap-highlight-color:transparent}
.dashboard-widget-drag-handle:hover{background:rgba(10,24,38,.85);border-color:rgba(255,255,255,.2);color:var(--text,#e5eef7)}
.dashboard-widget-drag-handle:focus-visible{outline:2px solid var(--teal,#00d4aa);outline-offset:2px}
.dashboard-widget-drag-handle[aria-grabbed="true"]{cursor:grabbing;border-color:rgba(0,212,170,.45);background:rgba(0,212,170,.14);color:var(--teal,#00d4aa)}
/* The dedicated edge/corner resize interaction is preserved and stays edge
   aligned — but an edge-pinned 44px column laid OVER the card's own content is
   the same defect as the corner kebab was, one edge along. So edit mode reserves
   the column instead of borrowing it: while editing, every managed card gets a
   44px handle + 8px breathing gutter of right padding, the content box ends
   before it, and the rail (right-aligned inside that same content box) ends
   before it too. The handle therefore overlaps neither intrinsic content nor
   either rail button, on any widget, without a single per-widget offset.
   Reserved only where a width enum is actually renderable: at one column the
   handle is hidden AND disabled (syncEditAffordances), so no gutter is taken. */
@media(min-width:721px){
  html .dashboard-widgets-editing .dashboard-widget-card{padding-right:52px}
}
/* The visible grip is a slim edge rail, but the TARGET is a full 44px column so
   it is reachable by touch. It is pinned INSIDE the card's own right edge — no
   control ever floats in an inter-card gutter. */
.dashboard-widget-resize-handle{position:absolute;top:50%;right:0;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;width:44px;height:44px;padding:0;border:0;border-radius:12px;background:transparent;color:rgba(226,232,240,.42);cursor:ew-resize;touch-action:none;z-index:9;-webkit-tap-highlight-color:transparent}
.dashboard-widget-resize-handle:hover{color:var(--teal,#00d4aa)}
.dashboard-widget-resize-handle:focus-visible{outline:2px solid var(--teal,#00d4aa);outline-offset:-2px}
.dashboard-widget-resize-handle:disabled{cursor:default;opacity:0}
/* The lifted card is taken OUT OF FLOW and pinned to the rect it had when the
   drag began, so the placeholder is an exact 1:1 stand-in for its footprint. Left
   in flow it would keep its own grid cell while the placeholder claimed a second
   one — the grid would gain a cell and every other card would jump the instant a
   drag started, and the preview would stop describing where the drop lands. */
html .dashboard-widget-card--dragging{position:fixed;top:var(--wf-drag-top,0);left:var(--wf-drag-left,0);width:var(--wf-drag-w,auto);height:var(--wf-drag-h,auto);margin:0;z-index:30;opacity:.85;pointer-events:none;overflow:hidden;transform:translate(var(--wf-drag-x,0),var(--wf-drag-y,0));box-shadow:0 22px 54px rgba(0,0,0,.45)}
html .dashboard-widget-drop-placeholder{min-height:96px;border:2px dashed rgba(0,212,170,.55);border-radius:16px;background:rgba(0,212,170,.07)}
/* The preview slot is deliberately NOT a managed card — order derivation counts
   those — so without an explicit span per width enum it would fall through to the
   full-row `> :not(.dashboard-widget-card)` rule above and preview a whole row
   for a one-track card. */
html #tab-dashboard.active > .dashboard-widget-drop-placeholder.dashboard-widget-card--compact{grid-column:span 1;min-width:0}
html #tab-dashboard.active > .dashboard-widget-drop-placeholder.dashboard-widget-card--standard{grid-column:span 2;min-width:0}
html #tab-dashboard.active > .dashboard-widget-drop-placeholder.dashboard-widget-card--full{grid-column:1 / -1;min-width:0}
html.wf-dragging{cursor:grabbing}
html.wf-dragging,html.wf-resizing{-webkit-user-select:none;user-select:none}
html.wf-resizing{cursor:ew-resize}
html .dashboard-widget-card--resizing{outline:2px solid rgba(0,212,170,.5);outline-offset:2px}
/* The desktop edit-mode two-row controller header is GONE, not relocated. It
   existed solely because edit mode used to add a SECOND 44px control into the
   `.sec-hdr` title row, which crushed the provenance pill ("Source: Neptune
   Apex" clipping to "Source: Neptune"). The rail no longer enters any header, so
   editing adds nothing to that row and it keeps its ordinary single-row form.
   The phone two-row lane below is unrelated — that one is about 390px width, not
   about edit chrome — and stays. */
/* The polite live region for the mode change and completed moves/resizes.
   Visually hidden but never display:none, or assistive tech would not announce
   it at all. */
.dashboard-widget-gesture-status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ── INT-2578 design reset (Meghann): one anatomy for titled widgets ───────
   "the widget titles are either in the card or outside of it (controller status
    vs 2 conrollers). the buttons are the same either in the card or outside of
    it" — so a TITLED widget always puts its title and its single kebab in the
   in-flow `.sec-hdr` above the content surface, and the aggregate multi-
   controller summary now follows that same convention instead of painting a
   self-contained strip with the count inside it. No nested card, no toolbar. */
.controllers-summary-hdr{display:flex;align-items:center;gap:8px;min-width:0}
.controllers-summary-title{min-width:0;overflow-wrap:anywhere;color:var(--text,#dfe9e7);font-weight:700;font-size:.68rem;text-transform:uppercase;letter-spacing:.06em}
/* One restrained content surface for the chips — the only painted boundary the
   summary has. It wraps rather than scrolling sideways at any width. */
.controllers-summary-body{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0;padding:8px 14px;border:1px solid rgba(0,212,170,.18);border-radius:12px;background:rgba(0,212,170,.05);font-size:.78rem;color:var(--text-dim,#8899aa)}
.controllers-summary-body .tank-controller-chip{min-width:0;max-width:100%;overflow-wrap:anywhere}

/* ── Controller-scoped widget cards (read-only) ─────────────────────────────
   Provenance is identity: every scoped card names its source in text. */
.controller-widget-card{margin:20px 0 10px}
/* One-line controller header (INT-1757 Meghann mobile cleanup): title stays on
   the left; the provenance/status group is pushed to the right and is the only
   part that shrinks (the source pill truncates); status + menu never shrink and
   the menu drops its stray auto-margin so it packs next to the group instead of
   floating in the inter-card gap. */
.controller-widget-hdr{flex-wrap:nowrap}
.controller-widget-title{flex:0 0 auto;white-space:nowrap}
.controller-widget-provenance{display:inline-flex;align-items:center;gap:8px;min-width:0;flex:0 1 auto;margin-left:auto}
.controller-widget-source{display:inline-flex;align-items:center;gap:6px;min-width:0;flex:0 1 auto;padding:2px 9px;border-radius:999px;border:1px solid rgba(0,212,170,.3);background:rgba(0,212,170,.08);color:var(--teal,#00d4aa);font-size:.66rem;font-weight:800;text-transform:none;letter-spacing:0;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}
.controller-widget-status{flex:0 0 auto;font-size:.68rem;font-weight:700;white-space:nowrap}
.controller-widget-status--connected{color:rgba(0,212,170,.85)}
.controller-widget-status--degraded,.controller-widget-status--unconfirmed{color:#fbbf24}
.controller-widget-provenance--degraded .controller-widget-source,
.controller-widget-provenance--unconfirmed .controller-widget-source{border-color:rgba(251,191,36,.34);background:rgba(251,191,36,.08);color:#fbbf24}
.controller-widget-body{padding:12px 14px;border:1px solid rgba(255,255,255,.10);border-radius:14px;background:rgba(255,255,255,.03)}
.controller-widget-note{margin:0 0 10px;color:var(--text-dim,rgba(226,232,240,.68));font-size:.74rem;line-height:1.4}
.controller-widget-empty{margin:0;color:var(--text-dim,rgba(226,232,240,.68));font-size:.8rem;line-height:1.5}
.controller-widget-params{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;margin:0;padding:0;list-style:none}
.controller-widget-param{display:flex;flex-direction:column;gap:2px;padding:9px 11px;border:1px solid rgba(255,255,255,.08);border-radius:10px;background:rgba(255,255,255,.03)}
.controller-widget-param-label{font-size:.66rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim,rgba(226,232,240,.68))}
.controller-widget-param-value{font-size:1.05rem;font-weight:800;color:var(--text-bright,#f8fafc)}
.controller-widget-param-meta{font-size:.64rem;color:var(--text-dim,rgba(226,232,240,.68))}
.controller-widget-param--stale .controller-widget-param-value{opacity:.55}
.controller-widget-param--missing .controller-widget-param-value{color:var(--text-dim,rgba(226,232,240,.68))}
.controller-widget-status-rows{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;list-style:none}
.controller-widget-status-row{display:flex;justify-content:space-between;gap:10px;font-size:.78rem;color:var(--text,#e5eef7)}
.controller-widget-status-row dt{color:var(--text-dim,rgba(226,232,240,.68));font-weight:700}
.controller-widget-status-row dd{margin:0;text-align:right;min-width:0;overflow:hidden;text-overflow:ellipsis}

/* Tablet: compact becomes one of TWO columns; standard and full both take the
   whole row, because two of two tracks is just a full row with extra rules. */
@media(max-width:1099px){
  html #tab-dashboard.active{grid-template-columns:repeat(2,minmax(0,1fr))}
  html #tab-dashboard.active > .dashboard-widget-card--standard{grid-column:1 / -1}
  html #tab-dashboard.active > .dashboard-widget-drop-placeholder.dashboard-widget-card--standard{grid-column:1 / -1}
}

@media(max-width:720px){
  html #tab-dashboard.active{grid-template-columns:minmax(0,1fr)}
  html #tab-dashboard.active > .dashboard-widget-card--compact{grid-column:1 / -1}
  html #tab-dashboard.active > .dashboard-widget-card--standard{grid-column:1 / -1}
  html #tab-dashboard.active > .dashboard-widget-card--full{grid-column:1 / -1}
  /* One visual column: the drop preview is the full column too, whatever desktop
     width the dragged card has saved. */
  html #tab-dashboard.active > .dashboard-widget-drop-placeholder{grid-column:1 / -1}
  .dashboard-widget-controls{align-items:flex-start;flex-direction:column}
  .dashboard-widget-actions{width:100%;justify-content:flex-start}
  .dashboard-widget-actions .btn{flex:1}
  .dashboard-widget-library{padding:0}
  .dashboard-widget-library-panel{min-height:100vh;margin:0;border-radius:0}

  /* Phone: Save and Reset share one full-width row and the status line sits
     under them, so neither 44px target is ever squeezed off-screen. */
  .dashboard-widget-manager-footer{flex-direction:column;align-items:stretch}
  .dashboard-widget-manager-footer button{width:100%}
  .dashboard-widget-save-status{flex:1 1 auto}
  /* Phone: the controller header becomes TWO in-flow rows instead of crushing
     title + source + status + kebab into one. Parent visual QA on the real
     390px proof caught the one-row form hard-clipping the widget's own identity
     to "Source: Neptun…" — and a scoped card that cannot name its controller is
     an appearance defect, not a truncation nicety.

       row 1  the widget's own title, full width;
       row 2  the provenance group, full width, still OUTSIDE/above the content
              surface.

     The 100% flex-basis on the provenance is what forces the break; a
     shrinkable basis would re-join row 1 and clip again. Desktop (>720px) keeps
     the compact single-row behavior above. The edit rail is not part of this
     row at any width — it sits above the header, on the wrapper. */
  .controller-widget-hdr{flex-wrap:wrap;row-gap:6px}
  .controller-widget-title{order:0;min-width:0}
  .controller-widget-provenance{order:2;flex:1 1 100%;margin-left:0;flex-wrap:wrap;row-gap:4px}
  /* The label is shown whole at the 358px widget width; a pathological long
     source name wraps inside the pill rather than clipping or overflowing. */
  .controller-widget-source{white-space:normal;overflow:visible;text-overflow:clip;overflow-wrap:anywhere;max-width:100%}
}

/* Reduced motion sits last so it overrides every geometry rule above it — and so
   the un-mediated desktop lane above stays a contiguous block. */
@media (prefers-reduced-motion: reduce){
  html .dashboard-widget-card--dragging{transform:none;transition:none}
  html .dashboard-widget-drop-placeholder{transition:none}
}
