/* ============================================================
   ella HUB — app shell & page styles (mockups)
   Shell faithfully mirrors the ella Secure / ella Converse admin
   layout: fixed BLACK collapsible sidebar (hover-expand) + white
   topbar with breadcrumbs + user menu. Font Awesome icons, Inter.
   Builds on brand.css (neon brand tokens).
   ============================================================ */

:root{
  --sidebar-width: 250px;
  --sidebar-collapsed: 60px;
  --sidebar-header-h: 70px;
  --topbar-h: 64px;
}

/* ============ SIDEBAR (black, hover-expand — ella Secure pattern) ============ */
.sidebar{
  position:fixed; top:0; left:0; height:100vh; width:var(--sidebar-collapsed);
  background:#000; color:#fff; display:flex; flex-direction:column; overflow:hidden;
  transition:width .3s ease, box-shadow .3s ease; z-index:1000;
}
.sidebar:hover, .sidebar.hover-expanded { box-shadow:4px 0 24px rgba(0,0,0,.45); }
.sidebar:not(:hover):not(.expanded) .sidebar-logo-text,
.sidebar:not(:hover):not(.expanded) .sidebar-menu-text,
.sidebar:not(:hover):not(.expanded) .nav-label,
.sidebar:not(:hover):not(.expanded) .sidebar-foot,
.sidebar:not(:hover):not(.expanded) .sidebar-section-arrow { opacity:0; transition:opacity .35s ease; }
.sidebar-logo{ display:flex; align-items:center; gap:11px; text-decoration:none; color:#fff; white-space:nowrap; }
.sidebar-logo-text{ font-size:1.18rem; font-weight:800; letter-spacing:-.03em; }
/* ── The second word of the lockup ────────────────────────────────────────────
   A gradient rather than a flat colour: the wordmark is the one piece of this
   chrome that is allowed to have some life in it, and it is the thing people
   look at to know which console they are in.

   `color` is set first and stays the fallback. Anything that cannot clip a
   background to text — and print, which drops backgrounds — gets the solid
   accent rather than an invisible word, which is what `-webkit-text-fill-color`
   would have left behind. */
.sidebar-logo-text .hub {
  color: var(--brand-accent);
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .sidebar-logo-text .hub {
    background-image: linear-gradient(100deg, var(--brand-accent) 0%, var(--brand-accent-2) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}
@media print {
  .sidebar-logo-text .hub { background-image: none; color: var(--brand-accent); }
}

.sidebar-menu{ list-style:none; margin:0; padding:10px 0 24px; overflow-y:auto; overflow-x:hidden; flex:1;
  scrollbar-width:thin; scrollbar-color:rgba(255,255,255,.3) transparent; }
.nav-label{ font-size: max(12px, .66rem); font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:rgba(255,255,255,.34); padding:16px 18px 6px; white-space:nowrap; }

/* An EXPLICIT line-height, and the same one in all six consoles.
 *
 * This was three different things: `normal` in ella Converse and ella Draw (a
 * 42px row), inherited 1.55 in ella Secure v2 and ella Jobs (47px), and an
 * inherited 1.5 here (46px). Same padding, same font, same icon — three heights,
 * because the row is sized by the icon's line box and nothing said what that
 * should be.
 *
 * Stated rather than inherited, because the body line-height still differs
 * between consoles (1.5 here, 1.55 in four others) and a menu that changes
 * height when somebody adjusts body copy is a menu that will drift again. */
.sidebar-menu-link{ display:flex; align-items:center; padding:11px 18px; color:rgba(255,255,255,.72);
  text-decoration:none; white-space:nowrap; position:relative; cursor:pointer;
  transition:background-color .15s ease, color .15s ease, opacity .35s ease; line-height: 1.5;
}
.sidebar-menu-link:hover{ color:#fff; background:rgba(255,255,255,.06); }
.sidebar-menu-link.active{ color:#fff; background:rgba(255,255,255,.10); }
.sidebar-menu-link.active::before{ content:""; position:absolute; left:0; top:0; bottom:0; width:3px;
  background:var(--mint); box-shadow:0 0 14px var(--mint); }

.sidebar-menu-icon{ width:24px; flex-shrink:0; text-align:center; margin-right:14px; font-size:1.02em; }
.sidebar-menu-text{ flex:1; font-size:.96rem; font-weight:500; }
.sidebar-menu-link .pip{ margin-left:auto; background:var(--danger); color:#fff; font-size: 12px; font-weight:700;
  min-width:18px; height:18px; padding:0 5px; border-radius:9px; display:grid; place-items:center; }

/* collapsed tooltip */
.sidebar:not(:hover):not(.expanded) .sidebar-menu-link::after{ content:attr(data-title); position:absolute;
  left:calc(100% + 8px); top:50%; transform:translateY(-50%); background:rgba(0,0,0,.92); color:#fff;
  padding:6px 10px; border-radius:6px; white-space:nowrap; font-size:.78rem; font-weight:500; opacity:0;
  pointer-events:none; transition:opacity .15s ease; z-index:1100; }
.sidebar:not(:hover):not(.expanded) .sidebar-menu-link:hover::after{ opacity:1; }

.sidebar-foot{ margin:10px 14px 4px; padding:13px; border-radius:12px; background:rgba(255,255,255,.05);
  border:1px solid rgba(255,255,255,.08); }
.sidebar-foot .h{ font-weight:700; font-size:13px; color:#fff; }

/* ============ TOPBAR ============ */
.topbar{ position:fixed; top:0; left:var(--sidebar-collapsed); right:0; height:var(--topbar-h);
  background:#fff; border-bottom:1px solid var(--border); display:flex; align-items:center;
  justify-content:space-between; padding:0 24px; box-shadow:var(--shadow-sm); transition:left .3s ease; z-index:900; }
.menu-toggle{ width:40px; height:40px; border-radius:10px; border:1px solid var(--border); background:#fff;
  display:inline-flex; align-items:center; justify-content:center; cursor:pointer; color:var(--ink); flex-shrink:0;
  transition:.15s ease; }
.menu-toggle:hover{ background:var(--bg); border-color:var(--border-strong); }
.crumbs a{ color:var(--slate-light); }
.crumbs .cur{ color:var(--ink); font-weight:600; }

/* ── The trail on a narrow screen ────────────────────────────────────────────
 *
 * Show only where you ARE: the last separator and the current crumb.
 *
 * The old rule was `.crumbs span:not(:last-of-type) { display:none }`, written
 * against markup where separators and crumbs were both spans. Once the estate
 * shared one `_crumbs.php` — home as an `<a>`, separators as `<span class=sep>`,
 * intermediate crumbs as `<a>`, the current one as `<span class=cur>` — that
 * selector hid the wrong things: `:last-of-type` among spans is `.cur`, so every
 * separator vanished and the links did not.
 *
 * Counting from the end is correct whatever the tags, which is what makes it
 * one rule for six consoles. */
@media (max-width: 768px) {
    .crumbs > * { display: none; }
    .crumbs > :nth-last-child(-n+2) { display: inline; }
}
.iconbtn{ position:relative; width:40px; height:40px; border-radius:10px; border:1px solid var(--border);
  background:#fff; display:grid; place-items:center; color:var(--slate); cursor:pointer; transition:.15s; }
.iconbtn:hover{ background:var(--bg); color:var(--ink); }
.iconbtn .badge{ position:absolute; top:-5px; right:-5px; background:var(--danger); color:#fff; font-size: 12px;
  font-weight:700; min-width:18px; height:18px; padding:0 4px; border-radius:9px; display:grid; place-items:center;
  border:2px solid #fff; }
.user-toggle{ display:inline-flex; align-items:center; gap:10px; padding:6px 12px 6px 14px; background:#fff;
  border:1px solid var(--border); border-radius:var(--radius-pill); cursor:pointer; font:inherit; color:var(--ink); }
.user-toggle:hover{ border-color:var(--border-strong); background:var(--bg); }
.user-toggle .who{ line-height:1.12; text-align:right; }
.user-toggle .who .n{ font-weight:700; font-size:13px; }
.user-toggle .who .r{ font-size: 12px; color:var(--slate-light); }
/* An initial in a disc is TEXT, and the disc has to be a colour it can be read
   on. It was the brand gradient — neon purple into ice cyan — with near-black
   letters: 4.28:1 at the purple end and about 1.3:1 at the cyan one, so half
   the avatars in the estate had an initial nobody could make out. The gradient
   is the same shape, moved into the half of the ramp that carries white. */
.user-avatar{ width:32px; height:32px; border-radius:50%; background: linear-gradient(135deg, #8B2BE0 0%, #2A57D0 100%); color:#fff;
  display:grid; place-items:center; font-weight:800; font-size:13px; flex-shrink:0; }

/* ============ MAIN CONTENT ============ */
/* `flex:1 1 auto; min-width:0`, NOT `width:100%`.

   It used to say `width:100%`, which is 100% of the flex line and takes no
   account of the `margin-left` that clears the sidebar — so the column ran a
   sidebar's width off the right of the window at every viewport, and every page
   scrolled sideways by 64px. Growing into the free space measures the margin
   correctly; `min-width:0` then lets the column shrink past the widest thing
   inside it, which a flex item otherwise refuses to do.

   It hid for a long time because the overflow lands on the WINDOW, not on the
   element that appears cut off: the no-wrap tab strip measured itself as fitting
   because it had been handed room that was never really on screen. */
.main-content{ margin-left:var(--sidebar-collapsed); padding-top:var(--topbar-h); min-height:100vh;
  flex:1 1 auto; min-width:0; transition:margin-left .3s ease; }
.sidebar.expanded ~ .main-content{ margin-left:var(--sidebar-width); }

/* ── A phone gets a drawer, not a desktop's rail ──────────────────────────
 *
 * At 390px the 60px icon rail stayed on screen on every page: a sixth of the
 * width spent on navigation nobody could read, and the content column squeezed
 * to fit beside it. Below 768px the sidebar leaves the screen entirely and the
 * hamburger slides it in at full width, labels and all — the pattern ella
 * Converse already had. Tapping outside it, or following a link, puts it away.
 *
 * Hover-expand is off here: a touch screen has no hover, and the first tap on
 * an icon would otherwise expand the rail rather than follow the link. */
@media (max-width: 768px) {
  .sidebar { transform:translateX(-100%); width:var(--sidebar-width); transition:transform .25s ease; }
  .sidebar.shown { transform:translateX(0); box-shadow:4px 0 20px rgba(0,0,0,.4); }
  .sidebar:hover, .sidebar.hover-expanded { width:var(--sidebar-width); box-shadow:none; }
  .sidebar.shown:hover { box-shadow:4px 0 20px rgba(0,0,0,.4); }
  .sidebar .sidebar-logo-text, .sidebar .sidebar-menu-text, .sidebar .nav-label,
  .sidebar .sidebar-foot, .sidebar .sidebar-section-arrow { opacity:1 !important; }
  .sidebar .sidebar-menu-link::after { display:none; }
  .main-content, .sidebar.expanded ~ .main-content { margin-left:0; }
  .topbar, .sidebar.expanded ~ .main-content .topbar { left:0; }
}
/* Secure's value, and Secure is the estate's blueprint for the chrome.
   26/32 here against 24 everywhere else put every page in this console a
   couple of pixels out from the same page in the next one — invisible on
   its own and obvious when you switch between them. */
.content-wrapper{ margin:0 auto; padding:24px; max-width:none; width:100%; }
/* Title on the left, action on the right — stated here rather than repeated as
   an inline style on every page that happens to have a button. Pages that set it
   inline are unaffected; pages that forgot no longer drop their button onto its
   own line under the heading. `flex-wrap` keeps that graceful when the window is
   genuinely too narrow, and `min-width:0` lets the text column shrink rather
   than push the button off. */
/* TWO MARKUP SHAPES ARE IN USE, and both are reasonable to write:

     A. the title parts wrapped in a <div>, with the actions as a sibling
     B. the title parts written FLAT — a back-link, an <h1>, one or two `.sub`
        lines — with the actions in `.pagehead-actions`

   Only A worked. Under B every part became a flex ITEM on one line, so
   `justify-content:space-between` spread them across the page: "Your work" on
   the left and the sentence explaining it a thousand pixels away against the
   right edge, and on a job, the reference pinned right with its own title
   underneath. It read as a mistake because it was one.

   The rule now says which parts are the title column — they take a line each —
   and which is the action rail, which stays up on the heading's line. No
   `order` is involved: flex-basis does it, so the parts land in the order they
   are written.

   `.pagehead-actions` had three call sites and NO rule at all, like `.pad`
   before it. A class that does nothing is worse than no class: it reads as
   handled. */
/* The margin is in estate.css, once, for all six consoles. */
.pagehead{ display:flex; justify-content:space-between;
  align-items:flex-end; gap:6px 16px; flex-wrap:wrap; }

/* Shape A: the wrapper holding the title parts. */
.pagehead > div:not(.sub):not(.pagehead-actions){ flex:1 1 380px; min-width:0; }

/* Shape B: a back-link and each strap line get a full line of their own; the
   heading shares its line with the actions. */
/* A BACK-LINK takes a line of its own; A BUTTON DOES NOT. Without the
   `:not(.btn)` this matched the delivery board's `<a class="btn btn-primary">
   New job</a>` and gave it `flex: 1 1 100%` — a 1,840px pill across the top of
   the page, which reads as a banner rather than as something to press. */
.pagehead > a:not(.btn){ flex:1 1 100%; }
.pagehead > h1{ flex:1 1 auto; min-width:0; }
.pagehead > .sub{ flex:1 1 100%; margin-top:2px; }

/* The action rail, in either shape. */
.pagehead-actions{ flex:0 0 auto; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }

/* And the rails that never took the class.
   Sixteen screens write `style="display:flex;gap:8px"` on a div in the pagehead
   instead of using `.pagehead-actions` — which is drift, and not the kind worth
   editing sixteen files to undo. What it costs is `flex-wrap`: three buttons in
   a nowrap row are 195 + 124 + a gap on a 390px screen, so the PAGE scrolled
   sideways and took the sidebar with it.
   `flex-wrap` is inert on anything that is not a flex container, so this reaches
   exactly the children that are one and nothing else. It does not override a
   call site that asked for `nowrap` on purpose; none does. */
.pagehead > *{ flex-wrap:wrap; }
/* One size at every width, which is what makes a page title look the same
   in this console as in the next one. `rem` rather than px because the wide
   breakpoints below bump `body`, and a title that grew with the body text
   was how this drifted to 30px and 33px on a large monitor while every
   other application stayed at 26. Same declaration as `.page-title` in
   ella Secure, ella Draw and ella Converse. */
/* On `h1` itself, not on `.pagehead h1`.
   A bare <h1> — which is what the dashboard's heading row uses — was falling
   through to the browser's default of 2em, and 2em of a body that grows at the
   wide breakpoints is how a page title reached 30px here while the same title
   in ella Secure stayed at 26. Sized once, so it no longer matters which
   wrapper a page happens to put its heading in. Screens with a deliberately
   different heading (the auth box, the landing hero) still set their own. */
h1,
.page-title{ font-size:1.65rem; font-weight:700; letter-spacing:-.01em; }
.pagehead .sub{ color:var(--slate); margin-top:6px; font-size:14.5px; }

/* KPI */
/* A STAT ROW FILLS ITS WIDTH, whatever number of cards it holds.
 *
 * `repeat(4,1fr)` was four columns exactly, so a row of three left the fourth
 * empty and a row of two left half the page empty — and seven call sites then
 * wrote `style="display:flex;flex-wrap:wrap"` on top, which defeats the grid
 * entirely and shrink-wraps the cards against the left edge. Measured at 1600px:
 * between 496px and 1105px of unused row, the worst being 74% of the width.
 *
 * `auto-fit` collapses the empty tracks and stretches what is left, so three
 * cards fill exactly as four do. Same definition as `.kpi-row` below it in
 * hub.css — two names for one component, and now one behaviour.
 *
 * The call sites set no display of their own. A row's shape is the stylesheet's
 * to decide; an inline `display` matching nothing in the sheet is the quiet way
 * a layout stops being a layout. */
.kpis{ display:grid; grid-template-columns:repeat(auto-fit,minmax(min(150px, 100%), 1fr)); gap:16px; margin-bottom:24px; }
.kpi{ padding:16px 18px; display:flex; gap:13px; align-items:center; }
.kpi .ic{ width:44px; height:44px; border-radius:12px; display:grid; place-items:center; flex:none; font-size:18px; }
.kpi .v{ font-size:23px; font-weight:800; letter-spacing:-.02em; }
.kpi .l{ font-size:12.5px; color:var(--slate); margin-top:1px; }

/* dashboard split */
.dash{ display:grid; grid-template-columns:1fr 360px; gap:24px; align-items:start; }
.sec-head{ display:flex; align-items:center; justify-content:space-between; margin:4px 0 14px; }
.sec-head h2{ font-size:17px; }

/* service tiles */
.tiles{ display:grid; grid-template-columns:repeat(3,1fr); gap:16px; }
.tile{ padding:18px; display:block; color:inherit; position:relative; transition:.16s ease; }
.tile:hover{ transform:translateY(-2px); box-shadow:var(--shadow); border-color:var(--border-strong); }
.tile .glyph{ width:46px; height:46px; border-radius:12px; display:grid; place-items:center; margin-bottom:13px; font-size:20px; }
.tile h3{ font-size:15.5px; display:flex; align-items:center; gap:8px; }
.tile p{ color:var(--slate); font-size:13px; margin:6px 0 0; line-height:1.5; }
.tile .meta{ display:flex; align-items:center; gap:8px; margin-top:14px; }
.tile .status{ display:flex; align-items:center; gap:6px; font-size:12px; color:var(--slate); font-weight:600; }
.tile.dim{ opacity:.6; pointer-events:none; }
.tile .unread{ margin-left:auto; }

/* glyph helper: tinted bg + coloured icon set inline via --c */
.glyph{ background:color-mix(in srgb, var(--c) 15%, #fff); color:var(--c); }

/* feed / alerts */
/* A panel is a padded card, and it says so ON ITS OWN.
   
   This was `padding` and nothing else, because every call site here writes
   `class="card panel"` and took the surface from `.card`. The other five
   consoles define `.panel` complete, so a bare `class="panel"` is a card there
   and was a transparent box here — one class, two meanings, and the one view
   that wrote it bare showed the page through itself.
   
   The values are `.card`'s, so the paired `class="card panel"` is unchanged. */
.panel{
  padding:18px;
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:var(--radius);
  box-shadow:var(--shadow-sm);
}
.feed{ display:flex; flex-direction:column; }
.feed-item{ display:flex; gap:12px; padding:13px 0; border-bottom:1px solid var(--border); }
.feed-item:last-child{ border-bottom:0; padding-bottom:2px; }
.feed-item .g{ width:34px; height:34px; border-radius:9px; display:grid; place-items:center; flex:none; font-size:14px;
  background:color-mix(in srgb, var(--c) 16%, #fff); color:var(--c); }
.feed-item .t{ font-weight:700; font-size:13.5px; }
.feed-item .b{ font-size:12.5px; color:var(--slate); margin-top:2px; line-height:1.45; }
.feed-item .tag{ font-size: 12px; font-weight:700; color:var(--slate-light); margin-top:6px; display:flex; align-items:center; gap:6px; }
.feed-item .time{ font-size: 12px; color:var(--slate-light); margin-left:auto; flex:none; white-space:nowrap; }
/* The text column may shrink, and a long unbroken token in it may wrap. A
   flex child will not go narrower than its content by default, so one
   notification naming a 26-character reference (an erasure request's) pushed
   the time off the card and the whole dashboard scrolled sideways, at both
   widths (console audit, journey run 6 Oct 2026). */
.feed-item .grow{ min-width:0; overflow-wrap:anywhere; }

/* task list */
.task{ display:flex; gap:11px; align-items:flex-start; padding:11px 0; border-bottom:1px solid var(--border); }
.task:last-child{ border-bottom:0; }
.task .box{ width:18px; height:18px; border-radius:6px; border:2px solid var(--border-strong); flex:none; margin-top:1px;
  display:grid; place-items:center; color:#fff; font-size: 12px; }
.task .box.done{ background:var(--success); border-color:var(--success); }
.task .tt{ font-size:13.5px; font-weight:600; }
.task.done .tt{ color:var(--slate-light); text-decoration:line-through; }
.task .meta{ font-size: 12px; color:var(--slate-light); margin-top:2px; }

/* notifications */
.filterbar{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:18px; }
.seg{ display:inline-flex; background:#fff; border:1px solid var(--border); border-radius:11px; padding:4px; gap:2px; }
.seg button{ border:0; background:transparent; font:inherit; font-weight:600; font-size:13px; color:var(--slate);
  padding:7px 14px; border-radius:8px; cursor:pointer; }
.seg button.on{ background:var(--ink); color:#fff; }
.fchip.on{ background:#F3E9FF; border-color:var(--primary-light); color:var(--purple-deep); }
.fchip .sw{ width:9px; height:9px; border-radius:50%; }
.note{ display:flex; gap:14px; padding:16px 18px; align-items:flex-start; margin-bottom:10px; }
.note.unread{ border-left:3px solid var(--purple); }
.note .g{ width:38px; height:38px; border-radius:10px; display:grid; place-items:center; flex:none; font-size:15px;
  background:color-mix(in srgb, var(--c) 16%, #fff); color:var(--c); }
.note .src{ font-size: 12px; font-weight:700; color:var(--slate-light); display:flex; align-items:center; gap:7px; }
.note .ti{ font-weight:700; font-size:14.5px; margin:3px 0 0; display:flex; align-items:center; gap:9px; }
.note .bo{ font-size:13px; color:var(--slate); margin-top:4px; line-height:1.5; max-width:640px; }
.note .when{ font-size:12px; color:var(--slate-light); white-space:nowrap; }
.note .act{ display:flex; flex-direction:column; align-items:flex-end; gap:9px; margin-left:auto; }
.sev{ width:8px; height:8px; border-radius:50%; flex:none; margin-top:6px; }

/* service launcher */
.handoff{ max-width:560px; margin:14px auto 28px; text-align:center; padding:42px 38px; }
.handoff .flow{ display:flex; align-items:center; justify-content:center; gap:22px; margin:26px 0 22px; }
.handoff .lg{ width:74px; height:74px; border-radius:20px; display:grid; place-items:center; color:#fff; box-shadow:var(--shadow); font-size:26px; }
.spinner{ width:54px; height:54px; border-radius:50%; border:4px solid var(--border); border-top-color:var(--purple);
  animation:spin 1s linear infinite; margin:0 auto 18px; }
@keyframes spin{ to{ transform:rotate(360deg); } }
.dash-anim{ stroke-dasharray:6 7; animation:dash 1s linear infinite; }
@keyframes dash{ to{ stroke-dashoffset:-26; } }
.switcher{ max-width:1000px; margin:0 auto; }
.sw-tile .glyph{ width:44px; height:44px; border-radius:12px; display:grid; place-items:center; margin:0 auto 11px; font-size:19px; }
.sw-tile .n{ font-weight:700; font-size:14px; }
.sw-tile .s{ font-size: 12px; color:var(--slate-light); margin-top:3px; }
.sw-tile.dim{ opacity:.55; pointer-events:none; }

/* admin access */
.statstrip{ display:flex; background:#fff; border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; margin-bottom:22px; }
.statstrip .s{ flex:1; padding:16px 20px; border-right:1px solid var(--border); }
.statstrip .s:last-child{ border-right:0; }
.statstrip .v{ font-size:21px; font-weight:800; }
.statstrip .l{ font-size:12px; color:var(--slate); margin-top:2px; }

.person{ display:flex; align-items:center; gap:11px; }
.person .nm{ font-weight:700; }
.person .em{ font-size:12px; color:var(--slate-light); }
.pill-ent.on{ color:#fff; border:0; }

/* drawer / modal */
.scrim{ position:fixed; inset:0; background:rgba(8,8,16,.5); backdrop-filter:blur(2px); display:none; z-index:1200;
  align-items:flex-start; justify-content:flex-end; }
.scrim.open{ display:flex; }
.drawer{ width:440px; max-width:92vw; background:#fff; height:100vh; overflow-y:auto; padding:26px; box-shadow:var(--shadow-lg);
  animation:slidein .22s ease; }
@keyframes slidein{ from{ transform:translateX(30px); opacity:.4; } }
.drawer h2{ font-size:19px; }

/* ============ LOGIN (dark hero + light form) ============ */
.login{ display:grid; grid-template-columns:1.05fr 1fr; min-height:100vh; }
.brandpanel{ color:#fff; padding:54px 56px; display:flex; flex-direction:column; position:relative; overflow:hidden;
  background:
    radial-gradient(120% 90% at 8% 0%, rgba(172,67,255,.42), transparent 46%),
    radial-gradient(120% 110% at 100% 100%, rgba(67,255,187,.30), transparent 48%),
    radial-gradient(80% 80% at 80% 10%, rgba(67,244,255,.22), transparent 50%),
    #000; }
.brandpanel .tag{ font-size:34px; font-weight:800; letter-spacing:-.03em; line-height:1.15; margin-top:auto; max-width:460px; }
.brandpanel .tag .accent{ color:var(--mint); }
.brandpanel .lede{ font-size:15px; opacity:.86; margin-top:16px; max-width:430px; line-height:1.55; }
.ticks{ display:flex; flex-direction:column; gap:12px; margin-top:30px; }
.ticks .t{ display:flex; align-items:center; gap:11px; font-weight:600; font-size:14.5px; }
.ticks .t .c{ width:26px; height:26px; border-radius:50%; background:rgba(255,255,255,.12); display:grid;
  place-items:center; flex:none; color:var(--mint); font-size:12px; }
.loginpanel{ display:flex; align-items:center; justify-content:center; padding:40px; }
.logincard{ width:100%; max-width:392px; }
/* The card's own wordmark: hidden while the brand panel is showing one, and
   shown at the width where that panel goes away. */
.login-mark{ display:none; margin-bottom:24px; }
.divider{ display:flex; align-items:center; gap:14px; color:var(--slate-light); font-size:12px; margin:18px 0; }
.divider::before, .divider::after{ content:""; flex:1; height:1px; background:var(--border); }
.protected{ display:flex; align-items:center; justify-content:center; gap:7px; margin-top:24px; font-size:12px; color:var(--slate-light); }

/* ============ INDEX landing ============ */
.land{ max-width:1100px; margin:0 auto; padding:54px 28px 70px; }
.land-head{ text-align:center; margin-bottom:42px; }
.land-head h1{ font-size:38px; }
.land-head p{ color:var(--slate); font-size:16px; margin-top:14px; max-width:760px; margin-inline:auto; }
.previews{ display:grid; grid-template-columns:repeat(3,1fr); gap:20px; }
.preview{ color:inherit; overflow:hidden; transition:.16s; display:flex; flex-direction:column; }
.preview:hover{ transform:translateY(-3px); box-shadow:var(--shadow-lg); }
.preview .thumb{ height:140px; display:grid; place-items:center; color:#fff; font-size:34px; }
.preview .pb{ padding:18px; }
.preview .pb h3{ font-size:16px; display:flex; align-items:center; gap:8px; }
.preview .pb p{ color:var(--slate); font-size:13px; margin:7px 0 0; line-height:1.5; }
.preview .num{ font-size:12px; font-weight:800; color:var(--slate-light); }
hr.soft{ border:0; border-top:1px solid var(--border); margin:18px 0; }

@media (max-width:1100px){ .dash{grid-template-columns:1fr;}
  .tiles{grid-template-columns:repeat(2,1fr);} .previews{grid-template-columns:1fr 1fr;}
  .login{grid-template-columns:1fr;} .brandpanel{display:none;} .login-mark{display:block;} }
@media (max-width:680px){ .statstrip{flex-wrap:wrap;} .previews{grid-template-columns:1fr;} }

/* ── Two people editing one record ─────────────────────────────────────────
   Both banners sit at the very top of the page, above the chrome, because both
   are about something that happened outside this tab — a conflict shown halfway
   down a long form is a conflict somebody scrolls past and saves again.
   Identical in all six repositories; see docs/concurrency-guard.md. */
.guard-banner {
    position: sticky; top: 0; z-index: 60;
    padding: 12px 18px; font-size: .92rem; line-height: 1.5;
    border-bottom: 1px solid var(--border, #e5e7eb);
}

/* Advisory. Quiet on purpose: it is a heads-up, not a problem, and a warning
   colour for "Sam is here too" would train people to ignore the colour. */
.guard-banner.is-presence {
    background: var(--info-bg, #DBEAFE); color: #1E3A8A; border-bottom-color: #BFDBFE;
}
.guard-banner.is-presence.is-editing {
    background: var(--warning-bg, #FEF3C7); color: var(--warning-ink, #78350F); border-bottom-color: #FDE68A;
}

/* Authoritative. Their save was refused and there is something to do about it. */
.guard-banner.is-conflict {
    background: var(--danger-bg, #fff1f2); color: #991B1B; border-bottom-color: #FECACA;
    display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
}
