/* =====================================================================
   Gearshift theme - component skins, built entirely from tokens.css.
   Loaded last so its :root wins over Bootstrap's.

   Organised as: reset -> bootstrap bridge -> primitives -> layout ->
   components -> auth. Every selector is namespaced `gs-` except the small
   bridge section, which is the one place Bootstrap's own classes are
   remapped onto our tokens so .btn/.card/.form-control inherit the theme
   without per-component overrides.
   ===================================================================== */

/* ── base ─────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--gs-font);
  font-size: var(--gs-fs-base);
  line-height: 1.55;
  color: var(--gs-ink);
  background: var(--gs-bg);
  -webkit-font-smoothing: antialiased;
  transition: background var(--gs-dur) var(--gs-ease),
              color var(--gs-dur) var(--gs-ease);
}

a { color: var(--gs-accent-soft); text-decoration: none; }
a:hover { color: var(--gs-accent); }

h1, h2, h3, h4, h5 {
  margin: 0 0 var(--gs-space-3);
  font-weight: 800;
  letter-spacing: -.4px;
  color: var(--gs-ink);
}
h1 { font-size: var(--gs-fs-2xl); }
h2 { font-size: var(--gs-fs-xl); }
h3 { font-size: var(--gs-fs-lg); }

p { margin: 0 0 var(--gs-space-3); }

hr { border: 0; border-top: 1px solid var(--gs-line); margin: var(--gs-space-5) 0; }

code, pre { font-family: var(--gs-font-mono); font-size: var(--gs-fs-sm); }

/* Focus is never removed - only restyled. Removing it makes the app
   unusable by keyboard, which is both an accessibility failure and a
   support burden. */
:focus-visible {
  outline: 2px solid var(--gs-accent);
  outline-offset: 2px;
  border-radius: 4px;
}

::selection { background: var(--gs-accent-ring); color: var(--gs-ink); }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--gs-line-strong); border-radius: 10px;
  border: 2px solid transparent; background-clip: content-box;
}

/* ── bootstrap bridge ─────────────────────────────────────────────── */

.form-control, .form-select {
  width: 100%;
  min-height: 46px;
  padding: 10px 14px;
  border-radius: var(--gs-radius);
  background: var(--gs-surface-2);
  border: 1px solid var(--gs-line);
  color: var(--gs-ink);
  font-family: inherit;
  font-size: var(--gs-fs-md);
  transition: border-color var(--gs-dur) var(--gs-ease),
              box-shadow var(--gs-dur) var(--gs-ease);
}
.form-control::placeholder { color: var(--gs-muted); opacity: .75; }
.form-control:focus, .form-select:focus {
  border-color: var(--gs-accent);
  box-shadow: 0 0 0 3px var(--gs-accent-ring);
  outline: none;
}
.form-control:disabled, .form-control[readonly] {
  background: var(--gs-surface-3); color: var(--gs-muted); cursor: not-allowed;
}
textarea.form-control { min-height: 110px; resize: vertical; }

.form-label {
  display: block;
  margin-bottom: 6px;
  font-size: var(--gs-fs-sm);
  font-weight: 700;
  color: var(--gs-ink-2);
}

.text-danger  { color: var(--gs-danger) !important; }
.text-success { color: var(--gs-success) !important; }
.text-muted   { color: var(--gs-muted) !important; }

/* ── primitives ───────────────────────────────────────────────────── */

.gs-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 0 18px;
  border: 1px solid transparent; border-radius: var(--gs-radius);
  font-family: inherit; font-size: var(--gs-fs-md); font-weight: 700;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: transform var(--gs-dur) var(--gs-ease),
              box-shadow var(--gs-dur) var(--gs-ease),
              background var(--gs-dur) var(--gs-ease),
              border-color var(--gs-dur) var(--gs-ease);
}
.gs-btn:active { transform: translateY(1px); }
.gs-btn:disabled, .gs-btn[aria-disabled='true'] {
  opacity: .5; cursor: not-allowed; transform: none;
}

.gs-btn--primary {
  background: var(--gs-btn-face); color: var(--gs-btn-ink);
  /* The rim is what stops the dark foot dissolving into a dark page. */
  border-color: var(--gs-btn-line);
  box-shadow: var(--gs-btn-glow);
}
.gs-btn--primary:hover { filter: brightness(1.10); color: var(--gs-btn-ink); }

.gs-btn--ghost {
  background: transparent; color: var(--gs-ink);
  border-color: var(--gs-line-strong);
}
.gs-btn--ghost:hover {
  background: var(--gs-surface-2); border-color: var(--gs-accent);
  color: var(--gs-ink);
}

.gs-btn--danger {
  background: var(--gs-danger-wash); color: var(--gs-danger);
  border-color: color-mix(in srgb, var(--gs-danger) 35%, transparent);
}
.gs-btn--danger:hover { background: var(--gs-danger); color: var(--gs-white); }

.gs-btn--sm { min-height: 34px; padding: 0 12px; font-size: var(--gs-fs-sm); }
.gs-btn--block { width: 100%; }

.gs-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: var(--gs-radius-pill);
  font-size: var(--gs-fs-xs); font-weight: 800; letter-spacing: .2px;
  background: var(--gs-surface-3); color: var(--gs-ink-2);
  border: 1px solid var(--gs-line);
}
.gs-badge--success { background: var(--gs-success-wash); color: var(--gs-success);
                     border-color: transparent; }
.gs-badge--warning { background: var(--gs-warning-wash); color: var(--gs-warning);
                     border-color: transparent; }
.gs-badge--danger  { background: var(--gs-danger-wash);  color: var(--gs-danger);
                     border-color: transparent; }
.gs-badge--accent  { background: var(--gs-accent-wash);  color: var(--gs-accent);
                     border-color: transparent; }
.gs-badge--muted   { background: transparent; color: var(--gs-muted); }
.gs-badge__dot { width: 6px; height: 6px; border-radius: 50%;
                 background: currentColor; }

.gs-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--gs-gradient); color: var(--gs-on-accent);
  font-size: var(--gs-fs-sm); font-weight: 800; flex: none;
  overflow: hidden;
}
.gs-avatar img { width: 100%; height: 100%; object-fit: cover; }
.gs-avatar--lg { width: 64px; height: 64px; font-size: var(--gs-fs-lg); }

/* ── layout: card, page header, grid ─────────────────────────────── */

.gs-card {
  /* Depth is three things at once: a soft gradient fill, a hairline of light
     along the top edge, and a long shadow. All three are tokens, so light
     mode redefines them rather than switching them off. */
  background: var(--gs-depth-card), var(--gs-surface);
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius-lg);
  box-shadow: var(--gs-depth-shadow-sm), var(--gs-depth-edge);
  /* Cards stacked one after another had NOTHING between them - two rounded
     panels touching, which reads as one broken panel. The margin is on the
     card rather than on a wrapper because pages compose them directly, and
     `:last-child` keeps it from adding dead space at the bottom of a page.
     Inside a grid the gap already spaces them, so the margin is cancelled
     there rather than doubling up. */
  margin-bottom: var(--gs-space-5);
}
.gs-card:last-child { margin-bottom: 0; }
.gs-grid > .gs-card,
.gs-grid > * > .gs-card { margin-bottom: 0; }
.gs-card__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gs-space-3);
  padding: var(--gs-space-4) var(--gs-space-5);
  border-bottom: 1px solid var(--gs-line);
}
.gs-card__title { margin: 0; font-size: var(--gs-fs-lg); font-weight: 800; }
.gs-card__sub { margin: 2px 0 0; font-size: var(--gs-fs-sm); color: var(--gs-muted); }
.gs-card__body { padding: var(--gs-space-5); }
.gs-card__body--flush { padding: 0; }

.gs-page-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--gs-space-4); flex-wrap: wrap;
  margin-bottom: var(--gs-space-5);
}
.gs-page-header__title { margin: 0; font-size: var(--gs-fs-2xl); }
.gs-page-header__sub { margin: 6px 0 0; color: var(--gs-muted);
                       font-size: var(--gs-fs-md); }
.gs-page-header__actions { display: flex; gap: 10px; flex-wrap: wrap; }

.gs-grid { display: grid; gap: var(--gs-space-4); }
.gs-grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
/* A wide main column and a narrower side one, collapsing to a single
   column rather than holding a 300px track a phone cannot give it. */
.gs-grid--split { grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); }
@media (max-width: 991.98px) {
  .gs-grid--split { grid-template-columns: 1fr; }
}
.gs-grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); }

.gs-stat {
  padding: var(--gs-space-4) var(--gs-space-5);
  background: var(--gs-depth-card), var(--gs-surface);
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius-lg);
  box-shadow: var(--gs-depth-shadow-sm), var(--gs-depth-edge);
  display: flex; align-items: center; gap: var(--gs-space-4);
}
.gs-stat__icon {
  width: 44px; height: 44px; border-radius: var(--gs-radius);
  display: grid; place-items: center; flex: none; font-size: 20px;
  background: var(--gs-accent-wash); color: var(--gs-accent);
}
.gs-stat__icon--muted { background: var(--gs-surface-3); color: var(--gs-muted); }
.gs-stat__icon--info  { background: var(--gs-info-wash);  color: var(--gs-info); }
.gs-stat__value { font-size: var(--gs-fs-2xl); font-weight: 800; line-height: 1.1; }
.gs-stat__label { font-size: var(--gs-fs-sm); color: var(--gs-muted);
                  font-weight: 600; }

/* ── table ────────────────────────────────────────────────────────── */

.gs-table-wrap { overflow-x: auto; }
.gs-table { width: 100%; border-collapse: collapse; font-size: var(--gs-fs-md); }
.gs-table th {
  text-align: left; padding: 12px var(--gs-space-5);
  font-size: var(--gs-fs-xs); font-weight: 800; letter-spacing: .6px;
  text-transform: uppercase; color: var(--gs-muted);
  border-bottom: 1px solid var(--gs-line); white-space: nowrap;
}
.gs-table td {
  padding: 14px var(--gs-space-5);
  border-bottom: 1px solid var(--gs-line-soft); vertical-align: middle;
}
.gs-table tbody tr:last-child td { border-bottom: 0; }
.gs-table tbody tr:hover { background: var(--gs-surface-2); }
.gs-table__actions { display: flex; gap: 8px; justify-content: flex-end; }
.gs-table__primary { font-weight: 700; color: var(--gs-ink); }
.gs-table__meta { font-size: var(--gs-fs-sm); color: var(--gs-muted); }

/* Below md a data table becomes stacked cards built from the same row
   markup. A 12-column table on a phone is unusable, and this is the one
   place that decision is made - not per screen. */
@media (max-width: 767.98px) {
  .gs-table, .gs-table tbody, .gs-table tr, .gs-table td { display: block; width: 100%; }
  .gs-table thead { display: none; }
  .gs-table tr {
    margin-bottom: var(--gs-space-3); padding: var(--gs-space-3) var(--gs-space-4);
    border: 1px solid var(--gs-line); border-radius: var(--gs-radius);
    background: var(--gs-surface-2);
  }
  .gs-table td { border: 0; padding: 6px 0; display: flex;
                 justify-content: space-between; gap: var(--gs-space-3); }
  .gs-table td::before {
    content: attr(data-label); font-size: var(--gs-fs-xs); font-weight: 800;
    text-transform: uppercase; letter-spacing: .5px; color: var(--gs-muted);
    flex: none;
  }
  .gs-table__actions { justify-content: flex-start; }
}


.gs-empty {
  padding: var(--gs-space-8) var(--gs-space-5); text-align: center;
  color: var(--gs-muted);
}
.gs-empty__icon { font-size: 34px; opacity: .5; margin-bottom: var(--gs-space-3); }
.gs-empty__title { font-weight: 800; color: var(--gs-ink-2);
                   margin-bottom: 6px; }

/* ── app shell: floating sidebar + command bar ────────────────────
   ServiceGeni's arrangement - the sidebar is a rounded card inset from the
   edge rather than flush, and each nav item carries its icon in a raised
   chip. The command bar is transparent and blurred over the content.
   ───────────────────────────────────────────────────────────────── */

/* No frame: the sidebar is a card floating on the page and the command bar
   is transparent across the top, which is what layouts/app.html has always
   said this shell is.

   The MARGIN stays. Only the frame's paint - fill, border, radius, shadow -
   comes off. Dropping the margin along with it is what put the command bar
   hard against the top of the window; the inset is what gives the whole
   console air, and it was never the thing making it look boxed in. */
.gs-shell {
  display: flex; position: relative; isolation: isolate;
  margin: var(--gs-space-4);
  min-height: calc(100dvh - var(--gs-space-4) * 2);
}

/* The sidebar is FIXED, not in-flow: the content column scrolls the page,
   and a sticky column would need the shell to be the scroll container,
   which breaks the sticky command bar above it. `border-radius: inherit`
   on the two left corners keeps it inside the frame's curve. */
.gs-sidenav {
  position: fixed; top: var(--gs-space-4); left: var(--gs-space-4);
  bottom: var(--gs-space-4); width: var(--gs-sidebar-w);
  display: flex; flex-direction: column;
  background: var(--gs-rail);
  -webkit-backdrop-filter: var(--gs-glass-blur);
  backdrop-filter: var(--gs-glass-blur);
  /* Rounded and bordered all the way round: it floats on the page now
     rather than being seamed into a frame's left edge. */
  border: 1px solid var(--gs-rail-line);
  border-radius: var(--gs-radius-lg);
  box-shadow: var(--gs-shadow-md);
  z-index: 40;
  transition: transform var(--gs-dur) var(--gs-ease),
              width var(--gs-dur) var(--gs-ease);
}

.gs-sidenav__header {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--gs-space-5) var(--gs-space-5) var(--gs-space-3);
}
.gs-sidenav__close {
  background: none; border: 0; color: var(--gs-muted); cursor: pointer;
  font-size: 18px; display: none;
}
.gs-sidenav__rule {
  margin: 0 var(--gs-space-5) var(--gs-space-3);
  border: 0; border-top: 1px solid var(--gs-line);
}

.gs-brand {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: var(--gs-fs-lg); font-weight: 800; letter-spacing: -.4px;
  color: var(--gs-ink); text-decoration: none;
}
.gs-brand:hover { color: var(--gs-ink); }
.gs-brand__mark {
  width: 32px; height: 32px; border-radius: 10px; flex: none;
  display: grid; place-items: center; font-size: 16px;
  background: var(--gs-gradient); color: var(--gs-on-accent);
  box-shadow: var(--gs-glow);
}
.gs-brand__glow { color: var(--gs-accent); }

.gs-sidenav__nav { flex: 1; overflow-y: auto; padding: 0 12px var(--gs-space-4); }
.gs-navlist { list-style: none; margin: 0; padding: 0; }
.gs-navlist__item { margin-bottom: 2px; }
.gs-navlist__empty {
  padding: 12px; font-size: var(--gs-fs-sm); color: var(--gs-muted);
}

/* The nav item: icon chip + label, exactly the ServiceGeni shape. */
.gs-navlink {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 12px; border-radius: var(--gs-radius-sm);
  color: var(--gs-ink-2); font-size: var(--gs-fs-md); font-weight: 600;
  text-decoration: none;
  transition: background var(--gs-dur) var(--gs-ease),
              color var(--gs-dur) var(--gs-ease);
}
.gs-navlink:hover { background: var(--gs-surface-2); color: var(--gs-ink); }
.gs-navlink__icon {
  width: 32px; height: 32px; flex: none; border-radius: 10px;
  display: grid; place-items: center; font-size: 15px;
  background: var(--gs-glass-bg-2); color: var(--gs-ink-2);
  border: 1px solid var(--gs-glass-border);
  box-shadow: var(--gs-shadow-sm), inset 0 1px 0 var(--gs-glass-edge);
  transition: background var(--gs-dur) var(--gs-ease),
              color var(--gs-dur) var(--gs-ease);
}
.gs-navlink__text { flex: 1; min-width: 0; overflow: hidden;
                    text-overflow: ellipsis; white-space: nowrap; }
.gs-navlink.is-active { color: var(--gs-ink); font-weight: 800;
                        background: var(--gs-surface-2); }
.gs-navlink.is-active .gs-navlink__icon {
  background: var(--gs-gradient); color: var(--gs-on-accent);
  border-color: transparent; box-shadow: var(--gs-glow);
}
.gs-navlink.is-disabled { opacity: .45; cursor: default; }
.gs-navlink__soon {
  font-size: 10px; font-weight: 800; letter-spacing: .4px;
  text-transform: uppercase; color: var(--gs-muted);
}

.gs-sidenav__foot {
  padding: var(--gs-space-3) 12px var(--gs-space-4);
  border-top: 1px solid var(--gs-line);
}

/* Content sits to the right of the floating card. */
.gs-main {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  /* The rail floats free of the content now, so this clears its width plus
     one gutter. The shell's own margin already supplies the gap on the
     rail's far side. */
  margin-left: calc(var(--gs-sidebar-w) + var(--gs-space-4));
  transition: margin-left var(--gs-dur) var(--gs-ease);
}

/* Inside the frame the bar is a header ROW, not a second floating panel:
   the frame already draws the edge that the pill was drawing for itself.
   It stays sticky, and carries the frame's own fill so content passing
   under it is covered rather than showing through. */
.gs-commandbar {
  height: var(--gs-topbar-h); flex: none;
  display: flex; align-items: center; gap: var(--gs-space-3);
  padding: 0 var(--gs-space-5);
  position: sticky; top: 0; z-index: 30;
  /* Genuinely transparent: no fill, no border, no rounded corner, and NO
     backdrop-filter. The filter was the last thing drawing an edge here - it
     paints a filtered rectangle, and saturate(170%) lifted the mesh inside
     the bar's 64px box, leaving a hard seam where the box ended. A
     transparent background cannot hide that, because the filter is itself
     the visible object. */
  background: transparent;
}
/* `.gs-commandbar__crumbs` and `.gs-commandbar__title` are GONE, with their
   markup — see _navbar.html. The bar was rendering "Pages / Integrations"
   over a bold "Integrations" directly above the page's own header saying it
   a third time, and a one-level app cannot have a breadcrumb trail without
   inventing one. What is left is the burger and the actions.

   On every width: below xl it opens the drawer, above it collapses the
   rail (see `initSidebar` in app.js and `window.gsRail` in theme.js) — one
   control, because to a reader it is the same idea: show me less of the
   sidebar. */
.gs-commandbar__burger { display: grid; }
.gs-commandbar__actions {
  margin-left: auto; display: flex; align-items: center; gap: 6px;
}
.gs-commandbar__user {
  display: flex; align-items: center; gap: 8px;
  /* --gs-radius, like every button. It is a button - it opens the account
     menu - so a pill made it the one control in the bar with its own shape. */
  padding: 5px 12px 5px 5px; border-radius: var(--gs-radius);
  background: var(--gs-glass-bg-2);
  border: 1px solid var(--gs-glass-border);
  box-shadow: inset 0 1px 0 var(--gs-glass-edge);
  color: var(--gs-ink); font-size: var(--gs-fs-sm); font-weight: 700;
  text-decoration: none; max-width: 230px; cursor: pointer;
  font-family: inherit;
}
.gs-commandbar__caret { font-size: 14px; color: var(--gs-muted); flex: none; }
.gs-commandbar__user:hover { border-color: var(--gs-accent);
                             color: var(--gs-ink); }
.gs-commandbar__username { overflow: hidden; text-overflow: ellipsis;
                           white-space: nowrap; }

.gs-iconbtn {
  /* --gs-radius, like every other button. It was the small radius, which on
     a 38px square read as a different family of control. */
  width: 38px; height: 38px; border-radius: var(--gs-radius);
  display: grid; place-items: center; flex: none;
  background: transparent; border: 1px solid transparent;
  color: var(--gs-ink-2); cursor: pointer; font-size: 17px;
  text-decoration: none;
  transition: background var(--gs-dur) var(--gs-ease),
              color var(--gs-dur) var(--gs-ease);
}
.gs-iconbtn:hover { background: var(--gs-surface-2); color: var(--gs-ink); }
.gs-iconbtn--danger:hover { background: var(--gs-danger-wash);
                            color: var(--gs-danger); }

/* -- the collapsed rail ----------------------------------------------
   The burger shrinks the sidebar to its icons. --gs-sidebar-w-collapsed has
   been sitting in tokens.css unused since the theme was written; this is
   what it was for.

   The class goes on <html>, not on the sidebar, for the same reason the
   theme does: it is restored from localStorage by theme.js BEFORE first
   paint, and <body> does not exist yet at that point. Stamped a frame late,
   you would watch the sidebar collapse itself on every page load.

   Everything that is words rather than an icon comes out: the wordmark, the
   labels, the group headings and the docs card. What is left is the column
   of chips, which is the whole point of a rail. */
.is-rail .gs-sidenav { width: var(--gs-sidebar-w-collapsed); }
.is-rail .gs-main {
  margin-left: calc(var(--gs-sidebar-w-collapsed) + var(--gs-space-4));
}
.is-rail .gs-navlink { justify-content: center; gap: 0; padding: 8px; }
.is-rail .gs-navlink__text,
.is-rail .gs-navlink__soon,
.is-rail .gs-navlink__pip,
.is-rail .gs-sidenav__grouplabel,
.is-rail .gs-sidedoc,
.is-rail .gs-brand { display: none; }
/* The header keeps its height so the rule below it does not jump up the
   column when the wordmark goes. */
.is-rail .gs-sidenav__header { justify-content: center; min-height: 32px; }
.is-rail .gs-sidenav__nav { padding-left: 8px; padding-right: 8px; }
.is-rail .gs-sidenav__foot { padding-left: 8px; padding-right: 8px; }

/* The ground the console sits on - darker than the frame, so the frame
   reads as lifted off it rather than painted onto it. */
.gs-app { background: var(--gs-ground); }

.gs-content { padding: var(--gs-space-5) var(--gs-space-5) var(--gs-space-7); }
/* NO max-width. The console frame is what bounds the layout now - it is
   already inset from the window - so a second cap in here just parked the
   cards against the left edge and left a dead gutter on the right of any
   screen wider than 1180px. Forms still get `--narrow`: a text field 1600px
   wide is unreadable, which is a different problem from a card grid. */
.gs-container { width: 100%; }
.gs-container--narrow { max-width: 760px; }

.gs-backdrop {
  position: fixed; inset: 0; background: rgb(0 0 0 / .55);
  z-index: 35; backdrop-filter: blur(2px);
}

@media (max-width: 1199.98px) {
  /* The rail is a DESKTOP idea. Down here the sidebar is an overlay that
     is either open or gone, so a 76px version of it would be a third state
     nobody asked for. The width override is reset rather than left to
     fight the drawer. */
  .is-rail .gs-sidenav { width: var(--gs-sidebar-w); }
  .is-rail .gs-navlink { justify-content: flex-start; gap: 11px; padding: 8px 12px; }
  .is-rail .gs-navlink__text,
  .is-rail .gs-sidenav__grouplabel,
  .is-rail .gs-sidedoc,
  .is-rail .gs-brand { display: revert; }
  .is-rail .gs-brand { display: inline-flex; }
  .gs-sidenav { transform: translateX(calc(-100% - var(--gs-space-5))); }
  .gs-sidenav.is-open { transform: translateX(0); }
  .gs-sidenav__close { display: block; }
  .gs-main { margin-left: 0; }
  .gs-commandbar__burger { display: grid; }
  .gs-commandbar__username { display: none; }
  .gs-content { padding: var(--gs-space-4) var(--gs-space-4) var(--gs-space-7); }
}

@media (max-width: 767.98px) {
  /* Reclaim the gutters. Three paddings stack on the way to a line of text:
     the shell's margin, the content's padding and the card's own. At 16 +
     16 + 22 that is 54px a side, and on a 390px phone it means 28% of the
     screen is gutter before a word is drawn.

     The console's inset is the one to lose first. It exists to make the
     shell read as a device sitting on a ground, and a phone screen IS the
     device - there is no ground to sit on, so the margin is only costing
     width. The card keeps a smaller inset, because that one is doing real
     work: it separates the card's content from its own edge. */
  .gs-shell { margin: 0; min-height: 100dvh; border-radius: 0; }
  .gs-content { padding: var(--gs-space-4) var(--gs-space-3) var(--gs-space-6); }
  .gs-card__head { padding: var(--gs-space-3) var(--gs-space-4); }
  .gs-card__body { padding: var(--gs-space-4); }
  .gs-page-header { margin-bottom: var(--gs-space-4); }
}
@media (min-width: 1200px) {
  .gs-backdrop { display: none !important; }
}

/* ── modals ──────────────────────────────────────────────────────────
   Native <dialog>: the browser supplies the backdrop, the focus trap,
   Escape-to-close and inertness behind. Everything create/edit in the
   product happens in one of these rather than on its own page.
   ───────────────────────────────────────────────────────────────── */

.gs-modal {
  padding: 0; border: 1px solid var(--gs-glass-border);
  border-radius: var(--gs-radius-xl);
  background: var(--gs-glass-bg-2); color: var(--gs-ink);
  -webkit-backdrop-filter: var(--gs-glass-blur);
  backdrop-filter: var(--gs-glass-blur);
  box-shadow: var(--gs-glass-shadow), inset 0 1px 0 var(--gs-glass-edge);
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100dvh - 64px);
  overflow: hidden;
}
.gs-modal--sm { width: min(390px, calc(100vw - 32px)); }
.gs-modal--lg { width: min(860px, calc(100vw - 32px)); }
.gs-modal::backdrop { background: rgb(0 0 0 / .45);
                      -webkit-backdrop-filter: blur(6px);
                      backdrop-filter: blur(6px); }
.gs-modal[open] { animation: gs-modal-in var(--gs-dur) var(--gs-ease); }
@keyframes gs-modal-in {
  from { opacity: 0; transform: translateY(8px) scale(.99); }
  to   { opacity: 1; transform: none; }
}

.gs-modal__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gs-space-3);
  padding: var(--gs-space-4) var(--gs-space-5);
  border-bottom: 1px solid var(--gs-line);
  /* Head and foot are bands, the body is the middle. Three strips rather
     than one flat sheet is what gives a small dialog its structure - with
     everything on one fill, the buttons float in the same space as the
     question. */
  background: var(--gs-surface-2);
}
/* Icon and title travel together on the left. */
.gs-modal__lead { display: flex; align-items: center; gap: 11px; min-width: 0; }
.gs-modal__icon {
  width: 30px; height: 30px; flex: none; border-radius: var(--gs-radius-sm);
  display: grid; place-items: center; font-size: 16px;
  background: var(--gs-accent-wash); color: var(--gs-accent);
}
/* A destructive confirm says so in the icon, not only in the button. */
.gs-modal--danger .gs-modal__icon {
  background: var(--gs-danger-wash); color: var(--gs-danger);
}
.gs-modal__title { margin: 0; font-size: var(--gs-fs-lg); font-weight: 800; }
.gs-modal__sub { margin: 4px 0 0; font-size: var(--gs-fs-sm);
                 color: var(--gs-muted); }
.gs-modal__x {
  background: none; border: 0; color: var(--gs-muted); cursor: pointer;
  font-size: 18px; line-height: 1; padding: 2px;
}
.gs-modal__x:hover { color: var(--gs-ink); }

.gs-modal--sm .gs-modal__body { padding: var(--gs-space-5); }
.gs-modal__body {
  padding: var(--gs-space-5);
  overflow-y: auto;
  max-height: calc(100dvh - 260px);
}
.gs-modal__text { margin: 0; color: var(--gs-ink-2); }

.gs-modal__foot {
  display: flex; justify-content: flex-end; gap: 10px;
  padding: var(--gs-space-4) var(--gs-space-5);
  border-top: 1px solid var(--gs-line);
  background: var(--gs-surface-2);
}
/* The title stays in the normal ink. The icon carries the warning, and a
   red heading next to a red button says the same thing twice. */

/* Compact actions. Small, uppercase and tracked, so two short verbs read as
   a pair of controls rather than two words in a sentence. */
.gs-modal__foot .gs-btn {
  min-height: 36px; padding: 0 16px;
  font-size: var(--gs-fs-xs); letter-spacing: .08em; text-transform: uppercase;
}

.gs-inline-form { display: inline; }

/* ── flash messages ───────────────────────────────────────────────── */

.gs-flashes { position: fixed; top: 18px; right: 18px; z-index: 80;
              display: flex; flex-direction: column; gap: 10px;
              max-width: min(420px, calc(100vw - 36px)); }
.gs-flash {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 13px 15px; border-radius: var(--gs-radius);
  background: var(--gs-bg-elevated); border: 1px solid var(--gs-line);
  box-shadow: var(--gs-shadow-lg); font-size: var(--gs-fs-md);
  animation: gs-slide-in var(--gs-dur) var(--gs-ease);
}
.gs-flash--success { border-left: 3px solid var(--gs-success); }
.gs-flash--error   { border-left: 3px solid var(--gs-danger); }
.gs-flash--info    { border-left: 3px solid var(--gs-info); }
.gs-flash__close { margin-left: auto; background: none; border: 0;
                   color: var(--gs-muted); cursor: pointer; font-size: 16px; }
@keyframes gs-slide-in {
  from { opacity: 0; transform: translateX(14px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ── forms: field, help, error, switch ────────────────────────────── */

.gs-field { margin-bottom: var(--gs-space-4); }
.gs-field__help { margin-top: 6px; font-size: var(--gs-fs-sm); color: var(--gs-muted); }
.gs-field__error { margin-top: 6px; font-size: var(--gs-fs-sm);
                   color: var(--gs-danger); font-weight: 600; }
.gs-field--invalid .form-control { border-color: var(--gs-danger); }

/* ── password reveal ─────────────────────────────────────────────────
   The eye sits INSIDE the input's box rather than beside it, so a password
   field is the same width as every other field on the form. The input gets
   right padding to match, which is what stops long passwords running under
   the button. */

.gs-pw { position: relative; display: block; }
/* display:block on the input is not cosmetic. An inline-block input leaves a
   ~4px line-box descender under it, so the wrapper ends up taller than the
   field and the eye - which is height:100% of the wrapper - hangs below the
   input's bottom border. On the auth pages, where .form-control has a fixed
   48px height, that misalignment is obvious. */
.gs-pw .form-control { display: block; }
.gs-input--reveal { padding-right: 46px; }

.gs-pw__eye {
  position: absolute; top: 0; right: 0;
  width: 44px; height: 100%;
  display: grid; place-items: center;
  background: none; border: 0; padding: 0;
  color: var(--gs-muted); font-size: 17px; line-height: 1;
  cursor: pointer;
  border-radius: 0 var(--gs-radius) var(--gs-radius) 0;
  transition: color var(--gs-dur) var(--gs-ease);
}
.gs-pw__eye:hover { color: var(--gs-ink); }
.gs-pw__eye[aria-pressed='true'] { color: var(--gs-accent); }
.gs-pw__eye:focus-visible { outline: 2px solid var(--gs-accent);
                            outline-offset: -2px; }

/* Chromium and Edge render their own reveal control on password inputs, and
   Safari renders a password-manager key. Two eyes side by side looks broken,
   so the native ones are removed and ours is the only affordance. */
.gs-input--reveal::-ms-reveal,
.gs-input--reveal::-ms-clear { display: none; }
.gs-input--reveal::-webkit-credentials-auto-fill-button,
.gs-input--reveal::-webkit-strong-password-auto-fill-button {
  margin-right: 34px;
}

.gs-check { display: flex; align-items: flex-start; gap: 10px;
            font-size: var(--gs-fs-md); color: var(--gs-ink-2); cursor: pointer; }
.gs-check input { accent-color: var(--gs-accent); width: 16px; height: 16px;
                  margin-top: 3px; flex: none; }

.gs-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; }
.gs-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.gs-switch__track {
  width: 42px; height: 24px; border-radius: 999px; flex: none;
  background: var(--gs-surface-3); border: 1px solid var(--gs-line);
  position: relative; transition: background var(--gs-dur) var(--gs-ease);
}
.gs-switch__track::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%; background: var(--gs-muted);
  transition: transform var(--gs-dur) var(--gs-ease),
              background var(--gs-dur) var(--gs-ease);
}
.gs-switch input:checked + .gs-switch__track { background: var(--gs-accent-wash);
                                               border-color: var(--gs-accent); }
.gs-switch input:checked + .gs-switch__track::after {
  transform: translateX(18px); background: var(--gs-accent);
}
.gs-switch input:focus-visible + .gs-switch__track {
  box-shadow: 0 0 0 3px var(--gs-accent-ring);
}

/* Module picker: a scrollable checkbox grid, used on the role form. */
.gs-picker {
  max-height: 380px; overflow-y: auto; padding: 4px;
  border: 1px solid var(--gs-line); border-radius: var(--gs-radius);
  background: var(--gs-surface-2);
}
.gs-picker__group { font-size: var(--gs-fs-xs); font-weight: 800;
                    letter-spacing: .6px; text-transform: uppercase;
                    color: var(--gs-muted); padding: 12px 10px 6px; }
.gs-picker__item {
  display: flex; align-items: flex-start; gap: 10px; padding: 9px 10px;
  border-radius: var(--gs-radius-sm); cursor: pointer;
}
.gs-picker__item:hover { background: var(--gs-surface-3); }
.gs-picker__item input { accent-color: var(--gs-accent); width: 16px;
                         height: 16px; margin-top: 3px; flex: none; }
.gs-picker__name { font-weight: 700; font-size: var(--gs-fs-md); }
.gs-picker__code { font-family: var(--gs-font-mono); font-size: var(--gs-fs-xs);
                   color: var(--gs-muted); }

.gs-code { font-family: var(--gs-font-mono); font-size: var(--gs-fs-xs);
           background: var(--gs-surface-3); padding: 2px 7px;
           border-radius: 6px; color: var(--gs-ink-2); }

.gs-toolbar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
              margin-bottom: var(--gs-space-4); }
.gs-toolbar .form-control, .gs-toolbar .form-select { width: auto; min-width: 190px; }

.gs-danger-zone {
  border: 1px solid color-mix(in srgb, var(--gs-danger) 35%, transparent);
  border-radius: var(--gs-radius-lg); background: var(--gs-danger-wash);
}
.gs-danger-zone .gs-card__head { border-bottom-color:
  color-mix(in srgb, var(--gs-danger) 25%, transparent); }

/* =====================================================================
   Auth - ONE card, split on a diagonal
   ---------------------------------------------------------------------
   The old layout was two half-screen columns with an animated brand panel
   on the left. This is a single card floating on a lit ground, cut across
   by a diagonal: the form on the dark half, a greeting on the teal half.

   The two halves SWAP SIDES between sign-in and sign-up, and that swap is
   the whole motion idea. It is driven by one variable, `--gs-auth-slant`,
   with the two clip-paths written as mirrors of each other, so the two
   pages are the same geometry flipped rather than two shapes that can
   drift apart.

   Crossing between the two pages animates for real, via a cross-document
   view transition - see the `@view-transition` rule below. That is why the
   panel does not also animate its own position on load: two things moving
   the same element is how a slide turns into a stutter.

   Everything from `.gs-auth__title` down is unchanged: the form internals,
   the Google button, the pickers and the field styling are all still the
   rules they were, so seven auth pages did not need touching.
   ===================================================================== */

.gs-auth {
  min-height: 100vh; min-height: 100dvh;
  display: grid; place-items: center;
  padding: 30px 22px;
  position: relative; isolation: isolate;
  background: var(--gs-auth-ground);
}
/* Two soft pools behind the card, so its halo has something to sit in
   rather than glowing against a flat wall. Fixed and gradient-only: one
   paint, no filtered layers. */
.gs-auth::before {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(700px 540px at 84% 6%,  var(--gs-auth-bloom-1), transparent 68%),
    radial-gradient(620px 500px at 10% 94%, var(--gs-auth-bloom-2), transparent 70%);
}

.gs-auth__card {
  position: relative;
  width: min(1000px, 100%);
  display: grid;
  /* One height for every auth screen, set by the fullest of them: sign-in,
     with two fields, the remember row, the button, the divider and Google.
     Reset, check-your-email and activate carry a fraction of that content
     and were rendering as short, wide letterbox cards that looked like a
     different component.

     min-height, not height: sign-up is taller than this and has to stay
     free to grow. It pads the short pages up and never squashes a long one.
     The diagonal is a percentage, so a consistent height is also what keeps
     the slash at the same angle from screen to screen. */
  min-height: 580px;
  border-radius: var(--gs-radius-xl);
  background: var(--gs-auth-card);
  border: 1px solid var(--gs-auth-edge);
  box-shadow: var(--gs-auth-halo);
  overflow: hidden;
}

/* -- the teal half ----------------------------------------------------
   54% wide, clipped to a trapezoid. Anchored right for sign-in and left
   for sign-up; the two clip-paths are mirrors, so the diagonal leans the
   same way whichever side it is on.

   Static. The shape is two constants and nothing animates it, so the slant
   is used directly rather than through a multiplier - the indirection that
   used to be here existed only so a keyframe could interpolate it. */
.gs-auth__panel {
  position: absolute; top: 0; bottom: 0; width: 54%;
  display: grid; place-items: center; padding: 40px;
  background: var(--gs-auth-panel);
}
.gs-auth__card--right .gs-auth__panel {
  right: 0;
  clip-path: polygon(var(--gs-auth-slant) 0, 100% 0, 100% 100%, 0 100%);
}
.gs-auth__card--left .gs-auth__panel {
  left: 0;
  clip-path: polygon(0 0, 100% 0,
                     calc(100% - var(--gs-auth-slant)) 100%, 0 100%);
}

/* -- the panel holding content --------------------------------------
   Normally the teal half is a greeting in the middle of a lot of nothing.
   On sign-up it carries the organisation picker, because that is the one
   question only that page asks and the space was already there.

   The content column has to stay clear of the diagonal. On the sign-up
   card the cut runs from the panel's full width at the top down to 76% of
   it at the bottom, so 26% of padding on that edge keeps the widest option
   card off the slant at every height. */
.gs-auth__panelin { display: grid; place-items: center; min-width: 0; }
.gs-auth__panel--holds { place-items: stretch; }
.gs-auth__panel--holds .gs-auth__panelin {
  display: flex; flex-direction: column; justify-content: center;
  height: 100%; place-items: stretch;
}
.gs-auth__card--left  .gs-auth__panel--holds { padding: 44px 26% 44px 44px; }
.gs-auth__card--right .gs-auth__panel--holds { padding: 44px 44px 44px 26%; }
/* Left-aligned and smaller: it is a heading over a list now, not the only
   thing on the half. */
.gs-auth__panel--holds .gs-auth__greet {
  font-size: clamp(1.25rem, 2.1vw, 1.65rem); text-align: left;
  margin-bottom: 22px;
}

/* -- the picker, on teal ----------------------------------------------
   Deliberately NOT .gs-pickcard restyled. That component sits on the app's
   own surfaces on half a dozen screens; this one sits on artwork and needs
   its own contrast story, and sharing a class would tie the two together
   for no reason. */
.gs-authpick { border: 0; padding: 0; margin: 0; min-width: 0; }
.gs-authpick__legend {
  font-size: var(--gs-fs-xs); font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gs-authpick-sub);
  margin-bottom: 10px; padding: 0;
}
.gs-authpick__card { display: block; cursor: pointer; }
.gs-authpick__card + .gs-authpick__card { margin-top: 9px; }
/* Hidden but not display:none -- a display-hidden radio is skipped by
   keyboard navigation and unreachable by a screen reader. */
.gs-authpick__card input {
  position: absolute; opacity: 0; width: 1px; height: 1px;
  margin: -1px; overflow: hidden; clip: rect(0 0 0 0);
}
.gs-authpick__body {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border-radius: var(--gs-radius);
  background: var(--gs-authpick-plate);
  border: 1px solid var(--gs-authpick-line);
  transition: background var(--gs-dur) var(--gs-ease),
              border-color var(--gs-dur) var(--gs-ease);
}
.gs-authpick__card input:checked + .gs-authpick__body {
  background: var(--gs-authpick-plate-on);
  border-color: var(--gs-authpick-line-on);
}
.gs-authpick__card input:focus-visible + .gs-authpick__body {
  outline: 2px solid var(--gs-auth-greet); outline-offset: 2px;
}
.gs-authpick__icon {
  width: 32px; height: 32px; flex: none; border-radius: 10px;
  display: grid; place-items: center; font-size: 15px;
  background: var(--gs-authpick-chip); color: var(--gs-auth-greet);
}
.gs-authpick__text { min-width: 0; flex: 1; }
.gs-authpick__name {
  display: block; font-size: var(--gs-fs-md); font-weight: 800;
  color: var(--gs-auth-greet);
}
.gs-authpick__desc {
  display: block; font-size: var(--gs-fs-xs); line-height: 1.45;
  color: var(--gs-authpick-sub);
}
.gs-authpick__tick {
  flex: none; opacity: 0; color: var(--gs-auth-greet); font-size: 16px;
  transition: opacity var(--gs-dur) var(--gs-ease);
}
.gs-authpick__card input:checked + .gs-authpick__body .gs-authpick__tick {
  opacity: 1;
}
/* With nothing chosen, every option is the thing to fix, so they all take
   the alert border. The message itself is at the top of the form column,
   not here. */
.gs-authpick--invalid .gs-authpick__body {
  border-color: var(--gs-authpick-err-line);
}

.gs-auth__greet {
  font-size: clamp(1.6rem, 3.4vw, 2.6rem); font-weight: 800;
  line-height: 1.05; letter-spacing: -.02em; text-transform: uppercase;
  text-align: center; text-wrap: balance;
  color: var(--gs-auth-greet);
  /* The greeting sits over artwork, so it gets the scrim a photo caption
     gets. Without it the words land on the pale top corner at about 2:1. */
  text-shadow: 0 1px 18px rgb(4 26 32 / .45);
}
.gs-auth__greet small {
  display: block; margin-top: 12px;
  font-size: var(--gs-fs-md); font-weight: 600; letter-spacing: 0;
  text-transform: none; opacity: .84; max-width: 30ch; margin-inline: auto;
}

/* ── the dark half ───────────────────────────────────────────────
   45% rather than 55%: the diagonal's inner edge reaches 46% of the card at
   its widest, so a wider pane would put the last field under the teal. */
.gs-auth__pane {
  position: relative; z-index: 2;
  width: 45%; padding: 42px 40px 38px;
}
.gs-auth__card--right .gs-auth__pane { justify-self: start; }
.gs-auth__card--left  .gs-auth__pane { justify-self: end; }

.gs-auth__panelogo { display: flex; margin-bottom: 26px; }

/* ── the fields, underlined rather than boxed ────────────────────
   Scoped to .gs-auth on purpose. `.form-control` is every input in the
   app; the underline treatment belongs to this screen and nowhere else. */
.gs-auth .form-control {
  height: 48px;
  /* Room on the trailing edge for the glyph and the password eye. The rest
     of the shape -- the rounded corners, the border, the fill and the focus
     ring -- is the app's own .form-control, inherited rather than restated.
     An auth field that styles itself is one that drifts from every other
     field in the product. */
  padding: 10px 42px 10px 15px;
}

/* Chrome repaints an autofilled input with its OWN fill and text colour, so
   a remembered email rendered as a pale box that matched nothing else on the
   card. That fill cannot be overridden with `background`; a large inset
   shadow is the only thing that covers it. */
.gs-auth .form-control:-webkit-autofill,
.gs-auth .form-control:-webkit-autofill:hover,
.gs-auth .form-control:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--gs-ink);
  -webkit-box-shadow: 0 0 0 1000px var(--gs-surface-2) inset;
  caret-color: var(--gs-ink);
}
.gs-auth .form-label {
  font-size: var(--gs-fs-xs); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--gs-muted);
}
/* The trailing glyph, and the password eye, share one box. */
.gs-field__wrap { position: relative; }
.gs-field__icon {
  position: absolute; right: 15px; top: 50%; transform: translateY(-50%);
  font-size: 17px; color: var(--gs-muted); pointer-events: none;
}

/* ── the load sequence ───────────────────────────────────────────
   Four pieces, in order: the card settles, the greeting rises, the form
   rises behind it. Each starts from a visible resting state on the last
   frame, so a browser that refuses to animate still shows the finished
   page rather than an empty one. */
@keyframes gs-auth-settle {
  from { opacity: 0; transform: translateY(14px) scale(.988); }
  to   { opacity: 1; transform: none; }
}
@keyframes gs-auth-rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}
.gs-auth__card  { animation: gs-auth-settle 620ms var(--gs-ease) both; }
.gs-auth__greet { animation: gs-auth-rise 620ms 180ms var(--gs-ease) both; }
.gs-auth__pane  { animation: gs-auth-rise 560ms 120ms var(--gs-ease) both; }

/* The sweep is gone: the two halves are static, so there is no cover, no
   retract, no timing variables and no .is-leaving / .gs-auth-arriving
   rules. Sign-in and sign-up are ordinary links between two pages that
   happen to mirror each other. auth-sweep.js is deleted with it. */

@media (max-width: 899.98px) {
  /* Stacked: the teal becomes a band across the top with a slanted lower
     edge, which keeps the diagonal as the motif without asking a phone to
     fit two columns beside each other. */
  /* Stacked, the card is a teal band above a form and its height is whatever
     that comes to. The desktop floor would only add dead space under the
     last field. */
  .gs-auth__card { display: block; min-height: 0; }
  .gs-auth__panel {
    position: static; width: 100%; height: 150px; padding: 26px 24px 34px;
    clip-path: polygon(0 0, 100% 0, 100% 74%, 0 100%) !important;
  }
  /* A band cannot hold a list, so when the panel carries the picker it
     stops being a fixed 150px strip and takes the height it needs. The
     picker stays on the teal rather than being duplicated into the form,
     which would mean two sets of radios with one name. */
  .gs-auth__panel--holds {
    height: auto; padding: 26px 24px 38px !important;
  }
  .gs-auth__panel--holds .gs-auth__greet { font-size: 1.35rem; }
  .gs-auth__pane { width: 100%; padding: 26px 24px 32px; }
  .gs-auth__greet { font-size: 1.5rem; }
  .gs-auth__greet small { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .gs-auth__card, .gs-auth__greet, .gs-auth__pane { animation: none; }
}

/* ── the logo ─────────────────────────────────────────────────────
   The wordmark alone - the GS mark and the divider bar are cropped off, and
   the "Gearshift" text that used to sit beside the chip is gone with them,
   because the artwork now IS the name.

   Sized by WIDTH, not height. At 368x27 the wordmark is 13.6:1, and a height
   big enough to read would run past the 216px the sidebar header has once the
   close button takes its share below xl. Width-first with height:auto also
   means it simply shrinks on a narrow rail instead of overflowing.

   The <img> tags carry the intrinsic 368x27 so the browser reserves the right
   box before the file lands; the rules below decide how big it actually draws.

   TWO FILES, not one filter. The wordmark is flat #005050: on a #050c0e rail
   that is 2.1:1 and it effectively disappears, so the dark theme gets a
   reversed copy lifted to #54EEF0 - 13.9:1, and NOT a new colour: it is
   sampled from the bright end of the S in the supplied mark, so nothing
   enters the wordmark that was not already in the artwork. No neutral is
   used anywhere. "AI" is already bright cyan and is left alone in both.

   The theme rules mirror tokens.css deliberately: dark is what bare :root
   gets, an explicit [data-theme] wins either direction, and the media query
   only speaks when nothing has been stamped. Get that shape wrong and the
   un-stamped state shows one theme's logo on the other theme's ground. */
.gs-brand__logo { display: block; width: 100%; height: auto; }
.gs-brand__logo--light { display: none; }

[data-theme='light'] .gs-brand__logo--light { display: block; }
[data-theme='light'] .gs-brand__logo--dark  { display: none; }

@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) .gs-brand__logo--light { display: block; }
  :root:not([data-theme='dark']) .gs-brand__logo--dark  { display: none; }
}

.gs-brand { min-width: 0; }
.gs-brand .gs-brand__logo        { max-width: 186px; }
.gs-auth__brand .gs-brand__logo  { max-width: 300px; }
/* Ties with the rule above on specificity, so it has to stay after it. */
.gs-auth__logo-m .gs-brand__logo { max-width: 240px; }

/* Kept from the old shell: the logo link, the small-screen logo slot and
   the theme toggle are all still in the layout. The brand panel, its orbs,
   the gear-teeth bars, the headline, the lead and the role chips are not -
   the diagonal card has no column for them. */
.gs-auth__logo-m { display: flex; justify-content: center; margin-bottom: 22px; }
.gs-auth__logo-m .gs-auth__brand { color: var(--gs-ink); }
.gs-auth__themetoggle { position: absolute; top: 18px; right: 18px; z-index: 3; }

.gs-auth__title { font-size: 28px; font-weight: 800; letter-spacing: -.6px;
                  color: var(--gs-ink); margin: 0 0 6px; }
.gs-auth__msg { font-size: 14px; color: var(--gs-muted); margin: 0 0 24px; }
.gs-auth__msg :is(.text-danger, .text-success) { font-weight: 700; }


.gs-auth__submit {
  width: 100%; height: 48px; border-radius: var(--gs-radius);
  border: 1px solid var(--gs-btn-line);
  font-size: 14.5px; font-weight: 800; color: var(--gs-btn-ink);
  background: var(--gs-btn-face); box-shadow: var(--gs-btn-glow);
  cursor: pointer; font-family: inherit;
  transition: transform var(--gs-dur) var(--gs-ease),
              filter var(--gs-dur) var(--gs-ease);
}
.gs-auth__submit:hover { transform: translateY(-1px); filter: brightness(1.10); }
.gs-auth__submit:active { transform: translateY(0); }

.gs-auth__btns { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* Two buttons across a 320px screen leaves each about 130px, and "Send
   reset link" does not fit in 130px. Below that they stack. */
@media (max-width: 479.98px) {
  .gs-auth__btns { grid-template-columns: 1fr; }
}

/* `.gs-auth__submit--wide` is gone. It existed to make the sign-in and
   sign-up button a pill, and that pill was the one shape in the app that
   disagreed with every other button. Width was never the difference -
   .gs-auth__submit is already width:100% - so with the radius gone the
   modifier did nothing. */

/* The way to the other page, as a sentence under the button rather than a
   second button of equal weight. */
.gs-auth__swap { margin: 16px 0 0; text-align: center;
                 font-size: var(--gs-fs-sm); color: var(--gs-muted); }
.gs-auth__swap a { font-weight: 700; color: var(--gs-accent); }
.gs-auth__swap a:hover { text-decoration: underline; }
.gs-auth__btn-outline {
  height: 48px; border-radius: var(--gs-radius); font-size: 14px;
  font-weight: 700; color: var(--gs-ink); background: transparent;
  border: 1px solid var(--gs-line-strong); text-decoration: none;
  display: flex; align-items: center; justify-content: center;
  transition: border-color var(--gs-dur) var(--gs-ease),
              background var(--gs-dur) var(--gs-ease);
}
.gs-auth__btn-outline:hover { border-color: var(--gs-accent);
                              background: var(--gs-surface-2);
                              color: var(--gs-ink); }

.gs-auth__forgot { display: block; text-align: right; font-size: 13px;
                   font-weight: 700; color: var(--gs-accent-soft);
                   margin: 2px 0 20px; }
.gs-auth__forgot:hover { text-decoration: underline; }

.gs-auth__divider { display: flex; align-items: center; gap: 12px;
                    margin: 22px 0; color: var(--gs-muted);
                    font-size: 12px; font-weight: 700; }
.gs-auth__divider::before, .gs-auth__divider::after {
  content: ''; flex: 1; height: 1px; background: var(--gs-line);
}

.gs-auth__google {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; height: 48px; border-radius: var(--gs-radius);
  font-size: 14px; font-weight: 700; color: var(--gs-ink);
  background: var(--gs-surface); border: 1px solid var(--gs-line-strong);
  text-decoration: none;
  transition: border-color var(--gs-dur) var(--gs-ease),
              background var(--gs-dur) var(--gs-ease);
}
.gs-auth__google:hover { border-color: var(--gs-accent);
                         background: var(--gs-surface-2); color: var(--gs-ink); }
.gs-auth__google--off { opacity: .5; cursor: not-allowed; }

.gs-auth__check { display: flex; align-items: flex-start; gap: 9px;
                  font-size: 13px; font-weight: 600; color: var(--gs-muted);
                  margin-bottom: 18px; cursor: pointer; }
.gs-auth__check input { accent-color: var(--gs-accent); width: 15px;
                        height: 15px; margin-top: 3px; flex: none; }

.gs-auth__remember { display: flex; align-items: center; gap: 8px;
                     font-size: 13px; font-weight: 600; color: var(--gs-muted);
                     cursor: pointer; }
.gs-auth__remember input { accent-color: var(--gs-accent); width: 15px;
                           height: 15px; }
.gs-auth__row { display: flex; align-items: center; justify-content: space-between;
                gap: 12px; margin: 2px 0 20px; }
.gs-auth__row .gs-auth__forgot { margin: 0; }

.gs-auth__back { display: block; text-align: center; font-size: 13px;
                 font-weight: 700; color: var(--gs-accent-soft);
                 margin-top: 20px; }
.gs-auth__back:hover { text-decoration: underline; }

.gs-auth__note {
  background: var(--gs-accent-wash);
  border: 1px solid color-mix(in srgb, var(--gs-accent) 26%, transparent);
  border-radius: var(--gs-radius); padding: 14px 16px;
  font-size: 13.5px; color: var(--gs-ink-2); margin-bottom: 20px;
  line-height: 1.55;
}

/* The status disc on the "check your email" and "set your password" screens.
   Tinted by role rather than always accent: a dead link and a delivered email
   are opposite outcomes and must not look identical at a glance. */
.gs-auth__icon {
  width: 56px; height: 56px; border-radius: var(--gs-radius-pill);
  display: grid; place-items: center; font-size: 26px;
  margin: 0 0 20px;
  color: var(--gs-accent-soft);
  background: var(--gs-accent-wash);
  border: 1px solid color-mix(in srgb, var(--gs-accent) 30%, transparent);
}
.gs-auth__icon--ok {
  color: var(--gs-success); background: var(--gs-success-wash);
  border-color: color-mix(in srgb, var(--gs-success) 34%, transparent);
}
.gs-auth__icon--warn {
  color: var(--gs-warning); background: var(--gs-warning-wash);
  border-color: color-mix(in srgb, var(--gs-warning) 34%, transparent);
}

.gs-auth__foot {
  font-size: 13px; color: var(--gs-muted); margin: 18px 0 0; text-align: center;
}

/* ── a settings screen: section rail + one panel ───────────────────
   The shape a settings page wants once it has more than two things in it.
   Password, Sessions, Sign-in methods and Close account are four different
   errands, and stacking them means scrolling past a destructive one to
   reach a routine one.

   Built to grow. Two-factor, API tokens and device history each become a
   rail entry rather than a fifth card on an ever-longer page. */
.gs-settings {
  display: grid; gap: var(--gs-space-5); align-items: start;
  grid-template-columns: 232px minmax(0, 1fr);
}
/* Follows you down a long panel - the danger zone is the tallest, and a rail
   that scrolls away is a rail you have to scroll back up to use. */
.gs-settings__rail { position: sticky; top: var(--gs-space-4); }
.gs-settings__list { display: flex; flex-direction: column; gap: 3px; }
.gs-settings__link {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border-radius: var(--gs-radius);
  font-family: inherit; font-size: var(--gs-fs-md); font-weight: 700;
  color: var(--gs-muted); background: none; border: 0; cursor: pointer;
  text-align: left;
}
.gs-settings__link:hover { background: var(--gs-surface-2); color: var(--gs-ink); }
.gs-settings__link.is-active { background: var(--gs-accent-wash); color: var(--gs-ink); }
.gs-settings__ico {
  width: 28px; height: 28px; flex: none; border-radius: 9px;
  display: grid; place-items: center; font-size: 14px;
  background: var(--gs-surface-3); color: var(--gs-muted);
}
.gs-settings__link.is-active .gs-settings__ico {
  background: var(--gs-accent); color: var(--gs-on-accent);
}
/* Closing an account is not the same kind of errand as changing a password,
   and the rail is the one place that difference is visible at a glance. */
.gs-settings__link--danger { color: var(--gs-danger); }
.gs-settings__link--danger:hover { background: var(--gs-danger-wash); color: var(--gs-danger); }
.gs-settings__link--danger.is-active { background: var(--gs-danger-wash); color: var(--gs-danger); }
.gs-settings__link--danger.is-active .gs-settings__ico {
  background: var(--gs-danger); color: var(--gs-white);
}

@media (max-width: 899.98px) {
  /* No rail on a phone: it would be a full-width row of four buttons above
     the thing you came for. The page falls back to every section stacked,
     which is what it was before the rail existed.

     `!important` because the utilities set `[hidden] { display: none
     !important }`, and here the panels are meant to all be visible. */
  .gs-settings { grid-template-columns: 1fr; }
  .gs-settings__rail { display: none; }
  .gs-settings__panel[hidden] { display: block !important; }
}

/* ── nothing goes sideways ────────────────────────────────────────
   Emails, slugs, run ids, endpoint names and module codes have no spaces
   in them, and on a phone there is nowhere for them to wrap. One long
   value in a stacked table cell then pushes the whole page sideways, and
   a horizontally scrolling page is the most common phone bug there is.

   `anywhere` rather than `break-word`: break-word will not break inside a
   word that has already established a line, which is exactly the case
   here - a 40-character address on its own line. */
.gs-table td, .gs-kv dd, .gs-dl dd,
.gs-table__meta, .gs-field__help, .gs-field__error {
  overflow-wrap: anywhere;
}
/* Except where the value IS a column of digits: breaking a phone number or
   a total across two lines makes it unreadable rather than merely wide. */
.gs-table td.num, .num { overflow-wrap: normal; }

/* ── utilities, the few Bootstrap lacks ───────────────────────────── */

.gs-stack   { display: flex; flex-direction: column; gap: var(--gs-space-3); }
.gs-row     { display: flex; align-items: center; gap: var(--gs-space-3);
              flex-wrap: wrap; }
.gs-row--between { justify-content: space-between; }
.gs-mt-0 { margin-top: 0; }
.gs-mb-0 { margin-bottom: 0; }
.gs-mb-4 { margin-bottom: var(--gs-space-4); }
.gs-mb-5 { margin-bottom: var(--gs-space-5); }
.gs-text-sm { font-size: var(--gs-fs-sm); }
.gs-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
               max-width: 100%; }
[hidden] { display: none !important; }

/* ── form helpers used inside modals ─────────────────────────────── */

/* Two fields side by side, stacking on a phone. Used by every modal form,
   so the breakpoint is decided once. */
.gs-form-row { display: grid; grid-template-columns: 1fr 1fr;
               gap: var(--gs-space-3); }
@media (max-width: 575.98px) { .gs-form-row { grid-template-columns: 1fr; } }

.gs-note {
  background: var(--gs-accent-wash);
  border: 1px solid color-mix(in srgb, var(--gs-accent) 26%, transparent);
  border-radius: var(--gs-radius); padding: 12px 14px;
  font-size: var(--gs-fs-sm); color: var(--gs-ink-2); line-height: 1.55;
}
.gs-note--warning {
  background: var(--gs-warning-wash);
  border-color: color-mix(in srgb, var(--gs-warning) 30%, transparent);
}

/* ── definition list: a label/value pair grid ─────────────────────────
   Two columns on desktop, stacked on narrow screens. Used for read-only
   detail panels (a store's contact block, an approval summary) where a
   table would be four lines of markup for two of content. */

.gs-kv {
  display: grid; grid-template-columns: minmax(90px, 34%) 1fr;
  gap: 8px 16px; margin: 0; font-size: var(--gs-fs-md);
}
.gs-kv dt { color: var(--gs-muted); font-weight: 600; }
.gs-kv dd { margin: 0; color: var(--gs-ink); overflow-wrap: anywhere; }

@media (max-width: 560px) {
  .gs-kv { grid-template-columns: 1fr; gap: 2px 0; }
  .gs-kv dd { margin-bottom: 10px; }
}

/* ── section heading inside a form ───────────────────────────────────
   Splits a long modal into readable groups without adding a nested card. */

.gs-formsection {
  font-size: var(--gs-fs-sm); font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: var(--gs-muted);
  margin: var(--gs-space-5) 0 var(--gs-space-3);
  padding-bottom: 8px; border-bottom: 1px solid var(--gs-line);
}
.gs-formsection:first-child { margin-top: 0; }

/* ── checkbox cards: a pickable list with descriptions ───────────────
   A bare checkbox list makes people guess what "Inspections" means. The card
   carries the explanation, and the whole card is the hit target rather than
   the 16px box. */

.gs-checkgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: 10px; list-style: none; margin: 0; padding: 0;
}
.gs-checkgrid li { list-style: none; }

.gs-checkcard {
  display: flex; align-items: flex-start; gap: 10px; cursor: pointer;
  padding: 12px 14px; border-radius: var(--gs-radius);
  background: var(--gs-surface-2); border: 1px solid var(--gs-line);
  transition: border-color var(--gs-dur) var(--gs-ease),
              background var(--gs-dur) var(--gs-ease);
}
.gs-checkcard:hover { border-color: var(--gs-line-strong); }
.gs-checkcard input {
  accent-color: var(--gs-accent); width: 16px; height: 16px;
  margin-top: 2px; flex: none;
}
/* :has() is supported in every browser this app targets; where it is not, the
   card simply does not tint and the checkbox still reads as checked. */
.gs-checkcard:has(input:checked) {
  border-color: var(--gs-accent); background: var(--gs-accent-wash);
}
.gs-checkcard:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--gs-accent-ring); }
.gs-checkcard__title { display: block; font-weight: 700; color: var(--gs-ink);
                       font-size: var(--gs-fs-md); }
.gs-checkcard__desc { display: block; margin-top: 3px;
                      font-size: var(--gs-fs-sm); color: var(--gs-muted);
                      line-height: 1.45; }

/* A feature whose prerequisite is not held yet. Dimmed rather than hidden:
   somebody scanning the list needs to see that reconciliation EXISTS and what
   it costs, or they cannot decide to buy the connector that unlocks it.

   Every value here is a token, so the light theme's redefinition carries it
   with no second rule. `not-allowed` on the whole card rather than on the
   input, because the label is what the pointer is actually over. */
.gs-checkcard.is-locked {
  opacity: .6; cursor: not-allowed;
  background: var(--gs-surface-3); border-style: dashed;
}
.gs-checkcard.is-locked:hover { border-color: var(--gs-line); }
.gs-checkcard.is-locked input { cursor: not-allowed; }
.gs-checkcard.is-locked .gs-checkcard__desc strong { color: var(--gs-ink-2); }
.gs-checkcard [data-gs-needs-note] { display: block; margin-top: 5px;
                                     color: var(--gs-warning); }
.gs-checkcard [data-gs-needs-note] i { margin-right: 3px; }

/* The WTForms ListWidget wraps each option in a <li>; the label inside is the
   card. Without this the bullets and default list padding show through. */
.gs-checkgrid > li > label { width: 100%; }

/* ── opening hours grid ──────────────────────────────────────────────
   Seven fixed rows. Grid rather than a table so it can collapse to two lines
   per day on a phone without the columns fighting each other. */

.gs-hours { display: flex; flex-direction: column; gap: 10px; }
.gs-hours__row {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: 110px 96px 1fr auto 1fr;
}
.gs-hours__day { font-weight: 700; color: var(--gs-ink);
                 font-size: var(--gs-fs-md); }
.gs-hours__closed { font-size: var(--gs-fs-sm); }
.gs-hours__to { color: var(--gs-muted); font-size: var(--gs-fs-sm); }
.gs-hours .form-control { margin: 0; }
.gs-hours .form-control:disabled { opacity: .6; cursor: not-allowed; }

@media (max-width: 640px) {
  .gs-hours__row { grid-template-columns: 1fr 1fr; row-gap: 6px; }
  .gs-hours__day { grid-column: 1 / -1; }
  .gs-hours__closed { grid-column: 1 / -1; }
  .gs-hours__to { display: none; }
}

/* ── completeness meter ──────────────────────────────────────────────
   The nudge that gets profiles finished. A bar that visibly moves does
   more than a checklist that sits still. */

.gs-meter {
  height: 6px; border-radius: var(--gs-radius-pill);
  background: var(--gs-surface-3); overflow: hidden; max-width: 180px;
}
.gs-meter--lg { height: 10px; max-width: none; }
.gs-meter__bar {
  height: 100%; border-radius: inherit; background: var(--gs-gradient);
  transition: width var(--gs-dur) var(--gs-ease);
}

.gs-checklist { list-style: none; margin: 12px 0 0; padding: 0;
                display: flex; flex-direction: column; gap: 7px; }
.gs-checklist li { display: flex; align-items: center; gap: 9px;
                   font-size: var(--gs-fs-sm); color: var(--gs-muted); }
.gs-checklist i { color: var(--gs-muted); font-size: 14px; flex: none; }

/* ── search-result preview ───────────────────────────────────────────
   Deliberately shaped like a Google result. People write far better meta
   copy when they can see it truncating in the shape it will appear. */

.gs-serp {
  background: var(--gs-surface-2); border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius); padding: 16px 18px; max-width: 620px;
}
.gs-serp__url { font-size: var(--gs-fs-xs); color: var(--gs-muted);
                margin-bottom: 4px; overflow-wrap: anywhere; }
.gs-serp__title {
  font-size: 1.125rem; color: var(--gs-accent-soft); font-weight: 600;
  line-height: 1.3; margin-bottom: 4px;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
  overflow: hidden;
}
.gs-serp__desc {
  font-size: var(--gs-fs-md); color: var(--gs-ink-2); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── tag lists and galleries (shared with the public page) ─────────── */

.gs-taglist { list-style: none; margin: 0; padding: 0;
              display: flex; flex-wrap: wrap; gap: 8px 18px; }
.gs-taglist li { display: flex; align-items: center; gap: 7px;
                 font-size: var(--gs-fs-md); color: var(--gs-ink-2); }
.gs-taglist i { color: var(--gs-accent); font-size: 15px; flex: none; }

.gs-gallery {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr));
}
.gs-gallery img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
                  border-radius: var(--gs-radius); display: block;
                  border: 1px solid var(--gs-line); }

.gs-openhours { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px;
                margin: 0; font-size: var(--gs-fs-md); }
.gs-openhours dt { color: var(--gs-muted); font-weight: 700; }
.gs-openhours dd { margin: 0; color: var(--gs-ink); }
.gs-openhours__closed { color: var(--gs-muted); }


/* ── popovers: the bell panel and the account menu ────────────────── */

.gs-pop { position: relative; }

.gs-pop__panel {
  position: absolute; right: 0; top: calc(100% + 10px);
  min-width: 250px; padding: 8px; z-index: 60;
  border-radius: var(--gs-radius-lg);
  animation: gs-pop-in var(--gs-dur) var(--gs-ease);
}
.gs-pop__panel--wide { width: min(380px, calc(100vw - 32px)); padding: 0; }

@keyframes gs-pop-in {
  from { opacity: 0; transform: translateY(-6px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

/* The shared glass material. Applied by class so a panel, the sidebar and a
   modal cannot drift apart. */
.gs-glass {
  background: var(--gs-glass-bg-2);
  -webkit-backdrop-filter: var(--gs-glass-blur);
  backdrop-filter: var(--gs-glass-blur);
  border: 1px solid var(--gs-glass-border);
  box-shadow: var(--gs-glass-shadow), inset 0 1px 0 var(--gs-glass-edge);
}

.gs-pop__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--gs-glass-border);
}
.gs-pop__title { font-weight: 800; font-size: var(--gs-fs-md); }
.gs-pop__action {
  background: none; border: 0; cursor: pointer; font-family: inherit;
  font-size: var(--gs-fs-sm); font-weight: 700; color: var(--gs-accent-soft);
}
.gs-pop__action:hover { color: var(--gs-accent); text-decoration: underline; }

.gs-pop__identity { padding: 12px 12px 14px; }
.gs-pop__name { font-weight: 800; }
.gs-pop__mail { font-size: var(--gs-fs-sm); color: var(--gs-muted);
                word-break: break-all; }
.gs-pop__sep { height: 1px; background: var(--gs-glass-border); margin: 6px 0; }

.gs-pop__foot {
  padding: 9px 16px; border-top: 1px solid var(--gs-glass-border);
  text-align: center;
}
.gs-notif__more { font-size: var(--gs-fs-xs); color: var(--gs-muted);
                  font-weight: 700; letter-spacing: .3px; }

.gs-menuitem {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border-radius: var(--gs-radius-sm);
  color: var(--gs-ink-2); font-size: var(--gs-fs-md); font-weight: 600;
  background: none; border: 0; cursor: pointer; font-family: inherit;
  text-align: left; text-decoration: none;
}
.gs-menuitem:hover { background: var(--gs-surface-2); color: var(--gs-ink); }
.gs-menuitem--danger { color: var(--gs-danger); }
.gs-menuitem--danger:hover { background: var(--gs-danger-wash);
                             color: var(--gs-danger); }

/* ── the bell ─────────────────────────────────────────────────────── */

.gs-bell { position: relative; }
.gs-bell__dot {
  position: absolute; top: 4px; right: 3px; min-width: 17px; height: 17px;
  padding: 0 4px; border-radius: 999px;
  background: var(--gs-danger); color: var(--gs-white);
  font-size: 10px; font-weight: 800; line-height: 17px; text-align: center;
  /* Ring in the bar's colour so the badge reads as sitting on top of the
     icon rather than merging into it. */
  box-shadow: 0 0 0 2px var(--gs-glass-bg-2);
}

/* ── the notification list ────────────────────────────────────────── */

/* Sized to show five and cut the sixth, so it is visibly scrollable without
   a scrollbar being the only clue. */
.gs-notif__list { max-height: 340px; overflow-y: auto; overscroll-behavior: contain; }

.gs-notif {
  display: flex; gap: 11px; padding: 12px 16px;
  border-bottom: 1px solid var(--gs-glass-border);
  text-decoration: none; color: inherit; cursor: pointer;
  transition: background var(--gs-dur) var(--gs-ease);
}
.gs-notif:last-child { border-bottom: 0; }
.gs-notif:hover { background: var(--gs-surface-2); }
.gs-notif.is-unread { background: var(--gs-accent-wash); }
.gs-notif.is-unread .gs-notif__title { font-weight: 800; }
.gs-notif.is-unread .gs-notif__title::after {
  content: ''; display: inline-block; width: 6px; height: 6px;
  border-radius: 50%; background: var(--gs-accent); margin-left: 7px;
  vertical-align: middle;
}

.gs-notif__icon {
  width: 32px; height: 32px; flex: none; border-radius: 10px;
  display: grid; place-items: center; font-size: 15px;
  background: var(--gs-info-wash); color: var(--gs-info);
}
.gs-notif__icon--success  { background: var(--gs-success-wash); color: var(--gs-success); }
.gs-notif__icon--warning  { background: var(--gs-warning-wash); color: var(--gs-warning); }
.gs-notif__icon--critical { background: var(--gs-danger-wash);  color: var(--gs-danger); }

.gs-notif__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.gs-notif__title { font-size: var(--gs-fs-md); font-weight: 700;
                   color: var(--gs-ink); }
.gs-notif__body {
  font-size: var(--gs-fs-sm); color: var(--gs-muted); line-height: 1.45;
  /* Two lines then ellipsis: a long body must not push the other four
     notifications out of view. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.gs-notif__ago { font-size: var(--gs-fs-xs); color: var(--gs-muted);
                 margin-top: 2px; }

.gs-notif__empty, .gs-notif__loading {
  padding: 34px 16px; text-align: center; color: var(--gs-muted);
  font-size: var(--gs-fs-sm);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.gs-notif__empty i { font-size: 26px; opacity: .5; }

@media (max-width: 575.98px) {
  .gs-pop__panel--wide {
    position: fixed; right: 8px; left: 8px; top: 76px; width: auto;
  }
}

/* backdrop-filter is the whole effect; without it the translucent fill just
   looks washed out. Fall back to an opaque surface instead. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  /* .gs-commandbar is deliberately NOT in this list: it carries no fill by
     design, so there is nothing to fall back to. Giving it one here would
     paint exactly the band the transparent bar exists to avoid. */
  .gs-glass, .gs-sidenav, .gs-modal {
    background: var(--gs-bg-elevated);
  }
}


/* =====================================================================
   The public store page
   ---------------------------------------------------------------------
   No sidebar, no command bar, no account menu - a visitor here has no
   account and never will. It uses the SAME tokens as the app, so it
   follows the viewer's light/dark preference like everything else; a
   marketing page that ignores the OS theme is the one page that flashes
   white at night.
   ===================================================================== */

.gs-public { background: var(--gs-bg); color: var(--gs-ink); }
.gs-public__wrap { min-height: 100dvh; display: flex; flex-direction: column; }

.gs-public__hero {
  position: relative;
  background: var(--gs-surface-2);
  background-size: cover; background-position: center;
  border-bottom: 1px solid var(--gs-line);
  padding: clamp(40px, 8vw, 88px) var(--gs-space-5) clamp(32px, 6vw, 64px);
}
/* When there IS a cover photo the hero carries a dark scrim (set inline),
   so its text is light in BOTH themes - otherwise dark-on-photo becomes
   unreadable the moment somebody uploads a dark image. */
.gs-public__hero[style*="url("] { color: var(--gs-white); }
.gs-public__hero[style*="url("] .gs-public__tagline,
.gs-public__hero[style*="url("] .gs-public__where { color: rgb(255 255 255 / .88); }

.gs-public__heroinner { max-width: 1100px; margin: 0 auto; }
.gs-public__logo {
  width: 76px; height: 76px; border-radius: var(--gs-radius-lg);
  object-fit: cover; background: var(--gs-surface);
  border: 1px solid var(--gs-glass-border); margin-bottom: 18px; display: block;
}
.gs-public__title {
  font-size: clamp(1.9rem, 5vw, 3rem); font-weight: 800; line-height: 1.08;
  margin: 0 0 10px; letter-spacing: -.02em;
}
.gs-public__tagline { font-size: var(--gs-fs-lg); color: var(--gs-ink-2);
                      margin: 0 0 12px; max-width: 60ch; }
.gs-public__where { font-size: var(--gs-fs-md); color: var(--gs-muted);
                    margin: 0 0 22px; display: flex; align-items: center;
                    gap: 7px; flex-wrap: wrap; }
.gs-public__cta { display: flex; flex-wrap: wrap; gap: 10px; }

.gs-public__main { flex: 1; width: 100%; max-width: 1100px;
                   margin: 0 auto; padding: var(--gs-space-6) var(--gs-space-5); }

.gs-public__trust {
  display: flex; flex-wrap: wrap; gap: 12px 26px;
  padding: 18px 20px; margin-bottom: var(--gs-space-6);
  background: var(--gs-surface); border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius-lg);
}
.gs-trustitem { display: flex; align-items: center; gap: 9px;
                font-size: var(--gs-fs-md); color: var(--gs-ink-2); }
.gs-trustitem i { color: var(--gs-success); font-size: 19px; flex: none; }

.gs-public__cols {
  display: grid; gap: var(--gs-space-6);
  grid-template-columns: minmax(0, 1.7fr) minmax(280px, 1fr);
  align-items: start;
}
@media (max-width: 900px) {
  .gs-public__cols { grid-template-columns: 1fr; }
}
.gs-public__col { display: flex; flex-direction: column;
                  gap: var(--gs-space-5); min-width: 0; }

.gs-public__section h2,
.gs-public__card h2 {
  font-size: var(--gs-fs-xl); font-weight: 800; margin: 0 0 14px;
  letter-spacing: -.01em;
}
.gs-public__subhead {
  font-size: var(--gs-fs-sm); font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: var(--gs-muted);
  margin: 20px 0 10px;
}
.gs-public__prose { font-size: var(--gs-fs-base); line-height: 1.7;
                    color: var(--gs-ink-2); margin: 0; white-space: pre-line;
                    max-width: 68ch; }

.gs-public__card {
  background: var(--gs-surface); border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius-lg); padding: 22px 24px;
  box-shadow: var(--gs-shadow-sm);
}
.gs-public__card h2 { font-size: var(--gs-fs-lg); }
.gs-public__card .gs-btn--block { margin-top: 16px; }

.gs-public__foot {
  border-top: 1px solid var(--gs-line); padding: 22px var(--gs-space-5);
  display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  font-size: var(--gs-fs-sm); color: var(--gs-muted);
  max-width: 1100px; margin: 0 auto; width: 100%;
}
.gs-public__by { opacity: .8; }

@media print {
  /* A shop page is printed and pinned to a noticeboard more often than
     anyone expects. Drop the photo and the buttons; keep the facts. */
  .gs-public__hero { background-image: none !important; color: var(--gs-ink); }
  .gs-public__cta, .gs-public__by { display: none; }
  .gs-public__cols { grid-template-columns: 1fr; }
}


/* ── split row: content left, actions right ──────────────────────────
   The shape every "read it, edit it in a modal" card uses. Collapses to
   stacked on narrow screens with the action moving BELOW the content, not
   above it - an Edit button before the thing it edits reads as a header. */

.gs-splitrow {
  display: flex; align-items: flex-start; gap: var(--gs-space-5);
}
.gs-splitrow__side {
  flex: none; text-align: right; display: flex; flex-direction: column;
  align-items: flex-end; gap: 10px;
}
.gs-sidenote { text-align: left; max-width: 280px; margin-top: 4px; }

@media (max-width: 720px) {
  .gs-splitrow { flex-direction: column; gap: var(--gs-space-4); }
  .gs-splitrow__side { text-align: left; align-items: flex-start;
                       width: 100%; }
  .gs-sidenote { max-width: none; }
}

.gs-lead {
  font-size: var(--gs-fs-lg); color: var(--gs-ink); font-weight: 600;
  margin: 0 0 10px; max-width: 60ch;
}

/* Full day names need more room than the three-letter public-page version. */
.gs-openhours--wide { grid-template-columns: 110px 1fr; }


/* ── tabs: filter a list without leaving the page ────────────────────
   Links, not buttons - each filter is a real URL, so it is bookmarkable,
   works with the back button, and needs no JavaScript. */

.gs-tabs {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  border-bottom: 1px solid var(--gs-line); padding-bottom: 0;
}
.gs-tab {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 14px; border-radius: var(--gs-radius) var(--gs-radius) 0 0;
  font-size: var(--gs-fs-md); font-weight: 700; color: var(--gs-muted);
  text-decoration: none; border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--gs-dur) var(--gs-ease),
              border-color var(--gs-dur) var(--gs-ease);
}
.gs-tab:hover { color: var(--gs-ink); }
.gs-tab.is-active { color: var(--gs-accent); border-bottom-color: var(--gs-accent); }
.gs-tab__count {
  display: inline-grid; place-items: center; min-width: 20px; height: 20px;
  padding: 0 6px; border-radius: var(--gs-radius-pill);
  background: var(--gs-surface-3); color: var(--gs-ink-2);
  font-size: var(--gs-fs-xs); font-weight: 800;
}
.gs-tab.is-active .gs-tab__count {
  background: var(--gs-accent-wash); color: var(--gs-accent);
}


/* ── feature cards: what you have and what you can ask for ───────────── */

.gs-featuregrid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
}
.gs-featurecard {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 16px 18px; border-radius: var(--gs-radius);
  background: var(--gs-surface-2); border: 1px solid var(--gs-line);
}
/* Held features are tinted rather than moved to a separate list: seeing what
   you have beside what you could have is what makes the comparison possible. */
.gs-featurecard.is-held {
  background: var(--gs-success-wash);
  border-color: color-mix(in srgb, var(--gs-success) 28%, transparent);
}
.gs-featurecard__icon {
  width: 38px; height: 38px; border-radius: var(--gs-radius-sm); flex: none;
  display: grid; place-items: center; font-size: 18px;
  background: var(--gs-surface-3); color: var(--gs-accent);
}
.gs-featurecard.is-held .gs-featurecard__icon {
  background: transparent; color: var(--gs-success);
}
.gs-featurecard__body { flex: 1; min-width: 0; }
.gs-featurecard__title { font-weight: 700; color: var(--gs-ink);
                         font-size: var(--gs-fs-md); }
.gs-featurecard__cat {
  margin-left: 8px; font-size: var(--gs-fs-xs); font-weight: 700;
  color: var(--gs-muted); text-transform: uppercase; letter-spacing: .04em;
}
.gs-featurecard__desc { margin: 4px 0 0; font-size: var(--gs-fs-sm);
                        color: var(--gs-muted); line-height: 1.5; }
.gs-featurecard__price { margin-top: 8px; font-size: var(--gs-fs-sm);
                         font-weight: 700; color: var(--gs-ink-2); }
.gs-featurecard__action { flex: none; }


/* ── picker cards: choose one of a few, with room to explain ──────────
   A radio group that reads as cards. Used where a <select> would hide the
   descriptions that are the whole reason somebody can tell the options
   apart - the signup system picker, above all. */

.gs-picker { border: 0; padding: 0; margin: 0 0 var(--gs-space-4);
             display: flex; flex-direction: column; gap: 10px; }
.gs-picker legend { padding: 0; margin-bottom: 8px; }

.gs-pickcard { display: block; cursor: pointer; }
/* The input is visually hidden but NOT display:none - a hidden-by-display
   radio is skipped by keyboard navigation and unreachable by a screen
   reader, which would make this control unusable without a mouse. */
.gs-pickcard input {
  position: absolute; opacity: 0; width: 1px; height: 1px;
  margin: -1px; overflow: hidden; clip: rect(0 0 0 0);
}
.gs-pickcard__body {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 16px; border-radius: var(--gs-radius);
  background: var(--gs-surface-2); border: 1px solid var(--gs-line);
  transition: border-color var(--gs-dur) var(--gs-ease),
              background var(--gs-dur) var(--gs-ease);
}
.gs-pickcard:hover .gs-pickcard__body { border-color: var(--gs-line-strong); }
.gs-pickcard input:checked + .gs-pickcard__body {
  border-color: var(--gs-accent); background: var(--gs-accent-wash);
}
.gs-pickcard input:focus-visible + .gs-pickcard__body {
  outline: 2px solid var(--gs-accent); outline-offset: 2px;
}
.gs-pickcard__icon {
  width: 40px; height: 40px; border-radius: var(--gs-radius-sm); flex: none;
  display: grid; place-items: center; font-size: 19px;
  background: var(--gs-surface-3); color: var(--gs-accent);
}
.gs-pickcard input:checked + .gs-pickcard__body .gs-pickcard__icon {
  background: var(--gs-accent); color: var(--gs-on-accent);
}
.gs-pickcard__title { display: block; font-weight: 700; color: var(--gs-ink);
                      font-size: var(--gs-fs-md); }
.gs-pickcard__desc { display: block; margin-top: 3px;
                     font-size: var(--gs-fs-sm); color: var(--gs-muted);
                     line-height: 1.45; }
.gs-pickcard__tick {
  margin-left: auto; flex: none; opacity: 0;
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; font-size: 13px;
  background: var(--gs-accent); color: var(--gs-on-accent);
  transition: opacity var(--gs-dur) var(--gs-ease);
}
.gs-pickcard input:checked + .gs-pickcard__body .gs-pickcard__tick {
  opacity: 1;
}
.gs-picker--invalid .gs-pickcard__body { border-color: var(--gs-danger); }

/* ── phone picker (intl-tel-input) ───────────────────────────────────
   The library ships light-only colours and its own border/background on the
   wrapper. Both are overridden here so the field is indistinguishable from
   every other .form-control, in both themes - the tokens do the work, so a
   palette change carries the picker with it.

   The flag IS a control now: it opens the country list, and picking a
   country rewrites the dial code in the field. It therefore has to look
   clickable, which is the opposite of what this block used to enforce. */

.iti { display: block; width: 100%; }

/* The library sets padding-left inline on the input to clear the flag. It is
   left to do that - hardcoding a value here breaks the moment the flag's width
   changes between versions. */
.iti .form-control { padding-right: 14px; }

/* Its wrapper otherwise paints a white box behind our transparent-cornered
   input, which shows as a pale rectangle under the field in dark mode. */
.iti__country-container { background: transparent; }

.iti__selected-country {
  background: transparent;
  border-radius: var(--gs-radius) 0 0 var(--gs-radius);
  padding-left: 12px;
}
/* It is a button. `pointer-events: none` here is what was swallowing every
   click on the flag, so the list could never open however the widget was
   configured. */
.iti__selected-country-primary { cursor: pointer; }
.iti__selected-country:hover,
.iti__selected-country-primary:hover { background: var(--gs-surface-3); }
.iti__arrow { border-top-color: var(--gs-muted); }
.iti__arrow--up { border-bottom-color: var(--gs-muted); }

/* The list itself. The library ships a light popup, which on a dark surface
   arrives as a white sheet - so its surfaces, text and hover state are
   re-pointed at the theme's tokens. */
.iti__dropdown-content {
  background: var(--gs-surface-2); color: var(--gs-ink);
  border: 1px solid var(--gs-line); border-radius: var(--gs-radius);
  box-shadow: var(--gs-shadow-md);
}
.iti__search-input {
  background: var(--gs-surface); color: var(--gs-ink);
  border: 0; border-bottom: 1px solid var(--gs-line);
  padding: 10px 14px; font-family: inherit; font-size: var(--gs-fs-md);
}
.iti__search-input::placeholder { color: var(--gs-muted); }
.iti__country { color: var(--gs-ink); padding: 8px 14px; }
.iti__country-name { color: var(--gs-ink); }
.iti__dial-code { color: var(--gs-muted); }
.iti__country.iti__highlight,
.iti__country:hover { background: var(--gs-surface-3); }
.iti__country-list { scrollbar-width: thin; }
/* The divider the library draws under the preferred countries. */
.iti__standard-country:first-of-type { border-top: 1px solid var(--gs-line); }

/* The dial code sits next to the flag and must read as app text, not as the
   library's near-black default, which is invisible on a dark surface. */
.iti__selected-dial-code { color: var(--gs-muted); font-size: var(--gs-fs-md); }

/* The focus ring belongs to the input, and the input is inside the wrapper -
   without this the ring is clipped along the left edge where the flag sits. */
.iti--allow-dropdown .form-control:focus { z-index: 1; position: relative; }

/* Invalid state, matching .gs-field--invalid above. */
.gs-field--invalid .iti .form-control { border-color: var(--gs-danger); }


/* =====================================================================
   Dashboard blocks
   The depth treatment applied to the six pieces the dashboard composes.
   Each has a macro in templates/components/layout/, one per file, and
   every colour below is a token - nothing here names a hex.

   Responsiveness: the two-column grids collapse at the breakpoint the rest
   of the app uses, and the identity block stacks rather than letting its
   actions fall off the right edge.
   ===================================================================== */

/* ── KPI tile ─────────────────────────────────────────────────────
   A stat with a gradient chip and room for a state note underneath. The
   note is where an unbuilt metric says so, rather than a bare em-dash
   sitting in the primary grid looking like a zero. */
.gs-kpi {
  position: relative; overflow: hidden;
  padding: var(--gs-space-4) var(--gs-space-5);
  background: var(--gs-depth-card), var(--gs-surface);
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius-lg);
  box-shadow: var(--gs-depth-shadow-sm), var(--gs-depth-edge);
  display: flex; align-items: flex-start; gap: var(--gs-space-4);
}
/* One bloom per tile, behind the chip, clipped by the tile. This is the
   only glow in the theme; a second one elsewhere makes both look cheap. */
.gs-kpi::before {
  content: ''; position: absolute; width: 150px; height: 150px;
  top: -70px; left: -50px; background: var(--gs-bloom);
  pointer-events: none;
}
.gs-kpi__chip {
  position: relative; width: 38px; height: 38px; flex: none;
  border-radius: var(--gs-radius-sm); display: grid; place-items: center;
  font-size: 17px; background: var(--gs-chip-grad);
  color: var(--gs-on-accent);
  box-shadow: var(--gs-chip-glow), var(--gs-depth-edge-2);
}
.gs-kpi__chip--muted {
  background: var(--gs-surface-3); color: var(--gs-muted);
  box-shadow: var(--gs-depth-edge);
}
.gs-kpi__chip--info {
  background: var(--gs-info-wash); color: var(--gs-info);
  box-shadow: var(--gs-depth-edge);
}
.gs-kpi__chip--warning {
  background: var(--gs-warning-wash); color: var(--gs-warning);
  box-shadow: var(--gs-depth-edge);
}
.gs-kpi__body { position: relative; min-width: 0; }
/* Label ABOVE the figure. Four tiles then read as a row of numbers sitting
   on a common baseline; with the figure first, four different label lengths
   push the numbers to four different heights. */
.gs-kpi__label {
  font-size: var(--gs-fs-xs); color: var(--gs-muted); font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gs-kpi__value {
  font-size: var(--gs-fs-xl); font-weight: 800; line-height: 1;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  margin-top: 5px;
}
.gs-kpi__value--empty { color: var(--gs-muted); }
.gs-kpi__note {
  display: inline-block; margin-top: 8px;
  font-family: var(--gs-font-mono); font-size: 10px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--gs-muted);
  border: 1px dashed var(--gs-line-strong);
  border-radius: var(--gs-radius-sm); padding: 2px 7px;
}

/* ── identity card ────────────────────────────────────────────────
   Who you are and which system you are on. Same content it always had -
   avatar, greeting, facts, actions - given the reference card's TREATMENT:
   a teal ground lit from the upper left, darkening to almost black on the
   right, with a two-disc mark in the corner.

   It keeps its own ground in BOTH themes. Everything else on the page is a
   panel that follows the theme; this is an object sitting on the page, and
   an object does not change colour when the room does. That is also what
   lets it read as the first thing on the screen worth looking at.

   The reference's five blurred <span>s are background LAYERS here: a radial
   gradient is already soft, so the same picture costs one paint instead of
   five composited filter layers. */
.gs-identity {
  position: relative; overflow: hidden;
  /* A COLUMN, not a row: this card sits in a ~340px grid track, where a
     horizontal avatar-then-text layout wraps the name onto three lines. */
  display: flex; flex-direction: column; align-items: flex-start;
  height: 100%; min-height: 172px; padding: var(--gs-space-5);
  border-radius: var(--gs-radius-lg);
  /* NO rim, and the reason is the artwork layer below rather than the rim's
     own colour. `overflow: hidden` clips a child to the PADDING box, so a
     1px border holds the ::before pools 1px short of the card's edge. In
     that ring the base gradient is left showing on its own, without the
     light pool that is sitting on top of it everywhere else - a hard 1px
     line, darkest down the right side, which is exactly where the pool is
     brightest and so has the most to subtract.

     It looked like a border and it was drawn where a border goes, which is
     why changing the border colour never touched it: the line is the gap
     the border opens in the artwork, not the border itself. Removing the
     border closes the gap, and the ground reaches the edge.

     (`background-clip: padding-box` used to sit here for the same rim. It
     never applied - the `background` shorthand two lines down resets
     background-clip - and with no border it would mean nothing anyway.) */
  border: 0;
  color: var(--gs-idcard-ink);
  /* The card itself carries only the clean diagonal and its sheen. The soft
     stuff goes on the blurred layer below. */
  background: var(--gs-idcard-sheen), var(--gs-idcard-bg);
  box-shadow: var(--gs-depth-shadow), var(--gs-depth-edge-2);
}
/* The artwork, on its own BLURRED layer. This is the thing plain gradients
   could not fake: the reference's pools are blurred shapes, and a blur is
   what gives them the soft organic edge that reads as mist rather than as a
   gradient. Inset past the card's edges so the blur's own falloff never
   reaches a corner, and clipped by the card's `overflow: hidden`.

   The scrim rides along on this layer as its topmost stop. Blurring a
   vertical gradient changes nothing about it, and putting it here is what
   keeps it in FRONT of the pools - where the text needs it. */
.gs-identity::before {
  /* PIXELS, not a percentage. -12% sounds generous until you notice this
     card is about 230px tall, where it comes to 28px - and the blur below
     is 26px, so the layer's own falloff lands exactly on the card's edges.
     The light pool thins out at the rim, the darker base gradient shows
     through, and the result is a dark band round the edge that looks for
     all the world like a border. It is not: it is this layer running out.

     A fixed inset comfortably larger than the blur keeps the falloff
     outside the card at every card size. */
  content: ''; position: absolute; inset: -72px;
  background: var(--gs-idcard-scrim), var(--gs-idcard-blob),
              var(--gs-idcard-shade);
  filter: blur(26px);
  pointer-events: none;
  /* The layer clips itself to the card's own curve. Without this it is a
     BLURRED surface being cut by the card's `overflow: hidden` at a rounded
     corner, and that combination leaves a faint dark seam tracing the
     radius - a hairline that looks like a border and cannot be fixed by
     changing the border, because it is not one. */
  border-radius: inherit;
}

/* The two overlapping discs the reference card carries, top right. Drawn
   with a box-shadow on one pseudo-element: decorative, so the template
   never has to pass a node for it, and assistive tech never sees it. */
.gs-identity::after {
  content: ''; position: absolute;
  top: var(--gs-space-5); right: calc(var(--gs-space-5) + 12px);
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--gs-idcard-mark);
  box-shadow: 12px 0 0 var(--gs-idcard-mark-2);
  pointer-events: none;
}
.gs-identity__avatar {
  position: relative; width: 46px; height: 46px; flex: none;
  margin-bottom: var(--gs-space-4);
  border-radius: var(--gs-radius); display: grid; place-items: center;
  background: var(--gs-idcard-chip);
  border: 1px solid var(--gs-idcard-chip-line);
  color: var(--gs-idcard-ink);
  font-size: var(--gs-fs-md); font-weight: 800;
  box-shadow: var(--gs-depth-edge-2);
}
.gs-identity__body { position: relative; width: 100%; min-width: 0; }
.gs-identity__eyebrow {
  font-size: var(--gs-fs-xs); color: var(--gs-idcard-cap); margin-bottom: 2px;
}
.gs-identity__title {
  /* `color: inherit` is load-bearing: the base `h1` rule sets --gs-ink,
     which beats inheritance from the card. In light mode that is near-black
     on this card's dark half, so the name vanished. */
  margin: 0; color: inherit; font-size: var(--gs-fs-xl); font-weight: 800;
  letter-spacing: -.02em; line-height: 1.15;
}
/* One line, dot-separated, no icons: icons force a wrap in a narrow track
   and turn three facts into three rows. */
.gs-identity__meta {
  margin-top: 6px; font-size: var(--gs-fs-sm); color: var(--gs-idcard-cap);
  line-height: 1.5; overflow-wrap: break-word;
}
.gs-identity__dot { margin: 0 6px; opacity: .55; }
.gs-identity__actions {
  position: relative; display: flex; gap: var(--gs-space-2);
  margin-top: auto; padding-top: var(--gs-space-5); width: 100%;
}
/* The primary verb takes the slack so the pair fills the card's width
   rather than huddling at its left edge. */
.gs-identity__cta { flex: 1; justify-content: center; }
/* The page's ghost button is a hairline against a dark panel; on a coloured
   card it disappears, so it becomes glass cut into the card instead. */
.gs-identity__actions .gs-btn--ghost {
  background: var(--gs-idcard-chip); color: var(--gs-idcard-ink);
  border-color: var(--gs-idcard-chip-line);
}
.gs-identity__actions .gs-btn--ghost:hover {
  background: var(--gs-idcard-chip-line); color: var(--gs-idcard-ink);
}

/* ── meter ────────────────────────────────────────────────────────
   One bar split by kind, with the legend carrying the numbers. Not a
   chart: no axis, no second scale, labels attached directly. */
.gs-meter__bar {
  display: flex; gap: 2px; height: 8px;
  border-radius: var(--gs-radius-pill); overflow: hidden;
  background: var(--gs-surface-3);
}
.gs-meter__seg { height: 100%; }
.gs-meter__seg--accent { background: var(--gs-accent); }
.gs-meter__seg--soft   { background: var(--gs-accent-2); }
.gs-meter__seg--muted  { background: var(--gs-line-strong); }
.gs-meter__legend {
  display: flex; flex-wrap: wrap;
  gap: var(--gs-space-2) var(--gs-space-5); margin-top: var(--gs-space-4);
}
.gs-meter__item {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--gs-fs-sm); color: var(--gs-ink-2);
}
.gs-meter__key { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.gs-meter__key--accent { background: var(--gs-accent); }
.gs-meter__key--soft   { background: var(--gs-accent-2); }
.gs-meter__key--muted  { background: var(--gs-line-strong); }
.gs-meter__n {
  font-family: var(--gs-font-mono); color: var(--gs-ink);
  font-size: var(--gs-fs-sm);
}

/* ── task row ─────────────────────────────────────────────────────
   One thing to do, its reason, one verb. A finished row keeps its place
   and goes quiet rather than vanishing, so the list does not jump. */
.gs-task {
  display: flex; align-items: center; gap: var(--gs-space-3);
  padding: var(--gs-space-3); border-radius: var(--gs-radius);
  /* A well, not a card: these rows sit INSIDE a panel, and giving them the
     card treatment turns one panel into a stack of four. */
  background: var(--gs-well); border: 1px solid var(--gs-well-line);
  box-shadow: var(--gs-depth-edge);
}
.gs-task + .gs-task { margin-top: 10px; }
.gs-task__icon {
  width: 30px; height: 30px; flex: none;
  border-radius: var(--gs-radius-sm); display: grid; place-items: center;
  font-size: 15px; background: var(--gs-accent-wash); color: var(--gs-accent);
  border: 1px solid var(--gs-accent-ring);
}
.gs-task__body { flex: 1; min-width: 0; }
.gs-task__title { font-size: var(--gs-fs-md); font-weight: 700; }
.gs-task__hint {
  font-size: var(--gs-fs-xs); color: var(--gs-muted); margin-top: 2px;
}
.gs-task--done { background: transparent; box-shadow: none; }
.gs-task--done .gs-task__icon {
  background: var(--gs-success-wash); color: var(--gs-success);
  border-color: transparent;
}
.gs-task--done .gs-task__title {
  color: var(--gs-muted); text-decoration: line-through;
}

/* ── module row ───────────────────────────────────────────────────
   A module you hold, and whether it is a screen you can open or an action
   permission that only unlocks buttons. That distinction lives in the
   registry and had never been shown to a customer. */
.gs-module {
  display: flex; align-items: center; gap: var(--gs-space-3);
  padding: 10px var(--gs-space-3); border-radius: var(--gs-radius-sm);
  color: var(--gs-ink-2); text-decoration: none;
  transition: background var(--gs-dur) var(--gs-ease);
}
.gs-module + .gs-module { margin-top: 2px; }
a.gs-module:hover { background: var(--gs-surface-2); color: var(--gs-ink); }
.gs-module__icon {
  width: 26px; text-align: center; color: var(--gs-accent);
  font-size: 16px; flex: none;
}
.gs-module__name {
  flex: 1; min-width: 0; font-size: var(--gs-fs-md); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── note panel ───────────────────────────────────────────────────
   The one explanatory block on the page. Reads as part of the product
   rather than as documentation bolted onto it. */
.gs-note {
  display: flex; align-items: center; gap: var(--gs-space-4);
  padding: var(--gs-space-4) var(--gs-space-5);
  background: var(--gs-depth-card), var(--gs-surface);
  border: 1px solid var(--gs-line); border-radius: var(--gs-radius-lg);
  box-shadow: var(--gs-depth-shadow-sm), var(--gs-depth-edge);
}
.gs-note__icon {
  width: 38px; height: 38px; flex: none;
  border-radius: var(--gs-radius-sm); display: grid; place-items: center;
  font-size: 19px; background: var(--gs-accent-wash); color: var(--gs-accent);
}
.gs-note__body { flex: 1; min-width: 0; }
.gs-note__title { font-size: var(--gs-fs-md); font-weight: 700; }
.gs-note__text {
  font-size: var(--gs-fs-sm); color: var(--gs-muted); margin-top: 2px;
}

/* ── dashboard grid ───────────────────────────────────────────────
   Two columns, the left one carrying the heavier block. auto-fit would
   reflow at an arbitrary width; this collapses at the app's breakpoint. */
@media (max-width: 767.98px) {
  /* Released from the grid row's height, and the two actions share the
     width evenly rather than the primary swallowing it. */
  .gs-identity { height: auto; min-height: 0; }
  .gs-identity__actions .gs-btn { flex: 1; justify-content: center; }
  .gs-kpi { padding: var(--gs-space-4); }
  .gs-task { align-items: flex-start; }
  .gs-task__icon { margin-top: 2px; }
}

/* Sidebar group label - the static Account block in the footer. Mono and
   small, matching the section labels in the dashboard blocks. */
.gs-sidenav__grouplabel {
  display: block; padding: 2px 12px 8px;
  font-family: var(--gs-font-mono); font-size: 9px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--gs-muted);
}


/* =====================================================================
   Depth - the shell and the remaining dashboard blocks
   Built to the reference layout: 4-up figures, then a 3-up row (identity
   and two gauges), then two 2-up rows, then the footer strip.
   ===================================================================== */

/* ── the lit mesh everything floats on ────────────────────────────
   Fixed, behind the content, one paint. Without it the panels' gradients
   have nothing to be lighter *than* and the depth reads as noise. */
.gs-shell::before {
  content: ''; position: fixed; inset: 0; z-index: -1;
  background: var(--gs-mesh); pointer-events: none;
}

/* ── KPI tile: figure left, chip right ───────────────────────────
   The reference puts the chip on the trailing edge, which lets the four
   values line up as a row of numbers rather than a row of icons. */
.gs-kpi { justify-content: space-between; align-items: center; }
.gs-kpi__body { flex: 1; }

/* ── gauge ────────────────────────────────────────────────────────
   A ring, a number in the middle, a legend under it. The ring is a conic
   gradient masked to an annulus: no SVG, no library, and the segment
   angles arrive as inline custom properties from the template. */
.gs-gauge { display: flex; flex-direction: column;
            gap: var(--gs-space-4); padding: var(--gs-space-3) 0 0; }
/* The ring is centred in the card; the legend below it is not. Centring
   both leaves a stack of three centred things and no edge for the eye to
   read down, which is why the reference pins the legend left. */
.gs-gauge__wrap {
  position: relative; align-self: center;
  width: 128px; height: 128px; flex: none;
}
/* The single glow the ring is allowed, and it sits BEHIND the ring rather
   than on it: a drop-shadow on a masked element traces the annulus and
   reads as a halo, not as light spilling off a lit dial. */
.gs-gauge__wrap::before {
  content: ''; position: absolute; inset: -14px; border-radius: 50%;
  background: var(--gs-bloom); pointer-events: none;
}
.gs-gauge__ring {
  position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(
      var(--gs-accent) 0 var(--gs-gauge-a, 0%),
      var(--gs-accent-2) var(--gs-gauge-a, 0%) var(--gs-gauge-b, 0%),
      var(--gs-surface-3) var(--gs-gauge-b, 0%) 100%);
  box-shadow: var(--gs-ring-glow);
  -webkit-mask: radial-gradient(farthest-side, transparent 76%,
                                var(--gs-mask-solid) 77%);
          mask: radial-gradient(farthest-side, transparent 76%,
                                var(--gs-mask-solid) 77%);
}
.gs-gauge__value {
  font-size: var(--gs-fs-xl); font-weight: 800; line-height: 1;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.gs-gauge__cap {
  font-family: var(--gs-font-mono); font-size: 9px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--gs-muted); margin-top: 4px;
}
/* The dial's face: a well sunk into the card, not a hole punched through
   it. The number lives here rather than inside the ring, which is masked
   and would clip it. */
.gs-gauge__center {
  position: absolute; inset: 15px; border-radius: 50%;
  display: grid; place-items: center; text-align: center;
  background: var(--gs-well); box-shadow: var(--gs-well-shadow);
  pointer-events: none;
}
/* Tiny, quiet, and on the card's left edge - a key, not a second chart. */
.gs-gauge .gs-meter__legend {
  margin-top: 0; gap: var(--gs-space-2) var(--gs-space-4);
}
.gs-gauge .gs-meter__item { font-size: var(--gs-fs-xs);
                            color: var(--gs-muted); }
.gs-gauge .gs-meter__key { width: 7px; height: 7px; border-radius: 2px; }
.gs-gauge .gs-meter__n { font-size: var(--gs-fs-xs); }

/* ── activity frame ───────────────────────────────────────────────
   The shape a time series will take, holding its own empty state. A
   dashed baseline says "an axis will be here", which is honest; a flat
   line at zero would say "your numbers are zero", which is not. */
.gs-activity {
  position: relative; height: 168px; border-radius: var(--gs-radius);
  background: var(--gs-surface-2); border: 1px solid var(--gs-line);
  display: grid; place-items: center; overflow: hidden;
}
.gs-activity::after {
  content: ''; position: absolute; left: 18px; right: 18px; bottom: 34px;
  border-bottom: 1px dashed var(--gs-line-strong);
}
.gs-activity__msg {
  position: relative; text-align: center; max-width: 42ch;
  padding: 0 var(--gs-space-5);
}
.gs-activity__title { font-size: var(--gs-fs-md); font-weight: 700; }
.gs-activity__text {
  font-size: var(--gs-fs-sm); color: var(--gs-muted); margin-top: 4px;
}

/* ── role list ────────────────────────────────────────────────────
   One row per role held, with the rank badge. Coverage per role is a
   platform-staff decision and is not on this page for that reason. */
.gs-rolerow {
  display: flex; align-items: center; gap: var(--gs-space-3);
  padding: 11px var(--gs-space-3); border-radius: var(--gs-radius-sm);
}
.gs-rolerow + .gs-rolerow { margin-top: 2px; }
.gs-rolerow__mark {
  width: 30px; height: 30px; flex: none; border-radius: var(--gs-radius-sm);
  display: grid; place-items: center; background: var(--gs-accent-wash);
  color: var(--gs-accent); font-size: var(--gs-fs-xs); font-weight: 800;
}
.gs-rolerow__name {
  flex: 1; min-width: 0; font-size: var(--gs-fs-md); font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── footer strip ─────────────────────────────────────────────────
   Closes the column. Quiet, and the only place on the page allowed to
   carry links that leave it. */
.gs-dashfoot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gs-space-4); flex-wrap: wrap;
  margin-top: var(--gs-space-5); padding-top: var(--gs-space-4);
  border-top: 1px solid var(--gs-line);
  font-size: var(--gs-fs-sm); color: var(--gs-muted);
}
.gs-dashfoot__links { display: flex; gap: var(--gs-space-5); flex-wrap: wrap; }
.gs-dashfoot__links a { color: var(--gs-muted); text-decoration: none; }
.gs-dashfoot__links a:hover { color: var(--gs-accent); }

/* ── the live panel ───────────────────────────────────────────────
   The one block on a screen holding things that are waiting on you. Blue
   where everything else is cyan, lit from its own edge, so "there is work
   here" cannot be read as "here is another number". Exactly one per screen
   - a second one makes both of them ordinary. */
.gs-card--live {
  background: var(--gs-live-card), var(--gs-surface);
  border-color: var(--gs-live-line);
  box-shadow: var(--gs-depth-shadow), var(--gs-live-edge),
              var(--gs-live-glow);
}
.gs-card--live .gs-card__head { border-bottom-color: var(--gs-live-line); }

/* The counterpart to --live, and the reason it exists: reconciliation needs a
   card that is wrong rather than a card that is happening. A vendor whose
   sign-in failed, or a run that produced no numbers. Deliberately quieter
   than --live — it is a state to read, not a thing to watch. */
.gs-card--danger {
  border-color: color-mix(in srgb, var(--gs-danger) 30%, var(--gs-line));
}
.gs-card--danger .gs-card__mark {
  background: var(--gs-danger-wash); color: var(--gs-danger);
  box-shadow: var(--gs-depth-edge);
}
.gs-card--danger .gs-card__count {
  background: var(--gs-danger-wash); color: var(--gs-danger);
}

/* The head's left group: mark, titles, count - one flex row so the count
   sits against the title rather than drifting to the far edge. */
.gs-card__lead {
  display: flex; align-items: center; gap: var(--gs-space-3); min-width: 0;
}
.gs-card__mark {
  width: 28px; height: 28px; flex: none; border-radius: 9px;
  display: grid; place-items: center; font-size: 14px;
  background: var(--gs-chip-grad); color: var(--gs-on-accent);
  box-shadow: var(--gs-chip-glow), var(--gs-depth-edge-2);
}
.gs-card--live .gs-card__mark {
  background: var(--gs-live-grad); color: var(--gs-on-live);
  box-shadow: var(--gs-live-chip-glow), var(--gs-live-edge);
}
.gs-card__count {
  flex: none; font-size: var(--gs-fs-xs); font-weight: 800;
  padding: 3px 9px; border-radius: var(--gs-radius-pill);
  background: var(--gs-accent-wash); color: var(--gs-accent);
}
.gs-card--live .gs-card__count {
  background: var(--gs-live-wash); color: var(--gs-live-ink);
}
/* Inside the live panel an unfinished row picks up the panel's blue; a
   finished one stays green, because "done" means the same thing on every
   screen and must not change colour with its container. */
.gs-card--live .gs-task:not(.gs-task--done) .gs-task__icon {
  background: var(--gs-live-wash); color: var(--gs-live-ink);
  border-color: var(--gs-live-line);
}

/* ── sidebar docs card ────────────────────────────────────────────
   The reference's promo block, in the one place it belongs: pinned to the
   bottom of the nav, not competing with the page. */
.gs-sidedoc {
  position: relative; overflow: hidden;
  margin: var(--gs-space-4) 12px 0; padding: var(--gs-space-4);
  border-radius: var(--gs-radius);
  background: var(--gs-depth-card-2), var(--gs-surface-2);
  border: 1px solid var(--gs-line);
  box-shadow: var(--gs-depth-edge);
}
.gs-sidedoc::before {
  content: ''; position: absolute; width: 190px; height: 150px;
  top: -90px; right: -60px; background: var(--gs-bloom); pointer-events: none;
}
.gs-sidedoc__icon {
  position: relative; width: 30px; height: 30px; border-radius: var(--gs-radius-sm);
  display: grid; place-items: center; background: var(--gs-chip-grad);
  color: var(--gs-on-accent); font-size: 16px;
  box-shadow: var(--gs-chip-glow);
}
.gs-sidedoc__title {
  position: relative; font-size: var(--gs-fs-sm); font-weight: 800;
  margin-top: 10px;
}
.gs-sidedoc__text {
  position: relative; font-size: var(--gs-fs-xs); color: var(--gs-muted);
  line-height: 1.55; margin-top: 3px;
}

/* ── the reference's grid rhythm ──────────────────────────────────
   1.05fr 1fr 1fr for the identity row, 1.42fr 1fr for the two below it.
   Explicit fractions rather than auto-fit, so the columns keep their
   relationship instead of reflowing at an arbitrary width. */
/* The dashboard's top row: identity, then two columns of two tiles. The
   identity card is the widest track because it is the only one carrying a
   sentence; the tile columns only ever hold a number and a caption. */
.gs-dash-hero { display: grid; gap: var(--gs-space-4); align-items: stretch;
                grid-template-columns: 1.45fr 1fr 1fr;
                margin-bottom: var(--gs-space-4); }
.gs-dash-hero > .gs-identity { margin: 0; }
/* A column of tiles that splits the row's height between them, so the two
   stacks and the identity card all end on the same line. */
.gs-dash-stack { display: flex; flex-direction: column; gap: var(--gs-space-4); }
.gs-dash-stack > * { flex: 1; }

/* Row two: two equal cards. `gs-dash-2` is 1.42fr 1fr and is still used by
   other pages, so this is its own class rather than a redefinition. */
.gs-dash-even { display: grid; gap: var(--gs-space-4); align-items: start;
                grid-template-columns: 1fr 1fr; }
.gs-dash-even > .gs-card { margin-bottom: 0; }

@media (max-width: 1199.98px) {
  .gs-dash-hero { grid-template-columns: 1fr 1fr; }
  .gs-dash-hero > .gs-identity { grid-column: 1 / -1; }
}
@media (max-width: 767.98px) {
  .gs-dash-hero { grid-template-columns: 1fr; }
  .gs-dash-hero > .gs-identity { grid-column: auto; }
  .gs-dash-even { grid-template-columns: 1fr; }
}
@media (max-width: 991.98px) {
  .gs-dash-even { grid-template-columns: 1fr; }
}

/* ── metric tile ──────────────────────────────────────────────────
   Chip first, then LABEL / figure / caption. Same panel treatment as a
   card - it is a card, just one holding a single number. */
.gs-metric {
  display: flex; align-items: center; gap: 15px;
  padding: 18px 20px;
  background: var(--gs-depth-card), var(--gs-surface);
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius-lg);
  box-shadow: var(--gs-depth-shadow-sm), var(--gs-depth-edge);
}
.gs-metric__chip {
  width: 42px; height: 42px; flex: none; border-radius: 13px;
  display: grid; place-items: center; font-size: 19px;
  background: var(--gs-chip-grad); color: var(--gs-on-accent);
  box-shadow: var(--gs-chip-glow), var(--gs-depth-edge-2);
}
.gs-metric__body { min-width: 0; flex: 1; }
.gs-metric__label {
  font-size: var(--gs-fs-xs); font-weight: 800; letter-spacing: 1.1px;
  text-transform: uppercase; color: var(--gs-muted);
}
.gs-metric__value {
  font-size: 1.5rem; font-weight: 800; letter-spacing: -1px; line-height: 1;
  margin-top: 5px; font-variant-numeric: tabular-nums;
}
.gs-metric__suffix { font-size: var(--gs-fs-base); font-weight: 700;
                     color: var(--gs-muted); letter-spacing: 0; }
.gs-metric__caption { font-size: var(--gs-fs-xs); color: var(--gs-muted);
                      margin-top: 5px; }
.gs-metric__track { margin-top: 9px; height: 5px; border-radius: 3px;
                    background: var(--gs-surface-3); overflow: hidden; }
.gs-metric__fill { display: block; height: 100%; border-radius: 3px;
                   background: var(--gs-gradient); }

/* The one tile allowed to say "this is waiting on you". Blue where the rest
   is cyan, same rule and same tokens as .gs-card--live - and if this tile
   is live, nothing else on the screen may be. */
.gs-metric--live {
  background: var(--gs-live-card), var(--gs-surface);
  border-color: var(--gs-live-line);
  box-shadow: var(--gs-depth-shadow-sm), var(--gs-live-edge);
}
.gs-metric--live .gs-metric__chip {
  background: var(--gs-live-grad); color: var(--gs-on-live);
  box-shadow: var(--gs-live-chip-glow), var(--gs-live-edge);
}
.gs-metric--live .gs-metric__fill { background: var(--gs-live-grad); }

.gs-dash-3 { display: grid; gap: var(--gs-space-4); align-items: stretch;
             grid-template-columns: 1.05fr 1fr 1fr; }
.gs-dash-2 { display: grid; gap: var(--gs-space-4); align-items: start;
             grid-template-columns: 1.42fr 1fr; }
.gs-dash-3 > .gs-card { margin-bottom: 0; height: 100%; }
/* NOT height:100% - the left card stretching to match a taller right card is
   what turned the activity panel into a mostly-empty box. */
.gs-dash-2 > .gs-card { margin-bottom: 0; }

@media (max-width: 1199.98px) {
  .gs-dash-3 { grid-template-columns: 1fr 1fr; }
  .gs-dash-3 > *:first-child { grid-column: 1 / -1; }
}
@media (max-width: 991.98px) {
  .gs-dash-2 { grid-template-columns: 1fr; }
}
@media (max-width: 767.98px) {
  .gs-dash-3 { grid-template-columns: 1fr; }
  .gs-dash-3 > *:first-child { grid-column: auto; }
  .gs-activity { height: 140px; }
  .gs-dashfoot { flex-direction: column; align-items: flex-start; gap: 10px; }
}

/* =====================================================================
   Reconciliation — the components this feature ADDS to the kit.
   Ported verbatim from gearshift-recon-mock, which was built against this
   very tokens.css, so nothing here needed re-deriving. Namespaced rc-* so
   a grep says instantly what is new surface area and what is the existing
   kit: everything else on those screens is gs-card, gs-table, gs-badge.

   Every value is a token. The mock's two `animation: none` overrides are
   dropped — they were there because the artifact preview host does not
   animate — and the keyframes plus a reduced-motion block are restored at
   the end of this section.
   ===================================================================== */

/* ── the run banner ──────────────────────────────────────────────
   The single most important object on the tenant screen. It answers the
   only question a scheduled job raises: did it run, and when does it run
   again. A WELL rather than a card — sunk into the page, not another
   panel competing with the figures beside it. */
.rc-runbanner { display: flex; align-items: center; gap: var(--gs-space-4);
  flex-wrap: wrap; padding: 14px var(--gs-space-5);
  border-radius: var(--gs-radius); background: var(--gs-well);
  border: 1px solid var(--gs-well-line); box-shadow: var(--gs-well-shadow);
  margin-bottom: var(--gs-space-5); }
/* A plain status dot. It was a heartbeat — a glowing pip with a ring pinging
   out of it every 2.4s — and the pulse was doing the opposite of its job:
   it animates identically whether the last run was an hour or a fortnight
   ago, so it reads as "live" on a screen that is not. The colour still
   carries the state; the line beside it carries the time. */
.rc-runbanner__pulse { position: relative; width: 10px; height: 10px; flex: none;
  border-radius: 50%; background: var(--gs-success); }
.rc-runbanner--warn .rc-runbanner__pulse { background: var(--gs-warning); }
.rc-runbanner--bad .rc-runbanner__pulse { background: var(--gs-danger); }
.rc-runbanner--idle .rc-runbanner__pulse { background: var(--gs-muted); }
.rc-runbanner__main { flex: 1 1 340px; min-width: 0; }
.rc-runbanner__line { font-size: var(--gs-fs-md); font-weight: 700; }
.rc-runbanner__meta { font-size: var(--gs-fs-xs); color: var(--gs-muted);
  margin-top: 2px; }
.rc-runbanner__next { display: flex; align-items: center; gap: 10px;
  padding: 8px 14px; border-radius: var(--gs-radius-sm);
  background: var(--gs-accent-wash); border: 1px solid var(--gs-accent-line); }
.rc-runbanner__nextlbl { font-size: 10px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gs-muted); }
.rc-runbanner__nextval { font-family: var(--gs-font-mono);
  font-size: var(--gs-fs-md); font-weight: 600; color: var(--gs-accent); }

/* ── inline notice ───────────────────────────────────────────────── */
.rc-note { display: flex; gap: 12px; align-items: flex-start;
  padding: 12px var(--gs-space-4); border-radius: var(--gs-radius);
  background: var(--gs-warning-wash);
  border: 1px solid color-mix(in srgb, var(--gs-warning) 28%, transparent);
  font-size: var(--gs-fs-sm); color: var(--gs-ink-2);
  margin-bottom: var(--gs-space-4); }
.rc-note i { color: var(--gs-warning); font-size: 15px; margin-top: 1px; }
.rc-note--risk { background: var(--gs-danger-wash);
  border-color: color-mix(in srgb, var(--gs-danger) 28%, transparent); }
.rc-note--risk i { color: var(--gs-danger); }
.rc-note--info { background: var(--gs-live-wash); border-color: var(--gs-live-line); }
.rc-note--info i { color: var(--gs-live-ink); }
.rc-note b { color: var(--gs-ink); }

/* ── verdict: the sentence the numbers add up to ─────────────────── */
.rc-verdict { padding: var(--gs-space-4) var(--gs-space-5);
  border-radius: var(--gs-radius); background: var(--gs-well);
  border: 1px solid var(--gs-well-line); box-shadow: var(--gs-well-shadow);
  border-left: 3px solid var(--gs-warning); font-size: var(--gs-fs-md);
  color: var(--gs-ink-2); margin-bottom: var(--gs-space-5); }
.rc-verdict b { color: var(--gs-ink); }
.rc-verdict--good { border-left-color: var(--gs-success); }
.rc-verdict--risk { border-left-color: var(--gs-danger); }

/* ── panel heading inside a card body ────────────────────────────── */
.rc-h { font-size: var(--gs-fs-xs); font-weight: 800; letter-spacing: .09em;
  text-transform: uppercase; color: var(--gs-muted); margin-bottom: 4px; }
.rc-sub { font-size: var(--gs-fs-xs); color: var(--gs-muted); margin: 0 0 14px; }

/* ── the composition ring ────────────────────────────────────────
   A conic gradient masked to an annulus — the same technique the dashboard
   gauge already uses. No SVG and no chart library; the segment angles
   arrive as an inline custom property from the template. */
.rc-ring { position: relative; width: 132px; height: 132px; flex: none;
  margin: 0 auto; }
.rc-ring__arc { position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(from -90deg, var(--rc-stops));
  -webkit-mask: radial-gradient(farthest-side, transparent 61%, var(--gs-mask-solid) 62%);
  mask: radial-gradient(farthest-side, transparent 61%, var(--gs-mask-solid) 62%); }
.rc-ring__hub { position: absolute; inset: 24px; border-radius: 50%;
  background: var(--gs-well); box-shadow: var(--gs-well-shadow);
  display: grid; place-items: center; text-align: center; }
.rc-ring__n { font-size: var(--gs-fs-xl); font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums; }
.rc-ring__lbl { font-size: 9px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gs-muted); margin-top: 3px; }
.rc-legend { display: flex; flex-direction: column; gap: 7px;
  margin-top: var(--gs-space-4); }
.rc-legend__row { display: flex; align-items: center; gap: 9px;
  font-size: var(--gs-fs-sm); color: var(--gs-ink-2); }
.rc-legend__key { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.rc-legend__n { margin-left: auto; font-family: var(--gs-font-mono);
  color: var(--gs-ink); }

/* ── vendor rows ─────────────────────────────────────────────────── */
.rc-logo { width: 30px; height: 30px; flex: none; border-radius: 9px;
  display: grid; place-items: center; font-size: 11px; font-weight: 800;
  letter-spacing: .03em; background: var(--gs-surface-3); color: var(--gs-ink-2);
  border: 1px solid var(--gs-line); }
.rc-logo--sm { width: 22px; height: 22px; font-size: 9px; border-radius: 7px; }
.rc-vendor { display: flex; align-items: center; gap: 12px;
  padding: 11px var(--gs-space-4); border-radius: var(--gs-radius);
  background: var(--gs-well); border: 1px solid var(--gs-well-line);
  box-shadow: var(--gs-well-shadow); width: 100%; text-align: left;
  font-family: inherit; cursor: pointer; color: var(--gs-ink); }
.rc-vendor + .rc-vendor { margin-top: 9px; }
.rc-vendor__body { flex: 1; min-width: 0; }
.rc-vendor__name { display: block; font-size: var(--gs-fs-md); font-weight: 700; }
.rc-vendor__meta { display: block; font-size: var(--gs-fs-xs); color: var(--gs-muted); }
.rc-vendor__fig { text-align: right; }
.rc-vendor__val { display: block; font-family: var(--gs-font-mono);
  font-size: var(--gs-fs-md); font-weight: 600; }
.rc-vendor__val--risk { color: var(--gs-danger); }
/* A vendor that produced no numbers. Dimmed, never given a figure — the
   whole point is that there is no figure to give. */
.rc-vendor--blind { border-style: dashed; }
.rc-vendor--blind .rc-vendor__val { color: var(--gs-muted); }

/* ── branch rows (the overview list) ─────────────────────────────────
   Same skeleton as .rc-vendor one level up: a chip, a body, a figure. It
   is a separate class rather than a modifier because it carries a fourth
   column — the state chip and when the branch next runs — and bolting a
   column onto .rc-vendor would change every vendor row on three screens.

   The two variants encode the same rule the vendor rows encode: a branch
   nobody could check gets a dashed border and no figure, never a $0. */
.rc-branches { display: flex; flex-direction: column; gap: 9px;
  padding: var(--gs-space-4); }
.rc-branch { display: flex; align-items: center; gap: 12px;
  padding: 13px var(--gs-space-4); border-radius: var(--gs-radius);
  background: var(--gs-well); border: 1px solid var(--gs-well-line);
  box-shadow: var(--gs-well-shadow); width: 100%; text-align: left;
  color: var(--gs-ink); text-decoration: none; }
.rc-branch:hover { border-color: var(--gs-accent); color: var(--gs-ink); }
.rc-branch__body { flex: 1; min-width: 0; }
.rc-branch__name { display: block; font-size: var(--gs-fs-md);
  font-weight: 700; }
.rc-branch__meta { display: block; font-size: var(--gs-fs-xs);
  color: var(--gs-muted); }
.rc-branch__fig { text-align: right; flex: none; min-width: 108px; }
.rc-branch__val { display: block; font-family: var(--gs-font-mono);
  font-size: var(--gs-fs-md); font-weight: 600; }
.rc-branch__val--risk { color: var(--gs-danger); }
.rc-branch__lbl { display: block; font-size: var(--gs-fs-xs);
  color: var(--gs-muted); margin-top: 2px; }
.rc-branch__state { text-align: right; flex: none; min-width: 152px; }
/* Needs a person: the left edge carries it, so the row reads as urgent in
   peripheral vision without a second colour anywhere else in the row. */
.rc-branch--risk { border-left: 3px solid var(--gs-danger); }
.rc-branch--idle { border-style: dashed; }
.rc-branch--idle .rc-branch__val { color: var(--gs-muted); }

/* Below ~760px the four columns stop being columns. The figure and the
   state chip sit under the name rather than squeezing to two characters
   each, which is what they did before. */
@media (max-width: 759.98px) {
  .rc-branch { flex-wrap: wrap; }
  .rc-branch__body { flex: 1 1 100%; order: 1; }
  .rc-logo { order: 0; }
  .rc-branch__fig { order: 2; text-align: left; min-width: 0; flex: 1 1 auto; }
  .rc-branch__state { order: 3; text-align: right; min-width: 0;
                      flex: 1 1 auto; }
}

/* ── routing bars ────────────────────────────────────────────────── */
.rc-bar + .rc-bar { margin-top: 11px; }
.rc-bar__top { display: flex; justify-content: space-between; gap: 10px;
  font-size: var(--gs-fs-sm); color: var(--gs-ink-2); margin-bottom: 5px; }
.rc-bar__n { font-family: var(--gs-font-mono); color: var(--gs-ink); }
.rc-bar__track { height: 7px; border-radius: var(--gs-radius-pill);
  background: var(--gs-surface-3); overflow: hidden; }
.rc-bar__fill { height: 100%; border-radius: inherit; background: var(--gs-accent); }
.rc-bar__fill--bad { background: var(--gs-danger); }
.rc-bar__fill--warn { background: var(--gs-warning); }
.rc-bar__fill--good { background: var(--gs-success); }
.rc-bar__fill--soft { background: var(--gs-line-strong); }

/* ── ticket board ────────────────────────────────────────────────── */
.rc-tabs { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 10px; margin-bottom: var(--gs-space-4); }
.rc-tab { display: flex; align-items: center; gap: 11px;
  padding: 12px var(--gs-space-4); border-radius: var(--gs-radius);
  background: var(--gs-well); border: 1px solid var(--gs-well-line);
  box-shadow: var(--gs-well-shadow); cursor: pointer; font-family: inherit;
  text-align: left; color: var(--gs-ink); text-decoration: none; }
.rc-tab.is-on { border-color: var(--gs-accent-line);
  background: var(--gs-accent-wash); box-shadow: none; }
.rc-tab__dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.rc-tab__l { display: block; font-size: var(--gs-fs-sm); font-weight: 700; }
.rc-tab__hint { display: block; font-size: var(--gs-fs-xs); color: var(--gs-muted); }
.rc-tab__n { margin-left: auto; font-family: var(--gs-font-mono);
  font-size: var(--gs-fs-lg); font-weight: 600; }
.rc-filters { display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
  margin-bottom: var(--gs-space-4); }
.rc-select, .rc-input { font-family: inherit; font-size: var(--gs-fs-sm);
  min-height: 38px; padding: 0 12px; border-radius: var(--gs-radius-sm);
  background: var(--gs-surface-2); color: var(--gs-ink);
  border: 1px solid var(--gs-line-strong); }
.rc-input::placeholder { color: var(--gs-muted); }
.rc-input--grow { flex: 1 1 220px; }
.rc-count { margin-left: auto; font-size: var(--gs-fs-xs); color: var(--gs-muted); }
.rc-tickets { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 12px; }
.rc-ticket { position: relative; padding: 14px var(--gs-space-4);
  border-radius: var(--gs-radius);
  background: var(--gs-depth-card-2), var(--gs-surface);
  border: 1px solid var(--gs-line);
  box-shadow: var(--gs-depth-shadow-sm), var(--gs-depth-edge);
  border-left: 3px solid var(--gs-line-strong); cursor: pointer;
  display: block; color: inherit; text-decoration: none; }
.rc-ticket--open { border-left-color: var(--gs-danger); }
.rc-ticket--wip { border-left-color: var(--gs-warning); }
.rc-ticket--done { border-left-color: var(--gs-success); opacity: .8; }
.rc-ticket__top { display: flex; align-items: center; gap: 8px;
  justify-content: space-between; }
.rc-ticket__part { font-family: var(--gs-font-mono); font-size: var(--gs-fs-md);
  font-weight: 600; color: var(--gs-ink); }
.rc-ticket__desc { font-size: var(--gs-fs-sm); color: var(--gs-ink-2);
  margin-top: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-ticket__line { font-size: var(--gs-fs-sm); color: var(--gs-muted); margin-top: 8px; }
.rc-ticket__line b { color: var(--gs-danger); font-family: var(--gs-font-mono); }
.rc-ticket__badges { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 9px; }
.rc-ticket__foot { display: flex; align-items: center; gap: 8px; margin-top: 12px;
  padding-top: 11px; border-top: 1px solid var(--gs-line); }
.rc-ticket__meta { font-size: var(--gs-fs-xs); color: var(--gs-muted); flex: 1; }

/* ── ticket detail ───────────────────────────────────────────────── */
.rc-dl { display: grid; grid-template-columns: auto 1fr;
  gap: 8px var(--gs-space-5); margin: 0 0 var(--gs-space-5);
  font-size: var(--gs-fs-sm); }
.rc-dl dt { color: var(--gs-muted); font-weight: 700; }
.rc-dl dd { margin: 0; color: var(--gs-ink); }
.rc-timeline { position: relative; padding-left: 22px; }
.rc-timeline::before { content: ''; position: absolute; left: 5px; top: 6px;
  bottom: 6px; width: 1px; background: var(--gs-line-strong); }
.rc-tl { position: relative; padding-bottom: 14px; }
.rc-tl:last-child { padding-bottom: 0; }
.rc-tl::before { content: ''; position: absolute; left: -21px; top: 5px;
  width: 9px; height: 9px; border-radius: 50%; background: var(--gs-surface-3);
  border: 1px solid var(--gs-line-strong); }
.rc-tl--sys::before { background: var(--gs-accent); border-color: transparent; }
.rc-tl--done::before { background: var(--gs-success); border-color: transparent; }
.rc-tl__t { font-size: var(--gs-fs-sm); color: var(--gs-ink-2); }
.rc-tl__t b { color: var(--gs-ink); }
.rc-tl__w { font-size: var(--gs-fs-xs); color: var(--gs-muted);
  font-family: var(--gs-font-mono); }

/* ── admin: run console ──────────────────────────────────────────── */
.rc-presets { display: flex; gap: 6px; flex-wrap: wrap; }
.rc-preset { font-family: inherit; font-size: var(--gs-fs-sm); font-weight: 700;
  padding: 8px 14px; border-radius: var(--gs-radius-pill); cursor: pointer;
  background: var(--gs-surface-2); color: var(--gs-ink-2);
  border: 1px solid var(--gs-line-strong); }
.rc-preset.is-on { background: var(--gs-accent-wash); color: var(--gs-accent);
  border-color: var(--gs-accent-line); }
.rc-runbar { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.rc-runbar__spacer { flex: 1 1 20px; }
.rc-sources { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 10px; }
.rc-source { display: flex; align-items: center; gap: 11px;
  padding: 12px var(--gs-space-4); border-radius: var(--gs-radius);
  background: var(--gs-well); border: 1px solid var(--gs-well-line);
  box-shadow: var(--gs-well-shadow); cursor: pointer; }
.rc-source.is-off { opacity: .5; cursor: not-allowed; }
.rc-source input { accent-color: var(--gs-accent); width: 16px; height: 16px; flex: none; }
.rc-source__body { flex: 1; min-width: 0; }
.rc-source__name { display: block; font-size: var(--gs-fs-md); font-weight: 700; }
.rc-source__meta { display: block; font-size: var(--gs-fs-xs); color: var(--gs-muted); }

/* Phase chips — queued / fetching / fetched / failed / skipped. */
.rc-phase { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px;
  border-radius: var(--gs-radius-pill); font-size: var(--gs-fs-xs); font-weight: 800;
  background: var(--gs-surface-3); color: var(--gs-muted);
  border: 1px solid var(--gs-line); }
.rc-phase--run { background: var(--gs-accent-wash); color: var(--gs-accent);
  border-color: transparent; }
.rc-phase--ok { background: var(--gs-success-wash); color: var(--gs-success);
  border-color: transparent; }
.rc-phase--fail { background: var(--gs-danger-wash); color: var(--gs-danger);
  border-color: transparent; }
.rc-phase--skip { background: var(--gs-warning-wash); color: var(--gs-warning);
  border-color: transparent; }
.rc-phase__spin { width: 9px; height: 9px; border-radius: 50%;
  border: 1.5px solid currentColor; border-top-color: transparent;
  animation: rc-spin .9s linear infinite; }
.rc-prog { display: flex; align-items: center; gap: 12px;
  padding: 11px var(--gs-space-4); border-radius: var(--gs-radius);
  background: var(--gs-well); border: 1px solid var(--gs-well-line);
  box-shadow: var(--gs-well-shadow); }
.rc-prog + .rc-prog { margin-top: 9px; }
.rc-prog__body { flex: 1; min-width: 0; }
.rc-prog__name { font-size: var(--gs-fs-md); font-weight: 700; }
.rc-prog__meta { font-size: var(--gs-fs-xs); color: var(--gs-muted); margin-top: 2px; }

/* ── admin: schedule rows ────────────────────────────────────────
   Seven blocks, one per night. A connection's health is a history, not a
   status word — three amber nights in a row is the thing worth seeing. */
.rc-nights { display: flex; gap: 4px; margin: 4px 0 0; }
.rc-nights__n { flex: 1; height: 24px; border-radius: 5px;
  background: var(--gs-surface-3); }
.rc-nights__n--ok { background: var(--gs-success); opacity: .85; }
.rc-nights__n--bad { background: var(--gs-danger); }
.rc-nights__n--skip { background: var(--gs-line-strong); }
.rc-nights__scale { display: flex; justify-content: space-between;
  font-size: 10px; color: var(--gs-muted); margin-top: 6px;
  font-family: var(--gs-font-mono); }

/* A field in the one form this feature has. */
.rc-field { margin-bottom: var(--gs-space-3); }
.rc-field__l { display: block; font-size: var(--gs-fs-xs); font-weight: 700;
  color: var(--gs-muted); margin-bottom: 5px; }
.rc-field .rc-input, .rc-field .rc-select { width: 100%; }

/* ── the schedule settings block ─────────────────────────────────────
   A settings LIST, not a form row: one setting per line, its name and its
   consequence on the left, its control on the right. The three fields used
   to sit on a single flex row with the explanation in a paragraph under all
   of them, which left each control labelled by two words ("At", "Looking
   back") and the sentence that made it mean something several lines away.

   This is the screen where a shop decides that a thirty-minute scrape of
   their supplier accounts happens on a timer. Giving it three rows instead
   of one line is the point, not an accident of markup. */
.rc-settings { display: flex; flex-direction: column; }

/* The state, said in words above the controls. A checkbox is the one
   control whose meaning cannot be read off it — unticked and ticked look
   equally deliberate — so the sentence carries it. */
.rc-settings__state { display: flex; gap: 12px; align-items: flex-start;
  padding: 12px var(--gs-space-4); border-radius: var(--gs-radius);
  background: var(--gs-well); border: 1px solid var(--gs-well-line);
  font-size: var(--gs-fs-sm); color: var(--gs-ink-2);
  margin-bottom: var(--gs-space-4); }
.rc-settings__state i { font-size: 18px; color: var(--gs-muted);
  flex: none; margin-top: 1px; }
.rc-settings__state.is-on { border-color: var(--gs-success); }
.rc-settings__state.is-on i { color: var(--gs-success); }

.rc-setting { display: flex; gap: var(--gs-space-4); align-items: flex-start;
  padding: var(--gs-space-4) 0; border-top: 1px solid var(--gs-line); }
.rc-setting:first-of-type { border-top: 0; padding-top: 0; }
.rc-setting__text { flex: 1; min-width: 0; }
.rc-setting__name { display: block; font-size: var(--gs-fs-md);
  font-weight: 700; color: var(--gs-ink); margin-bottom: 4px; }
.rc-setting__help { margin: 0; font-size: var(--gs-fs-sm);
  color: var(--gs-muted); max-width: 62ch; }
/* Fixed width so the three controls line up down the right edge — a select
   that sizes to its longest option makes the column ragged, and ragged
   reads as three unrelated controls rather than one settings list. */
.rc-setting__ctrl { flex: none; width: 168px; padding-top: 2px; }
.rc-setting__ctrl .rc-select { width: 100%; }

.rc-settings__foot { display: flex; align-items: center; gap: 14px;
  flex-wrap: wrap; padding-top: var(--gs-space-4);
  border-top: 1px solid var(--gs-line); }

/* Below ~660px the control drops under its explanation rather than
   squeezing a 24-option select into a third of the width. */
@media (max-width: 659.98px) {
  .rc-setting { flex-direction: column; gap: 10px; }
  .rc-setting__ctrl { width: 100%; padding-top: 0; }
}

.rc-textarea { width: 100%; min-height: 76px; font-family: inherit;
  font-size: var(--gs-fs-sm); padding: 10px 12px;
  border-radius: var(--gs-radius-sm); background: var(--gs-surface-2);
  color: var(--gs-ink); border: 1px solid var(--gs-line-strong); resize: vertical; }

/* ── keyframes ───────────────────────────────────────────────────
   One left. `rc-ping` — the run banner's heartbeat — is gone: it pulsed at
   the same rate for a run that finished a minute ago and one that finished
   last month, which is motion that carries no information. The spinner
   stays, because a vendor being fetched right now genuinely is in motion,
   and it is switched off under reduced-motion below. */
@keyframes rc-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .rc-phase__spin { animation: none; }
}

@media (max-width: 767.98px) {
  .rc-runbanner__next { width: 100%; justify-content: space-between; }
  .rc-ring { width: 112px; height: 112px; }
  .rc-dl { grid-template-columns: 1fr; gap: 2px var(--gs-space-4); }
  .rc-dl dd { margin-bottom: 10px; }
}

/* ── sidebar nav group ────────────────────────────────────────────
   Reconciliation is a SECTION, not a link: it owns four screens, and a flat
   list buried them between Stores and Team. The parent is a disclosure and
   the children hang off one hairline rail, so four indented rows read as one
   group rather than four orphans.

   Ported verbatim from the mock. See `_nav_groups()` in core/jinja.py for
   which prefixes become a group and why it is not every prefix. */
.gs-navgroup { margin-bottom: 2px; }
.gs-navgroup__toggle { width: 100%; background: none; border: 0;
  font-family: inherit; text-align: left; }
.gs-navgroup__caret { flex: none; font-size: 16px; color: var(--gs-muted);
  transition: transform var(--gs-dur) var(--gs-ease); }
.gs-navgroup.is-open .gs-navgroup__caret { transform: rotate(90deg); }
/* Collapsed but holding the current screen still says so — otherwise closing
   the group loses the only marker of where you are. */
.gs-navgroup.has-active > .gs-navlink { color: var(--gs-ink); font-weight: 800; }
.gs-navgroup.has-active > .gs-navlink .gs-navlink__icon {
  background: var(--gs-gradient); color: var(--gs-on-accent);
  border-color: transparent; box-shadow: var(--gs-glow); }
.gs-navsub { list-style: none; margin: 4px 0 6px; padding: 0 0 0 27px;
  position: relative; }
.gs-navsub::before { content: ''; position: absolute; left: 15px; top: 4px;
  bottom: 4px; width: 1px; background: var(--gs-line-strong); }
.gs-navgroup:not(.is-open) .gs-navsub { display: none; }
.gs-subnavlink { display: flex; align-items: center; gap: 9px; padding: 7px 10px;
  border-radius: var(--gs-radius-sm); color: var(--gs-ink-2);
  font-size: var(--gs-fs-sm); font-weight: 600; cursor: pointer;
  text-decoration: none; }
.gs-subnavlink:hover { background: var(--gs-surface-2); color: var(--gs-ink); }
.gs-subnavlink__dot { width: 5px; height: 5px; border-radius: 50%; flex: none;
  background: var(--gs-line-strong); }
.gs-subnavlink__text { flex: 1; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.gs-subnavlink.is-active { color: var(--gs-ink); font-weight: 800;
  background: var(--gs-accent-wash); }
.gs-subnavlink.is-active .gs-subnavlink__dot { background: var(--gs-accent);
  box-shadow: var(--gs-ring-glow); }
.gs-navlink__pip { width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--gs-danger); }
