/* Zoomable leaf-collage hero.
   Scoped entirely to the hero; the original .masthead rules are left untouched
   and simply unused, so reverting is a one-line change in index.html. */

@import url("fonts/roboto.css");

:root {
  --hero-black: #000;             /* must match make_dzi.py --bg or tiles seam */
  --hero-rail: #0d1f0d;           /* pure dark green frame (was teal #0d1f14 -- its
                                     extra blue read as grey next to williamweaver.xyz's
                                     pure green #0a1f0a; blue now equals red) */
  --hero-rail-edge: #071007;
  /* Artwork for the faded green leaf texture over the bars (see .hero-rail::before /
     .hero-railwrap::before). The LOSSLESS PNG is used on purpose: a compressed JPG
     rings the sharp leaf edges, which showed up as light halos once the filter and
     lighten blend amplified them. williamweaver.xyz uses this same PNG. */
  --hero-frontispiece: url("/img/frontispiece-01.png");
  --lm-green: #3db207;            /* site's bright green  */
  --lm-green-deep: #2c7509;
  --lm-green-bright: #5fd41f;       /* site's primary green */
  /* The tiled map is 27078x12430 = 2.178:1. Locking the hero to that ratio is
     what removes the letterbox: fitting a 2.18 image into a 1.6 stage is where
     all the black came from -- not from the rails, which only take 10% of width.
     Clamped so it can never exceed the window or collapse on narrow screens. */
  --hero-aspect: 2.178;
  /* The MAP band keeps the map's own ratio so it never letterboxes; the HEADER
     is a full viewport so the white page below stays out of sight until the
     visitor scrolls. Whatever height is left over becomes the black foot that
     carries the call to action. Capped at 74vh so the foot always has room. */
  /* Reserve the foot first, then give the map whatever height is left -- capped
     by its own aspect so it never grows wider than the stage. */
  --hero-foot-h: clamp(62px, 8.5vh, 104px);
  --hero-map-h: min(calc(86vw / var(--hero-aspect)),
                    calc(100vh - var(--hero-foot-h)));
  /* 100vh on a phone is the LARGE viewport -- the height with the browser toolbar
     retracted -- so the black foot and its "Learn More" button end up below the
     initially visible area, hidden under Android's collapsing toolbar. dvh is the
     *dynamic* viewport: it tracks the currently visible height, so the foot always
     sits just above the toolbar. vh stays as the fallback for older browsers. */
  --hero-h: 100vh;
  --hero-h: 100dvh;
}

/* The site's off-canvas menu parks 250px beyond the right edge, which has always
   given the page a horizontal scrollbar -- it was simply less visible against a
   light background. The menu is fixed and slides in, so clipping costs nothing. */
html, body { overflow-x: hidden; }

.masthead-osd {
  position: relative;
  display: block;
  width: 100%;
  height: var(--hero-h);
  min-height: 30rem;
  background: var(--hero-black);
  overflow: hidden;
  font-family: 'Roboto', 'Source Sans Pro', -apple-system, sans-serif;
}

/* Ordinary page area either side of the viewer. The viewer swallows the wheel to
   zoom, so these give the wheel somewhere to land that still scrolls the page. */
.hero-rail {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 7%;
  z-index: 4;                 /* also isolates the ::before blend to this bar */
  background: linear-gradient(to right, var(--hero-rail-edge), var(--hero-rail));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(6px, .9vh, 12px);
  padding: 12px 0;
}
.hero-rail-left  { left: 0; }
.hero-rail-right {
  right: 0;
  background: linear-gradient(to left, var(--hero-rail-edge), var(--hero-rail));
}

/* Frontispiece as a faded, green-tinted TEXTURE over each bar -- the same handling
   as williamweaver.xyz's .about-bg: cover-cropped (proportional, never squished),
   faded, recoloured to OUR hero green by a duotone blend, and lighten-blended so
   only its light parts glow over the dark gradient. A ::before per bar keeps it above the
   gradient but behind the tiles. A hidden bar is display:contents, which STILL
   generates its ::before -- and with no box to anchor to, that ::before escapes to the
   masthead and would tint the whole map. So each ::before is switched OFF in the layout
   where its bar is display:contents (the toggles just below, flipped in the narrow
   block). The image is one full-width backdrop; the rails just window onto its edges. */
.hero-rail::before,
.hero-railwrap::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;                /* above the gradient (layer 0), below the tiles */
  pointer-events: none;
  /* ONE continuous full-width backdrop, sized to the VIEWPORT width (not each bar),
     so the left rail shows the image's left edge and the right rail its right edge --
     a single william-style frontispiece running the whole width with the map viewer
     laid over the middle. */
  background-image: var(--hero-frontispiece);
  background-repeat: no-repeat;
  background-size: 100vw auto;
  /* williamweaver.xyz's .about-bg recipe. Because the blend is lighten, the texture
     only LIFTS the dark base and brightness() sets how much -- lowered from his .4 to
     .2 here so the bars read darker/blacker. Raise it back toward .4 to lighten. */
  opacity: .15;
  mix-blend-mode: lighten;
  filter: sepia(100%) hue-rotate(70deg) saturate(.5) brightness(.2);
}
/* The rails are the left and right slivers of that one full-width image; both are
   vertically centred so the two slivers line up across the map. The narrow bottom bar
   spans the full width, so it shows the whole image width, starting from the top. */
.hero-rail::before       { background-position: left center; }
.hero-rail-right::before { background-position: right center; }
.hero-railwrap::before   { background-position: center top; }
/* Lift the tile groups (and their labels) above that texture layer. */
.rail-group { position: relative; z-index: 1; }
/* Wide layout: the wrap is display:contents, so switch its escaping ::before off;
   the two rails are real boxes and paint the texture. Flipped in the narrow block. */
.hero-railwrap::before { display: none; }

/* A rail carrying more than one group scrolls rather than overflowing on a
   short window. The left rail holds projection + layout, the right holds the
   overlay switch on its own. */
.hero-rail-left, .hero-rail-right {
  justify-content: center;          /* groups sit centred in the bar */
  gap: clamp(12px, 2.4vh, 26px);
  overflow-y: auto;
  /* overflow-y:auto makes overflow-x compute to auto too, and sub-pixel group
     widths round up by a pixel or two; clip that rather than let a phantom
     horizontal scrollbar appear inside the rail. */
  overflow-x: hidden;
  scrollbar-width: none;
  padding: clamp(8px, 2vh, 22px) 0;
}
.hero-rail-right::-webkit-scrollbar { display: none; }
/* Label beside its tiles, reading up the rail and spanning the whole stack, so
   it brackets the group rather than captioning only the first tile. */
.rail-group {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: clamp(3px, .35vw, 11px);
}
.hero-rail-right .rail-group { flex-direction: row-reverse; }  /* label to the outer edge */

.rail-tiles {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(5px, .8vh, 10px);
}
.rail-label {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(10px, .82vw, 19px);
  font-weight: 500;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .5);
  user-select: none;
  flex: 0 0 auto;
}

/* ---- rail tiles (glass) --------------------------------------------------
   Periodic-table cell: index, symbol, name. Neutral glass until selected, then
   the site green. Sized off the rail width so it can never overflow it. */
.hero-tile {
  position: relative;
  width: clamp(46px, 5.05vw, 150px);
  /* A true square. min-height only set a FLOOR, so any tile whose name wrapped
     to two lines ("EQUAL EARTH", "SAMPLING DENSITY") grew taller than it was
     wide and the row came out ragged. */
  aspect-ratio: 1 / 1;
  padding: 5px 2px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, .13);
  border-radius: clamp(6px, .7vw, 10px);
  background: rgba(255, 255, 255, .045);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  cursor: pointer;
  user-select: none;
  transition: background .2s ease, border-color .2s ease,
              box-shadow .2s ease, transform .2s ease;
}
.hero-tile .t-i {
  position: absolute; top: 4px; right: 6px;
  font-size: clamp(9px, .8vw, 14px);
  color: rgba(255, 255, 255, .42);
}
.hero-tile .t-s {
  font-size: clamp(14px, 1.35vw, 22px);
  font-weight: 400; line-height: 1; color: #eef3ee;
}
.hero-tile .t-n {
  margin-top: 4px;
  font-size: clamp(10px, .9vw, 15px);
  line-height: 1.08;
  letter-spacing: .04em; text-transform: uppercase;
  color: rgba(255, 255, 255, .62);
  text-align: center; padding: 0 3px;
  /* "Equal Earth" is two words and must wrap rather than overflow the tile. */
  white-space: normal;
  overflow-wrap: break-word;
}
.hero-tile:hover {
  background: rgba(61, 178, 7, .15);
  border-color: rgba(61, 178, 7, .55);
}
.hero-rail:first-child  .hero-tile:hover { transform: translateX(2px); }
.hero-rail:last-child   .hero-tile:hover { transform: translateX(-2px); }
.hero-tile:focus-visible { outline: 2px solid var(--lm-green); outline-offset: 2px; }
.hero-tile[aria-pressed="true"] {
  background: rgba(61, 178, 7, .28);
  border-color: var(--lm-green-bright, #5fd41f);
  box-shadow: 0 0 18px rgba(61, 178, 7, .45);
}
.hero-tile[aria-pressed="true"] .t-s { color: #fff; }
.hero-tile[aria-pressed="true"] .t-n { color: rgba(255, 255, 255, .72); }
.hero-tile.is-busy { opacity: .45; pointer-events: none; }

/* No box in the wide layout: the two rails inside it are positioned against the
   header instead. It becomes a real element -- the single centred green bar --
   only in the narrow layout below. Positioning the wide rails absolutely is what
   lets the same DOM serve both, without display:contents on the column, which
   did not give usable grid items. */
.hero-railwrap { display: contents; }

.hero-col {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-width: 0;
  padding: 0 7%;          /* clears the two absolutely positioned rails */
}
.hero-stage {
  position: relative;
  flex: 1 1 auto;               /* take everything the foot does not need */
  min-height: 0;
}
.hero-foot {
  /* Fixed and thin. Previously this was flex:1 and silently absorbed every pixel
     the map could not use, which is why it measured 204px against a 81px setting. */
  flex: 0 0 auto;
  min-height: var(--hero-foot-h);
  background: var(--hero-black);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(.35rem, .9vh, .75rem);
  padding: clamp(.4rem, 1.1vh, .9rem) 1rem;
}
/* The viewer element is sized to the MAP's aspect and centred, so OpenSeadragon
   fits it edge to edge with no letterbox of its own. Without this the map is
   fitted into a wider stage and floats with black margins whose width changes
   every time the projection changes -- invisible on black, but it makes the tall
   projections render noticeably smaller than the wide ones. */
/* The viewer fills the whole stage: flush to the top, edge to edge, with only
   the thin foot below it. The map is then COVERED into it (homeFillsViewer in
   hero-collage.js) rather than fitted, so there is no black band anywhere --
   the trade is that the default view crops the map's long edges, which stay
   reachable by panning and zooming out. */
#osd-hero {
  position: absolute;
  inset: 0;
  background: var(--hero-black);
}
#osd-hero .openseadragon-canvas:focus { outline: none; }
/* Error fallback only. hero-collage.js reveals this when OpenSeadragon is
   missing, the manifest fails, or a tile source cannot open. No media query
   should ever show it -- see the small-screen note below. */
.hero-fallback { display: none; }

/* ---- title ---------------------------------------------------------------
   Light grey glyphs carrying the site's green as a glow rather than as fill,
   so the text stays legible over both the black ocean and the leaves. */
.hero-title {
  position: absolute;
  /* Vertical home lives in --hero-title-top (default 24%). hero-collage.js adds
     #hero.is-explored on the first zoom/pan, which drops this to 5%; the transition
     below makes it slide. The subtitle reads the same var so their gap is fixed. */
  left: 0; right: 0; top: var(--hero-title-top, 24%);
  z-index: 5;
  margin: 0;
  text-align: center;
  transition: top .6s ease;
  pointer-events: none;
  font-family: 'Roboto', sans-serif;
  font-weight: 300;
  font-size: clamp(2.2rem, 6.5vw, 5.5rem);
  letter-spacing: .14em;
  /* Solid fill in the glow's own colour, so glyph and halo are one light source
     rather than a grey letter sitting inside a green haze. */
  color: var(--lm-green);
  /* Glow strengthened ~50%: radii x1.5, alphas raised toward opaque. */
  text-shadow:
      0 0 9px   rgba(61, 178, 7, 1),
      0 0 27px  rgba(61, 178, 7, .88),
      0 0 63px  rgba(61, 178, 7, .58),
      0 0 135px rgba(61, 178, 7, .33);
  animation: hero-pulse 4.5s ease-in-out infinite;
}
/* The 3 stays the accent: same green, carried by weight and a touch more
   brightness rather than a different hue. */
.hero-title .lm-3 {
  font-weight: 700;
  color: #5fd41f;
}

.hero-sub-foot {
  position: static !important;
  top: auto !important;
  color: rgba(226, 232, 228, .72);
  text-shadow: 0 0 18px rgba(61, 178, 7, .30);
}

.hero-sub {
  position: absolute;
  /* Tracks the title's --hero-title-top so the two move together, keeping the
     same gap between them whether at home (24%) or explored (5%). */
  left: 0; right: 0; top: calc(var(--hero-title-top, 24%) + clamp(2.9rem, 8.2vw, 7.2rem));
  z-index: 5;
  text-align: center;
  pointer-events: none;
  transition: top .6s ease;
  font-family: 'Roboto', sans-serif;
  font-weight: 700;
  font-size: clamp(.72rem, 1.15vw, .95rem);
  letter-spacing: .34em;
  text-transform: uppercase;
  color: rgba(226, 232, 228, .82);
  /* The line sits over the map, so it needs its own contrast rather than
     relying on the black ocean being behind it. */
  text-shadow: 0 0 4px rgba(0, 0, 0, .95), 0 0 14px rgba(0, 0, 0, .8),
               0 0 26px rgba(61, 178, 7, .35);
}

/* Explored = the viewer is no longer at its home framing. hero-collage.js toggles
   this on #hero; the title block slides from its home spot up to 5% from the top. */
#hero.is-explored { --hero-title-top: 5%; }

@keyframes hero-pulse {
  0%, 100% { opacity: .93; }
  50%      { opacity: 1; }
}

/* ---- call to action ------------------------------------------------------ */
/* index.html carries an inline script that sets margin-top on this button for
   the old full-bleed masthead; it is bottom-anchored here, so override it.
   The .btn/.btn-primary classes are kept so that script still finds the node. */
.hero-foot .hero-cta {
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  margin-top: 0 !important;
  padding: .95rem 2.3rem !important;
  font-family: 'Roboto', sans-serif;
  font-weight: 500;
  font-size: .95rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: #eafbe3 !important;
  background: rgba(10, 22, 12, .55) !important;
  border: 1px solid rgba(61, 178, 7, .75) !important;
  border-radius: 2px;
  box-shadow: 0 0 0 rgba(61, 178, 7, 0), inset 0 0 18px rgba(61, 178, 7, .12);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background .25s ease, box-shadow .25s ease,
              color .25s ease, transform .25s ease;
}
.hero-foot .hero-cta:hover,
.hero-foot .hero-cta:focus {
  color: #fff !important;
  background: rgba(44, 117, 9, .72) !important;
  border-color: var(--lm-green) !important;
  box-shadow: 0 0 22px rgba(61, 178, 7, .55), inset 0 0 22px rgba(61, 178, 7, .25);
  transform: translateY(-1px);
}
.hero-foot .hero-cta .cta-arrow { transition: transform .25s ease; }
.hero-foot .hero-cta:hover .cta-arrow { transform: translateY(3px); }

/* ---- hint + controls ----------------------------------------------------- */
.hero-hint {
  position: absolute;
  left: 0; right: 0; bottom: 2.2rem;
  text-align: center;
  z-index: 5;
  color: rgba(255, 255, 255, .40);
  font-size: .72rem;
  font-weight: 300;
  letter-spacing: .18em;
  text-transform: uppercase;
  pointer-events: none;
  transition: opacity .6s ease;
}
.hero-hint.is-hidden { opacity: 0; }

/* OpenSeadragon's own zoom / home / fullscreen buttons, restyled to match the
   rail tiles: same glass panel, same green hover. brightness(0) crushes each
   sprite to solid black while keeping its alpha, then invert(1) makes it solid
   white -- independent of the sprite's own colours, unlike a plain invert which
   only lightens a mid-grey glyph. */
.masthead-osd .openseadragon-container div[title] {
  border: 1px solid rgba(255, 255, 255, .13) !important;
  border-radius: 8px !important;
  background: rgba(255, 255, 255, .045) !important;
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  margin: 7px 5px !important;
  /* NO padding. OpenSeadragon lays imgRest out as position:relative and stacks
     imgGroup/imgHover/imgDown as position:absolute top:0 left:0 over it. Padding
     shifts only the relative one, so the four states sit offset from each other
     and you see doubled icons while they cross-fade. The panel is enlarged with
     a box-shadow ring instead, which draws outside the box without moving it. */
  padding: 0 !important;
  box-shadow: 0 0 0 7px rgba(255, 255, 255, .045);
  transition: background .2s ease, border-color .2s ease, box-shadow .2s ease;
}
.masthead-osd .openseadragon-container div[title]:hover {
  background: rgba(61, 178, 7, .2) !important;
  border-color: rgba(61, 178, 7, .6) !important;
  box-shadow: 0 0 0 7px rgba(61, 178, 7, .2), 0 0 14px rgba(61, 178, 7, .35);
}
.masthead-osd .openseadragon-container div[title] > img {
  /* Bootstrap Icons are already solid white at the right size, so no filter is
     needed -- emphasis is opacity only. Do NOT set width/height here:
     OpenSeadragon stacks four absolutely positioned images per button and
     resizing them desynchronises the stack into a ghosted double image. */
  opacity: .8;
  transition: opacity .2s ease;
}
.masthead-osd .openseadragon-container div[title]:hover > img { opacity: 1; }

/* ---- small screens -------------------------------------------------------
   The viewer stays LIVE at every width. One finger scrolls the page rather than
   panning the map (gestureSettingsTouch.dragToPan is false in hero-collage.js),
   so a touch screen is not trapped and there is no reason to swap in a flat
   image. .hero-fallback is an ERROR fallback only, shown by the script when the
   library, manifest or tiles fail -- never by a width rule.

   This block previously hid #osd-hero and the rails and revealed .hero-fallback.
   Every one of those declarations was undone a few rules later by the narrow bar
   layout, which also matches below 400px: it restores the rails with
   display:contents and re-hides the fallback. The result was neither layout --
   a black map area with floating controls. Declarations that fight across two
   matching media queries are worse than either behaviour, so they are gone. */
@media (max-width: 399.98px) {
  .hero-hint { display: none; }    /* no room for it, and it fades anyway */
  #hero { --hero-title-top: 20%; } /* home sits a touch higher on tiny screens */
}

@media (prefers-reduced-motion: reduce) {
  .hero-title { animation: none; }
  .hero-title, .hero-sub,
  .hero-hint, .hero-foot .hero-cta { transition: none; }
}


/* The site's menu button is fixed 15px from the right edge, which lands it half
   on the map. Centre it in the right rail instead: the rail is 7vw, so its
   centre is 3.5vw in, less half the 50px button. Reverted below the breakpoint
   where the rails are hidden. */
@media (min-width: 992px) {
  .menu-toggle { right: calc(3.5vw - 25px) !important; }
}


/* ---- data overlay -------------------------------------------------------
   One SVG per source, added as an OpenSeadragon overlay covering the whole
   image, so it pans and zooms with the map for free. Polygons carry their own
   fill and opacity, resolved at build time. */
.hex-overlay {
  width: 100%;
  height: 100%;
  pointer-events: none;          /* never steal a drag from the viewer */
  overflow: visible;
}
.hex-overlay polygon { stroke: none; }

.overlay-legend {
  position: absolute;
  left: 50%;
  bottom: 10px;
  transform: translateX(-50%);
  z-index: 6;
  display: none;
  align-items: center;
  gap: 10px;
  padding: 7px 14px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, .13);
  background: rgba(0, 0, 0, .55);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  font-family: 'Roboto', sans-serif;
  font-size: clamp(9px, .72vw, 12px);
  font-weight: 300;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .82);
  white-space: nowrap;
  pointer-events: none;
}
.overlay-legend.is-on { display: flex; }
.overlay-legend .ramp {
  width: clamp(80px, 9vw, 150px);
  height: 9px;
  border-radius: 2px;
  border: 1px solid rgba(255, 255, 255, .18);
}
.overlay-legend .lo, .overlay-legend .hi { opacity: .75; }


/* ==========================================================================
   Vertical / narrow layout.

   Side rails only work while the window is wide enough to spare 7% twice over.
   On a portrait window -- or a narrow landscape one -- they collapse into a
   SINGLE horizontal green bar sitting between the map and the black foot, with
   every tile visible at once.

   The rails are siblings of .hero-col, not of the stage, so they cannot be
   reordered between stage and foot by flex alone. `display: contents` dissolves
   that wrapper so stage, rails and foot all become items of one grid, which can
   then place them in any order.
   ========================================================================== */


/* ==========================================================================
   Vertical / narrow layout.

   Side rails only work while the window can spare 7% twice over. Below that the
   tiles stop being left/right and fuse into a SINGLE centred green bar between
   the map and the black foot. The rails dissolve, so their groups become direct
   children of that bar and simply WRAP -- at the narrowest, projection / layout
   / overlay stack as three rows inside one object instead of being clipped
   inside two half-width rails.
   ========================================================================== */
@media (max-aspect-ratio: 1/1), (max-width: 1100px) {

  /* The bar is now a normal child of .hero-col, between stage and foot, so no
     grid or display:contents gymnastics are needed. */
  .hero-col { padding: 0; }
  .hero-stage { flex: 1 1 auto; min-height: 0; }

  .hero-railwrap {
    flex: 0 0 auto;
    position: relative;                 /* anchors + isolates the ::before texture */
    z-index: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: clamp(6px, 1.2vh, 12px) clamp(8px, 1.8vw, 24px);
    padding: clamp(6px, 1.2vh, 13px) clamp(4px, .8vw, 12px);
    /* One element, so the gradient runs the whole width of the tile section; the
       frontispiece texture rides on top via .hero-railwrap::before (defined above). */
    background: linear-gradient(to bottom, var(--hero-rail-edge), var(--hero-rail));
    border-top: 1px solid rgba(255, 255, 255, .06);
    min-width: 0;
  }
  /* Dissolve the rails so their groups become direct children of the bar and
     wrap as one object -- at the narrowest, projection / layout / overlay stack
     as three rows instead of being clipped inside two half-width rails. */
  .hero-rail-left, .hero-rail-right { position: static; display: contents; }
  /* Rails are now display:contents and the wrap is the real box, so flip which
     ::before paints the frontispiece texture (see the note by the shared rule). */
  .hero-rail::before     { display: none; }
  .hero-railwrap::before { display: block; }

  /* The bar wraps, a category never does: each .rail-group is one flex item and
     nowrap inside, so projection / layout / overlay drop to new rows whole
     rather than shedding individual tiles. */
  .rail-group {
    flex-direction: row;
    align-items: center;
    flex-wrap: nowrap;
    flex: 0 0 auto;
    gap: clamp(3px, .6vw, 9px);
  }
  .rail-tiles {
    flex-direction: row;
    flex-wrap: nowrap;
    gap: clamp(3px, .6vw, 9px);
  }

  /* In the side rails the overlay label sits on the OUTER edge, which means
     row-reverse. Once everything is in the single bottom bar there is no outer
     edge, so every label goes to the left of its tiles like the others. This
     needs the same two-class specificity as the rule it overrides -- a media
     query alone does not outrank `.hero-rail-right .rail-group`. */
  .hero-rail-right .rail-group { flex-direction: row; }

  /* Vertical here too: rotated, a category name costs ~13px of width rather than
     ~70px, and width is the scarce axis in a horizontal bar. */
  .rail-label {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    font-size: clamp(7px, .85vw, 12px);
    letter-spacing: .16em;
    white-space: nowrap;
    flex: 0 0 auto;
  }

  /* A real floor is what makes the bar WRAP. With a 33px minimum the tiles just
     kept shrinking to fit, so all three categories stayed on one row at every
     width and nothing ever wrapped -- they only became illegible. Holding the
     tile at a readable size means the bar genuinely runs out of room, and since
     each .rail-group is one nowrap flex item, a whole category is what moves to
     the next row. flex:0 0 auto stops the tiles being compressed instead. */
  .hero-tile {
    /* Sized off the VIEWPORT, not a fixed floor: 6vw is a couple of pixels on a
       phone, so tiles pinned to the 58px floor and looked shrunken on a large,
       high-DPI screen. ~19vw lets them grow with the display, and makes the
       widest group (4-tile overlay) very nearly fill the row -- which forces
       projection / layout / overlay each onto their own row (the wrapping that
       keeps every tile large). Capped so it can't overflow the bar's width. */
    width: clamp(62px, 19vw, 90px);
    aspect-ratio: 1 / 1;
    flex: 0 0 auto;
  }
  .hero-tile .t-s { font-size: clamp(15px, 4.2vw, 22px); }
  .hero-tile .t-n { font-size: clamp(9px, 2.4vw, 12px); }
  .hero-tile .t-i { font-size: clamp(8px, 1.9vw, 11px); }

  .hero-tile:hover { transform: translateY(-2px) !important; }

  .hero-fallback { display: none; }
}

/* The slide-out menu ships with z-index:2, which was fine over a flat photo but
   sits UNDER this hero's layers (rails 4, title 5, legend 6), so it would open
   behind the tiles. Lift it clear while preserving the original stacking with
   the toggle button, which the theme puts at 999 -- above the panel. */
#sidebar-wrapper { z-index: 998; }
.menu-toggle     { z-index: 999; }
