/* Grafy navigation drawer — the SINGLE source of truth for the sidebar's
   box, nav rail, and collapse behaviour, shared by the graph app (index.html)
   and every satellite (Social / 3D Studio / Chat / Code via appdrawer.js).
   Before this file the two had separate CSS that drifted, so the same drawer
   behaved differently per surface.

   The rail lives at the INLINE START, not on the left: Grafy ships Arabic,
   Persian and Urdu, and in those the start edge is the right one. Everything
   here that has a side is therefore written logically (`inset-inline-start`,
   `border-inline-end`, `margin-inline-start`, `text-align: start`) so one
   stylesheet lays the drawer out correctly in both directions. Issue #63 was
   what happens when it is not: the whole page mirrored around a rail that
   stayed pinned to the left, with the menu button on top of the tab strip.

   Two things make one stylesheet serve both:
   1. Colours come from `--drawer-*` tokens that fall back to the graph app's
      own tokens. The app defines none, so it keeps its exact look; a satellite
      (which has no --text/--accent of its own) sets the tokens to a fixed dark
      palette on the drawer root.
   2. Collapse/expand state lives on an ANCESTOR class (`drawer-collapsed`,
      `graph-open`, `more-open`) rather than a specific element, so it matches
      whether that class sits on the app's `.shell` or a satellite's <html>.

   Page LAYOUT (how the app's grid or a satellite's body makes room for the
   drawer, and the app's narrow-screen overlay) stays with each host — only the
   drawer's own chrome is shared here. */

.appdrawer {
  --d-fg: var(--drawer-fg, var(--text, #f4f2ed));
  --d-muted: var(--drawer-muted, var(--muted, #979ba4));
  --d-accent: var(--drawer-accent, var(--accent, #aeb7c2));
  --d-line: var(--drawer-line, var(--line, #2b313e));
  --d-line-soft: var(--drawer-line-soft, var(--line-soft, #1f2430));
  --d-raised: var(--drawer-raised, var(--surface-raised, #1b1f2a));
  --d-bg: var(--drawer-bg, color-mix(in srgb, var(--bg, #0b0e15) 90%, #000 6%));
  /* The padlock on a row this plan does not include. Red on purpose: it used
     to be --d-muted, which on a row already dimmed to .5 opacity was a grey
     speck on a grey icon that nobody read as "locked". */
  --d-lock: var(--drawer-lock, var(--danger, #ff6b6b));

  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100vh;
  max-height: 100vh;
  border-inline-end: 1px solid var(--d-line-soft);
  background: var(--d-bg);
  color: var(--d-fg);
  overflow: hidden;
  /* Own the font so the drawer reads identically on every surface. Without this
     it inherited each host's font (the app's Inter vs a satellite's fallback),
     which is why the nav text looked a different size when you switched pages. */
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 14px;
  line-height: 1.4;
}

.appdrawer-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 14px 14px 8px;
}
.appdrawer-brand { display: flex; align-items: baseline; gap: 7px; text-decoration: none; color: var(--d-fg); }
/* The row is baseline-aligned so "Grafy" and the surface name sit on one
   line. The logo has no text baseline of its own, so an empty box would be
   aligned by its bottom edge and ride high above the wordmark -- centre it
   explicitly. Both class names are listed because the graph app writes the
   mark as .brand-mark and the injected drawer as .gd-mark; matching only one
   is why /app's wordmark sat lower than every other surface's. */
.appdrawer-brand .gd-mark,
.appdrawer-brand .brand-mark { align-self: center; }

/* A surface whose own top bar carries a "Grafy <app>" wordmark shows it
   twice while the rail is expanded, because the rail says exactly the same
   thing three centimetres to the left. The page's copy stands down and
   returns when the rail collapses to icons, where the surface name is no
   longer readable. Tag the page's own wordmark with .grafy-page-brand. */
html.gd-on:not(.drawer-collapsed) .grafy-page-brand { display: none; }
.appdrawer-brand-name { font-size: 16px; font-weight: 750; }
/* "Grafy <app>" — the surface name smaller and dimmer beside the wordmark. */
.appdrawer-brand-app { font-size: 12.5px; font-weight: 600; color: var(--d-muted); }
.appdrawer-brand-app:empty { display: none; }

.appdrawer-collapse {
  width: 30px; height: 30px; display: grid; place-items: center; padding: 0;
  border: 1px solid transparent; border-radius: 8px; background: transparent;
  color: var(--d-muted); cursor: pointer;
}
.appdrawer-collapse svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.7; }
.appdrawer-collapse:hover { border-color: var(--d-line); color: var(--d-fg); }

.appdrawer-nav { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 10px; display: flex; flex-direction: column; gap: 2px; }
.appdrawer-bottom { padding: 8px 10px 12px; border-top: 1px solid var(--d-line-soft); display: flex; flex-direction: column; gap: 2px; }

.dnav {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 11px; border: 1px solid transparent; border-radius: 9px;
  background: transparent; color: var(--d-fg); cursor: pointer;
  font-size: 13.5px; font-weight: 600; text-align: start; text-decoration: none;
  transition: gap .2s ease, padding .2s ease, background-color .16s ease, border-color .16s ease;
}
.dnav:hover { background: var(--d-raised); border-color: var(--d-line); }
/* Graph Chat used to be tinted with --d-accent to mark it as the primary app.
   That token (#aeb7c2) is dimmer than --d-fg (#f4f2ed), so the "highlight" read
   as disabled next to every sibling. It now matches them; which surface you are
   on is carried by .gd-active's raised background instead. */
.dnav-accent { color: var(--d-fg); }
.dnav-hidden { display: none; }

/* Apps the signed-in plan does not include (see lockDrawerApps in app.js and
   lockApps in appdrawer.js). The row keeps its icon and label — knowing Grafy
   HAS a Film Studio is why it stays visible — and it still opens the app: the
   page is served read-only (a transparent layer over it, see main._app_locked)
   rather than refused, so you can see what you would be buying. The little
   pill on the right is the "off" switch: knob parked at the left, nothing lit.
   The row is dimmed, but its padlock is red so the reason reads at a glance. */
.dnav-locked { opacity: .62; }
.dnav-locked:hover { opacity: .85; }
.dnav-switch {
  flex: none; margin-inline-start: auto; width: 24px; height: 13px; border-radius: 7px;
  border: 1px solid var(--d-line2, var(--d-line)); background: var(--d-raised); position: relative;
}
.dnav-switch::after {
  content: ""; position: absolute; top: 1px; inset-inline-start: 1.5px; width: 9px; height: 9px;
  border-radius: 50%; background: var(--d-muted);
}
/* No room for the switch on a 60px icon rail, so a padlock badge rides the
   icon instead — that one stays legible at every width. */
.drawer-collapsed .dnav-switch { display: none; }
.dnav-locked .dnav-ic { position: relative; }
.dnav-lock-badge {
  position: absolute; inset-inline-end: -5px; bottom: -4px; width: 11px; height: 11px;
  color: var(--d-lock); background: var(--d-bg); border-radius: 3px;
}
.dnav-lock-badge svg { width: 11px; height: 11px; display: block; stroke-width: 2.6; }

.dnav-ic {
  width: 18px; height: 18px; display: inline-grid; place-items: center; flex: none;
  opacity: .85; font-size: 14px;
  transition: width .2s ease, height .2s ease, font-size .2s ease, transform .2s ease;
}
.dnav-ic svg {
  width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
  transition: width .2s ease, height .2s ease;
}
.appdrawer-bottom .dnav-ic { color: var(--d-accent); opacity: 1; }
.dnav-accent .dnav-ic { color: inherit; opacity: .85; }

.dnav-tx { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dnav-group { padding: 12px 11px 4px; font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--d-muted); }
.dnav-group-list { display: none; flex-direction: column; gap: 2px; padding-inline-start: 12px; }
.graph-open .dnav-group-list { display: flex; }
.dnav-group-list .dnav { padding-top: 7px; padding-bottom: 7px; font-size: 12.5px; }
.dmore { display: none; flex-direction: column; gap: 2px; }
.more-open .dmore { display: flex; }
.dnav-caret { margin-inline-start: auto; opacity: .7; transition: transform .18s; font-size: 12px; }
.graph-open .dnav-group-toggle .dnav-caret { transform: rotate(180deg); }
.more-open .dnav-more .dnav-caret { transform: rotate(180deg); }

/* --- App switcher ---------------------------------------------------------
   Grafy is eighteen surfaces, and a rail that listed every one of them was a
   scroll rather than a menu. The 3x3 button in the drawer header opens the
   whole catalogue as a grid (appswitcher.js) and the rail keeps what you pin.
   Nothing is unpinned by default, so a rail nobody has curated is the rail it
   always was.

   The row is HIDDEN, not removed: its href still works, lockApps still sees
   it, and pinning it back costs a class. Specificity is deliberate — it has to
   beat `.more-open .dmore` and `.graph-open .dnav-group-list`, which set
   `display:flex` on the containers this also hides. */
.appdrawer .dnav.dnav-unpinned,
.appdrawer .dnav-unpinned { display: none; }

.gapps-btn {
  width: 30px; height: 30px; display: grid; place-items: center; padding: 0; flex: none;
  border: 1px solid transparent; border-radius: 8px; background: transparent;
  color: var(--d-muted); cursor: pointer;
}
.gapps-btn svg { width: 18px; height: 18px; fill: currentColor; stroke: none; }
.gapps-btn:hover { border-color: var(--d-line); color: var(--d-fg); }
/* The one header control that survives the 60px rail: it is how you get the
   other apps back, so it cannot be the thing collapsing hides. */
.drawer-collapsed .gapps-btn { display: grid; }

/* The modal lives on <body>, outside .appdrawer, so it cannot inherit the
   --d-* tokens and declares its own against the same fallbacks. */
.gapps-backdrop {
  --g-fg: var(--drawer-fg, var(--text, #f4f2ed));
  --g-muted: var(--drawer-muted, var(--muted, #979ba4));
  --g-line: var(--drawer-line, var(--line, #2b313e));
  --g-raised: var(--drawer-raised, var(--surface-raised, #1b1f2a));
  --g-panel: var(--drawer-bg, var(--surface, #12151d));
  --g-lock: var(--drawer-lock, var(--danger, #ff6b6b));
  display: none; position: fixed; inset: 0; z-index: 9300;
  background: rgba(0, 0, 0, .55);
  padding: 24px 16px;
  overflow: auto;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.gapps-backdrop.open { display: flex; align-items: flex-start; justify-content: center; }
.gapps-dialog {
  width: min(680px, 100%); margin-top: 6vh; padding: 18px 18px 20px;
  border: 1px solid var(--g-line); border-radius: 18px;
  background: var(--g-panel); color: var(--g-fg);
  box-shadow: 0 30px 90px rgba(0, 0, 0, .5);
}
.gapps-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.gapps-title { margin: 0; font-size: 17px; font-weight: 750; }
.gapps-close {
  width: 32px; height: 32px; display: grid; place-items: center; padding: 0; flex: none;
  border: 1px solid transparent; border-radius: 9px; background: transparent;
  color: var(--g-muted); cursor: pointer;
}
.gapps-close svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.gapps-close:hover { border-color: var(--g-line); color: var(--g-fg); }
.gapps-hint { margin: 6px 0 14px; font-size: 12.5px; color: var(--g-muted); }
.gapps-grid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
}
.gapps-tile {
  position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 8px; padding: 14px 8px 12px; border: 1px solid var(--g-line);
  border-radius: 14px; background: transparent; color: var(--g-fg);
  font: 600 12px/1.3 inherit; text-align: center; cursor: pointer;
  opacity: .52;
}
.gapps-tile:hover { background: var(--g-raised); }
/* Pinned reads as "on the rail": full strength, and a dot in the corner. */
.gapps-tile.gapps-on { opacity: 1; }
.gapps-mark {
  position: absolute; top: 8px; inset-inline-end: 8px; width: 8px; height: 8px;
  border-radius: 50%; border: 1px solid var(--g-line); background: transparent;
}
.gapps-tile.gapps-on .gapps-mark { border-color: transparent; background: var(--g-fg); }
.gapps-ic { position: relative; width: 26px; height: 26px; display: grid; place-items: center; }
.gapps-ic svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.gapps-ic .dnav-lock-badge { inset-inline-end: -6px; bottom: -5px; color: var(--g-lock); background: var(--g-panel); }
.gapps-tx { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gapps-open {
  position: absolute; inset-inline-start: 8px; top: 8px; width: 20px; height: 20px;
  display: grid; place-items: center; border-radius: 6px;
  color: var(--g-muted); opacity: 0; transition: opacity .14s ease;
}
.gapps-open svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.gapps-tile:hover .gapps-open, .gapps-open:focus-visible { opacity: 1; }
.gapps-open:hover { color: var(--g-fg); background: var(--g-raised); }

/* --- Collapsed icon rail --------------------------------------------------
   Labels, carets, and group headers fall away; each icon grows so it stays
   legible as the whole nav item, and rows centre. The caret hides because a
   group toggle only means something beside its label — and its margin-inline-start:auto
   would otherwise claim the row's free space and push that icon off-centre. */
.drawer-collapsed .appdrawer-brand-name,
.drawer-collapsed .appdrawer-brand-app,
.drawer-collapsed .dnav-tx,
.drawer-collapsed .dnav-caret,
.drawer-collapsed .dnav-group,
.drawer-collapsed .dnav-group-list,
.drawer-collapsed .dmore,
.drawer-collapsed .dnav-more,
.drawer-collapsed .appdrawer-social,
.drawer-collapsed .appdrawer-legal { display: none; }
.drawer-collapsed .appdrawer-top { flex-direction: column; justify-content: center; gap: 8px; }
.drawer-collapsed .appdrawer-collapse { display: none; }
.drawer-collapsed:not(.drawer-overlay) .dnav { justify-content: center; padding: 8px 4px; }
.drawer-collapsed:not(.drawer-overlay) .dnav-ic { width: 24px; height: 24px; font-size: 20px; }
.drawer-collapsed:not(.drawer-overlay) .dnav-ic svg { width: 24px; height: 24px; }

.appdrawer-expand { display: none; }
.drawer-collapsed .appdrawer-expand {
  display: grid; place-items: center; width: 34px; height: 34px; padding: 0;
  border: 0; border-radius: 8px; background: transparent; color: var(--d-muted); cursor: pointer;
}
.appdrawer-expand svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.drawer-collapsed .appdrawer-expand:hover { color: var(--d-fg); }

.appdrawer-social { display: flex; gap: 6px; padding: 8px 1px 2px; }
.appdrawer-social a {
  flex: 1; text-align: center; padding: 6px 0; border: 1px solid var(--d-line);
  border-radius: 8px; color: var(--d-muted); text-decoration: none; font-size: 11px; font-weight: 700;
}
.appdrawer-social a:hover { border-color: var(--d-accent); color: var(--d-fg); }
.appdrawer-social a svg { width: 17px; height: 17px; fill: currentColor; }

/* --- Tiny legal links -----------------------------------------------------
   A footnote row under the social icons. Sizing is min(cap, container units):
   the pair shrinks with a narrow drawer so the two labels keep sharing one
   line, but stops growing at the cap so a wide panel doesn't turn footnotes
   into another nav row. */
.appdrawer-legal {
  container-type: inline-size;
  display: flex; align-items: baseline; justify-content: center;
  gap: min(6px, 2.4cqi); padding: 6px 1px 2px;
}
.appdrawer-legal a, .appdrawer-legal .appdrawer-legal-sep {
  font-size: min(10px, 4.8cqi); line-height: 1.4;
}
.appdrawer-legal a {
  min-width: 0; color: var(--d-muted); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.appdrawer-legal a:hover { color: var(--d-fg); text-decoration: underline; }
.appdrawer-legal .appdrawer-legal-sep { color: var(--d-line); flex: none; }

/* --- Session rows ---------------------------------------------------------
   Shared by /app, /chat, and /creator. Each host supplies its own thumbnail,
   metadata, and colour variables, but the interaction stays identical:
   compact/ellipsized at rest; raised, fully wrapped, and action-enabled while
   hovered or keyboard-focused. The expanded row is absolutely positioned
   inside a fixed-height slot so it overlays its neighbours instead of moving
   the whole list. */
.grafy-session-list {
  --grafy-session-row-height: 44px;
  --grafy-session-row-gap: 4px;
  --grafy-session-row-bg: var(--panel, var(--bg-elev, #13161f));
  --grafy-session-row-hover-bg: var(--panel-2, var(--bg-elev-2, #191d28));
  --grafy-session-row-border: var(--border, var(--line, #2b313e));
  --grafy-session-row-hover-border: var(--accent, #aeb7c2);
  --grafy-session-row-text: var(--text, #f4f2ed);
  --grafy-session-row-muted: var(--muted, var(--text-dim, #979ba4));
  --grafy-session-actions-width: 106px;
  isolation: isolate;
}

.grafy-session-slot {
  position: relative;
  min-width: 0;
  min-height: var(--grafy-session-row-height);
  margin-bottom: var(--grafy-session-row-gap);
}

.grafy-session-slot:has(.grafy-session-row:hover),
.grafy-session-slot:has(.grafy-session-row:focus-within) {
  z-index: 30;
}

.grafy-session-row {
  position: absolute;
  z-index: 0;
  top: 0;
  right: 0;
  left: 0;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  min-width: 0;
  min-height: var(--grafy-session-row-height);
  overflow: hidden;
  border: 1px solid transparent;
  border-radius: 11px;
  color: var(--grafy-session-row-text);
  background: var(--grafy-session-row-bg);
  transition:
    background-color .14s ease,
    border-color .14s ease,
    box-shadow .14s ease;
}

.grafy-session-row:hover,
.grafy-session-row:focus-within {
  z-index: 30;
  overflow: visible;
  border-color: var(--grafy-session-row-hover-border);
  background: var(--grafy-session-row-hover-bg);
  box-shadow: 0 14px 28px rgba(0, 0, 0, .34);
}

/* Near the bottom of a scroller, grow upward so the last long title does not
   get clipped below the panel. A one-row list stays top-anchored. */
.grafy-session-slot:nth-last-child(-n+2):not(:first-child)
  > .grafy-session-row:is(:hover, :focus-within) {
  top: auto;
  bottom: 0;
}

.grafy-session-content {
  box-sizing: border-box;
  flex: 1 1 auto;
  min-width: 0;
}

.grafy-session-title {
  display: block;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  color: inherit;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.grafy-session-row:is(:hover, :focus-within) .grafy-session-title {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.grafy-session-row:is(:hover, :focus-within):has(.grafy-session-actions)
  .grafy-session-content {
  padding-inline-end: var(--grafy-session-actions-width);
}

.grafy-session-actions {
  position: absolute;
  z-index: 2;
  top: 6px;
  inset-inline-end: 6px;
  display: flex;
  align-items: center;
  gap: 2px;
  box-sizing: border-box;
  min-width: calc(var(--grafy-session-actions-width) - 12px);
  padding: 2px;
  border: 1px solid color-mix(in srgb, var(--grafy-session-row-border) 75%, transparent);
  border-radius: 9px;
  background: color-mix(in srgb, var(--grafy-session-row-hover-bg) 94%, transparent);
  box-shadow: -12px 0 18px var(--grafy-session-row-hover-bg);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-2px);
  transition: opacity .12s ease, transform .12s ease, visibility 0s linear .12s;
}

.grafy-session-row:hover .grafy-session-actions,
.grafy-session-row:focus-within .grafy-session-actions {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
  transition-delay: 0s;
}

.grafy-session-action {
  flex: 0 0 28px;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  color: var(--grafy-session-row-muted);
  background: transparent;
  cursor: pointer;
}

.grafy-session-action:hover,
.grafy-session-action:focus-visible {
  outline: none;
  color: var(--grafy-session-row-text);
  background: color-mix(in srgb, var(--grafy-session-row-border) 72%, transparent);
}

.grafy-session-action[data-action="delete"]:hover,
.grafy-session-action[data-action="delete"]:focus-visible {
  color: var(--danger, #ff6b6b);
  background: color-mix(in srgb, var(--danger, #ff6b6b) 12%, transparent);
}

.grafy-session-action:disabled {
  cursor: wait;
  opacity: .48;
}

.grafy-session-action svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.grafy-session-rename {
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--grafy-session-row-hover-border);
  border-radius: 7px;
  outline: none;
  color: var(--grafy-session-row-text);
  background: var(--grafy-session-row-bg);
  font: inherit;
}

@media (prefers-reduced-motion: reduce) {
  .grafy-session-row,
  .grafy-session-actions { transition: none; }
}

/* NO cross-document view transitions here, and this is deliberate.

   They were tried across three attempts and every version made the thing they
   were meant to fix worse, ending in a measurement that settled it: clicking
   between surfaces with `@view-transition { navigation: auto }` painted a
   COMPLETELY BLANK frame on roughly half of all navigations. Screencasting
   every compositor frame of the same hop, six runs each:

       navigation: auto   ->  3 of 6 hops had a fully blank frame
       navigation: none   ->  0 of 6, and a third of the frames overall

   That blank frame is the "it looks like the whole page refreshed" report.
   The cause is structural: a cross-document transition suspends rendering to
   snapshot the old document and wait for the new one, which DEFEATS Chrome's
   paint holding. Paint holding is what we actually want here -- it keeps the
   previous page fully on screen until the next one can paint, so the rail
   appears untouched and only the content area changes. A view transition
   replaces that guarantee with a window in which nothing is painted at all.

   So the smooth hop comes from: paint holding (free, as long as we do not
   opt out of it), the prerender in index.html / appdrawer.js, and the
   server-rendered rail that makes the incoming page's first paint already
   correct. No transition API is involved, and none should be re-added
   without re-running that frame capture -- `navigation: auto` alone is
   enough to bring the blank frame back. */

/* Navigation feedback — the fallback for what prerender + view transitions
   don't cover (Firefox, cold first visits): the clicked rail item presses
   immediately, and if the next page still hasn't taken over after ~300ms a
   thin indeterminate bar runs along the top of the viewport. Wired twice, as
   ever: app.js for the graph app's static rail, appdrawer.js for the
   satellites — both share these styles. Fast (prerendered) hops never see
   the bar; only genuinely slow ones stop feeling broken. */
.dnav.gd-nav-pending { background: var(--d-raised); border-color: var(--d-line); }
.dnav.gd-nav-pending .dnav-ic { animation: gd-nav-pulse 0.9s ease-in-out infinite; }
@keyframes gd-nav-pulse { 50% { opacity: 0.3; } }

#gdNavProgress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 9999;
  overflow: hidden;
  pointer-events: none;
  background: transparent;
}
#gdNavProgress::before {
  content: "";
  display: block;
  height: 100%;
  width: 40%;
  border-radius: 2px;
  background: var(--accent, #aeb7c2);
  animation: gd-nav-progress 1.1s ease-in-out infinite;
}
@keyframes gd-nav-progress {
  from { transform: translateX(-100%); }
  to { transform: translateX(350%); }
}
/* A transform is geometric, not logical, so the bar sweeps the same way in
   both directions unless it is told otherwise — and a progress indicator
   running against the reading direction reads as "going backwards". */
[dir="rtl"] #gdNavProgress::before { animation-name: gd-nav-progress-rtl; }
@keyframes gd-nav-progress-rtl {
  from { transform: translateX(100%); }
  to { transform: translateX(-350%); }
}
@media (prefers-reduced-motion: reduce) {
  .dnav.gd-nav-pending .dnav-ic { animation: none; opacity: 0.5; }
  #gdNavProgress::before { animation: none; width: 100%; opacity: 0.5; }
}
