/* ==========================================================================
   Hemisphere Lens — hemisphere-pathology.ca

   Palette inherited from the sibling property hemisphere-intelligence.ca so the
   two read as one product family. Wordmark, tagline and the "L" ring mark are
   preserved from docs/lens-promo-video-brief.md.

   Type is Inter only. No monospace, no micro-labels above sections, no chips,
   no accent dots. A section is a heading, a lead, and its content.
   ========================================================================== */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 200;
  font-display: swap;
  src: url('assets/fonts/inter-latin-200-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/inter-latin-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('assets/fonts/inter-latin-600-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('assets/fonts/inter-latin-700-normal.woff2') format('woff2');
}

:root {
  --bg: #1a0d14;
  --surface: #21121b;
  --text: #f1e7ec;
  --dim: #c3a8b4;

  --border: rgba(200, 110, 145, 0.24);
  --border-soft: rgba(200, 110, 145, 0.14);
  --tint: rgba(212, 130, 165, 0.08);

  --blue: #4285f4;
  --green: #34a853;
  --yellow: #fbbc05;

  --sans: 'Inter', -apple-system, 'system-ui', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --measure: 1180px;
  --nav-h: 64px;

  --radius: 14px;
  --pill: 999px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 1rem);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* The wallpaper the tile field floats in. Flat colour gives the eye nothing to
   judge distance against, which is what leaves the empty half of the hero
   reading as a wall rather than as air — the same problem the portal had. A
   pool of light behind the mark and a darker floor give the void somewhere to
   recede to. Fixed, so the sections scroll through it rather than dragging it
   along. Held to the family: the page colour lifted and dropped, no new hue. */
body.lens-page {
  background:
    radial-gradient(ellipse 86% 66% at 68% 20%, #2e1a28 0%, #20111a 36%, transparent 70%),
    radial-gradient(ellipse 130% 80% at 50% 104%, #120810 0%, transparent 62%),
    var(--bg);
  background-attachment: fixed;
}

img,
svg,
video {
  max-width: 100%;
  display: block;
}

img {
  height: auto;
}

h1,
h2,
h3 {
  margin: 0;
  text-wrap: balance;
}

p {
  margin: 0;
  text-wrap: pretty;
}

a {
  color: inherit;
}

/* ---------- WebGL backdrop ---------- */
#bg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}

/* Deliberately recessive — it is a backdrop, never competition for the copy. */
#bg.is-ready {
  opacity: 1;
}

/* Everything else sits above the canvas. */
.nav,
main,
footer {
  position: relative;
  z-index: 1;
}

/* ---------- Accessibility ---------- */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: 0.75rem 1.25rem;
  background: var(--text);
  color: var(--surface);
  border-radius: 0 0 var(--radius) 0;
  font-size: 0.875rem;
  font-weight: 600;
}
.skip-link:focus {
  left: 0;
}

:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---------- Layout ---------- */
.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.center {
  text-align: center;
}

.s {
  padding-block: clamp(6rem, 15vh, 11rem);
  position: relative;
}

/* A soft well behind the copy so it stays legible over the moving field.

   It bleeds vertically but not sideways. A horizontal bleed pushes the document
   wider than the viewport and the whole page scrolls left-to-right — 5% of a
   375px phone is 19px of drift on every section. `.picker::before` on the
   portal carries the same warning; this is the same trap. The gradient fades
   out well before its own edge, so nothing is lost by stopping at the box. */
.s::before {
  content: '';
  position: absolute;
  inset: -8% 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    ellipse 66% 58% at 50% 50%,
    rgba(26, 13, 20, 0.86) 0%,
    rgba(26, 13, 20, 0.8) 42%,
    rgba(26, 13, 20, 0.35) 72%,
    transparent 88%
  );
}

/* The intro's type is large and bold, so let the brand mark read through. */
.intro::before {
  background: radial-gradient(
    ellipse 58% 50% at 50% 50%,
    rgba(26, 13, 20, 0.6) 0%,
    rgba(26, 13, 20, 0.46) 46%,
    rgba(26, 13, 20, 0.2) 74%,
    transparent 90%
  );
}

/* ==========================================================================
   Nav
   ========================================================================== */
/* Matched to the bar on hemisphere-intelligence.ca: full bleed, 24px of edge,
   transparent over a 12px blur, one hairline underneath. */
.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  background: transparent;
  border-bottom: 1px solid var(--border);
  box-shadow:
    rgba(14, 4, 10, 0.55) 0 2px 8px 0,
    rgba(14, 4, 10, 0.4) 0 4px 16px 0;
}

.nav-inner {
  width: 100%;
  padding-inline: clamp(1rem, 2.4vw, 1.5rem);
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.nav-brand {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  text-decoration: none;
  margin-right: auto;
  min-width: 0;
}

.nav-brand img {
  width: 32px;
  height: 32px;
  flex: none;
}

.nav-word {
  font-size: clamp(0.9375rem, 2.4vw, 1.5rem);
  font-weight: 200;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.3em;
  padding: 0.6875rem 1.375rem;
  border-radius: var(--pill);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  border: 1px solid transparent;
  cursor: pointer;
  transition:
    background-color 0.25s var(--ease),
    border-color 0.25s var(--ease);
}

.btn-solid {
  background: var(--text);
  color: var(--surface);
}
.btn-solid:hover {
  background: #fff;
}

.btn-outline {
  background: rgba(26, 13, 20, 0.5);
  color: var(--text);
  border-color: var(--border);
}
.btn-outline:hover {
  background: var(--tint);
  border-color: rgba(200, 110, 145, 0.45);
}

.btn-lg {
  padding: 0.8125rem 1.75rem;
  font-size: 0.9375rem;
}

/* ==========================================================================
   1 · Intro
   ========================================================================== */
.intro {
  min-height: calc(100svh - var(--nav-h));
  display: flex;
  align-items: center;
  padding-block: clamp(4rem, 10vh, 7rem);
}

.wordmark {
  font-weight: 200;
  font-size: clamp(1rem, 2.4vw, 1.5rem);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-indent: 0.2em;
}

.tagline {
  font-weight: 200;
  font-size: clamp(0.8125rem, 1.6vw, 1rem);
  letter-spacing: 0.04em;
  color: var(--dim);
  text-transform: lowercase;
  margin-top: 0.75rem;
}

/* Two-column hero: copy left, the "L" mark and wordmark right. */
.intro-grid {
  display: grid;
  /* Even columns. The mark used to take the smaller half and sit under a
     wordmark; now it is the whole of this side and wants the room. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

.intro-copy {
  min-width: 0;
}

.intro .lead {
  margin-top: clamp(1.5rem, 3vw, 2rem);
}

.intro-mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* Carried much larger now that the wordmark and tagline are gone from under it
   — with nothing below to share the column, the mark is the whole right-hand
   half of the hero and wants to be read as the object it is.

   The floor stays low on purpose. Below about 520px the columns stack and the
   mark sits *above* the headline, so every pixel it gains pushes the headline
   further down the phone; at a 240px floor the first screen was mark and
   nothing else. 46vw governs everywhere the layout is still two columns. */
.intro-mark img {
  width: clamp(170px, 46vw, 560px);
  animation: mark-in 1.4s var(--ease) both;
}

@keyframes mark-in {
  from {
    opacity: 0;
    scale: 0.94;
    filter: blur(6px);
  }
  to {
    opacity: 1;
    scale: 1;
    filter: blur(0);
  }
}

.intro h1 {
  font-size: clamp(2.25rem, 7vw, 5rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.03;
}

.lines {
  list-style: none;
  padding: 0;
  margin: clamp(2rem, 4vw, 2.75rem) 0 0;
  max-width: 46ch;
  color: var(--dim);
  font-size: clamp(1rem, 1.7vw, 1.25rem);
  line-height: 1.85;
}

.kicker {
  margin-top: clamp(1.75rem, 3.5vw, 2.5rem);
  font-size: clamp(1.0625rem, 2vw, 1.375rem);
  font-weight: 600;
}

.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: clamp(2rem, 4vw, 2.75rem);
}

/* The three platforms, standing in for the line of text that used to sit under
   the buttons. Dim on purpose: this is a caption to the row, not a third
   control, and it must not read as something you can press. */
.platforms {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  margin-left: 0.6rem;
  color: var(--dim);
}

/* A definite size, deliberately not one derived from the row height. Sizing
   these off `height: 100%` leaves their width unknown while the row is being
   laid out: the group measures almost nothing, flex-wrap never learns it needs
   a line of its own, and the glyphs paint at full size straight off the side of
   a narrow screen. 2.875rem is the button height, matched by value instead.
   max-width is overridden because the global `svg` rule would otherwise clamp
   against a container whose own width these glyphs determine. */
.platforms svg {
  display: block;
  width: 2.875rem;
  height: 2.875rem;
  flex: none;
  max-width: none;
  fill: currentColor;
}

.center .cta {
  justify-content: center;
}

.note {
  margin-top: 1.5rem;
  font-size: 0.875rem;
  color: var(--dim);
}

.note a {
  color: var(--text);
  text-underline-offset: 4px;
}

/* ==========================================================================
   Section headings + beats
   ========================================================================== */
.s h2 {
  font-size: clamp(1.875rem, 5vw, 3.25rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.08;
  max-width: 20ch;
}

.center h2 {
  margin-inline: auto;
}

.lead {
  margin-top: clamp(1.25rem, 2.5vw, 1.75rem);
  max-width: 58ch;
  font-size: clamp(1rem, 1.6vw, 1.1875rem);
  color: var(--dim);
}

.center .lead {
  margin-inline: auto;
}

.lead em {
  font-style: normal;
  color: var(--text);
  font-weight: 600;
}

.beats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1rem;
  margin-top: clamp(3rem, 6vw, 4.5rem);
}

.beat {
  background: rgba(33, 18, 27, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 1.75rem 1.625rem;
}

.beat.wide {
  grid-column: 1 / -1;
}

.beat h3 {
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.35;
}

.beat p {
  color: var(--dim);
  font-size: 0.9375rem;
  margin-top: 0.875rem;
}

/* ==========================================================================
   The radial dial — the real viewer component
   ========================================================================== */
.dial-beat {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(360px, 1.15fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: center;
  padding: clamp(1.75rem, 3.5vw, 2.75rem);
}

.dial-copy h3 {
  font-size: clamp(1.25rem, 2.4vw, 1.625rem);
}

.dial-copy p {
  margin-top: 1rem;
}

.dial-hint {
  color: var(--text);
  font-size: 0.875rem;
  opacity: 0.75;
}

.dial-stage {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  /* The dial is dark glass in the app; give it a slide-ish ground so the
     furniture and ticks read the way they do over tissue. */
  background: #f4eff1;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: clamp(0.75rem, 2vw, 1.5rem);
  overflow: hidden;
  min-height: 340px;
}

/* the slide the dial sits on */
.smear {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
}

/* keeps the bright field from fighting the page, and frames the dial */
.dial-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(circle at 50% 48%, transparent 46%, rgba(20, 10, 16, 0.42) 100%);
}

.dial {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 620px;
}

.dial-svg {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
  touch-action: none;
}

.dial-puck {
  cursor: pointer;
}

.dial-puck:focus {
  outline: none;
}

.dial-puck:focus-visible circle {
  stroke: var(--blue);
  stroke-width: 2.5;
}

/* Typography lives inline in radial.js, verbatim from the component; the
   classes below carry only the component's motion. */

.dial-drop {
  animation: radialDropIn 0.26s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

@keyframes radialDropIn {
  0% {
    opacity: 0;
    transform: translateY(-16px);
  }
  60% {
    opacity: 1;
    transform: translateY(3px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

.dial-arc {
  animation: radialArcIn 0.2s ease-out backwards;
}

@keyframes radialArcIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* The dial's SVG text never carries inline styles (the CSP forbids them) */
.dial-svg text {
  user-select: none;
}

/* The docked "Image Adjust" complication card — ViewerApp's enhancePanel in
   the viewer's dark-glass chrome (themeDefinitions darkGlassTheme). Docked
   outside the bezel at 3 o'clock: left = (C + R_BRAND + 14) / S. */
.dial-card {
  position: absolute;
  left: 81.613%;
  top: 32.258%;
  min-width: 230px;
  background: rgba(20, 20, 22, 0.97);
  border: 1px solid rgba(255, 255, 255, 0.13);
  border-radius: 12px;
  padding: 16px;
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.7), 0 2px 12px rgba(0, 0, 0, 0.5);
  font-family: 'Inter', system-ui, sans-serif;
  z-index: 3;
  animation: qwatchCardIn 0.18s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

.dial-card-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.dial-card-title {
  font-size: 9.9px;
  font-weight: 700;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.dial-card-reset {
  font-size: 8.8px;
  font-weight: 600;
  color: #999;
  letter-spacing: 0.04em;
  cursor: pointer;
}

.dial-card-tabs {
  display: flex;
  gap: 2px;
  margin-bottom: 14px;
  padding: 2px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.13);
}

.dial-card-chip {
  flex: 1;
  padding: 4px 0;
  text-align: center;
  border-radius: 999px;
  cursor: pointer;
  font-size: 8.8px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #999;
}

.dial-card-chip.is-selected {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  font-weight: 700;
}

.dial-card-row {
  margin-bottom: 8px;
}

.dial-card-rowhead {
  display: flex;
  justify-content: space-between;
  margin-bottom: 4px;
}

.dial-card-lab {
  font-size: 9.6px;
  font-weight: 600;
  color: #999;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.dial-card-val {
  font-size: 9.6px;
  font-weight: 700;
  color: #fff;
  font-family: 'Cascadia Code', Consolas, monospace;
}

.dial-card-range {
  width: 100%;
  height: 4px;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  cursor: pointer;
}

.dial-card-range::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.13);
}

.dial-card-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid rgba(20, 20, 22, 1);
  margin-top: -5px;
  cursor: pointer;
}

.dial-card-check {
  width: 15px;
  height: 15px;
  accent-color: #fff;
  cursor: pointer;
}

.dial-card-hint {
  font-size: 9.3px;
  color: #999;
  font-style: italic;
  margin-top: 4px;
}

@keyframes qwatchCardIn {
  from {
    opacity: 0;
    transform: translateX(-8px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .dial-drop,
  .dial-arc,
  .dial-card {
    animation: none;
  }
}

@media (max-width: 900px) {
  .dial-beat {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   5 · Ownership — the struck fee list
   ========================================================================== */
.struck {
  list-style: none;
  padding: 0;
  margin: clamp(2.5rem, 5vw, 3.5rem) 0 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  align-items: flex-start;
}

.struck li {
  position: relative;
  font-size: clamp(1.125rem, 2.6vw, 1.75rem);
  color: var(--dim);
  opacity: 0.6;
  padding-inline: 0.25rem;
}

.struck li::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 52%;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  /* Default drawn, so the lines still read as struck out with JS disabled. */
  transform: scaleX(1);
  transform-origin: left center;
  transition: transform 0.7s var(--ease) var(--d, 0ms);
}

/* Once the reveal script has claimed the list, hold the strike back until the
   list scrolls into view, then draw it. */
.struck.reveal:not(.is-in) li::after {
  transform: scaleX(0);
}

.statement {
  margin-top: clamp(2rem, 4vw, 2.75rem);
  font-size: clamp(1.375rem, 3.6vw, 2.25rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  max-width: 22ch;
}

/* ==========================================================================
   Close
   ========================================================================== */
.leaf {
  display: inline-block;
  vertical-align: -0.08em;
  width: 0.78em;
  height: 0.78em;
  margin-left: 0.18em;
  color: #d94a3d;
}
.close h2 {
  max-width: 18ch;
}

/* ==========================================================================
   Footer
   ========================================================================== */
footer {
  border-top: 1px solid var(--border-soft);
  padding-block: clamp(2.5rem, 5vw, 4rem);
  background: rgba(26, 13, 20, 0.86);
}

.footer-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem clamp(2rem, 6vw, 5rem);
  justify-content: space-between;
  align-items: flex-start;
}

.wordmark.small {
  font-size: 0.875rem;
}

.tagline.small {
  font-size: 0.8125rem;
  margin-top: 0.5rem;
}

.family {
  margin-top: 1.5rem;
  font-size: 0.875rem;
  color: var(--dim);
}

.family a {
  color: var(--dim);
  text-underline-offset: 3px;
}

.family a:hover,
.family .is-current {
  color: var(--text);
}

.footer-meta {
  font-size: 0.8125rem;
  color: var(--dim);
  max-width: 52ch;
}

.footer-meta p + p {
  margin-top: 0.625rem;
}

.footer-meta a {
  color: var(--text);
}

/* ==========================================================================
   Motion
   ========================================================================== */
.reveal {
  opacity: 0;
  translate: 0 20px;
  transition:
    opacity 0.85s var(--ease) var(--d, 0ms),
    translate 0.85s var(--ease) var(--d, 0ms);
}

.reveal.is-in {
  opacity: 1;
  translate: 0 0;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.001ms !important;
  }
  .reveal {
    opacity: 1;
    translate: none;
  }
  .struck li::after {
    transform: scaleX(1);
  }
}

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 900px) {
  .intro-grid {
    grid-template-columns: 1fr;
    text-align: center;
  }
  .intro-copy {
    order: 2;
  }
  .intro-mark {
    order: 1;
  }
  .intro .cta {
    justify-content: center;
  }
  .intro .lead,
  .lines {
    margin-inline: auto;
  }
}

@media (max-width: 620px) {
  .nav .btn span,
  .nav-word span,
  .nav-brand + .btn-outline {
    display: none;
  }
  .nav-inner {
    gap: 0.75rem;
  }
  .beats {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   Print
   ========================================================================== */
@media print {
  body {
    background: #fff;
    color: #000;
  }
  #bg,
  .nav {
    display: none !important;
  }
  .beat {
    background: none;
    border-color: #ccc;
  }
}

/* ==========================================================================
   Portal — three products, three spheres of tiles.

   Each product's mark idles as a sphere of tiles in the air above the names.
   Choosing a name brings its sphere forward, flattens it into a grid and
   resolves the mark inside it. The page is locked to one screen; nothing
   scrolls, and the field behind it is drawn by portal.js.
   ========================================================================== */
html:has(body.portal-page) {
  overflow: hidden;
}

body.portal-page {
  height: 100svh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}

.stage {
  flex: 1;
  min-height: 0;
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  padding: 0 var(--gutter) clamp(0.5rem, 2vh, 1.25rem);
}

/* The air the marks live in. Measured, never drawn. */
.scene {
  flex: 1 1 auto;
  min-height: clamp(9rem, 30vh, 26rem);
}

.picker {
  flex: none;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.75rem, 2.4vh, 1.5rem);
}

/* Keeps the type crisp where the field passes behind it. Kept inside the
   layout box on purpose — bleeding sideways makes the page scrollable, and a
   scrolled page slides out from under the marks, which are placed by measuring
   it. */
.picker::before {
  content: '';
  position: absolute;
  inset: -22% 0 -40%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    ellipse 76% 60% at 50% 46%,
    rgba(26, 13, 20, 0.86) 0%,
    rgba(26, 13, 20, 0.66) 44%,
    rgba(26, 13, 20, 0.24) 72%,
    transparent 86%
  );
}

/* ---------- the three names ---------- */

/* Each product's own colour, keyed off the same attribute the script reads, so
   a name and its sphere are never told apart by two different lists. Same three
   accents in the same order as the ring's segments. */
[data-p='lens'] {
  --accent: var(--blue);
}
[data-p='intelligence'] {
  --accent: var(--green);
}
[data-p='atlas'] {
  --accent: var(--yellow);
}

.apps {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: clamp(1.25rem, 4.4vw, 3.25rem);
  list-style: none;
  margin: 0;
  padding: 0;
}

.app {
  display: block;
  position: relative;
  padding-bottom: 0.3em;
  text-decoration: none;
  color: var(--text);
  font-size: clamp(1.0625rem, 4vw, 2.625rem);
  font-weight: 200;
  letter-spacing: 0.18em;
  /* the tracking hangs off the last letter; pull it back so the row centres */
  margin-right: -0.18em;
  text-transform: uppercase;
  line-height: 1.15;
  white-space: nowrap;
  opacity: 0.6;
  cursor: pointer;
  transition:
    opacity 0.45s var(--ease),
    scale 0.5s var(--ease),
    translate 0.45s var(--ease),
    text-shadow 0.45s var(--ease);
}

/* A short tick under each one, so it reads as something you can take. It runs
   out to the width of the word when that word is chosen. The 0.18em is the
   tracking hanging off the last letter — discounted so the rule lines up with
   the glyphs, not with the space after them. */
.app::after {
  content: '';
  position: absolute;
  left: calc(50% - 0.09em);
  translate: -50% 0;
  bottom: 0;
  width: 1.5em;
  height: 1px;
  /* Mixed rather than set, so the tick stays a rule that happens to be tinted
     instead of a coloured bar. One ratio has to serve both states: pale enough
     to be a hint under a name at rest, saturated enough to name the colour when
     that name is chosen and the rule runs out to full width. */
  background: color-mix(in srgb, var(--accent) 60%, currentColor);
  opacity: 0.34;
  transition:
    width 0.55s var(--ease),
    opacity 0.45s var(--ease);
}

/* while one is chosen the other two shrink back out of its way */
.apps.has-sel .app {
  opacity: 0.18;
  scale: 0.84;
}

/* the chosen one lights up */
.apps .app.is-on,
.apps .app:focus-visible {
  opacity: 1;
  scale: 1.06;
  translate: 0 -2px;
  /* The accent arrives as light sitting just outside the white bloom, which is
     how the mark spends these three colours and the one use the brief allows.
     The white stays underneath and does the legibility, so what reads is that
     Lens glows a little blue — not that the type has been recoloured. */
  text-shadow:
    0 0 34px color-mix(in srgb, var(--accent) 30%, transparent),
    0 0 28px rgba(241, 231, 236, 0.3),
    0 0 6px rgba(241, 231, 236, 0.22);
}

.apps .app.is-on::after,
.apps .app:focus-visible::after,
.app:hover::after {
  width: calc(100% - 0.18em);
  opacity: 1;
}

.app.is-soon {
  cursor: default;
}

/* ---------- the line under them ---------- */
.readout {
  position: relative;
  width: 100%;
  max-width: 54ch;
  /* fixed, so swapping never moves the names */
  height: clamp(6.75rem, 15vh, 8.25rem);
}

.panel {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
  opacity: 0;
  translate: 0 6px;
  pointer-events: none;
  transition:
    opacity 0.35s var(--ease),
    translate 0.35s var(--ease);
}

.panel.is-on {
  opacity: 1;
  translate: 0 0;
  pointer-events: auto;
}

.panel-desc {
  color: var(--dim);
  font-size: clamp(0.8125rem, 1.25vw, 0.9375rem);
  line-height: 1.55;
  max-width: 46ch;
}

/* The resting line is one line against a box sized for three, so centre it —
   it then sits midway between the names and the footer. The product panels
   stay top-aligned, keeping each description tight under its name. */
.panel-rest {
  justify-content: center;
}

.panel-rest .panel-desc {
  text-transform: lowercase;
  letter-spacing: 0.06em;
  font-weight: 200;
}

/* the same chip the nav uses */
.panel-go {
  margin-top: 0.125rem;
  padding: 0.5625rem 1.25rem;
  font-size: 0.8125rem;
}

.panel-go::after {
  content: '\2192';
}

/* nothing to go to yet */
.panel-go.is-soon {
  color: var(--dim);
  border-color: var(--border-soft);
  background: transparent;
  cursor: default;
}

.panel-go.is-soon::after {
  content: '';
}

/* ---------- compact footer so the page fits one screen ---------- */
.portal-foot {
  flex: none;
  padding: 0.75rem var(--gutter) 1.125rem;
  text-align: center;
  font-size: 0.6875rem;
  color: var(--dim);
  position: relative;
  z-index: 1;
}

@media (max-width: 720px) {
  .apps {
    flex-direction: column;
    align-items: center;
    gap: clamp(0.375rem, 1.8vh, 0.875rem);
  }
  .app {
    font-size: clamp(1.25rem, 6.4vw, 2rem);
  }
  .scene {
    min-height: clamp(7rem, 22vh, 18rem);
  }
  .readout {
    height: clamp(5.5rem, 15vh, 7.5rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .app,
  .panel {
    transition-duration: 0.001ms !important;
  }
}
