/* ============================================================
   Hephaestus Technologies — demo site styles
   Layout study: modern autonomy-brand landing page structure
   ============================================================ */

:root {
  color-scheme: dark;
  --ink: #12222f;
  --ink-soft: #48606f;
  /* Accent greens, centralised — these were previously scattered as 23 literals
     in four different notations. Saturation is raised well above the old pair,
     which read as dull against black; lightness is deliberately unchanged, so
     only the vividness shifts. The -rgb triplet exists so rgba() calls can
     share the same value. */
  --accent: #9d4e24;              /* eyebrows, primary button */
  --accent-dark: #84421e;         /* primary button hover */
  --accent-bright: #9d4e24;       /* highlights on black */
  --accent-bright-rgb: 157, 78, 36;
  /* Text that sits ON the accent, chosen to suit its lightness: black on this
     rust is 3.4:1 and hard to read, white is 5.9:1. Change the accent to
     something light and this wants to go back to #08080a — the two belong
     together. */
  --accent-ink: #ffffff;
  /* The hero footage was shot green and the brand is not, so the blend layer
     over it — .hero-tint — recolours the footage to the accent. Set to 0 to
     show the footage's own colour instead. */
  --tint-strength: 1;
  /* Every surface on every page is dark; the old white here was a leftover
     from the light layout study, and it was what mobile rubber-banding
     exposed past either end of the page. */
  --bg: #08080a;
  --bg-soft: #f4f7f8;
  --bg-mute: #e9f2f0;
  --line: #dde5e8;
  --radius-lg: 24px;
  --radius-md: 16px;
  --radius-pill: 999px;
  --shadow-card: 0 8px 30px rgba(18, 34, 47, 0.08);
  --header-h: 72px;       /* compact, once scrolled */
  --header-h-tall: 108px; /* at the top of the page */
  /* One grid for the whole page: the header, every section's content and
     the footer all sit on this width, so their left and right edges line
     up as you scroll. Previously the header was 1320, the mission and
     feature sections 1240 and the newsroom, map and footer 1200, which
     left the edges stepping in and out by 20 to 60px between sections. */
  --content-w: 1240px;
  --content: min(var(--content-w), 100% - 48px);
  /* The vertical room every dark section takes above and below its content */
  --section-pad: 120px;
  /* Every section heading is set at this size, so the mission, the two
     feature sections, the newsroom and the map all read at the same weight.
     Only the hero's h1 is larger. */
  --h2-size: clamp(2rem, 3.4vw, 2.9rem);
  /* The paragraph under each section heading, and the bold label over a
     block of smaller grey text (the cost terms, the numbered points) */
  --lead-size: 1.04rem;
  --lead-ink: rgba(255, 255, 255, 0.7);
  --label-size: 1.16rem;
  --bar: rgba(8, 8, 10, 0.48); /* frosted bar surface — pairs with the blur */
  --font: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 16px);
  /* Paints the overscroll gutter, so rubber-banding past either end
     shows black rather than the white body background. */
  background: #08080a;
}

body {
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

a { color: inherit; text-decoration: none; }

.container {
  width: var(--content);
  margin-inline: auto;
}

/* ---------- Type ---------- */

h1, h2, h3 { font-weight: 400; letter-spacing: -0.02em; line-height: 1.12; }

.eyebrow {
  display: inline-block;
  color: var(--accent);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.82rem;
  margin-bottom: 14px;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-block;
  font-family: inherit;
  font-weight: 700;
  font-size: 0.98rem;
  padding: 13px 28px;
  border-radius: var(--radius-pill);
  border: 2px solid transparent;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}

.btn:active { transform: scale(0.97); }

.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-dark); }

.btn-ghost { background: rgba(255, 255, 255, 0.14); color: #fff; border-color: rgba(255,255,255,0.7); backdrop-filter: blur(4px); }
.btn-ghost:hover { background: rgba(255, 255, 255, 0.28); }

.btn-lg { padding: 16px 36px; font-size: 1.05rem; }

/* ============================================================
   Header
   ============================================================ */

.site-header {
  --nav-size: 1.11rem;        /* nav text scales with the bar, like the logo */
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--header-h-tall);
  background: transparent;
  z-index: 100;
  transition: height 0.32s ease, background 0.32s ease, box-shadow 0.32s ease;
}

.site-header.scrolled {
  --nav-size: 0.97rem;
  height: var(--header-h);
  background: var(--bar);
  backdrop-filter: blur(12px);
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.35);
}

/* Keep the bar readable when the mobile menu is open over the hero */
.site-header.menu-open { background: var(--bar); backdrop-filter: blur(12px); }

.header-inner {
  width: var(--content);       /* the logo lines up with the content below it */
  margin-inline: auto;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 36px;
}

.logo { display: flex; align-items: center; gap: 10px; color: #fff; flex-shrink: 0; }
.logo-img { height: 44px; width: auto; transition: height 0.32s ease; }
.site-header.scrolled .logo-img { height: 34px; }
.logo-img-footer { height: 40px; }

/* ---- First-load intro: logo centred on black, then the whole cover fades ---- */
.intro {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: grid;
  place-items: center;
  background: #08080a;
  pointer-events: none;
  /* Failsafe: if the script never runs, the cover still lifts on its own */
  animation: intro-failsafe 0.6s ease 4s forwards;
}

.no-intro .intro { display: none; }

@keyframes intro-failsafe { to { opacity: 0; visibility: hidden; } }

.intro-logo {
  width: min(430px, 70vw);
  height: auto;
  opacity: 0;
  animation: intro-logo-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.15s forwards;
}

@keyframes intro-logo-in { to { opacity: 1; } }

/* Logo and cover leave together */
.intro.is-leaving {
  opacity: 0;
  transition: opacity 1s cubic-bezier(0.4, 0, 0.2, 1);
}

/* No scrolling while the cover is fully black; it comes back the moment the
   cover starts to fade (script.js removes .intro-hold). The gutter is kept
   reserved so the page does not shift sideways when the scrollbar returns. */
html { scrollbar-gutter: stable; }
html.intro-hold,
html.intro-hold body { overflow: hidden; }

/* Hold the hero's slide-in until the page starts to show */
.intro-hold .hero-content > * { animation-play-state: paused; }

.main-nav { flex: 1; }

.nav-list { display: flex; justify-content: flex-end; gap: 34px; list-style: none; }

/* Plain text, deliberately: no pill, no fill on hover — just the white
   brightening, so the links read as part of the header rather than as
   buttons competing with the one real button beside them. */
.nav-link {
  position: relative;
  display: inline-block;
  padding: 6px 0;
  font: inherit;
  /* Small spaced capitals, echoing the HEPHAESTUS wordmark beside them */
  font-weight: 500;
  font-size: calc(var(--nav-size) * 0.72);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;          /* two-word labels must never stack */
  color: rgba(255, 255, 255, 0.7);
  transition: color 0.2s ease, font-size 0.32s ease;
}

/* A hairline that draws out from the centre on hover */
.nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0.16em;               /* ignore the trailing letter-space */
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

.nav-link:hover,
.nav-link:focus-visible { color: #fff; }

.nav-link:hover::after,
.nav-link:focus-visible::after { transform: scaleX(1); }

.nav-item { position: relative; }

.header-actions { display: flex; align-items: center; gap: 14px; }

/* ---------- Private-access button + gate dialog ---------- */

.gate-open {
  position: relative;         /* anchors the glow behind it */
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 18px 9px 15px;
  font: inherit;
  font-weight: 600;
  font-size: 0.93rem;
  color: rgba(255, 255, 255, 0.85);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--radius-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

/* Slow green bloom behind the button, to draw the eye without nagging.
   The glow is an outer box-shadow on an empty pseudo-element: a shadow paints
   only beyond the box, so it haloes the pill instead of tinting its middle
   (a radial gradient pools in the centre of a wide shape). Only opacity
   animates, which the compositor handles. */
.gate-open::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;                /* behind the button, above the header bar */
  border-radius: inherit;
  box-shadow: 0 0 17px 2px rgba(var(--accent-bright-rgb), 0.6);
  pointer-events: none;
  animation: forgeGlow 3.6s ease-in-out infinite;
}

/* Intensity only, deliberately no scale: the pill is wide and short, so a
   uniform scale grows it far more horizontally than vertically and the halo
   stops matching the button's outline. */
@keyframes forgeGlow {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1; }
}

.gate-open svg { width: 15px; height: 15px; }

.gate-open:hover {
  color: #fff;
  background: rgba(var(--accent-bright-rgb), 0.14);
  border-color: rgba(var(--accent-bright-rgb), 0.55);
}

/* Once they are on it, the invitation has done its job — settle to a steady
   glow so it is not still pulsing under the cursor. */
.gate-open:hover::before { animation: none; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .gate-open::before { animation: none; opacity: 0.45; }
}


/* Standalone login page: centres the panel the dialog used to centre itself */
.login {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 30px;
  padding: 32px;
  background: #08080a;
  color: #fff;
}

.login-logo img { height: 42px; width: auto; }

.login .gate-panel { width: min(400px, 100%); }

.gate-panel {
  padding: 34px 32px 28px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-lg);
  background: #0d0f11;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.7);
}

.gate-panel h2,
.gate-title { font-size: 1.5rem; }

/* ---- Into the Forge: the homepage dims out, the login rises in ---- */

/* Laid over the homepage when "Access the Forge" is clicked. It ends on the
   login page's own background, so the swap between documents is invisible. */
.page-fade {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #08080a;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.42s cubic-bezier(0.4, 0, 0.2, 1);
}

.page-fade.is-on { opacity: 1; pointer-events: auto; }

@keyframes forge-rise {
  from { opacity: 0; transform: translateY(18px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

@keyframes forge-fade {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}

.login.is-entering .login-logo {
  animation: forge-fade 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}

.login.is-entering .gate-panel {
  animation: forge-rise 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.12s both;
}

/* The panel's contents follow it in one after another */
.login.is-entering .gate-panel > * {
  animation: forge-fade 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.login.is-entering .gate-panel > :nth-child(1) { animation-delay: 0.28s; }
.login.is-entering .gate-panel > :nth-child(2) { animation-delay: 0.34s; }
.login.is-entering .gate-panel > :nth-child(3),
.login.is-entering .gate-panel > :nth-child(4) { animation-delay: 0.40s; }
.login.is-entering .gate-panel > :nth-child(n+5) { animation-delay: 0.46s; }

/* A wrong password gets a short shake rather than the whole entrance again */
@keyframes forge-shake {
  0%, 100% { transform: none; }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(7px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(2px); }
}

.login.is-rejected .gate-panel { animation: forge-shake 0.42s ease-in-out; }

@media (prefers-reduced-motion: reduce) {
  .page-fade { transition-duration: 0.01s; }
  .login.is-entering .login-logo,
  .login.is-entering .gate-panel,
  .login.is-entering .gate-panel > *,
  .login.is-rejected .gate-panel { animation: none; }
}

.gate-intro { margin-top: 8px; color: rgba(255, 255, 255, 0.58); font-size: 0.95rem; }

.gate-label {
  display: block;
  margin: 26px 0 8px;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

/* "optional" beside a field's label: quieter, and in ordinary case, so it
   reads as a note on the label rather than part of it */
.gate-optional {
  margin-left: 6px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(255, 255, 255, 0.35);
}

.gate-input {
  width: 100%;
  padding: 12px 16px;
  font: inherit;
  font-size: 1rem;
  color: #fff;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 12px;
  outline: none;
  transition: border-color 0.2s ease;
}

.gate-input:focus { border-color: var(--accent); }

/* The sign-in form's dropdown: a drawn chevron in place of the browser's
   own, which ignores the dark field it sits in */
.gate-select {
  padding-right: 46px;
  appearance: none;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.7)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 16px;
}

.gate-select option { color: #fff; background: #0c0d11; }
.gate-select:invalid { color: rgba(255, 255, 255, 0.45); }

/* The "please specify" field appears when "Other" is chosen */
.gate-detail[hidden] { display: none; }

/* The bot trap: off-screen rather than display:none, which some scripts
   check for */
.gate-trap {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}


.gate-error {
  min-height: 1.3em;
  margin-top: 9px;
  color: #ff8a8a;
  font-size: 0.88rem;
}

.gate-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 8px; }

.gate-cancel {
  padding: 13px 22px;
  font: inherit;
  font-weight: 600;
  font-size: 0.95rem;
  color: rgba(255, 255, 255, 0.65);
  background: none;
  border: none;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
}

.gate-cancel:hover { color: #fff; background: rgba(255, 255, 255, 0.08); }


@media (max-width: 820px) {
  /* the label would crowd the hamburger; keep the padlock only */
  .gate-open { padding: 9px 12px; gap: 0; font-size: 0; }
  .gate-open svg { width: 17px; height: 17px; }
}

.hamburger {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: none;
  padding: 8px;
  cursor: pointer;
}
.hamburger span { width: 24px; height: 2.5px; background: #fff; border-radius: 2px; transition: transform 0.25s ease, opacity 0.25s ease; }
.hamburger.active span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
.hamburger.active span:nth-child(2) { opacity: 0; }
.hamburger.active span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

/* ============================================================
   Hero
   ============================================================ */

/* Full viewport, so the image edge and the stat bar land on the window bottom.
   Deliberately not clipped: the stat bar has to hang below the hero's bottom
   edge as it drifts. z-index lifts that overhang above the next section, which
   would otherwise paint over it as a later sibling. */
.hero {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: flex;
  align-items: center;
  color: #fff;
}

/* dvh tracks the area actually visible, so mobile browser chrome doesn't
   push the stat bar below the fold */
@supports (min-height: 100dvh) {
  .hero { min-height: 100dvh; }
}

.hero-media, .hero-media video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* Paints black behind the footage, so nothing flashes through while the first
   frame is still decoding. isolation seals the video/overlay z-indexes below
   into their own stacking context — without it they outrank the hero copy,
   which carries no z-index of its own, and paint straight over it. */
.hero-media {
  background: #08080a;
  isolation: isolate;
}

/* Safari paints its own start-playback glyph over any video it has refused to
   autoplay — Low Power Mode, Low Data Mode, or a per-site Auto-Play setting of
   Never will all do it, and a programmatic pause can too. On a decorative
   backdrop that reads as a broken player, and there is nothing worth pressing,
   so hide it and let the poster frame stand in.

   Each selector gets its own block on purpose: an unrecognised pseudo-element
   invalidates the entire rule it sits in, so sharing a block would take the
   others down in engines that do not know it. */
.hero-video::-webkit-media-controls-start-playback-button {
  display: none !important;
  -webkit-appearance: none;
}

.hero-video::-webkit-media-controls {
  display: none !important;
}

.hero-video::-webkit-media-controls-panel {
  display: none !important;
}

/* Loop dissolve. Both clips sit at full opacity and identical geometry; which
   one you see is decided by stacking order, and only the outgoing clip's
   opacity is animated. Fading both at once would dip the composite dark
   halfway through, since two half-transparent layers over black do not add
   back up to one. */
.hero-video { z-index: 1; }
.hero-video.is-front { z-index: 2; }

/* 1.2s must match XFADE in script.js */
.hero-video.is-fading {
  opacity: 0;
  transition: opacity 1.2s linear;
}

/* Keeps the headline legible over the footage without recolouring it. Two
   things matter here: the alpha, which was heavy enough to halve the plasma's
   brightness and take most of its saturation, and the colour, which was a
   blue-grey (10, 22, 32) and dragged every hue underneath it towards blue —
   so the accent you chose was not the colour you saw. It is now the site's own
   neutral black, at a lighter weight. */
/* Takes hue and saturation from the accent and luminance from the footage
   below it, which recolours the plasma exactly rather than approximately.
   Filters were the obvious route and the wrong one: hue-rotate() is a matrix
   that only approximates a rotation and drifted up to 19 degrees, and an SVG
   gradient map — exact on images — never reaches a video element at all.
   .hero-media is already an isolated stacking context, so the blend is
   confined to the footage and cannot reach the page behind it. */
.hero-tint {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: var(--accent-bright);
  mix-blend-mode: color;
  opacity: var(--tint-strength, 0);
  pointer-events: none;
}

/* Black wash over the footage, its opacity driven by scroll (script.js) */
.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;                 /* above the overlay gradient */
  background: #08080a;
  opacity: var(--hero-fade, 0);
  pointer-events: none;
}

.hero-overlay {
  position: absolute; inset: 0;
  z-index: 4;                 /* above the videos and the tint layer */
  background: linear-gradient(180deg,
      rgba(8, 8, 10, 0.38) 0%,
      rgba(8, 8, 10, 0.10) 45%,
      rgba(8, 8, 10, 0.46) 100%);
}

.hero-content {
  position: relative;
  /* Wider than the 1320 header: at full size the headline needs ~1333px to
     sit on two lines. Centred, it no longer has to square up with the logo. */
  width: min(1400px, 100% - 48px);
  margin-inline: auto;
  padding-top: var(--header-h-tall);
  /* Bottom padding lifts the centred block a touch above true centre */
  padding-bottom: 7vh;
  text-align: center;
}

/* Entrance: each line rises in from below, staggered. */
@keyframes heroSlideIn {
  from { opacity: 0; transform: translateY(70px); }
  to   { opacity: 1; transform: translateY(0); }
}

.hero-content > * {
  animation: heroSlideIn 1s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.hero-content h1 { animation-delay: 0.15s; }
.hero-content .hero-sub { animation-delay: 0.32s; }
.hero-content .hero-ctas { animation-delay: 0.48s; }

/* Once in, the text holds still: no drift after the entrance */

@media (prefers-reduced-motion: reduce) {
  .hero-content > * { animation: none; }
}

/* The middle term scales the type to exactly what two lines need in the
   space available (the text is ~19.4x its font size wide when split in two),
   so it holds two lines from ~830px up, and only drops to three below that. */
.hero h1 { font-size: clamp(2.5rem, calc((100vw - 48px) / 19.6), 4.3rem); text-wrap: balance; }

/* Sizes to the sentence so it stays on one line, and only wraps once the
   viewport is genuinely too narrow. Beats a fixed max-width, which would
   need revisiting every time the wording changes. */
.hero-sub {
  font-size: clamp(1.05rem, 2vw, 1.3rem);
  width: max-content;
  max-width: 100%;
  margin: 20px auto 0;
  color: rgba(255,255,255,0.92);
  /* The sentence breaks after "plasma," (a <br> in the markup). Where the
     first half is too long for one line, as on a tablet, balancing splits
     it evenly instead of leaving "triangularity plasma," on a line alone. */
  text-wrap: balance;
}

.hero-ctas { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px; margin-top: 34px; }

/* Drifts downward with scroll. The translate is a percentage of the bar's own
   height, so 100% lands its top edge exactly on the image's bottom edge —
   reached as that edge arrives at the top of the window. */
.hero-stat-bar {
  position: absolute;
  inset: auto 0 0 0;
  background: rgba(10, 22, 32, 0.2);
  backdrop-filter: blur(8px);
  padding: 30px 0;
  transform: translateY(calc(var(--bar-progress, 0) * 20%));
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .hero-stat-bar { transform: none; }
}

.hero-stat-inner {
  width: var(--content);
  margin-inline: auto;
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}

.hero-stat-number { font-size: clamp(1.5rem, 3vw, 2.2rem); font-weight: 800; color: var(--accent-bright); }
.hero-stat-text { font-size: 1rem; color: rgba(255,255,255,0.9); }

/* ============================================================
   Forge call to action — between the feature sections and the news

   The header's Forge link at a larger size, with a soft radial glow
   behind it that slowly breathes. Only transform and opacity animate; the
   glow is decorative (aria-hidden), and still for reduced motion.
   ============================================================ */

/* Close under the NT section, which it follows with no rule between: the
   two sections' full padding together left a band of black taller than
   the call to action itself */
.forge-cta {
  padding: 32px 0 130px;
  background: #08080a;
  color: #fff;
  text-align: center;
  overflow: hidden;             /* the glow is wider than a phone */
}

.forge-cta-title {
  margin-top: 14px;
  font-size: var(--h2-size);
}

/* The stage: exactly the button's size (the effects are taken
   out of flow and centred on it), so the rings take the button's outline */
.forge-cta-fire {
  position: relative;
  display: inline-block;
  margin-top: 72px;
}

.forge-cta-glow {
  position: absolute;
  left: 50%;
  top: 50%;
}

/* The button: the header link's pill (it shares .gate-open, so it gets the
   fade to the login page too), larger and in the accent */
.forge-cta .forge-cta-btn {
  position: relative;
  z-index: 3;
  gap: 11px;
  padding: 17px 34px 17px 28px;
  font-size: 1.08rem;
  color: #fff;
  background: linear-gradient(180deg, #b45a29, var(--accent));
  border: 1px solid rgba(255, 196, 150, 0.55);
  box-shadow:
    inset 0 1px 0 rgba(255, 220, 190, 0.3),
    0 0 22px 2px rgba(240, 120, 50, 0.3);
  transition: transform 0.25s ease, background 0.25s ease;
}

.forge-cta .forge-cta-btn svg { width: 18px; height: 18px; }

/* the header button's own halo is replaced by the glow below */
.forge-cta .forge-cta-btn::before { display: none; }

.forge-cta .forge-cta-btn:hover,
.forge-cta .forge-cta-btn:focus-visible {
  transform: translateY(-2px);
  background: linear-gradient(180deg, #c8662f, #a8522a);
  border-color: rgba(255, 210, 170, 0.8);
  box-shadow:
    inset 0 1px 0 rgba(255, 230, 200, 0.45),
    0 0 32px 6px rgba(240, 120, 50, 0.45);
}

.forge-cta .forge-cta-btn:focus-visible { outline: 2px solid #fff; outline-offset: 4px; }

/* A soft radial glow behind the button, slowly breathing */
.forge-cta-glow {
  width: 440px;
  height: 200px;
  margin: -100px 0 0 -220px;
  border-radius: 50%;
  background: radial-gradient(closest-side,
    rgba(240, 140, 70, 0.32),
    rgba(var(--accent-bright-rgb), 0.16) 50%,
    transparent 100%);
  filter: blur(24px);
  animation: forgeFlicker 4.5s ease-in-out infinite;
}

@keyframes forgeFlicker {
  0%, 100% { opacity: 0.7; transform: scale(0.96); }
  50%      { opacity: 1;   transform: scale(1.04); }
}

@media (max-width: 900px) {
  .forge-cta { padding: 20px 0 96px; }
  .forge-cta-fire { margin-top: 56px; }
  .forge-cta-glow { width: 340px; height: 170px; margin: -85px 0 0 -170px; }
}

@media (prefers-reduced-motion: reduce) {
  .forge-cta-glow { animation: none; opacity: 0.9; }
}

/* ============================================================
   Newsroom — story carousel

   The track is a native horizontal scroller with scroll-snap, so
   touch and trackpad swiping work without any script; the arrows
   are a convenience on top, and the cards are buttons that open a
   dialog each. --reveal is scrubbed by scroll like the other
   sections, and defaults to its finished value.
   ============================================================ */

.news {
  --reveal: 1;
  position: relative;
  padding: var(--section-pad) 0;
  background: #08080a;
  color: #fff;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}

/* Centred, because the stage below it is: ranged left, with the controls out
   on the right, the head read as belonging to a different section. */
.news-head {
  text-align: center;
  opacity: var(--reveal);
  translate: 0 calc((1 - var(--reveal)) * 22px);
  will-change: opacity, translate;
}

.news-title { font-size: var(--h2-size); }

.news-arrow {
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
}

.news-arrow svg { width: 19px; height: 19px; }

.news-arrow:hover {
  background: rgba(var(--accent-bright-rgb), 0.14);
  border-color: rgba(var(--accent-bright-rgb), 0.5);
}

/* ---------- the stage ----------

   A centred hero card with its neighbours tucked in behind it, cycling
   endlessly: the script assigns every card a slot relative to the middle one
   (…-1, 0, +1…) using modular arithmetic, so a card leaving one side is
   already the card arriving at the other. It hands each slot to CSS as --x
   (where the card sits), --d (signed, which way it tucks) and --ad
   (absolute, how far it recedes), so every aesthetic decision stays here.

   The list items are the slots and the cards are the content: all of the
   stage's positioning lives on the item, leaving the card free to own its
   own border, hover and shadow.  */

.news-track {
  --card-w: clamp(268px, 82vw, 620px);
  /* the band either side of the hero card, which is exactly the strip the
     neighbours occupy */
  --fade: calc((100% - var(--card-w)) / 2);
  position: relative;
  list-style: none;
  display: flex;
  gap: 26px;
  margin-top: 38px;
  /* the row is wider than the stage by design — the cards are translated into
     place from wherever they land in flow */
  overflow: hidden;
  /* vertical room for the hero card's shadow, which the clip would cut */
  padding: 16px 0 22px;
  opacity: var(--reveal);
  cursor: grab;
  /* vertical swipes still scroll the page; horizontal ones are ours */
  touch-action: pan-y;

  /* Without this the neighbours are sliced off by a hard vertical line at the
     page edge, half a word at a time, which reads as broken layout rather
     than depth. The ramp is measured from the hero card's own edges, so it
     covers precisely the neighbour strip at every width and never touches
     the card in the middle. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0,
      #000 var(--fade), #000 calc(100% - var(--fade)), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0,
      #000 var(--fade), #000 calc(100% - var(--fade)), transparent 100%);
}

.news-track.is-dragging { cursor: grabbing; }

.news-track > li {
  flex: 0 0 var(--card-w);
  /* --drag is set on the track while a finger is down, so the whole stage
     follows it; custom properties inherit, so one value moves every slot */
  /* --sides (0 -> 1, scrubbed by scroll) slides the cards beside the centre
     in from their own edges: --d's sign says which edge, and the centre card
     (--d 0) is untouched */
  translate: calc(var(--x, 0px) + var(--drag, 0px)
      + clamp(-1, var(--d, 0), 1) * (1 - var(--sides, 1)) * 140px) 0;

  /* --ad is clamped to 1.6 by the script, so these bottom out one card either
     side of the middle and stay there. Pushed well back: a neighbour that is
     nearly as legible as the hero competes with it, and half-readable text is
     more distracting than plainly background text. */
  scale: calc(1 - min(var(--ad, 0), 1) * 0.2);
  opacity: calc((1 - min(var(--ad, 0), 1) * 0.66)
      * (1 - min(var(--ad, 0), 1) * (1 - var(--sides, 1))));
  filter: blur(calc(min(var(--ad, 0), 1) * 3px));
  z-index: calc(10 - var(--ad, 0) * 4);
  transition: translate 0.55s cubic-bezier(0.16, 1, 0.3, 1),
              scale 0.55s cubic-bezier(0.16, 1, 0.3, 1),
              opacity 0.4s ease,
              filter 0.4s ease;
}

/* Set while a card wraps from one end of the stage to the other, and while a
   finger is dragging: in both cases the position should track exactly rather
   than ease its way across. */
.news-track > li.is-jumping,
.news-track.is-dragging > li { transition: none; }

.news-card {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  /* Fills the slot, which stretches to the tallest card, so a one-line
     headline does not leave a shorter card beside a taller one */
  height: 100%;
  padding: 0;
  overflow: hidden;
  text-align: left;
  font: inherit;
  color: inherit;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.045);
  cursor: pointer;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

.news-card:focus-visible { outline: none; }

/* Nested opacity multiplies, so this takes the side cards' copy down to a
   texture while their images stay legible as images */
.news-card:not(.is-active) .news-body { opacity: 0.35; }

/* Only the card in the middle is lit; the rest are scenery until they are
   brought forward. */
.news-card.is-active {
  border-color: rgba(255, 255, 255, 0.2);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6);
}

.news-card.is-active:hover,
.news-card.is-active:focus-visible {
  border-color: rgba(var(--accent-bright-rgb), 0.5);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6), 0 0 40px rgba(var(--accent-bright-rgb), 0.09);
}

.news-shot {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #0b0c0f;        /* the cut-out artwork has transparent corners */
}

/* --focus and --zoom come from the story's own framing, set in /admin and
   written onto the element by the Worker. They arrive as custom properties
   rather than finished declarations so that rules here can still compose with
   them — the hover zoom below multiplies the story's own rather than
   replacing it. transform-origin tracks the focal point, so zooming tightens
   the crop around the chosen spot instead of drifting off it. */
.news-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
  transform: scale(var(--zoom, 1));
  transform-origin: var(--focus, 50% 50%);
  /* the stills come from a warmer palette than the rest of the site, and a
     touch less saturation settles them into it */
  filter: saturate(0.88);
  transition: transform 0.55s ease;
}

.news-card.is-active:hover .news-shot img { transform: scale(calc(var(--zoom, 1) * 1.04)); }

/* Sinks the foot of the image into the card, so the headline is not sitting
   on a hard horizontal edge */
.news-shot::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(8, 8, 10, 0) 45%, rgba(8, 8, 10, 0.85));
}

.news-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 24px clamp(22px, 2.6vw, 32px) 28px;
}

.news-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
}

.news-tag { color: var(--accent-bright); font-weight: 700; }

.news-headline {
  display: block;
  margin-top: 12px;
  font-size: clamp(1.24rem, 1.9vw, 1.6rem);
  font-weight: 600;
  line-height: 1.28;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

/* Every card is as tall as the tallest one, so that the stage does not
   change height as you step through it. That leaves slack under a short
   headline, so the link sits on a hairline and reads as the foot of the card
   rather than as a gap someone forgot to close. */
.news-more {
  display: flex;
  align-items: center;
  align-self: stretch;
  gap: 9px;
  margin-top: auto;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--accent-bright);
}

.news-more svg { width: 16px; height: 16px; transition: translate 0.2s ease; }
.news-card.is-active:hover .news-more svg { translate: 4px 0; }

/* ---------- position markers ---------- */

.news-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  margin-top: 6px;
}

.news-dots {
  display: flex;
  align-items: center;
  gap: 9px;
  opacity: var(--reveal);
}

.news-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.24);
  cursor: pointer;
  transition: width 0.3s ease, background 0.3s ease;
}

.news-dot:hover { background: rgba(255, 255, 255, 0.45); }

/* The marker for the card in the middle stretches into a bar, so position
   reads at a glance without needing colour alone to carry it */
.news-dot.is-active {
  width: 26px;
  background: var(--accent-bright);
}

/* ---------- the story itself ---------- */

.news-dialog {
  width: min(720px, 100% - 32px);
  max-height: min(88vh, 940px);
  margin: auto;                /* centres it in every engine, old and new */
  padding: 0;
  overflow: auto;
  color: #fff;
  background: #0c0d11;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius-lg);
  box-shadow: 0 40px 90px rgba(0, 0, 0, 0.7);
}

.news-dialog::backdrop {
  background: rgba(4, 5, 7, 0.76);
  backdrop-filter: blur(6px);
}

.news-dialog[open] { animation: newsOpen 0.32s cubic-bezier(0.16, 1, 0.3, 1) both; }

@keyframes newsOpen {
  from { opacity: 0; translate: 0 16px; scale: 0.985; }
  to   { opacity: 1; translate: 0 0;    scale: 1; }
}

.news-close {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(8, 8, 10, 0.62);
  backdrop-filter: blur(6px);
  color: #fff;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.news-close svg { width: 17px; height: 17px; }

.news-close:hover {
  background: rgba(var(--accent-bright-rgb), 0.18);
  border-color: rgba(var(--accent-bright-rgb), 0.55);
}

.news-dialog-shot {
  margin: 0;
  aspect-ratio: 21 / 9;
  background: #0b0c0f;
}

/* The dialog crops harder than the card — 21:9 against 16:9 — so the same
   focal point is doing more work here. */
.news-dialog-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
  transform: scale(var(--zoom, 1));
  transform-origin: var(--focus, 50% 50%);
  filter: saturate(0.88);
}

.news-dialog-body { padding: 30px clamp(24px, 4vw, 40px) 40px; }

.news-dialog-body h2 {
  margin-top: 14px;
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  text-wrap: pretty;
}

.news-dialog-body p + p { margin-top: 16px; }

/* Source links in a story. They open in a new tab, so the reader keeps their
   place in the newsroom rather than losing the page behind the dialog. */
.news-dialog-body a {
  color: var(--accent-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
  /* long report URLs must not push the dialog wider than the viewport */
  overflow-wrap: anywhere;
  transition: color 0.2s ease;
}

.news-dialog-body a:hover { color: #fff; }

.news-dialog-body h2 + p:not(.news-meta) { margin-top: 22px; }

/* Emphasis comes from the story itself now, chosen in /admin, rather than
   from a rule that assumed the first paragraph was always a standfirst. */
.news-dialog-body strong { font-weight: 600; color: rgba(255, 255, 255, 0.95); }
.news-dialog-body em { font-style: italic; }

/* Underline on the web usually means "link", so this one is set apart from
   the link style: no colour change, and a gap so it does not crowd descenders */
.news-dialog-body u {
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.news-dialog-body p:not(.news-meta) {
  color: rgba(255, 255, 255, 0.72);
  font-size: 1.02rem;
  line-height: 1.68;
  text-wrap: pretty;
}

/* Keeps the page behind from scrolling under the open story */
body:has(.news-dialog[open]) { overflow: hidden; }

@media (max-width: 900px) {
  .news { padding: var(--section-pad-sm, 76px) 0; }
  .news-head { align-items: flex-start; flex-direction: column; }
  .news-track { margin-top: 30px; }
  .news-head { opacity: 1; translate: none; }
}

@media (prefers-reduced-motion: reduce) {
  .news { --reveal: 1; }
  .news-shot img,
  .news-more svg,
  .news-dot { transition: none; }
  .news-card.is-active:hover .news-shot img { transform: none; }
  /* the recede is a static depth cue rather than motion, so it stays; the
     glide between slots does not */
  .news-track > li { transition: none; filter: none; }
  .news-dialog[open] { animation: none; }
}

/* ============================================================
   Lede — the case for the company, between the hero and the mission
   ============================================================ */

/* A statement rather than a section: a slow orange glow behind it, the
   title as a small label, the text set large, and its three key phrases
   underlined in orange.

   script.js scrubs two values from the title's position as it scrolls in:
     --reveal     0 -> 1  the label fades and rises in
     --underline  0 -> 1  the underlines draw left to right, a beat later
   Both default to 1, so without the script everything simply shows. */
.lede {
  --reveal: 1;
  --underline: 1;
  position: relative;
  isolation: isolate;          /* keeps the glow behind this section's text only */
  overflow: hidden;            /* the glow is larger than the section */
  padding: 150px 0 130px;
  background: #08080a;
  color: #fff;
}

/* Two soft blooms of the accent, drifting slowly in opposite directions
   so the light never looks static. Blurred radial gradients, so they cost
   nothing to paint once composited. */
.lede-glow {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: calc(0.45 + var(--reveal) * 0.55);
}

.lede-glow span {
  position: absolute;
  width: min(900px, 90vw);
  aspect-ratio: 1.6;
  border-radius: 50%;
  filter: blur(40px);
  /* a hotter orange at the heart, fading through the accent */
  background: radial-gradient(closest-side, rgba(230, 120, 55, 0.4), rgba(var(--accent-bright-rgb), 0.24) 45%, rgba(var(--accent-bright-rgb), 0) 74%);
  will-change: transform;
}

.lede-glow span:first-child {
  left: 50%;
  top: 50%;
  translate: -78% -58%;
  animation: ledeDriftA 18s ease-in-out infinite alternate;
}

.lede-glow span:last-child {
  left: 50%;
  top: 50%;
  translate: -22% -42%;
  background: radial-gradient(closest-side, rgba(245, 150, 80, 0.3), rgba(224, 112, 52, 0.14) 45%, rgba(224, 112, 52, 0) 74%);
  animation: ledeDriftB 23s ease-in-out infinite alternate;
}

@keyframes ledeDriftA {
  from { transform: translate(-40px, -20px) scale(1); }
  to   { transform: translate(60px, 30px) scale(1.12); }
}

@keyframes ledeDriftB {
  from { transform: translate(50px, 20px) scale(1.08); }
  to   { transform: translate(-60px, -30px) scale(0.94); }
}

/* The title as a label: small, spaced capitals in the accent, like the
   section eyebrows but centred above the statement */
.lede-title {
  margin-bottom: 30px;
  text-align: center;
  font-size: 0.86rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #e8925f;              /* the accent, lightened to read at this size */
  opacity: var(--reveal);
  translate: 0 calc((1 - var(--reveal)) * 24px);
  will-change: opacity, translate;
}

.lede-text {
  max-width: 1200px;
  margin-inline: auto;
  text-align: center;
  font-size: clamp(1.45rem, 2.8vw, 2.3rem);
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: -0.015em;
  color: rgba(255, 255, 255, 0.92);
  text-wrap: pretty;
}

/* Underlined in orange, drawn in from the left. The line is a background
   image rather than text-decoration so it can be animated, and so it wraps
   with the phrase when the phrase breaks across two lines. */
.lede-key {
  color: #fff;
  background-image: linear-gradient(90deg, var(--accent), #e8925f);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: calc(var(--underline) * 100%) 3px;
  padding-bottom: 4px;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

@media (max-width: 900px) {
  .lede { padding: 96px 0 72px; }
  .lede-title { margin-bottom: 22px; }
  .lede-key { background-size: calc(var(--underline) * 100%) 2px; }
}

@media (prefers-reduced-motion: reduce) {
  .lede-glow span { animation: none; }
}

/* ============================================================
   Mission — Anvil C scroll reveal

   Nothing is pinned: the section scrolls like any other. Three
   custom properties, written by JS from the block's position in
   the viewport, drive the choreography:
     --fade    0 = reactor invisible, 1 = fully faded in
     --shift   0 = reactor centred and large, 1 = parked right, smaller
     --reveal  0 = copy hidden, 1 = copy fully in
   They default to 1 so the section still lands in a sensible
   finished layout if the script never runs.
   ============================================================ */

.mission {
  --fade: 1;
  --shift: 1;
  --reveal: 1;
  /* The parked layout is the feature sections' layout (HTS Magnets and NT
     Fusion below): the copy in a column at the grid's left edge, the same
     width as theirs, and the picture centred in the column to its right.
     So the Anvil C statement, the HTS cable and the NT copy all share the
     mission copy's left edge. The frame starts centred in the grid, and
     the centre of the right-hand column is half the copy plus the gap to
     the right of that, whatever the grid's width. The machine itself fills
     69% of its frame's width, the rest being transparent margin. */
  --copy-w: min(600px, 44vw);             /* .mission-copy's max-width: wider than .materials-copy, for the larger heading */
  --frame-w: min(880px, 74vw);            /* the reactor's frame, before it shrinks */
  --frame-h: calc(var(--frame-w) * 1354 / 1760);   /* its height: the frames are 1760 x 1354 */
  --anvil-gap: 64px;                      /* .mission-anvil's margin-top */
  --park-shrink: 0.18;                    /* how much smaller it parks: 0.18 leaves it at 82% */
  --pair-gap: 56px;                       /* .materials-inner's column gap */
  --park-x: calc((var(--copy-w) + var(--pair-gap)) / 2);
  position: relative;
  /* No min-height: a viewport-based floor leaves slack that centring splits
     above and below, which reopened the gap on tall windows. Sizing from
     content + padding keeps the spacing identical at every window height,
     and the reveal is driven by the block's position, not section height. */
  display: flex;
  flex-direction: column;     /* the reactor pair, then the Anvil C statement */
  align-items: center;
  /* The same room as every other section. The frame is taller than the
     copy it is centred on (see .mission-visual), and this also keeps its
     top inside the section while it is still at full size. */
  padding: var(--section-pad) 0;
  /* Where "Mission" in the nav (and the hero's button) lands: not on the
     section's edge, which puts the heading 200px down the window with the
     reactor still parking and the copy still fading in, but 16px above the
     copy, so its top sits 104px down the window. The choreography in
     script.js and tokamak.js has finished by then on any window 650px tall
     or more (its last step ends with the block's top 16% of the way down).
     Read by the smooth scroll in script.js, and by the browser's own jump. */
  scroll-margin-top: calc(16px - var(--section-pad));
  /* The parked reactor must not widen the page. clip rather than hidden:
     hidden makes the section a scroll container. hidden stays as the fallback. */
  overflow: hidden;
  overflow: clip;
  background: #08080a;
  color: #fff;
}

/* The header and the reactor as a pair. The block is as tall as the parked
   machine (the frame once shrunk by --park-shrink, less the transparent
   margins above and below the machine in the picture, about 9% of the frame
   each), so the header is centred on the machine and the costs below start
   just under it. A tall header still sizes the block itself. */
.mission-inner {
  position: relative;
  width: var(--content);
  min-height: calc(var(--frame-h) * (1 - var(--park-shrink)) * 0.82);
  margin-inline: auto;
  display: grid;
  align-items: center;
}

/* The reactor is taken out of flow and centred on the block, so the frame's
   transparent margins never become empty rows above and below the header. */
.mission-copy { grid-area: 1 / 1; }

.mission-rail {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.mission-visual {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--frame-w);
  margin: 0;
  /* centred on the block, before the park; the frame is taller than the
     block while still at full size, which the section's padding covers */
  translate: -50% -50%;
  opacity: var(--fade);
  /* rises a touch while fading in, then parks beside the copy and shrinks
     (--park-x and --park-shrink: see .mission) */
  transform:
    translateX(calc(var(--shift) * var(--park-x)))
    translateY(calc((1 - var(--fade)) * 44px))
    scale(calc(1 - var(--shift) * var(--park-shrink)));
  will-change: transform, opacity;
}

/* On a narrower desktop the copy takes more of the width, so the parked
   reactor gives up a little size to stay inside the grid */
@media (max-width: 1200px) {
  .mission { --park-shrink: 0.26; }
}

.mission-visual img {
  width: min(880px, 74vw);
  height: auto;
}

/* Warm halo that lifts the reactor off the black, strongest once parked */
.mission-visual::before {
  content: "";
  position: absolute;
  inset: -14%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(var(--accent-bright-rgb), 0.22), rgba(var(--accent-bright-rgb), 0) 70%);
  opacity: calc(0.35 + var(--reveal) * 0.65);
  z-index: -1;
}

/* The reactor is Anvil C, drawn from frames rendered offline (js/tokamak.js
   picks the one for the scroll position): it turns and opens to its
   cross-section as the block scrolls up — inside the fade, shrink and park
   above, which carry it along. The frame has the frames' own shape, so the
   canvas has a size to draw into and the picture fills it. */
.mission-visual--model {
  width: min(880px, 74vw);
  aspect-ratio: 1760 / 1354;
}

/* The canvas and the fallback picture fill the frame; the picture shows
   until the first frame has drawn, and stays if none ever does */
.mission-visual--model canvas,
.mission-visual--model img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  transition: opacity 0.7s ease;
}

.mission-visual--model img { object-fit: contain; }
.mission-visual--model canvas { opacity: 0; }
.mission-visual--model.is-ready canvas { opacity: 1; }
.mission-visual--model.is-ready img { opacity: 0; }

.mission-copy {
  position: relative;         /* above the reactor frame, which overlaps it while it parks */
  justify-self: start;
  max-width: var(--copy-w);
  opacity: var(--reveal);
  /* comes in from the left, to the grid's edge */
  transform: translateX(calc((1 - var(--reveal)) * -36px));
  will-change: transform, opacity;
}

/* balance: at the shared column width "Next-Generation Fusion / Energy"
   otherwise leaves one word on the second line */
/* Larger than the other sections' headings: with the costs moved below,
   the header alone stands beside the reactor, and at the shared size it
   left a wide black band around it. The column is widened to match
   (--copy-w on .mission) so "Next-Generation" never breaks at its hyphen. */
.mission-copy h2 {
  font-size: clamp(2.5rem, 4.6vw, 4.4rem);
  line-height: 1.05;
  text-wrap: balance;
}

.mission-copy > p {
  margin-top: 24px;
  color: var(--lead-ink);
  font-size: clamp(1.15rem, 1.75vw, 1.55rem);
  line-height: 1.45;
  text-wrap: pretty;
}

/* The two costs, under the header and the reactor, side by side across the
   full width of the grid, under a rule as the Anvil C statement is */
.mission-costs {
  position: relative;         /* above the reactor's frame, which can overlap it while large */
  width: var(--content);
  margin: 56px auto 0;
}

/* Three shared rows (heading, challenge, solution) that both costs sit on,
   via subgrid, so when one heading wraps to two lines and the other does
   not, the two challenges and the two solutions still start level */
.mission-specs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: auto auto auto;
  column-gap: 64px;
  padding-top: 32px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}

.mission-specs > div {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  align-content: start;
}

/* The same label-over-grey-text pair as the numbered points in the feature
   sections (.materials-steps h3 and p) */
.mission-specs dt {
  font-weight: 600;
  font-size: var(--label-size);
  letter-spacing: -0.01em;
}

/* Each cost as "The challenge" then "The solution": a small spaced label
   over its text. The solution is set off by an orange rule at its left and
   opens with a link to the section that explains it. */
.mission-part { margin: 14px 0 0; }

.mission-part-label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
}

.mission-part p {
  color: rgba(255, 255, 255, 0.62);
  font-size: 0.95rem;
  line-height: 1.6;
  text-wrap: pretty;          /* avoids a lone word on the last line */
}

.mission-part--solution {
  align-self: start;          /* its rule only as long as its own text, not the shared row */
  padding-left: 16px;
  border-left: 2px solid var(--accent);
}

.mission-part--solution .mission-part-label { color: #e8925f; }
.mission-part--solution p { color: rgba(255, 255, 255, 0.78); }

/* The closing statement: a label in the same style as the cost terms above,
   then one sentence at a larger size across the whole grid, under a rule
   that runs the whole grid too, as the rules over the numbered points do. */
.mission-anvil {
  position: relative;         /* above the reactor's track, which runs down to it */
  width: var(--content);
  margin: var(--anvil-gap) auto 0;
  padding-top: 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}

.mission-anvil h3 {
  font-weight: 600;
  font-size: var(--label-size);
  letter-spacing: -0.01em;
}

.mission-anvil p {
  margin-top: 10px;
  color: rgba(255, 255, 255, 0.78);
  font-size: clamp(1.1rem, 1.5vw, 1.3rem);
  line-height: 1.55;
  text-wrap: pretty;
}

/* The three advantages side by side, each opening with its name in white,
   marked by a short orange rule rather than a bullet */
.mission-anvil-points {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px 40px;
  margin-top: 26px;
  padding: 0;
  list-style: none;
}

.mission-anvil-points li {
  position: relative;
  padding-top: 16px;
  color: rgba(255, 255, 255, 0.62);
  font-size: 0.95rem;
  line-height: 1.6;
  text-wrap: pretty;
}

.mission-anvil-points li::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--accent);
}

.mission-anvil-points strong { display: block; margin-bottom: 4px; font-weight: 600; color: #fff; }

/* The solution's lead-in, "Solved by …:", links down to the section that
   explains it. The accent lightened, as at this size the accent itself is
   too dark to read on black. */
a.mission-solved {
  font-weight: 600;
  color: #e8925f;
  text-decoration: underline;
  text-decoration-color: rgba(232, 146, 95, 0.35);
  text-underline-offset: 3px;
  transition: text-decoration-color 0.2s ease, color 0.2s ease;
}

a.mission-solved:hover,
a.mission-solved:focus-visible {
  color: #f2a97c;
  text-decoration-color: currentColor;
}

a.mission-solved svg {
  display: inline-block;
  width: 0.95em;
  height: 0.95em;
  margin-right: 5px;
  vertical-align: -0.12em;
}

/* Stacked, no scroll choreography on small screens */
@media (max-width: 900px) {
  .mission-anvil { margin-top: 48px; }
  .mission {
    min-height: 0;
    display: block;
    --fade: 1; --shift: 0; --reveal: 1;
    padding: var(--section-pad-sm, 76px) 0;
    scroll-margin-top: calc(40px - var(--section-pad-sm, 76px));   /* the smaller padding here */
  }
  .mission-inner { display: block; min-height: 0; }
  .mission-rail { position: static; }
  .mission-costs { margin-top: 44px; }
  /* one cost above the other, with a hairline between them */
  .mission-specs { grid-template-columns: 1fr; grid-template-rows: none; }
  .mission-specs > div { grid-row: auto; grid-template-rows: none; }
  .mission-specs > div + div { margin-top: 30px; padding-top: 28px; border-top: 1px solid rgba(255, 255, 255, 0.08); }
  .mission-visual { position: static; translate: none; transform: none; opacity: 1; }
  .mission-anvil-points { grid-template-columns: 1fr; gap: 22px; }
  .mission-visual img,
  .mission-visual--model { width: 100%; }
  .mission-copy { max-width: none; margin-top: 40px; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .mission { --fade: 1; --shift: 1; --reveal: 1; }
}

/* ============================================================
   Materials — superconducting wire

   The cable art is a cutout whose two ends run off one side of
   the frame. Pulling it past the section's left edge (with the
   section clipping) makes it read as one continuous run of wire
   passing through, rather than a picture of a loop.

   NT Fusion uses the same section mirrored (.materials--mirror):
   the picture on the right, running off the right-hand edge, and
   every movement reversed. --side carries the direction: 1 here,
   -1 in the mirror.
   ============================================================ */

.materials {
  --drift: 0.5;               /* scroll parallax, written by JS */
  --converge: 1;              /* 0 = halves parked off each edge, 1 = met in place */
  --side: 1;                  /* -1 mirrors the movements (see .materials--mirror) */
  position: relative;
  overflow: hidden;           /* trims the wire at the section edge */
  padding: var(--section-pad) 0;
  background: #08080a;
  color: #fff;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}

.materials-inner {
  position: relative;
  width: var(--content);
  margin-inline: auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 56px;
  align-items: center;
}

.materials-visual {
  position: relative;
  margin: 0 0 0 -38%;         /* carries the wire past the left edge */
  width: 138%;
  opacity: var(--converge);
  transform: translateX(calc((1 - var(--converge)) * -170px * var(--side)));
  will-change: transform, opacity;
}

.materials-visual img {
  width: 100%;
  height: auto;
  /* gentle horizontal drift as the section passes through the viewport */
  transform: translateX(calc((var(--drift) - 0.5) * 70px * var(--side)));
  will-change: transform;
}

/* cool steel glow so the cable is not floating on flat black */
.materials-visual::before {
  content: "";
  position: absolute;
  inset: 6% 14%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(186, 214, 224, 0.16), rgba(186, 214, 224, 0) 70%);
  z-index: -1;
}

.materials-copy {
  max-width: 520px;
  opacity: var(--converge);
  transform: translateX(calc((1 - var(--converge)) * 170px * var(--side)));
  will-change: transform, opacity;
}

.materials-copy h2 { font-size: var(--h2-size); text-wrap: balance; }

.materials-lead {
  margin-top: 20px;
  color: var(--lead-ink);
  font-size: var(--lead-size);
  text-wrap: pretty;
}

/* Full width under the picture and its introduction, three points in a
   row (the problem each section opens with is now set out in the mission
   above, so each section has three). The row above is the copy's height
   in both sections (the pictures are sized to it, see below), so this gap
   is the same under the cable as under the render. */
.materials-steps {
  width: var(--content);
  margin: 48px auto 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 44px 48px;
}

/* Each point is a small grid of its number, title and description, on two
   rows it shares with the points beside it (subgrid): a title that wraps to
   two lines then pushes every description in the row down together, so
   the three always start level. The wrapper round the title and text is
   display: contents, so its children are the grid's own items. */
.materials-steps li {
  display: grid;
  grid-row: span 2;
  grid-template-rows: subgrid;
  grid-template-columns: 24px minmax(0, 1fr);
  column-gap: 20px;
  row-gap: 0;
  align-content: start;
  padding-top: 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}

.materials-steps li > div { display: contents; }

.materials-steps h3 { grid-column: 2; grid-row: 1; align-self: start; }
.ms-body { grid-column: 2; grid-row: 2; }

.ms-num {
  grid-column: 1;
  grid-row: 1;
  /* fixed width (the grid's first column), so every heading starts on the
     same line whatever the digits */
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--accent-bright);
  padding-top: 5px;
}

/* balance: the longer titles take two lines, and split evenly rather than
   leaving one word on the second */
.materials-steps h3 { font-size: var(--label-size); font-weight: 600; letter-spacing: -0.01em; text-wrap: balance; }

.materials-steps p {
  margin-top: 7px;            /* under the title, and between paragraphs */
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.95rem;
}

/* A lead-in such as "Anvil 0." picked out of the grey body text */
.materials-steps p strong { font-weight: 600; color: rgba(255, 255, 255, 0.92); }

/* ---- The mirror: NT Fusion ---- */
.materials--mirror { --side: -1; }

/* Picture in the right-hand column, carried past the right edge as the wire
   is carried past the left */
.materials--mirror .materials-visual {
  --art-left: 12.2%;          /* where the art starts; see below */
  grid-column: 2;
  grid-row: 1;
  margin: 0 -38% 0 0;
  aspect-ratio: 1522 / 1099;
}

.materials--mirror .materials-copy { grid-column: 1; grid-row: 1; }

/* The separatrix art (separatrix-line.png) is a transparent cutout like
   the wire: separatrix_single_line_steel_thinner.png exactly as drawn, with
   only its dark backdrop lifted out, so it sits on the page with no box
   around it. The frame keeps the wire's proportions, so the section keeps
   exactly the HTS layout; within it the art is placed so the loop mirrors
   where the wire sits (--art-left moves it), and drawn at a fixed height.
   Its width follows from the file's proportions, so a replacement image
   with different proportions needs a new file name: a browser still
   holding the old file would draw it at the new height and wrong width. */
.materials--mirror .materials-visual img {
  position: absolute;
  left: var(--art-left);
  top: 50%;
  translate: 0 -50%;
  width: auto;
  max-width: none;
  height: 80.9%;
}

/* The steel glow, centred on the loop rather than on the frame (the loop's
   centre is 16% of the frame's width in from the art's left edge) */
.materials--mirror .materials-visual::before {
  inset: 6% auto;
  left: calc(var(--art-left) + 16% - 23%);
  width: 46%;
}

/* ---- The cable: HTS Magnets (.materials--cable) ----
   The base layout's bleed (-38%) was sized for the old loop, which had wide
   empty margins; the cable fills its file to the left edge, so that pull
   put the cut end at the window's edge and the body off the page. Instead
   the picture sits in its column, a little past the left edge so the sheath
   still runs off the page, and is faded out over that first stretch so the
   file's edge dissolves rather than cutting. The drift still applies. */
.materials--cable .materials-visual {
  /* The cable occupies the left 62% of its file and the band from 8% to
     73% of its height, the rest being empty canvas. At 135% of the column
     the cable's body comes out about the height of the copy beside it
     (about 355px on a 1300–1728px window). Shifted right so the cut end
     lands just short of the copy, with the file's empty right-hand side
     lying under the gap.

     The figure is the copy's height, not the file's: stretched to the
     grid row, which the copy alone sizes, with the picture taken out of
     flow. Sized by the file, the row was 130px taller than the copy and
     the points below sat 115px under it, against 40px in NT Fusion. */
  margin: 0 -50% 0 15%;
  width: 135%;
  align-self: stretch;
  translate: none;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 10%);
  mask-image: linear-gradient(to right, transparent, #000 10%);
}

/* The body's middle is 41% of the way down the file, so that line — not
   the file's centre — is put on the copy's centre line. The empty lower
   canvas then hangs below the figure, where the mask clips it. */
.materials--cable .materials-visual img {
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: auto;
  translate: 0 -41%;
}

/* the glow, centred on the cable's body rather than the frame */
.materials--cable .materials-visual::before { inset: 10% 34% 10% 20%; }

/* ---- The render: Anvil C in NT Fusion (.materials-visual--photo) ----
   An opaque picture rather than a cutout, so it sits in the column as a
   rounded plate instead of bleeding off the edge the way the cable does.
   The drift as the section passes still applies to it. Written for either
   side, at the mirror's specificity, so it wins in both. */
.materials .materials-visual--photo {
  margin: 0;
  width: 100%;
  aspect-ratio: auto;
  /* Sized by the copy beside it. The figure has no height of its own (its
     picture is taken out of flow below), so the grid row is the copy's
     height and the figure is stretched to it; the picture is centred on
     that, in the column, and sits over the right-hand column of points
     below, which shares this column's edges. */
  align-self: stretch;
  min-height: 0;
}

/* A fifth taller than the copy, overhanging it equally above and below:
   the render is a tall, narrow cutout, and drawn exactly the copy's height
   it came out a third narrower than the cable and the reactor in the
   sections either side of it. */
.materials .materials-visual--photo img {
  position: absolute;
  inset: -10% 0;
  width: 100%;
  height: 120%;
  max-width: none;
  translate: none;
  object-fit: contain;
  object-position: center;
}

/* NT Fusion mirrors HTS Magnets exactly. There, the copy starts at the
   inner edge of its half (half the grid's gap, 28px, right of the centre
   line) and the cable ends about 70px before it. Here the copy ENDS at the
   inner edge of its half, and the render begins 70px after it, drawn a
   little taller than the copy and centred on it, as the cable is. */
@media (min-width: 901px) {
  .materials--mirror .materials-copy { justify-self: end; }

  /* The render's file has a transparent margin of about 6% round the
     machine, which the -28px takes back: the machine itself, not the file,
     starts 70px from the copy (14px + the grid's 56px gap). */
  .materials--mirror .materials-visual--photo img {
    inset: -22% auto auto 14px;
    width: calc(100% - 14px);
    height: 144%;
    object-position: -28px center;
  }

  .materials--mirror .materials-steps { margin-top: 96px; }

  /* less room under NT Fusion, as the Forge call to action follows it
     directly (see .forge-cta) */
  .materials--mirror { padding-bottom: 80px; }
}

/* A warm haze behind it, matching the render's own backdrop */
.materials .materials-visual--photo::before {
  inset: 6% 4%;               /* also the left, which the mirror rule sets */
  width: auto;
  background: radial-gradient(closest-side, rgba(172, 108, 62, 0.4), rgba(120, 74, 42, 0) 74%);
}

/* In NT Fusion the haze follows the render, which is drawn from its
   column's inner edge (see above). After the rule above, which it overrides. */
@media (min-width: 901px) {
  .materials--mirror .materials-visual--photo::before { left: 0; right: auto; width: 74%; }
}


@media (max-width: 900px) {
  .materials { padding: var(--section-pad-sm, 76px) 0; }
  .materials-inner { grid-template-columns: 1fr; gap: 12px; }
  /* wire first, still bleeding off the left, but far less aggressively */
  .materials-visual { margin-left: -26%; width: 118%; }
  /* the cable, as above: from the left edge, the empty part of the file
     overflowing the right. One column, so the picture is back in flow at
     its own height; the file's empty lower canvas is trimmed instead. */
  .materials--cable .materials-visual { margin: 0 -45% 0 0; width: 145%; align-self: auto; }
  /* (27% of the file's height, which is 18% of its width, is empty below the cable) */
  .materials--cable .materials-visual img { position: static; translate: none; margin-bottom: -16%; }
  .materials-visual,
  .materials-copy { opacity: 1; transform: none; }
  .materials-visual img { transform: none; }
  .materials-copy { max-width: none; }
  .materials-steps { grid-template-columns: 1fr; gap: 30px; margin-top: 48px; }
  /* picture first here too, bleeding off the right instead */
  .materials--mirror .materials-visual,
  .materials--mirror .materials-copy { grid-column: auto; grid-row: auto; }
  .materials--mirror .materials-visual { margin: 0 0 0 8%; }
  /* one column here, so the render is drawn at its own height again, as
     wide as the column, above the copy */
  .materials .materials-visual--photo { margin: 0; width: 100%; align-self: auto; }
  /* Centred, at a height in step with the reactor and the cable above it
     (about 450px on a tablet, 240px on a phone). Drawn the full column
     width, as it was, the tall render came out 740px high on a tablet —
     the biggest picture on the page by far. */
  .materials .materials-visual--photo img {
    position: static;
    inset: auto;
    width: auto;
    max-width: 100%;
    height: min(62vw, 460px);
    margin-inline: auto;
    object-fit: contain;
  }
  /* the steps' gap, now that the picture is above the copy rather than beside it */
  .materials-steps { margin-top: 48px; }
}

@media (prefers-reduced-motion: reduce) {
  .materials-visual,
  .materials-copy { opacity: 1; transform: none; }
  .materials-visual img { transform: none; }
}

/* ============================================================
   Locations — world map

   The map is a real SVG, not an image: coastlines are plotted in an
   equirectangular projection (x from longitude, y from latitude) at
   1000 units wide, cropped vertically to the inhabited band. Because
   the pins use the same projection, their positions are geographically
   correct against the coastlines rather than eyeballed.
   ============================================================ */

.locations {
  --reveal: 1;                /* scrubbed by scroll */
  position: relative;
  padding: var(--section-pad) 0;
  background: #08080a;
  color: #fff;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}

/* Centred: ranged left it left a large hole to the right of it, above a
   full-width map. */
.locations-head {
  max-width: 820px;
  margin-inline: auto;
  text-align: center;
  opacity: var(--reveal);
  translate: 0 calc((1 - var(--reveal)) * 26px);
  will-change: opacity, translate;
}

/* Always one line, at the shared heading size. The sentence is ~16.1x its
   font size wide, so the second term is the largest size the container (the
   viewport less its 48px of gutter) can hold on one line, with a little to
   spare; it only bites below ~800px. nowrap stops a rounding error from
   wrapping it. */
.locations-title {
  font-size: min(var(--h2-size), calc((100vw - 48px) / 16.8));
  white-space: nowrap;
}

/* Phones: one line would mean shrinking it well below the rest of the
   page's headings, so here it keeps its size and wraps instead. Below 600px
   the one-line size would drop under this anyway. */
@media (max-width: 600px) {
  .locations-title { font-size: 1.9rem; white-space: normal; text-wrap: balance; }
}

.locations-lead {
  margin: 20px auto 0;
  max-width: 620px;
  color: var(--lead-ink);
  font-size: var(--lead-size);
}

/* Fades on its own travel through the viewport, not the section's, so the
   fade actually plays while the map is the thing you are looking at. The
   slide does most of the perceptual work — opacity alone reads as nothing. */
.locations-map {
  position: relative;
  margin-top: 56px;
  opacity: var(--map, 1);
  transform: translateY(calc((1 - var(--map, 1)) * 60px));
  will-change: transform, opacity;
}

/* Keeps the map from floating on flat black */
.locations-map::before {
  content: "";
  position: absolute;
  inset: -14% 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(58% 66% at 50% 46%, rgba(var(--accent-bright-rgb), 0.09), transparent 72%);
  animation: rsAura 13s ease-in-out infinite alternate;
}

@keyframes rsAura {
  from { opacity: 0.55; }
  to   { opacity: 1; }
}

.rs-svg { width: 100%; height: auto; display: block; overflow: visible; }

/* Graticule: structure behind the coastlines, so the map reads as an
   instrument rather than a sticker. Hairlines only. */
.rs-grid line {
  stroke: rgba(255, 255, 255, 0.05);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.rs-grid .rs-equator { stroke: rgba(var(--accent-bright-rgb), 0.13); }

/* non-scaling-stroke keeps hairlines crisp at every container width. The
   coastline is detailed now, so a finer line than the old hand-drawn one
   keeps it clean rather than heavy. */
.rs-lands path {
  fill: url(#rsLandFill);
  stroke: rgba(var(--accent-bright-rgb), 0.5);
  stroke-width: 0.75;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Lifts the coastline off the grid behind it */
.rs-lands { filter: drop-shadow(0 0 5px rgba(var(--accent-bright-rgb), 0.18)); }

/* ---- city pins: HTML over the SVG, so labels keep a fixed size ---- */

/* No reveal of their own: they sit inside .locations-map, so its opacity and
   slide already carry them, and all three arrive with the map. */
.rs-pin {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 36px;
  height: 36px;               /* generous hit area around a small dot */
  margin: -18px 0 0 -18px;
  display: grid;
  place-items: center;
  cursor: pointer;
}

.rs-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent-bright);
  box-shadow: 0 0 0 3px rgba(var(--accent-bright-rgb), 0.16), 0 0 14px rgba(var(--accent-bright-rgb), 0.75);
  transition: transform 0.25s ease;
}

.rs-pin:hover .rs-dot,
.rs-pin:focus-visible .rs-dot { transform: scale(1.45); }

.rs-ring {
  position: absolute;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid var(--accent-bright);
  animation: rsPulse 2.8s ease-out infinite;
}

.rs-pin:nth-of-type(2) .rs-ring { animation-delay: 0.9s; }
.rs-pin:nth-of-type(3) .rs-ring { animation-delay: 1.8s; }

@keyframes rsPulse {
  0%   { transform: scale(1);   opacity: 0.6; }
  70%  { transform: scale(3.2); opacity: 0; }
  100% { transform: scale(3.2); opacity: 0; }
}

/* Hover card: city name plus what that site works on. A rounded rect rather
   than a pill, since it now carries two lines. */
.rs-name {
  position: absolute;
  bottom: calc(100% - 4px);
  left: 50%;
  white-space: nowrap;
  padding: 9px 15px 11px;
  border-radius: 13px;
  background: rgba(8, 8, 10, 0.96);
  border: 1px solid rgba(var(--accent-bright-rgb), 0.42);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 0.86rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: left;
  opacity: 0;
  transform: translate(-50%, 6px);
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}

.rs-name .rs-role {
  display: block;
  margin-top: 3px;
  color: rgba(255, 255, 255, 0.58);
  font-size: 0.79rem;
  font-weight: 400;
  letter-spacing: 0;
}

.rs-pin:hover .rs-name,
.rs-pin:focus-visible .rs-name { opacity: 1; transform: translate(-50%, 0); }

/* Lift the hovered pin above its neighbours so a card is never clipped by them */
.rs-pin:hover,
.rs-pin:focus-visible { z-index: 2; }

.rs-pin:focus-visible { outline: none; }

@media (max-width: 900px) {
  .locations { padding: var(--section-pad-sm, 76px) 0; }
  .locations-head { opacity: 1; translate: none; }
  .locations-map { opacity: 1; transform: none; }
  .rs-name { font-size: 0.76rem; padding: 5px 10px; }
}

@media (prefers-reduced-motion: reduce) {
  .locations-head, .locations-map { opacity: 1; transform: none; translate: none; }
  .rs-ring { animation: none; opacity: 0; }
  .locations-map::before { animation: none; }
}

/* ============================================================
   Private page
   ============================================================ */

.private {
  padding: calc(var(--header-h) + 90px) 0 110px;
  background: #08080a;
  color: #fff;
  min-height: 100vh;
}

.private-title { font-size: clamp(2rem, 3.6vw, 3rem); }

.private-lead {
  max-width: 620px;
  margin-top: 18px;
  color: rgba(255, 255, 255, 0.66);
  font-size: 1.02rem;
}

.private-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px;
  margin-top: 54px;
}

.private-card {
  padding: 30px 28px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.035);
}

.private-card h2 { font-size: 1.2rem; font-weight: 600; }
.private-card p { margin-top: 8px; color: rgba(255, 255, 255, 0.58); font-size: 0.95rem; }

/* Present for screen readers, invisible on screen */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---- The Forge portal ----
   A ring of plasma around a dark core fills the screen, and the way in to
   each section sits inside that core. The clip (plasma_spin_2.mp4) is
   1952x1060; its core is a disc 70% of the clip's height across, centred
   16px above and 6px left of the frame's middle. Sizing the video from the
   core's diameter, and nudging it by that offset, keeps the core centred on
   screen with the buttons inside it at every size. Measured from the clip —
   a replacement video needs these numbers re-measuring. */
.forge-stage {
  --core: min(58vh, 84vw, 600px);   /* the core's diameter on screen */
  position: relative;
  display: grid;
  place-items: center;
  height: 100vh;
  height: 100svh;
  min-height: 520px;
  overflow: hidden;
  background: #08080a;
  color: #fff;
}

.forge-video {
  position: absolute;
  left: 50%;
  top: 50%;
  height: calc(var(--core) / 0.70);
  aspect-ratio: 1952 / 1060;
  /* -50% centres the frame; the extra puts the core, not the frame, in the
     middle (6px of 1952 across, 16.5px of 1060 down) */
  translate: -49.67% -48.44%;
  /* The clip is smaller than the screen, and its brown haze runs right to
     its edges, so it is dissolved into the page's black with an oval fade
     centred on the core (49.67% 48.44% is the core's centre in the frame).
     Up to 72% of the way out — just past the ring — it is fully opaque, so
     the rim keeps all its brightness; beyond that it falls away, tall enough
     to clear the glow above and below, wide enough to let the streaks trail
     off to the sides, and gone before any edge of the frame is reached. */
  --fade: radial-gradient(
    calc(var(--core) * 1.25) calc(var(--core) * 0.68) at 49.67% 48.44%,
    #000 72%, rgba(0, 0, 0, 0.45) 87%, transparent 100%);
  -webkit-mask-image: var(--fade);
          mask-image: var(--fade);
  /* knocked back so it reads as a backdrop rather than competing with the
     buttons. On the container, not the videos: the loop's dissolve stacks
     two clips, and dimming each would let the one behind show through. */
  opacity: 0.85;
  animation: forge-fade-in 1.4s ease both;
}

.forge-video video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 1;
}
.forge-video video.is-front { z-index: 2; }

/* 1.2s must match XFADE in video-loop.js */
.forge-video video.is-fading {
  opacity: 0;
  transition: opacity 1.2s linear;
}

.forge-core {
  position: relative;
  z-index: 3;
  animation: forge-core-in 1s cubic-bezier(0.22, 1, 0.36, 1) 0.35s both;
}

/* The two doors share one disc set just inside the core, split across by a
   straight line: HTS above, NT Fusion below, so together they echo the ring
   around them. Each is a full-width band cut to the disc's outline, and the
   split is simply where one band ends and the other begins (--split, as a
   fraction of the diameter from the top). Hovering a door slides the split
   away from it — a straight edge moving, not a wedge pivoting — until that
   door covers 55% of the disc. Quiet at rest — frosted glass and a fine
   line — and warmed with the brand colour on hover. */
.forge-links {
  --d: calc(var(--core) * 0.8);
  --gap: 12px;
  --split: 0.5;
  display: flex;
  flex-direction: column;
  width: var(--d);
  height: var(--d);
}

/* 0.5393 / 0.4607: where a straight cut leaves 55% of a disc's area on one
   side (the chord sits 0.079 radii off centre; it moves less than the share
   does because the disc is widest in the middle) */
.forge-links:has(.forge-link:first-child:is(:hover, :focus-visible)) { --split: 0.5393; }
.forge-links:has(.forge-link:last-child:is(:hover, :focus-visible))  { --split: 0.4607; }

.forge-link {
  position: relative;
  width: var(--d);
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: calc(var(--d) * 0.1);
  color: #fff;
  /* The visible gap between the doors is a transparent border on each, so
     the pointer never falls into a dead strip between them while the split
     is on the move — the backgrounds stop at the padding edge */
  border: 0 solid transparent;
  background-clip: padding-box;
  /* Glass, kept quiet: a faint frost over a blurred view of what is behind,
     and the slightest catch of light along the top edge. No outline. */
  background-color: rgba(255, 255, 255, 0.04);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  /* The same lettering as the header's nav links (.nav-link): the site's
     font at medium weight, spaced capitals — just larger */
  font-family: inherit;
  font-weight: 500;
  font-size: clamp(1.05rem, calc(var(--core) * 0.05), 1.6rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.35;
  text-align: center;
  transition: height 0.55s cubic-bezier(0.22, 1, 0.36, 1),
              background-color 0.35s ease, box-shadow 0.35s ease;
}

/* tracking trails the last letter too; pulled back so the name is truly
   centred */
.forge-link-name { position: relative; margin-right: -0.16em; }

/* Each band is cut to the same circle, centred on the disc: the top door's
   from its own top edge, the bottom door's from its own bottom edge. As the
   split moves, the curved edges therefore stay exactly where they are and
   only the flat edge travels. The bottom door takes whatever height the top
   one leaves. A segment's visual centre sits nearer its flat edge than its
   box's middle does, so each name is nudged that way. */
.forge-link:first-child {
  height: calc(var(--d) * var(--split));
  border-bottom-width: calc(var(--gap) / 2);
  clip-path: circle(calc(var(--d) / 2) at 50% calc(var(--d) / 2));
  padding-block: calc(var(--d) * 0.08) calc(var(--d) * 0.02);
}

.forge-link:last-child {
  flex: 1 1 0;
  border-top-width: calc(var(--gap) / 2);
  clip-path: circle(calc(var(--d) / 2) at 50% calc(100% - var(--d) / 2));
  padding-block: calc(var(--d) * 0.02) calc(var(--d) * 0.08);
}

.forge-link:hover,
.forge-link:focus-visible {
  background-color: rgba(var(--accent-bright-rgb), 0.3);
  box-shadow: inset 0 1px 0 rgba(255, 220, 190, 0.18);
  outline: none;
}

/* no `to`: it fades up to whatever opacity the element itself has */
@keyframes forge-fade-in { from { opacity: 0; } }

@keyframes forge-core-in {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: none; }
}

/* Back to the portal from a section page */
.forge-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 22px;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  transition: color 0.2s ease;
}

.forge-back svg { width: 16px; height: 16px; transition: transform 0.2s ease; }
.forge-back:hover { color: #fff; }
.forge-back:hover svg { transform: translateX(-3px); }

/* ---- Policy pages (/policies/*) ----
   A plain reading column for documents that are read for their exact words:
   numbered sections divided by hairlines, as in the source documents. */
.policy {
  padding: calc(var(--header-h) + 72px) 0 110px;
  background: #08080a;
  color: #fff;
}

.policy-col { width: min(760px, 100% - 48px); margin-inline: auto; }

.policy-title {
  font-size: clamp(2.2rem, 5vw, 3.4rem);
  line-height: 1.08;
  text-wrap: balance;
}

.policy-section {
  margin-top: 44px;
  padding-top: 28px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.policy-section h2 { font-size: 1.35rem; font-weight: 600; line-height: 1.3; }

.policy-num { margin-right: 0.35em; color: var(--accent-bright); }

.policy-section > * + * { margin-top: 0.9em; }

.policy-section h3 { margin-top: 1.4em; font-size: 1.02rem; font-weight: 600; }

.policy-section p,
.policy-section li {
  font-size: 1.02rem;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.76);
}

.policy-section ul { padding-left: 1.4em; list-style: disc; }
.policy-section ul ul { margin-top: 0.35em; list-style: circle; }
.policy-section li + li { margin-top: 0.35em; }
.policy-section li::marker { color: var(--accent-bright); }

.policy-section strong { color: #fff; font-weight: 600; }

.policy-section a {
  color: var(--accent-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Their footer: the notice and the three policies, one row */
.policy-footer { padding: 36px 0; }

.policy-footer-inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 14px 40px;
}

@media (max-width: 640px) {
  .policy { padding-top: calc(var(--header-h) + 48px); }
  .policy-footer .footer-links { justify-content: flex-start; }
}

/* ---- Private articles (/private/fusion) ----
   A single reading column, with the larger charts breaking out wider. Charts
   drawn on white sit on a white plate with breathing room, so their edges
   do not butt against rounded corners; photos and renders on dark
   backgrounds sit straight on the page. */
.article {
  --wide: min(1000px, 100vw - 48px);
  padding: calc(var(--header-h) + 72px) 0 130px;
  background: #08080a;
  color: #fff;
}

.article-col { width: min(720px, 100% - 48px); margin-inline: auto; }

.article-title {
  margin-top: 4px;
  font-size: clamp(2.6rem, 6vw, 4rem);
  line-height: 1.04;
}

.article-num {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--accent-bright);
}

.article-toc {
  margin-top: 44px;
  list-style: none;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.article-toc a {
  display: flex;
  align-items: baseline;
  gap: 20px;
  padding: 15px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.78);
  font-size: 1.04rem;
  transition: color 0.2s ease, padding-left 0.25s ease;
}

.article-toc a:hover,
.article-toc a:focus-visible { color: #fff; padding-left: 6px; }

.article-section { margin-top: 120px; }

.article-section-head {
  margin-bottom: 36px;
  padding-top: 28px;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}

.article-section h2 {
  margin-top: 12px;
  font-size: clamp(1.9rem, 3.6vw, 2.7rem);
  line-height: 1.12;
  text-wrap: balance;
}

.article-sub {
  margin-top: 14px;
  font-size: 1.15rem;
  color: rgba(255, 255, 255, 0.58);
}

.article-section p,
.article-list li {
  font-size: 1.04rem;
  line-height: 1.75;
  color: rgba(255, 255, 255, 0.76);
}

.article-section p + p { margin-top: 1.1em; }

.article-section h3 {
  margin: 2.3em 0 0.6em;
  font-size: 1.22rem;
  font-weight: 600;
  color: #fff;
}

.article-section a:not(.forge-back) {
  color: var(--accent-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.article-section em { color: #fff; }

/* the one-line paragraph that the Anvil 0 story turns on */
.article-section .article-beat {
  margin: 1.2em 0;
  font-size: 1.4rem;
  font-weight: 600;
  color: #fff;
}

.article-list { list-style: none; display: grid; gap: 0.75em; }

.article-list li { position: relative; padding-left: 30px; }

.article-list li::before {
  content: "→";
  position: absolute;
  left: 0;
  color: var(--accent-bright);
}

/* a numbered list, from the Forge editor: the number takes the arrow's place */
ol.article-list { counter-reset: item; }

ol.article-list li::before {
  content: counter(item, decimal-leading-zero);
  counter-increment: item;
  top: 0.4em;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
}

/* a list inside running text keeps the paragraph rhythm either side */
.article-section p + .article-list { margin-top: 0.9em; }
.article-section .article-list + p { margin-top: 1.1em; }

/* Tables: the column heads keep the colours of the source document (the
   brand orange for Line 0, a soft violet for Line 1), toned for a dark page.
   Wide tables scroll sideways on a phone rather than squeezing. */
.article-table-wrap {
  margin: 44px 0;
  overflow-x: auto;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 12px;
}

.article-table {
  width: 100%;
  min-width: 440px;
  border-collapse: collapse;
  font-size: 0.98rem;
  line-height: 1.45;
  text-align: left;
}

.article-table th,
.article-table td {
  padding: 15px 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  vertical-align: top;
}

.article-table tbody tr:last-child > * { border-bottom: 0; }

.article-table thead th { font-weight: 600; color: #fff; }
.article-table thead .is-line0 { background: rgba(var(--accent-bright-rgb), 0.3); }
.article-table thead .is-line1 { background: rgba(150, 128, 205, 0.28); }

.article-table tbody th { font-weight: 500; color: rgba(255, 255, 255, 0.58); }
.article-table td { color: rgba(255, 255, 255, 0.86); }

/* figures */
.article-fig { margin: 44px auto; }

.article-fig img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
}

.article-fig--plate {
  padding: 14px;
  background: #fff;
  border-radius: 14px;
}

.article-fig--plate img { border-radius: 4px; }

.article-fig figcaption {
  margin-top: 12px;
  font-size: 0.86rem;
  text-align: center;
  color: rgba(255, 255, 255, 0.5);
}

.article-fig--plate figcaption { color: rgba(0, 0, 0, 0.55); margin-top: 8px; }

/* small source images are kept near their real size so they stay sharp */
.article-fig--narrow { max-width: 460px; }
.article-fig--small { max-width: 340px; }

/* Out of the reading column, still centred on it. Percentage margins are
   measured against the column, so 100% here is the column's width. */
.article-fig--wide {
  width: var(--wide);
  margin-left: calc((100% - var(--wide)) / 2);
  margin-right: calc((100% - var(--wide)) / 2);
}

/* Side by side at a shared height: each column is sized by its picture's
   aspect ratio, given inline as --ratio */
.article-pair,
.article-gallery {
  display: flex;
  gap: 16px;
  margin-block: 44px;
}

.article-pair > .article-fig,
.article-gallery > .article-fig {
  flex: var(--ratio) 1 0;
  min-width: 0;
  margin: 0;
}

@media (max-width: 640px) {
  .article { padding-top: calc(var(--header-h) + 48px); }
  .article-section { margin-top: 90px; }
  .article-pair,
  .article-gallery { flex-direction: column; }
}

@media (prefers-reduced-motion: reduce) {
  .article-toc a { transition: none; }
}

@media (max-width: 900px) {
  .private { padding: calc(var(--header-h) + 56px) 0 76px; }
  .private-grid { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .forge-video,
  .forge-core { animation: none; }
  .forge-link { transition: none; }
}

/* ============================================================
   Footer
   ============================================================ */

/* Same black as the header and the dark sections, so the page reads as one
   surface and the overscroll gutter below it matches exactly. The hairline
   separates it from the equally black section above, as between the others. */
.site-footer {
  background: #08080a;
  color: rgba(255,255,255,0.85);
  padding: 80px 0 40px;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}

/* The brand block takes the room on the left; contact and careers sit as a
   pair on the right, close together, everything top-aligned so the logo
   and the two headings start on one line. The small print runs underneath
   (.footer-bottom). */
.footer-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 40px 64px;
  align-items: start;
}

.footer-cols {
  display: flex;
  align-items: flex-start;
  gap: 48px;
}

/* Each column is as wide as its heading, with its buttons centred under it */
.footer-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.logo-light { color: #fff; }

/* The logo with the company details beside it, their tops level */
.footer-brand {
  display: flex;
  align-items: flex-start;
  gap: 32px;
}

.footer-brand .logo { flex: none; }

.footer-brand p { color: rgba(255,255,255,0.6); }


/* A column's heading: small spaced capitals, as the labels in the mission */
.footer-heading {
  margin-bottom: 16px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.5);
}

.footer-social { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
/* A link (LinkedIn) and a button (contact, which opens the form) styled as
   one matching pair */
/* In the accent, white on orange, so they read as the footer's actions */
.footer-social a,
.footer-social button {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  transition: background 0.2s ease;
  border: none;                    /* button reset */
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.footer-social a:hover,
.footer-social button:hover,
.footer-social a:focus-visible,
.footer-social button:focus-visible { background: #b85c2b; }
.footer-social svg { width: 18px; height: 18px; }

/* Careers: the same chip as the icons, stretched to fit a word */
.footer-social a.footer-pill {
  width: auto;
  padding: 0 18px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
}

/* The small print: the notice on the left, the policy link on the right,
   under a hairline across the whole footer */
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 10px 24px;
  margin-top: 56px;
  padding-top: 24px;
  border-top: 1px solid rgba(255,255,255,0.08);
}

/* Policy links, in the same quiet type as the notice beside them */
.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 22px;
  font-size: 0.85rem;
}

.footer-links a {
  color: rgba(255,255,255,0.6);
  transition: color 0.2s ease;
}

.footer-links a:hover,
.footer-links a:focus-visible,
.footer-links a[aria-current="page"] { color: #fff; }

/* Company details, under the logo in the brand column: the quietest
   type in the footer, and a measure narrow enough to read as a block of
   small print rather than a line running under the whole footer */
.footer-brand .footer-legal {
  max-width: 460px;
  margin-top: -2px;           /* first line's cap height level with the logo's top */
  font-size: 0.8rem;
  line-height: 1.6;
  color: rgba(255,255,255,0.45);
}

/* The Forge pages' footer: just the notice, on one line, in the same quiet
   type as the main footer's meta text */
.footer-note {
  font-size: 0.85rem;
  color: rgba(255,255,255,0.55);
}

.footer-meta {
  font-size: 0.85rem;
  color: rgba(255,255,255,0.55);
}

/* ============================================================
   Responsive
   ============================================================ */

/* Closer together on smaller laptops, so all six still fit on one line
   down to the point where they step aside */
@media (max-width: 1150px) {
  .nav-list { gap: 22px; }
}

@media (max-width: 1060px) {
  /* Six links do not fit beside the logo and the Forge button on a narrow
     screen (below about 1060px the Forge button eats into the edge padding), and
     there is no menu to fold them into — the page is one scroll, so they
     step aside rather than wrap or crowd. */
  .main-nav { display: none; }
}

/* Footer below a wide desktop: three columns squeeze the company details
   into a tall sliver, so the logo and details go across the top with
   contact and careers side by side under them; on phones, one column, and
   the small print stacks with the notice first */
@media (max-width: 1200px) {
  .footer-inner { grid-template-columns: 1fr; }
  .footer-brand .footer-legal { max-width: 560px; }
}

@media (max-width: 560px) {
  .footer-brand { flex-direction: column; gap: 18px; }
  .footer-brand .footer-legal { margin-top: 0; }
  .footer-inner { gap: 36px; }
  /* one above the other, each still centred on its own heading */
  .footer-cols { flex-direction: column; gap: 30px; }
  .footer-bottom { flex-direction: column; align-items: flex-start; margin-top: 44px; }
}

/* ---- Contact pop-up: the news dialog's frame, the login form's fields ---- */
.contact-dialog {
  width: min(520px, 100% - 32px);
  max-height: min(90vh, 900px);
  margin: auto;
  padding: 0;
  overflow: auto;
  color: #fff;
  background: #0c0d11;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius-lg);
  box-shadow: 0 40px 90px rgba(0, 0, 0, 0.7);
}

.contact-dialog::backdrop {
  background: rgba(4, 5, 7, 0.76);
  backdrop-filter: blur(6px);
}

.contact-dialog[open] { animation: newsOpen 0.32s cubic-bezier(0.16, 1, 0.3, 1) both; }

.contact-form,
.contact-done { padding: 34px 32px 28px; }

.contact-title { font-size: 1.5rem; padding-right: 48px; }   /* clear of the close button */
.contact-intro { margin-top: 8px; color: rgba(255, 255, 255, 0.65); }
.contact-done .gate-actions { margin-top: 26px; }

.contact-form .gate-label { margin-top: 20px; }
.contact-message { resize: vertical; min-height: 120px; line-height: 1.5; }

.contact-trap {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.contact-form button[disabled] { opacity: 0.6; cursor: progress; }

@media (prefers-reduced-motion: reduce) {
  .contact-dialog[open] { animation: none; }
}

/* ---- Exit: content lifts and fades as it leaves the top (script.js) ---- */
[data-exit] {
  opacity: calc(1 - var(--exit, 0));
  translate: 0 calc(var(--exit, 0) * -48px);
}

/* The track already fades in on --reveal; the exit multiplies with it */
.news-track[data-exit] { opacity: calc(var(--reveal) * (1 - var(--exit, 0))); }

@media (prefers-reduced-motion: reduce) {
  [data-exit] { opacity: 1; translate: none; }
  .news-track[data-exit] { opacity: var(--reveal); }
}

/* ============================================================
   Careers (/careers)

   The list of open roles, and a dialog for each holding its
   posting and its application form. The markup is rendered by
   the Worker (src/careers-html.js) and driven by
   public/js/careers.js. The dialog is the news story's frame
   and the form uses the login and contact forms' fields, so the
   page reads as part of the same site.

   Everything here is scoped under .careers, the page's <main>.
   admin.html loads this stylesheet too, and its jobs editor has
   job-shaped class names of its own; scoping keeps these rules
   from reaching into it.
   ============================================================ */

.careers {
  position: relative;
  min-height: 70vh;
  padding: calc(var(--header-h) + 72px) 0 120px;
  background: #08080a;
  color: #fff;
}

/* A low warm light behind the heading, as the other sections have behind
   their artwork, so the top of the page is not flat black */
.careers::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 560px;
  background: radial-gradient(60% 70% at 15% 0%, rgba(var(--accent-bright-rgb), 0.16), transparent 70%);
  pointer-events: none;
}

/* The hidden attribute has to win over the display values given below:
   the success panel, the chosen-file row and the error slots are all
   toggled with it. */
.careers [hidden] { display: none !important; }

.careers-col {
  position: relative;
  width: min(960px, 100% - 48px);
  margin-inline: auto;
}

.careers-title {
  font-size: clamp(2.4rem, 6vw, 4rem);
  line-height: 1.04;
  text-wrap: balance;
}

.careers-lead {
  max-width: 560px;
  margin-top: 18px;
  font-size: clamp(1.08rem, 1.6vw, 1.25rem);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.68);
}

/* A notice above the list: a link to a role that has since closed, or how
   an application sent from a closed dialog went. The <noscript> note shares
   the look. */
.careers .jobs-notice,
.careers-noscript {
  margin-top: 36px;
  padding: 14px 18px;
  border: 1px solid rgba(var(--accent-bright-rgb), 0.5);
  border-radius: 14px;
  background: rgba(var(--accent-bright-rgb), 0.1);
  font-size: 0.95rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.88);
}

.careers .jobs-notice:empty {
  margin: 0;
  padding: 0;
  border: 0;
}

.careers-noscript a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.careers-roles { margin-top: 64px; }

/* Small spaced capitals ruled off to the right, like a section label in
   the source documents the policy pages are set from */
.careers-roles-title {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-bottom: 20px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.careers-roles-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.12);
}

.careers .jobs-list {
  display: grid;
  gap: 14px;
  list-style: none;
}

/* ---------- a role in the list ---------- */

.careers .job-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 28px;
  padding: 28px clamp(22px, 3.4vw, 36px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.035);
  color: #fff;
  transition: border-color 0.25s ease, background 0.25s ease, box-shadow 0.25s ease;
}

.careers .job-card:hover,
.careers .job-card:focus-visible {
  border-color: rgba(var(--accent-bright-rgb), 0.55);
  background: rgba(255, 255, 255, 0.05);
  box-shadow: 0 26px 60px rgba(0, 0, 0, 0.55), 0 0 40px rgba(var(--accent-bright-rgb), 0.09);
}

/* White rather than the accent: the rust is too dark against black to be a
   focus ring anyone can find */
.careers .job-card:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 3px;
}

.careers .job-card-body { display: block; min-width: 0; }

.careers .job-card-title {
  display: block;
  font-size: clamp(1.25rem, 2.1vw, 1.6rem);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

/* team · location · contract · workplace. The dots are drawn here rather
   than written into the markup, so a role without a team or a workplace
   never shows a stray separator.

   Every item carries a dot in front of it, of a fixed width, and the row is
   pulled left by that width and clipped there. The first item's dot is
   cut off; so is the dot of any item that wraps to start a new line — on a
   phone most cards' facts take two lines, and a line opening with "·"
   reads as a mistake. Dots between items on the same line are untouched.
   The 1.3em shift sits inside the card's padding, and nothing in the row
   can take focus, so the clipping hides nothing else. */
.careers .job-card-meta {
  display: flex;
  flex-wrap: wrap;
  margin-top: 8px;
  margin-left: -1.3em;
  clip-path: inset(0 0 0 1.3em);
  font-size: 0.93rem;
  color: rgba(255, 255, 255, 0.58);
}

.careers .job-card-meta > *::before {
  content: "\00B7";
  display: inline-block;
  width: 1.3em;
  text-align: center;
  color: rgba(255, 255, 255, 0.3);
}

.careers .job-card-team { color: var(--accent-bright); font-weight: 700; }

.careers .job-card-summary {
  display: block;
  max-width: 68ch;
  margin-top: 14px;
  font-size: 0.99rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.7);
  text-wrap: pretty;
}

.careers .job-card-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
}

.careers .job-card-foot > span {
  padding: 5px 12px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.03);
  font-size: 0.8rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.74);
}

.careers .job-card-arrow {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 50%;
  color: #fff;
  transition: background 0.25s ease, border-color 0.25s ease;
}

.careers .job-card-arrow svg { width: 19px; height: 19px; transition: translate 0.25s ease; }

.careers .job-card:hover .job-card-arrow,
.careers .job-card:focus-visible .job-card-arrow {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}

.careers .job-card:hover .job-card-arrow svg,
.careers .job-card:focus-visible .job-card-arrow svg { translate: 3px 0; }

/* Nothing open: the same panel, dashed, as a space waiting to be filled */
.careers .jobs-empty {
  padding: clamp(28px, 5vw, 44px) clamp(22px, 4vw, 40px);
  border: 1px dashed rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.02);
}

.careers .jobs-empty-title { font-size: 1.3rem; font-weight: 600; line-height: 1.3; }

.careers .jobs-empty-text {
  max-width: 62ch;
  margin-top: 10px;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.64);
}

.careers .jobs-empty a {
  color: var(--accent-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
  overflow-wrap: anywhere;
  transition: color 0.2s ease;
}

.careers .jobs-empty a:hover { color: #fff; }

/* ---------- the role's dialog ----------

   The news dialog's frame, wider, for a posting and a form one after the
   other. It scrolls inside itself — the page behind is held still — and
   stops short of the viewport's edges so the backdrop frames it. */

.careers .job-dialog {
  width: min(820px, 100% - 32px);
  max-width: 100%;                /* the browser's own cap is narrower */
  max-height: min(92vh, 1120px);
  max-height: min(92dvh, 1120px);
  margin: auto;
  padding: 0;
  overflow: auto;
  /* Scrolling to the end of the dialog stops there, rather than carrying
     on into the page behind it */
  overscroll-behavior: contain;
  color: #fff;
  background: #0c0d11;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius-lg);
  box-shadow: 0 40px 90px rgba(0, 0, 0, 0.7);
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}

.careers .job-dialog::backdrop {
  background: rgba(4, 5, 7, 0.76);
  backdrop-filter: blur(6px);
}

.careers .job-dialog[open] { animation: newsOpen 0.32s cubic-bezier(0.16, 1, 0.3, 1) both; }

/* Keeps the page behind from scrolling under the open role */
body:has(.careers .job-dialog[open]) { overflow: hidden; }

/* The close button rides in a bar of no height that sticks to the top of
   the dialog, so it stays in the corner however far down the form the
   applicant has scrolled, without taking a strip of the dialog for itself.
   .news-close positions itself against the bar. */
.careers .job-dialog-bar {
  position: sticky;
  top: 0;
  z-index: 5;
  height: 0;
}

.careers .job-close:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 2px;
}

.careers .job-back { display: none; }

.careers .job-posting { padding: clamp(36px, 6vw, 56px) clamp(22px, 6vw, 56px) 4px; }

.careers .job-eyebrow {
  margin-bottom: 14px;
  padding-right: 56px;          /* clear of the close button */
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-bright);
}

.careers .job-title {
  padding-right: 52px;          /* clear of the close button */
  font-size: clamp(1.75rem, 4.6vw, 2.8rem);
  line-height: 1.08;
  text-wrap: balance;
}

/* Focused by script when the dialog opens, so that reading starts here; it
   is not a control, so it shows no ring for it. The same goes for the
   dialog itself, which some browsers focus as a whole. */
.careers .job-dialog:focus,
.careers .job-title:focus,
.careers .job-apply-title:focus,
.careers .job-done:focus { outline: none; }

.careers .job-lead {
  max-width: 62ch;
  margin-top: 16px;
  font-size: 1.1rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.72);
  text-wrap: pretty;
}

/* Label above value, in a row that wraps, between two hairlines */
.careers .job-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 18px 40px;
  margin-top: 28px;
  padding: 20px 0;
  border-block: 1px solid rgba(255, 255, 255, 0.1);
}

.careers .job-facts dt {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
}

.careers .job-facts dd {
  margin-top: 4px;
  font-size: 0.98rem;
  font-weight: 500;
  color: #fff;
}

.careers .job-apply-jump { margin-top: 28px; }

.careers .btn-primary:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 3px;
}

/* ---------- the posting's description ---------- */

.careers .job-description { padding-bottom: 8px; }

.careers .job-section { margin-top: 38px; }

.careers .job-section-title {
  font-size: 1.22rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

.careers .job-section-title + * { margin-top: 12px; }

.careers .job-section p,
.careers .job-section li {
  font-size: 1.02rem;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.74);
  text-wrap: pretty;
}

.careers .job-section > * + * { margin-top: 14px; }

.careers .job-list { padding-left: 1.3em; list-style: disc; }
.careers .job-list li + li { margin-top: 8px; }
.careers .job-list li::marker { color: var(--accent-bright); }

.careers .job-section a {
  color: var(--accent-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
  /* a long address must not push the dialog wider than the screen */
  overflow-wrap: anywhere;
  transition: color 0.2s ease;
}

.careers .job-section a:hover { color: #fff; }
.careers .job-section strong { font-weight: 600; color: rgba(255, 255, 255, 0.95); }
.careers .job-section em { font-style: italic; }

/* As in a story: underline is set apart from the link style */
.careers .job-section u {
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* ---------- the application ----------

   A panel inset in the dialog, a shade lighter, so the form reads as the
   thing to do once the posting has been read. */

.careers .job-apply {
  margin: 40px clamp(12px, 3vw, 24px) clamp(12px, 3vw, 24px);
  padding: clamp(28px, 5vw, 44px) clamp(20px, 5vw, 40px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.025);
  scroll-margin-top: 12px;
}

.careers .job-apply-title { font-size: clamp(1.45rem, 3vw, 1.8rem); }

.careers .job-apply-intro {
  max-width: 62ch;
  margin-top: 12px;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.68);
}

.careers .job-apply-intro + .job-apply-intro { margin-top: 10px; }

/* Shown in place of the form until careers.js has run, which without
   script is for good */
.careers .job-nojs {
  display: none;
  margin-top: 16px;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.72);
}

.careers .job-nojs a {
  color: var(--accent-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.careers .job-required-note {
  margin-top: 14px;
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.5);
}

/* An asterisk in the accent, after the question */
.careers .job-req {
  margin-left: 4px;
  color: var(--accent-bright);
  font-weight: 700;
}

.careers .job-required-note .job-req { margin: 0 2px 0 0; }

/* fieldset reset: a choice question is a fieldset, and must look like the rest */
.careers .job-field {
  min-width: 0;
  margin: 28px 0 0;
  padding: 0;
  border: 0;
}

/* The login form's label, set in sentence case: questions here can run to
   a full sentence, and a sentence in spaced capitals is hard work to read */
.careers .job-label {
  margin: 0 0 9px;
  padding: 0;
  font-size: 0.97rem;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(255, 255, 255, 0.92);
}

.careers .job-help {
  margin: -3px 0 10px;
  font-size: 0.88rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.55);
}

.careers .job-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-bright-rgb), 0.32);
}

.careers .job-field.is-invalid .job-input,
.careers .job-input[aria-invalid="true"] { border-color: #ff8a8a; }

.careers .job-textarea {
  min-height: 132px;
  line-height: 1.55;
  resize: vertical;
}

/* A drawn chevron in place of the browser's own, which ignores the dark
   field it sits in */
.careers .job-select {
  padding-right: 46px;
  appearance: none;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.7)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 16px;
}

.careers .job-select option { color: #fff; background: #0c0d11; }

/* An empty date field collapses in Safari, and the spinners on a number
   field are too small to hit and easy to nudge by accident */
.careers .job-date { min-height: 50px; }
.careers .job-date::-webkit-calendar-picker-indicator { cursor: pointer; opacity: 0.7; }
.careers .job-input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.careers .job-input[type="number"]::-webkit-inner-spin-button,
.careers .job-input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

.careers .job-count {
  margin-top: 6px;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
  /* 0.5 rather than anything fainter: small type on the form's panel needs
     it to reach 4.5:1 */
  color: rgba(255, 255, 255, 0.5);
}

.careers .job-count.is-near { color: #f0b37e; }

.careers .job-field-error {
  margin-top: 8px;
  font-size: 0.88rem;
  line-height: 1.45;
  color: #ff8a8a;
}

/* ---------- choices: radio, checkboxes, yes / no, consent ---------- */

.careers .job-options { display: grid; gap: 8px; }

.careers .job-options--inline { grid-template-columns: repeat(2, minmax(0, 150px)); }

.careers .job-option {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 16px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  font-size: 0.98rem;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.86);
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}

.careers .job-option:hover { border-color: rgba(255, 255, 255, 0.32); }

.careers .job-option:has(input:checked) {
  border-color: rgba(var(--accent-bright-rgb), 0.75);
  background: rgba(var(--accent-bright-rgb), 0.12);
  color: #fff;
}

.careers .job-option:has(input:focus-visible) {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 2px;
}

.careers .job-field.is-invalid .job-option { border-color: rgba(255, 138, 138, 0.6); }

/* Drawn rather than native, so they match each other and the dark panel in
   every browser. The checked states are backgrounds — a gradient dot, an
   inline tick — so there is no pseudo-element on an input to rely on. */
.careers .job-option input {
  flex: none;
  width: 18px;
  height: 18px;
  margin: 2px 0 0;
  appearance: none;
  -webkit-appearance: none;
  border: 1.5px solid rgba(255, 255, 255, 0.45);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
}

.careers .job-option input[type="checkbox"] { border-radius: 5px; }

.careers .job-option input:checked { border-color: var(--accent); }

.careers .job-option input[type="radio"]:checked {
  background: radial-gradient(circle, #fff 0 3.5px, var(--accent) 4px);
}

.careers .job-option input[type="checkbox"]:checked {
  background: var(--accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E") center / 12px no-repeat;
}

.careers .job-option input:focus { outline: none; }

/* The consent box sits apart from the questions, above the button */
.careers .job-consent {
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.careers .job-option--consent { font-size: 0.93rem; color: rgba(255, 255, 255, 0.78); }

.careers .job-option--consent a {
  color: #fff;
  text-decoration: underline;
  text-decoration-color: rgba(var(--accent-bright-rgb), 0.9);
  text-underline-offset: 3px;
}

/* ---------- uploads ----------

   The native file input is laid transparent over the whole zone: it is
   what is clicked, focused and read out, and the zone around it is only
   its look. The remove button and the file's name sit above it, so they
   can be reached; anywhere else on the zone picks a different file. */

.careers .job-drop {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 132px;
  padding: 22px 20px;
  border: 1.5px dashed rgba(255, 255, 255, 0.24);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.03);
  text-align: center;
  transition: border-color 0.2s ease, background 0.2s ease;
}

.careers .job-drop:hover,
.careers .job-drop.is-over {
  border-color: rgba(var(--accent-bright-rgb), 0.8);
  background: rgba(var(--accent-bright-rgb), 0.08);
}

.careers .job-drop:has(.job-file-input:focus-visible) {
  border-color: var(--accent);
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 2px;
}

.careers .job-field.is-invalid .job-drop { border-color: #ff8a8a; }

.careers .job-file-input {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.careers .job-drop-prompt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.careers .job-drop-icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: rgba(var(--accent-bright-rgb), 0.16);
  color: #f0b37e;
}

.careers .job-drop-icon svg { width: 20px; height: 20px; }

.careers .job-drop-text { font-size: 0.95rem; color: rgba(255, 255, 255, 0.72); }

.careers .job-drop-action {
  font-weight: 600;
  color: #fff;
  text-decoration: underline;
  text-decoration-color: rgba(var(--accent-bright-rgb), 0.9);
  text-underline-offset: 3px;
}

/* As .job-count: 0.5 is the faintest that reads at 4.5:1 on the zone */
.careers .job-drop-hint { font-size: 0.8rem; color: rgba(255, 255, 255, 0.5); }

/* With a file attached the zone settles into a solid row: the file, its
   size, and the way to take it off again */
.careers .job-drop.has-file {
  align-items: stretch;
  min-height: 0;
  padding: 14px 14px 14px 16px;
  border-style: solid;
  border-color: rgba(255, 255, 255, 0.18);
  text-align: left;
}

.careers .job-drop.has-file .job-drop-hint { display: none; }

/* Icon, then the name over its size, then the button — the name taking
   whatever width is left, so a long one shortens rather than wraps */
.careers .job-drop-file {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "icon name remove"
    "icon size remove";
  align-items: center;
  column-gap: 14px;
  min-width: 0;
}

.careers .job-drop-file .job-drop-icon { grid-area: icon; }

.careers .job-drop-file .job-drop-icon { width: 38px; height: 38px; }

/* Above the input, so the whole of a long name can be read in its tooltip */
.careers .job-drop-name {
  grid-area: name;
  align-self: end;
  position: relative;
  z-index: 2;
  display: flex;
  min-width: 0;
  white-space: nowrap;
  font-weight: 600;
  cursor: default;
}

/* The name gives way before the extension does (see careers.js) */
.careers .job-drop-base {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.careers .job-drop-ext { flex: none; }

.careers .job-drop-size {
  grid-area: size;
  align-self: start;
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, 0.5);
}

.careers .job-drop-remove {
  grid-area: remove;
  position: relative;
  z-index: 2;
  padding: 7px 14px;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.82);
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.careers .job-drop-remove:hover { color: #fff; background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.4); }

.careers .job-drop-remove:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 2px;
}

/* ---------- a heading inside the form ---------- */

.careers .job-form-section {
  margin-top: 40px;
  padding-top: 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.careers .job-form-section-title { font-size: 1.15rem; font-weight: 600; line-height: 1.3; }

.careers .job-form-section .job-help { margin: 8px 0 0; font-size: 0.93rem; }

/* ---------- sending ---------- */

.careers .job-form-error {
  min-height: 0;
  margin-top: 22px;
  line-height: 1.5;
}

.careers .job-form-error:empty { margin-top: 0; }

.careers .job-form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 14px 18px;
  margin-top: 26px;
}

.careers .job-form-status {
  flex: 1 1 200px;
  font-size: 0.88rem;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.6);
}

.careers .job-form-status:empty { display: none; }

.careers .job-submit[aria-disabled="true"] { opacity: 0.75; cursor: progress; }

/* A small ring that turns while the application is on its way */
.careers .job-form.is-busy .job-submit::before {
  content: "";
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-right: 10px;
  vertical-align: -2px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: jobSpin 0.8s linear infinite;
}

@keyframes jobSpin { to { rotate: 360deg; } }

/* ---------- sent ---------- */

.careers .job-done { padding: 16px 0 4px; text-align: center; }

.careers .job-done-icon {
  display: grid;
  place-items: center;
  width: 60px;
  height: 60px;
  margin: 0 auto 20px;
  border: 1px solid rgba(var(--accent-bright-rgb), 0.6);
  border-radius: 50%;
  background: rgba(var(--accent-bright-rgb), 0.16);
  color: #f0b37e;
}

.careers .job-done-icon svg { width: 26px; height: 26px; }

.careers .job-done-title { font-size: clamp(1.45rem, 3vw, 1.8rem); }

.careers .job-done-text {
  max-width: 46ch;
  margin: 12px auto 0;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.72);
}

.careers .job-done-text strong { font-weight: 600; color: #fff; }

.careers .job-done .gate-actions { justify-content: center; margin-top: 28px; }

/* Once sent there is nothing left to apply for; the thank-you speaks for
   itself */
.careers .job-dialog.is-sent .job-apply-jump,
.careers .job-dialog.is-sent .job-apply-title,
.careers .job-dialog.is-sent .job-apply-intro { display: none; }

/* ---------- without script, or without <dialog> ----------

   Until careers.js has run — which without script is never — the forms
   stay hidden and an email address stands in for them, since there is
   nothing to send them. A role's posting is then shown in the page when
   its link is followed: careers.html's <noscript> style does that without
   script, and .careers-nomodal (set by careers.js) in a browser too old
   for modal dialogs. */

html:not(.careers-ready) .careers .job-form,
html:not(.careers-ready) .careers .job-apply-jump { display: none; }

html:not(.careers-ready) .careers .job-nojs { display: block; }

html:not(.careers-modal) .careers .job-dialog {
  position: static;
  width: min(820px, 100% - 32px);
  height: auto;
  max-height: none;
  margin: 56px auto 0;
  overflow: visible;
  box-shadow: none;
  animation: none;
}

html:not(.careers-modal) .careers .job-dialog-bar { display: none; }

html:not(.careers-modal) .careers .job-back {
  display: inline-block;
  margin: 28px clamp(22px, 6vw, 56px) 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.7);
}

html:not(.careers-modal) .careers .job-back::before { content: "\2190\00A0"; }
html:not(.careers-modal) .careers .job-back:hover { color: #fff; }

.careers-nomodal .careers .job-dialog:target { display: block; }

/* ---------- smaller screens ---------- */

@media (max-width: 640px) {
  .careers { padding-top: calc(var(--header-h) + 48px); }
  .careers-col { width: min(960px, 100% - 32px); }
  .careers-roles { margin-top: 48px; }

  .careers .job-card {
    align-items: start;
    gap: 16px;
    padding: 22px 20px;
  }

  .careers .job-card-arrow { width: 38px; height: 38px; }
  .careers .job-card-arrow svg { width: 16px; height: 16px; }

  /* A phone gets the whole screen: a long form in a floating panel with a
     strip of backdrop round it is only less room to type in */
  .careers .job-dialog {
    width: 100%;
    height: 100%;
    height: 100dvh;
    max-height: 100%;
    max-height: 100dvh;
    margin: 0;
    border: 0;
    border-radius: 0;
  }

  .careers .job-dialog-bar .news-close { top: 12px; right: 12px; }

  .careers .job-posting { padding: 30px 20px 4px; }

  .careers .job-facts { gap: 14px 28px; }

  .careers .job-apply {
    margin: 36px 0 0;
    padding: 30px 20px 40px;
    border-inline: 0;
    border-bottom: 0;
    border-radius: 0;
  }

  .careers .job-form-actions .btn { width: 100%; }

}

/* Windows High Contrast (forced colours) drops background images and
   forces every background and border to a system colour — and the drawn
   choices above show their checked state with exactly those: a gradient
   dot, an accent fill with a white tick. Left alone, a chosen answer looks
   the same as one not chosen, and in a light theme the ticked consent box
   looks empty (a white tick on a white box). So in that mode the inputs go
   back to the browser's own, which it draws in the system's colours. The
   :checked rules are named again because they are more specific than the
   first and would otherwise keep their backgrounds. */
@media (forced-colors: active) {
  .careers .job-option input,
  .careers .job-option input[type="radio"]:checked,
  .careers .job-option input[type="checkbox"]:checked {
    appearance: auto;
    -webkit-appearance: auto;
    background: none;
    border: 0;
  }
  .careers .job-option:has(input:checked) { border-color: Highlight; }
}

@media (prefers-reduced-motion: reduce) {
  .careers .job-dialog[open] { animation: none; }
  .careers .job-card,
  .careers .job-card-arrow,
  .careers .job-card-arrow svg,
  .careers .job-option,
  .careers .job-option input,
  .careers .job-drop,
  .careers .job-drop-remove,
  .careers .jobs-empty a,
  .careers .job-section a { transition: none; }
  .careers .job-card:hover .job-card-arrow svg,
  .careers .job-card:focus-visible .job-card-arrow svg { translate: none; }
  /* still says "busy", just without the turning */
  .careers .job-form.is-busy .job-submit::before { animation: none; border-right-color: currentColor; opacity: 0.5; }
}
