/* ============================================================
   SW NAV - bottom-centre pill nav
   Overlay stacks ABOVE the bar and expands upward.
   ============================================================ */

.sw-menu {
  /* Dark nav on a black page. The surfaces sit just above pure black
     so the panel separates from the page behind it, and the border is
     off-white rather than #fff - at 1.5px a pure white line glares. */
  --sw-bg: #000;             /* bar fill */
  --sw-bg-soft: #1b1b1b;     /* overlay fill, a step lighter than the bar */
  --sw-fg: #f4f4f2;
  --sw-muted: #8f8f88;
  --sw-border: #000;
  --sw-rule: #2e2e2a;        /* interior hairlines */
  --sw-pipe: #5a5a54;        /* bar dividers - must read against the black bar */

  /* Corner chamfer, in SVG user units - see the viewBox on each shape */
  --sw-notch: 14px;
  --sw-bw: 1.5px;            /* stroke width of the outline */

  /* Type face for the whole nav - neutral, precise, clean at any size */
  --sw-font-link: "Inter Tight", sans-serif;

  /* Bar height - matches the 60px bar in the reference */
  --sw-bar-h: 60px;

  /* Corner cut on the featured image - smaller than the panel's own */
  --sw-media-notch: 22px;

  /* Width of the featured image column */
  --sw-media-w: 300px;

  /* How long each slide holds before advancing. The timer bar reads
     this too, so the fill and the switch always stay in step. */
  --sw-media-interval: 4s;

  /* Menu icon - rendered size of the 100x100 SVG toggle */
  --sw-ico-size: 44px;

  /* Icon stroke weight, as a multiple of the nav's border width. */
  --sw-ico-weight: 1.6;

  /* How far the open panel is inset from the bar on each side. */
  --sw-panel-inset: 22px;

  /* Vertical offset between the panel and the bar. Negative pulls the
     panel down so its base tucks behind the bar, which paints over it -
     the two then read as one object rather than stacked slabs. */
  --sw-panel-gap: -20px;

  /* Breathing room kept between the panel's top and the viewport edge,
     including whatever the device reserves for a notch or status bar. */
  --sw-panel-safe: calc(3rem + env(safe-area-inset-top, 0px));

  will-change: transform;
  z-index: 1000;
  width: calc(100% - 4rem);
  max-width: 900px;
  /* Transition lives with the .hidden rule below */
  /* Absolute within .sw-menu-stage, which is the fixed element now */
  position: absolute;
  bottom: 2rem;
  left: 50%;
  pointer-events: auto;
  /* Centring transform is set with the hide transition below, so both
     states carry the same base and the nav cannot jump between them. */

  /* Column so the overlay can sit above the header in flow */
  display: flex;
  flex-direction: column;
}

/* Masks the overlay while it sits parked behind the bar.
   Padding on the sides/top leaves room for the panel's own shadow. */
/* Masks the panel while it is parked behind the bar.

   The mask sizes itself to the panel rather than reserving a fixed
   block: a fixed height taller than the panel is harmless, but one
   shorter than it silently crops the panel's top, since the panel is
   bottom-anchored and grows upward. Letting the content set the height
   and capping it with max-height removes that failure entirely. */
.sw-menu .sw-menu-overlay-clip {
  position: absolute;
  /* Extends down over the bar by the overlap, so the panel's tucked
     base is not clipped away before the bar can paint over it. The bar
     has the higher z-index, so it still covers this strip. */
  bottom: calc(100% + var(--sw-panel-gap));
  left: 0;
  right: 0;
  /* Never taller than the space between the bar and the viewport top. */
  max-height: calc(100vh - var(--sw-bar-h) - var(--sw-panel-safe));
  /* svh tracks the *small* viewport, so mobile browser chrome that
     appears on scroll cannot push the panel out of view. Older engines
     keep the vh value above. */
  max-height: calc(100svh - var(--sw-bar-h) - var(--sw-panel-safe));
  display: flex;
  align-items: flex-end;
  justify-content: center;   /* keeps the narrower panel centred on the bar */
  overflow: hidden;
  pointer-events: none;
}

/* Shut and not about to open: out of the page entirely, so the hidden
   panel costs nothing per frame. js/menu.js brings it back as the
   pointer or focus reaches the toggle - see shut() there. */
.sw-menu .sw-menu-overlay-clip.is-shut {
  display: none;
}

/* The panel is bottom-anchored, so anything that does not fit is cut
   off the TOP - silently, with no scrollbar to hint at it. Keeping the
   content shorter than the mask is what prevents that; the scroll below
   is only a last resort. */

.sw-menu .sw-menu-overlay-clip .sw-menu-overlay {
  pointer-events: auto;
}

/* Flipped (js/menu.js pickDirection): no room above the parked bar,
   so the mask hangs BELOW it and the panel is top-anchored, tucking
   its top edge up behind the bar instead of its base. max-height is
   set inline to the room actually left under the bar. */
.sw-menu.is-flipped .sw-menu-overlay-clip {
  bottom: auto;
  top: calc(100% + var(--sw-panel-gap));
  align-items: flex-start;
}

/* ------------------------------------------------------------
   Hide - "recede"

   The nav pulls backward and slightly down rather than sliding off.

   The perspective is written into the transform itself rather than
   set on an ancestor: `perspective` as a property would make body a
   containing block for fixed-position children, which would re-anchor
   this nav to the document and break `bottom` once the page scrolls.

   The centring translate is repeated in the hidden transform because
   a transform replaces the previous one rather than adding to it.
   ------------------------------------------------------------ */
/* The vanishing point sits well ABOVE the bar - roughly the middle of
   the viewport - so receding on Z carries the nav upward toward it
   rather than shrinking it in place. That rise is the whole effect.

   perspective() inside the transform cannot do this: it always uses
   the element's own centre as the origin. It needs to be the property,
   with an explicit perspective-origin - but the property makes an
   element a containing block for fixed descendants, so it goes on this
   wrapper and the nav inside it stays absolutely positioned. */
.sw-menu-stage {
  position: fixed;
  /* Anchored to the BOTTOM only, at a height that never changes.
     inset: 0 pinned it to the top and the bottom at once, so a phone
     resized it every time its toolbars slid in or out - and resized
     it late, once the slide had finished. Until then the bar rode up
     with the page, then snapped back into place. A box anchored to
     the bottom is carried by the browser through the slide, and
     100lvh (the viewport with the toolbars away) is one fixed
     number, so the perspective origin never moves either. */
  left: 0;
  right: 0;
  bottom: 0;
  height: 100vh;
  height: 100lvh;
  pointer-events: none;
  z-index: 1000;
  /* A longer focal length flattens the distortion - at 1200px the bar
     skewed noticeably as it receded, which is what read as an effect
     rather than depth. */
  perspective: 1800px;
  /* Vanishing point above the bar, so receding carries it gently
     upward. Higher on the screen means a more pronounced rise. */
  perspective-origin: 50% 38%;
}

/* Parked at the foot of .page while the footer is revealed - js/menu.js
   sets top and height so the box sits exactly where the fixed one was. */
.sw-menu-stage.is-parked {
  position: absolute;
  inset: auto 0 auto 0;
}

.sw-menu {
  transform: translate(-50%);
  /* Returning: slightly longer than the exit, and the opacity leads
     so the bar is already there as it settles rather than fading in
     after it has arrived. */
  transition: transform 0.78s cubic-bezier(0.22, 0.61, 0.24, 1),
              opacity 0.42s ease-out;
}

.sw-menu.hidden {
  /* Leaving: the fade runs across most of the move, not tacked on at
     the end - the bar dissolves INTO the distance instead of
     travelling there and then blinking out.

     No translateY: the perspective already carries it upward toward
     the vanishing point, and pushing down at the same time was what
     made the motion read as two gestures fighting each other. */
  transition: transform 0.72s cubic-bezier(0.4, 0.02, 0.3, 1),
              opacity 0.5s ease-in 0.12s;
  transform: translate(-50%) translateZ(-300px);
  opacity: 0;
  pointer-events: none;
}

/* ------------------------------------------------------------
   Bar
   ------------------------------------------------------------ */
/* The bar is a plain transparent flex box.
   Its shape and border are one SVG polygon painted behind the content -
   fill for the grey face, stroke for the border. One shape, one stroke. */
.sw-menu .sw-menu-header {
  background: none;
  border: none;
  z-index: 2;
  /* Only the toggle opens the menu, so the bar itself is not a target */
  cursor: default;
  justify-content: space-between;
  align-items: center;
  /* Fixed height, matching the 60px bar in the reference. Set explicitly
     so the icon's box can never inflate it. */
  height: var(--sw-bar-h);
  /* Tighter right side - the marquee wants the width more than the
     toggle wants the breathing room. */
  padding: 0 1.25rem 0 1.5rem;
  display: flex;
  position: relative;
}

/* The outline shape - stretches to the element, stroke stays uniform */
.sw-menu .sw-shape {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  overflow: visible;
}

.sw-menu .sw-shape path {
  fill: var(--sw-bg);
  stroke: var(--sw-border);
  stroke-width: var(--sw-bw);
  stroke-linejoin: miter;
}

/* Content sits above the painted shape */
.sw-menu .sw-menu-logo,
.sw-menu .sw-bar-pipe,
.sw-menu .sw-bar-stack,
.sw-menu .sw-menu-toggle {
  position: relative;
  z-index: 1;
}

/* ------------------------------------------------------------
   Bar text - static line above, marquee below
   ------------------------------------------------------------ */
.sw-menu .sw-bar-pipe {
  width: 1px;
  height: 28px;
  background: var(--sw-pipe);
  margin: 0 0.9rem;
  flex: none;
}

.sw-menu .sw-bar-stack {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  /* Takes the space between the logo and the toggle, and min-width:0
     lets it shrink so the marquee clips rather than pushing the
     toggle out of the bar. */
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

.sw-menu .sw-bar-line {
  font-size: 0.8rem;
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  /* Heavier than the marquee under it: this line names the section
     in view, so it should read first of the two. */
  font-weight: 600;
  color: var(--sw-fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Full track width, matching the marquee beneath it */
  width: 100%;
  text-align: center;
}

/* Once the script takes over, the line becomes a clipping box that
   two stacked text layers move through. */
.sw-menu .sw-bar-line--roll {
  position: relative;
  display: block;
  text-overflow: clip;
  /* Height is pinned so swapping layers cannot make the bar jump */
  height: 1.15em;
}

/* Full track width, not shrink-wrapped to the text: xPercent is a
   share of the element's OWN width, so a short label would otherwise
   travel only a few pixels and read as a fade rather than a slide. */
.sw-menu .sw-bar-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  white-space: nowrap;
  display: block;
  /* Absolutely positioned, so it needs its own alignment - the
     parent's text-align does not reach it. */
  text-align: center;
  will-change: transform, opacity;
}

/* The marquee track. Overflow hidden here is what clips the strip
   as it wraps. */
.sw-menu .sw-marquee {
  overflow: hidden;
  width: 100%;
  /* Room for the skew to lean without clipping the glyph tops */
  padding: 1px 0;
}

.sw-menu .sw-marquee-inner {
  display: flex;
  width: max-content;
  flex-direction: row;
  will-change: transform;
}

.sw-menu .sw-marquee-inner > span {
  flex: none;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 0.75em;
  padding-right: 0.75em;
  font-size: 0.8rem;
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--sw-muted);
}

/* Dot separator, drawn rather than typed so it stays optically even */
.sw-menu .sw-marquee-inner > span > i {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: currentColor;
  display: inline-block;
  flex: none;
  opacity: 0.8;
}

/* The logo is the green mark in img/logo-mark-green.svg, not a
   wordmark in type. The anchor is only the hit area - the colour
   is baked into the asset. */
.sw-menu .sw-menu-logo {
  cursor: pointer;
  flex: none;            /* never squeezed by the marquee beside it */
  display: flex;
  align-items: center;
  /* Kills the inline-element baseline gap under the image, which
     would otherwise push the mark off the bar's optical centre. */
  line-height: 0;
}

/* Height drives the mark off the bar; width is auto so the asset's own
   ratio decides it.

   The 3d file's box carries shadow bleed - 4 units left/top and 8
   right/bottom around the ink - so only about 84% of this height is the
   mark itself and the rest is the shadow falling down-right. The figure
   below is therefore the SHADOW BOX, not the mark: it stays a little
   larger than a flat mark would need so the lit face still reads at bar
   scale, while coming down from the old 0.72 so the lockup sits quieter
   in the bar than the flat mark did.

   The width/height attributes in the markup give the box its aspect
   ratio up front, so the bar cannot reflow when the file lands. */
.sw-menu .sw-menu-logo-mark {
  height: clamp(27px, calc(var(--sw-bar-h) * 0.63), 38px);
  width: auto;
  display: block;
}

.sw-menu .sw-menu-toggle {
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  /* Box matches the icon; the tap target is widened below
     without moving the icon itself. */
  width: var(--sw-ico-size);
  height: var(--sw-ico-size);
  display: block;
  position: relative;
  flex: none;            /* never squeezed by the marquee beside it */
}

/* Comfortable hit area - 44px min, per WCAG target size */
.sw-menu .sw-menu-toggle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 44px;
  height: 44px;
}

/* ------------------------------------------------------------
   Toggle - SVG stroke-dash hamburger
   After ainalem (codepen.io/ainalem/pen/LJYRxz), the "ham8" variant.

   Each line is one continuous path longer than what you see. Only a
   40-unit dash is painted; animating stroke-dashoffset slides that
   dash along the path, so the stroke appears to travel around the
   curve rather than rotate into place. The whole icon also turns 45deg.
   ------------------------------------------------------------ */
.sw-menu .sw-menu-hamburger-icon {
  cursor: pointer;
  width: var(--sw-ico-size);
  height: var(--sw-ico-size);
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  transition: transform 400ms;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  overflow: visible;
}

.sw-menu .sw-menu-hamburger-icon.open {
  transform: translateY(-50%) rotate(45deg);
}

.sw-menu .sw-ham-line {
  fill: none;
  stroke: var(--sw-fg);
  /* viewBox is 100 units wide, so convert the target pixel weight
     into viewBox units - the stroke then stays visually constant
     whatever --sw-ico-size is set to. */
  stroke-width: calc(var(--sw-bw) * var(--sw-ico-weight) * 100 / var(--sw-ico-size));
  stroke-linecap: round;
  transition: stroke-dasharray 400ms, stroke-dashoffset 400ms;
}

.sw-menu .sw-ham-top    { stroke-dasharray: 40 160; }
.sw-menu .sw-ham-bottom { stroke-dasharray: 40 85; }

.sw-menu .sw-ham-middle {
  stroke-dasharray: 40 142;
  transform-origin: 50%;
  transition: transform 400ms;
}

.sw-menu .sw-menu-hamburger-icon.open .sw-ham-top    { stroke-dashoffset: -64px; }
.sw-menu .sw-menu-hamburger-icon.open .sw-ham-bottom { stroke-dashoffset: -64px; }
.sw-menu .sw-menu-hamburger-icon.open .sw-ham-middle { transform: rotate(90deg); }

/* ------------------------------------------------------------
   Overlay - anchored to the bottom, scales up from its base
   ------------------------------------------------------------ */
.sw-menu .sw-menu-overlay {
  background: none;
  border: none;
  will-change: transform;
  padding: 2rem;
  position: relative;

  /* Two columns: links on the left, the image on the right spanning
     both content rows. The footer runs the full width beneath. */
  /* Slightly deeper at the foot than the other sides, so the credits
     are not sitting tight against the panel's bottom edge. */
  padding-bottom: 2.25rem;

  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto auto;
  column-gap: 3.5rem;
  row-gap: 1.25rem;
  /* Inset from the bar on both sides, so the panel reads as its own
     object sitting on the bar rather than a continuation of it. */
  width: calc(100% - var(--sw-panel-inset) * 2);
  /* The clip wrapper already carries the offset onto the bar, so the
     panel just sits flush at its bottom edge. */
  /* The panel sets the mask's height, and is itself capped by the
     mask's max-height. If content exceeds that, it scrolls inside
     the panel rather than overflowing past the top of the mask. */
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}

.sw-menu .sw-menu-overlay::-webkit-scrollbar {
  width: 0;
  display: none;
}

/* Overlay face is a touch lighter than the bar */
.sw-menu .sw-menu-overlay .sw-shape path {
  fill: var(--sw-bg-soft);
}

/* Overlay content rides above the face */
.sw-menu .sw-menu-overlay-items,
.sw-menu .sw-menu-overlay-row,
.sw-menu .sw-menu-overlay-footer {
  position: relative;
  z-index: 1;
}

/* Grid placement - the image column spans the two content rows, so the
   links and the reach/legal row sit beside it rather than under it. */
.sw-menu .sw-menu-overlay-items  { grid-column: 1; grid-row: 1; }
.sw-menu .sw-menu-overlay-row    { grid-column: 1; grid-row: 2; }
/* align-self: stretch so the column fills both spanned rows - the
   default would size it to its own content instead. */
.sw-menu .sw-menu-overlay-col-media {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: stretch;
}
.sw-menu .sw-menu-overlay-footer { grid-column: 1 / -1; grid-row: 3; }

.sw-menu .sw-menu-overlay-items {
  /* Tighter than before - the space was padding out the Navigate
     column rather than doing any work. */
  gap: 2.5rem;
  display: flex;
}

.sw-menu .sw-menu-items-header p {
  color: var(--sw-muted);
  font-family: var(--sw-font-link);
  font-size: clamp(0.7rem, 0.75vw, 0.8rem);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.sw-menu .sw-menu-overlay-col-sm {
  flex-direction: column;
  flex: 1.5;
  gap: 2rem;
  display: flex;
}


/* Align the "Navigate" header with the link text below it */
.sw-menu .sw-menu-overlay-col-sm .sw-menu-items-header {
  padding-left: 0.05em;
}

.sw-menu .sw-menu-overlay-col-sm .sw-menu-main-link {
  /* Full column width - the 70% inset was inherited from a wider
     layout and now just wastes space the image took. */
  width: 100%;
  color: var(--sw-fg);
  justify-content: space-between;
  align-items: center;
  display: flex;
  text-decoration: none;
}

.sw-menu .sw-menu-overlay-col-sm .sw-menu-main-link h4 {
  color: var(--sw-fg);
  font-family: var(--sw-font-link);
  font-weight: 500;
  text-transform: uppercase;
  /* Upper bound trimmed so the longest word still fits the narrower
     column - "CONTACT" at 2.25rem overran it. */
  font-size: clamp(1.4rem, 2.1vw, 1.95rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  display: block;
}

.sw-menu .sw-menu-overlay-col-lg {
  flex: 2.5;
  gap: 2rem;
  display: flex;
}

/* The last link has no closing border on desktop, so its bottom
   padding sits as dead space under the text - trimmed right back. */
.sw-menu .sw-menu-sub-links a:last-child {
  padding-bottom: 0.15rem;
}

/* The chevron is a small, light glyph next to a 700-weight label -
   sized up and dimmed so it reads as a marker rather than competing. */
.sw-menu .sw-menu-sub-links a > span:last-child {
  font-size: 1.35rem;
  line-height: 1;
  font-weight: 400;
  color: var(--sw-muted);
  transform: translateY(-1px);   /* optical centring against the caps */
}

/* ------------------------------------------------------------
   Social icons
   Inline SVG, stroked so the weight matches the nav's own border.
   ------------------------------------------------------------ */
/* ------------------------------------------------------------
   Second row - socials at the left, legal links at the right
   ------------------------------------------------------------ */
/* The two halves take the same flex ratios as the columns above, so
   Reach us lines up under Navigate and Legal under Menu. */
.sw-menu .sw-menu-overlay-row {
  display: flex;
  align-items: flex-end;
  gap: 2.5rem;   /* must match .sw-menu-overlay-items for the two to line up */
  position: relative;
  z-index: 1;            /* above the panel's painted shape */
  padding-top: 1.25rem;
  border-top: 1px solid var(--sw-rule);
}

.sw-menu .sw-reach,
.sw-menu .sw-legal {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Same ratios as the columns above so the two rows share a grid */
.sw-menu .sw-reach { flex: 1.5; }
.sw-menu .sw-legal { flex: 2.5; }

/* Right-hand group aligns to the panel edge */
.sw-menu .sw-legal {
  align-items: flex-start;
}

.sw-menu .sw-legal-links {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.sw-menu .sw-legal-links a {
  color: var(--sw-fg);
  font-family: var(--sw-font-link);
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-decoration: none;
}

/* Separator between links, not a marker on each. The dot is small and
   sits on the baseline, so it is sized to the links rather than up like
   the chevron was, and dimmed so it reads as punctuation. */
.sw-menu .sw-legal-sep {
  color: var(--sw-muted);
  font-size: 0.8rem;
  line-height: 1;
  opacity: 0.7;
  user-select: none;
}

/* The parent column's own gap provides the space above - no margin
   here, or the two would stack. */
.sw-menu .sw-socials {
  display: flex;
  gap: 1.1rem;
  align-items: center;
}

.sw-menu .sw-social {
  display: block;
  width: 20px;
  height: 20px;
  color: var(--sw-fg);
  transition: color 0.25s ease;
}

.sw-menu .sw-social svg {
  width: 100%;
  height: 100%;
  display: block;
  /* Solid brand marks, as in the footer (Simple Icons, CC0). */
  fill: currentColor;
}

/* Only the hovered icon changes - it takes the accent, the rest stay
   as they are. focus-visible gets the same cue for keyboard users. */
@media (hover: hover) {
  .sw-menu .sw-social:hover {
    color: var(--sw-accent);
  }
}

.sw-menu .sw-social:focus-visible {
  color: var(--sw-accent);
}

/* ------------------------------------------------------------
   Featured image widget - optional third column in the panel
   ------------------------------------------------------------ */
.sw-menu .sw-menu-overlay-col-media {
  /* Fixed width in the grid rather than a flex ratio - it now spans
     two rows, so its height comes from them, not its own aspect. */
  width: var(--sw-media-w);
  flex-direction: column;
  /* Tighter than the other columns - the timer belongs to the image
     directly above it, not as a separate block of content. */
  gap: 0.75rem;
  display: flex;
  min-width: 0;          /* lets the column shrink rather than overflow */
}

.sw-menu .sw-media {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: var(--sw-fg);
  /* Takes the height the grid row gives the column, so the frame's
     flex:1 has something to resolve against - without this the link
     shrink-wraps its caption and the image collapses to nothing. */
  flex: 1;
  min-height: 0;
}

/* Chamfered on one corner - the same cut as the nav, at a smaller
   radius, so the widget belongs to the panel rather than sitting in it. */
.sw-menu .sw-media-frame {
  position: relative;
  overflow: hidden;
  background: var(--sw-bg);
  /* Fills the height the two link rows set, rather than imposing its
     own - so the image is as tall as the content beside it. The floor
     means it can never collapse to nothing if that height is absent. */
  flex: 1;
  min-height: 200px;
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--sw-media-notch)),
    calc(100% - var(--sw-media-notch)) 100%,
    0 100%
  );
}

/* Slides are stacked; only the active one is opaque. */
.sw-menu .sw-media-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.7s ease;
}

.sw-menu .sw-media-img.is-active {
  opacity: 1;
}

/* Caption sits on the image itself, along the top edge. A gradient
   scrim behind it keeps the text legible over a light photo without
   darkening the whole frame. */
.sw-menu .sw-media-meta {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.85rem 1rem 1.6rem;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #fff;
  background: linear-gradient(to bottom,
              rgba(0, 0, 0, 0.55),
              rgba(0, 0, 0, 0));
  pointer-events: none;
}

.sw-menu .sw-media-title {
  font-weight: 600;
}

/* Dimmed against the scrim rather than the panel, so it stays legible
   over the image while still sitting back from the title. */
.sw-menu .sw-media-year {
  color: rgba(255, 255, 255, 0.72);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------
   Slide timer - one segment per slide, the active one fills
   ------------------------------------------------------------ */
.sw-menu .sw-media-timer {
  display: flex;
  gap: 4px;
  width: 100%;
}

.sw-menu .sw-media-tick {
  flex: 1;
  height: 2px;
  background: var(--sw-rule);
  overflow: hidden;
  position: relative;
}

/* The fill is driven by a transform, so it animates on the compositor
   rather than triggering layout on every frame.

   Painted in the page's accent rather than the nav's foreground: the
   bar is the one moving thing in the panel, and the green is what
   marks live state everywhere else on the site - the cursor label
   over anything clickable, the draggable pills, the footer. */
.sw-menu .sw-media-tick i {
  position: absolute;
  inset: 0;
  background: var(--sw-accent);
  transform: scaleX(0);
  transform-origin: left center;
}

/* Already-seen segments read as full */
.sw-menu .sw-media-tick.is-done i {
  transform: scaleX(1);
}

/* The active segment fills across the interval */
.sw-menu .sw-media-tick.is-active i {
  transform: scaleX(1);
  transition: transform var(--sw-media-interval) linear;
}

.sw-menu .sw-menu-overlay-col-lg .sw-menu-overlay-sub-col {
  flex-direction: column;
  flex: 1;
  gap: 2rem;
  display: flex;
}

.sw-menu .sw-menu-sub-links {
  flex-direction: column;
  display: flex;
}

.sw-menu .sw-menu-sub-links a {
  color: var(--sw-fg);
  border-top: 1px solid var(--sw-rule);
  will-change: transform, opacity;
  padding: 1rem 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
  font-family: var(--sw-font-link);
  font-size: 0.95rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-decoration: none;
}


.sw-menu .sw-menu-overlay-footer {
  border-top: 1px solid var(--sw-rule);
  justify-content: space-between;
  width: 100%;
  padding: 1rem 0 0;
  display: flex;
}


/* ------------------------------------------------------------
   Roll-over text
   Two stacked copies of each label, split into characters. On hover
   the visible copy rolls up and out while the duplicate rolls in
   from below, staggered per character.
   ------------------------------------------------------------ */
.sw-roll {
  /* Shrink-wraps the text and clips it - inline-block so the box is
     the glyphs' size, not the parent block's width. */
  display: inline-block;
  position: relative;
  overflow: hidden;
  vertical-align: top;
  /* Room for ascenders/descenders, so nothing is shaved while rolling */
  line-height: 1.15;
}

.sw-roll .sw-roll-face {
  display: inline-block;
  white-space: nowrap;
}

/* The duplicate sits exactly on top of the original */
.sw-roll .sw-roll-copy {
  position: absolute;
  top: 0;
  left: 0;
}

/* No will-change. There are some seventy of these across the panel,
   and promoting every one held seventy compositor layers for the
   whole visit - closed panel included - which the browser re-sorted
   on every frame anything else on the page moved. They only roll
   while hovered, and GSAP lifts the ones it is moving onto their
   own layers for the length of that tween. */
.sw-roll .sw-char {
  display: inline-block;
}

/* Preserve spaces, which would otherwise collapse once split */
.sw-roll .sw-char--space {
  white-space: pre;
}

.sw-menu .sw-menu-credit {
  color: var(--sw-muted);
  font-family: var(--sw-font-link);
  font-size: clamp(0.7rem, 0.75vw, 0.8rem);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ============================================================
   RESPONSIVE - mobile (<=1000px)
   ============================================================ */
@media (max-width: 1000px) {
  .sw-menu {
    width: calc(100% - 2rem);
    bottom: 1.5rem;
  }

  /* The mark comes down from 38px to 30px, on every page. On a phone
     the bar is only the mark and the toggle, and at desktop size the
     mark outweighed the button it sits opposite. This used to live in
     css/home-mobile.css, which only the index loads - so the index
     bar carried a 30px mark and every other page a 38px one. Here it
     is one size site-wide. The index loader measures the slot live
     when it flies the mark in, so the landing follows on its own. */
  .sw-menu .sw-menu-logo-mark {
    height: 30px;
  }

  /* The bar's own padding, trimmed to match the smaller mark. */
  .sw-menu .sw-menu-header {
    padding: 0 0.9rem 0 1.15rem;
  }

  /* The bar has no room for the text block beside a logo and toggle
     at phone width - the logo alone reads better than three cramped
     things competing. */
  .sw-menu .sw-bar-pipe,
  .sw-menu .sw-bar-stack {
    display: none;
  }

  /* Single column on mobile - the image spanning two rows only works
     when there is width to put it beside. */
  .sw-menu .sw-menu-overlay {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    row-gap: 1.5rem;
    padding: 1.5rem;
  }

  .sw-menu .sw-menu-overlay-col-media {
    grid-column: 1;
    grid-row: 1;          /* image leads, as before */
    width: auto;
  }

  .sw-menu .sw-menu-overlay-items  { grid-column: 1; grid-row: 2; }
  .sw-menu .sw-menu-overlay-row    { grid-column: 1; grid-row: 3; }

  .sw-menu {
    --sw-notch: 11px;
    /* Screen space is tight - the panel sits closer to the bar's edges
       than it does on desktop, buying back width for the content.
       The half-pixel trims 1px of total width. */
    --sw-panel-inset: 10.5px;
  }

  .sw-menu .sw-menu-overlay-items {
    flex-direction: column;
    gap: 2rem;
  }

  .sw-menu .sw-menu-items-header {
    display: none;
  }

  .sw-menu .sw-menu-overlay-col-lg {
    flex-direction: column;
  }

  /* Main links run as a horizontal row on mobile. Four short words
     stacked vertically at display size eat most of the panel; in a
     row they take one or two lines and read as an index. */
  .sw-menu .sw-menu-main-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.25rem;
    /* Breathing room under the links, which sit last on mobile */
    padding-bottom: 1.5rem;
  }

  .sw-menu .sw-menu-overlay-col-sm .sw-menu-main-link {
    width: auto;
  }

  .sw-menu .sw-menu-overlay-col-sm .sw-menu-main-link h4 {
    /* Stepped down from display size - it is a row of labels now,
       not a stack of headlines. */
    font-size: 1.125rem;
    letter-spacing: 0.01em;
  }

  /* The second row keeps socials and legal side by side - both are
     small enough to share a line even at phone width. */
  .sw-menu .sw-menu-overlay-row {
    padding-top: 1.25rem;
    gap: 1rem;
  }

  .sw-menu .sw-legal-links {
    gap: 0.4rem;
  }

  .sw-menu .sw-legal-links a,
  .sw-menu .sw-legal-sep {
    font-size: 0.7rem;
  }

  /* Mobile running order: image, then contact, then the page links.
     Set explicitly on all three rather than leaving two to source
     order, so the sequence cannot drift if the markup is reordered. */
  .sw-menu .sw-menu-overlay-col-media { order: 1; }   /* image */
  .sw-menu .sw-menu-overlay-col-lg    { order: 2; }   /* email / whatsapp / linkedin */
  .sw-menu .sw-menu-overlay-col-sm    { order: 3; }   /* home / work / studio */

  .sw-menu .sw-media-frame {
    /* Height-driven rather than ratio-driven: the frame stays full
       width and the image crops, so a short viewport shrinks the band
       instead of narrowing it. */
    aspect-ratio: auto;
    /* flex-basis rather than height, so the frame can be compressed by
       the flex algorithm when the panel runs out of room. */
    flex: 0 1 auto;
    height: clamp(110px, 24vh, 180px);
    height: clamp(110px, 24svh, 180px);
    min-height: 90px;
  }

  /* The image is the one element allowed to shrink below its preferred
     height. Everything else in the panel is text that must stay legible,
     so if space runs short the picture gives way rather than the links
     being pushed off the top of the mask. */
  .sw-menu .sw-menu-overlay-col-media {
    min-height: 0;
    flex-shrink: 1;
  }

  /* The link between the column and the frame. Without this the chain
     breaks here: the column is allowed to shrink, but .sw-media keeps
     the default min-height:auto and so never resolves a height to hand
     down. The frame then collapses and the absolutely-positioned slides
     have no box to fill, which reads as a missing image. */
  .sw-menu .sw-media {
    min-height: 0;
    flex: 1 1 auto;
  }

  /* The caption is text, so it holds its size and the frame above it
     takes the compression - the same rule the panel applies to its
     other columns. */
  .sw-menu .sw-media-meta {
    flex: 0 0 auto;
  }

  .sw-menu .sw-menu-overlay-col-sm,
  .sw-menu .sw-menu-overlay-col-lg {
    flex-shrink: 0;
  }

  /* Short viewports: the panel's padding tightens before anything has
     to scroll. The horizontal link row already recovered enough space
     that the footer can stay. */
  @media (max-height: 700px) {
    .sw-menu .sw-menu-overlay {
      padding: 1.25rem;
    }

    .sw-menu .sw-media-frame {
      height: clamp(110px, 22vh, 150px);
      height: clamp(110px, 22svh, 150px);
    }
  }

  /* Tighter vertical rhythm so the extra media block still fits the
     panel without the layout needing to scroll on a small screen. */
  .sw-menu .sw-menu-overlay-items {
    gap: 1.5rem;
  }

  .sw-menu .sw-menu-sub-links a {
    padding: 0.7rem 0;
  }

  /* Closing rule under the last link - mobile only. On desktop the
     list ends open, which reads cleaner beside the image column. */
  .sw-menu .sw-menu-sub-links a:last-child {
    border-bottom: 1px solid var(--sw-rule);
    padding-bottom: 0.7rem;
  }


  /* The credits are desktop-only - on mobile the space is better spent
     on the image and the links. */
  .sw-menu .sw-menu-overlay-footer {
    display: none;
  }

  /* ----------------------------------------------------------
     Touch targets. A finger needs about 44px; several of these
     controls are drawn much smaller than that on purpose - the
     20px social icons and the 0.7rem legal links are sized to
     read as fine print, and growing them would coarsen the
     panel.

     So the HIT AREA grows and the artwork does not. Each target
     keeps its drawn size and gains a centred pseudo-element
     padded out to 44px, which takes the tap. The pseudo is
     transparent and sits above the icon but below nothing else,
     so the layout, the spacing and the type stay exactly as
     they were on a wide screen.
     ---------------------------------------------------------- */
  .sw-menu .sw-social,
  .sw-menu .sw-legal-links a,
  .sw-menu .sw-menu-overlay-col-sm .sw-menu-main-link {
    position: relative;
  }

  .sw-menu .sw-social::after,
  .sw-menu .sw-legal-links a::after,
  .sw-menu .sw-menu-overlay-col-sm .sw-menu-main-link::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    /* Never narrower than the element itself - on a wide label
       like "Privacy" the box tracks the text rather than
       pinching in to a fixed 44px. */
    width: max(100%, 44px);
    height: max(100%, 44px);
  }

  /* The socials sit in a row 1.1rem apart, so two 44px boxes
     would overlap and the wrong icon would take the tap. Widening
     the gap to 1.5rem keeps each box clear of its neighbour while
     the icons stay where they were. */
  .sw-menu .sw-socials {
    gap: 1.5rem;
  }

  /* Same collision on the legal row, which is tighter still. The
     separators are decorative, so they are pulled out of the way
     rather than being given a box of their own. */
  .sw-menu .sw-legal-links {
    gap: 0.75rem;
  }

  .sw-menu .sw-legal-sep {
    pointer-events: none;
  }
}

/* Lock background scroll while the overlay is open */
/* Page dim while the panel is open (js/menu.js builds the element).
   z-index 999 sits under the nav stage's 1000; being later in the DOM
   than the tag pills lets it cover them at their own 999 ceiling. */
.sw-menu-scrim {
  position: fixed;
  inset: 0;
  z-index: 999;
  background: rgba(12, 12, 10, 0.45);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.45s ease, visibility 0s linear 0.45s;
}

body.menu-open .sw-menu-scrim {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.6s cubic-bezier(0.22, 0.61, 0.24, 1), visibility 0s;
}

body.menu-open {
  overflow: hidden;
  touch-action: none;
}

