/* ==========================================================================
   DSP Remote — base, layout, and components
   All values come from tokens.css. Nothing here hard-codes a color.
   ========================================================================== */

/* ── fonts ─────────────────────────────────────────────────────
   Geist ships as a single variable woff2 covering 100-900, so one file
   replaces the eight static weights the design uses.
   ─────────────────────────────────────────────────────────────── */
@font-face {
  font-family: "Geist";
  src: url("../fonts/Geist-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/GeistMono-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

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

/* Full-bleed at any width (Ed, September 2026): backgrounds (header bar, hero
   photography, bands, footer) run edge to edge however wide the window gets,
   while text and content stay inside .wrap's max-width. This replaces an
   earlier 1920px page cap that left dark gutters on 2560px monitors. */
html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  background-color: var(--black);
}

body {
  margin: 0;
  background: var(--black);
  color: var(--slate);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }
svg { display: block; }

/* Headings are normal weight, never bold — the DSP signature. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  color: var(--ink);
  font-weight: 400;
  line-height: var(--lh-heading);
  margin: 0;
  letter-spacing: 0.005em;
}
h1 { font-size: var(--fs-h1); line-height: var(--lh-tight); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-h4); }

p { margin: 0 0 var(--sp-4); }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration: none; transition: color var(--t-fast); }
a:hover { color: var(--accent-hover); }

hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-8) 0; }

::selection { background: var(--accent-dim); color: var(--ink); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Screen-reader-only, used for skip links and form labels. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: var(--black);
  padding: var(--sp-3) var(--sp-5); font-weight: 600;
}
.skip-link:focus { left: 0; }

/* ── layout ────────────────────────────────────────────────── */
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--sp-5); }
.wrap-narrow { max-width: var(--maxw-narrow); margin: 0 auto; padding: 0 var(--sp-5); }

.section { padding: var(--section-y) 0; }
.section-tight { padding: var(--section-y-tight) 0; }
.section-line { border-top: 1px solid var(--border); }
.section-panel { background: var(--panel); }

main { display: block; min-height: 50vh; }

/* ── eyebrow ───────────────────────────────────────────────────
   The shared DSP label style: tiny, wide-tracked, uppercase, rust.
   Uses --rust-text, DSP's brand red, exactly as DSP styles its own eyebrows.
   ─────────────────────────────────────────────────────────────── */
.eyebrow {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--rust-text);
  margin: 0 0 var(--sp-4);
}
.eyebrow-muted { color: var(--slate-dim); }
.eyebrow.center { text-align: center; }

.lead {
  color: var(--slate);
  font-size: var(--fs-lead);
  max-width: 56ch;
}
.section-head { max-width: 680px; margin-bottom: var(--sp-8); }
.section-head.center { margin-left: auto; margin-right: auto; text-align: center; }

/* ── buttons ───────────────────────────────────────────────────
   DSP's real button spec: small, wide-tracked, uppercase, bold, square.
   ─────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-button);
  text-transform: uppercase;
  padding: 0.85rem 1.75rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  cursor: pointer;
  text-align: center;
  transition: background var(--t-fast), color var(--t-fast),
              border-color var(--t-fast), transform var(--t-fast);
}
.btn:hover { transform: translateY(-1px); }

.btn-primary { background: var(--accent); color: var(--black); }
.btn-primary:hover { background: var(--accent-hover); color: var(--black); }

/* DSP has exactly two button treatments — gold filled and gold outline —
   so there is no neutral secondary here either. Use .btn-ghost. */
.btn-ghost { background: transparent; color: var(--accent); border-color: var(--accent); }
.btn-ghost:hover { background: var(--accent); color: var(--black); }

.btn-block { width: 100%; }
.btn-sm { padding: 0.6rem 1.1rem; font-size: var(--fs-eyebrow); }

.btn-row { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.btn-row.center { justify-content: center; }

/* ── header ────────────────────────────────────────────────── */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: var(--header-bg);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}
.nav {
  display: flex; align-items: center; gap: var(--sp-6);
  height: var(--header-h);
}
.brand { display: flex; align-items: center; gap: var(--sp-3); flex-shrink: 0; }
.brand-text, .mainnav a:not(.btn), .has-menu > button { white-space: nowrap; }
.brand:hover { color: inherit; }
.brand img { width: 44px; height: 44px; border-radius: var(--radius-pill); }
.brand-text {
  font-family: var(--font-sans); font-weight: 600; font-size: 0.8rem;
  letter-spacing: 0.18em; text-transform: uppercase; line-height: 1.25;
  color: var(--ink-ui); border-left: 1px solid var(--border); padding-left: var(--sp-3);
}
.brand-text span { color: var(--slate-dim); font-weight: 500; display: block; }

/* 10Micron partner badge, between the brand and the nav. It carries the
   margin-right: auto that pushes the nav to the right edge, so the badge sits
   beside the brand rather than floating mid-bar. */
.partner {
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 5px;
  margin-right: auto; padding-left: var(--sp-5); flex-shrink: 0;
  border-left: 1px solid var(--border);
  opacity: 0.88; transition: opacity var(--t-fast);
}
.partner:hover { opacity: 1; color: inherit; }
.partner-label {
  font-family: var(--font-sans); font-weight: 500; font-size: 0.6rem;
  letter-spacing: 0.18em; text-transform: uppercase; line-height: 1;
  color: var(--slate-dim);
}
.partner img { display: block; height: 22px; width: auto; }

.mainnav { display: flex; gap: var(--sp-5); align-items: center; margin: 0; padding: 0; }
.mainnav > li { list-style: none; position: relative; }
/* :not(.btn) so a button in the nav keeps its own color. A bare `.mainnav a`
   rule outranks `.btn-primary` on specificity, which painted the Contact
   button's label the same color as its own background. */
.mainnav a:not(.btn) {
  color: var(--slate); font-family: var(--font-sans); font-weight: 500;
  font-size: 0.78rem; letter-spacing: var(--ls-nav); text-transform: uppercase;
  display: block; padding: var(--sp-2) 0;
}
.mainnav a:not(.btn):hover, .mainnav .current a:not(.btn) { color: var(--ink-ui); }

/* dropdown groups: "Host With Us" / "Why DSP Remote" are menu-only parents */
.has-menu > button {
  background: none; border: 0; cursor: pointer;
  color: var(--slate); font-family: var(--font-sans); font-weight: 500;
  font-size: 0.78rem; letter-spacing: var(--ls-nav); text-transform: uppercase;
  display: flex; align-items: center; gap: 6px; padding: var(--sp-2) 0;
}
.has-menu > button:hover { color: var(--ink-ui); }
.has-menu > button svg { width: 9px; height: 9px; stroke: currentColor; fill: none; stroke-width: 2; }
/* Matches DSP's dropdown: #0c0d10 panel, hairline border, hover to gold on
   a #111316 row, hairline separators between items. */
.submenu {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  min-width: 230px; background: var(--surface-menu);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0; margin: 0; list-style: none;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
  opacity: 0; visibility: hidden; transition: opacity var(--t-fast);
}
.has-menu:hover .submenu,
.has-menu:focus-within .submenu,
.has-menu.open .submenu { opacity: 1; visibility: visible; }
.submenu li { border-bottom: 1px solid var(--border-subtle); }
.submenu li:last-child { border-bottom: 0; }
/* Must out-specify `.mainnav a:not(.btn)` — :not(.btn) carries a class's
   worth of specificity, so a plain `.submenu a` loses and the dropdown
   inherits the top-level nav's uppercase tracking and zero side padding. */
.mainnav .submenu a:not(.btn) {
  display: block;
  padding: var(--sp-3) var(--sp-4);
  text-transform: none;
  font-size: 0.84rem; letter-spacing: 0; font-weight: 400;
  color: var(--slate-muted);
}
.mainnav .submenu a:not(.btn):hover { background: var(--surface-hover); color: var(--accent); }

.menu-toggle {
  display: none; background: none; border: 0; color: var(--ink-ui);
  cursor: pointer; padding: var(--sp-2);
}
.menu-toggle svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.8; }

/* ── hero ──────────────────────────────────────────────────── */
.hero {
  position: relative; overflow: hidden;
  border-bottom: 1px solid var(--border);
  background: var(--black);
}
.hero-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Halved from the original .62/.78/.97 — the images were screened back so far
   the astrophotography barely read. The text stays legible because the type
   carries its own shadow (below) rather than relying on the veil. */
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(7,8,15,.31) 0%, rgba(7,8,15,.39) 62%, rgba(7,8,15,.49) 100%);
}

/* ── rotating hero ─────────────────────────────────────────────
   Cross-fades between the aerial site view and members' deep-sky images.
   Slides stack absolutely and only opacity animates, so the browser can
   composite it on the GPU and never reflows the text on top.
   ─────────────────────────────────────────────────────────── */
.hero-slides { position: absolute; inset: 0; }
.hero-slide {
  position: absolute; inset: 0; margin: 0;
  opacity: 0; transition: opacity 1.4s ease-in-out;
  will-change: opacity;
}
.hero-slide.is-active { opacity: 1; }
.hero-slide img { width: 100%; height: 100%; object-fit: cover; object-position: center; }

/* Image credit. Astrophotographers care about attribution, and it doubles as
   proof the pictures are real and taken here. On a static hero it floats
   bottom-right; on the rotator it moves into .hero-meta so it cannot collide
   with the stats strip. */
.hero-credit {
  position: absolute; z-index: 3; right: var(--sp-5); bottom: var(--sp-4);
  font-family: var(--font-sans); font-size: 0.68rem; letter-spacing: 0.08em;
  color: var(--slate-muted); text-align: right; max-width: 60vw;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  transition: opacity var(--t-fast);
}
.hero-credit b { color: var(--ink-ui); font-weight: 600; display: block; }

/* Controls + credit share one row above the trust strip. */
.hero-meta {
  position: relative; z-index: 3;
  max-width: 820px; margin: 0 auto; padding: 0 var(--sp-5);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); min-height: 34px;
}
.hero-meta .hero-credit {
  position: static; right: auto; bottom: auto; max-width: none;
}

.hero-dots { display: flex; gap: var(--sp-2); }
.hero-dots button {
  width: 26px; height: 3px; padding: 0; border: 0; border-radius: 2px;
  background: var(--border-strong); cursor: pointer;
  transition: background var(--t-fast);
}
.hero-dots button[aria-current="true"] { background: var(--accent); }
.hero-dots button:hover { background: var(--accent-hover); }

/* With reduced motion, hold the first slide and hide the controls rather
   than cross-fading. */
@media (prefers-reduced-motion: reduce) {
  .hero-slide { transition: none; }
}

/* ── image band: a photo behind a section, DSP's quote-band pattern ── */
.band {
  position: relative; overflow: hidden;
  border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.band > img.band-bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0;
}
/* Dark enough to keep the quote legible, light enough that the nebula is
   actually visible rather than a wash of near-black. */
.band::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(7,8,15,.8) 0%, rgba(7,8,15,.62) 50%, rgba(7,8,15,.85) 100%);
}
.band > .wrap { position: relative; z-index: 2; }
.hero-inner {
  position: relative; z-index: 2; isolation: isolate;
  max-width: 820px; margin: 0 auto; text-align: center;
  padding: 6rem var(--sp-5) 3.5rem;
}
/* A soft pool of shade behind the headline only. The hero rotates between a
   bright daytime aerial and dark deep-sky frames, and no single full-bleed
   overlay suits both: dark enough for the aerial washes out the nebulae.
   Shading locally keeps the photograph bright at the edges while the type
   always has something to sit on.

   The stop list is long and eased on purpose. A three-stop radial puts a
   visible ring where the falloff rate changes — the gradient reads as a hard
   edge rather than shade. Spreading the tail across many stops that ease out
   gradually, over an ellipse much larger than the text block, makes the
   boundary disappear. Peak is lower than before for the same reason. */
.hero-inner::before {
  content: ""; position: absolute; inset: -55% -30%; z-index: -1;
  /* closest-side, not the default farthest-corner: with farthest-corner the
     ramp still has opacity left when it reaches the element's left and right
     edges, so it gets clipped there and leaves a faint vertical seam.
     closest-side lands the transparent stop exactly on those edges. */
  background: radial-gradient(ellipse closest-side at center,
    rgba(7, 8, 15, 0.56) 0%,
    rgba(7, 8, 15, 0.54) 14%,
    rgba(7, 8, 15, 0.49) 24%,
    rgba(7, 8, 15, 0.42) 33%,
    rgba(7, 8, 15, 0.34) 41%,
    rgba(7, 8, 15, 0.26) 49%,
    rgba(7, 8, 15, 0.19) 57%,
    rgba(7, 8, 15, 0.13) 65%,
    rgba(7, 8, 15, 0.08) 73%,
    rgba(7, 8, 15, 0.04) 81%,
    rgba(7, 8, 15, 0.015) 89%,
    rgba(7, 8, 15, 0) 100%);
}
/* A hero with no stats strip under it needs its own breathing room back. */
.hero:not(:has(.trust)) .hero-inner { padding-bottom: 6rem; }
/* With a lighter veil the type has to hold its own contrast, so everything
   sitting on the photograph carries a shadow. */
.hero .eyebrow,
.hero h1,
.hero p { text-shadow: 0 2px 14px rgba(0, 0, 0, 0.85), 0 1px 3px rgba(0, 0, 0, 0.7); }
.hero h1 { font-size: var(--fs-hero); letter-spacing: 0; }
.hero h1 em { font-style: italic; color: var(--accent); }
.hero p { color: var(--ink-ui); font-size: 1.16rem; margin: var(--sp-5) auto var(--sp-7); max-width: 52ch; }
.trust .n, .trust .l { text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8); }

/* trust strip under the hero */
.trust {
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  gap: var(--sp-8); flex-wrap: wrap;
  max-width: 820px; margin: 0 auto;
  /* Equal above and below: with 0 bottom the stats sat flush on the hero's
     lower edge. */
  padding: var(--sp-6) var(--sp-5);
  border-top: 1px solid var(--border);
}
.trust > div { text-align: center; }
.trust .n {
  display: block;
  font-family: var(--font-display); font-size: 1.55rem; color: var(--ink); line-height: 1;
}
/* Labels sit on the photograph, so they use the light UI ink rather than a
   gray: --slate-dim read as barely there (Ed, September 2026). */
.trust .l {
  display: block; margin-top: var(--sp-2);
  font-size: 0.75rem; letter-spacing: 0.03em; color: var(--ink-ui);
}

/* ── cards / panels ────────────────────────────────────────── */
.panel {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-6);
}
.panel h3 { margin-bottom: var(--sp-3); }
.panel p { color: var(--slate); }

/* Phosphor icon at DSP's "light" weight, in gold — the same treatment DSP
   gives its own section icons. */
.panel-icon {
  display: block; width: 34px; height: 34px;
  color: var(--accent); margin-bottom: var(--sp-4);
}
.panel-icon svg { width: 100%; height: 100%; display: block; }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-5); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-4); }

/* pricing cards */
.cards { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--sp-3); align-items: stretch; }
.card {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-6) var(--sp-5);
  display: flex; flex-direction: column;
  transition: transform var(--t-med), border-color var(--t-med);
}
.card:hover { transform: translateY(-4px); border-color: var(--border-strong); }
.card .tier {
  font-family: var(--font-sans); font-weight: 600; font-size: 0.68rem;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--rust-text);
}
.card .price {
  font-family: var(--font-display); font-size: 2.3rem; color: var(--ink);
  margin: var(--sp-3) 0 2px; line-height: 1;
}
.card .price small {
  font-family: var(--font-sans); font-size: 0.8rem; font-weight: 500; color: var(--slate-dim);
}
.card .swing { font-size: 0.8rem; color: var(--accent); margin-bottom: var(--sp-3); }
.card .desc { font-size: 0.82rem; color: var(--slate); line-height: 1.55; margin: 0 0 var(--sp-5); flex: 1; }

/* feature columns with hairline dividers */
.feat {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 0;
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; background: var(--panel);
}
.feat .col { padding: var(--sp-7) var(--sp-6); border-right: 1px solid var(--border); }
.feat .col:last-child { border-right: 0; }
.feat .ic { width: 38px; height: 38px; margin-bottom: var(--sp-4); }
/* Gold, matching how DSP colors its section icons. The brand red is reserved
   for type; a 1.4px stroke in it would disappear against the panel. */
.feat .ic svg { width: 32px; height: 32px; stroke: var(--accent); fill: none; stroke-width: 1.4; }
.feat h4 { font-family: var(--font-display); font-weight: 400; margin: 0 0 var(--sp-2); }
.feat p { color: var(--slate); font-size: 0.86rem; margin: 0; }

/* checklists */
.checklist { list-style: none; margin: var(--sp-6) 0 0; padding: 0; display: grid; gap: var(--sp-4); }
.checklist li { display: flex; gap: var(--sp-3); align-items: flex-start; font-size: 0.94rem; color: var(--slate); }
.checklist strong { color: var(--ink-ui); font-weight: 600; }
/* Solid brand red with a light tick. A red tick on a translucent red disc
   would be two near-identical darks and effectively invisible. */
.check {
  flex: 0 0 22px; width: 22px; height: 22px; border-radius: var(--radius-pill);
  background: var(--rust); display: flex; align-items: center; justify-content: center;
  margin-top: 3px;
}
.check svg { width: 11px; height: 11px; stroke: var(--ink); fill: none; stroke-width: 2.5; }

/* team photos: the source portraits have wildly different aspect ratios, so
   crop them to a common square. Without this the names sit at three
   different heights across the row. */
.team-photo {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  object-position: center top;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: var(--sp-5);
}

/* credited inline image, for use beside body copy */
.figure-credited { margin: 0; }
.figure-credited img {
  width: 100%; border: 1px solid var(--border); border-radius: var(--radius);
}
.figure-credited figcaption {
  margin-top: var(--sp-3); font-size: 0.76rem; color: var(--slate-dim);
  letter-spacing: 0.04em;
}
.figure-credited figcaption b {
  display: block; color: var(--ink-ui); font-weight: 600; font-size: 0.82rem;
}

/* ── carousel ──────────────────────────────────────────────────
   One component for both the testimonial slideshows and the image
   slideshows the original site ran in Elementor. Slides are stacked and
   cross-faded; the track height comes from the tallest slide so nothing
   jumps between transitions.
   ─────────────────────────────────────────────────────────── */
.carousel { position: relative; }
.carousel-track { position: relative; }
.carousel-slide {
  opacity: 0; visibility: hidden;
  transition: opacity 0.6s ease;
  position: absolute; inset: 0;
  margin: 0;
  display: flex; flex-direction: column; justify-content: center;
}
/* The first slide stays in flow to give the track its height; the rest
   stack on top of it. */
.carousel-slide:first-child { position: relative; }
.carousel-slide.is-active { opacity: 1; visibility: visible; }

/* Without JS every slide would be invisible, so reveal the first one and
   let the others fall back to a plain stacked list. */
.no-js .carousel-slide { position: relative; opacity: 1; visibility: visible; }

/* Wrapping, because this row does not fit a phone. A general testimonial has
   nine dots: at 320px the row needs 309px of content inside a 224px box, and
   without wrap the arrows overflow and clip. That was true before the stop
   control was added and the control made it worse. */
.carousel-controls {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: var(--sp-4); margin-top: var(--sp-6);
}
.carousel-dots { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2); }
@media (max-width: 600px) {
  /* Buttons on one line, dots on their own beneath. Nine dots and three
     buttons cannot share a row on a phone, and letting them wrap in DOM order
     strands each arrow on a line of its own. */
  .carousel-dots { order: 1; flex-basis: 100%; }
}
.carousel-dots button {
  width: 22px; height: 3px; padding: 0; border: 0; border-radius: 2px;
  background: var(--border-strong); cursor: pointer;
  transition: background var(--t-fast);
}
.carousel-dots button[aria-current="true"] { background: var(--accent); }
.carousel-dots button:hover { background: var(--accent-hover); }
.carousel-nav {
  background: none; border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--slate); cursor: pointer; width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.carousel-nav:hover { color: var(--accent); border-color: var(--border-strong); }
.carousel-nav svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2; }

/* testimonial variant */
.carousel-quote { text-align: center; padding: 0 var(--sp-5); }
.carousel-quote blockquote {
  font-size: clamp(1.15rem, 2.2vw, 1.6rem); margin: 0 auto var(--sp-5);
  max-width: 60ch;
}
.carousel-quote .who b {
  display: block; font-family: var(--font-sans); font-size: 0.86rem;
  color: var(--ink-ui); font-weight: 600;
}
.carousel-quote .who span { font-size: 0.78rem; color: var(--slate-dim); }

/* image variant */
.carousel-photo .carousel-slide { display: block; }
.carousel-photo img {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border: 1px solid var(--border); border-radius: var(--radius);
}
.carousel-photo figcaption {
  margin-top: var(--sp-3); text-align: center;
  font-size: 0.8rem; color: var(--slate-muted);
}

/* Swing diameter gallery.
   Unlike .carousel-photo, which crops to 16:9, these are annotated images:
   cropping would slice Ed's labels and legends off. A fixed frame with
   object-fit: contain keeps every annotation visible, holds the panel at one
   height as slides change, and lets the single portrait shot letterbox
   instead of being destroyed. */
.carousel-figure .carousel-slide { display: block; }
.carousel-figure .carousel-zoom {
  display: block; background: var(--black);
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; cursor: zoom-in;
}
.carousel-figure .carousel-zoom:hover { border-color: var(--border-strong); }
.carousel-figure img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: contain; }
/* --slate-dim measures 3.42:1 on --panel, under the WCAG minimum of 4.5:1
   for normal text. --slate-muted is 5.36:1. These are instructions, not the
   short uppercase brand labels that tokens.css deliberately exempts. */
.carousel-figure figcaption {
  margin-top: var(--sp-3); text-align: center;
  font-size: 0.8rem; color: var(--slate-muted);
}
.carousel-hint {
  margin: var(--sp-2) 0 0; text-align: center;
  font-size: 0.78rem; color: var(--slate-muted);
  letter-spacing: var(--ls-nav); text-transform: uppercase;
}
/* Without JS the link opens the raw image, so the zoom cursor would promise a
   lightbox that is not there. */
.no-js .carousel-figure .carousel-zoom { cursor: pointer; }
.no-js .carousel-hint { display: none; }

/* Lightbox. A <dialog>, so focus trapping, Escape and page inertness come
   from the browser rather than being hand-rolled. Closed state is left to the
   user agent stylesheet; only [open] gets a display value. */
.lightbox {
  /* Must be fixed. The UA gives dialog:modal position: fixed, and overriding
     it with relative makes a top-layer element compute to absolute against
     the initial containing block: opened from a gallery 900px down the page,
     the dialog rendered 120px above the top of the viewport. */
  position: fixed; inset: 0;
  width: auto; height: auto; max-width: none; max-height: none;
  margin: 0; padding: var(--sp-5); border: 0; background: transparent;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--sp-4);
}
.lightbox[open] { display: grid; }
.lightbox::backdrop { background: rgba(7, 8, 15, 0.94); }
.lightbox figure { margin: 0; min-width: 0; text-align: center; }
.lightbox img {
  max-width: 100%; max-height: 80vh; object-fit: contain;
  border: 1px solid var(--border); border-radius: var(--radius);
  /* The reset sets img { display: block }, so the figure's text-align cannot
     centre this and auto margins have to do it. */
  margin-inline: auto;
}
.lightbox figcaption {
  margin-top: var(--sp-4); color: var(--slate); font-size: 0.85rem;
}
.lightbox-close, .lightbox-nav {
  display: grid; place-items: center; flex: none;
  width: 44px; height: 44px; padding: 0; cursor: pointer;
  color: var(--slate); background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--radius);
}
.lightbox-close:hover, .lightbox-nav:hover { color: var(--accent); border-color: var(--border-strong); }
.lightbox-close svg, .lightbox-nav svg {
  width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2;
}
.lightbox-close { position: absolute; top: var(--sp-5); right: var(--sp-5); z-index: 2; }

@media (max-width: 700px) {
  /* Side-by-side arrows would eat the width the image needs, so they move
     over the image edges. Hiding them is not an option: once the dialog is
     open the carousel's own dots are unreachable behind it. */
  .lightbox { grid-template-columns: minmax(0, 1fr); padding: var(--sp-4); }
  .lightbox-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; }
  .lightbox-nav.prev { left: var(--sp-3); }
  .lightbox-nav.next { right: var(--sp-3); }
  .lightbox img { max-height: 70vh; }
}

/* value grid: image beside copy */
.value-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-9); align-items: center; }
.value-grid img { border-radius: var(--radius); border: 1px solid var(--border); width: 100%; }

/* ── live all-sky camera (conditions page) ─────────────────── */
.allsky { max-width: 1024px; margin: 0 auto; }
.allsky img {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--panel);
  transition: opacity var(--t-fast);
}
.allsky.is-offline img { opacity: 0.35; }
.allsky figcaption {
  margin-top: var(--sp-3); text-align: center;
  font-size: 0.78rem; line-height: 1.5; color: var(--slate-muted);
}

/* ── counters (roof hours, site stats) ─────────────────────── */
.counters {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-5);
  text-align: center;
}
.counter .n {
  font-family: var(--font-display); font-size: clamp(2rem, 4.6vw, 3.2rem);
  color: var(--ink); line-height: 1; display: block;
}
/* --slate, not --slate-dim: at this size the dim gray falls short of 4.5:1
   contrast on the page background. */
.counter .l {
  display: block; font-size: 0.78rem; color: var(--slate); margin-top: var(--sp-2); line-height: 1.4;
  letter-spacing: 0.03em;
}
.counters-live .n { color: var(--accent); }
.counter-note { font-size: 0.75rem; color: var(--slate-muted); text-align: center; margin-top: var(--sp-5); }

/* ── tables ────────────────────────────────────────────────────
   Spec-sheet styling lifted from DSP: dim first column, hairline rows.
   ─────────────────────────────────────────────────────────────── */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table {
  width: 100%; border-collapse: collapse; margin: var(--sp-5) 0;
  font-size: 0.88rem;
}
/* Gold rather than brand red: these are column headers people actually read
   to parse the table, not decorative labels. */
thead th {
  text-align: left; color: var(--accent); font-family: var(--font-sans);
  font-weight: 600; font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase;
  padding: var(--sp-3); border-bottom: 1px solid var(--border-strong);
  white-space: nowrap;
}
tbody tr { border-bottom: 1px solid var(--border); }
tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.015); }
td { padding: var(--sp-3); vertical-align: top; color: var(--slate); }
td:first-child { color: var(--ink-ui); font-weight: 500; }

/* ── quotes ────────────────────────────────────────────────── */
.quote-band {
  position: relative; padding: var(--sp-10) 0; text-align: center;
  border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
  background: var(--panel);
}
blockquote {
  font-family: var(--font-display); font-style: italic;
  font-size: clamp(1.3rem, 3vw, 2rem); line-height: 1.4; color: var(--ink);
  max-width: 820px; margin: var(--sp-2) auto var(--sp-6); font-weight: 400;
  padding: 0;
}
.quote-who b { font-family: var(--font-sans); font-weight: 600; font-size: 0.86rem; color: var(--ink-ui); display: block; }
.quote-who span { font-size: 0.8rem; color: var(--slate-dim); }

.quote-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-5); }
.quote-card {
  background: var(--panel); border: 1px solid var(--border);
  border-left: 2px solid var(--rust); border-radius: var(--radius);
  padding: var(--sp-6);
}
.quote-card p {
  font-family: var(--font-display); font-style: italic;
  color: var(--ink); font-size: 1rem; line-height: 1.55; margin-bottom: var(--sp-4);
}
.quote-card .who b { display: block; font-family: var(--font-sans); font-style: normal; font-size: 0.82rem; color: var(--ink-ui); }
.quote-card .who span { font-size: 0.76rem; color: var(--slate-dim); }

/* ── forms ─────────────────────────────────────────────────── */
.form { display: grid; gap: var(--sp-4); text-align: left; }
/* The form grid stretches its children, which turns an inline calculator
   button into a full-width bar. Only .btn-block should span. */
.form > .btn:not(.btn-block) { justify-self: start; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.field { display: grid; gap: var(--sp-2); }
.field label {
  font-family: var(--font-sans); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--slate-dim);
}
.field .req { color: var(--error); }
/* DSP sets --input to the same hairline as --border, and --ring to the gold,
   so fields read as quiet until focused. */
input[type="text"], input[type="email"], input[type="tel"], textarea, select {
  width: 100%;
  background: var(--black);
  border: 1px solid var(--border);
  color: var(--ink-ui);
  border-radius: var(--radius);
  padding: 0.85rem 0.9rem;
  font-size: 0.95rem;
  font-family: var(--font-sans);
  transition: border-color var(--t-fast);
}
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent); }
input::placeholder, textarea::placeholder { color: var(--slate-dim); }
textarea { min-height: 150px; resize: vertical; }

/* Chrome paints autofilled inputs a hard light color with dark text, which
   blows out a dark form. background-color is ignored on autofill, so use the
   long inset shadow trick instead (same fix DSP uses). */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill {
  -webkit-text-fill-color: var(--ink-ui);
  -webkit-box-shadow: 0 0 0 1000px #0a0b0f inset;
  box-shadow: 0 0 0 1000px #0a0b0f inset;
  transition: background-color 5000s ease-in-out 0s;
  caret-color: var(--ink-ui);
}

.form-status { border-radius: var(--radius); padding: var(--sp-4); font-size: 0.9rem; display: none; }
.form-status.is-visible { display: block; }
.form-status.ok { background: rgba(90, 154, 106, 0.14); border: 1px solid var(--success); color: #cfe8d5; }
.form-status.err { background: rgba(187, 51, 51, 0.14); border: 1px solid var(--error); color: #f0cccc; }

/* Honeypot — bots fill it, humans never see it. */
.hp { position: absolute; left: -9999px; opacity: 0; height: 0; overflow: hidden; }

/* ── newsletter ────────────────────────────────────────────── */
.newsletter { background: var(--panel); border-top: 1px solid var(--border); padding: var(--sp-9) 0; }
.newsletter-inner { max-width: 620px; margin: 0 auto; text-align: center; }
.newsletter form { display: flex; gap: var(--sp-3); margin-top: var(--sp-5); flex-wrap: wrap; }
.newsletter input { flex: 1; min-width: 220px; }
.newsletter .msg { font-size: 0.84rem; margin-top: var(--sp-3); min-height: 1.2em; }
.newsletter .msg.ok { color: var(--success); }
.newsletter .msg.err { color: var(--error); }

/* ── prose (blog post bodies, generic pages) ───────────────── */
.prose { color: var(--slate); font-size: 1.02rem; line-height: 1.8; }
.prose > * + * { margin-top: var(--sp-4); }
.prose h2 {
  font-size: 1.6rem; color: var(--ink); margin-top: var(--sp-8);
  padding-top: var(--sp-7); border-top: 1px solid var(--border);
}
.prose h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.prose h3 { font-size: 1.25rem; color: var(--ink-ui); margin-top: var(--sp-6); }
.prose h4 {
  font-family: var(--font-sans); font-size: 0.95rem; font-weight: 600;
  letter-spacing: 0.03em; color: var(--ink-ui); margin-top: var(--sp-5);
}
.prose strong { color: var(--ink-ui); font-weight: 600; }
.prose a { text-decoration: underline; text-underline-offset: 3px; }
.prose ul, .prose ol { padding-left: 1.25rem; }
.prose li { margin-bottom: var(--sp-2); }
.prose img { border: 1px solid var(--border); border-radius: var(--radius); margin: var(--sp-6) 0; }
.prose figure { margin: var(--sp-6) 0; }
.prose figcaption { font-size: 0.8rem; color: var(--slate-dim); text-align: center; margin-top: var(--sp-2); }
.prose blockquote {
  font-size: 1.15rem; border-left: 3px solid var(--rust);
  background: var(--panel); padding: var(--sp-5) var(--sp-6);
  margin: var(--sp-6) 0; text-align: left;
}
.prose code {
  font-family: var(--font-mono); font-size: 0.88em;
  background: var(--panel-2); padding: 0.15em 0.35em; border-radius: var(--radius);
  color: var(--ink-ui);
}
.prose pre {
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-4); overflow-x: auto;
}
.prose pre code { background: none; padding: 0; }
.prose hr { margin: var(--sp-7) 0; }

/* Ghost card classes emitted by the editor */
.kg-width-wide { max-width: calc(var(--maxw-prose) + 12rem); margin-inline: auto; }
.kg-width-full { max-width: 100%; }
.kg-image { margin-inline: auto; }
.kg-embed-card { display: flex; justify-content: center; }
.kg-embed-card iframe { max-width: 100%; }
.kg-gallery-container { display: grid; gap: var(--sp-3); }
.kg-gallery-row { display: grid; grid-auto-flow: column; gap: var(--sp-3); }
.kg-bookmark-card {
  display: block; border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-5); background: var(--panel);
}

/* ── blog ──────────────────────────────────────────────────── */
.post-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-6); }
.post-card {
  display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
  transition: transform var(--t-med), border-color var(--t-med);
}
.post-card:hover { transform: translateY(-4px); border-color: var(--border-strong); }
.post-card .thumb { aspect-ratio: 16 / 9; overflow: hidden; background: var(--panel-2); border-bottom: 1px solid var(--border); }
.post-card .thumb img { width: 100%; height: 100%; object-fit: cover; }
.post-card .body { padding: var(--sp-5); display: flex; flex-direction: column; flex: 1; }
.post-card h3 { font-size: 1.15rem; margin-bottom: var(--sp-3); }
.post-card p { font-size: 0.88rem; color: var(--slate); flex: 1; }
.post-card .meta {
  font-family: var(--font-sans); font-size: 0.7rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--slate-dim); margin-top: var(--sp-4);
}

.post-header { text-align: center; padding: var(--sp-9) 0 var(--sp-7); border-bottom: 1px solid var(--border); }
.post-header h1 { max-width: 20ch; margin: var(--sp-3) auto; }
.post-meta {
  font-family: var(--font-sans); font-size: 0.72rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--slate-dim);
}
.post-feature { margin: 0 auto var(--sp-8); max-width: var(--maxw); padding: 0 var(--sp-5); }
.post-feature img { width: 100%; border: 1px solid var(--border); border-radius: var(--radius); }
.post-body { max-width: var(--maxw-prose); margin: 0 auto; padding: 0 var(--sp-5) var(--sp-9); }

.pagination { display: flex; justify-content: center; gap: var(--sp-4); align-items: center; margin-top: var(--sp-8); }
.pagination .page-number { color: var(--slate-dim); font-size: 0.82rem; }

/* ── gallery (member images) ───────────────────────────────── */
.gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-4); }
.gallery figure { margin: 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.gallery img { width: 100%; aspect-ratio: 1; object-fit: cover; transition: transform var(--t-med); }
.gallery figure:hover img { transform: scale(1.03); }
.gallery figcaption { padding: var(--sp-3) var(--sp-4); font-size: 0.8rem; color: var(--slate); }
.gallery figcaption b { display: block; color: var(--ink-ui); font-weight: 600; }
.gallery .badge {
  display: inline-block; font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--rust-text); margin-bottom: 4px;
}

/* ── embeds (weather widgets, satellite, radar) ────────────── */
.embed-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-5); align-items: start; }
.embed-card {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-5); overflow: hidden;
}
.embed-card h3 { font-size: 1.15rem; margin-bottom: var(--sp-2); }
.embed-card > p { font-size: 0.88rem; margin-bottom: var(--sp-4); }
.embed-card iframe, .embed-card img { max-width: 100%; margin-inline: auto; border-radius: var(--radius); }
.embed-card iframe { border: 0; display: block; }

/* ── footer ────────────────────────────────────────────────── */
.site-footer {
  background: var(--footer-bg); border-top: 1px solid var(--border);
  padding: var(--sp-9) 0 var(--sp-7);
}
.fgrid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1.2fr; gap: var(--sp-7); }
.fbrand img { width: 60px; height: 60px; margin-bottom: var(--sp-4); border-radius: var(--radius-pill); }
.fbrand p { color: var(--slate-dim); font-size: 0.84rem; max-width: 32ch; }
.fcol h5 {
  font-family: var(--font-sans); font-weight: 600; font-size: 0.68rem;
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
  color: var(--rust-text); margin: 0 0 var(--sp-4);
}
.fcol ul { list-style: none; margin: 0; padding: 0; }
.fcol a, .fcol p { display: block; color: var(--slate); font-size: 0.88rem; margin-bottom: var(--sp-2); }
.fcol a:hover { color: var(--ink-ui); }
.fbot {
  display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
  margin-top: var(--sp-8); padding-top: var(--sp-5); border-top: 1px solid var(--border);
}
.fbot .copy { color: var(--slate-dim); font-size: 0.8rem; margin-right: auto; }
.socials { display: flex; gap: var(--sp-3); }
.socials a {
  width: 34px; height: 34px; border-radius: var(--radius);
  border: 1px solid var(--border); display: flex; align-items: center;
  justify-content: center; color: var(--slate);
}
.socials a:hover { color: var(--accent); border-color: var(--border-strong); }
.socials svg { width: 15px; height: 15px; fill: currentColor; }

/* ── utilities ─────────────────────────────────────────────── */
.center { text-align: center; }
.mt-0 { margin-top: 0; }
.mt-4 { margin-top: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }
.mt-8 { margin-top: var(--sp-8); }
.mb-6 { margin-bottom: var(--sp-6); }
.muted { color: var(--slate-dim); font-size: 0.82rem; }
.accent { color: var(--accent); }

/* ── responsive ────────────────────────────────────────────── */
@media (max-width: 1000px) {
  .cards { grid-template-columns: repeat(2, 1fr); }
  .post-list, .quote-grid, .grid-3, .gallery { grid-template-columns: repeat(2, 1fr); }
  .value-grid, .grid-2, .embed-grid { grid-template-columns: 1fr; }
  .feat { grid-template-columns: 1fr; }
  .feat .col { border-right: 0; border-bottom: 1px solid var(--border); }
  .feat .col:last-child { border-bottom: 0; }
  .fgrid { grid-template-columns: 1fr 1fr; }
  .counters { grid-template-columns: repeat(2, 1fr); gap: var(--sp-6); }
}

/* The nav has its own breakpoint because it depends on what the header holds,
   not on the page grids above. With the 10Micron badge beside the brand, the
   full nav needs about 1,010px; below 1,080px its labels start to wrap. */
@media (max-width: 1080px) {
  .mainnav {
    display: none; position: absolute; top: var(--header-h); left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--panel-2); border-bottom: 1px solid var(--border-strong);
    padding: var(--sp-4); max-height: calc(100vh - var(--header-h)); overflow-y: auto;
  }
  .mainnav.open { display: flex; }
  .mainnav a, .has-menu > button { padding: var(--sp-3) 0; font-size: 0.86rem; width: 100%; }
  .submenu {
    position: static; transform: none; opacity: 1; visibility: visible;
    display: none; border: 0; background: none; box-shadow: none;
    padding: 0 0 0 var(--sp-4); min-width: 0;
  }
  .has-menu.open .submenu { display: block; }
  .menu-toggle { display: block; }
}

/* Phones: the brand, badge and menu button share about 340px. The brand
   drops its second line (the round mark already reads "Remote Hosting"), and
   nothing is allowed to shrink, because a squeezed logo distorts. */
@media (max-width: 600px) {
  .nav { gap: var(--sp-4); }
  .brand-text { white-space: nowrap; }
  .brand-text span { display: none; }
  .partner { padding-left: var(--sp-4); gap: 4px; }
  .partner-label { font-size: 0.52rem; white-space: nowrap; }
  .partner img { height: 15px; }
}
@media (max-width: 400px) {
  .nav { gap: var(--sp-3); }
  .brand { gap: var(--sp-2); }
  .brand img { width: 38px; height: 38px; }
  .brand-text { letter-spacing: 0.12em; padding-left: var(--sp-2); }
  .partner { padding-left: var(--sp-3); }
  /* Hidden visually only, so a screen reader still hears "Partnered with
     10Micron" rather than just "10Micron". Same technique as .sr-only. */
  .partner-label {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
  .partner img { height: 14px; }
}
/* 320px phones (the first iPhone SE) are 17px short even then. */
@media (max-width: 350px) {
  .partner { display: none; }
  .brand { margin-right: auto; }
}

@media (max-width: 640px) {
  :root { --section-y: 3.5rem; }
  .cards, .post-list, .quote-grid, .grid-3, .grid-4, .gallery, .fgrid, .form-row { grid-template-columns: 1fr; }
  .hero-inner { padding: 4.5rem var(--sp-5) 3.5rem; }
  /* Four stats in a 2 x 2 grid, not a 1-2-1 wrap. */
  .trust { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5) var(--sp-4); }
  .feat .col { padding: var(--sp-6) var(--sp-5); }
}
