/* DEF IT Informatikai Zrt. — site styles
   Contents: fonts, tokens, base, header, hero, about, competencies,
   partners, contact, footer, motion modes, forced colours and contrast. */

/* ---------- Fonts (self-hosted, OFL) ---------- */
@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/jost-latin-ext-wght-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/jost-latin-wght-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Commissioner";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/commissioner-latin-ext-wght-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Commissioner";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/commissioner-latin-wght-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- Tokens ---------- */
:root {
  --green: #96bb39; /* logo green, fills and graphics only */
  --pine-black: #061417;
  --font-display: "Jost", "Century Gothic", "Avenir Next", system-ui, sans-serif;
  --font-body: "Commissioner", "Segoe UI", system-ui, sans-serif;
  --wrap: 77.5rem;
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --header-h: 4.75rem;
  --bar: 0.375rem; /* thickness of the rounded bars taken from the wordmark */
  --ease: cubic-bezier(0.22, 0.8, 0.24, 1);
  /* Motion grammar, mirrored as G in main.js: --ease settles blocks and UI state,
     --e-arrive brings anything out of a mask, --e-travel is for rest-to-rest
     moves; --t1 hover and state, --t2 rows and hairlines, --t3 blocks and sheens,
     --t4 titles and plaques. Nothing overshoots. */
  --e-arrive: cubic-bezier(0.16, 1, 0.3, 1);
  --e-travel: cubic-bezier(0.65, 0, 0.35, 1);
  --t1: 0.24s;
  --t2: 0.6s;
  --t3: 0.9s;
  --t4: 1.2s;
}

:root,
[data-theme="dark"] {
  color-scheme: dark;
  --bg: #061417;
  --bg-raised: #0a1e22;
  --ink: #e8eee6;
  --ink-soft: #a3b4ab;
  --line: rgba(214, 232, 220, 0.13);
  --line-strong: rgba(214, 232, 220, 0.3);
  --accent-text: #b4d65c;
  --plate: #030d0f;
  --net-rgb: 150, 187, 57;
  --glow: rgba(150, 187, 57, 0.16);
}

[data-theme="light"] {
  color-scheme: light;
  --bg: #f3f5f1;
  --bg-raised: #ffffff;
  --ink: #0a2326;
  --ink-soft: #4c5f5b;
  --line: rgba(10, 35, 38, 0.13);
  --line-strong: rgba(10, 35, 38, 0.32);
  --accent-text: #47650c;
  --plate: #061417;
  --net-rgb: 104, 140, 28;
  --glow: rgba(150, 187, 57, 0.22);
}

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

html {
  scroll-behavior: smooth;
  /* Anchor jumps stop below the fixed header; sections bring their own top padding. */
  scroll-padding-top: var(--header-h);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.4s ease, color 0.4s ease;
}

h1,
h2,
h3,
h4,
p,
ul,
dl,
dd,
address {
  margin: 0;
}

ul {
  padding: 0;
  list-style: none;
}

address {
  font-style: normal;
}

a {
  color: inherit;
}

img,
svg,
canvas {
  display: block;
}

::selection {
  background: var(--green);
  color: var(--pine-black);
}

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

.wrap {
  width: 100%;
  max-width: calc(var(--wrap) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.sprite {
  position: absolute;
}

/* Paint for the second <use> of a logo symbol: the flat emblem's glint and the
   header wordmark's sheen. Only the stop colour is set here, per theme (the
   opacities are attributes in the sprite). Dark theme: lighter than the artwork.
   Light theme: deeper greens, never lighter than the artwork, so neither the
   green mark nor the dark wordmark loses contrast against the light page. */
.glint-stop {
  stop-color: #e3f0b8;
}

.sheen-stop {
  stop-color: #b4d65c;
}

[data-theme="light"] .glint-stop {
  stop-color: #7e9f2c;
}

[data-theme="light"] .sheen-stop {
  stop-color: #47650c;
}

/* Moving network field behind the whole page (drawn by network.js).
   z-index -1 keeps it above the page background and below all content.
   100lvh: the canvas keeps its size when a phone's URL bar slides in or out. */
.field {
  position: fixed;
  top: 0;
  left: 0;
  z-index: -1;
  width: 100%;
  height: 100vh;
  height: 100lvh;
  pointer-events: none;
}

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

.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 100;
  padding: 0.6rem 1rem;
  border-radius: 999px;
  background: var(--green);
  color: var(--pine-black);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
}

.skip-link:focus {
  transform: none;
}

.icon {
  width: 1.5rem;
  height: 1.5rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  padding: 0.7rem 1.5rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font: 600 0.98rem/1.2 var(--font-body);
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease;
}

.btn--primary {
  background: var(--green);
  color: var(--pine-black);
}

.btn--primary:hover {
  background: #a9cd4c;
}

.btn--quiet {
  border-color: var(--line-strong);
  color: var(--ink);
}

.btn--quiet:hover {
  border-color: var(--ink);
}

.section {
  padding-block: clamp(4.5rem, 10vw, 9rem);
}

.section__title {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(2.25rem, 1.4rem + 3.2vw, 4rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
}

/* ---------- Header ---------- */
.site-header {
  position: fixed;
  inset: 0 0 auto;
  z-index: 50;
  border-bottom: 1px solid transparent;
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

.site-header.is-stuck {
  background: rgba(6, 20, 23, 0.86);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  border-bottom-color: var(--line);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

/* Phones: blurring over the animated canvas is the biggest scroll cost on
   mid-range Android, so the stuck bar is a solid fill instead (a see-through
   fill without blur lets large text show through). Placed before the mobile
   block so the open menu's solid header still wins. */
@media (pointer: coarse), (max-width: 56.24rem) {
  .site-header.is-stuck {
    background: var(--bg);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  height: var(--header-h);
}

.brand {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  gap: 0.8rem;
  text-decoration: none;
}

.brand__mark {
  width: 2.9rem;
  height: auto;
  aspect-ratio: 144 / 130;
  fill: var(--green);
}

.brand__word {
  width: 5.4rem;
  height: auto;
  aspect-ratio: 175 / 46;
  fill: var(--ink);
  transition: fill 0.4s ease;
}

/* Sheen of the landing beat: the same wordmark symbol a second time, exactly on
   top of the first (it is also a .brand__word, so it has the same size; the auto
   margins centre it the way the flex row centres the first), painted only with
   the #word-sheen gradient. Invisible outside the beat, so at rest the lockup is
   the plain mark and wordmark. */
.brand__sheen {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  margin-block: auto;
  opacity: 0;
  pointer-events: none;
}

.site-nav {
  margin-left: auto;
}

.site-nav__list {
  display: flex;
  gap: clamp(1.25rem, 2.6vw, 2.5rem);
}

.site-nav a {
  position: relative;
  display: block;
  padding: 0.5rem 0;
  color: var(--ink-soft);
  font-size: 0.95rem;
  font-weight: 500;
  text-decoration: none;
  transition: color 0.25s ease;
}

.site-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 1.5rem;
  height: 0.1875rem;
  border-radius: 999px;
  background: var(--green);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.35s var(--ease);
}

.site-nav a:hover,
.site-nav a.is-active {
  color: var(--ink);
}

.site-nav a.is-active::after {
  transform: scaleX(1);
}

.site-tools {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.lang {
  display: flex;
  gap: 0.55rem;
  margin-right: 0.4rem;
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.lang span {
  color: var(--ink);
}

.lang a {
  color: var(--ink-soft);
  text-decoration: none;
}

.lang a:hover {
  color: var(--accent-text);
}

.tool-btn {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.25s ease;
}

.tool-btn:hover {
  border-color: var(--line-strong);
}

.theme-toggle .icon {
  grid-area: 1 / 1;
  width: 1.25rem;
  height: 1.25rem;
  transition: opacity 0.3s ease, transform 0.5s var(--ease);
}

[data-theme="dark"] .icon--moon,
[data-theme="light"] .icon--sun {
  opacity: 0;
  transform: rotate(-60deg) scale(0.6);
}

/* Motion control: three stacked icons, the one for the current mode shows
   (theme.js sets the html class). Without JS no class is set: two waves. */
.motion-toggle .icon,
.site-nav__motion .icon {
  grid-area: 1 / 1;
  width: 1.25rem;
  height: 1.25rem;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.motion-full .icon--motion-full,
.motion-reduced .icon--motion-reduced,
.motion-off .icon--motion-off,
html:not(.motion-reduced):not(.motion-off) .icon--motion-full {
  opacity: 1;
}

/* Phone version at the bottom of the menu panel; the round button covers wide screens. */
.site-nav__motion {
  display: inline-grid;
  grid-template-columns: 1.25rem auto;
  align-items: center;
  column-gap: 0.6rem;
  min-height: 2.75rem;
  margin-top: 2rem;
  padding: 0.6rem 1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-soft);
  font: 500 0.95rem/1.2 var(--font-body);
  cursor: pointer;
  transition: border-color 0.25s ease, color 0.25s ease;
}

.site-nav__motion:hover {
  border-color: var(--line-strong);
  color: var(--ink);
}

.site-nav__motion [data-motion-text] {
  grid-area: 1 / 2;
}

@media (min-width: 56.25rem) {
  .site-nav__motion {
    display: none;
  }
}

.menu-toggle {
  display: none;
  align-content: center;
  gap: 0.375rem;
}

.menu-toggle__bar {
  width: 1.125rem;
  height: 0.125rem;
  border-radius: 999px;
  background: currentColor;
  transition: transform 0.35s var(--ease);
}

.menu-open .menu-toggle__bar:first-child {
  transform: translateY(0.25rem) rotate(45deg);
}

.menu-open .menu-toggle__bar:last-child {
  transform: translateY(-0.25rem) rotate(-45deg);
}

@media (max-width: 56.24rem) {
  :root {
    --header-h: 4rem;
  }

  .site-header__inner {
    gap: 0.75rem;
  }

  .brand {
    position: relative;
    z-index: 2;
  }

  .site-tools {
    position: relative;
    z-index: 2;
    margin-left: auto;
  }

  .menu-toggle {
    display: grid;
  }

  .site-tools .motion-toggle {
    display: none;
  }

  /* Column: the link list, then the motion pill below it. */
  .site-nav {
    position: fixed;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    height: 100vh;
    height: 100dvh;
    margin: 0;
    padding: var(--header-h) var(--gutter) 2rem;
    background: var(--bg);
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.3s ease, visibility 0s linear 0.3s;
  }

  .menu-open .site-nav {
    visibility: visible;
    opacity: 1;
    transition-delay: 0s;
  }

  .site-nav__list {
    flex-direction: column;
    gap: 0.5rem;
  }

  .site-nav a {
    padding: 0.4rem 0 0.7rem;
    color: var(--ink);
    font: 300 clamp(2rem, 9vw, 2.75rem) / 1.1 var(--font-display);
  }

  .menu-open .site-header {
    background: var(--bg);
    transform: none;
  }
}

/* Smallest phones (320 px): tighter tool spacing keeps the menu button on screen. */
@media (max-width: 22.49rem) {
  .site-header__inner {
    gap: 0.375rem;
  }

  .site-tools {
    gap: 0.25rem;
  }

  .lang {
    gap: 0.4rem;
    margin-right: 0;
  }
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: 100vh;
  min-height: 100svh;
  padding-block: calc(var(--header-h) + 1.5rem) clamp(2.5rem, 8vh, 4.5rem);
  overflow: hidden;
}

.hero__inner {
  position: relative;
  display: grid;
  gap: clamp(2rem, 6vh, 3.5rem);
}

/* The emblem is the supplied logo artwork, unaltered. Only its container moves. */
/* The copy stays in front of the emblem's veil where the two overlap. */
.hero__copy {
  position: relative;
  z-index: 1;
}

/* --veil follows --bg with the body's 0.4s colour fade, so the veil never shows
   as a disc during a theme switch. Browsers without @property simply switch. */
@property --veil {
  syntax: "<color>";
  inherits: true;
  initial-value: #061417;
}

.hero__emblem {
  --veil: var(--bg);
  position: relative;
  isolation: isolate;
  order: -1;
  justify-self: end;
  width: min(52vw, 14rem);
  margin-right: 3vw;
  transition: --veil 0.4s ease;
}

/* Green glow over an opaque veil of the page colour: solid out to about 0.7 x
   the emblem width from its centre, gone by 0.95 x. Field lines fade out before
   they reach the logo and never cross it. Square box, so the circle is exact. */
.hero__emblem::before {
  content: "";
  position: absolute;
  top: 50%;
  left: -45%;
  z-index: -1;
  width: 190%;
  aspect-ratio: 1;
  border-radius: 50%;
  translate: 0 -50%;
  background:
    radial-gradient(circle, var(--glow), transparent 62%),
    radial-gradient(circle closest-side, var(--veil) 0 72%, transparent 96%);
  pointer-events: none;
  transition: opacity 0.3s ease; /* only .is-away changes it */
}

/* While logo3d.js composites its own glow (.is-lit), the stylesheet's green glow
   steps back to --glow-gain of itself, so the two do not add up; the veil stays.
   Browsers without color-mix() keep the full glow. */
.hero__emblem.is-lit::before {
  background:
    radial-gradient(circle, color-mix(in srgb, var(--glow) calc(var(--glow-gain, 0.35) * 100%), transparent), transparent 62%),
    radial-gradient(circle closest-side, var(--veil) 0 72%, transparent 96%);
}

.hero__mark {
  width: 100%;
  height: auto;
  aspect-ratio: 144 / 130;
  fill: var(--green);
  transition: opacity 0.9s ease;
}

/* Glint on the flat emblem (tiers without the 3D one): the same symbol a second
   time, in register, painted only with the #mark-glint gradient. main.js shows it
   for the length of one pass (.is-glint); otherwise it is not there. */
.hero__glint {
  opacity: 0;
}

.hero__emblem.is-glint .hero__glint {
  opacity: 1;
}

.hero__emblem.is-3d .hero__glint {
  display: none;
}

/* 3D emblem (logo3d.js). The canvas is larger than the emblem box so the turning
   solid is never clipped and its light (glow, shafts) has room to fade out before
   the canvas edge: it reaches as far to the sides as the glow above. The camera
   keeps the logo at the flat SVG's size, so the change from flat to 3D is seamless. */
.hero__3d {
  position: absolute;
  top: -45%;
  left: -45%;
  width: 190%;
  height: 190%;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.9s ease;
}

.hero__emblem.is-3d .hero__3d {
  opacity: 1;
}

.hero__emblem.is-3d .hero__mark {
  opacity: 0;
  transition-delay: 0.9s; /* the canvas fades in over the opaque flat emblem first */
}

/* The scan (logo3d.js): from the moment the solid is ready until ignition the
   canvas and the flat emblem are both fully shown, without a fade, and the shader
   decides per pixel which of the two is seen. From ignition on the solid stands
   alone. These follow the .is-3d rules above and win over them. */
.hero__emblem.is-scan .hero__3d {
  opacity: 1;
  transition: none;
}

.hero__emblem.is-scan .hero__mark {
  opacity: 1;
  transition: none;
}

.hero__emblem.is-solid .hero__mark {
  opacity: 0;
  transition: none;
}

/* The emblem has left its box for the flight to the header (dock.js): veil, glow,
   flat emblem and the canvas in the box are hidden; the actor is on the stage. */
.hero__emblem.is-away::before,
.hero__emblem.is-away .hero__mark,
.hero__emblem.is-away .hero__3d {
  opacity: 0;
}

/* Flight layer (built by dock.js): fixed over the page and under the header, so
   the emblem passes below the bar; .is-over lifts it above the header for the
   landing on the header mark. It never takes pointer input, and it is hidden
   under the open phone menu. Actors are placed with transforms from its corner. */
.stage {
  position: fixed;
  inset: 0;
  z-index: 49;
  overflow: clip;
  pointer-events: none;
}

.stage.is-over {
  z-index: 60;
}

.menu-open .stage {
  visibility: hidden;
}

/* After .hero__3d on purpose: the canvas keeps its class when dock.js moves it
   here, and these values replace its in-box position, hidden start and fade. */
.stage > * {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 1;
  transition: opacity 180ms linear;
}

.stage__flat {
  fill: var(--green);
  transform-origin: 0 0;
}

.hero__title {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(2.75rem, 0.9rem + 6.2vw, 6.75rem);
  line-height: 1;
  letter-spacing: -0.03em;
}

/* Each headline line sits in its own mask so it can rise into view. */
.hero__title .line {
  display: block;
  overflow: hidden;
  margin-top: -0.14em;
  padding: 0.14em 0 0.1em;
}

.hero__title .line__in {
  display: block;
}

.hero__title .stop {
  color: var(--accent-text);
}

.hero__lead {
  max-width: 33rem;
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  color: var(--ink-soft);
  font-size: clamp(1.0625rem, 0.95rem + 0.5vw, 1.25rem);
  line-height: 1.55;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
}

@media (min-width: 56.25rem) {
  .hero {
    align-items: center;
    padding-block: calc(var(--header-h) + 2rem) 4rem;
  }

  .hero__inner {
    grid-template-columns: minmax(0, 8fr) minmax(0, 5fr);
    align-items: center;
    gap: 2rem;
  }

  .hero__emblem {
    order: 0;
    justify-self: center;
    width: min(100%, 26rem);
    margin-right: 0;
  }
}

/* Intro start state. Applied only when scripts run (html.js). The delayed
   animations are a failsafe that shows everything even if the intro never runs;
   main.js switches them off (html.intro-done) once the intro has finished. */
.js .hero__title .line__in {
  transform: translateY(115%);
  animation: intro-failsafe 0.01s 5s forwards;
}

.js .hero [data-intro] {
  opacity: 0;
  animation: intro-failsafe 0.01s 5s forwards;
}

.js .hero__emblem,
.js .field {
  opacity: 0;
  animation: intro-failsafe-fade 0.01s 5s forwards;
}

.intro-done .hero__title .line__in,
.intro-done .hero [data-intro],
.intro-done .hero__emblem,
.intro-done .field {
  animation: none;
}

@keyframes intro-failsafe {
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes intro-failsafe-fade {
  to {
    opacity: 1;
  }
}

/* ---------- About ---------- */
.about__grid {
  display: grid;
  gap: 2.5rem;
}

.about__lead {
  max-width: 44rem;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.35rem, 1.1rem + 1.05vw, 1.95rem);
  line-height: 1.34;
  letter-spacing: -0.005em;
}

.about__text {
  display: grid;
  gap: 1.15em;
  max-width: 41rem;
  margin-top: clamp(2rem, 4vw, 3rem);
  color: var(--ink-soft);
}

.certs {
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding-top: 1.75rem;
  border-top: 1px solid var(--line-strong);
}

.certs__title {
  font: 500 1.25rem/1.3 var(--font-display);
}

.certs__text {
  max-width: 41rem;
  margin-top: 0.5rem;
  color: var(--ink-soft);
}

.certs__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: 1rem;
  margin-top: 1.75rem;
  /* Depth for the plaques' set-down and hover tilt (main.js, full motion only). */
  perspective: 56rem;
}

/* Satin plaque: lit from the top edge, with a long soft shadow. Flat at rest. */
.cert {
  position: relative;
  isolation: isolate;
  display: grid;
  gap: 0.3rem;
  padding: 1.4rem 1.5rem 1.5rem 2.6rem;
  border: 1px solid var(--line);
  border-radius: 0.5rem;
  background: linear-gradient(rgba(255, 255, 255, 0.03), transparent 3.5rem), var(--bg-raised);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 18px 40px -24px rgba(0, 0, 0, 0.7);
  transition: transform 0.6s var(--ease);
}

[data-theme="light"] .cert {
  box-shadow: inset 0 1px 0 #fff, 0 18px 40px -24px rgba(10, 35, 38, 0.3);
}

/* The bar is satin metal in the emblem's own greens. */
.cert::before {
  content: "";
  position: absolute;
  left: 1.25rem;
  top: 1.5rem;
  bottom: 1.5rem;
  width: var(--bar);
  border-radius: 999px;
  background: linear-gradient(#c9df86, #96bb39 55%, #7e9f2c);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5), 0 0 12px rgba(150, 187, 57, 0.25);
}

/* Sheen under the text. main.js moves --lx/--ly with the pointer; the default
   is the static highlight used in off mode, on touch and without JS. */
.cert::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: radial-gradient(18rem circle at var(--lx, 30%) var(--ly, 20%), rgba(201, 223, 134, 0.1), transparent 60%);
  pointer-events: none;
}

[data-theme="light"] .cert::after {
  background: radial-gradient(18rem circle at var(--lx, 30%) var(--ly, 20%), rgba(150, 187, 57, 0.12), transparent 60%);
}

.motion-full .cert.is-tilting {
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
}

/* While GSAP writes an inline transform (the set-down), the CSS transition
   would trail behind every frame; the tween alone drives it. */
.cert[style*="transform"] {
  transition: none;
}

.cert__std {
  font: 400 1.4rem/1.2 var(--font-display);
}

.cert__name {
  color: var(--ink-soft);
  font-size: 0.95rem;
}

@media (min-width: 56.25rem) {
  .about__grid {
    grid-template-columns: 4fr 8fr;
    gap: 2rem;
  }

  .about__grid > .section__title {
    position: sticky;
    top: calc(var(--header-h) + 2rem);
    align-self: start;
  }
}

/* ---------- Competencies ---------- */
/* Slightly see-through, so the field stays faintly visible behind this band. */
.competencies {
  background: var(--bg-raised);
  background: color-mix(in srgb, var(--bg-raised) 80%, transparent);
  transition: background-color 0.4s ease;
}

.competencies__head {
  display: grid;
  gap: 1.5rem;
}

.competencies__intro {
  max-width: 40rem;
  font: 300 clamp(1.3rem, 1.1rem + 0.9vw, 1.75rem) / 1.35 var(--font-display);
}

/* Experience: bar length encodes years, on one shared scale. */
.experience {
  margin-top: clamp(3rem, 6vw, 5rem);
}

.experience__list {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.25rem);
}

.exp {
  display: grid;
  grid-template-columns: clamp(4.75rem, 12vw, 8.5rem) 1fr;
  column-gap: clamp(1rem, 2.5vw, 2rem);
  row-gap: 0.7rem;
  align-items: end;
}

.exp__label {
  display: contents;
}

/* The counted copy is decoration; copying text gives the real value from the hidden span. */
.exp__num [data-count] {
  user-select: none;
}

.exp__num {
  grid-row: 1 / 3;
  font: 300 clamp(2.5rem, 1.6rem + 3.4vw, 4.5rem) / 0.86 var(--font-display);
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.exp__text {
  max-width: 38rem;
  color: var(--ink-soft);
}

/* Depth through material only; the bars stay flat so the comparison is true.
   The track is a groove milled into the band, the fill is satin. */
.exp__track {
  display: block;
  height: var(--bar);
  border-radius: 999px;
  background: var(--line);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.35);
}

[data-theme="light"] .exp__track {
  box-shadow: inset 0 1px 1px rgba(10, 35, 38, 0.18);
}

.exp__fill {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 999px;
  background: linear-gradient(#c9df86, #96bb39 55%, #86a832);
  box-shadow: 0 0 10px -2px rgba(150, 187, 57, 0.35);
  transform-origin: left;
}

/* Glint that main.js runs once along each fill by moving --glint (a share of
   the fill width). At the default it sits just outside the left end, unseen. */
.exp__fill::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--glint, -30%);
  width: 30%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.55), transparent);
}

.exp[data-share="75"] .exp__fill {
  width: 75%;
}

.exp[data-share="50"] .exp__fill {
  width: 50%;
}

/* Register of competencies: grouped rows, not cards. */
.register {
  margin-top: clamp(3.5rem, 8vw, 6.5rem);
}

.group {
  display: grid;
  gap: 1rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line-strong);
}

.group + .group {
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.group__title {
  font: 500 1.25rem/1.3 var(--font-display);
}

.cap {
  position: relative;
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  column-gap: 1.1rem;
  row-gap: 0.25rem;
  padding: 1.15rem 0;
  border-bottom: 1px solid var(--line);
}

.cap:first-child {
  padding-top: 0.25rem;
}

/* Every icon shape has pathLength="1", so one dash is the whole stroke.
   main.js draws the icons on by moving --draw from 1 to 0 (full motion only). */
.cap__icon {
  grid-row: 1 / 3;
  margin-top: 0.2rem;
  color: var(--accent-text);
  stroke-dasharray: 1;
  stroke-dashoffset: var(--draw, 0);
}

.cap__title {
  font: 400 1.3rem/1.3 var(--font-display);
}

.cap__text {
  grid-column: 2;
  color: var(--ink-soft);
  font-size: 1rem;
}

@media (min-width: 56.25rem) {
  .competencies__head {
    grid-template-columns: 4fr 8fr;
    gap: 2rem;
    align-items: end;
  }

  .group {
    grid-template-columns: 4fr 8fr;
    gap: 2rem;
  }

  .group__title {
    position: sticky;
    top: calc(var(--header-h) + 2rem);
    align-self: start;
    max-width: 15rem;
  }

  .cap {
    grid-template-columns: 1.5rem minmax(11rem, 17rem) 1fr;
    align-items: baseline;
  }

  .cap__icon {
    grid-row: auto;
    align-self: start;
    margin-top: 0.3rem;
  }

  .cap__text {
    grid-column: 3;
  }
}

/* Row hover on desktop: the wordmark bar grows at the row's left edge and a
   faint green wash lights the row. The text itself never moves. */
@media (hover: hover) and (min-width: 56.25rem) {
  .cap::before {
    content: "";
    position: absolute;
    left: -1.25rem;
    top: 0.9rem;
    bottom: 0.9rem;
    width: var(--bar);
    border-radius: 999px;
    background: var(--green);
    transform: scaleY(0);
    transition: transform 0.4s var(--ease), opacity 0.4s ease;
  }

  .cap:first-child::before {
    top: 0;
  }

  .cap:hover {
    background: linear-gradient(90deg, color-mix(in srgb, var(--green) 6%, transparent), transparent 70%);
  }

  .cap:hover::before {
    transform: scaleY(1);
  }
}

/* ---------- Partners (dark plate in both themes: logos are dark-background versions) ---------- */
.partners {
  --ink: #e8eee6;
  --ink-soft: #a3b4ab;
  --line: rgba(214, 232, 220, 0.13);
  --accent-text: #b4d65c;
  background: var(--plate);
  color: var(--ink);
}

.partners__head {
  display: grid;
  gap: 1.5rem;
}

.partners__lead {
  max-width: 40rem;
  color: var(--ink-soft);
}

/* The frame clips and rounds the wall, so the grid itself can stay a 3D
   relief (overflow on the grid would flatten it). */
.partners__frame {
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
  border: 1px solid var(--line);
  border-radius: 0.5rem;
  overflow: hidden;
  perspective: 100rem;
}

.partners__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  /* The 1px gaps show this background. Near the pointer the grid lines glow
     (main.js moves --gx/--gy); the default position parks the glow off-screen. */
  background-color: var(--line);
  background-image: radial-gradient(15rem circle at var(--gx, -60rem) var(--gy, -60rem), rgba(150, 187, 57, 0.8), transparent 70%);
  transform-style: preserve-3d;
}

.partner {
  position: relative;
  isolation: isolate;
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 2;
  background: var(--plate);
  transition: box-shadow 0.45s ease, transform 0.6s var(--ease);
}

/* Light inside the cell: follows the pointer across neighbouring cells
   (--mx/--my from main.js), or sits in the centre of a highlighted cell. */
.partner::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(16rem circle at var(--mx, 50%) var(--my, 50%), rgba(150, 187, 57, 0.17), transparent 62%);
  opacity: 0;
  transition: opacity 0.45s ease;
}

.partners__grid.is-pointed .partner::before,
.partner.is-lit::before {
  opacity: 1;
}

/* Green ring of the highlighted cell, drawn 4px inside the cell: the frame clips at its
   edge, and the lifted cell (Full) grows up to about 3px past it in perspective. */
.partner::after {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: 3px;
  box-shadow: 0 0 0 1px rgba(150, 187, 57, 0.6);
  opacity: 0;
  transition: opacity 0.45s ease;
  pointer-events: none;
}

.partner.is-lit::after {
  opacity: 1;
}

/* Full motion: the lit cell lifts out of the relief, with a deep shadow and a
   faint green underglow pooling into the gaps. */
.js.motion-full .partner.is-lit {
  z-index: 1;
  transform: translateZ(8px);
  box-shadow: 0 18px 40px -12px rgba(0, 0, 0, 0.7), 0 0 28px -6px rgba(150, 187, 57, 0.35);
}

.partner img {
  width: auto;
  height: auto;
  max-width: 64%;
  max-height: 40%;
  transition: filter 0.45s ease, opacity 0.45s ease, transform 0.6s var(--ease);
}

/* Partner name, shown under the logo of the highlighted cell (wide screens). */
.partner__name {
  display: none;
}

/* Logos that are too dark for the plate in their brand colours. */
.partner--lift img {
  filter: brightness(1.7);
}

/* JPEG on white: invert to a dark ground, then let black drop out. */
.partner--invert img {
  filter: invert(1) hue-rotate(180deg);
  mix-blend-mode: screen;
}

/* With scripts: quiet monochrome wall. One partner at a time is highlighted
   (.is-lit, set by main.js): the one under the pointer, otherwise a slow rotation. */
.js .partner img {
  filter: grayscale(1) brightness(1.5);
  opacity: 0.78;
}

.js .partner--lift img {
  filter: grayscale(1) brightness(2.6);
}

.js .partner--invert img {
  filter: invert(1) hue-rotate(180deg) grayscale(1);
}

/* While the pointer is on the wall, everything but the chosen partner steps back. */
.js .partners__grid.is-pointed .partner:not(.is-lit) img {
  opacity: 0.4;
}

.js .partner.is-lit img {
  filter: none;
  opacity: 1;
  transform: scale(1.07);
}

.js .partner--lift.is-lit img {
  filter: brightness(1.7);
}

.js .partner--invert.is-lit img {
  filter: invert(1) hue-rotate(180deg);
}

@media (min-width: 48rem) {
  .partner__name {
    position: absolute;
    inset: auto 0.75rem 11%;
    display: block;
    overflow: hidden;
    color: var(--ink);
    font-size: 0.8125rem;
    font-weight: 500;
    line-height: 1.2;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
    opacity: 0;
    transform: translateY(0.4rem);
    transition: opacity 0.4s ease, transform 0.6s var(--ease);
  }

  .partner.is-lit .partner__name {
    opacity: 1;
    transform: none;
  }

  /* The logo moves up to make room for the name. */
  .js .partner.is-lit img {
    transform: translateY(-0.6rem) scale(1.07);
  }
}

@media (min-width: 22.5rem) {
  .partners__grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 48rem) {
  .partners__grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

@media (min-width: 56.25rem) {
  .partners__head {
    grid-template-columns: 4fr 8fr;
    gap: 2rem;
    align-items: end;
  }
}

@media (min-width: 68.75rem) {
  .partners__grid {
    grid-template-columns: repeat(6, 1fr);
  }
}

/* ---------- Contact ---------- */
.contact__grid {
  display: grid;
  gap: clamp(2.5rem, 6vw, 4rem);
}

.contact__company {
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
  font: 500 1.25rem/1.3 var(--font-display);
}

.contact__list {
  display: grid;
  gap: 1.75rem;
  margin-top: 1.75rem;
}

.contact__list dt {
  color: var(--ink-soft);
  font-size: 0.95rem;
}

.contact__list dd {
  margin-top: 0.2rem;
  font: 300 clamp(1.4rem, 1.15rem + 1vw, 1.9rem) / 1.3 var(--font-display);
}

.contact__list a {
  text-decoration-color: var(--green);
  text-decoration-thickness: 0.125rem;
  text-underline-offset: 0.25em;
}

.contact__list a:hover {
  color: var(--accent-text);
}

.map {
  position: relative;
  min-height: 22rem;
  border: 1px solid var(--line);
  border-radius: 0.5rem;
  background-color: var(--bg-raised);
  overflow: hidden;
}

.map__cover {
  position: relative;
  isolation: isolate;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.9rem;
  height: 100%;
  min-height: inherit;
  padding: 2rem 1.5rem;
  overflow: hidden;
  text-align: center;
}

/* The 3rem grid laid down as a ground plane in perspective, fading towards the
   horizon. main.js settles --floor-tilt once (full motion); the text stays flat. */
.map__floor {
  position: absolute;
  inset: -190% -60% 0;
  z-index: -1;
  background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 3rem 3rem;
  background-position: center bottom;
  transform-origin: 50% 100%;
  transform: perspective(36rem) rotateX(var(--floor-tilt, 56deg)) scale(1.9);
  /* Fades out just above the pin, so the pin stands on visible floor. */
  -webkit-mask-image: linear-gradient(to top, #000 0 72%, transparent 92%);
  mask-image: linear-gradient(to top, #000 0 72%, transparent 92%);
  pointer-events: none;
}

/* The pin stands upright on the floor over a soft contact shadow. */
.map__pin-wrap {
  display: grid;
  justify-items: center;
}

.map__pin {
  width: 2.25rem;
  height: 2.25rem;
  color: var(--accent-text);
}

.map__pin-shadow {
  width: 2.6rem;
  height: 0.6rem;
  margin-top: -0.3rem;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.6), transparent);
}

[data-theme="light"] .map__pin-shadow {
  background: radial-gradient(closest-side, rgba(10, 35, 38, 0.35), transparent);
}

.map__address {
  font: 400 1.2rem/1.3 var(--font-display);
}

.map__note {
  max-width: 24rem;
  color: var(--ink-soft);
  font-size: 0.875rem;
  line-height: 1.5;
}

.map__link {
  font-size: 0.95rem;
  text-decoration-color: var(--green);
  text-underline-offset: 0.25em;
}

.map iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Focus moves into the map after "Load map": keep it visible on the container, because
   a frame element never matches :focus-visible once focus is inside it. */
.map.is-loaded:focus-within {
  outline: 2px solid var(--accent-text);
  outline-offset: 3px;
}

/* Without scripts the theme, motion and menu buttons cannot work: do not offer them. */
html:not(.js) .tool-btn,
html:not(.js) .site-nav__motion,
html:not(.js) [data-map-load] {
  display: none;
}

@media (min-width: 56.25rem) {
  .contact__grid {
    grid-template-columns: 5fr 7fr;
    align-items: stretch;
  }

  .map {
    min-height: 28rem;
  }
}

/* ---------- Footer ---------- */
.site-footer {
  border-top: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: 0.9rem;
}

.site-footer__inner {
  display: grid;
  gap: 1rem;
  padding-block: 2.5rem;
}

.site-footer .brand__mark {
  width: 2.4rem;
}

.site-footer .brand__word {
  width: 4.5rem;
}

@media (min-width: 56.25rem) {
  .site-footer__inner {
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 2.5rem;
  }
}

/* ---------- Flight to the header (dock.js) ---------- */
/* Hand-over without a ghost: when the actor leaves the box, the flat emblem and the
   canvas in the box are gone in that same frame (their 0.9 s fade belongs to the
   change from flat to 3D at load), so the logo is never seen twice. */
.hero__emblem.is-away .hero__mark,
.hero__emblem.is-away .hero__3d {
  transition: none;
}

/* The emblem has to appear in mid-flight (page loaded scrolled, window resized):
   it fades in a little slower than it fades onto the header mark. */
.stage.is-late > * {
  transition-duration: 0.3s;
}

/* Landed: once the actor has faded onto the header mark (180 ms, DOCK.fade) the
   whole flight layer is taken out of rendering, so nothing of it can show or cost
   anything while the rest of the page is read. Lift-off shows it again at once. */
html.is-docked .stage {
  visibility: hidden;
  transition: visibility 0s linear 180ms;
}

/* Motion modes block: the flight exists in Full only. */
html:is(.motion-reduced, .motion-off) .stage {
  display: none;
}

/* Print block: the flight layer does not print, so the flat emblem stands in its
   box again (the forced-colours block of main.css already does the same). */
@media print {
  .hero__emblem.is-away .hero__mark {
    opacity: 1;
  }
}

/* ---------- Landing beat (main.js) ---------- */
/* The header lockup answers the emblem's landing: a soft glow behind the mark, and
   the wordmark's sheen overlay (main.js runs the gradient across it once). Both are
   gone again when the beat ends; nothing of the logo itself changes. */
@keyframes brand-landed {
  from,
  to {
    opacity: 0;
  }

  30% {
    opacity: 1;
  }
}

.brand::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  z-index: -1;
  width: 6.5rem;
  height: 6.5rem;
  margin: -3.25rem 0 0 -2rem;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(150, 187, 57, 0.5), rgba(150, 187, 57, 0.14) 55%, transparent);
  opacity: 0;
  pointer-events: none;
}

.site-footer .brand::before {
  content: none;
}

.brand.is-landed::before {
  animation: brand-landed 0.9s ease-out 1;
}

.brand.is-landed .brand__sheen {
  animation: brand-landed 0.9s linear 1;
}

html.motion-off .brand.is-landed::before,
html.motion-off .brand.is-landed .brand__sheen {
  animation: none;
}

/* ---------- Theme switch as an iris (main.js) ---------- */
/* With view transitions and full motion the new theme opens as a circle from the theme
   button: main.js sets --vt-x, --vt-y and --vt-r and the class for that long. The
   radius grows at a constant speed, the same as the ring of light main.js sends through
   the field at that moment. Colour transitions are off meanwhile, so the page inside
   the circle already is the new theme. */
@keyframes vt-iris {
  from {
    clip-path: circle(0 at var(--vt-x, 50%) var(--vt-y, 0));
  }

  to {
    clip-path: circle(var(--vt-r, 150vmax) at var(--vt-x, 50%) var(--vt-y, 0));
  }
}

html.vt-theme::view-transition-old(root) {
  animation: none;
}

html.vt-theme::view-transition-new(root) {
  animation: vt-iris 0.7s linear both;
}

html.vt-theme,
html.vt-theme *,
html.vt-theme *::before,
html.vt-theme *::after {
  transition: none !important;
}

/* ---------- Buttons (main.js) ---------- */
/* The fill of a button grows as a circle from the point where the pointer came in
   (main.js sets --px and --py; from the middle for the keyboard). The label never moves. */
.btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.btn::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  background: var(--btn-fill, transparent);
  clip-path: circle(0 at var(--px, 50%) var(--py, 50%));
  transition: clip-path 0.55s var(--ease);
}

.btn:hover::before,
.btn:focus-visible::before {
  clip-path: circle(160% at var(--px, 50%) var(--py, 50%));
}

.btn--primary {
  --btn-fill: #bfe063;
}

.btn--quiet {
  --btn-fill: var(--green);
}

.btn--quiet:hover,
.btn--quiet:focus-visible {
  border-color: var(--green);
  color: var(--pine-black);
}

.js.motion-full .btn {
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease, transform var(--t1) var(--ease);
}

.js.motion-full .btn:active {
  transform: scale(0.97);
}

/* Reduced and off: the fill fades in place. */
:is(.motion-reduced, .motion-off) .btn::before {
  clip-path: none;
  opacity: 0;
  transition: opacity 0.25s ease;
}

:is(.motion-reduced, .motion-off) .btn:hover::before,
:is(.motion-reduced, .motion-off) .btn:focus-visible::before {
  opacity: 1;
}

@media (forced-colors: active) {
  .btn::before {
    display: none;
  }
}

/* ---------- Reading light (main.js) ---------- */
/* The two statements (About lead, Competencies intro) start in the soft ink and are lit
   to full ink from their first word to their last as the visitor scrolls: main.js moves
   --read from 0 to 1. The text is one inline box, so the lit strip runs on from line to
   line. Real text throughout; plain ink in Off, without scripts, in forced colours, with
   more contrast and on paper. */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  @media screen and (forced-colors: none) and (not (prefers-contrast: more)) {
    .js:is(.motion-full, .motion-reduced):not(.no-motion) .read {
      background-image: linear-gradient(90deg, var(--ink) calc(100% - 4rem), transparent), linear-gradient(var(--ink-soft), var(--ink-soft));
      background-size: calc(var(--read, 1) * (100% + 4rem)) 100%, 100% 100%;
      background-repeat: no-repeat;
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    .js:is(.motion-full, .motion-reduced):not(.no-motion) .read::selection {
      -webkit-text-fill-color: var(--pine-black);
    }
  }
}

/* ---------- Section scenes (scenes.js) ---------- */
/* Experience: a scale under the bars, and one cursor that crosses it and takes every
   bar and figure along; each row stops at its own value. scenes.js builds the axis,
   seats it over the track column (left) and moves the cursor through --cx (0 to 1). */
.experience.has-axis {
  position: relative;
  padding-bottom: 2.25rem;
}

.experience__axis {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  opacity: 0.75;
  pointer-events: none;
  transition: opacity var(--t2) ease;
}

.experience__axis.is-waiting {
  opacity: 0;
}

.experience__axis.is-live {
  opacity: 1;
}

.experience__tick {
  position: absolute;
  bottom: 0;
  padding-top: 0.65rem;
  color: var(--ink-soft);
  font: 400 0.8125rem/1 var(--font-body);
  font-variant-numeric: tabular-nums;
  transform: translateX(-50%);
  transition: color var(--t2) ease;
}

.experience__tick::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 1px;
  height: 0.4rem;
  background: var(--line-strong);
  transition: background-color var(--t2) ease;
}

.experience__tick.is-locked {
  color: var(--accent-text);
}

.experience__tick.is-locked::before {
  background: var(--accent-text);
}

.experience__cursor {
  position: absolute;
  top: -0.5rem;
  bottom: 1.6rem;
  left: calc(var(--cx, 0) * 100%);
  width: 1px;
  background: linear-gradient(transparent, var(--accent-text) 10%, var(--accent-text) 90%, transparent);
  box-shadow: 0 0 14px 1px rgba(150, 187, 57, 0.55);
  opacity: 0;
  transition: opacity var(--t2) ease;
}

.experience__axis.is-live .experience__cursor {
  opacity: 1;
}

/* A row that has reached its value: its figure lights once. */
@keyframes exp-lock {
  from {
    color: var(--accent-text);
    text-shadow: 0 0 24px rgba(150, 187, 57, 0.5);
  }
}

.js:is(.motion-full, .motion-reduced):not(.no-motion) .exp.is-locked .exp__num {
  animation: exp-lock var(--t4) var(--ease) 1;
}

/* Register: with full motion the rules of the groups and rows are drawn in from the
   left (scenes.js moves --rule from 0 to 1), so there the borders are pseudo-elements.
   --rule does not inherit: a group's rule being drawn must not restyle every row in it. */
@property --rule {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

.register {
  position: relative;
  isolation: isolate;
}

.js.motion-full .group {
  position: relative;
  border-top-color: transparent;
}

.js.motion-full .group::before,
.js.motion-full .cap::after {
  content: "";
  position: absolute;
  right: 0;
  left: 0;
  height: 1px;
  transform: scaleX(var(--rule, 1));
  transform-origin: left;
}

.js.motion-full .group::before {
  top: -1px;
  background: var(--line-strong);
}

.js.motion-full .cap {
  border-bottom-color: transparent;
}

.js.motion-full .cap::after {
  bottom: -1px;
  background: var(--line);
}

/* The current row (the row under a mouse pointer, otherwise the row on the reading
   line) shows the wordmark bar and a faint green wash, drawn inside the row so it
   always fits the row's height. scenes.js sets is-current; the highlight fades in. */
.register.has-marker .cap::before {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  left: -12px;
  right: 0;
  z-index: -1;
  border-left: var(--bar) solid var(--green);
  background: linear-gradient(90deg, color-mix(in srgb, var(--green) 9%, transparent), transparent 72%);
  opacity: 0;
  transform: none;
  transition: opacity var(--t1) ease;
  pointer-events: none;
}

@media (min-width: 56.25rem) {
  .register.has-marker .cap::before {
    left: -20px;
  }
}

.register.has-marker .cap.is-current::before {
  opacity: 1;
}

/* The current-row highlight replaces the per-row hover wash. */
.register.has-marker .cap:hover {
  background: none;
}

/* The current row's icon brightens (a colour change only: a glow filter on fourteen
   icons cost frames while scrolling). */
.cap__icon {
  transition: color var(--t1) ease;
}

.cap.is-current .cap__icon {
  color: #dcf29a;
}

[data-theme="light"] .cap.is-current .cap__icon {
  color: #4f6f0f;
}

/* Partner wall: the light that scenes.js walks along the grid lines and hops from cell
   to cell (.has-light) looks like the one under a mouse pointer (.is-pointed). */
.partners__grid.has-light .partner::before {
  opacity: 1;
}

/* Map: rings on the floor around the pin's foot after it has landed. The ring is one
   more background of the floor, so the floor's own perspective foreshortens it.
   scenes.js sets --fy (how far up the floor the foot stands) and --fn (how many rings). */
@property --fr {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

@property --fa {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

@keyframes map-ping {
  from {
    --fr: 0px;
    --fa: 1;
  }

  to {
    --fr: 15rem;
    --fa: 0;
  }
}

/* Reduced motion: the ring glows in place instead of growing. */
@keyframes map-ping-still {
  from,
  to {
    --fr: 5rem;
    --fa: 0;
  }

  40% {
    --fr: 5rem;
    --fa: 1;
  }
}

.map__floor.is-pinging {
  background-image: radial-gradient(
      circle at 50% calc(100% - var(--fy, 6rem)),
      transparent calc(var(--fr) - 0.9rem),
      rgb(150 187 57 / calc(var(--fa) * 0.85)) var(--fr),
      transparent calc(var(--fr) + 0.9rem)
    ),
    linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 100% 100%, 3rem 3rem, 3rem 3rem;
  background-repeat: no-repeat, repeat, repeat;
  background-position: 0 0, center bottom, center bottom;
  animation: map-ping 1s linear var(--fn, 3);
}

html.motion-reduced .map__floor.is-pinging {
  animation-name: map-ping-still;
}

html.motion-off .map__floor.is-pinging {
  animation: none;
}

/* The loaded map opens over the cover as a circle from the button (Reduced: a fade). */
.map iframe.is-iris {
  clip-path: circle(0 at var(--bx, 50%) var(--by, 50%));
  transition: clip-path 0.7s var(--e-travel);
}

.map iframe.is-iris.is-open {
  clip-path: circle(150% at var(--bx, 50%) var(--by, 50%));
}

html.motion-reduced .map iframe.is-iris {
  clip-path: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}

html.motion-reduced .map iframe.is-iris.is-open {
  opacity: 1;
}

/* ---------- Split text (type.js) ---------- */
/* Temporary markup of a letter or line entrance; revert() takes all of it away
   again. A block that is split into letters gets .sp as its mask: it reaches 0.14em
   above and 0.1em below the text for accents and descenders, and its margins take
   the same amounts back, so nothing around the title moves. Letters measure their
   offsetLeft from .sp. Kerning is off, because separate boxes cannot kern. */
.sp {
  position: relative;
  display: block;
  overflow: hidden;
  margin: -0.14em 0 -0.1em;
  padding: 0.14em 0 0.1em;
  font-kerning: none;
}

/* An inline element (a headline line inside its own mask) keeps an inline wrapper. */
.sp--inline {
  display: inline;
  overflow: visible;
  margin: 0;
  padding: 0;
}

.sp__w,
.sp__c {
  display: inline-block;
}

.sp__w {
  white-space: nowrap;
}

/* A paragraph split into lines: every line rises inside its own mask. */
.ln {
  display: block;
  overflow: hidden;
}

.ln__in {
  display: block;
}

/* ---------- Motion modes (type.js) ---------- */
/* Reduced and off: no mask, so split text can only ever fade, never sit hidden. */
:is(.motion-reduced, .motion-off) .sp,
:is(.motion-reduced, .motion-off) .ln {
  overflow: visible;
}

/* ---------- Wave band in display text (wave.js) ---------- */
/* While a wave crosses an element marked data-wave, wave.js gives it .is-waved and
   four custom properties: --wx and --wy (the wave's origin in the element's own box),
   --wr (radius, px) and --wa (strength, 0 to 1). The band is a ring-shaped background
   clipped to the letters; the text itself is untouched and stays --ink wherever the
   ring is not, and everywhere when the properties are missing. The 90px half width
   is TEXT.band in wave.js. Not in forced colours, with more contrast or on paper, and
   only where the browser can clip a background to text and mix colours. */
@supports ((background-clip: text) or (-webkit-background-clip: text)) and (color: color-mix(in srgb, red, blue)) {
  @media screen and (forced-colors: none) and (not (prefers-contrast: more)) {
    .is-waved {
      --wave-core: var(--green);
      margin-block: -0.14em -0.1em;
      padding-block: 0.14em 0.1em;
      background-image: radial-gradient(
        circle at var(--wx, 0px) calc(var(--wy, 0px) + 0.14em),
        var(--ink) calc(var(--wr, -400px) - 260px),
        color-mix(in srgb, var(--accent-text) calc(var(--wa, 0) * 100%), var(--ink)) calc(var(--wr, -400px) - 110px),
        color-mix(in srgb, var(--wave-core) calc(var(--wa, 0) * 100%), var(--ink)) calc(var(--wr, -400px) - 14px),
        color-mix(in srgb, var(--wave-core) calc(var(--wa, 0) * 100%), var(--ink)) var(--wr, -400px),
        var(--ink) calc(var(--wr, -400px) + 46px)
      );
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    [data-theme="light"] .is-waved {
      --wave-core: #688c1c;
    }

    [data-theme="light"] .partners .is-waved {
      --wave-core: var(--green);
    }

    /* The green full stop keeps its own colour. */
    .is-waved .stop {
      -webkit-text-fill-color: var(--accent-text);
    }

    /* Selected text takes the selection colours, not the transparent fill. */
    .is-waved::selection,
    .is-waved ::selection {
      -webkit-text-fill-color: var(--pine-black);
    }
  }
}

/* ---------- Motion modes (wave.js) ---------- */
/* Reduced: the band is a light change and stays. Off: no wave is emitted; this rule
   makes the text plain at once on a live switch, before wave.clear() has run. */
html.motion-off .is-waved {
  background-image: none;
  -webkit-text-fill-color: currentColor;
}

/* ---------- Headline being written (main.js) ---------- */
/* While its line is written the full stop runs along it, so it needs a box of its own. */
.hero__title.is-writing .stop {
  display: inline-block;
}

/* ---------- Motion modes ---------- */
/* theme.js sets exactly one of motion-full / motion-reduced / motion-off on
   <html> before first paint (?motion=, then the stored choice, then the OS).
   Without JS no class is set and only the OS setting applies. */
@media (prefers-reduced-motion: reduce) {
  html:not(.motion-full) {
    scroll-behavior: auto;
  }
}

html.motion-reduced,
html.motion-off {
  scroll-behavior: auto;
}

/* Reduced and off: nothing slides or scales; colour and opacity fades stay. */
:is(.motion-reduced, .motion-off) .site-header,
:is(.motion-reduced, .motion-off) .site-nav a::after,
:is(.motion-reduced, .motion-off) .menu-toggle__bar,
:is(.motion-reduced, .motion-off) .theme-toggle .icon {
  transition-property: background-color, border-color, opacity;
}

/* Highlighted partner changes colour only; logo and name stay in place. */
.js:is(.motion-reduced, .motion-off) .partner.is-lit img,
:is(.motion-reduced, .motion-off) .partner__name {
  transform: none;
}

/* Headline fades in instead of rising (main.js runs the fade). */
.js:is(.motion-reduced, .motion-off) .hero__title .line__in {
  transform: none;
  opacity: 0;
}

/* The row hover bar fades in place instead of growing. */
@media (hover: hover) and (min-width: 56.25rem) {
  :is(.motion-reduced, .motion-off) .cap::before {
    opacity: 0;
    transform: none;
  }

  :is(.motion-reduced, .motion-off) .cap:hover::before {
    opacity: 1;
  }
}

/* Rest state of the hero text once the intro has finished, in every mode: it does
   not depend on the inline styles the intro leaves behind, so main.js can clear
   them (and split text can be put back) without hiding the headline. */
html.js.intro-done .hero__title .line__in,
html.js.intro-done .hero [data-intro] {
  animation: none;
  transform: none;
  opacity: 1;
}

/* Set by main.js when the animation library is missing: everything is shown.
   Same specificity as the mode rules above and later, so it wins in every mode. */
.js.no-motion .hero__title .line__in,
.js.no-motion .hero [data-intro],
.js.no-motion .hero__emblem,
.js.no-motion .field {
  transform: none;
  opacity: 1;
  animation: none;
}

/* ---------- Forced colours and contrast ---------- */
/* Decorative canvases go; the flat SVG emblem stays in system colours. */
@media (forced-colors: active) {
  .field,
  .hero__3d,
  .stage,
  .hero__glint,
  .brand__sheen {
    display: none;
  }

  .hero__emblem.is-3d .hero__mark,
  .hero__emblem.is-solid .hero__mark,
  .hero__emblem.is-away .hero__mark {
    opacity: 1;
  }
}

/* Paper: the flight layer never prints. */
@media print {
  .stage {
    display: none;
  }
}

/* A filter, not opacity: main.js fades the canvas in through its opacity. */
@media (prefers-contrast: more) {
  .field {
    filter: opacity(0.5);
  }
}
