/* ============================================================================
   shell.css — the app frame: top bar, sidebar (desktop) / drawer (tablet &
   phone), and the scrolling content region. This is where the responsive
   strategy lives: ONE layout that reflows from a 3-column desktop to a
   single-column phone with an off-canvas menu — no separate mobile build.
   ============================================================================ */

.app {
  display: grid;
  /* minmax(0,…) not plain 1fr: a `1fr` track refuses to shrink below its
     content's min-content width, so one wide child (the "View as" select,
     whose longest option is ~260px) silently widened the whole app on
     phones. minmax(0,…) lets the track shrink and the child scroll/ellipsis. */
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  grid-template-rows: var(--topbar-h) 1fr;
  grid-template-areas: "sidebar topbar" "sidebar main";
  height: 100dvh;
}

/* ---- Top bar ---------------------------------------------------------- */
.topbar {
  grid-area: topbar;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 0 var(--sp-5); padding-right: max(var(--sp-5), env(safe-area-inset-right));
  background: var(--surface); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}
.topbar .page-title { display: flex; flex-direction: column; min-width: 0; }
.topbar .page-title .name { font-weight: 800; color: var(--ink); font-size: var(--fs-md); line-height: 1.1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar .page-title .sub  { color: var(--muted); font-size: var(--fs-xs); }
.topbar .spacer { flex: 1 1 auto; }

.menu-btn { display: none; }   /* shown only when the sidebar collapses */

/* ---- Sidebar ---------------------------------------------------------- */
.sidebar {
  grid-area: sidebar;
  background: linear-gradient(180deg, var(--surface), var(--surface-2));
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  min-height: 0;               /* let the nav scroll, not the whole column */
}
.brand {
  /* The supplied lockup, with the institution's name beneath it. Stacked
     because the artwork is a wide horizontal lockup that already carries its
     own tagline — anything set beside it collides. */
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5) var(--sp-3);
  border-bottom: 1px solid var(--line-soft);
}

/* The artwork is transparent, but its wordmark is near-black navy (#0A1B3D),
   which is all but invisible against the dark theme's Midnight sidebar.
   Recolouring it would be modifying it, so dark mode puts it on a deliberate
   white plate instead — padded and rounded, clearly a panel the logo sits on.
   Light mode needs none of that and gets none. */
.brand-logo { display: block; width: 100%; }
.brand-logo .sg-logo {
  display: block; width: 100%; height: auto;
  /* Sized by width, not height — see logoFull() in core/brand.js. The file
     carries wide internal margins, so these negative insets pull the visual
     block back to the size of the lockup itself. Nothing is cropped: the whole
     image is still painted, it just overhangs its own box. */
  margin: -14px -8px -18px; /* audit-ok: optical trim of the artwork own margins, measured not chosen */
}
:root[data-theme="dark"] .brand-logo,
:root:not([data-theme="light"]) .brand-logo {
  background: #FFFFFF; border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-3);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .brand-logo { background: none; padding: 0; }
}

.brand .org {
  color: var(--muted); font-size: var(--fs-xs); font-weight: 600;
  letter-spacing: .01em; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.nav { flex: 1 1 auto; overflow-y: auto; padding: var(--sp-3) var(--sp-3) var(--sp-6); }
.nav-group + .nav-group { margin-top: var(--sp-4); }
.nav-group > .label {
  font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); padding: var(--sp-2) var(--sp-3);
}
.nav-list { list-style: none; display: flex; flex-direction: column; gap: 2px; }
.nav-link {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; text-align: left; min-height: var(--tap);
  padding: var(--sp-2) var(--sp-3); border-radius: 10px;
  color: var(--body); font-weight: 600; font-size: var(--fs-sm);
  border: 1px solid transparent; transition: background var(--dur), color var(--dur);
}
.nav-link:hover { background: var(--surface-3); color: var(--ink); text-decoration: none; }
.nav-link svg { flex: none; color: var(--muted); }
.nav-link .txt { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-link[aria-current="page"] {
  background: var(--accent-tint); color: var(--accent); border-color: var(--info-line);
}
.nav-link[aria-current="page"] svg { color: var(--accent); }
.nav-link .badge {
  flex: none; min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--radius-pill);
  background: var(--bad); color: var(--on-color); font-size: var(--fs-2xs); font-weight: 800;
  display: grid; place-items: center;
}

/* ---- Main content ----------------------------------------------------- */
.main { grid-area: main; overflow-y: auto; min-width: 0; }
.view { max-width: var(--content-max); margin: 0 auto;
  padding: var(--sp-6) var(--sp-6) var(--sp-12);
  padding-left: max(var(--sp-6), env(safe-area-inset-left));
  padding-right: max(var(--sp-6), env(safe-area-inset-right)); }

/* ---- Drawer scrim (only used in collapsed mode) ---------------------- */
.scrim {
  position: fixed; inset: 0; z-index: 25; background: rgba(6,19,64,.45);
  opacity: 0; visibility: hidden;
  /* Fade opacity, but step `visibility` — animating visibility keeps the element
     stuck at its start value, so the overlay never appears. On close we delay the
     hide until the fade finishes; on open (below) it flips to visible instantly. */
  transition: opacity var(--dur) var(--ease), visibility 0s linear var(--dur);
}

/* ---- State ribbon ------------------------------------------------------
   Says what this installation IS. Absent entirely on a claimed one — see
   stateRibbon() in shell.js for why the three states read differently. */
.demo-ribbon {
  grid-column: 1 / -1;
  background: var(--planetary); color: var(--on-color); text-align: center;
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: .03em;
  padding: 4px var(--sp-4);
}

/* `hidden` is only display:none by UA default, which `display: grid` on an
   ancestor's child would not override — but this element is a grid ITEM, and
   grid items ignore `hidden`'s display:none only if something sets display.
   Nothing here does, so this is belt and braces: an empty ribbon must take no
   space at all, or a claimed installation gets a 4px coloured bar with no text
   in it and no explanation. */
.demo-ribbon[hidden] { display: none; }

/* The unclaimed-and-not-a-demo state. Amber rather than the brand blue, because
   this one is a warning about the installation's security and reads as
   decoration in the same colour as everything else.

   Built from the same --warn-bg / --ink pair as `.note.warn` rather than white
   on --warn. A first draft did the latter, which is legible in the light theme
   and unreadable in the dark one: --warn inverts to a pale amber there, so it
   would have been white text on a light background. The token pairs exist
   precisely so that both themes are somebody else's already-solved problem. */
.demo-ribbon.warn {
  background: var(--warn-bg); color: var(--ink);
  border-bottom: 1px solid var(--warn-line);
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
  flex-wrap: wrap; padding: var(--sp-2) var(--sp-4); letter-spacing: 0;
  text-align: left; font-weight: 500;
}

.ribbon-action {
  flex: none;
  background: var(--warn); color: var(--warn-bg);
  border: 0; border-radius: var(--radius-sm);
  font: inherit; font-weight: 700;
  padding: 4px 12px; cursor: pointer;
  /* 32px, not the 44px coarse-pointer floor: it sits inside a persistent
     ribbon, and a 44px band across the top of every screen on a phone costs
     more than it is worth. It is not the only route — Settings carries the
     same action at full size. */
  min-height: 32px;
}
.ribbon-action:hover { filter: brightness(1.15); }
.ribbon-action:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* ============================================================================
   Responsive: below 960px the sidebar becomes an off-canvas drawer.
   ============================================================================ */
@media (max-width: 960px) {
  .app { grid-template-columns: minmax(0, 1fr); grid-template-areas: "topbar" "main"; }
  .menu-btn { display: inline-grid; }

  .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0; width: min(86vw, var(--sidebar-w));
    z-index: 30; transform: translateX(-100%);
    transition: transform var(--dur) var(--ease); box-shadow: var(--shadow-lg);
  }
  .app[data-drawer="open"] .sidebar { transform: translateX(0); }
  .app[data-drawer="open"] .scrim { opacity: 1; visibility: visible; transition: opacity var(--dur) var(--ease), visibility 0s; }
  .view { padding: var(--sp-5) var(--sp-4) var(--sp-10); }
}

@media (max-width: 560px) {
  .topbar .page-title .sub { display: none; }   /* keep the phone top bar tight */
  .view { padding-top: var(--sp-4); }
}
