/* ============================================================================
   ASTROVE — THE INDEX
   ----------------------------------------------------------------------------
   The global navigation overlay, and the ONE place it is styled. It used to
   live inside landing.css, which only the homepage loads, so a reader who
   followed the index onto a product page could not open it again: the shared
   masthead's INDEX link scrolled to the footer sitemap instead. The overlay is
   now generated into every static page from content/navigation.js, driven by
   js/site-index.js, and dressed from here -- one stylesheet, one behaviour, one
   route manifest, on the homepage and on every generated document alike.

   The bag stays in landing.css. The two used to share their panel and header
   base rules; they no longer do, because the bag belongs to the homepage and
   this does not. The declarations below are the menu's half of that split,
   unchanged.

   Nothing about the approved visual direction changed in the move: the spectral
   rail, the group nodes, the 01/04 numerals, the SKU hover colours and the
   atmosphere are exactly as they were.
   ========================================================================== */
/* THE SCROLL LOCK, AND WHY IT IS NOT `overflow: hidden`.

   Measured, because the obvious rule loses the reader's place. landing.css
   locks the homepage with `body.is-locked { overflow: hidden }`; a generated
   page inherits static-shell.css instead, where html and body are both
   `overflow: visible`, so hiding body's overflow PROPAGATES TO THE VIEWPORT and
   the offset collapses. Hiding the ROOT's overflow does the same thing more
   directly. Both were measured on a product page at scrollY 900: 0 behind the
   scrim, 0 after closing -- the reader returned to the top of a page they were
   halfway down.

   So the page is not un-scrolled, it is pinned where it stands. body goes
   fixed at minus its own scroll offset, which looks identical to the reader and
   cannot be scrolled behind the scrim; js/site-index.js sets that offset and
   puts it back on close. The one sticky element on any of these pages is the
   About portrait, which has nothing to do while the index is open. */
html.is-locked,
html.is-locked body { overflow: hidden; }
html.is-locked body {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
}

/* THE MASTHEAD STAYS WHERE THE READER LEFT IT. On a generated page it is
   sticky, so it lives inside the body that the lock pins at minus its scroll
   offset -- and went with it: measured at -2532px while the index was open, so
   the bar vanished behind the scrim and snapped back on close. While locked it
   is held to the top of the viewport instead, and body gives back the exact
   height it vacated so nothing behind the scrim moves by a pixel. Scoped to
   html.is-locked, which only js/site-index.js sets; the homepage locks body
   through landing.js and has no .shell-masthead, so none of this reaches it. */
html.is-locked .shell-masthead {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
}
html.is-locked body { padding-top: var(--shell-masthead-h); }

/* The index dresses itself. It used to sit inside landing.css and quietly rely
   on that file's `a { text-decoration: none }`; static-shell.css does not strip
   underlines, so the first generated page to carry the overlay drew every
   destination underlined. The hover line is a pseudo-element and is unaffected. */
.site-menu a { text-decoration: none; }

.site-menu { position: fixed; z-index: 420; inset: 0; }
.site-menu[hidden] { display: none; }
.site-menu__scrim { position: absolute; inset: 0; background: rgba(5,5,7,0.72); opacity: 0; backdrop-filter: blur(6px); transition: opacity 240ms var(--ease-out); }
.site-menu__panel { position: absolute; top: 0.65rem; right: 0.65rem; bottom: 0.65rem; display: flex; flex-direction: column; width: min(34rem, calc(100% - 1.3rem)); overflow: hidden; border: 1px solid rgba(255,255,255,0.16); border-radius: 0.4rem; background: rgba(21,10,40,0.94); box-shadow: 0 2.4rem 7rem rgba(0,0,0,0.6); opacity: 0; transform: translateX(5%) scale(0.98); transform-origin: top right; backdrop-filter: blur(28px) saturate(145%); transition: opacity 240ms var(--ease-out), transform 420ms var(--ease-drawer); }
.site-menu.is-open .site-menu__scrim { opacity: 1; }
.site-menu.is-open .site-menu__panel { opacity: 1; transform: translateX(0) scale(1); }
.site-menu__top { display: flex; min-height: 5rem; align-items: center; justify-content: space-between; padding: 0 1.4rem; border-bottom: 1px solid rgba(255,255,255,0.15); }
.site-menu__top span { color: var(--moon-dim); font-size: 0.64rem; letter-spacing: 0.12em; }
.site-menu__top button { border: 0; background: transparent; cursor: pointer; font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; }
.site-menu nav { display: flex; flex: 1; flex-direction: column; justify-content: center; padding: 1.5rem; }
.site-menu__primary { display: flex; align-items: baseline; gap: 1rem; margin: 0; padding: 0.75rem 0; border-bottom: 1px solid rgba(255,255,255,0.15); font-family: var(--font-display); font-size: clamp(2.8rem, 9vw, 5.8rem); line-height: 0.92; letter-spacing: -0.04em; }
.site-menu__primary span { color: var(--chlorophyll); font-family: var(--font-ui); font-size: 0.62rem; letter-spacing: 0.1em; }
.site-menu__panel > p { margin: 0; padding: 1.4rem; color: var(--moon-dim); font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase; }
/* --- MOBILE INDEX -----------------------------------------------------------
   The panel was a purple poster: rgba(21,10,40,.94) ground with seven display-
   serif rows at up to 5.8rem, vertically centred, so every destination was a
   headline and the list needed most of the screen to say six words.

   It is now an index. Obsidian ground from the same family as the PDP bays,
   UI type instead of display, the numeral demoted to a quiet gold tick and the
   NAME promoted to the scanning target. Rows are hairline-separated and sized
   for a thumb rather than for a poster.

   Every selector here is scoped to .site-menu. It once shared base rules with
   the bag; that split when the index became global, and the bag is not in
   scope here at all. */
.site-menu__panel {
  /* The shared rule pins top AND bottom, which stretched the panel to the full
     viewport. With six compact rows that left ~374px of empty panel under the
     last item at 390. Release the bottom edge so the index is exactly as tall
     as it needs to be, and cap it so it can still scroll on a short screen.
     The bag keeps the full-height treatment; this is scoped to the menu. */
  bottom: auto;
  max-height: calc(100% - 1.3rem);
  background: rgba(9, 9, 12, 0.96);
  border-color: rgba(255, 247, 230, 0.14);
}
.site-menu__top {
  min-height: 3.5rem;
  padding: 0 1.1rem;
  border-bottom-color: rgba(255, 247, 230, 0.14);
}
.site-menu__top span {
  color: rgba(255, 247, 230, 0.5);
  font-family: var(--font-ui);
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
/* Close is the one control that must never be hunted for. */
.site-menu__top button {
  min-height: 2.75rem;
  padding: 0 0.2rem 0 1rem;
  color: var(--nectar);
  font-family: var(--font-ui);
  font-weight: 500;
}
.site-menu__top button:focus-visible { outline: 2px solid var(--nectar); outline-offset: 3px; }
/* flex-start, not centre: centring a short list in a tall panel is what left
   the dead region under the rows.

   THE SITE INDEX (four groups). The panel is capped to the viewport, so the
   index itself scrolls when a short phone cannot show all four groups; the
   top bar with Close stays put. */
.site-menu nav {
  flex: 0 1 auto;
  min-height: 0;
  justify-content: flex-start;
  padding: 0.4rem 1.1rem 0.4rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.site-menu__group { padding: 0.35rem 0 0.7rem; border-bottom: 1px solid rgba(255, 247, 230, 0.12); }
.site-menu__group:last-child { border-bottom: 0; }
.site-menu__primary {
  align-items: center;
  gap: 0.95rem;
  min-height: 3.2rem;
  padding: 0.45rem 0 0.2rem;
  border-bottom: 0;
  color: var(--moon);
  font-family: var(--font-ui);
  font-size: 1.32rem;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.012em;
  transition: color 200ms var(--ease-out);
}
/* A group with no page of its own is a label, not a link: it never looks
   pressable and never takes focus. */
.site-menu__primary--label { color: rgba(255, 247, 230, 0.62); }
.site-menu__primary span {
  min-width: 1.5rem;
  color: rgba(255, 165, 10, 0.68);
  font-size: 0.58rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.14em;
}
/* Supporting destinations: two columns under the primary row, indented to the
   name so the numeral column stays clean. Each row is a full 44px target. */
.site-menu__sub {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 1rem;
  margin: 0;
  padding: 0 0 0 2.45rem;
  list-style: none;
}
.site-menu__sub a {
  display: flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0.2rem 0;
  color: rgba(255, 247, 230, 0.72);
  font-family: var(--font-ui);
  font-size: 0.9rem;
  line-height: 1.2;
  transition: color 200ms var(--ease-out);
}
.site-menu__primary:focus-visible, .site-menu__sub a:focus-visible { outline: 2px solid var(--nectar); outline-offset: 3px; }
@media (hover: hover) and (pointer: fine) {
a.site-menu__primary:hover, .site-menu__sub a:hover { color: var(--nectar); }
}

a.site-menu__primary:active, .site-menu__sub a:active { color: var(--nectar); }
/* The narrowest phones: keep two columns, pull the list under the numeral, and
   fade the foot of the index when it has to scroll so the fourth group reads
   as "more below" rather than as the end of the list. */
@media (max-width: 340px) {
.site-menu__sub { column-gap: 0.7rem; padding-left: 0; }
.site-menu__sub a { font-size: 0.84rem; }
}

@media (max-width: 340px), (max-height: 620px) {
.site-menu nav { mask-image: linear-gradient(to bottom, #000 calc(100% - 2.2rem), transparent); padding-bottom: 1.6rem; }
}

/* Reduced motion: the index appears, it does not slide. The opacity fade is
   the 120ms hideOverlay() already waits for on this path. Menu only; the bag
   keeps its own treatment. */
@media (prefers-reduced-motion: reduce) {
.site-menu__scrim, .site-menu__panel { transition: opacity 120ms linear; }
.site-menu__panel, .site-menu.is-open .site-menu__panel { transform: none; }
.site-menu__primary, .site-menu__sub a { transition: none; }
}

.site-menu__panel > p {
  padding: 0.9rem 1.1rem 1.1rem;
  color: rgba(255, 247, 230, 0.38);
  font-size: 0.58rem;
  letter-spacing: 0.16em;
}
/* ============================================================================
   THE INDEX AS AN INSTRUMENT
   ----------------------------------------------------------------------------
   The index above is correct and legible and reads like a settings screen. This
   is the pass that makes it ASTROVE without moving a single destination: no
   markup that carries meaning, no JavaScript, no WebGL, and every rule scoped
   to .site-menu so the bag -- which shares the panel and header base rules --
   is untouched.

   It is built from four things the site already owns: the spectral palette in
   tokens.css, the SKU colours, the gold tick on the numerals, and hairlines.
   None of it is ornament for its own sake. The rail is the spine of the index,
   the node says which group you are in, the underline says what you are about
   to choose, and the fruit colours say which fruit.

   Each group carries one accent, running cool as the index descends: gold at
   the shop, violet through discover, ice at the house, green at trade. One
   variable, inherited by the node, the numeral and the group's hairline. */
.site-menu__group:nth-child(1) { --group-accent: var(--nectar); }
.site-menu__group:nth-child(2) { --group-accent: var(--sku-jamun-bright); }
.site-menu__group:nth-child(3) { --group-accent: var(--electric-ice); }
.site-menu__group:nth-child(4) { --group-accent: var(--chlorophyll); }
/* ---- ATMOSPHERE -----------------------------------------------------------
   Two radial blooms and nothing else: a warm one under the top bar, a violet
   one at the foot. Both are pinned to the panel and inert to the pointer, so
   neither can intercept a tap meant for a link. The panel is a flex container
   and an absolutely positioned pseudo-element is out of flow, so this adds no
   box to the column. */
.site-menu__panel::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(135% 45% at 100% 0%, rgba(255, 165, 10, 0.10), transparent 62%),
    radial-gradient(110% 45% at 0% 100%, rgba(122, 45, 255, 0.12), transparent 66%);
}
/* Everything real sits above the bloom. */
.site-menu__top, .site-menu__index, .site-menu__panel > p { position: relative; z-index: 1; }
/* ---- THE STATUS LINE ------------------------------------------------------
   "ASTROVE / INDEX" was already here. The coordinate beside it is atmosphere,
   not a claim: it names the market the site is written for and nothing more. It
   is aria-hidden, so a screen reader gets the index's name without the
   ornament, and it is dropped on the narrowest phones. */
.site-menu__top span { display: inline-flex; align-items: center; gap: 0.6rem; }
.site-menu__top span i {
  padding-left: 0.6rem;
  border-left: 1px solid rgba(255, 247, 230, 0.16);
  color: rgba(255, 165, 10, 0.55);
  font-style: normal;
  font-size: 0.55rem;
  letter-spacing: 0.18em;
}
/* ---- THE SPECTRAL RAIL ----------------------------------------------------
   One hairline down the left of the index carrying the four accents in the
   order the groups use them. It is the only continuous vertical line in the
   panel, so it reads as a spine rather than as a border. The nav gains left
   padding to hold it, and the rail scrolls with the index because it belongs
   to the index. */
/* .site-menu nav (0,1,1) outranks a bare .site-menu__index (0,1,0) and kept its
   own 1.1rem, which left the group nodes hanging off the panel's left edge and
   clipped by its overflow. Matched on both so the rail has the room it needs. */
.site-menu nav.site-menu__index { padding-left: 2rem; }
.site-menu__index::before {
  content: "";
  position: absolute;
  left: 0.7rem;
  top: 0.75rem;
  bottom: 0.75rem;
  width: 1px;
  background: linear-gradient(
    180deg,
    rgba(255, 165, 10, 0.55),
    rgba(187, 85, 255, 0.5) 34%,
    rgba(46, 233, 255, 0.45) 67%,
    rgba(201, 255, 43, 0.5)
  );
  opacity: 0.5;
}
/* ---- THE GROUP NODE -------------------------------------------------------
   A point on the rail beside each group, dim until that group is hovered or
   holds focus, then lit in its accent. This is the travelling glow: nothing
   moves, the light changes hands. It costs no animation frame and it survives
   reduced motion as a colour change. */
.site-menu__group { position: relative; transition: border-color 200ms var(--ease-out); }
.site-menu__group::before {
  content: "";
  position: absolute;
  left: -1.36rem;
  top: 1.45rem;
  width: 0.32rem;
  height: 0.32rem;
  border-radius: 50%;
  background: var(--group-accent);
  opacity: 0.4;
  transform: scale(0.75);
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out), box-shadow 200ms var(--ease-out);
}
.site-menu__group:hover::before, .site-menu__group:focus-within::before {
  opacity: 1;
  transform: scale(1);
  box-shadow: 0 0 0.55rem var(--group-accent);
}
/* The hairline under a group takes its accent while the group is live. */
.site-menu__group:hover, .site-menu__group:focus-within { border-bottom-color: color-mix(in srgb, var(--group-accent) 34%, transparent); }
/* ---- THE NUMERAL AS AN INSTRUMENT -----------------------------------------
   01 becomes 01/04: a position in a set of four rather than a loose ordinal.
   The denominator is generated, so the markup keeps one number and a hand-typed
   count cannot drift out of step with the groups. The numeral takes the group's
   accent while the group is live. */
.site-menu__primary span {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  min-width: 2.6rem;
  transition: color 200ms var(--ease-out);
}
.site-menu__primary span::after {
  content: "/04";
  margin-left: 0.12em;
  opacity: 0.42;
  font-size: 0.86em;
}
.site-menu__group:hover .site-menu__primary span, .site-menu__group:focus-within .site-menu__primary span { color: var(--group-accent); }
/* ---- CHOOSING -------------------------------------------------------------
   A line drawn under the label from the left in 180ms, with a small point
   ahead of it. Together they read as an instrument acquiring a target, and they
   finish well inside the time it takes to decide to click. Both are drawn on
   the anchor, so they follow the text at any width and never affect layout. */
.site-menu__sub a { position: relative; width: fit-content; padding-right: 0.1rem; }
.site-menu__sub a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.55rem;
  height: 1px;
  background: currentColor;
  opacity: 0.6;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 180ms var(--ease-out);
}
.site-menu__sub a::before {
  content: "";
  position: absolute;
  left: -0.85rem;
  top: 50%;
  width: 0.28rem;
  height: 0.28rem;
  margin-top: -0.14rem;
  border-radius: 50%;
  background: currentColor;
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 160ms var(--ease-out), transform 200ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
.site-menu__sub a:hover::after { transform: scaleX(1); }
.site-menu__sub a:hover::before { opacity: 0.85; transform: scale(1); }
}

.site-menu__sub a:focus-visible::after { transform: scaleX(1); }
.site-menu__sub a:focus-visible::before { opacity: 0.85; transform: scale(1); }
.site-menu__sub a:active::after { transform: scaleX(1); }
/* The fruit rows answer in their own colour, and only while they are being
   chosen, so the resting index stays one calm ivory column rather than three
   coloured buttons. These are the pack colours the rest of the site uses. */
@media (hover: hover) and (pointer: fine) {
.site-menu__sub li[data-sku="mango"] a:hover { color: var(--sku-mango); }
.site-menu__sub li[data-sku="jamun"] a:hover { color: var(--sku-jamun-bright); }
.site-menu__sub li[data-sku="pineapple"] a:hover { color: var(--sku-pineapple); }
}

.site-menu__sub li[data-sku="mango"] a:focus-visible, .site-menu__sub li[data-sku="mango"] a:active { color: var(--sku-mango); }
.site-menu__sub li[data-sku="jamun"] a:focus-visible, .site-menu__sub li[data-sku="jamun"] a:active { color: var(--sku-jamun-bright); }
.site-menu__sub li[data-sku="pineapple"] a:focus-visible, .site-menu__sub li[data-sku="pineapple"] a:active { color: var(--sku-pineapple); }
/* The group's own name takes the same underline, at its own weight. */
.site-menu__primary { position: relative; }
a.site-menu__primary::after {
  content: "";
  position: absolute;
  left: 2.6rem;
  right: 0;
  bottom: 0.45rem;
  height: 1px;
  background: var(--group-accent);
  opacity: 0.7;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 220ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
a.site-menu__primary:hover::after { transform: scaleX(1); }
}

a.site-menu__primary:focus-visible::after, a.site-menu__primary:active::after { transform: scaleX(1); }
/* ---- CLOSE ----------------------------------------------------------------
   It still says CLOSE, because a word is unambiguous and an icon is a guess.
   The two hairlines beside it make the meaning geometric as well as literal and
   rotate a quarter turn under the pointer -- the smallest gesture that reads as
   "this contracts". The target is unchanged. */
.site-menu__top button { padding: 0 1.5rem 0 1rem; }
.site-menu__top button::before, .site-menu__top button::after {
  content: "";
  position: absolute;
  right: 0.3rem;
  top: 50%;
  width: 0.66rem;
  height: 1px;
  background: currentColor;
  opacity: 0.8;
  transition: transform 220ms var(--ease-out), opacity 200ms var(--ease-out);
}
.site-menu__top button::before { transform: translateY(-50%) rotate(45deg); }
.site-menu__top button::after { transform: translateY(-50%) rotate(-45deg); }
@media (hover: hover) and (pointer: fine) {
.site-menu__top button:hover::before { transform: translateY(-50%) rotate(135deg); opacity: 1; }
.site-menu__top button:hover::after { transform: translateY(-50%) rotate(45deg); opacity: 1; }
}

.site-menu__top button:focus-visible::before { transform: translateY(-50%) rotate(135deg); opacity: 1; }
.site-menu__top button:focus-visible::after { transform: translateY(-50%) rotate(45deg); opacity: 1; }
/* The sign-off takes a rule above it so the panel closes the way it opened.
   The copy is untouched. */
.site-menu__panel > p {
  border-top: 1px solid rgba(255, 247, 230, 0.1);
  color: rgba(255, 247, 230, 0.44);
}
/* ---- NARROW ---------------------------------------------------------------
   The rail stays, because it is one pixel and it is the spine. The coordinate
   goes, because at 340px the status line is competing with Close. */
@media (max-width: 340px) {
.site-menu__top span i { display: none; }
.site-menu__index { padding-left: 1.6rem; }
.site-menu__group::before { left: -1.05rem; }
}

/* ---- REDUCED MOTION -------------------------------------------------------
   Everything that MOVES is removed; everything that is COLOUR stays. The rail,
   the nodes, the numerals, the fruit colours and the bloom are all unaffected.
   A reader who does not want animation still gets the instrument; it simply
   arrives already drawn rather than sweeping in. */
@media (prefers-reduced-motion: reduce) {
.site-menu__group, .site-menu__group::before, .site-menu__primary span, .site-menu__sub a::before, .site-menu__sub a::after, a.site-menu__primary::after, .site-menu__top button::before, .site-menu__top button::after { transition: none; }
.site-menu__sub a:hover::after, .site-menu__sub a:focus-visible::after, a.site-menu__primary:hover::after, a.site-menu__primary:focus-visible::after { transform: scaleX(1); }
.site-menu__top button:hover::before { transform: translateY(-50%) rotate(45deg); }
.site-menu__top button:hover::after { transform: translateY(-50%) rotate(-45deg); }
}

@media (prefers-reduced-transparency: reduce) {
.site-menu__panel { background: var(--void-deep); backdrop-filter: none; }
}
