/* Home opening screen ("The Curiosity Engine") — scoped entirely under
   #home-intro so it never bleeds into the rest of the site's design system.
   Managed by js/intro.js (mount/unmount per route change). */

body.page-home #app-content { padding: 0; max-width: none; }

.home-below {
  padding: clamp(2.5rem, 6vw, 6rem) clamp(1.25rem, 5vw, 3rem) clamp(4rem, 8vw, 7rem);
  max-width: 1400px;
  margin-inline: auto;
}

/* ---------- Home project strip ----------
   The works drift across the page in 16:9 boxes, on the same continuous
   travel as the tool ticker above. The track is duplicated in the markup so
   the -50% loop is seamless; hovering holds it so a box can be clicked. */
.strip {
  width: 100%;
  overflow: hidden;
  /* The marquee clips horizontally, which would also clip a hovered card as
     it grows into a circle and scales up. This padding is the headroom that
     growth and its glow need, so neither is ever cut:
     (224px circle - 200px card) / 2 = 12px of growth, + 15px of glow. */
  padding-block: 40px;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 7%, #000 93%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 7%, #000 93%, transparent 100%);
}
.strip__track {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 3vw, 25px);
  width: max-content;
  animation: marquee 80s linear infinite;
}
.strip:hover .strip__track { animation-play-state: paused; }

.strip__card {
  position: relative;
  flex: 0 0 auto;
  display: block;
  width: clamp(240px, 30vw, 430px);
  /* The band sits a step back so the card that leads can sit forward.
     Emphasis here is opacity and nothing else: a bigger box for one work
     would break the even travel and drop its label off the shared line. */
  opacity: 0.68;
  transition: opacity 0.45s var(--ease);
}
.strip__card--lead,
.strip__card:hover { opacity: 1; }
.strip__card--lead .strip__title { opacity: 1; }
.strip__card--lead .strip__meta { opacity: 0.85; }
/* Hovered card sits above its neighbours so its halo never hazes their edges. */
.strip__card:hover { z-index: 3; }

/* The picture keeps the 16:9 frame; the title sits under it, always legible,
   so no card is ever an unlabelled image you have to click to identify. */
.strip__media {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
}
.strip__label {
  display: block;
  padding-top: 0.85rem;
  text-align: center;
}
.strip__title {
  display: block;
  font-size: var(--fs-small);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg);
  opacity: 0.88;
  transition: opacity 0.35s var(--ease);
}
.strip__card:hover .strip__title { opacity: 1; }
/* Three fades used to stack here — the dim colour, 0.55, and the band's
   0.68 — leaving the section name at 1.7:1 on black, unreadable. The band
   still rests at 0.68; the meta now reaches about 5:1 through it, and stays
   a step under the title by tracking and weight of place, not by vanishing. */
.strip__meta {
  display: block;
  margin-top: 0.3rem;
  /* The project's name is what the card is; the section it belongs to is
     a step smaller, so the two never read as equals. */
  font-size: calc(var(--fs-small) * 0.82);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--fg);
  opacity: 0.78;
}

.strip__card img {
  /* 56.25% of a 16:9 box's width equals its height — so the image becomes a
     true square on hover, and `border-radius: 50%` makes it a real circle
     rather than an ellipse. Percentages animate, `auto` would not. */
  width: 100%;
  height: 100%;
  margin-inline: auto;
  /* Filled by default; a card whose own ground is the page colour can ask for
     `contain` so none of the work is cropped away. */
  object-fit: var(--fit, cover);
  object-position: var(--focus, center);
  display: block;
  border-radius: 0;
  box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
  transition: width 0.55s var(--ease), border-radius 0.55s var(--ease),
    transform 0.55s var(--ease), box-shadow 0.55s var(--ease);
}
.strip__card:hover .strip__media img {
  width: 56.25%;
  border-radius: 50%;
  transform: scale(1.12);
  /* Glow hugs the circle and stays inside the card's own footprint. */
  box-shadow: 0 0 var(--glow-blur) 0 rgba(255, 255, 255, 0.28);
}

/* The rest of the band recedes so the focused work carries the eye. */
.strip__track:hover .strip__card:not(:hover) { opacity: 0.3; }

/* ---------- Strip arrows ----------
   One thin mark on each side of the band, nothing around them — the same
   bare-arrow language the lightbox and gallery rails use. There's no counter
   here on purpose: the strip is an endless loop, so it has no "3 of 8" to
   report. */
.strip-wrap {
  display: flex;
  align-items: center;
  gap: clamp(0.5rem, 1.5vw, 1.25rem);
}
.strip-wrap .strip { flex: 1 1 auto; min-width: 0; }
.strip-nav__btn {
  flex: 0 0 auto;
  background: none;
  border: none;
  /* The arrow is a 7px-tall mark, so the target is the padding. 10px left it
     27px tall on a phone — the strip is the one thing on the home page a
     thumb is meant to drive, and it had the smallest target on it. The band
     reserves 40px of headroom above and below, so this costs no layout. */
  padding: 19px 10px;
  color: var(--fg);
  opacity: 0.35;
  transition: opacity 0.35s var(--ease);
}
.strip-nav__btn:hover, .strip-nav__btn:focus-visible { opacity: 1; }
.strip-nav__btn svg {
  display: block;
  /* 40% shorter than the first pass — a mark, not a bar. */
  width: clamp(23px, 3vw, 31px);
  height: auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

@media (prefers-reduced-motion: reduce) {
  .strip__card img { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .strip__track { animation: none; }
  .strip { overflow-x: auto; }
}

#home-intro {
  position: relative;
  width: 100%;
  /* No margin: the ticker above is in flow and has already carried the nav's
     height, so a margin here counted the fixed nav a second time. The height
     subtracts the whole chrome — nav AND ticker — instead of the nav alone,
     which is what used to push the intro 120px past the fold and leave the
     wordmark sitting well below the optical centre of the screen. */
  margin-top: 0;
  height: calc(100vh - var(--chrome-h));
  min-height: 480px;
  overflow: hidden;
  background-color: var(--bg);
  color: #ebebeb;
  font-family: "Montserrat", sans-serif;
  /* One font, one weight. The opening screen inherited 300 from the Tailwind
     build it came out of, so the first thing anyone saw was the one place on
     the site set in a heavier face than the rest of it. */
  font-weight: 165;
  -webkit-user-select: none;
  user-select: none;
}
.font-brand {
  font-family: var(--font-brand);
  font-weight: 400;
  /* No outline. The rest of the type was thickened twice for readability;
     the wordmark is a mark, not something anyone has to read a paragraph
     of, and thin is what it is. It stays at its own single weight. */
}
/* The wordmark and the logic/soul toggle. These four rules replace the
   Tailwind utility classes the markup used to carry (text-xl md:text-2xl
   uppercase text-white opacity-95 / flex items-center gap-2 / w-4 md:w-6
   h-[1px] bg-white/10 self-center) so the intro no longer needs the Tailwind
   CDN to render correctly — a blocked CDN used to leave the mark unstyled. */
.intro-title {
  letter-spacing: clamp(0.08em, 2.5vw, 0.8em) !important;
  white-space: nowrap;
  font-size: 1.25rem;
  line-height: 1.75rem;
  text-transform: uppercase;
  color: #fff;
  opacity: 0.95;
}
@media (min-width: 768px) {
  .intro-title { font-size: 1.5rem; line-height: 2rem; }
}

/* The answer to "what is this person, and for what". Before these two lines
   the whole first screen carried no sentence at all — a wordmark, a mode
   toggle and fourteen tool names, none of which says what the work is.
   The role is stated plainly; the line under it makes the breadth a position
   rather than an absence of one. */
.intro-role {
  margin: 0;
  line-height: 1.75;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.9);
  font-size: clamp(0.72rem, 1.6vw, 0.86rem);
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.86);
  /* Uppercase at 0.26em tracking is far wider than a ch count implies. */
  max-width: 38rem;
}
.intro-stance {
  margin: 0.55rem 0 0;
  line-height: 1.85;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.9);
  max-width: 34rem;
  font-size: clamp(0.76rem, 1.5vw, 0.92rem);
  line-height: 1.65;
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.6);
  text-wrap: pretty;
}
@media (max-width: 600px) {
  /* On a phone the sphere and the mark already fill the screen; the stance
     line would push the toggle off it, so the role alone carries the page
     and the full line waits on the Contact route. */
  .intro-stance { display: none; }
  .intro-role { letter-spacing: 0.2em; }
}

.mode-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.mode-rule {
  width: 16px;
  height: 1px;
  background: rgba(255, 255, 255, 0.1);
  align-self: center;
}
@media (min-width: 768px) {
  .mode-rule { width: 24px; }
}

#canvas-container {
  position: absolute;
  inset: 0;
  z-index: 10;
  width: 100%;
  height: 100%;
}

.pointer-auto { pointer-events: auto; }

#intro-center {
  position: absolute;
  /* Held a little below the middle: the sphere's lit crescent rides its
     upper half, so a few points down puts the sentences over the dark. */
  top: 52%;
  /* This block is 640px wide and paints above the field labels, so with
     pointer events on it swallowed clicks meant for the navigation — even
     where it only nearly touches them. It takes none; the two things in it
     that are actually clickable ask for them back below. */
  pointer-events: none;
  left: 50%;
  /* Given a width of its own. Left to shrink-wrap it took the width of the
     wordmark — 267px — and the two lines beneath it wrapped to three and four
     lines inside that column instead of reading as sentences. */
  width: min(92vw, 40rem);
  transform: translate(-50%, -50%);
  z-index: 32;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Tighter by three points, which buys back ten pixels of clearance
     against the labels below without the block reading as cramped. */
  gap: 13px;
  text-align: center;
}
#intro-center .intro-title,
#intro-center .mode-btn { pointer-events: auto; }

.intro-role em,
.self-intro .intro-role em {
  font-style: italic;
  font-synthesis-style: none;
}

/* The sculpture is 3.3 units across in a 4.25-unit frame — 78% of the
   screen's height — so there is no clear band to move the writing into:
   the only gaps are the top and bottom elevenths, and the field labels
   already sit in those. So the text carries its own ground instead — an
   ellipse of shade with no edge, falling off to nothing long before it
   could read as a panel. The light sweeps with the pointer, so this is the
   only way the words stay readable in every position rather than most. */
#intro-center::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 50%;
  width: 165%;
  height: 235%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  background: radial-gradient(
    ellipse at center,
    rgba(0, 0, 0, 0.8) 0%,
    rgba(0, 0, 0, 0.66) 34%,
    rgba(0, 0, 0, 0.34) 58%,
    rgba(0, 0, 0, 0.1) 74%,
    rgba(0, 0, 0, 0) 86%
  );
}
#intro-center .intro-title {
  margin: 0;
  text-shadow: 0 1px 16px rgba(0, 0, 0, 0.85);
  /* It's a link, so it lights the way the field labels do. */
  transition: color 0.6s cubic-bezier(0.19, 1, 0.22, 1),
    text-shadow 0.6s cubic-bezier(0.19, 1, 0.22, 1);
}
#intro-center .intro-title:hover,
#intro-center .intro-title:has(:focus-visible) {
  color: #fff;
  text-shadow: 0 0 20px rgba(255, 255, 255, 0.55);
}
#intro-center .intro-title [data-go] { cursor: pointer; outline: none; }
@media (prefers-reduced-motion: reduce) {
  #intro-center .intro-title { transition: none; }
}

.noise-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.12;
  z-index: 20;
  background-image: url('data:image/svg+xml,%3Csvg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)"/%3E%3C/svg%3E');
  mix-blend-mode: overlay;
}

.vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 21;
  /* This sits at z-21, directly over the canvas at z-10, and it was clear for
     only the middle 20% before ramping to 90% black. The sphere lives outside
     that circle, so the lit crescent was being viewed through a near-opaque
     scrim — which is why turning the lamp up kept doing almost nothing. The
     clear core now reaches past the sphere and the outer stop is lighter; the
     vignette still settles the frame edges so the type holds the centre. */
  background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0) 52%, rgba(0, 0, 0, 0.72) 100%);
}

.mode-btn {
  font-size: 10px;
  font-weight: 165;
  letter-spacing: 0.4em;
  text-transform: uppercase;
  transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
  /* 5.4:1 on the black ground. At 10px this is the smallest interactive
     text on the site, so it has to clear 4.5:1 rather than sit near it. */
  color: #80808a;
  cursor: none;
  padding: 6px 14px;
  background: none;
  border: none;
}
.mode-btn.active { color: #fff; }

#label-container {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  max-width: 500px;
  max-height: 500px;
  z-index: 30;
  pointer-events: none;
}

.field-label {
  position: absolute;
  font-size: 11px;
  font-weight: 165;
  text-transform: uppercase;
  letter-spacing: 0.6em;
  color: rgba(255, 255, 255, 0);
  transition: color 0.6s cubic-bezier(0.19, 1, 0.22, 1), transform 0.8s cubic-bezier(0.19, 1, 0.22, 1), opacity 0.5s ease;
  white-space: nowrap;
  cursor: none;
  pointer-events: auto;
  padding: 20px;
  line-height: 1.8;
  text-align: center;
  background: none;
  border: none;
}

#label-1 { top: 10%; left: 50%; transform: translateX(-50%); }
/* Low enough to clear the writing above them. At 20% the two of them ran
   from 62% to 76% of the screen while the text block ended at 71% — they
   overlapped, and the navigation was the thing that lost. The writing has since
   grown to five lines, so they sit lower again. */
#label-2 { bottom: 4%; left: 0%; transform: translateX(-20%); }
#label-3 { bottom: 4%; right: 0%; transform: translateX(20%); }

.field-label.revealed { color: rgba(255, 255, 255, 0.56); }
/* A keyboard has no pointer to bring near a label, so focus reveals it. */
.field-label:focus-visible {
  color: #fff !important;
  opacity: 1 !important;
  outline: none;
  text-shadow: 0 0 15px rgba(255, 255, 255, 0.6);
}
.field-label:hover {
  color: #fff !important;
  opacity: 1 !important;
  text-shadow: 0 0 15px rgba(255, 255, 255, 0.6);
}

.label-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

@media (max-width: 768px) {
  #label-container { max-width: 320px; }
  .field-label { font-size: 9px; letter-spacing: 0.4em; }
  #label-1 { top: 15%; }
  #label-2, #label-3 { bottom: 12%; }
}

@media (max-width: 420px) {
  #label-container { max-width: 250px; max-height: 400px; }
  .field-label { font-size: 8px; letter-spacing: 0.28em; padding: 12px; }
}

/* A touchscreen has no pointer to carry near a label, so the proximity
   reveal never fired and a phone showed the name on black with no way into
   the work. Where there is no hover, the three labels simply stand at the
   revealed strength. !important because the proximity script writes an
   inline opacity of 0 on every touchmove far from a label. Large enough to
   read and to hit: 10px type in a 44px target. */
@media (hover: none), (pointer: coarse) {
  .field-label {
    color: rgba(255, 255, 255, 0.68) !important;
    opacity: 1 !important;
  }
}
@media (hover: none) and (max-width: 768px), (pointer: coarse) and (max-width: 768px) {
  .field-label { font-size: 10px; letter-spacing: 0.3em; padding: 14px; }
  /* Visible at rest, they need clear air from the writing above them. */
  #label-2, #label-3 { bottom: 5%; }
}

@media (max-height: 560px) {
  #home-intro { min-height: 420px; }
  #label-container { max-height: 280px; }
}

@media (prefers-reduced-motion: reduce) {
  .field-label { transition: opacity 0.2s linear; }
}
