/* Bullish Media — floating pill menu.
   1-to-1 replica of lamalama.com's header. Every value here is the resolved
   form of the Tailwind utility on the reference markup; the compiled source is
   archived at docs/reference/2026-08-05/lama.css so each one stays checkable.

   Reference class            -> resolved
   top-4                      -> top: 1rem
   max-w-[calc(438/16*1rem)]  -> max-width: 27.375rem
   backdrop-blur-sm           -> backdrop-filter: blur(4px)
   h-[calc(50/16*1rem)]       -> height: 3.125rem
   pl-[calc(9/16*1rem)]       -> padding-left: .5625rem
   pr-[calc(13/16*1rem)]      -> padding-right: .8125rem
   w-9                        -> width: 2.25rem
   gap-y-[calc(3/16*1rem)]    -> row-gap: .1875rem
   h-0.5 / w-4                -> height: .125rem / width: 1rem
   size-2.5                   -> width/height: .625rem
   left-3                     -> left: .75rem
   pl-8 / pt-0.5              -> padding-left: 2rem / padding-top: .125rem
   h-[calc(1/16*1rem)]        -> height: .0625rem
   bg-white/10                -> background: rgb(255 255 255 / .1)

   TYPEFACE DEVIATION (deliberate, documented in the spec §2.4): the reference
   sets SuisseBPIntl for nav items and Sometype for the tag. Both are
   commercial licences we cannot self-host, so they map to Inter and
   JetBrains Mono — already loaded by this site. Every other metric
   (size / weight / letter-spacing / line-height / transform) is unchanged. */

/* ------------------------------------------------------------ theme tokens

   These live here rather than in pages.css because menu.css is the ONE
   stylesheet every page of this site loads, and the pill has to read the same
   tokens as the page under it.

   Values are lamalama's own, read out of its :root as space-separated RGB
   triplets (docs/reference/2026-08-05/lama.css) and written here as hex:

     --offWhite  249 244 235  #F9F4EB   the cream
     --offBlack    1   1   1  #010101
     --dark       26  28  28  #1A1C1C
     --black       0   0   0  #000000
     --white     255 255 255  #FFFFFF

   Only three of their themes exist (dark / light / pink); we use two. The
   reference resolves a page's theme from <main data-theme> and writes the
   tokens inline onto <html>; we put the attribute straight on <html> (for the
   canvas background) and on <body> (so a page's own rules can key off it).

   --rule and --hoverBackdrop are ours: the reference hard-codes bg-white/10
   and border-white/15 because every one of its dark sections is dark. Ours
   have to survive both themes, so the tint is a token, not a literal. */
:root,
[data-theme='dark'] {
  --bgPrimary: #1a1c1c;
  --textPrimary: #f9f4eb;
  --bgSecondary: #000000;
  --rule: rgb(249 244 235 / 0.15);
  --hoverBackdrop: rgb(255 255 255 / 0.1);
}

[data-theme='light'] {
  --bgPrimary: #f9f4eb;
  --textPrimary: #010101;
  --bgSecondary: #ffffff;
  --rule: rgb(1 1 1 / 0.15);
  --hoverBackdrop: rgb(0 0 0 / 0.06);
}

:root {
  --menu-ease: cubic-bezier(.165, .84, .44, 1);
  /* GSAP power4.out, which every hover transition on the reference uses. */
  --power4-out: cubic-bezier(.165, .84, .44, 1);
}

/* The contact page inverts, exactly as depoluxe's data-palette="secondary"
   does. One menu implementation serves both because it reads the tokens.

   The pill must be PURE white here while the page sits on off-white — same
   relationship lamalama's light theme uses (--bgPrimary: offWhite against
   --bgSecondary: white). Setting both to the same value makes the bar
   disappear against the page. Kept as its own selector because contact.html
   carries no data-theme: its page background is #fff, not the cream. */
[data-palette='secondary'] {
  --bgSecondary: #ffffff;
  --textPrimary: var(--black);
  --rule: rgb(0 0 0 / 0.15);
  --hoverBackdrop: rgb(0 0 0 / 0.06);
}

/* ------------------------------------------------------------- container */

.menu {
  position: fixed;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  flex-direction: column;
  width: calc(100vw - 2rem);
  max-width: 27.375rem;
  border-radius: 0.25rem;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  color: var(--textPrimary);
  /* Revealed by menu.js once the intro has handed over — see main.js. */
  opacity: 0;
  transition: opacity .6s ease;
}
.menu.is-ready { opacity: 1; }

/* Tint lives on its own layer so the bar can be translucent while the label
   text above it stays fully opaque. */
.menu__fill {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  background: var(--bgSecondary);
  opacity: 0.6;
  pointer-events: none;
}

/* ------------------------------------------------------------------- bar */

.menu__bar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 3.125rem;
  padding-left: 0.5625rem;
  padding-right: 0.8125rem;
  cursor: pointer;
}

/* Full-bleed hit area — the whole bar toggles, not just the button. */
.menu__hit {
  position: absolute;
  inset: 0;
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
}

.menu__logo {
  position: relative;
  display: block;
  width: 2.25rem;
  flex-shrink: 0;
}
.menu__logo-grid {
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  display: grid;
  place-items: center;
  color: currentColor;
}
.menu__logo-grid svg { width: 100%; height: 100%; display: block; }

/* Centred message. -right-1/2 + max-width in the reference lets a long line
   overhang symmetrically without pushing the logo or the toggle. */
.menu__message {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  max-width: calc(100% - 7rem);
  text-align: center;
  line-height: 1.5;
  pointer-events: none;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
}
/* One line, retyped in place. The rotation is a mono scramble typewriter, not
   a slide — see menu.js _startMessages, which mirrors the reference's
   mono_text_reveal. So there is nothing to transition here; the text content
   itself is what changes, frame by frame. */
.menu__message-inner { position: relative; }
.menu__message-line { display: block; }

.menu__toggle {
  position: relative;
  display: flex;
  flex-direction: column;
  row-gap: 0.1875rem;
  padding: 0.75rem 0.4375rem;
  flex-shrink: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.menu__toggle span {
  display: block;
  height: 0.125rem;
  width: 1rem;
  background: currentColor;
  transition: transform .4s var(--menu-ease), opacity .3s ease;
}
/* Open state folds the three bars into a cross. */
.menu.is-open .menu__toggle span:nth-child(1) { transform: translateY(0.3125rem) rotate(45deg); }
.menu.is-open .menu__toggle span:nth-child(2) { opacity: 0; }
.menu.is-open .menu__toggle span:nth-child(3) { transform: translateY(-0.3125rem) rotate(-45deg); }

/* ------------------------------------------------------------------ body */

.menu__body {
  position: relative;
  height: 0;
  overflow: hidden;
  transition: height .55s var(--menu-ease);
}
.menu__scroll {
  position: relative;
  max-height: calc(100vh - 6rem);
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.menu__scroll::-webkit-scrollbar { display: none; }
.menu__list { display: flex; flex-direction: column; width: 100%; }

/* ------------------------------------------------------------- nav items */

.menu__item {
  position: relative;
  display: flex;
  flex-shrink: 0;
  align-items: center;
  width: 100%;
  height: 3.125rem;
  /* .ll-add-navItem */
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.4;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
  color: inherit;
}

/* Measured on the reference (app.pretty.js:11079): the row backdrop fades
   0 -> 1 over .65s power4.out, not the .4s we shipped first. */
.menu__item-backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: var(--hoverBackdrop);
  opacity: 0;
  transition: opacity .65s var(--power4-out);
  pointer-events: none;
}
.menu__item:hover .menu__item-backdrop,
.menu__item:focus-visible .menu__item-backdrop { opacity: 1; }

/* Hairline draws in from the left on hover. */
.menu__item-line {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
  height: 0.0625rem;
  background: currentColor;
  opacity: 0.1;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .6s var(--menu-ease);
}
.menu__item:hover .menu__item-line,
.menu__item:focus-visible .menu__item-line,
.menu__item.is-revealed .menu__item-line { transform: scaleX(1); }

/* Pixel icon: four stacked frames, three hidden. menu.js cycles them.

   The row icons are INVISIBLE at rest — measured, not assumed. The reference's
   nav_item_icon controller calls gsap.set(mainIcon paths, {opacity: 0}) in
   setDefaults(), and a live CDP probe of lamalama's open menu returned
   ["0","0","0","0","0"] for the first five paths of .js-icon at rest and
   ["1","1","1","1","1"] 700ms into a hover. So the base frame's own squares
   start at 0 here too, and menu.js fades them in one square at a time.

   The menu-BAR logo (.menu__logo-grid) is a separate thing and stays visible
   at rest — the reference's bar logo is visible at rest as well. */
.menu__icon {
  position: absolute;
  left: 0.75rem;
  width: 0.625rem;
  height: 0.625rem;
}
.menu__icon-frame {
  position: absolute;
  inset: 0;
  width: 0.625rem;
  height: 0.625rem;
  opacity: 0;
}
.menu__icon-frame--base { position: relative; opacity: 1; }
.menu__icon svg { width: 100%; height: 100%; display: block; fill: currentColor; }

/* Base frame ships as 13 separate <path> squares (exactly as the reference
   does) so each can be staggered independently. Hidden here rather than by JS
   so a no-JS visitor never sees a half-drawn icon. */
.menu__icon-frame--base path {
  opacity: 0;
  transition: opacity .15s var(--power4-out);
}

/* Label wipes up from its own baseline. */
.menu__label {
  padding-left: 2rem;
  padding-top: 0.125rem;
  clip-path: inset(0% 0% 100% 0%);
  transition: clip-path .6s var(--menu-ease);
}
.menu__item.is-revealed .menu__label { clip-path: inset(0% 0% 0% 0%); }

.menu__item[aria-current='page'] .menu__item-line { transform: scaleX(1); opacity: 0.35; }

/* ------------------------------------------------------------- dropdown */

.menu__dropdown { position: relative; width: 100%; flex-shrink: 0; }
.menu__dropdown-panel {
  height: 0;
  overflow: hidden;
  transition: height .5s var(--menu-ease);
}
/* Nested rows sit one indent deeper than their parent. */
.menu__dropdown .menu__item--child { height: 2.75rem; font-size: 0.9375rem; }
.menu__dropdown .menu__item--child .menu__icon { left: 1.75rem; }
.menu__dropdown .menu__item--child .menu__label { padding-left: 3rem; }

.menu__caret {
  position: absolute;
  right: 1rem;
  width: 0.625rem;
  height: 0.625rem;
  transition: transform .45s var(--menu-ease);
}
.menu__caret::before,
.menu__caret::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  background: currentColor;
  transform: translate(-50%, -50%);
}
.menu__caret::before { width: 0.625rem; height: 0.0625rem; }
.menu__caret::after  { width: 0.0625rem; height: 0.625rem; transition: opacity .3s ease; }
.menu__dropdown.is-open .menu__caret::after { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .menu__body,
  .menu__dropdown-panel { transition-duration: .001ms; }
  .menu__label { clip-path: none; }
  .menu__item-line { transform: scaleX(1); }
}

/* ------------------------------------------------------------- page trail
   Site-wide chrome like the menu itself, so it lives in the everywhere
   stylesheet. The canvas is mounted by src/trail.js.

   The trail sits at z -1: BETWEEN html's background (which pages.css /
   contact.css make the page colour for exactly this reason) and the body's
   content — the reference's fullscreen .js-canvas is under its DOM the same
   way, and this keeps the stamps clear of image-fx's over-photo rendition. */
.page-trail {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

/* --------------------------------------------------------- page transition
   src/page-transition.js. The dissolve canvas covers everything, menu pill
   (z 60) included — the reference covers its header during a transition. */
.pt-canvas {
  position: fixed;
  inset: 0;
  z-index: 70;
  pointer-events: none;
}

/* Centred mono "N%" — the reference's .ll-add-tag: 10px mono, 500,
   -.02em, uppercase. Only ever seen when a page load outlasts 0.9s. */
.pt-counter {
  position: fixed;
  inset: 0;
  z-index: 71;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.8;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity 0.25s var(--power4-out);
}
.pt-counter.is-in { opacity: 1; }

/* Pre-paint cover: an inline <head> script (stamped by build/pages.mjs)
   sets data-pt-cover on <html> BEFORE first paint when the previous page
   left a transition note, so the incoming document is born covered — no
   flash between documents. page-transition.js swaps it for the identical
   canvas and dissolves. The animation is the failsafe: if that module never
   runs, the cover clears itself rather than leaving the page unreadable. */
html[data-pt-cover]::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 70;
  animation: pt-cover-failsafe 0.4s ease 3s forwards;
}
html[data-pt-cover='dark']::before { background: #1a1c1c; }
html[data-pt-cover='light']::before { background: #f9f4eb; }

@keyframes pt-cover-failsafe {
  to { opacity: 0; visibility: hidden; }
}
