/* Shared "floating nav" component: a fixed pill bar of jump/tab links, used
   on the projects page (Pinned/Popular/Gists) and the Discord reference
   pages (Formats/Limits' section chips). One component so all three read as
   the same pattern instead of three near-identical bespoke implementations.

   Structure (see components/floating_nav.html.jinja2):
     <nav class="center-container floating-nav floating-nav--{top|bottom}">
       <div class="floating-nav__bar">
         <a class="floating-nav__header">...</a>          [optional]
         <div class="floating-nav__primary">
           <a class="floating-nav__link">...</a>*
           ...caller()-injected extras (e.g. a divider + control button)
         </div>
         <div class="floating-nav__extra" hidden>...</div>  [optional, JS-filled]
       </div>
     </nav>

   `position: fixed` rather than `sticky` on purpose: `.site-shell` (and
   similar page shells) lay out their rows as CSS Grid items, and a sticky
   element inside a grid item only stays stuck for the height of its own row
   track - it unsticks and scrolls away almost immediately. Fixed positioning
   takes it out of grid flow entirely and anchors it to the viewport. */

.floating-nav {
  position: fixed;
  inset-inline: 0;
  z-index: 40;
  padding-inline: var(--space-4);
  pointer-events: none;
}

.floating-nav--top {
  top: calc(var(--navbar-height, 72px) + var(--space-4));
}

.floating-nav--bottom {
  bottom: max(var(--space-4), env(safe-area-inset-bottom));
}

/* When the site navbar is permanently docked at the bottom of the viewport
   (sticky position "bottom") it occupies the same fixed-position real
   estate this bar rests in by default - stack this bar above it instead of
   letting the two overlap. --navbar-live-h is the navbar's real current
   height, kept in sync by a ResizeObserver in navbar.ts. */
html[data-navbar-sticky="on"][data-navbar-position="bottom"] .floating-nav--bottom {
  bottom: calc(var(--navbar-live-h, 0px) + var(--space-4) + max(var(--space-4), env(safe-area-inset-bottom)));
}

/* "bottom-on-scroll" only docks at the bottom once scrolled - continuously,
   in step with --nav-p (0 at rest, 1 once fully compacted/docked; also
   published on <html>, not just .navbar, by navbar.ts's scroll handler for
   exactly this kind of sibling read). Scaling the offset by --nav-p instead
   of applying it flatly means this bar only moves out of the way as much as
   the navbar has actually encroached on its space - at rest (--nav-p: 0,
   navbar still at the top) it's a no-op, exactly matching the navbar's own
   `top:` formula in navbar.css. */
html[data-navbar-sticky="on"][data-navbar-position="bottom-on-scroll"] .floating-nav--bottom {
  bottom: calc(
    max(var(--space-4), env(safe-area-inset-bottom)) +
    (var(--navbar-live-h, 0px) + var(--space-4)) * var(--nav-p, 0)
  );
}

/* ── Bottom-bar clearance ──
   A fixed bottom bar is out of flow, so it sits *over* whatever the page ends
   with (the projects page's gists paginator was landing underneath it). The
   component reserves that room itself rather than relying on each consuming
   page to remember bottom padding: any page whose body contains a bottom bar
   gets the room reserved on <body>.

   Written as `body:has(...)` so it follows the component wherever it is used -
   add a bottom bar to a new page and the clearance comes with it. */
/* Applied to <body> rather than the page's shell on purpose. A shell often
   sets its own `padding-block` (e.g. .profile-shell), which would overwrite a
   `padding-bottom` set here from a stylesheet that loads earlier; scrolling
   room is a property of the document anyway, not of one shell element.

   --floating-nav-bar-height is written onto <body> at runtime by navbar.ts,
   which measures the bar. It cannot be computed in CSS: the bar's height
   depends on whether it has a header row, how its pills wrap, and the current
   font - an earlier hardcoded guess (62px) was off by nearly half against a
   real two-row bar (113px). The fallback only has to cover the first paint. */
body:has(.floating-nav--bottom) {
  padding-bottom: calc(var(--floating-nav-bar-height, 120px) + var(--space-8) + env(safe-area-inset-bottom));
}

/* Stack on top of (not replace) the reserve above - the floating-nav bar's
   own clearance, plus room for the site navbar now sitting below it too,
   when the navbar is permanently docked at the bottom (see the `bottom:`
   offset rule above). */
html[data-navbar-sticky="on"][data-navbar-position="bottom"] body:has(.floating-nav--bottom) {
  padding-bottom: calc(
    var(--floating-nav-bar-height, 120px) + var(--space-8) + env(safe-area-inset-bottom) +
    var(--navbar-live-h, 0px) + var(--space-4)
  );
}

/* Same stacking, scaled by --nav-p (0 at rest, 1 once fully docked) to
   match the "bottom-on-scroll" .floating-nav--bottom `bottom:` offset rule
   above - the extra room this bar needs only grows as the navbar actually
   encroaches on it while scrolling. */
html[data-navbar-sticky="on"][data-navbar-position="bottom-on-scroll"] body:has(.floating-nav--bottom) {
  padding-bottom: calc(
    var(--floating-nav-bar-height, 120px) + var(--space-8) + env(safe-area-inset-bottom) +
    (var(--navbar-live-h, 0px) + var(--space-4)) * var(--nav-p, 0)
  );
}

/* The 'inline' variant: the same pill bar, but laid out as an ordinary row of
   the page (a section header above the content) instead of floating over it.
   Used by the logged-in Discord account tabbar.

   Deliberately `position: static`, not sticky. Unlike the single opaque element
   this replaced, the inline bar is a transparent <nav> wrapping an opaque pill
   plus a bare action cluster, so a sticky version does not hide what scrolls
   beneath it - cards showed through the gaps around the buttons. Staying in
   flow means it can never overlap anything. */
.floating-nav--inline {
  position: static;
  inset-inline: auto;
  padding-inline: 0;
  pointer-events: auto;
  width: 100%;
}

/* Inline bars span the content width rather than hugging their links. Padding
   and radius now come from the base .floating-nav__bar rule. */
.floating-nav--inline .floating-nav__bar {
  align-self: stretch;
  min-width: 0;
}

/* One row inside the bar: the scrolling link list, then the action cluster.
   Both live on the bar's own opaque surface, so nothing can show through. */
.floating-nav__row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
  /* Never wider than the bar's content box - otherwise the trailing actions
     are pushed out past the rounded edge instead of the links scrolling. */
  max-width: 100%;
}

.floating-nav__primary-wrap {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
}

.floating-nav__row .floating-nav__primary {
  width: 100%;
}

/* Mobile-only directional affordances. Their visibility follows the same
   overflow classes as the edge mask, so they disappear at the corresponding
   end instead of suggesting movement that is no longer possible. */
.floating-nav__scroll-hint {
  display: none;
}

/* Trailing action cluster (e.g. Refresh / Log out). `flex: none` so the links
   scroll rather than the buttons being squeezed. */
.floating-nav__actions {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--space-3);
}

/* Buttons in the action cluster keep the pill language of the links they sit
   beside, and stay on one line so they can't grow the bar's height. */
.floating-nav__actions > * {
  flex: none;
  white-space: nowrap;
}

.floating-nav__bar {
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  align-self: center;
  gap: var(--space-2);
  max-width: 100%;
  /* Padding has to clear the corner arc, not just sit inside the border box.
     With a 50px radius and only 8px of padding, the first and last pills in a
     row were visibly clipped by the rounded corners. --radius-lg (32px) with
     roomier padding keeps the soft shape while leaving the arc clear. */
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg);
  border: 1px solid color-mix(in srgb, var(--primary-color) 22%, transparent);
  /* Deliberately more opaque than the shared `--site-surface-background`
     token: this bar sits fixed over arbitrary scrolling content (including,
     at times, a card's own large heading), and the usual ~30-48% alpha
     glass surface let that content show through underneath it. */
  background:
    linear-gradient(180deg, color-mix(in srgb, white 8%, transparent), transparent 60%),
    color-mix(in srgb, var(--background-color) 92%, transparent);
  box-shadow: var(--ap-panel-shadow, var(--site-surface-shadow));
  backdrop-filter: blur(22px) saturate(1.3);
  -webkit-backdrop-filter: blur(22px) saturate(1.3);
}

/* Backdrop blur forces the compositor to re-rasterize the blurred region on
   effectively every scroll frame while this fixed bar is on screen - the
   dominant cost of scrolling on content-heavy pages (see navbar.ts's
   `is-scrolling-fast` scroll listener). Dropping to a plain opaque fill for
   the ~150ms a scroll is actively happening removes that cost exactly when
   it matters. The swap back once scrolling settles is NOT unnoticeable -
   this bar is fixed on screen the whole time, so an instant blur pop right
   as the eye settles is exactly the kind of flicker this file is otherwise
   trying to avoid. Easing it back in below fixes that without giving up the
   saving during the scroll itself. */
html.is-scrolling-fast .floating-nav__bar {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: var(--background-color);
}

/* Scoped to the "not scrolling" side of the toggle above so the transition
   never applies while blur is actively being dropped mid-scroll - only on the
   restore, which is the half that needs easing. */
html:not(.is-scrolling-fast) .floating-nav__bar {
  transition: backdrop-filter 220ms var(--transition-easing), -webkit-backdrop-filter 220ms var(--transition-easing),
    background 220ms var(--transition-easing);
}

.floating-nav__header {
  display: flex;
  /* Only as wide as its own copy: as a flex item in the bar's column it would
     otherwise stretch the full width and make its whole row clickable. */
  align-self: flex-start;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-2);
  color: inherit;
  text-decoration: none;
}

.floating-nav__header-icon {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: var(--radius-lg);
  color: #fff;
  background: linear-gradient(145deg, var(--accent-color), color-mix(in srgb, var(--accent-color) 70%, black));
  box-shadow: 0 8px 20px color-mix(in srgb, var(--accent-color) 30%, transparent);
}

.floating-nav__header-copy {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  line-height: 1.05;
}

.floating-nav__header-copy strong {
  font-size: 0.94rem;
  letter-spacing: -0.01em;
}

.floating-nav__header-copy small {
  padding: var(--space-1) var(--space-2);
  border: 1px solid color-mix(in srgb, var(--accent-color) 20%, transparent);
  border-radius: var(--radius-pill);
  color: var(--accent-color);
  background: color-mix(in srgb, var(--accent-color) 9%, transparent);
  font-size: 0.58rem;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.floating-nav__primary,
.floating-nav__extra {
  display: flex;
  align-items: center;
  overflow-x: auto;
  /* Explicit, not left to default: a lone `overflow-x: auto` makes browsers
     compute the unset y-axis as `auto` too (not `visible`), which clips
     anything taller than the row's own content-box height - invisible for
     text links (well under their own line-height), but cut circular items
     like the badge-hunter jump dock's icons off at the top/bottom. */
  overflow-y: visible;
  scrollbar-width: none;
}

.floating-nav__primary::-webkit-scrollbar,
.floating-nav__extra::-webkit-scrollbar {
  display: none;
}

/* Fades the pills toward transparent at whichever edge still has more to
   scroll to, as a hint that the row continues. A plain overlay would have to
   match whatever sits behind it (glass panels, gradients), so this masks the
   content itself instead, which works over any background. The classes are
   toggled from navbar.ts as the row scrolls. */
.floating-nav__primary.has-scroll-end {
  -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
}

.floating-nav__primary.has-scroll-start {
  -webkit-mask-image: linear-gradient(to left, black calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to left, black calc(100% - 28px), transparent 100%);
}

.floating-nav__primary.has-scroll-start.has-scroll-end {
  -webkit-mask-image: linear-gradient(to right, transparent, black 28px, black calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to right, transparent, black 28px, black calc(100% - 28px), transparent 100%);
}

.floating-nav__primary {
  gap: var(--space-3);
}

/* Second, smaller row - e.g. the Discord reference pages mirror the current
   section's own in-card "Jump to" sub-links here (see discord_reference.ts),
   so both nav levels stay reachable while scrolling. Populated by JS/left
   empty+hidden by default; pages that don't need it just omit it. */
.floating-nav__extra {
  gap: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid color-mix(in srgb, var(--primary-color) 16%, transparent);
}

.floating-nav__extra[hidden] {
  display: none;
}

.floating-nav__link {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 46px;
  padding: var(--space-3) var(--space-6);
  border-radius: var(--radius-pill);
  font-weight: 750;
  font-size: 0.94rem;
  line-height: 1.2;
  white-space: nowrap;
  text-align: center;
  text-decoration: none;
  color: color-mix(in srgb, var(--text-color) 82%, transparent);
  border: 1px solid transparent;
  background: transparent;
  transition:
    color var(--transition-duration) var(--transition-easing),
    border-color var(--transition-duration) var(--transition-easing),
    background var(--transition-duration) var(--transition-easing);
}

.floating-nav__link i {
  font-size: 1em;
  opacity: 0.85;
}

html:not([data-hover-effects="off"]) .floating-nav__link:hover,
.floating-nav__link:focus-visible {
  color: var(--text-color);
  border-color: color-mix(in srgb, var(--accent-color) 22%, transparent);
  background: color-mix(in srgb, var(--accent-color) 8%, transparent);
}

.floating-nav__link.is-active {
  color: var(--text-color);
  border-color: color-mix(in srgb, var(--accent-color) 36%, transparent);
  background: color-mix(in srgb, var(--accent-color) 14%, transparent);
}

/* A link may carry its own accent via --fn-accent (set inline by the macro),
   letting each tab own a colour while the rest of the component stays shared.
   These fall back to --accent-color, so links without one are unaffected. */
.floating-nav__link[style*="--fn-accent"] i {
  color: var(--fn-accent, currentColor);
}

html:not([data-hover-effects="off"]) .floating-nav__link[style*="--fn-accent"]:not(.is-active):hover,
.floating-nav__link[style*="--fn-accent"]:not(.is-active):focus-visible {
  color: var(--fn-accent, var(--text-color));
  border-color: color-mix(in srgb, var(--fn-accent, var(--accent-color)) 22%, transparent);
  background: color-mix(in srgb, var(--fn-accent, var(--accent-color)) 10%, transparent);
}

/* The active tab fills with its own accent gradient and flips to white text,
   which is what gives the Discord tabbar its per-section colour. */
.floating-nav__link[style*="--fn-accent"].is-active {
  color: #fff;
  border-color: transparent;
  background: linear-gradient(135deg, var(--fn-accent), var(--fn-accent-end, var(--fn-accent)));
  box-shadow: 0 8px 20px color-mix(in srgb, var(--fn-accent) 30%, transparent);
}

.floating-nav__link[style*="--fn-accent"].is-active i {
  color: currentColor;
}

/* ── Breadcrumb chain ──
   Drill-down pages tether crumbs onto the active tab ("Servers > Guild >
   Membership"). Crumbs are quieter than tabs, and the last one is plain text. */
.floating-nav__crumb-sep {
  flex-shrink: 0;
  font-size: 0.7rem;
  opacity: 0.45;
}

.floating-nav__link--crumb {
  font-weight: 650;
  color: color-mix(in srgb, var(--text-color) 70%, transparent);
  background: color-mix(in srgb, var(--primary-color) 7%, transparent);
}

.floating-nav__link--crumb.is-current {
  color: var(--text-color);
  border-color: color-mix(in srgb, var(--fn-accent, var(--accent-color)) 28%, transparent);
  background: color-mix(in srgb, var(--fn-accent, var(--accent-color)) 12%, transparent);
  cursor: default;
}

.floating-nav__extra-link {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  padding: 6px var(--space-4);
  border-radius: var(--radius-pill);
  font-size: 0.8rem;
  font-weight: 650;
  white-space: nowrap;
  text-decoration: none;
  color: color-mix(in srgb, var(--text-color) 70%, transparent);
  border: 1px solid transparent;
  background: color-mix(in srgb, var(--primary-color) 8%, transparent);
  transition:
    color var(--transition-duration) var(--transition-easing),
    border-color var(--transition-duration) var(--transition-easing),
    background var(--transition-duration) var(--transition-easing);
}

html:not([data-hover-effects="off"]) .floating-nav__extra-link:hover,
.floating-nav__extra-link:focus-visible {
  color: var(--text-color);
  border-color: color-mix(in srgb, var(--accent-color) 24%, transparent);
  background: color-mix(in srgb, var(--accent-color) 10%, transparent);
}

/* A vertical rule + icon-only button, injected via the macro's `caller()`
   block (e.g. the projects page's grid/row/list layout switch) - not part
   of the link list itself, so it needs its own spacing/sizing rather than
   inheriting `.floating-nav__link`'s pill treatment. */
.floating-nav__divider {
  align-self: stretch;
  width: 1px;
  margin-block: var(--space-2);
  background: color-mix(in srgb, var(--primary-color) 22%, transparent);
}

.floating-nav__control {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 38px;
  min-height: 38px;
  border-radius: var(--radius-pill);
  color: color-mix(in srgb, var(--text-color) 72%, transparent);
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  font: inherit;
  transition:
    color var(--transition-duration) var(--transition-easing),
    border-color var(--transition-duration) var(--transition-easing),
    background var(--transition-duration) var(--transition-easing);
}

html:not([data-hover-effects="off"]) .floating-nav__control:hover,
.floating-nav__control:focus-visible {
  color: var(--text-color);
  border-color: color-mix(in srgb, var(--accent-color) 22%, transparent);
  background: color-mix(in srgb, var(--accent-color) 8%, transparent);
}

@media (max-width: 700px) {
  .floating-nav {
    padding-inline: var(--space-3);
  }

  .floating-nav__bar {
    gap: var(--space-1);
    padding: var(--space-2);
    box-shadow:
      var(--ap-panel-shadow, var(--site-surface-shadow)),
      0 18px 40px rgba(0, 0, 0, 0.28);
  }

  .floating-nav__header-copy small {
    display: none;
  }

  .floating-nav__link {
    min-height: 40px;
    font-size: 0.86rem;
    padding: var(--space-2) var(--space-5);
  }

  .floating-nav__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-2);
    width: 100%;
  }

  .floating-nav__primary-wrap {
    /* Only clip horizontally - this wrap is what the scroll-hint chevrons
       are absolutely positioned inside, so it needs *some* overflow control
       for their placement, but `overflow: hidden` clips vertically too,
       cutting off anything taller than the row (e.g. the badge-hunter jump
       dock's circular icons). */
    overflow-x: hidden;
    overflow-y: visible;
  }

  .floating-nav__scroll-hint {
    position: absolute;
    top: 50%;
    z-index: 2;
    display: grid;
    place-items: center;
    width: 24px;
    height: 30px;
    border-radius: var(--radius-pill);
    color: var(--text-color);
    background: color-mix(in srgb, var(--background-color) 88%, transparent);
    box-shadow: 0 2px 10px color-mix(in srgb, var(--text-color) 18%, transparent);
    opacity: 0;
    pointer-events: none;
    transform: translateY(-50%);
    transition: opacity 140ms ease;
  }

  .floating-nav__scroll-hint--start { left: 2px; }
  .floating-nav__scroll-hint--end { right: 2px; }

  .floating-nav__primary.has-scroll-start ~ .floating-nav__scroll-hint--start,
  .floating-nav__primary.has-scroll-end ~ .floating-nav__scroll-hint--end {
    opacity: .9;
  }


  .floating-nav__extra-link {
    padding: 5px var(--space-3);
    font-size: 0.76rem;
  }

  .floating-nav__control {
    width: 34px;
    min-height: 34px;
  }

  .floating-nav__actions {
    min-width: 0;
    justify-content: flex-end;
    gap: 6px;
  }

  /* Action implementations may come from a consuming page and carry roomy
     desktop padding. Normalize them inside the component on narrow screens so
     the action cluster always remains within the rounded bar. */
  .floating-nav__actions > * {
    box-sizing: border-box;
    display: inline-grid;
    place-items: center;
    width: 42px;
    min-width: 42px;
    height: 42px;
    min-height: 42px;
    padding: 0;
    gap: 0;
    border-radius: 14px;
  }

  /* Icon-only actions on narrow screens - the icons carry the meaning. */
  .floating-nav__action-label {
    display: none;
  }
}
