/* ─────────────────────────────────────────────────────────────
   foilnexus.com — shared stylesheet

   Colour is transcribed from the eFoilCrew design system:
     src/app/globals.css  @theme block (Deep palette) and the
                          [data-theme="light"] block

   Typography is deliberately NOT shared. eFoilCrew uses Inter with
   Instrument Serif for display; FoilNexus uses IBM Plex Sans and
   IBM Plex Mono throughout. The separation between the corporate
   entity and the consumer product is carried by the typeface rather
   than the palette: teal collided with Lift's brand colour and the
   graphite/rust option collided with Strava, so the accent stays.

   Light is the DEFAULT here, dark is the mirror. That is the
   opposite of the product app and deliberate: the app is used by
   members who have chosen a theme, this site is read cold by people
   arriving from search, the Connect IQ listing or an enrolment
   check. It matches the public /watch-guide route, which moved to
   light-by-default for the same reason.

   Same-origin stylesheet rather than six inlined copies: the tokens
   drifted between two files within a week of the second page
   existing. Still no build step.
   ───────────────────────────────────────────────────────────── */

:root {
  /* Brand */
  --accent: #3B82F6;
  --accent-light: #60A5FA;
  --accent-dim: rgba(59, 130, 246, 0.08);

  /* Surfaces — light */
  --bg: #F0F5FC;
  --bg-card: #FFFFFF;
  --bg-elevated: #E8F0FA;
  --border: #C0D0E8;

  /* Text */
  --text: #0A1628;
  --text-2: #3A5878;
  --text-3: #6A88A8;

  /* Type — IBM Plex, not eFoilCrew's Inter + Instrument Serif */
  --sans: 'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --display: 'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;

  /* Shape */
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 20px;
  --shadow: 0 2px 12px rgba(10, 22, 40, 0.06);
  --shadow-lg: 0 12px 32px rgba(10, 22, 40, 0.10);

  color-scheme: light;
}

/* Dark is opt-in via the header toggle, NOT via prefers-color-scheme.
   The site is read cold by people arriving from search, the Connect IQ
   listing or an entity check, and it should look the same to all of them
   on first load. A stored choice wins; absent one, light. The attribute
   is set by a pre-paint inline script in every page head, so there is no
   flash of the wrong theme. Values are the eFoilCrew Deep dark palette
   from src/app/globals.css. */
[data-theme="dark"] {
  --accent-dim: rgba(59, 130, 246, 0.14);
  --bg: #0A1628;
  --bg-card: #101E32;
  --bg-elevated: #0E1A2E;
  --border: #1E3050;
  --text: #E8F0FA;
  --text-2: #7EA0C4;
  --text-3: #4E7098;
  --shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  overflow-x: clip;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  overflow-x: clip;
  max-width: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

.wrap {
  width: 100%;
  max-width: 58rem;
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 5vw, 2.5rem);
}

/* Hide the skip link by clipping it, NOT by parking it at left:-9999px.
   An element sitting that far off-canvas does not grow scrollWidth, which is
   why every automated check passed, but a touch browser still lets you drag
   the page toward it - the page drifts left and right while you scroll. */
.skip-link {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.skip-link:focus {
  position: fixed;
  top: 1rem;
  left: 1rem;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0.625rem 1rem;
  overflow: visible;
  clip: auto;
  clip-path: none;
  background: var(--bg-card);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  z-index: 20;
}

/* ── Header ─────────────────────────────────────────────── */
.site-header {
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 10;
}
.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: 4rem;
  /* padding-block only. The shorthand `padding: 0.75rem 0` also zeroed the
     horizontal padding this element inherits from .wrap, so the wordmark sat
     hard against the viewport edge at every width below the wrap's max-width
     while the page content below it was correctly inset. */
  padding-block: 0.75rem;
}
/* Wordmark only. There is no logo yet, and a placeholder glyph reads worse
   than none at all. */
.brand {
  display: inline-flex;
  align-items: center;
  color: var(--text);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.brand span { color: var(--accent); }
.brand:hover { text-decoration: none; color: var(--text); }

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem 1.375rem;
  align-items: center;
  font-size: 0.875rem;
  font-weight: 500;
}
.nav a { color: var(--text-2); }
.nav a:hover { color: var(--accent); text-decoration: none; }
.nav a[aria-current="page"] { color: var(--text); }

/* ── Mobile nav ─────────────────────────────────────────────
   Below 900px the links are replaced by a burger that opens a
   panel under the bar. The previous attempt kept them in a
   horizontal scroller, which wrapped onto three rows at phone
   width and clipped the first link off the left edge.

   900px matches the carousels and the prototype this came from. */
.menubtn { display: none; }
.navmenu { display: none; }

@media (max-width: 900px) {
  .nav { display: none; }

  .menubtn {
    display: grid;
    place-items: center;
    width: 2.125rem;
    height: 2.125rem;
    flex: none;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
  }
  .menubtn:hover { background: var(--bg-elevated); color: var(--text); }
  .menubtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .menubtn svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
  .menubtn svg path { transition: transform 0.18s, opacity 0.12s; transform-origin: center; }
  .menubtn[aria-expanded="true"] .l1 { transform: translateY(5px) rotate(45deg); }
  .menubtn[aria-expanded="true"] .l2 { opacity: 0; }
  .menubtn[aria-expanded="true"] .l3 { transform: translateY(-5px) rotate(-45deg); }

  .navmenu {
    display: block;
    background: var(--bg);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 12px 28px rgba(10, 22, 40, 0.14);
  }
  .navmenu[hidden] { display: none; }
  .navmenu a {
    display: block;
    padding: 0.8125rem clamp(1.25rem, 5vw, 2.5rem);
    font-size: 0.9375rem;
    font-weight: 500;
    color: var(--text);
    border-top: 1px solid var(--border);
  }
  .navmenu a:hover { background: var(--bg-elevated); text-decoration: none; }
  .navmenu a:first-child { border-top: 0; }
  .navmenu a[aria-current="page"] { color: var(--accent); font-weight: 600; }
}

@media (prefers-reduced-motion: reduce) {
  .menubtn svg path { transition: none; }
}

.header-right { display: flex; align-items: center; gap: 0.625rem; flex: none; }

.theme-toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.125rem;
  height: 2.125rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
}
.theme-toggle:hover { background: var(--bg-elevated); color: var(--text); }
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.theme-toggle svg { display: block; }
/* Show the icon for the theme you would switch TO. */
.theme-toggle .i-moon { display: block; }
.theme-toggle .i-sun { display: none; }
[data-theme="dark"] .theme-toggle .i-moon { display: none; }
[data-theme="dark"] .theme-toggle .i-sun { display: block; }

/* ── Type ───────────────────────────────────────────────── */
.eyebrow {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 0.75rem;
}

h1 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(2rem, 5vw, 3.25rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
  margin: 0;
  max-width: 22ch;
}
h1.wide { max-width: 28ch; }

h2 {
  font-family: var(--display);
  font-size: clamp(1.3125rem, 2.8vw, 1.75rem);
  font-weight: 600;
  line-height: 1.22;
  letter-spacing: -0.025em;
  margin: 0 0 1rem;
}

h3 {
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.01em;
  margin: 0 0 0.5rem;
}

.lead {
  font-size: clamp(1.0625rem, 2.2vw, 1.3125rem);
  line-height: 1.5;
  color: var(--text-2);
  margin: 1.5rem 0 0;
  max-width: 52ch;
}
.lead.wide { max-width: 60ch; }
/* Two leads are two audiences, not two thoughts. No extra margin at all: they
   should read as one statement that happens to break after the first line. */
.lead + .lead { margin-top: 0; }

.prose { max-width: 45rem; }
.prose p { margin: 0 0 1rem; color: var(--text-2); }
.prose p:last-child { margin-bottom: 0; }
.prose h3 { margin-top: 2rem; color: var(--text); }
.prose h3:first-child { margin-top: 0; }
.prose ul { margin: 0 0 1rem; padding-left: 1.25rem; color: var(--text-2); }
.prose li { margin-bottom: 0.5rem; }
.prose strong { color: var(--text); font-weight: 600; }
.prose code {
  font-family: var(--mono);
  font-size: 0.8125rem;
  background: var(--bg-elevated);
  border-radius: var(--r-sm);
  padding: 0.0625rem 0.375rem;
}

.muted { color: var(--text-3); font-size: 0.875rem; }

/* ── Sections ───────────────────────────────────────────── */
.hero { padding: clamp(3rem, 8vw, 5.5rem) 0 clamp(2rem, 5vw, 3rem); }
section { padding-bottom: clamp(3rem, 7vw, 4.5rem); }
.section-head { margin-bottom: 2rem; max-width: 45rem; }
.section-head p { color: var(--text-2); margin: 0; }
.rule { border: 0; border-top: 1px solid var(--border); margin: 0 0 clamp(2.5rem, 6vw, 4rem); }

/* ── Cards ──────────────────────────────────────────────── */
.cards {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: 1fr;
}
@media (min-width: 52rem) { .cards-3 { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 44rem) { .cards-2 { grid-template-columns: repeat(2, 1fr); } }

.card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow);
}
.card-body {
  padding: 1.375rem;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  flex: 1;
}
.card-body p { margin: 0; color: var(--text-2); font-size: 0.9375rem; line-height: 1.6; flex: 1; }
/* The tag/kicker line is a <p> too. Without this it takes the flex share and
   pushes the heading down in whichever card has the shorter body. */
.card-body > p:first-child { flex: 0 0 auto; }
.card-shot {
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top center;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
}
.kicker {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 0.375rem;
}
/* A card grid that follows prose needs the gap a section-head would have given it. */
.prose + .cards { margin-top: 2rem; }

/* Mirrors the overview page's crew grid, breakpoints included: three across above
   900px, two down to 621px, one below that. From 899px down the kicker goes and the
   heading runs into its own sentence — the same trick .features uses, without which
   six cards become six screens of scrolling on a phone. .card-body is a column flex
   box, and a flex child's `display: inline` blockifies, so the compact rule has to
   take the card body back to block for the run-in to happen at all. */
.cards-compact { grid-template-columns: 1fr; }
@media (min-width: 38.8125rem) { .cards-compact { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 56.3125rem) { .cards-compact { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 56.1875rem) {
  .cards-compact { gap: 0.625rem; }
  .cards-compact .card-body { display: block; padding: 0.8125rem 0.9375rem; }
    .cards-compact h3 { display: inline; font-size: 0.96875rem; line-height: 1.4; }
  .cards-compact h3::after { content: " "; }
  .cards-compact .card-body p { display: inline; font-size: 0.875rem; color: var(--text-3); }
  /* After the rule above, and one class heavier than it: a bare .cards-compact
     .kicker loses the specificity fight and the label runs on inline instead. */
  .cards-compact .card-body .kicker { display: none; }
}

.card-link { font-size: 0.875rem; font-weight: 600; }
.card-link::after { content: " \2192"; }

.tag {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--accent-dim);
  border-radius: 999px;
  padding: 0.1875rem 0.5rem;
}
.tag-quiet { color: var(--text-3); background: var(--bg-elevated); }

/* ── Figures ────────────────────────────────────────────── */
figure { margin: 0 0 1.5rem; }
figure img {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
figcaption { margin-top: 0.75rem; font-size: 0.8125rem; color: var(--text-3); }

/* ── Buttons ────────────────────────────────────────────── */
.btn {
  display: inline-block;
  background: var(--accent);
  color: #FFFFFF;
  font-size: 0.9375rem;
  font-weight: 600;
  padding: 0.75rem 1.375rem;
  border-radius: var(--r-md);
  border: 1px solid transparent;
}
.btn:hover { background: var(--accent-light); text-decoration: none; }
.btn-secondary {
  background: transparent;
  color: var(--text);
  border-color: var(--border);
}
.btn-secondary:hover { background: var(--bg-elevated); color: var(--text); }
.btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.75rem; }

/* ── Facts grid (about page) ────────────────────────────── */
.facts {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: clamp(1.5rem, 4vw, 2rem);
  display: grid;
  gap: 1.75rem;
  grid-template-columns: 1fr;
}
@media (min-width: 44rem) { .facts { grid-template-columns: 1fr 1fr; } }
.facts h3 {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 0.5rem;
}
.facts p, .facts address {
  margin: 0;
  font-style: normal;
  font-size: 0.9375rem;
  line-height: 1.7;
}
.span-2 { grid-column: 1 / -1; }
.ubi-number {
  font-family: var(--mono);
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.trader { border-top: 1px solid var(--border); padding-top: 1.5rem; }
.trader p { font-size: 0.8125rem; color: var(--text-2); }

/* ── Footer ─────────────────────────────────────────────── */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 2.5rem 0 3rem;
  font-size: 0.875rem;
  color: var(--text-3);
  background: var(--bg-elevated);
}
.footer-grid {
  display: grid;
  gap: 2rem;
  grid-template-columns: 1fr;
  margin-bottom: 2rem;
}
@media (min-width: 44rem) { .footer-grid { grid-template-columns: 1.4fr 1fr 1fr; } }
.footer-grid h3 {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 0.75rem;
}
.footer-list { list-style: none; margin: 0; padding: 0; }
.footer-list li { margin-bottom: 0.4375rem; }
.footer-list a { color: var(--text-2); }
.footer-entity { font-style: normal; line-height: 1.7; color: var(--text-2); }
.footer-entity strong { color: var(--text); font-weight: 600; }
.footer-bottom {
  border-top: 1px solid var(--border);
  padding-top: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  justify-content: space-between;
}
.footer-bottom p { margin: 0; }

/* ── Phone shot trio (eFoilCrew page) ───────────────────────── */
.shots {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;
  max-width: 22rem;
  margin: 0 auto;
}
@media (min-width: 40rem) {
  .shots { grid-template-columns: repeat(3, 1fr); max-width: none; margin: 0; }
}
.shots img {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
/* Two portrait captures side by side, shown whole. They were cropped to 4:3
   inside cards, which removed the part worth looking at. */
/* Portrait app captures. auto-fit rather than a fixed column count: this
   started as two cards and became three, and a hard `1fr 1fr` left the third
   on its own row with both survivors stretched to 412px wide and 890px tall.

   The images are also capped at their own size rather than filling whatever
   column they land in. The prototype holds these at a constant ~274px at
   every viewport, and it is right to - a phone screenshot rendered 900px tall
   stops reading as a phone. */
.figure-2 {
  display: grid;
  gap: 2rem 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  justify-items: center;
}
.figure-2 figure { margin: 0; max-width: 17.25rem; }
.figure-2 img { width: 100%; max-width: 17.25rem; }
.figure-2 figcaption { max-width: 34rem; }

/* Same cap for the phone trio, so a wide viewport cannot inflate them. */
.shots img { max-width: 17.25rem; margin-inline: auto; }

/* The replay capture is a 195x422 phone recording. Stretched across the
   content column it upscales more than four times and falls apart, so it is
   framed at something close to its native size. */
.figure-phone { max-width: 20rem; margin-inline: auto; }
.figure-phone figcaption { text-align: center; }

figure video {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  background: var(--bg-elevated);
}

/* ── Garmin watch renders ───────────────────────────────────
   A fenix8 bezel with a live SVG screen positioned inside it.
   The .scr percentages are measured against that exact bezel
   image; swap the image and they all have to be re-measured. */
.wintro { display: grid; gap: 2.5rem; grid-template-columns: 1fr; align-items: center; }
@media (min-width: 52rem) { .wintro { grid-template-columns: 1.3fr 0.7fr; gap: 3.25rem; } }

.fenix { position: relative; width: 100%; max-width: 15rem; margin: 0 auto 0.875rem; }
.fenix .frame { width: 100%; height: auto; border: 0; border-radius: 0; box-shadow: none; }
.fenix .scr {
  position: absolute;
  left: 17.359%;
  top: 23.232%;
  width: 67.359%;
  height: 50.954%;
  border-radius: 50%;
  overflow: hidden;
  background: #000;
}
.fenix .scr svg { width: 100%; height: 100%; display: block; }

.wherohero { margin: 0; max-width: 17rem; margin-inline: auto; text-align: center; }
.wherohero figcaption { text-align: center; }

.wshots { display: grid; gap: 2.25rem 1.5rem; grid-template-columns: 1fr; }
@media (min-width: 44rem) { .wshots { grid-template-columns: repeat(3, 1fr); } }
.wshot { text-align: center; }
.wshot h3 { margin-bottom: 0.375rem; }
.wshot p { color: var(--text-2); font-size: 0.875rem; line-height: 1.55; margin: 0 auto; max-width: 17rem; }
.wshot p + h3 { margin-top: 0; }

.ciq { display: flex; align-items: center; gap: 1.375rem; flex-wrap: wrap; margin-top: 2.25rem; }
.ciq-badge img { display: block; height: 3rem; width: auto; border: 0; border-radius: 0; box-shadow: none; }
.ciq-badge:hover { opacity: 0.85; }
.models { margin: 0; font-size: 0.875rem; color: var(--text-3); }

.band {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1.125rem 1.25rem;
  border-radius: var(--r-md);
  border: 1px dashed var(--border);
  color: var(--text-2);
  font-size: 0.9375rem;
  margin-top: 1.875rem;
}
.band .soon {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.3125rem 0.5625rem;
  border-radius: var(--r-sm);
  background: var(--bg-elevated);
  color: var(--text-3);
}
.band strong { color: var(--text); }

/* ── Home hero lockup: desktop, phone, watch ────────────────
   Phone and watch overlap the LEFT of the desktop shot. The
   board-telemetry panel lives in its right third and is the
   point of the image, so nothing may cover it. Below 46rem the
   overlap is abandoned entirely and the three stack, because
   absolute positioning at phone width is how horizontal drift
   gets reintroduced. */
.lockup { margin: 0; }
.lockup figcaption { text-align: center; }
.lockup-stage { position: relative; padding-bottom: 3.5rem; }

/* The desktop shot is split into three windows onto the same image so the map
   can be panned WITHOUT dragging the telemetry panel and the app nav with it.
   It is one flat JPEG; transforming it moved everything at once.

   Geometry measured off the pixels, not eyeballed:
     nav strip ends at   72/1000 =  7.20% of height
     panel starts at   1132/1600 = 70.75% of width
   Re-measure both if the screenshot is ever retaken - scripts that find the
   dark/light edges are in the commit that introduced this.

   Each window sizes the image to the FRAME width and then offsets it, so all
   three stay in register at any viewport width. Only .desk-map takes a
   transform; tune it with sites/foilnexus/lockup-tuner.html. */
.deskframe {
  position: relative;
    width: 84%;
  margin-inline: auto;
  aspect-ratio: 1600 / 1000;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  background: var(--bg-elevated);
}
.deskframe > div { position: absolute; overflow: hidden; }
.deskframe img { position: absolute; display: block; width: 100%; max-width: none; }

/* app nav: full width, top strip, never moves */
.desk-nav { inset: 0 0 auto 0; height: 7.2%; }
.desk-nav img { top: 0; left: 0; }

/* map: everything left of the panel, below the nav. The only layer that moves. */
.desk-map { top: 7.2%; bottom: 0; left: 0; width: 70.75%; }
.desk-map img {
  top: 0;
  left: 0;
  width: 141.343%;          /* 100 / 0.7075 - image sized to the frame */
  top: 0;
  /* The nav crop is the LAST transform in the list, so it is applied first, in
     image coordinates, and the scale then multiplies it. The nav is 7.2% of the
     image's height whatever the zoom, so -7.2% removes it at any scale. Doing
     it with `top` instead pinned the crop at scale 1, and zooming to 1.46
     dragged the nav strip back into view.

     Origin top-RIGHT: scale grows the image leftward, so shifting it right
     never exposes the frame behind its left edge. Which also means
     scale >= 1 + translateX, or a bare band appears. 45% needs >= 1.45. */
  transform: translateX(50%) translateY(-21%) scale(1.31) translateY(-7.6%);
  transform-origin: 100% 0;
}

/* telemetry panel: fixed, and nothing is allowed to cover it */
.desk-panel { top: 7.2%; bottom: 0; right: 0; width: 29.25%; }
.desk-panel img {
  top: 0;
  right: 0;
  width: 341.880%;          /* 100 / 0.2925 */
  top: 0;
  transform: translateY(-7.6%);   /* same nav crop, no zoom here */
}

.lockup-phone {
  position: absolute;
  left: 10%;
  bottom: 0;
  width: 22%;
}
.lockup-phone img {
  display: block;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
}
/* Watch sits over the eastern arm of the track and the empty land below it,
   not on the main Lake Union body. Stage geometry: the desktop shot starts at
   16% and the telemetry panel begins around 71%, so 57-70% is the one band
   that is clear of both the lake and the numbers. */
/* 42% leaves about 5% of clear stage between the phone's right edge and the
   watch. At 38% they sat 1.2% apart, which reads as a collision rather than a
   composition. */
.lockup-watch {
  position: absolute;
  left: 34%;
  bottom: 0;
  width: 13%;
}
.lockup-watch .fenix { max-width: none; margin: 0; }

/* The lockup holds together at every width. It used to break apart into a
   stack below 46rem, which read as three loose pictures rather than one
   image, and the stacked rule also set `transform: none` on the map layer -
   that same transform carries the nav crop, so the app's nav bar rendered a
   second time inside the map.

   Nothing here needs a breakpoint: every position and size is a percentage of
   the stage, so the whole composition scales down intact. Only the bottom
   padding is absolute, so that is the one thing that shrinks. */
@media (max-width: 46rem) {
  .lockup-stage { padding-bottom: 2rem; }
}

/* ── Carousels ──────────────────────────────────────────────
   Ported from docs/prototypes/2026-09-14-overview-page.html.

   Above 900px a [data-carousel] track is an ordinary grid. Below
   it, the same markup becomes a horizontal scroll-snap slider and
   carousel.js builds the controls underneath: arrows, labelled
   tabs, and ONE caption line that follows the active card. The
   per-card captions are hidden at that width so the text is not
   said twice.

   900px on purpose, matching the prototype it was tuned at,
   rather than the 44/52rem the rest of this file uses. */
/* The controls exist only for the scroller. Above 900px the track is an
   ordinary grid and every card shows its own caption, so arrows, tabs and the
   shared caption line are all redundant. */
.cnav, .ccap { display: none; }

@media (max-width: 900px) {
  .track {
    display: flex !important;
    grid-template-columns: none !important;
    justify-content: flex-start !important;
    gap: 1rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    padding-bottom: 0.25rem;
    /* .shots and .figure-2 both cap their width and centre themselves for the
       single-column layout. Left alone, those would strangle the scroller. */
    max-width: none;
    margin-inline: 0;
    /* Centre the first and last card: scroll-snap-align is `center`, so
       without this padding the outer cards can never reach the middle. */
    padding-inline: max(0px, calc(0.5 * (100% - min(86%, 300px))));
  }
  .track::-webkit-scrollbar { display: none; }
  .track > * {
    flex: 0 0 min(86%, 300px);
    max-width: 300px;
    scroll-snap-align: center;
    margin: 0;
  }
  .track .fenix { max-width: 15rem; margin-inline: auto; }
  /* Said once, in .ccap, rather than under every card. */
  .track .carousel-cap { display: none; }

  .cnav {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    margin-top: 1rem;
  }
  .cnav .arrow-btn {
    flex: none;
    width: 2rem;
    height: 2rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-card);
    color: var(--text-2);
    cursor: pointer;
  }
  .cnav .arrow-btn:hover:not(:disabled) { color: var(--text); background: var(--bg-elevated); }
  .cnav .arrow-btn:disabled { opacity: 0.35; cursor: default; }
  .cnav .arrow-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
  .cnav .arrow-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

  .cnav .tabs {
    display: flex;
    gap: 0.375rem;
    overflow-x: auto;
    scrollbar-width: none;
    justify-content: center;
  }
  .cnav .tabs::-webkit-scrollbar { display: none; }
  .cnav .tabs button {
    flex: 0 0 auto;
    font-family: var(--sans);
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.5rem 0.875rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--bg-card);
    color: var(--text-2);
    white-space: nowrap;
    cursor: pointer;
  }
  .cnav .tabs button[aria-selected="true"] {
    background: var(--accent);
    border-color: var(--accent);
    color: #FFFFFF;
  }
  .cnav .tabs button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

  .ccap {
    display: block;
    margin: 0.75rem auto 0;
    max-width: 22rem;
    text-align: center;
    font-size: 0.8125rem;
    line-height: 1.55;
    color: var(--text-3);
  }
  .ccap b { color: var(--text-2); font-weight: 600; }
}

/* ── Feature grid ───────────────────────────────────────────
   Hairline rules come from a 1px gap over the grid's own
   background, not from borders on each cell - so the lines are
   always exactly 1px and never double up where cells meet.
   Taken from the overview prototype.

   It stays TWO columns down to the narrowest phone rather than
   collapsing to one. Eight single-file rows is a long scroll for
   eight short items; two columns reads as a table you can scan. */
.features {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.features > div { background: var(--bg-card); padding: 1rem 1.125rem; }
.features h3 { font-size: 0.9375rem; margin: 0 0 0.25rem; }
.features p { margin: 0; font-size: 0.875rem; line-height: 1.55; color: var(--text-2); }

@media (max-width: 900px) {
  .features { grid-template-columns: 1fr 1fr; border-radius: var(--r-md); }
  .features > div { padding: 0.75rem 0.875rem; }
  /* Label and text run together on one line: at this width a stacked
     heading wastes a whole row per cell. */
  .features h3 { display: inline; font-size: 0.875rem; }
  .features h3::after { content: " "; }
  .features p { display: inline; font-size: 0.8125rem; line-height: 1.5; color: var(--text-3); }
}
@media (max-width: 26.25rem) {
  .features > div { padding: 0.625rem 0.6875rem; }
  .features h3 { font-size: 0.8125rem; }
  .features p { font-size: 0.75rem; }
}
