/* TITECH v3 · hero.css: owned by the work order listed in work-orders/DESIGN-SPEC.md §1. */
/* WO-06: the home hero. Layout, background, headline, orbit graphic, meta strip, then motion (bottom of file). */

/* 1. Context and geometry ---------------------------------------------------------------------------------------- */
.hero {
  /* Dark context values from DESIGN-SPEC §2. surfaces.css (WO-05) sets the same values on .hero; they are repeated
     here so the hero is correct on its own. */
  --ctx-line: rgb(255 255 255 / .09);
  --ctx-line-strong: rgb(255 255 255 / .18);
  --ctx-link: var(--c-signal-soft);

  --hero-pad: clamp(16px, 2vw, 32px);      /* vertical padding of the grid */
  --art-w: min(44vw, 620px);
  --art-glow: calc(var(--art-w) * 1.16);  /* the glow behind the art: 720px across at full size */
  --hero-fade-x: 70%;                      /* where the grid fades out and where the soft glow sits: inline-end */
  --hero-glow-x: 85%;
}
[dir="rtl"] .hero { --hero-fade-x: 30%; --hero-glow-x: 15%; }
/* On short screens the art gives way, so the header, the grid and the 74px meta strip still fit in one viewport
   (1 : 1.0613 is the art's proportion, see the orbit section). Taller screens are unaffected. */
@supports (height: 100svh) {
  @media (min-width: 1001px) {
    .hero {
      --art-w: min(44vw, 620px, (min(100svh, 1000px) - var(--header-height) - var(--wp-adminbar-offset) - 2 * var(--hero-pad) - 74px) * 1.0613);
    }
  }
}

/* 2. Section: full-screen, dark, content centred between the header and the meta strip ------------------------------ */
.hero {
  display: flex;
  flex-direction: column;
  min-height: calc(min(100vh, 1000px) - var(--wp-adminbar-offset));
  min-height: calc(min(100svh, 1000px) - var(--wp-adminbar-offset));
  padding-block-start: var(--header-height);  /* the header overlays the hero (WO-03), so the room is reserved here */
  background-color: var(--c-banner);
  background-image: var(--bg-banner); /* owner choice: brand navy with a soft aurora */
}

/* Grid lines (DESIGN-SPEC §5.1), faded out by a mask. Replaces the legacy ellipse glow. */
.hero::before {
  background-color: transparent;
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(ellipse 80% 60% at var(--hero-fade-x) 0%, #000 0%, transparent 75%);
  mask-image: radial-gradient(ellipse 80% 60% at var(--hero-fade-x) 0%, #000 0%, transparent 75%);
}
/* Grain (DESIGN-SPEC §5.1): feTurbulence as an inline data URI, so there is no request. */
.hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  pointer-events: none;
  opacity: .035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

/* 3. Grid: copy about 7/12 (a little more, so "Connecting tomorrow." fits on one line), art to the inline-end ------ */
.hero-grid {
  position: relative;
  flex: 1 1 auto;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  align-content: center;
  align-items: center;
  gap: clamp(30px, 4vw, 72px);
  padding-block: var(--hero-pad);
}
/* One large glow behind the art (about 720px across). It is centred on the art, which is right-aligned in the grid. */
.hero-grid::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset-block-start: 50%;
  inset-inline-end: calc((var(--art-w) - var(--art-glow)) / 2);
  width: var(--art-glow);
  aspect-ratio: 1;
  translate: 0 -50%;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(closest-side, rgb(76 141 255 / .20), transparent);
}

/* 4. Headline ---------------------------------------------------------------------------------------------------- */
/* Colleague review 2026-09-29, note 2: "Connecting tomorrow." is the brandbook's main gradient (Gradient 1: rosewood, blue,
   sage), without the plum step that turned the line mauve, and with each colour at the deepest strength that still reads on
   the banner blue. She asked for it darker, like the brandbook's "Our Service" heading, which sits on white. On this blue
   the brand colours at full strength are 1.5 to 2.4:1, and 3:1 is the floor for large text, so the colours keep their
   brand hue and chroma and go as dark as that floor allows: 3.03 to 3.06:1 on the lit banner (#3E5680), against 2.98 to
   3.0:1 for the pastels they replace. */
.hero {
  --hero-g-1: #FF7F94;  /* Rosewood */
  --hero-g-2: #7CA9E9;  /* Deep Navy, lifted */
  --hero-g-3: #62B877;  /* Sage Green */
  --hero-g: linear-gradient(90deg, var(--hero-g-1) 0%, var(--hero-g-2) 52%, var(--hero-g-3) 100%);
  --hero-g-rtl: linear-gradient(270deg, var(--hero-g-1) 0%, var(--hero-g-2) 52%, var(--hero-g-3) 100%);
}
.hero h1 { max-width: none; }  /* the legacy 18ch cap forced a third line */

/* The second line is the gradient carrier. background-clip: text only paints inside the element's box, so the
   padding gives descenders room and the negative margin gives the space back (no layout change). hero.js may move the
   carrier inside the line masks (below); these rules work in both places. */
.hero h1 > span:not(.h-line),
.hero h1 .h-line__inner > span {
  display: block;
  padding-block: .16em;
  margin-block: -.16em;
  color: transparent;
  -webkit-text-fill-color: transparent;
  background-image: var(--hero-g);
  background-repeat: no-repeat;
  background-size: 100% 100%;  /* static state: the whole spectrum across the line; motion widens it, see the end */
  background-position: 0% 50%;
  -webkit-background-clip: text;
  background-clip: text;
}
/* The spectrum reads from inline-start to inline-end: same stops, mirrored in Arabic. */
[dir="rtl"] .hero h1 > span:not(.h-line),
[dir="rtl"] .hero h1 .h-line__inner > span {
  background-image: var(--hero-g-rtl);
}

/* Line masks (built by hero.js). The clip reaches past the line box so descenders and Arabic marks survive; it is
   a clip-path, so it changes no layout. The legacy ".hero h1 span" rule colours every span, so colour is reset here. */
.hero h1 .h-line {
  display: flow-root;
  color: inherit;
  clip-path: inset(-.1em -.5em -.16em -.5em);
}
.hero h1 .h-line__inner {
  display: block;
  color: inherit;
}

/* One line each on desktop, in both languages (the copy column is sized for it). Also holds while the web font loads. */
@media (min-width: 1001px) {
  .hero h1 { white-space: nowrap; }
}

/* 5. Meta strip: a thin mono line at the bottom edge ------------------------------------------------------------- */
.hero-meta {
  flex: none;
  padding-block: 20px 24px;
  border-block-start: 1px solid var(--ctx-line);
}
.hero .hero-meta .text-link {
  margin-inline-start: auto;
  padding-block: 0;
  gap: 14px;
  color: var(--ctx-heading);
  font-family: inherit;
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: inherit;
  text-transform: inherit;
}
/* A 1px × 24px line that draws downward and is wiped away downward, looping every 2.4s (motion block at the end). */
.hero .hero-meta .text-link::before {
  content: "";
  flex: none;
  width: 1px;
  height: 24px;
  background: var(--c-signal-soft);
  opacity: .6;
}

/* 6. Orbit graphic ----------------------------------------------------------------------------------------------- */
/* Larger than the legacy art, in the proportions the legacy art actually rendered: it declared 1 : 1.02, but at desktop
   widths max-height: 530px clamped it to 562 x 530 (the reference screenshot). The rings and the orb positions are
   percentages calibrated to that box; at 1 : 1.02 the bottom orb would sit 32px off its ring. It may be wider than its
   grid column: it is aligned to the inline-end and reaches back over the empty left edge of the art box. */
.hero-art {
  /* Owner request: the orbs keep their original colours. WO-01 remaps the legacy --green/--orange/--plum to a neutral
     tint, so the originals are pinned here. */
  --orb-green: #BFD4C2;
  --orb-amber: #EEC29C;
  --orb-violet: #C1B6D0;
  width: var(--art-w);
  aspect-ratio: 1.0613 / 1;
  max-width: none;
  max-height: none;
  margin-inline: 0;
  justify-self: end;
}
.hero-art .node-1 { background: var(--orb-green); }
.hero-art .node-2 { background: var(--orb-amber); }
.hero-art .node-3 { background: var(--orb-violet); }

/* Revolution period and direction per ring. Ring 2 turns the other way. hero.js reads these and adds the rest. */
.hero-art .orbit-1 { --orbit-t: 28s; --orbit-dir: normal;  --orbit-pd: 0s; }
.hero-art .orbit-2 { --orbit-t: 36s; --orbit-dir: reverse; --orbit-pd: -1s; }
.hero-art .orbit-3 { --orbit-t: 44s; --orbit-dir: normal;  --orbit-pd: -2s; }

/* hero.js moves each orb into a carrier inside its ring. The carrier is the ring's own box (to the centre of the 1px
   border), so rotating it moves the orb round the ring; because it sits inside the ring's rotate/scaleY it follows the
   squashed, tilted ellipse exactly. The orb's centre is the carrier's top centre (6px = half the 12px orb). */
.orbit-carrier {
  position: absolute;
  inset: -.5px;
  border-radius: 50%;
  pointer-events: none;
}
.orbit-carrier > .orbit-node {
  inset: -6px auto auto calc(50% - 6px);
}

/* Glow layers. Each is an outer shadow on a pseudo-element whose opacity carries the pulse (hero.js switches it on). */
.orbit-node::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  box-shadow: 0 0 22px 3px rgb(184 208 189 / .34);
  opacity: 0;
  pointer-events: none;
}
/* Owner rule (2026-09-24): the logo is white wherever it sits on a dark ground, and full colour only where the
   ground is white — as it is on the interior page-hero art, where the mark sits inside a white disc. The hero's
   core is the one dark-ground instance left, so the mark is rendered white here. One asset, no second file: the
   filter flattens the artwork to white, which is the white mark. */
.orbit-core img {
  filter: brightness(0) invert(1);
  opacity: .92;
}
/* Colleague review 2026-09-29, note 3: the mark sits straight on the banner blue. The dark disc behind it, its inner ring
   and the glow around it are gone (the orbits and the three orbs stay). Without the disc the mark is the centre of the
   drawing on its own, so it fills the space the disc had. */
.orbit-core { background: none; border: 0; box-shadow: none; }
.orbit-core::before { content: none; }
.orbit-core img { width: 100%; opacity: 1; }

/* Same rule on the interior page-hero art: its disc is a translucent navy, not a white plate, so the colour mark
   read muddy there — the blue arcs nearly vanished into the ground. White mark, and the disc keeps its ring. */
.page-hero-visual svg image {
  filter: brightness(0) invert(1);
  opacity: .9;
}

.orbit-core::after {
  /* Colleague review 2026-09-29, note 3: the soft glow that breathed behind the mark is gone with the disc, so the mark
     stands on the banner blue alone. (The core-breathe keyframes below now have nothing to animate.) */
  content: none;
}

/* 7. Motion. Everything here is inside no-preference: reduced motion gets the static final state above. -------------- */
@media (prefers-reduced-motion: no-preference) {
  /* Gradient drift: the 200%-wide spectrum slides across the line, 14s each way. */
  .hero h1 > span:not(.h-line),
  .hero h1 .h-line__inner > span {
    background-size: 200% 100%;
    animation: hero-drift 14s var(--ease-in-out) infinite alternate;
  }
  @keyframes hero-drift {
    from { background-position: 0% 50%; }
    to   { background-position: 100% 50%; }
  }

  /* Entrance: hero.js adds .is-entering, then removes it once everything has landed. The lines rise inside their
     masks; the rest fades and rises; the art fades and scales in (scale is its own property, so the pointer parallax,
     which uses translate, does not fight it). */
  .hero.is-entering .h-line__inner { animation: hero-line-rise 800ms var(--ease-out) both; }
  .hero.is-entering .h-line:nth-child(2) .h-line__inner { animation-delay: 90ms; }
  .hero.is-entering .hero-copy > .eyebrow   { animation: hero-rise 700ms var(--ease-out) 250ms both; }
  .hero.is-entering .hero-copy > .hero-lead { animation: hero-rise 700ms var(--ease-out) 330ms both; }
  .hero.is-entering .hero-copy > .actions   { animation: hero-rise 700ms var(--ease-out) 410ms both; }
  .hero.is-entering .hero-art               { animation: hero-art-in 900ms var(--ease-out) 300ms both; }
  @keyframes hero-line-rise {
    from { transform: translateY(120%); }
    to   { transform: none; }
  }
  @keyframes hero-rise {
    from { opacity: 0; transform: translateY(var(--rise)); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes hero-art-in {
    from { opacity: 0; scale: .96; }
    to   { opacity: 1; scale: 1; }
  }

  /* Orbs revolve along their rings (hero.js adds .is-orbiting after moving each orb into its carrier and setting
     --orbit-e, --orbit-k, --orbit-rot and --orbit-delay on its ring). The carrier spins; the orb gets the exact inverse
     of everything above it (spin, the carrier's x-stretch, the ring's scaleY and tilt), so on screen it only travels:
     it stays a perfect circle. Both use the same period, timing and (negative) delay, so they never drift apart. */
  .hero.is-orbiting .orbit-carrier,
  .hero.is-orbiting .orbit-carrier > .orbit-node {
    animation-duration: var(--orbit-t);
    animation-timing-function: linear;
    animation-delay: var(--orbit-delay, 0s);
    animation-iteration-count: infinite;
    animation-direction: var(--orbit-dir);
  }
  .hero.is-orbiting .orbit-carrier { animation-name: orbit-spin; }
  .hero.is-orbiting .orbit-carrier > .orbit-node { animation-name: orbit-counter; }
  @keyframes orbit-spin {
    from { transform: scaleX(var(--orbit-e, 1)) rotate(0deg); }
    to   { transform: scaleX(var(--orbit-e, 1)) rotate(360deg); }
  }
  @keyframes orbit-counter {
    from { transform: rotate(0deg)    scaleX(calc(1 / var(--orbit-e, 1))) scaleY(calc(1 / var(--orbit-k, 1))) rotate(calc(var(--orbit-rot, 0deg) * -1)); }
    to   { transform: rotate(-360deg) scaleX(calc(1 / var(--orbit-e, 1))) scaleY(calc(1 / var(--orbit-k, 1))) rotate(calc(var(--orbit-rot, 0deg) * -1)); }
  }

  /* Soft pulses: each orb 3s, staggered 1s apart (--orbit-pd per ring); the core breathes over 4s. */
  .hero.is-orbiting .orbit-node::after { animation: orb-pulse 3s ease-in-out var(--orbit-pd, 0s) infinite; }
  .hero.is-orbiting .orbit-core::after { animation: core-breathe 4s ease-in-out infinite; }
  @keyframes orb-pulse     { 0%, 100% { opacity: .2; } 50% { opacity: 1; } }
  @keyframes core-breathe  { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }

  /* The line beside the meta link draws downward, then is wiped away downward. */
  .hero .hero-meta .text-link::before { animation: hero-cue 2.4s var(--ease-in-out) infinite; }
  @keyframes hero-cue {
    0%   { clip-path: inset(0 0 100% 0); }
    45%  { clip-path: inset(0); }
    55%  { clip-path: inset(0); }
    100% { clip-path: inset(100% 0 0 0); }
  }

  /* Nothing loops while the hero is off screen (hero.js toggles .is-offscreen). */
  .hero.is-offscreen .orbit-carrier,
  .hero.is-offscreen .orbit-node,
  .hero.is-offscreen .orbit-node::after,
  .hero.is-offscreen .orbit-core::after,
  .hero.is-offscreen h1 span,
  .hero.is-offscreen .hero-meta .text-link::before { animation-play-state: paused; }
}

/* 8. Stacked layout: copy first, art below (the legacy stacked art sizes, as absolute lengths so the glow can follow) --- */
@media (max-width: 1000px) {
  .hero {
    --art-w: min((100vw - 2 * var(--gutter)) * .67, 430px);
    --stack-gap: clamp(20px, 5vw, 40px);
    --stack-end: 12px;
  }
  .hero-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--stack-gap);
    padding-block: 16px var(--stack-end);
  }
  .hero-copy { max-width: 730px; }
  .hero-art { justify-self: center; margin-block: 0; }
  /* The glow follows the art: horizontally centred, and as far above the bottom edge as the art's own centre. */
  .hero-grid::before {
    inset-block-start: auto;
    inset-block-end: calc(var(--stack-end) + (var(--art-w) / 1.0613 - var(--art-glow)) / 2);
    inset-inline-end: calc((100% - var(--art-glow)) / 2);
    translate: none;
  }
}
@media (max-width: 600px) {
  .hero { --art-w: min((100vw - 2 * var(--gutter)) * .81, 325px); }
  .hero .hero-meta .text-link { margin-inline-start: 0; }  /* the legacy mobile meta strip is a grid; the link starts the row */
}
@media (max-width: 374px) {
  .hero { --art-w: calc((100vw - 2 * var(--gutter)) * .88); }
}

/* 9. Print and forced colours: gradient text needs a real colour when backgrounds are dropped ------------------------ */
@media print {
  .hero h1 span { background: none; color: inherit; -webkit-text-fill-color: currentColor; }
}
@media (forced-colors: active) {
  .hero h1 span { background: none; color: CanvasText; -webkit-text-fill-color: currentColor; }
}
