/* Member app shell: header, global search, avatar menu, sidebar and bottom
   tab bar. Tokens only (site/member-tokens.css) -- see docs/design/DESIGN-SYSTEM.md.
   Every rule here is scoped under #member-shell (an id) or #watch-card-listbox
   (an existing id) so it beats the shipped .live-member :is(button,.button,
   .button-link) reset (specificity 0,2,0) without !important -- see
   site/member.css:602-610 for the identical trap already solved there. */

#member-shell { display: flex; flex-direction: column; min-height: 100vh; }

/* Section 8: inline SVG, 24x24 viewBox, fill:none, stroke:currentColor,
   stroke-width 1.7, round caps/joins -- symbols carry no presentation
   attributes of their own so every <use> inherits this once. */
.ps-icon {
  display: inline-block; width: 20px; height: 20px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ---- Header ------------------------------------------------------------ */

#member-shell #shell-header {
  /* Not sticky/fixed, on ANY route (O-74, restored round-36: PR #283 pinned
     this at <=899px for the installed-iOS-PWA safe-area case below, which
     reversed it): the header scrolls away with the page (owner, verbatim:
     "i dont want the top bar locked i want to be able to full screen scroll
     down i dont want to feel constrained"). An installed iOS PWA still needs
     the safe-area inset reserved at the header's own (unpinned) position, so
     that padding stays -- only `position:sticky` is what O-74 forbids. Ask
     Ash's OWN composer/thread rails may still use position:sticky for their
     internal layout (site/chat.css) -- that is a different, narrower surface
     O-74 never spoke to; only the SHELL header must always scroll away. */
  gap: var(--space-4);
  padding-top: calc(9px + env(safe-area-inset-top, 0px));
  padding-right: max(clamp(14px, 3vw, 40px), calc(env(safe-area-inset-right, 0px) + var(--space-2)));
  padding-bottom: 9px;
  padding-left: max(clamp(14px, 3vw, 40px), calc(env(safe-area-inset-left, 0px) + var(--space-2)));
}

#member-shell #shell-header-actions { flex: 0 0 auto; }

/* The blocking startup script can arrive after the static body has painted.
   Reserve the unknown account's slot without showing an identity or granting
   an action; startup replaces it with an inert skeleton, auth removes it. */
#member-shell.shell-boot-pending #shell-avatar-menu[hidden] {
  /* Override member.css's global [hidden] !important only while reserving
     an invisible, inaccessible slot. No account control is exposed. */
  display: block !important;
  visibility: hidden;
}

#member-shell .shell-theme-toggle,
#member-shell .shell-avatar-trigger {
  min-width: var(--control-height); min-height: var(--control-height);
  padding: 0; display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: var(--border-strong) solid transparent;
  border-radius: var(--radius-md); color: var(--text); cursor: pointer;
}
#member-shell .shell-theme-toggle:hover,
#member-shell .shell-avatar-trigger:hover { border-color: var(--accent); background: var(--card1-hi); }
#member-shell .shell-theme-toggle:focus-visible,
#member-shell .shell-avatar-trigger:focus-visible { outline: 0; border-color: var(--accent); box-shadow: var(--focus-ring); }
#member-shell .shell-theme-toggle .ps-icon { width: 20px; height: 20px; }

#member-shell .shell-avatar-menu { position: relative; }
#member-shell .shell-avatar-trigger {
  width: var(--control-height); border-radius: var(--radius-circle);
  background: var(--cta-grad); color: var(--accent-ink); font-family: var(--font-mono);
  font-weight: 700; font-size: var(--font-size-400); text-transform: uppercase;
  border-color: var(--outline); box-shadow: var(--elev-1);
}
#member-shell .shell-avatar-trigger:hover { background: var(--cta-grad-hi); border-color: var(--outline); }
#member-shell .shell-avatar-trigger.is-boot-pending,
#member-shell .shell-avatar-trigger.is-boot-pending:hover {
  color: transparent; cursor: default;
  background: linear-gradient(105deg, var(--well) 15%, var(--card1-hi) 42%, var(--well) 68%);
  background-size: 220% 100%; animation: member-shimmer var(--duration-ambient) ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  #member-shell .shell-avatar-trigger.is-boot-pending,
  #member-shell .shell-avatar-trigger.is-boot-pending:hover { animation: none; background: var(--card1-hi); }
}

#member-shell .shell-avatar-panel {
  position: absolute; top: calc(100% + var(--space-2)); right: 0; z-index: 20;
  min-width: calc(220px * var(--type-scale, 1));
  background: linear-gradient(180deg, var(--card1), var(--card2));
  border: var(--border-strong) solid var(--outline);
  border-radius: var(--radius-md);
  box-shadow: var(--elev-3);
  padding: var(--space-2);
  display: flex; flex-direction: column; gap: 2px;
}
#member-shell .shell-avatar-panel-email {
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-mono); font-size: var(--font-size-200); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#member-shell .shell-avatar-panel button {
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%; min-height: var(--control-height);
  padding: var(--space-2) var(--space-3);
  background: transparent; border: 0; border-radius: var(--radius-sm);
  color: var(--text); font-size: var(--font-size-400); text-align: left; cursor: pointer;
}
#member-shell .shell-avatar-panel button:hover { background: var(--card1-hi); }
#member-shell .shell-avatar-panel button:focus-visible { outline: 0; box-shadow: var(--focus-ring); }
#member-shell .shell-avatar-panel .ps-icon { width: 18px; height: 18px; color: var(--muted); }

/* ---- Shared tab-bar item look, shell nav only --------------------------- */

#member-shell .ps-tabs__item {
  display: flex; align-items: center; justify-content: flex-start; gap: var(--space-2);
  min-height: var(--control-height);
  border: 0; background: transparent; cursor: pointer;
  font-family: var(--font-mono); font-size: var(--font-size-200);
  font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--body);
}
#member-shell .ps-tabs__item:hover { color: var(--text); }
#member-shell .ps-tabs__item:focus-visible { outline: 0; box-shadow: var(--focus-ring); border-radius: var(--radius-sm); }
#member-shell .ps-tabs__item[aria-current="page"] { color: var(--text); }
#member-shell .ps-tabs__item .ps-icon { width: 20px; height: 20px; flex: none; }
#member-shell .ps-tabs__count {
  margin-left: auto; min-width: 18px; padding: 1px 6px;
  border-radius: var(--radius-pill); background: var(--accent); color: var(--accent-ink);
  font-size: var(--font-size-100); text-align: center; line-height: 1.4;
}

/* "ai" in the Ash ai product name (owner, 2026-09-28): modern.ai's tier-label "ai"
   holo cascade, copied verbatim from https://www.modern.ai/site.css?v=0.4.27
   (.tier .tk .ai) and scoped to this class. Lowercase inside uppercase eyebrows.
   Under reduced motion the gradient stays and the animation stops. */
.ps-ash-ai {
  font-weight: 600; text-transform: none;
  background: linear-gradient(108deg,#0000 44%,#fffffffa 50%,#0000 56%) 0 0/240% 100% no-repeat, linear-gradient(100deg,#3457c9,#2bb7ff,#9cf0ff,#fff,#ffb46b,#ff4fa3,#a45bff,#2bb7ff,#3457c9) 0 0/320%;
  -webkit-text-fill-color: transparent; color: #0000;
  -webkit-background-clip: text; background-clip: text;
  animation: 4.2s linear infinite psAshAiHolo;
}
@keyframes psAshAiHolo { 0% { background-position: -150% 0, 0% } to { background-position: 250% 0, 200% } }
@media (prefers-reduced-motion: reduce) { .ps-ash-ai { animation: none; } }

/* Light-safe "ai" (owner, 2026-09-28: "ship it, use the light-safe ai on light
   theme"). The dark-theme cascade above keeps a white sheen layer and pale-cyan
   / white gradient stops that fail contrast on the light member backgrounds
   (#ffffff, #f6f5fb). This variant drops the sheen layer and every low-contrast
   stop; each remaining stop is >=4.3:1 on both backgrounds. Dark theme is
   untouched -- this only matches when data-theme is not "dark". */
:root:not([data-theme="dark"]) .ps-ash-ai {
  background: linear-gradient(100deg,#3457c9,#0b6fb8,#b85a00,#d11f7a,#7a3ff2,#0b6fb8,#3457c9) 0 0/320%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: #0000;
  animation: 4.2s linear infinite psAshAiHoloLight;
}
@keyframes psAshAiHoloLight { 0% { background-position: 0% 0 } to { background-position: 200% 0 } }
@media (prefers-reduced-motion: reduce) { :root:not([data-theme="dark"]) .ps-ash-ai { animation: none; } }

/* ---- Sidebar (>=1100px) ------------------------------------------------- */

#shell-sidebar {
  flex: 0 0 auto; width: calc(220px * var(--type-scale, 1));
  border-right: var(--border-strong) solid var(--border);
  padding: var(--space-4) var(--space-3);
  display: flex; flex-direction: column; gap: 2px;
  /* The header is never sticky (O-74 above), so once it has scrolled away the
     rail pins to the viewport's own top edge. The former top of
     --shell-header-height left an empty header-high band above the rail on
     every scrolled route (and beside the full-height Ask Ash desk). */
  position: sticky; top: 0; align-self: flex-start;
  max-height: 100dvh;
  overflow-y: auto;
}
/* Rows match the Claude Code sidebar (owner, 2026-09-28): the app's sans at 14px
   regular, left-aligned beside an 18px icon, no borders, and a soft rounded fill for
   the selected row -- the same neutral fill as the selected Ash conversation row.
   Rows are 32px for a mouse or trackpad and keep the 44px floor on touch screens. */
#shell-sidebar .ps-tabs__item {
  width: 100%; min-height: var(--control-height); padding: 0 var(--space-2); gap: 10px;
  border: 0; border-radius: var(--radius-sm); background: transparent; box-shadow: none;
  font: 400 var(--font-size-400)/1.25 var(--font-sans); text-transform: none; letter-spacing: 0; text-align: left;
  color: var(--body);
}
@media (pointer: fine) { #shell-sidebar .ps-tabs__item { min-height: 32px; } }
#shell-sidebar .ps-tabs__item .ps-icon { width: 18px; height: 18px; }
#shell-sidebar .ps-tabs__item:hover { background: color-mix(in srgb, var(--text) 4%, transparent); box-shadow: none; color: var(--text); }
#shell-sidebar .ps-tabs__item[aria-current="page"] { background: color-mix(in srgb, var(--text) 7%, transparent); color: var(--text); }
#shell-sidebar .shell-nav-divider {
  margin: var(--space-3) var(--space-2) var(--space-2);
  border: 0; border-top: var(--border-hairline) solid var(--border);
}
/* A label this long ("Live opportunities") wrapped onto two lines beside its
   badge (owner report, 2026-09-23): the label span had no width constraint
   of its own, so once the badge's own min-width plus its margin-left:auto
   left less room than the label's natural text width, the label -- a flex
   item with the default min-width:auto -- wrapped instead of shrinking.
   min-width:0 lets it shrink below that natural width so the ellipsis
   below actually has room to apply, and flex:1 1 auto claims the row's
   leftover space instead of sizing to content. The badge gets a matching
   flex:none so it can never be the one to give up space instead. */
#shell-sidebar .ps-tabs__label {
  display: inline-block; flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#shell-sidebar .ps-tabs__count { flex: none; }

/* Icon-only rail. Two ways in: the width default between 1100 and 1279px, and
   the member's own toggle at any sidebar width (#shell-sidebar-toggle,
   site/member.js toggleShellSidebar; <html data-shell-sidebar> is set before
   first paint by site/member-startup.js). An explicit "expanded" choice
   overrides the width default. Owner, 2026-09-27: "i cant compress the left
   nav bar to make more space". Labels are clipped, not display:none, and every
   item carries its own aria-label, so each icon keeps an accessible name. */
.shell-sidebar-toggle { color: var(--muted); }
#member-shell #shell-sidebar .shell-sidebar-toggle:hover { color: var(--text); }
@media (min-width: 1100px) and (max-width: 1279px) {
  :root:not([data-shell-sidebar="expanded"]) #shell-sidebar { width: 68px; padding-inline: var(--space-2); }
  :root:not([data-shell-sidebar="expanded"]) #shell-sidebar .ps-tabs__item { position: relative; justify-content: center; padding-inline: 0; }
  :root:not([data-shell-sidebar="expanded"]) #shell-sidebar .ps-tabs__label {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
  :root:not([data-shell-sidebar="expanded"]) #shell-sidebar .ps-tabs__count {
    position: absolute; top: 4px; right: 6px; margin: 0;
    min-width: 16px; padding: 0 4px; font-size: var(--font-size-100); line-height: 16px;
  }
  :root:not([data-shell-sidebar="expanded"]) #shell-sidebar.ps-boot-nav .ps-boot-nav-item span { display: none; }
  :root:not([data-shell-sidebar="expanded"]) #shell-sidebar.ps-boot-nav .ps-boot-nav-item { justify-content: center; padding-inline: 0; }
}
@media (min-width: 1100px) {
  :root[data-shell-sidebar="collapsed"] #shell-sidebar { width: 68px; padding-inline: var(--space-2); }
  :root[data-shell-sidebar="collapsed"] #shell-sidebar .ps-tabs__item { position: relative; justify-content: center; padding-inline: 0; }
  :root[data-shell-sidebar="collapsed"] #shell-sidebar .ps-tabs__label {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
  :root[data-shell-sidebar="collapsed"] #shell-sidebar .ps-tabs__count {
    position: absolute; top: 4px; right: 6px; margin: 0;
    min-width: 16px; padding: 0 4px; font-size: var(--font-size-100); line-height: 16px;
  }
  :root[data-shell-sidebar="collapsed"] #shell-sidebar.ps-boot-nav .ps-boot-nav-item span { display: none; }
  :root[data-shell-sidebar="collapsed"] #shell-sidebar.ps-boot-nav .ps-boot-nav-item { justify-content: center; padding-inline: 0; }
}

/* ---- Bottom tab bar (<900px) -------------------------------------------- */

#shell-bottomnav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
  display: flex; justify-content: space-around;
  background: linear-gradient(180deg, var(--card1), var(--card2));
  border-top: var(--border-strong) solid var(--outline);
  padding-top: var(--space-1);
  padding-right: max(var(--space-1), env(safe-area-inset-right, 0px));
  padding-bottom: calc(var(--space-1) + env(safe-area-inset-bottom, 0px));
  padding-left: max(var(--space-1), env(safe-area-inset-left, 0px));
}
#shell-bottomnav .ps-tabs__item {
  position: relative;
  flex: 1 1 0; flex-direction: column; justify-content: center; gap: 2px; min-width: 0;
  min-height: 52px; font-size: var(--font-size-100); padding: var(--space-1);
}
#shell-bottomnav .ps-tabs__item .ps-icon { width: 22px; height: 22px; }
/* Bottom-nav labels must stay fully readable — never clipped ellipsis.
   Shared .ps-tabs__item mono/uppercase tracking is too wide for "Collection"
   in a phone bar, so labels here always use the compact sans face. With
   4 primary routes + More (5 columns), every current label fits at 320px. */
#shell-bottomnav .ps-tabs__label {
  display: block; max-width: 100%;
  font-family: var(--font-sans); font-size: calc(10px * var(--type-scale,1));
  font-weight: 700; text-transform: none; letter-spacing: 0;
  white-space: nowrap; overflow: visible; text-overflow: clip;
}
#shell-bottomnav .ps-tabs__count {
  position: absolute; top: 2px; right: calc(50% - 20px);
  min-width: 14px; height: 14px; padding: 0 3px; font-size:calc(9px * var(--type-scale,1)); line-height: 14px;
}
#shell-more-panel {
  position: fixed;
  left: max(var(--space-3), calc(env(safe-area-inset-left, 0px) + var(--space-2)));
  right: max(var(--space-3), calc(env(safe-area-inset-right, 0px) + var(--space-2)));
  bottom: var(--shell-bottomnav-offset, calc(64px + env(safe-area-inset-bottom, 0px)));
  z-index: 10;
  background: linear-gradient(180deg, var(--card1), var(--card2));
  border: var(--border-strong) solid var(--outline);
  border-radius: var(--radius-md); box-shadow: var(--elev-3);
  padding: var(--space-2);
  display: flex; flex-direction: column; gap: 2px;
}
#shell-more-panel .ps-tabs__item { width: 100%; padding: 0 var(--space-3); border-radius: var(--radius-sm); }

/* ---- Layout: which nav shows where -------------------------------------- */

#shell-sidebar, #shell-bottomnav, #shell-more-panel { display: none; }

/* A route without a shape-safe skeleton uses the persistent status row.
   It owns the usable member canvas for this brief state so it cannot
   shrink-wrap beside a desktop sidebar or drift above the mobile tab bar. */
#member-shell .shell-body:has(> #app > #member-persistent-loader) {
  flex: 1 1 auto;
  min-block-size: 0;
}
#member-shell #app:has(> #member-persistent-loader) {
  display: block;
  min-block-size: 0;
}
#member-shell #app:has(> #member-persistent-loader) > #member-persistent-loader {
  box-sizing: border-box;
  inline-size: 100%;
  max-inline-size: none;
  margin: 0;
  padding: var(--space-6) var(--space-4);
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}



@media (min-width: 1100px) {
  #member-shell .shell-body { display: flex; align-items: flex-start; }
  #shell-sidebar:not([hidden]) { display: flex; }
  #member-shell.shell-boot-pending #shell-sidebar[hidden] {
    /* The global hidden reset must not erase the pre-startup rail slot. */
    display: flex !important;
    visibility: hidden;
  }
  #member-shell .member-nav { display: none; }
  /* #app is a flex item beside #shell-sidebar with no flex-grow of its own, so on
     every route it shrink-to-fits its content's max-content width instead of filling
     the row. Discover/Collection/Overview already reach full width in practice because
     their own realistic content (tile grids, wide panels) has a max-content wide enough
     to fill the viewport anyway (site/discover.css's own note on this) -- Ask Ash does
     not: the three-column grid (chat.css .ps-ash-layout, fr tracks) measures its
     max-content off whatever is longest in the CURRENT conversation, so the whole desk
     resized between states at the same viewport (MEASURED: Sources column right edge
     landed at four different x positions across empty/queued/deleted/failed at 2160px).
     Forcing flex-grow on every route (tried first) reflowed Account/Discover/Collection/
     Overview at 2160px (MEASURED: 6 unrelated visual baselines failed). A
     #app:has(> .content.cp-shell) selector (tried second, to scope it) never matched on
     the card-sheet-over-Discover route -- element.matches() confirmed false in that
     exact DOM state -- and still broke that route's layout regardless (MEASURED,
     card-sheet.spec.cjs, mechanism not isolated); an explicit class removes that whole
     class of risk. site/chat.js toggles .ps-ash-fill on #app in the same mount/unmount
     that already toggles body.ps-ash-route, so this can only ever apply to what that
     module itself marks. */
  /* Class AND the Ash route's own content: a class left behind by any mount path can
     never widen another route (MEASURED: Alerts/Account reflowed when it leaked). */
  #member-shell #app.ps-ash-fill:has(> .content.cp-shell) { flex: 1 1 auto; min-width: 0; }
  /* Collection's first-boot status has intentionally tiny intrinsic width.
     Fill only while this exact transient skeleton is #app's direct child, so
     the avatar is centered in the workspace without changing any settled
     Collection route or another route's boot layout. */
  #member-shell #app:has(> #member-boot-skeleton.inventory-view) { flex: 1 1 auto; min-width: 0; }
  /* The Collection canvas owns the available width in every state, not only
     after a large grid happens to push the flex item open. The same bounds
     center the compact Ash loader, empty/error states and the real cards. */
  #member-shell #app:has(> .inventory-view) { flex: 1 1 auto; min-width: 0; }
  /* Search, short recommendation rows and empty states share the entire
     Discover canvas; intrinsic tile width must not set workspace width. */
  #member-shell #app:has(> #discover-view-root) { flex: 1 1 auto; min-width: 0; }
  /* Discover's verified cold-start skeleton contains only a compact search
     and tile shape, so unlike the settled gallery it has no intrinsic width
     large enough to claim the workspace. Keep the shell canvas full width
     for this exact transient state; without it the skeleton hugs the sidebar
     and leaves a misleading empty half-screen until the catalogue arrives. */
  #member-shell #app:has(> #member-boot-skeleton.discover-view) { flex: 1 1 auto; min-width: 0; }
  #member-shell #app:has(> #member-persistent-loader) { flex: 1 1 auto; min-width: 0; }
}
@media (max-width: 1099px) {
  /* The shell owns landscape side insets for every screen. Route content may add its
     normal spacing inside this guard, but must not consume the device inset again. */
  #member-shell #app {
    padding-right: env(safe-area-inset-right, 0px);
    padding-left: env(safe-area-inset-left, 0px);
  }
}
@media (max-width: 899px) {
  #shell-bottomnav:not([hidden]) { display: flex; }
  #shell-more-panel:not([hidden]) { display: flex; }
  #member-shell .member-nav { display: none; }
  #member-shell #app { padding-bottom: var(--shell-bottomnav-offset, calc(64px + env(safe-area-inset-bottom, 0px))); }
}

/* ---- Per-screen skeleton primitives (design system 9.15) ---------------- */

.ps-skeleton {
  display: block; /* a span carrying this class still needs width/height/aspect-ratio to apply */
  background: var(--well);
  border-radius: var(--radius-md);
  position: relative;
  overflow: hidden;
}
.ps-skeleton::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--card1-hi), transparent);
  animation: ps-skeleton-sweep var(--duration-ambient) linear infinite;
}
@media (prefers-reduced-motion: reduce) { .ps-skeleton::after { animation: none; display: none; } }
@keyframes ps-skeleton-sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
.ps-skeleton--text { display: block; height: 1em; margin: 3px 0; border-radius: var(--radius-xs); }
.ps-skeleton--tile { aspect-ratio: 5 / 7; }
.ps-skeleton--row { height: 56px; }
.ps-skeleton--figure { height: var(--font-size-700); width: 72px; }

/* ---- docs/product/loading-states.md: shared loading-state mechanics ----- */

/* #1: a skeleton group always carries the settled grid/list's own class
   (opportunity-grid, ps-tile-grid, collection-grid, ...) so column count and
   gap come from that rule, never from a bare fallback column. Individual
   skeleton items reuse the settled item's structural class too (see the
   *Skeleton() builders in member.js) -- this block only has to size the
   shimmer primitives inside them and fade every fresh subtree in.
   #4: both the skeleton and the settled content that replaces it in the same
   host animate in with an opacity-only fade -- opacity never contributes to
   layout shift, so this cannot itself cause CLS; the box-size match from
   rule #1 is what keeps the swap itself from jumping. Reduced motion drops
   the fade to an instant show, consistent with the shimmer above. */
@keyframes ps-content-fade { from { opacity: 0; } to { opacity: 1; } }
.ps-skeleton-group, .ps-skeleton-item, .opportunity-grid, .ps-tile-grid,
.collection-grid, .collection-table, .hd-timeline-list, .hd-feed-list,
.hd-strip, .discover-set-quick-row > *, .alert-inbox-skeleton-list,
#alert-inbox-list > *, .ps-sheet-skeleton, .watch-card-hit {
  animation: ps-content-fade 150ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .ps-skeleton-group, .ps-skeleton-item, .opportunity-grid, .ps-tile-grid,
  .collection-grid, .collection-table, .hd-timeline-list, .hd-feed-list,
  .hd-strip, .discover-set-quick-row > *, .alert-inbox-skeleton-list,
  #alert-inbox-list > *, .ps-sheet-skeleton, .watch-card-hit { animation: none; }
}

/* #2: the inline "Still loading" + Retry line a module appends after 8s.
   grid-column spans the full row when the host itself is a grid (e.g.
   discover-recommended-grid), and it is otherwise an ordinary block line --
   either way it is appended AFTER the skeleton, never replacing it, so nothing
   the member was already looking at changes size. */
.ps-still-loading { grid-column: 1 / -1; margin: var(--space-3) 0 0; }
.ps-still-loading-retry { margin-left: var(--space-2); }

/* #6: one quiet shell-level line, shown for as long as any api() call is
   inside its 503/Retry-After restart loop. Sits between the header and the
   sidebar/content row so it never overlaps either. */
.shell-reconnect-banner {
  margin: 0; padding: var(--space-2) var(--space-4); text-align: center;
  background: color-mix(in srgb, var(--accent) 14%, var(--well));
  color: var(--accent-text); font: 700 var(--font-size-200)/1.4 var(--font-mono);
  text-transform: uppercase; letter-spacing: .04em;
}

/* Card-sheet body skeleton (independent of the sheet's own settled/tile
   classes -- see member.js openCardSheet for why): art beside two identity
   lines, then two stacked section blocks standing in for evidence/notes. */
.ps-sheet-skeleton { display: grid; gap: var(--space-4); padding: var(--space-5); }
.ps-sheet-skeleton-top { display: grid; grid-template-columns: 96px minmax(0,1fr); gap: var(--space-4); }
.ps-sheet-skeleton-art { width: 96px; aspect-ratio: 5 / 7; }
.ps-sheet-skeleton-lines { display: grid; gap: var(--space-2); align-content: start; }
.ps-sheet-skeleton-section { height: 96px; }

/* Overview timeline row skeleton: dot + two lines, matching .hd-timeline-row's
   own grid-template-columns (overview.css) instead of the plain text this
   replaced. */
.hd-timeline-dot--skeleton { background: var(--card1-hi); animation: none; }

.shell-update-banner__button {
  margin-left: 8px;
  font: inherit;
  font-weight: 700;
  color: inherit;
  background: transparent;
  border: 1px solid currentColor;
  border-radius: 999px;
  padding: 2px 12px;
  cursor: pointer;
}
