/* Combined Menu — container / layout only.
   Link & button styling (.combined-menu a, .link-text, .year, hover, active)
   lives in components/links/links.css */

/* INDEX — container of more than one button */
.combined-menu {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--stack-3);
  padding: 0;
  position: relative;
  user-select: none;

  /* The two gaps below the menu, named so neither has to be worked out from
     the other. The dashed rule hangs --rule-gap under the menu; the content
     starts --content-gap under the rule; the margin is just their sum. Edit
     either number on its own — nothing else needs recomputing. */
  --rule-gap: -3px;
  --content-gap: 80px;
  /* How far below the first row's TOP the columns begin — half a pill, so
     they start level with its middle. The rules then read as passing behind
     the menu rather than boxing it in from the first row up. */
  --grid-top-drop: 14px;
}

/* Full-width menu so the dashed rules span the main-frame width. Pills still
   hug the left — each pill is inline-flex and the column keeps align-items:
   flex-start. Applied to BOTH the in-page menu and the fullscreen overlay menu
   (#combinedMenuOverlay) so the rules persist through page transitions — the
   overlay is what's shown while #content is hidden. The overlay is targeted by
   ID (not the class) on purpose: the bundle's scroll-restore queries
   '.combined-menu-inf-scroll' and uses [0], so giving the overlay that class
   would corrupt it. */
.combined-menu.combined-menu-inf-scroll,
#combinedMenuOverlay {
  display: flex;
  width: 100%;
  position: relative;
  margin-top: 48px;     /* 48px between the top rule and the menu */
  margin-bottom: calc(var(--rule-gap) + var(--content-gap));
}

/* Bottom rule — --rule-gap below the menu. Dashed in --rule, matching a lit
   pill's border exactly (links.css .is-hovered/.active: 1px dashed var(--rule)). */
.combined-menu.combined-menu-inf-scroll::after,
#combinedMenuOverlay::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(-1 * var(--rule-gap));
  border-top: 1px dashed var(--rule);
}

/* Link rows sit flush left with the "(index)" header — no indent in v2 */
.combined-menu a.indented {
  margin-left: 0;
}

/* Truncation: long link labels get an ellipsis instead of overflowing.
   Applied to both the in-page menu and the overlay so label widths match
   through the transition. */
.combined-menu-inf-scroll a,
#combinedMenuOverlay a {
  max-width: 100%;
}
.combined-menu-inf-scroll a.indented,
#combinedMenuOverlay a.indented {
  max-width: 100%;
}
.combined-menu-inf-scroll a .link-text,
#combinedMenuOverlay a .link-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Spacer span from the bundle DOM — not needed in the pill layout */
.combined-menu .dots-line {
  display: none;
}

/* Disabled items are non-interactive spacers — not pills. This is the
   "(index)" header: body face, faint, sitting 8px above the first row. */
.combined-menu span.disabled {
  color: var(--ink-faint);
  opacity: 1;
  display: block;
  font: var(--type-body);
  font-family: var(--font-rounded);
  font-style: italic;
  text-transform: lowercase;
  margin-bottom: var(--stack-3);
}

/* Full-strength ink, no alpha — dark theme: rgba(255, 255, 255, 1) */
.combined-menu span.disabled .year {
  color: rgba(0, 0, 0, 1);
  flex-shrink: 0;
}

/* ============================================================
   TIMELINE VIEW

   The same eight rows, read two ways. In list view they stack flush left; the
   "(timeline view click)" button underneath slides each row right to the year
   it started and fades in an axis below — the menu becomes a chart of itself.
   Vertical order never changes, so a row you were reading is still where you
   left it; only the horizontal position carries the new meaning.

   The scale is quantised to four stops (0 / 2024 / 2025 / 2026, with "now" at
   the right edge — see TL_STOPS in dist/core.bundle.js). Every row carries its
   stop as --tl-left and its place in the cascade as --tl-delay, both written
   inline by render(); everything below is CSS reading those two numbers.

   Geometry moves through margin-left rather than left, so the rows stay
   ordinary flex items: nothing leaves the flow, every row keeps its own label
   width, and the dashed rule under the menu keeps measuring a real height. The
   transition itself is declared in links.css — that sheet loads second and its
   pill `transition` shorthand would overwrite one written here.
   ============================================================ */

/* Percentages need a definite width to resolve against. Feed menus and the
   overlay are full-width already; this also picks up the standalone 404 menu,
   which is inline-flex until the timeline asks it for a frame to measure. */
.combined-menu.is-timeline {
  display: flex;
  width: 100%;
}

.combined-menu a {
  margin-left: 0;
}

.combined-menu.is-timeline a {
  margin-left: var(--tl-left);
  /* Without this a long label would overrun the right edge by exactly its own
     indent: max-width is a flat 100% everywhere else. */
  max-width: calc(100% - var(--tl-left));
}

/* The phone-only stand-in for the button: the plain "(index)" the menu opened
   with before the timeline existed. It wears span.disabled, so it is the same
   element in the same voice it always was, and it keeps that rule's 4px margin
   — with the column's own 4px gap below it, the first row sits exactly where it
   always did. Hidden on the web, where the button in the gutter speaks and the
   menu needs no word above it.

   Matched as `span.menu-tl-static`, not by class alone: it wears .disabled to
   borrow that voice, and `.combined-menu span.disabled` sets display:block at
   a specificity a bare class cannot beat. Both rules here carry the same
   weight and win on order instead. */
.combined-menu span.menu-tl-static {
  display: none;
}

/* ---- the year marks under the menu ----
   Every mark is absolutely placed, so the row has nothing in flow to give it a
   height and states its own — one line of body type, the same as the header. */
.menu-tl-footer {
  position: relative;
  width: 100%;
  height: var(--lh-body);
  /* 8px on top of the column's 4px gap = the 12px under the last pill. The
     footer sits INSIDE the menu, so the dashed ::after rule and the margin
     below it travel down with it and need no adjusting. */
  margin-top: var(--stack-2);
}

/* ---- the grid behind the rows ----
   One dashed vertical per year, each 4px left of the mark it belongs to: it
   reads as the edge a column starts at rather than a line struck through it.
   Drawn in --rule, the same weight as the horizontal rule it meets and as the
   year marks it carries, so the axis reads as one drawing.
   The layer is written into the DOM before the rows and carries no z-index —
   everything here is positioned at z-index:auto, so tree order alone puts the
   pills on top. z-index:-1 would be wrong: .combined-menu creates no stacking
   context, and the grid would sink behind the page ground entirely.

   It starts --grid-top-drop below the top of the first pill, and runs down to
   the same dashed rule the container draws --rule-gap below itself. That top is
   the bare drop: nothing precedes the first row in flow on the web — the index
   button is lifted out of it and the phone's "(index)" is display:none — so the
   container's top edge and the first pill's are the same line. */
.tl-grid {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--grid-top-drop);
  bottom: calc(-1 * var(--rule-gap));
  pointer-events: none;
}

/* Each rule is drawn rather than faded in: anchored at the bottom with no
   height, it grows up to full height when the timeline is thrown, so the
   columns rise out of the axis they belong to. Height and not scaleY — scaling
   would stretch the dash pattern on the way up and let it settle back, which
   reads as a wobble rather than a line being drawn. At height 0 the left
   border has nothing to run along, so nothing paints in list view and the
   layer needs no opacity of its own. */
.tl-gridline {
  position: absolute;
  bottom: 0;
  height: 0;
  left: calc(var(--tl-left) - 4px);
  border-left: var(--bw) dashed var(--rule);
  transition: height var(--dur-timeline) ease;
}

.combined-menu.is-timeline .tl-gridline {
  height: 100%;
}

/* ---- the index button ----
   One square in the left gutter, level with the first row: the control that
   opens the timeline, drawn as a menu pill with a picture of the menu in it.

   It hangs OUTSIDE the column. right:100% puts its right edge on the
   container's left edge, so the pills, the dashed rule and the --tl-left
   percentages all keep the frame they had — being absolutely positioned it is
   not a flex item and adds nothing to the menu's width. top:0 is the first
   row's top: on desktop that row is the first thing in flow, the button having
   left it and the phone's "(index)" being display:none. The gutter it sits in
   is .main-frame's (and #menuOverlay's) 10vw padding — ~77px at the 768px
   break, where the button hands over to the static label anyway.

   A <button> rather than an <a> so it stays clear of the <a>-based handlers in
   PageFeed. */
.tl-toggle {
  /* A menu pill's height exactly, squared: --lh-display 18 + --pad-pill 4
     top and bottom + --bw 1 each side. Stated once, read four times below. */
  --index-size: 28px;

  position: absolute;
  top: 0;
  right: calc(100% + var(--stack-3));
  width: var(--index-size);
  height: var(--index-size);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;

  /* The pill box exactly: the same rule, the same corner, and the same fill a
     menu row rests on, so the button and the eight rows read as one set of
     objects rather than a control parked beside them. --surface-solid and not
     --surface for that reason — it is the token that means "a resting pill",
     and taking it here keeps the two in step if either ever moves.

     The fill does not travel on hover the way a row's does. It has somewhere
     better to go: the panel inside opens white, which is this button's version
     of lighting up — see ::before below. */
  border: var(--bw) dashed var(--line-soft);
  border-radius: var(--r-pill);
  background: var(--surface-solid);
  /* The ring that keeps the lit fill off the edge — the same inset shadow every
     pill carries now (links.css, the pill box). Declared here rather than
     inherited because that sheet's rule lists the five <a>/<button> classes and
     this control is not one of them. */
  box-shadow: inset 0 0 0 2px var(--surface-solid);

  /* The bars are painted by currentColor, so they are the button's ink and
     lift with it — one value to change, not two. */
  color: var(--ink-faint);
  transition: background-color var(--dur-state) ease,
              border-color var(--dur-state) ease,
              color var(--dur-state) ease;
}

/* display:block, not inline: an inline <svg> sits on the text baseline and
   leaves a few px of descender space under it, which would push the drawing
   off the centre the flexbox just put it on. */
.tl-toggle-icon {
  display: block;
  fill: currentColor;
}

/* ---- the bars, and the journey between the two views ----
   Geometry lives here and not in the markup so the transition has two ends to
   run between: x and width are real CSS properties on an SVG rect, so the bars
   SLIDE and STRETCH from one arrangement to the other instead of one drawing
   dissolving into another. It is the same move the menu is making — and it
   takes the same --dur-timeline the rows take, so the icon and the column
   arrive together.

   List: both bars flush left, a short one over a long one. Timeline: the top
   bar stretches and the bottom one steps right and shortens, and the pair
   becomes the staircase the rows form on the axis. */
.tl-icon-bar {
  transition: x var(--dur-timeline) ease,
              width var(--dur-timeline) ease;
}

.tl-icon-bar-1 { x: 3px; width: 9px; }
.tl-icon-bar-2 { x: 3px; width: 16px; }

.combined-menu.is-timeline .tl-icon-bar-1 { x: 3px; width: 12.5px; }
.combined-menu.is-timeline .tl-icon-bar-2 { x: 8px; width: 11px; }

/* Lit on hover exactly as a menu row is: the fill goes white, the rule goes
   black and the ink darkens with them. The ring above holds the white 2px off
   the edge, so it opens as a panel inside the frame rather than washing over
   it. Plain :hover is safe here — the .is-hovered dance in links.css exists
   because menu rows share one pointer resolution with the preview image, and
   that resolver only looks at `a[data-link-index]`. A <button> is invisible
   to it. */
.tl-toggle:hover {
  background: #ffffff;
  border-color: black;
  color: var(--ink-strong);
}

/* Type comes from .ts-year in tokens.css, worn in the markup: these are dates,
   and a date should read the same here as it does beside a project title. Only
   placement, ink and the fade are decided here. Sitting at top:0 in a row one
   body-line tall is the same top-alignment .ts-year takes everywhere else, and
   it leaves the row's height — and so the dashed rule below it — untouched. */
.tl-tick {
  position: absolute;
  top: 0;
  left: var(--tl-left);
  /* --rule, not an ink: the years read as part of the axis they label rather
     than as text sitting on it, so they take the same value the horizontal
     rule under them is drawn in. */
  color: var(--rule);
  opacity: 0;
  transition: opacity var(--dur-state) ease;
}

.combined-menu.is-timeline .tl-tick {
  opacity: 1;
}

/* Mobile responsive adjustments */
@media (max-width: 768px) {
  /* .main-frame owns the page gutter now, so the menu adds none of its own
     and the rule spans the same width as the content below it. */
  .combined-menu {
    padding: 0;

    /* --rule-gap is measured from the bottom of the menu box, and that box
       ends in a different place here: the year row below is display:none on a
       phone, so the container stops at the last pill instead of 30px past it
       (12px gap + an 18px row). The desktop's -3px would then draw the rule
       3px INSIDE the last pill. 27px puts it the same distance below that pill
       as on desktop. --content-gap is untouched — the margin is their sum, so
       the 80px down to the content follows on its own. */
    --rule-gap: 27px;
  }

  .combined-menu.combined-menu-inf-scroll::after,
  #combinedMenuOverlay::after {
    left: 0;
    right: 0;
  }

  .combined-menu a.indented {
    margin-left: 0;
  }

  /* A phone has no width for a four-year axis, and no gutter to hang the
     button in — .main-frame's padding is --gutter here, not 10vw — so the menu
     is a list and only a list. The button hands over to a plain "(index)" above
     the first row: the line keeps the height and the voice the menu always
     opened with: the line keeps the height and the voice the menu always opened
     with, and the page stops offering a view it cannot show. The mode can still
     be carried in from a desktop visit in the same session, so the geometry
     below is unwound rather than merely hidden. */
  .tl-toggle,
  .menu-tl-footer,
  .tl-grid {
    display: none;
  }

  .combined-menu span.menu-tl-static {
    display: block;
  }


  .combined-menu.is-timeline a {
    margin-left: 0;
    max-width: 100%;
  }
}
