/* =========================================================================
   Barry Borsboom — portfolio
   -------------------------------------------------------------------------
   The deck is a stack of sticky, full-height panels. Native scrolling is
   never intercepted: each panel simply sticks at the top of the viewport
   while the next one slides up over it, so the wheel, trackpad, scrollbar,
   keyboard and touch all behave exactly as the browser intends.

   js/site.js writes two numbers onto every panel and everything visual is
   derived from them in CSS:
     --enter  0 → 1   this panel sliding up over the previous one
     --cover  0 → 1   the next panel sliding up over this one
   ========================================================================= */

:root {
  --font-display: 'Montserrat', 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Merriweather', Georgia, 'Times New Roman', serif;

  --ink: #fff;
  --ink-dim: rgba(255, 255, 255, .72);
  --void: #08090b;                 /* shows behind receding panels */

  --gutter: clamp(1.5rem, 7vw, 7.5rem);
  --bar-h: 3.5rem;

  --radius: 30px;                  /* corner radius of a fully receded panel */
  --recede: .08;                   /* how far a covered panel scales down */
  --sink: 2.4vh;                   /* how far it drifts down as it goes behind */
  --dim: .5;                       /* how far a covered panel darkens */
  /* Blur is OFF. Animating filter:blur from 0 makes the compositor build and
     tear down a full-screen render surface at the start and end of every
     transition, which flashes a frame — most visibly on the highest-contrast
     photos (Sailmon, Eneco & Oxxio). Set this back to 5px to restore the
     effect if the flicker turns out to come from somewhere else. */
  --blur: 0px;

  --ease: cubic-bezier(.16, 1, .3, 1);
  --ease-soft: cubic-bezier(.4, 0, .2, 1);

  /* Liquid glass. --glass-frost is the white tint in the button body,
     --glass-saturation lifts the colour of whatever shows through it. */
  --glass-frost: .1;
  --glass-saturation: 1.4;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  /* `proximity`, not `mandatory`, and this is the whole point of the rebuild.
     Mandatory snap on full-height panels swallows small gestures outright —
     arrow keys and short trackpad flicks land nowhere — which is just a softer
     kind of scroll hijacking. Proximity only settles a panel when you already
     stopped near its edge, so no input is ever thrown away. Whole-panel paging
     is offered explicitly instead: keyboard, the rail and the next button.
     Swap in `y mandatory` here if you ever want the stricter deck feel. */
  scroll-snap-type: y proximity;
  /* Deliberately NOT scroll-behavior: smooth — combined with snapping it makes
     each wheel gesture re-target mid-animation and run away to the last panel.
     Smoothing is opted into per jump in js/site.js instead. */
  background: var(--void);
  -webkit-text-size-adjust: 100%;
}
/* Touch is the one input where mandatory snap is the right call. A thumb swipe
   rarely clears half a panel, so with proximity it lands mid-transition and
   needs a second swipe; mandatory makes the browser carry the flick through to
   the adjacent panel, and scroll-snap-stop keeps a hard flick from skipping
   several at once. Deliberately scoped to coarse pointers: on a wheel or
   trackpad, mandatory swallows small gestures, which is the behaviour this
   rebuild set out to remove. */
@media (pointer: coarse) {
  html { scroll-snap-type: y mandatory; }
  .panel { scroll-snap-stop: always; }
}

body {
  margin: 0;
  background: var(--void);
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 400;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }
.sprite { position: absolute; }

/* Icons are drawn as strokes so they inherit the text colour and weight. */
.ico {
  width: 1em; height: 1em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
  border-radius: 4px;
}

.skip {
  position: fixed;
  top: .75rem; left: .75rem;
  z-index: 100;
  padding: .6rem 1rem;
  border-radius: 999px;
  background: #fff;
  color: #111;
  font: 600 .8rem/1 var(--font-display);
  transform: translateY(-200%);
  transition: transform .25s var(--ease-soft);
}
.skip:focus-visible { transform: none; }

/* ── Deck ─────────────────────────────────────────────────────────────── */

.deck { position: relative; }

/* The snap target. Deliberately free of transforms: scroll-snap-align is
   resolved against the *transformed* border box, so scaling this element would
   drag its own snap position around and the scroller would never settle. */
.panel {
  --enter: 1;                      /* sensible values if JS never runs */
  --cover: 0;

  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  scroll-snap-align: start;
}

/* All the motion lives here instead. */
.panel__card {
  position: absolute;
  inset: 0;
  overflow: clip;
  isolation: isolate;
  background: var(--tint, #14161a);   /* holds the frame while media loads */

  /* Rounded while arriving (top corners only, so it reads as a card lifting
     over the panel below) and rounded on every corner while receding.

     The constant 1px keeps the corners very slightly rounded at all times: with
     overflow:clip, a radius of exactly 0 uses a plain rectangular clip, and the
     switch to a rounded-rect mask on the first non-zero frame is another source
     of flicker. 1px is invisible and keeps the clip in one mode.

     The arriving corners also square off by the time the panel is half in, not
     at the very end. Rounded corners on a card that is already covering the
     screen are a peephole onto the panel behind it, and that is exactly what
     shows if --enter is even slightly behind during a fast scroll. */
  --r-in: calc(1px + var(--radius) * max(0, 1 - var(--enter) * 2));
  --r-out: calc(1px + var(--radius) * var(--cover));
  border-radius:
    max(var(--r-in), var(--r-out)) max(var(--r-in), var(--r-out))
    var(--r-out) var(--r-out);
  /* Sinks down as well as shrinking, so a gap opens along the top edge and the
     panel reads as dropping back behind the incoming one. Scaling from the
     centre rather than 50% 40% matters here: an origin above centre pulls the
     top edge back up and cancels most of that gap. */
  transform:
    translate3d(0, calc(var(--sink) * var(--cover)), 0)
    scale(calc(1 - var(--recede) * var(--cover)));
  transform-origin: 50% 50%;
}

/* Every panel but the first casts a shadow onto the one it slides over. */
.panel + .panel .panel__card { box-shadow: 0 -34px 70px -14px rgba(0, 0, 0, .7); }

.panel__media {
  position: absolute;
  inset: -7% 0;                    /* headroom so the parallax never gaps */
  z-index: 0;
  /* Drifts up more slowly than the panel itself, then pushes back on exit. */
  transform:
    translate3d(0, calc(5% * (1 - var(--enter))), 0)
    scale(calc(1 + .05 * var(--cover)));
  filter: blur(calc(var(--blur) * var(--cover)));
}
.panel__media picture, .panel__media img { display: block; width: 100%; height: 100%; }
.panel__media img { object-fit: cover; object-position: var(--focus, 50% 50%); }

/* Guarantees legible text over any photo, bright or dark. */
.panel__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(100deg,
      rgba(6, 8, 12, .76) 0%,
      rgba(6, 8, 12, .6) 26%,
      rgba(6, 8, 12, .25) 54%,
      rgba(6, 8, 12, .03) 78%,
      rgba(6, 8, 12, 0) 100%),
    linear-gradient(to top, rgba(6, 8, 12, .46), rgba(6, 8, 12, 0) 45%);
}

/* The receding-into-the-dark layer. Sits above everything in the panel. */
.panel__dim {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: #05070a;
  opacity: calc(var(--dim) * var(--cover));
}

.panel__inner {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 6rem var(--gutter) calc(var(--bar-h) + 3rem);
}

.panel__body {
  width: 100%;
  max-width: 40rem;
  transform: translate3d(0, calc(-3.25rem * var(--cover)), 0);
  opacity: calc(1 - var(--cover) * 1.7);
}

/* ── Type ─────────────────────────────────────────────────────────────── */

/* Belt and braces on top of the scrim: a soft shadow keeps the copy readable
   wherever a photo turns out lighter than expected. */
.title, .lede, .eyebrow { text-shadow: 0 1px 14px rgba(4, 6, 10, .5); }

.title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.2rem, 6vw, 4.25rem);
  line-height: 1.05;
  letter-spacing: -.005em;         /* was -.025em: the tight tracking made the
                                      big Montserrat titles feel cramped */
  text-wrap: balance;
  margin-bottom: 1.25rem;
}
/* Sized to stay on one line in its column at every width. */
.title--hero { font-size: clamp(2.5rem, 7.4vw, 5.25rem); }

/* Second line of the h1: the searchable role and location, set as a quiet
   standfirst so it reads as a subtitle rather than competing with the name. */
.title__sub {
  display: block;
  margin-bottom: .8rem;
  font-size: clamp(.82rem, .5vw + .68rem, 1rem);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: .01em;
  color: rgba(255, 255, 255, .86);
  text-wrap: pretty;
}

.eyebrow {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: .74rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: .9rem;
}

.lede {
  font-size: clamp(.95rem, .35vw + .87rem, 1.1rem);
  max-width: 32em;                 /* ~70 characters, and keeps the copy inside
                                      the dark part of the scrim */
  color: rgba(255, 255, 255, .92);
  text-wrap: pretty;
  margin-bottom: 1.75rem;
}

/* ── Availability pill ────────────────────────────────────────────────── */

/* Button and availability note share a row under the logo bar. */
.cta-row {
  display: flex;
  align-items: center;
  gap: 1.15rem;
  flex-wrap: wrap;
}

/* Plain white text now rather than a white pill, so it reads as a note beside
   the button instead of a second button competing with it. */
.status {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  color: #fff;
  /* Same face and weight as .lede so it reads as body copy, held at a fixed
     14px rather than scaling with the body size. */
  font-family: var(--font-body);
  font-size: .875rem;              /* 14px */
  font-weight: 400;
  line-height: 1.45;
  text-shadow: 0 1px 12px rgba(4, 6, 10, .6);
}
.status__radar { position: relative; width: .7rem; height: .7rem; }
.status__core, .status__ping {
  position: absolute; inset: 0;
  border-radius: 50%;
  background: #4ec95a;
}
.status__ping { animation: ping 2.8s var(--ease-soft) infinite; }
@keyframes ping {
  0%   { transform: scale(1);   opacity: .65; }
  70%  { transform: scale(3.4); opacity: 0; }
  100% { transform: scale(3.4); opacity: 0; }
}

/* ── Links ────────────────────────────────────────────────────────────── */

.links { display: flex; flex-wrap: wrap; gap: .6rem; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .55rem 1.05rem;
  border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 999px;

  /* Liquid glass, part one: the body. Frost tint, a specular sheen down from the
     top edge, and a soft bounce of light coming back up from the bottom — the
     three things that read as a thick glass lozenge. Stacked as background
     layers rather than a pseudo-element so the label stays on top without any
     stacking-context juggling. */
  background:
    linear-gradient(to bottom, rgba(255, 255, 255, .15), rgba(255, 255, 255, 0) 46%),
    radial-gradient(125% 150% at 50% 135%, rgba(255, 255, 255, .09), rgba(255, 255, 255, 0) 58%),
    hsl(0 0% 100% / var(--glass-frost, .1));

  /* A hairline just inside the edge, a soft bloom behind it, then gentle dark
     falloff from the top. This is what gives the button thickness — kept
     restrained so it reads as an edge rather than a lit ring. */
  box-shadow:
    inset 0 0 2px 0 rgba(255, 255, 255, .22),
    inset 0 0 8px 2px rgba(255, 255, 255, .06),
    inset 0 3px 12px rgba(17, 17, 26, .06),
    inset 0 6px 20px rgba(17, 17, 26, .05);

  font: 600 .84rem/1.4 var(--font-display);
  transition:
    background .28s var(--ease-soft),
    border-color .28s var(--ease-soft),
    box-shadow .28s var(--ease-soft),
    transform .28s var(--ease);
}
.pill:hover, .pill:focus-visible {
  --glass-frost: .2;
  border-color: rgba(255, 255, 255, .7);
  transform: translateY(-2px);
}

/* Pressed. Must come after :hover — same specificity, so source order decides.
   The glass settles back down and slightly in, the outer sheen drops away and
   the top inset goes dark, which reads as the surface being pushed rather than
   just dimmed. Fast on the way in, and it inherits the slower base transition on
   release, so it snaps under the finger and eases back. */
.pill:active {
  --glass-frost: .16;
  transform: translateY(0) scale(.975);
  box-shadow:
    inset 0 0 2px 0 rgba(255, 255, 255, .14),
    inset 0 2px 7px rgba(4, 6, 10, .3),
    inset 0 6px 16px rgba(4, 6, 10, .16);
  transition-duration: .07s;
}

/* Liquid glass, part two: the backdrop. Blurring and lifting the saturation of
   whatever shows through is what separates glass from a flat translucent chip.
   This is the only part that samples behind the button, so it is the only part
   with a cost — the backdrop it reads is a panel that scales and dims as you
   scroll. Delete these two lines and the buttons keep their glass body. */
.pill {
  -webkit-backdrop-filter: blur(6px) saturate(var(--glass-saturation));
  backdrop-filter: blur(6px) saturate(var(--glass-saturation));
}

/* ── Logo marquee (intro panel only) ──────────────────────────────────────
   Two identical lists sit side by side and the track slides exactly half its
   width, which lands the second list precisely where the first began — so the
   loop has no seam and needs no JavaScript. */

.marquee {
  /* In the content flow, directly under the copy, so it inherits the body's
     width rather than bleeding across the panel. */
  position: relative;
  margin-block: 2.25rem 1.9rem;    /* room for the CTA underneath */
  overflow: hidden;
  /* Logos ease in and out at the edges instead of appearing at a hard line. */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent);
}
.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee 45s linear infinite;
}
.marquee:hover .marquee__track { animation-play-state: paused; }

.marquee__group {
  display: flex;
  align-items: center;
  gap: clamp(2rem, 4.5vw, 3.75rem);
  padding-right: clamp(2rem, 4.5vw, 3.75rem);
  margin: 0;
  padding-left: 0;
  list-style: none;
}
.marquee__group li { display: flex; align-items: center; }

/* Every logo is flattened to white regardless of its own colours, which is what
   makes a mixed set of brand marks read as one row. brightness(0) zeroes the
   RGB and invert(1) lifts it to white; both leave alpha alone, so a logo that
   already has a transparent background survives unchanged. --h tunes any mark
   that reads too heavy at the shared height. */
.marquee__group img {
  display: block;
  height: var(--h, 1.45rem);
  width: auto;
  opacity: .82;
  filter: brightness(0) invert(1) drop-shadow(0 1px 8px rgba(0, 0, 0, .5));
}

@keyframes marquee {
  to { transform: translateX(-50%); }
}

/* ── Testimonials panel ───────────────────────────────────────────────────
   The one panel with no photograph. It behaves like every other panel — same
   sticky stacking, same recede — but its card is a bento of quotes, so the
   body is allowed to run full width instead of sitting in a 40rem column. */

.panel--quotes {
  --quotes-pad: clamp(1.25rem, 4vw, 4.5rem);
}
.panel--quotes .panel__card {
  background:
    radial-gradient(115% 85% at 12% 0%, #232a3d 0%, rgba(35, 42, 61, 0) 58%),
    radial-gradient(95% 80% at 92% 100%, #2b2142 0%, rgba(43, 33, 66, 0) 55%),
    #0d0f15;
}
.panel--quotes .panel__inner {
  padding: clamp(3.5rem, 9vh, 5.5rem) var(--quotes-pad) calc(var(--bar-h) + 2rem);
}
.panel--quotes .panel__body {
  display: flex;
  flex-direction: column;
  max-width: 82rem;
  margin-inline: auto;
}
.title--sm {
  font-size: clamp(1.75rem, 3.1vw, 2.6rem);
  margin-bottom: 1.6rem;
}

/* Rows size to the tallest card rather than stretching to fill the panel —
   stretching left the two-line quotes mostly empty. */
.bento {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: 1fr;
  gap: .8rem;
}
/* The long quote takes the double-height cell on the left. With the tall cell
   plus four singles the 3x2 grid fills exactly; if the card count ever leaves a
   hole, put --wide on one card to span the gap. */
.bento__cell--tall { grid-row: span 2; }
.bento__cell--wide { grid-column: span 2; }

.bento__cell {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 1rem;
  margin: 0;
  padding: clamp(1.1rem, 1.6vw, 1.6rem);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 16px;
  background: rgba(255, 255, 255, .055);
  transition: background .3s var(--ease-soft), border-color .3s var(--ease-soft), transform .4s var(--ease);
}
.bento__cell:hover {
  background: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .26);
  transform: translateY(-3px);
}

/* One size across every card, long quote or short. */
.bento blockquote {
  margin: 0;
  font-size: clamp(.84rem, .45vw + .66rem, 1rem);
  line-height: 1.6;
  color: rgba(255, 255, 255, .93);
  text-wrap: pretty;
}

.bento figcaption {
  font: 600 .68rem/1.35 var(--font-display);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #fff;
}
.panel--quotes .cta-row { margin-top: 1.9rem; }

.bento figcaption span {
  display: block;
  margin-top: .3rem;
  letter-spacing: .04em;
  text-transform: none;
  color: var(--ink-dim);
}

/* ── Staggered reveal ─────────────────────────────────────────────────── */

.js .reveal {
  opacity: 0;
  transform: translate3d(0, 1.4rem, 0);
  transition:
    opacity .75s var(--ease-soft) calc(var(--i, 0) * 85ms),
    transform .95s var(--ease) calc(var(--i, 0) * 85ms);
}
.js .panel.is-active .reveal { opacity: 1; transform: none; }

/* ── Fixed chrome ─────────────────────────────────────────────────────── */

/* Section rail — replaces the old pagination dots. Dots only: no connecting
   line, so the active dot is the only mark that reads as position. The rail is
   wider than the dots to give the links a usable hit area, and the tooltips are
   absolutely positioned so they never affect that width. */
.rail {
  --tip-bg: rgba(13, 15, 19, .94);

  position: fixed;
  top: 50%;
  right: clamp(.5rem, 1.4vw, 1.4rem);
  z-index: 20;
  display: grid;
  justify-items: center;
  gap: .5rem;
  transform: translateY(-50%);
}
.rail__list { display: grid; gap: .3rem; }

/* Step arrows, bare icons rather than buttons — they sit directly above and
   below the dots as part of the same control. */
.rail__step {
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  padding: 0;
  border: 0;
  background: none;
  color: rgba(255, 255, 255, .78);
  font-size: 1.35rem;
  cursor: pointer;
  filter: drop-shadow(0 1px 6px rgba(0, 0, 0, .7));
  transition:
    color .25s var(--ease-soft),
    transform .35s var(--ease),
    opacity .3s var(--ease-soft),
    visibility .3s;
}
.rail__step .ico { stroke-width: 2.4; }

/* Nothing above the first panel, so the up arrow has nothing to point at.
   Hidden rather than removed: visibility keeps its grid cell, so the dots stay
   put instead of jumping up, and it drops out of the accessibility tree too. */
body.is-first .rail__step[data-step='-1'] {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.rail__step:hover, .rail__step:focus-visible { color: #fff; }
.rail__step[data-step='-1']:hover { transform: translateY(-2px); }
.rail__step[data-step='1']:hover { transform: translateY(2px); }

.rail__item {
  position: relative;
  display: grid;
  place-items: center;
  width: 26px;
  padding: .35rem 0;
  cursor: pointer;
}

/* Tooltip. A solid-ish background instead of bare text, because white type on a
   photo was a coin toss depending on the panel behind it. */
.rail__label {
  position: absolute;
  right: calc(100% + .1rem);
  padding: .4rem .6rem .42rem;
  border-radius: 7px;
  background: var(--tip-bg);
  box-shadow: 0 6px 22px rgba(0, 0, 0, .45);
  font: 600 .7rem/1 var(--font-display);
  letter-spacing: .04em;
  white-space: nowrap;
  color: #fff;
  opacity: 0;
  transform: translateX(.35rem);
  transition: opacity .22s var(--ease-soft), transform .3s var(--ease);
  pointer-events: none;              /* never steals the hover from the link */
}
/* Arrow, sitting flush against the tooltip rather than overlapping it — an
   overlap would double up the background alpha and show a seam. */
.rail__label::after {
  content: '';
  position: absolute;
  top: 50%; left: 100%;
  border: 5px solid transparent;
  border-right-width: 0;
  border-left-color: var(--tip-bg);
  transform: translateY(-50%);
}

.rail__dot {
  width: 7px; height: 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .5);
  box-shadow: 0 0 0 3px rgba(0, 0, 0, .18);
  transition: height .4s var(--ease), background .3s var(--ease-soft);
}
.rail__item:hover .rail__label,
.rail__item:focus-visible .rail__label { opacity: 1; transform: none; }
.rail__item:hover .rail__dot { background: #fff; }
.rail__item[aria-current='true'] .rail__dot { height: 22px; background: #fff; }

/* Scroll hint over the first panel. */
.cue {
  position: fixed;
  left: 50%;
  bottom: calc(var(--bar-h) + 1.1rem);
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .55rem;
  padding: 0;
  border: 0;
  background: none;
  color: #fff;
  cursor: pointer;
  transform: translateX(-50%);
  transition: opacity .45s var(--ease-soft), visibility .45s;
}
.cue__mouse {
  position: relative;
  width: 25px; height: 40px;
  border: 2px solid rgba(255, 255, 255, .85);
  border-radius: 999px;
}
.cue__mouse::after {
  content: '';
  position: absolute;
  top: 6px; left: 50%;
  width: 4px; height: 4px;
  margin-left: -2px;
  border-radius: 50%;
  background: #fff;
  animation: wheel 1.7s cubic-bezier(.68, -.55, .27, 1.55) infinite;
}
@keyframes wheel {
  0%, 15% { transform: translateY(0) scale(1); opacity: 1; }
  100%    { transform: translateY(19px) scaleY(2.4) scaleX(.55); opacity: .05; }
}
body.is-scrolled .cue { opacity: 0; visibility: hidden; }

/* Wrap veil. Looping from the last panel to the first is an instant jump, not a
   smooth scroll — animating it would fly past all five panels in between. */
.veil {
  position: fixed;
  inset: 0;
  z-index: 30;
  background: var(--void);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s var(--ease-soft);
}
body.is-wrapping .veil { opacity: 1; }

/* Contact bar. */
.bar {
  position: fixed;
  bottom: 0; left: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 4vw, 3rem);
  width: 100%;
  height: var(--bar-h);
  padding-inline: var(--gutter);
  border-top: 1px solid rgba(255, 255, 255, .16);
  background: rgba(6, 8, 12, .34);
  font: 700 .78rem/1 var(--font-display);
  backdrop-filter: blur(14px);
  transition: background .4s var(--ease-soft);
}
.bar:hover { background: rgba(6, 8, 12, .6); }
.bar a { display: inline-flex; align-items: center; gap: .45rem; }
.bar a:hover span { text-decoration: underline; text-underline-offset: 3px; }

/* ── Small screens ────────────────────────────────────────────────────── */

@media (max-width: 46rem) {
  :root { --bar-h: 3.1rem; --radius: 22px; }
  /* A portrait panel puts the copy over the middle of the photo, where a bright
     app screenshot can sit. The lower half has to carry the text on its own. */
  .panel__scrim {
    background:
      linear-gradient(to top,
        rgba(6, 8, 12, .88) 0%,
        rgba(6, 8, 12, .8) 34%,
        rgba(6, 8, 12, .64) 56%,
        rgba(6, 8, 12, .26) 78%,
        rgba(6, 8, 12, 0) 100%),
      linear-gradient(100deg, rgba(6, 8, 12, .36), rgba(6, 8, 12, 0) 70%);
  }
  .panel__inner { align-items: flex-end; padding-top: 4.5rem; }
  .lede { margin-bottom: 1.4rem; }
  .rail__label { display: none; }   /* the rail links carry an aria-label */
  .bar__name { font-size: .8rem; }  /* the bar is the only place the name appears */

  /* A portrait crop cuts most of a landscape photo away, so each panel points
     at its own subject instead of the centre (see --focus-sm in the markup). */
  .panel__media img { object-position: var(--focus-sm, 60% 50%); }

  /* The hero is landscape inside a portrait frame, so object-fit: cover only
     ever crops horizontally — the vertical half of object-position does nothing
     at all here, and Barry's head sits right behind the headline. Zooming in is
     what creates the vertical room; the shift then lifts him above the copy. */
  #intro .panel__media img { transform: scale(1.2) translateY(-13%); }

  /* The cue is a mouse, which means nothing on touch, and it lands on top of
     the contact button at this width. */
  .cue { display: none; }

  /* Bigger taps for the step arrows, without moving the dots further apart. */
  .rail__step { width: 34px; height: 32px; font-size: 1.5rem; }

  /* Logo bar rides at the top of the intro panel on small screens, where the
     copy is bottom-aligned and that space is otherwise empty. The body becomes
     a flex column and the auto bottom margin on the reordered marquee absorbs
     all the free space, pinning it up top while the rest stays at the bottom.
     Desktop keeps the marquee in normal flow between the copy and the button. */
  #intro .panel__inner { align-items: stretch; }
  #intro .panel__body {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
  }
  #intro .marquee { order: -1; margin: 0 0 auto; }

  .marquee { margin-block: 1.5rem 1.4rem; }
  .marquee__group img { height: var(--h, 1.25rem); }

  /* Five stacked quote cards cannot fit a phone screen, and this panel has to
     stay exactly one viewport tall — a sticky element taller than the viewport
     pins its top and its bottom becomes unreachable. So the bento turns into a
     swipeable row, which keeps every quote available. */
  /* Extra room on the right so the peeking card stops short of the rail
     instead of sliding underneath its dots. */
  .panel--quotes .panel__inner { padding-inline: var(--quotes-pad) 3.1rem; }
  .bento {
    display: flex;
    grid-template-columns: none;
    gap: .7rem;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    margin-inline: calc(var(--quotes-pad) * -1) 0;
    padding-inline: var(--quotes-pad) 0;

    /* Soften the edge the cards run off, so the row reads as continuing rather
       than being sliced. js/site.js drops the fade on whichever end you have
       actually reached, so the first and last card never look clipped. */
    --fade-l: 0px;
    --fade-r: 2.75rem;
    -webkit-mask-image: linear-gradient(to right,
      transparent 0, #000 var(--fade-l),
      #000 calc(100% - var(--fade-r)), transparent 100%);
    mask-image: linear-gradient(to right,
      transparent 0, #000 var(--fade-l),
      #000 calc(100% - var(--fade-r)), transparent 100%);
  }
  .bento.at-start { --fade-l: 0px; }
  .bento:not(.at-start) { --fade-l: 2.75rem; }
  .bento.at-end { --fade-r: 0px; }
  .bento::-webkit-scrollbar { display: none; }
  .bento__cell {
    flex: 0 0 min(78%, 19rem);
    scroll-snap-align: center;
  }
  .bento__cell--tall { flex-basis: min(88%, 22rem); }
  .title--sm { font-size: clamp(1.7rem, 8vw, 2.2rem); margin-bottom: 1.1rem; }

  .bar { gap: 1.75rem; justify-content: center; font-size: 1rem; }
  /* Icon-only, but still named: display:none would drop these labels out of
     the accessibility tree and leave the links nameless. */
  .bar a span {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* Medium widths are the tight case: the text column is still a large fraction
   of the viewport, so the horizontal scrim has to reach further across before a
   bright app screenshot starts competing with the copy. */
@media (min-width: 46.0625rem) and (max-width: 75rem) {
  .panel__scrim {
    background:
      linear-gradient(100deg,
        rgba(6, 8, 12, .8) 0%,
        rgba(6, 8, 12, .68) 38%,
        rgba(6, 8, 12, .42) 64%,
        rgba(6, 8, 12, .1) 87%,
        rgba(6, 8, 12, 0) 100%),
      linear-gradient(to top, rgba(6, 8, 12, .5), rgba(6, 8, 12, 0) 45%);
  }
}

/* Very short viewports (landscape phones) get room to breathe instead of
   being locked into a screen that cannot fit the copy. */
@media (max-height: 34rem) {
  html { scroll-snap-type: y proximity; }
  .panel { height: auto; min-height: 100dvh; }
  .panel__inner { padding-block: 4.5rem 4.5rem; }
}

/* ── Reduced motion ───────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  html { scroll-snap-type: none; }
  .panel { position: relative; }
  .panel__card {
    border-radius: 0;
    transform: none;
    box-shadow: none;
  }
  .panel__media, .panel__body { transform: none; filter: none; opacity: 1; }
  .panel__dim { opacity: 0; }
  .js .reveal { opacity: 1; transform: none; transition: none; }
  .cue { display: none; }
  .veil { display: none; }
  .marquee__track { animation: none; }
  .status__ping { animation: none; opacity: .5; }
  .cue__mouse::after { animation: none; }
  *, *::before, *::after { transition-duration: .01ms !important; }
}
