/*
  Veyra: the engine.
  ==========================================================================

  index.html only. A standalone section: everything here is prefixed eng- or
  scoped to .veyra-engine, and removing the section removes the feature
  whole.

  The composition, back to front:

      sky        a horizon that the mountain is lit against
      ranges     three silhouettes, each paler and higher than the last
      water      a dark reflective plain with the ranges mirrored in it
      terrain    the near ground, with energy running through it
      mountain   obsidian, cracked, with a lit crater
      orbit      ten shards and the lines that hold them
      panel      what Veyra does with the one you chose

  All of it is drawn - gradients, clip paths and a few SVG strokes. There is
  no image in this section except the Veyra mark, which is the brand asset
  the opening frame uses.

  Motion budget: transform and opacity, plus stroke-dashoffset on the cracks,
  which is the one property that can make a line look like it is carrying
  something. Everything stops under prefers-reduced-motion.
*/

.veyra-engine {
  --eng-void: #03060d;
  --eng-deep: #071429;
  --eng-cyan: #5ecbff;
  --eng-cyan-dim: rgba(94, 203, 255, 0.5);
  --eng-line: rgba(126, 196, 240, 0.3);
  --eng-rock: #070b14;

  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: var(--space-32) 0 var(--space-24);
  border-top: 1px solid rgba(126, 196, 240, 0.1);
  background:
    radial-gradient(1400px 700px at 68% 92%, rgba(26, 104, 176, 0.16), transparent 66%),
    radial-gradient(900px 520px at 12% 4%, rgba(22, 82, 150, 0.12), transparent 70%),
    linear-gradient(180deg, #03060d 0%, #050b18 52%, #061020 100%);
  color: #eaf2fc;
}

/* ==========================================================================
   1. The world
   ========================================================================== */

/*
  The scene is what shakes. Putting the camera shake on the section itself
  would move the headline and the panel with it, and a camera that moves the
  interface is not a camera.
*/
.eng-scene {
  /*
    In flow, not absolute: the shell inside it is what gives the section its
    height, and an absolutely positioned wrapper takes that away. The world
    inside is absolute against this, which is the same box it had before.
  */
  position: relative;
  z-index: 0;
  transform: translate3d(var(--shake-x, 0px), var(--shake-y, 0px), 0);
}

.eng-world {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

/* ---------------------------------------------------------- the valley -- */

/*
  The town. Three depth bands drawn into one SVG, sitting on the valley
  floor. It is deliberately quiet: the businesses are what the mountain
  powers, so they have to be legible when looked at and invisible when not.
*/
.eng-town {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 132%;
  height: 46%;
  transform: translate3d(calc(-50% + var(--px, 0) * -14px), 0, 0);
}

.eng-road {
  fill: none;
  stroke: rgba(126, 196, 240, 0.16);
  stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
}

.eng-bridge {
  fill: none;
  stroke: rgba(126, 196, 240, 0.26);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* The lit windows. One colour, one shape, varying only in opacity - which is
   what a town at night actually looks like from a distance. */
.eng-windows rect {
  fill: #9fdcff;
}

.eng-lamp path {
  stroke: rgba(126, 196, 240, 0.22);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.eng-lamp circle {
  fill: #bfe9ff;
  filter: drop-shadow(0 0 4px rgba(126, 196, 240, 0.9));
}

/*
  The town's lights answer the mountain. --world-lit is raised by
  veyra-engine.js through the buildup and the blast, so the valley is lit by
  the eruption rather than merely near it.
*/
.eng-windows,
.eng-lamps {
  filter: brightness(calc(1 + var(--world-lit, 0) * 1.6));
}

/* ------------------------------------------------------- the foreground -- */

/*
  The near ridge. Darkest, largest, and the only layer that moves at the
  pointer's full rate - which is what puts the viewer inside the valley
  rather than in front of it.
*/
.eng-near {
  position: absolute;
  left: -8%;
  right: -8%;
  bottom: -2%;
  height: 26%;
  overflow: visible;
  transform: translate3d(calc(var(--px, 0) * -34px), calc(var(--py, 0) * -6px), 0);
}

.eng-near path {
  fill: #02040a;
}

/* Low cloud, between the ranges and the peak. Two bands at different rates,
   so the sky is never quite still. */
.eng-clouds {
  position: absolute;
  left: -20%;
  right: -20%;
  top: 8%;
  height: 34%;
  background:
    radial-gradient(38% 42% at 22% 60%, rgba(30, 86, 148, 0.16), transparent 72%),
    radial-gradient(46% 38% at 62% 34%, rgba(24, 74, 132, 0.14), transparent 74%),
    radial-gradient(30% 34% at 86% 56%, rgba(34, 96, 158, 0.12), transparent 72%);
  animation: eng-clouds 78s ease-in-out infinite alternate;
}

@keyframes eng-clouds {
  from { transform: translate3d(-3%, 0, 0); }
  to { transform: translate3d(3%, -1.5%, 0); }
}

/* The horizon: one wide band of light the ranges stand against, low enough
   that the mountain reads as taller than everything behind it. */
.eng-horizon {
  position: absolute;
  left: -10%;
  right: -10%;
  bottom: 27%;
  height: 34%;
  background:
    radial-gradient(60% 100% at 50% 100%, rgba(50, 150, 230, 0.22), transparent 72%),
    radial-gradient(90% 60% at 50% 100%, rgba(20, 70, 130, 0.16), transparent 76%);
}

.eng-horizon::after {
  content: '';
  position: absolute;
  left: 12%;
  right: 12%;
  bottom: 0;
  height: 1px;
  background: linear-gradient(
    to right,
    transparent,
    rgba(126, 196, 240, 0.12) 24%,
    rgba(180, 226, 255, 0.4) 50%,
    rgba(126, 196, 240, 0.12) 76%,
    transparent
  );
}

/*
  The ranges. Three layers, each paler, higher and slower than the one in
  front - which is the whole of how distance is drawn. They take the pointer
  at a tenth of the stage's rate, so the far ones barely move.
*/
.eng-range {
  position: absolute;
  left: -6%;
  width: 112%;
  bottom: 27%;
  overflow: visible;
}

.eng-range path {
  fill: currentColor;
}

.eng-range-far {
  color: #0a1526;
  height: 16%;
  transform: translate3d(calc(var(--px, 0) * -6px), 0, 0);
}

.eng-range-mid {
  color: #070f1d;
  height: 13%;
  transform: translate3d(calc(var(--px, 0) * -12px), 0, 0);
}

.eng-range-near {
  color: #04080f;
  height: 10%;
  transform: translate3d(calc(var(--px, 0) * -20px), 0, 0);
}

/*
  The water. A dark plain with the light of the horizon lying on it, and the
  ranges mirrored into it at a low opacity - which is cheaper and truer than
  a reflection filter, because a still water reflection IS the silhouette
  again, dimmer and stretched.
*/
.eng-water {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 27%;
  background:
    radial-gradient(56% 90% at 62% 0%, rgba(42, 132, 210, 0.2), transparent 70%),
    linear-gradient(180deg, rgba(8, 18, 34, 0.9) 0%, rgba(3, 6, 13, 0.98) 100%);
}

.eng-water-mirror {
  position: absolute;
  left: -6%;
  right: -6%;
  top: 0;
  height: 46%;
  opacity: 0.35;
  transform: scaleY(-1);
  transform-origin: top;
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent 88%);
  mask-image: linear-gradient(to bottom, #000, transparent 88%);
}

.eng-water-mirror path {
  fill: #0a1526;
}

/* The ripple: two hairlines drifting across the water, no more. */
.eng-water::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to bottom, transparent 38%, rgba(126, 196, 240, 0.07) 38.4%, transparent 39%),
    linear-gradient(to bottom, transparent 64%, rgba(126, 196, 240, 0.05) 64.3%, transparent 65%);
  animation: eng-ripple 19s ease-in-out infinite alternate;
}

@keyframes eng-ripple {
  from { transform: translateY(-4px); }
  to { transform: translateY(5px); }
}

/*
  The near ground, and the survey grid on it. The grid is in perspective and
  masked to nothing well before either edge, so it reads as a plane the
  mountain stands on rather than as a texture over the section.
*/
.eng-grid {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 30%;
  background-image:
    linear-gradient(to right, rgba(126, 196, 240, 0.09) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(126, 196, 240, 0.07) 1px, transparent 1px);
  background-size: 9% 22%;
  background-position: center top;
  transform: perspective(600px) rotateX(70deg) scale(2.1);
  transform-origin: center top;
  -webkit-mask-image: radial-gradient(52% 74% at 58% 0%, #000 6%, transparent 74%);
  mask-image: radial-gradient(52% 74% at 58% 0%, #000 6%, transparent 74%);
}

/* The energy the mountain feeds into the ground: two thin rivers of light
   running away from its foot. */
.eng-rivers {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 32%;
  overflow: visible;
  opacity: 0.85;
}

.eng-rivers path {
  fill: none;
  stroke: url(#eng-river-ink);
  stroke-width: 1.4;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 3 9;
  animation: eng-flow 9s linear infinite;
}

@keyframes eng-flow {
  to { stroke-dashoffset: -120; }
}

/* Fog: two soft bands that sit between the ranges and the water, which is
   where distance actually collects. */
.eng-fog {
  position: absolute;
  left: -10%;
  right: -10%;
  bottom: 20%;
  height: 26%;
  background:
    radial-gradient(60% 70% at 30% 60%, rgba(40, 110, 180, 0.1), transparent 72%),
    radial-gradient(70% 60% at 76% 40%, rgba(30, 90, 160, 0.09), transparent 74%);
  animation: eng-drift 44s ease-in-out infinite alternate;
}

@keyframes eng-drift {
  from { transform: translate3d(-2%, 0, 0); }
  to { transform: translate3d(2%, -1%, 0); }
}

/* The small lights out in the landscape. Placed by hand, because five
   deliberate points read as a settlement and fifty random ones read as
   noise. */
.eng-spark {
  position: absolute;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: rgba(180, 226, 255, 0.9);
  box-shadow: 0 0 7px rgba(126, 196, 240, 0.9);
  animation: eng-twinkle var(--dur, 6s) ease-in-out var(--delay, 0s) infinite;
}

@keyframes eng-twinkle {
  0%, 100% { opacity: 0.18; }
  50% { opacity: 0.9; }
}

/* ==========================================================================
   2. The page
   ========================================================================== */

.eng-shell {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 32fr) minmax(0, 68fr);
  align-items: start;
  gap: clamp(var(--space-8), 4vw, var(--space-16));
}

.eng-intro {
  padding-top: var(--space-4);
}

.eng-eyebrow {
  margin: 0 0 var(--space-6);
  font-family: var(--mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--eng-cyan);
}

.eng-title {
  margin: 0 0 var(--space-6);
  font-size: clamp(2.4rem, 4.4vw, 4rem);
  font-weight: 600;
  line-height: 0.98;
  letter-spacing: -0.045em;
  color: #f4f9ff;
}

/* The one coloured phrase on the page. */
.eng-title em {
  font-style: normal;
  color: var(--eng-cyan);
  text-shadow: 0 0 34px rgba(94, 203, 255, 0.45);
}

.eng-lead {
  margin: 0 0 var(--space-10);
  max-width: 42ch;
  color: #94a8c4;
  font-size: var(--text-md);
  line-height: 1.62;
}

/*
  Three benefits, as a list with a lit rule down its left edge rather than
  three cards. The section already has one object in it.
*/
.eng-benefits {
  display: grid;
  gap: var(--space-6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.eng-benefits li {
  padding-left: var(--space-5);
  border-left: 1px solid rgba(126, 196, 240, 0.22);
}

.eng-benefits b {
  display: block;
  margin-bottom: 4px;
  font-family: var(--mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #dbe8f8;
}

.eng-benefits span {
  color: #8398b6;
  font-size: var(--text-sm);
  line-height: 1.5;
}

/* ==========================================================================
   3. The stage
   ========================================================================== */

.eng-stage {
  /* Where the orbit sits, as a share of the stage. veyra-engine.js reads
     these, so the three layouts can disagree about the orbit without the
     script knowing there are three. */
  --orbit-cx: 50;
  --orbit-cy: 49;
  --orbit-rx: 40;
  --orbit-ry: 37;

  position: relative;
  height: clamp(34rem, 50vw, 48rem);
}

.eng-links {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 2;
}

.eng-link {
  fill: none;
  stroke: rgba(126, 196, 240, 0.22);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  transition: stroke 0.4s ease, stroke-width 0.4s ease, opacity 0.4s ease;
}

.eng-link.is-lit {
  stroke: var(--eng-cyan);
  stroke-width: 1.6;
  filter: drop-shadow(0 0 6px rgba(94, 203, 255, 0.7));
}

/* While one is being read, the rest step back rather than disappear. */
.is-reading .eng-link:not(.is-lit) {
  opacity: 0.4;
}

.eng-bead {
  fill: #cfeeff;
  opacity: 0;
  filter: drop-shadow(0 0 5px rgba(94, 203, 255, 0.8));
}

/* ==========================================================================
   4. The mountain
   ========================================================================== */

/*
  A button, because clicking it is the whole point of the section and a
  keyboard has to be able to do it too. Everything inside is decorative.
*/
.eng-volcano {
  position: absolute;
  left: 50%;
  bottom: 4%;
  width: 72%;
  padding: 0;
  border: 0;
  background: none;
  transform:
    translate3d(calc(-50% + var(--shift, 0px) + var(--px, 0) * 10px), calc(var(--py, 0) * 5px), 0);
  transition: transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
  cursor: pointer;
  z-index: 1;
}

.eng-volcano:focus-visible {
  outline: none;
}

.eng-volcano:focus-visible .eng-mountain {
  filter: drop-shadow(0 0 24px rgba(94, 203, 255, 0.85));
}

.eng-mountain {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  transition: filter 0.5s ease;
}

/* --- the rock ------------------------------------------------------------ */

/*
  Layered strata, not facets. The light sits upper left, so the lit flank
  takes a gradient that runs from steel at the ridge to near-black at the
  foot, and the shaded flank never lifts above it.
*/
.eng-rock-lit { fill: url(#eng-rock-lit); }
.eng-rock-mid { fill: #0c1524; }
.eng-rock-shade { fill: url(#eng-rock-shade); }

.eng-skirt {
  fill: #04070e;
}

/* The lines between strata. One pixel, low alpha: rock reads as layered
   because of its edges, not because of its colour. */
.eng-strata path {
  fill: none;
  stroke: rgba(150, 196, 236, 0.13);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.eng-debris path {
  fill: #070d18;
  stroke: rgba(150, 196, 236, 0.1);
  stroke-width: 0.6;
  vector-effect: non-scaling-stroke;
}

.eng-rim {
  fill: #0a1220;
  stroke: rgba(180, 226, 255, 0.5);
  stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
}

/*
  Lava: tapered channels, wide at the rim and narrowing as they run. A
  stroke of even width reads as a wire; a shape that loses width reads as a
  flow. --heat is raised by the script through the buildup, so this is the
  one material in the section that answers the interaction directly.
*/
.eng-lava path {
  fill: url(#eng-lava-ink);
  opacity: calc(0.2 + var(--heat, 0) * 0.62);
  filter: drop-shadow(0 0 calc(1.5px + var(--heat, 0) * 7px) rgba(94, 203, 255, 0.6));
  animation: eng-lava-pulse 6.5s ease-in-out infinite;
}

.eng-lava path:nth-child(2) { animation-duration: 8.3s; animation-delay: -2.1s; }
.eng-lava path:nth-child(3) { animation-duration: 7.1s; animation-delay: -4.4s; }
.eng-lava path:nth-child(4) { animation-duration: 9.4s; animation-delay: -1.2s; }
.eng-lava path:nth-child(5) { animation-duration: 10.2s; animation-delay: -6.3s; }
.eng-lava path:nth-child(6) { animation-duration: 7.8s; animation-delay: -3.5s; }
.eng-lava path:nth-child(7) { animation-duration: 9s; animation-delay: -5.1s; }

@keyframes eng-lava-pulse {
  0%, 100% { opacity: calc(0.16 + var(--heat, 0) * 0.62); }
  50% { opacity: calc(0.3 + var(--heat, 0) * 0.6); }
}

/* The cyan veins: the Veyra accent in the rock, kept thin and few so the
   lava stays the brighter of the two. */
.eng-vein path {
  fill: none;
  stroke: var(--eng-cyan);
  stroke-width: 1.1;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  opacity: calc(0.22 + var(--heat, 0) * 0.5);
  filter: drop-shadow(0 0 4px rgba(94, 203, 255, 0.7));
  stroke-dasharray: 40 62;
  animation: eng-crack-flow 7s linear infinite;
}

/* The cracks. Drawn as dashes and pulled along their own length, which is
   what makes a line look like it is carrying something rather than glowing. */
@keyframes eng-crack-flow {
  to { stroke-dashoffset: -208; }
}

/*
  The mountain answers the pointer through --heat, which veyra-engine.js
  raises while it is hovered - every material in the mountain already reads
  that variable, so one number lifts the lava, the veins and the crater
  together. A rule here could not do it: the script writes --heat every
  frame, and an inline custom property beats a stylesheet.
*/
.eng-volcano {
  transition: transform 0.55s cubic-bezier(0.16, 1, 0.3, 1), scale 0.4s ease;
}

.eng-volcano:hover,
.eng-volcano:focus-visible {
  scale: 1.012;
}

/* --- the crater ---------------------------------------------------------- */

.eng-crater {
  position: absolute;
  left: 50%;
  top: 6.5%;
  width: 15%;
  aspect-ratio: 2.4 / 1;
  transform: translate(-50%, -50%) scale(calc(1 + var(--heat, 0) * 0.5));
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(226, 248, 255, calc(0.55 + var(--heat, 0) * 0.45)),
    rgba(94, 203, 255, calc(0.3 + var(--heat, 0) * 0.5)) 42%,
    rgba(40, 140, 220, 0) 78%
  );
  pointer-events: none;
}

/* The light the crater throws up into the air above it. */
.eng-plume {
  position: absolute;
  left: 50%;
  top: 6.5%;
  width: 40%;
  height: 48%;
  transform: translate(-50%, -100%);
  background: radial-gradient(
    46% 60% at 50% 100%,
    rgba(94, 203, 255, calc(0.16 + var(--heat, 0) * 0.34)),
    rgba(40, 140, 220, 0.08) 45%,
    transparent 76%
  );
  pointer-events: none;
  animation: eng-breathe 8s ease-in-out infinite;
}

@keyframes eng-breathe {
  0%, 100% { opacity: 0.66; transform: translate(-50%, -100%) scale(0.96); }
  50% { opacity: 1; transform: translate(-50%, -100%) scale(1.05); }
}

/* --- the mark at the foot ------------------------------------------------ */

/*
  The Veyra mark, cut out of the lockup by scripts/mark-cutout.mjs: the same
  asset the opening frame puts in the middle of the screen, here at the foot
  of the mountain that the whole section says is the source.
*/
.eng-plate {
  position: absolute;
  left: 50%;
  bottom: 6%;
  display: grid;
  justify-items: center;
  gap: 6px;
  width: 40%;
  transform: translateX(-50%);
  pointer-events: none;
}

.eng-mark {
  width: clamp(4.5rem, 9vw, 8rem);
  height: auto;
  filter: drop-shadow(0 0 24px rgba(62, 162, 211, 0.7));
  animation: eng-mark-breathe 8s ease-in-out infinite;
}

@keyframes eng-mark-breathe {
  0%, 100% { filter: drop-shadow(0 0 20px rgba(62, 162, 211, 0.55)); }
  50% { filter: drop-shadow(0 0 34px rgba(94, 203, 255, 0.9)); }
}

.eng-word {
  font-family: var(--display);
  font-size: clamp(0.95rem, 1.4vw, 1.3rem);
  font-weight: 300;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  text-transform: uppercase;
  color: #eaf4ff;
}

/* --- the instruction ----------------------------------------------------- */

/*
  The one thing the visitor has to be told. It sits under the mountain until
  the mountain is opened, and then it is gone for good.
*/
.eng-cue {
  position: absolute;
  left: 50%;
  bottom: -2%;
  z-index: 3;
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  margin: 0;
  transform: translateX(-50%);
  font-family: var(--mono);
  font-size: var(--text-2xs);
  line-height: 1.6;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
  text-transform: uppercase;
  text-align: center;
  color: rgba(170, 200, 230, 0.7);
  pointer-events: none;
  animation: eng-cue-in 0.8s ease-out 0.6s both;
}

@keyframes eng-cue-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

/*
  Any author rule that sets display beats the user agent's [hidden], so a
  class with display: grid on it stays on screen after the script hides it.
  The panel has the same rule for the same reason.
*/
.eng-cue[hidden] {
  display: none;
}

/* A cursor, drawn, with one click ring leaving it on a slow period. */
.eng-cue svg {
  width: 20px;
  height: 20px;
  overflow: visible;
  fill: none;
  stroke: var(--eng-cyan);
  stroke-width: 1.4;
}

.eng-cue circle {
  transform-origin: 6px 5px;
  animation: eng-click 3s ease-out infinite;
}

@keyframes eng-click {
  0%, 60% { opacity: 0; transform: scale(0.4); }
  70% { opacity: 0.9; }
  100% { opacity: 0; transform: scale(2.6); }
}

/* ==========================================================================
   5. The shards
   ========================================================================== */

/*
  The ring moves as one container. Shifting the shards individually would
  fight the transform each of them already uses for its flight, its float and
  its hover; shifting their parent touches none of that, and the connectors
  are measured from real boxes so they follow on their own.
*/
/*
  The particle canvas: embers, blast debris and the trails the concepts leave
  behind them. It covers the stage, takes no pointer, and is drawn inside the
  frame the section already runs.
*/
.eng-motes {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  pointer-events: none;
}

.eng-orbit {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  transform: translate3d(var(--shift, 0px), 0, 0) scale(var(--orbit-scale, 1));
  transition: transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
}

/*
  A shard is not a card. It is an obsidian face with a lit edge and a label
  under it, and it is a button so a keyboard reaches it the same way a
  pointer does.

  Two transforms, two elements: the float lives on .eng-shard so it can run
  forever, and the flight and the hover live on the button, so neither ever
  overwrites the other.
*/
.eng-node {
  position: absolute;
  z-index: 3;
  display: grid;
  justify-items: center;
  gap: 8px;
  width: 7.4rem;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  pointer-events: auto;

  /*
    Before the eruption every shard is inside the mountain. After the click,
    veyra-engine.js writes this transform every frame from the trajectory, so
    there is deliberately no transition on it here - and nothing in this file
    may set transform on .eng-node again, because an inline style would win
    and the flight would stop.
  */
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.26);
  transition: opacity 0.35s ease;
}

.is-open .eng-node {
  opacity: 1;
}

.eng-node:focus-visible {
  outline: none;
}

/* Hover and selection scale the shard, which the script never touches. */
.is-open .eng-node:hover .eng-shard,
.is-open .eng-node:focus-visible .eng-shard,
.is-open .eng-node.is-active .eng-shard {
  scale: 1.1;
}

/* Everything else steps back while one is being read. */
.is-reading .eng-node:not(.is-active) {
  opacity: 0.44;
}

/*
  The panel takes the right quarter of the section, so the scene moves out
  from under it rather than being covered by it. The brief for this section
  is that the mountain stays visible the whole time, and half an orbit behind
  a panel is not that.
*/
.is-reading .eng-stage {
  --shift: -10vw;
  --orbit-scale: 0.9;
}

.eng-shard {
  position: relative;
  display: grid;
  place-items: center;
  width: 3.6rem;
  height: 3.6rem;
  /* A cut corner on the diagonal: enough to read as a fragment rather than a
     rounded rectangle, and cheap enough to animate. */
  clip-path: polygon(22% 0, 100% 0, 100% 78%, 78% 100%, 0 100%, 0 22%);
  background:
    linear-gradient(145deg, rgba(28, 48, 78, 0.9) 0%, rgba(8, 14, 26, 0.95) 62%);
  box-shadow:
    inset 0 0 0 1px rgba(126, 196, 240, 0.3),
    inset 0 1px 0 rgba(190, 230, 255, 0.16),
    0 14px 30px -14px rgba(0, 0, 0, 0.9);
  transition: box-shadow 0.35s ease, background 0.35s ease;
  animation: eng-float var(--float-dur, 8s) var(--float-delay, 0s) ease-in-out infinite alternate;
}

@keyframes eng-float {
  from { translate: 0 -4px; }
  to { translate: 0 5px; }
}

.eng-icon {
  width: 1.45rem;
  height: 1.45rem;
  fill: none;
  stroke: rgba(160, 210, 245, 0.85);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke 0.35s ease;
}

.eng-node-name {
  font-family: var(--mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(196, 216, 238, 0.92);
  transition: color 0.35s ease;
  white-space: nowrap;
}

.eng-node:hover .eng-shard,
.eng-node:focus-visible .eng-shard,
.eng-node.is-active .eng-shard {
  background: linear-gradient(145deg, rgba(40, 76, 122, 0.95) 0%, rgba(11, 20, 36, 0.95) 62%);
  box-shadow:
    inset 0 0 0 1px rgba(126, 196, 240, 0.75),
    inset 0 1px 0 rgba(200, 240, 255, 0.28),
    0 0 34px -6px rgba(56, 160, 235, 0.8),
    0 14px 30px -14px rgba(0, 0, 0, 0.9);
}

.eng-node:hover .eng-icon,
.eng-node:focus-visible .eng-icon,
.eng-node.is-active .eng-icon {
  stroke: #dff2ff;
}

.eng-node:hover .eng-node-name,
.eng-node:focus-visible .eng-node-name,
.eng-node.is-active .eng-node-name {
  color: #eaf4ff;
}

/* ==========================================================================
   6. The panel
   ========================================================================== */

/*
  A quarter of the screen, on the right, over the water rather than over the
  mountain. It slides once: choosing a second concept changes what it says
  and leaves it where it is.
*/
.eng-panel {
  position: absolute;
  right: 0;
  top: 8%;
  bottom: 8%;
  z-index: 6;
  width: min(27vw, 25rem);
  padding: var(--space-8);
  overflow-y: auto;
  background:
    linear-gradient(180deg, rgba(12, 22, 40, 0.86) 0%, rgba(6, 12, 24, 0.92) 100%);
  border: 1px solid rgba(126, 196, 240, 0.26);
  border-right: 0;
  border-radius: var(--radius-panel) 0 0 var(--radius-panel);
  backdrop-filter: blur(18px);
  box-shadow:
    inset 0 1px 0 rgba(190, 230, 255, 0.12),
    -30px 0 80px -40px rgba(0, 0, 0, 0.95),
    0 0 70px -30px rgba(40, 140, 220, 0.5);
  transform: translateX(102%);
  opacity: 0;
  transition: transform 0.52s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.4s ease;
}

.eng-panel.is-in {
  transform: translateX(0);
  opacity: 1;
}

.eng-panel[hidden] {
  display: none;
}

/* The content, which fades rather than slides when it is swapped: the panel
   has already arrived, and moving it again would read as a second panel. */
.eng-panel-inner {
  display: grid;
  gap: var(--space-5);
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.eng-panel-inner.is-out {
  opacity: 0;
  transform: translateY(6px);
}

.eng-close {
  position: absolute;
  right: var(--space-5);
  top: var(--space-5);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid rgba(126, 196, 240, 0.22);
  border-radius: 50%;
  background: rgba(8, 16, 30, 0.7);
  color: rgba(178, 202, 228, 0.85);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.25s ease, color 0.25s ease, background 0.25s ease;
}

.eng-close:hover,
.eng-close:focus-visible {
  border-color: rgba(126, 196, 240, 0.6);
  background: rgba(20, 38, 62, 0.8);
  color: #eaf4ff;
}

.eng-panel-icon {
  width: 2rem;
  height: 2rem;
  fill: none;
  stroke: var(--eng-cyan);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 10px rgba(94, 203, 255, 0.6));
}

.eng-panel-name {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--eng-cyan);
}

.eng-panel-subtitle {
  margin: 0;
  font-family: var(--display);
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.16;
  letter-spacing: -0.025em;
  color: #f4f9ff;
}

.eng-panel-body {
  margin: 0;
  color: #93a8c5;
  font-size: var(--text-sm);
  line-height: 1.62;
}

.eng-features {
  display: grid;
  gap: var(--space-4);
  margin: 0;
  padding: var(--space-5) 0 0;
  list-style: none;
  border-top: 1px solid rgba(126, 196, 240, 0.14);
}

.eng-features li {
  display: grid;
  gap: 3px;
  padding-left: var(--space-4);
  border-left: 1px solid rgba(126, 196, 240, 0.28);
}

.eng-features b {
  font-family: var(--mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #dbe8f8;
}

.eng-features span {
  color: #8398b6;
  font-size: var(--text-sm);
  line-height: 1.5;
}

/* ==========================================================================
   7. Tablet
   ========================================================================== */

@media (max-width: 1080px) {
  .eng-shell {
    grid-template-columns: 1fr;
    gap: var(--space-12);
  }

  .eng-lead {
    max-width: 58ch;
  }

  .eng-benefits {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-6);
  }

  .eng-stage {
    --orbit-rx: 42;
    --orbit-ry: 39;
    height: clamp(30rem, 68vw, 40rem);
  }

  .eng-volcano {
    width: 52%;
  }

  .eng-panel {
    width: min(42vw, 24rem);
  }
}

/* ==========================================================================
   8. Phone
   --------------------------------------------------------------------------
   Not the desktop composition made smaller. The orbit becomes a tall ring
   that the mountain sits inside, the shards lose their labels' second line,
   and the panel comes up from the bottom as a sheet - which is where a panel
   belongs on a device held in one hand.
   ========================================================================== */

@media (max-width: 900px) {
  .veyra-engine {
    padding-top: var(--space-24);
  }

  .eng-benefits {
    grid-template-columns: 1fr;
    gap: var(--space-5);
  }

  .eng-stage {
    --orbit-cy: 46;
    --orbit-rx: 39;
    --orbit-ry: 41;
    height: 34rem;
  }

  /* The sheet comes up from the bottom, so the scene has nothing to move out
     from under. */
  .is-reading .eng-stage {
    --shift: 0px;
    --orbit-scale: 1;
  }

  .eng-volcano {
    width: 66%;
    bottom: 6%;
  }

  .eng-node {
    width: 5.2rem;
    gap: 5px;
  }

  .eng-shard {
    width: 2.7rem;
    height: 2.7rem;
  }

  .eng-icon {
    width: 1.1rem;
    height: 1.1rem;
  }

  .eng-node-name {
    font-size: 0.56rem;
    letter-spacing: 0.1em;
  }

  /* A sheet, not a drawer. */
  .eng-panel {
    right: 0;
    left: 0;
    top: auto;
    bottom: 0;
    width: auto;
    max-height: 76%;
    padding: var(--space-6) var(--space-5) var(--space-8);
    border: 1px solid rgba(126, 196, 240, 0.26);
    border-bottom: 0;
    border-radius: var(--radius-panel) var(--radius-panel) 0 0;
    transform: translateY(102%);
    box-shadow:
      inset 0 1px 0 rgba(190, 230, 255, 0.12),
      0 -30px 80px -40px rgba(0, 0, 0, 0.95),
      0 0 70px -30px rgba(40, 140, 220, 0.5);
  }

  .eng-panel.is-in {
    transform: translateY(0);
  }

  /* The grab handle a sheet is expected to have. */
  .eng-panel::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 8px;
    width: 38px;
    height: 3px;
    border-radius: 2px;
    background: rgba(126, 196, 240, 0.3);
    transform: translateX(-50%);
  }

  .eng-panel-inner {
    padding-top: var(--space-3);
  }
}

@media (max-width: 620px) {
  .eng-stage {
    --orbit-rx: 38;
    --orbit-ry: 40;
    height: 30rem;
  }

  .eng-node {
    width: 4.6rem;
  }

  .eng-cue {
    letter-spacing: 0.18em;
    text-indent: 0.18em;
  }
}

/* ==========================================================================
   9. Reduced motion
   --------------------------------------------------------------------------
   The eruption is the point of the section, so it still happens - it simply
   happens at once rather than over a second and a half. Nothing loops.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .eng-plume,
  .eng-mark,
  .eng-shard,
  .eng-water::after,
  .eng-fog,
  .eng-spark,
  .eng-rivers path,
  .eng-cue,
  .eng-cue circle {
    animation: none !important;
  }

  .eng-node {
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }

  .eng-panel,
  .eng-panel-inner {
    transition-duration: 0.01ms !important;
  }
}
