:root {
  --blue: #0071ce;
  --blue-deep: #005da9;
  --white: #fff;
  --hairline: rgba(255, 255, 255, .42);
  color-scheme: light;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; background: var(--blue); color: var(--white); font-family: Arial, Helvetica, sans-serif; }
button, input { font: inherit; }
button { cursor: pointer; }
a { color: inherit; }
::selection { background: #fff; color: var(--blue-deep); }
/* Text on the page is not selectable (a drag or double click would paint it white);
   form fields keep normal selection so people can still edit what they type. */
body { -webkit-user-select: none; user-select: none; }
input, textarea, select, [contenteditable] { -webkit-user-select: text; user-select: text; }

.skip-link { position: fixed; z-index: 20; top: 12px; left: 12px; padding: 12px 16px; color: var(--blue); background: #fff; transform: translateY(-160%); }
.skip-link:focus { transform: translateY(0); }
:focus-visible { outline: 3px solid #fff; outline-offset: 4px; }

.journey { position: relative; height: 550svh; }
/* The scene fills the tallest viewport (address bar hidden, as it is while scrolling down): at
   100svh it left a bare band under it once a phone's bar slid away. It never resizes as the bar
   comes and goes, so neither do its canvases; what hangs from its bottom edge rises by the bar's
   height (--bar) to stay on screen. */
:root { --bar: 0px; }
@supports (height: 1dvh) { :root { --bar: calc(100lvh - 100dvh); } }
.scene-shell { position: sticky; top: 0; height: 100svh; height: 100lvh; min-height: 520px; overflow: hidden; isolation: isolate; background: var(--blue); }
.scene-shell::before { content: ""; position: absolute; z-index: -2; inset: 0; background: radial-gradient(circle at 61% 45%, rgba(255,255,255,.12), transparent 40%), linear-gradient(145deg, #047bd7, #006bc5 72%, #005fb3); }
.scene-shell::after { content: ""; position: absolute; z-index: -1; inset: 0; opacity: .21; pointer-events: none; background: linear-gradient(90deg, transparent calc(50% - .5px), rgba(255,255,255,.14) 50%, transparent calc(50% + .5px)); }
.scene-grid { position: absolute; z-index: 0; inset: 0; pointer-events: none; --grid-size: 72px; --grid-x: 50%; --grid-y: 50%; }
.scene-grid::before, .scene-grid::after { content: ""; position: absolute; inset: 0; background-image: linear-gradient(to right, #fff 1px, transparent 1px), linear-gradient(to bottom, #fff 1px, transparent 1px); background-size: var(--grid-size) var(--grid-size); }
.scene-grid::before { opacity: .07475; mask-image: radial-gradient(ellipse at center, #000 20%, rgba(0,0,0,.3) 100%); }
.scene-grid::after { opacity: 0; mask-image: radial-gradient(circle 220px at var(--grid-x) var(--grid-y), #000 0%, rgba(0,0,0,.65) 28%, transparent 100%); filter: drop-shadow(0 0 3.45px rgba(255,255,255,.9775)); transition: opacity .45s ease; }
.scene-grid.is-active::after { opacity: .368; }
.scene-grid.has-wave::before, .scene-grid.has-wave::after { display: none; }
.scene-grid canvas { display: block; width: 100%; height: 100%; }
@media (max-width: 700px) { .scene-grid { --grid-size: 48px; } }
@media (prefers-reduced-motion: reduce), (hover: none) { .scene-grid::after { display: none; } }
#tower-canvas, #tower-gl { position: absolute; z-index: 1; inset: 0; display: block; width: 100%; height: 100%; pointer-events: none; }

.scene-header { position: absolute; z-index: 5; top: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: clamp(22px, 3.2vw, 42px) clamp(22px, 5vw, 78px); }
.scene-header img { width: clamp(170px, 16vw, 240px); height: auto; max-height: 92px; object-fit: contain; object-position: left center; }
.scene-header > span { display: inline-flex; align-items: center; gap: 11px; font-size: 11px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; white-space: nowrap; }
.scene-header i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }

.intro-caption { position: absolute; z-index: 2; top: 50%; left: 50%; width: min(74vw, 520px); transform: translate(-50%, -50%); text-align: center; pointer-events: none; }
.eyebrow { margin: 0 0 22px; font-size: 11px; font-weight: 700; letter-spacing: .21em; line-height: 1.5; text-transform: uppercase; }
.intro-headline { margin: 0; font-size: clamp(32px, 5vw, 72px); line-height: .98; letter-spacing: -.06em; font-weight: 800; }
.intro-headline > span { position: relative; display: table; margin-inline: auto; }
.intro-headline .intro-tech-line { font-size: var(--tech-size, 1.8em); }
/* Under the cursor the letters are made of code: the solid text opens a soft hole there
   (radius --code-r, set by the script) and a canvas fills the letters' shape with tiny
   binary characters. With no radius the mask leaves the text whole. */
.intro-headline { position: relative; }
.intro-headline > span { -webkit-mask-image: radial-gradient(circle var(--code-r, 0px) at var(--hole-x, -999px) var(--hole-y, -999px), transparent 72%, #000 100%); mask-image: radial-gradient(circle var(--code-r, 0px) at var(--hole-x, -999px) var(--hole-y, -999px), transparent 72%, #000 100%); }
.intro-code-layer { position: absolute; z-index: 1; pointer-events: none; filter: drop-shadow(0 0 3px rgba(255,255,255,.85)); }
.intro-headline > span::after { content: attr(data-text); position: absolute; inset: -30px; padding: 30px; color: transparent; white-space: nowrap; text-shadow: 0 0 3px #fff, 0 0 12px #fff, 0 0 26px rgba(255,255,255,.85); opacity: var(--title-glow, 0); pointer-events: none; mask-image: radial-gradient(circle 115px at var(--glow-x, 50%) var(--glow-y, 50%), #000 0%, rgba(0,0,0,.7) 35%, transparent 100%); transition: opacity .18s ease; }
.intro-year { margin: 18px 0 0; font-size: clamp(24px, 2.8vw, 38px); letter-spacing: -.04em; }
.intro-support { margin: 10px auto 0; max-width: 23em; font-size: clamp(12px, 1.1vw, 15px); line-height: 1.5; }
.intro-support strong { display: block; font-size: 1.1em; font-weight: 700; }
.intro-support span { display: block; opacity: .78; }
.intro-scroll-demo { display: flex; align-items: center; justify-content: center; gap: 7px; height: 42px; margin-top: 22px; }
.intro-scroll-demo svg { stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.scroll-mouse { width: 28px; height: 42px; }
.scroll-arrows { width: 20px; height: 32px; animation: scroll-arrows-down 1.8s ease-in-out infinite; }
.scroll-wheel { animation: scroll-wheel-down 1.8s ease-in-out infinite; }
.scroll-arrows, .scroll-wheel { animation-play-state: var(--hint-play-state, running); }
@keyframes scroll-wheel-down { 0%, 15% { transform: translateY(0); opacity: 0; } 30% { opacity: 1; } 75%, 100% { transform: translateY(10px); opacity: 0; } }
@keyframes scroll-arrows-down { 0%, 15% { transform: translateY(-3px); opacity: .35; } 60% { opacity: 1; } 100% { transform: translateY(5px); opacity: .35; } }
.scroll-cue { position: absolute; z-index: 2; right: clamp(24px, 5vw, 78px); bottom: 80px; display: flex; align-items: center; gap: 14px; font-size: 10px; font-weight: 700; letter-spacing: .17em; text-transform: uppercase; pointer-events: none; }
.cue-line { display: block; width: 50px; height: 1px; background: currentColor; opacity: .65; }
.scroll-cue span:last-child { font-size: 20px; font-weight: 400; line-height: 1; }

.portal { position: absolute; z-index: 3; inset: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; background: var(--blue); }
.motion-ready .portal { will-change: clip-path; }
.motion-ready.finale-complete .portal { will-change: auto; }
.motion-ready .audience-caption { will-change: transform, opacity; }
.portal-ring { position: absolute; left: 50%; top: 50%; width: min(55vw, 700px); aspect-ratio: 1; border: 1.5px solid rgba(255,255,255,.8); border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none; box-shadow: 0 0 0 16px rgba(255,255,255,.025), 0 0 0 37px rgba(255,255,255,.025), inset 0 0 64px rgba(255,255,255,.035); }
.portal-ring::before, .portal-ring::after { content: ""; position: absolute; inset: 5%; border: 1px solid rgba(255,255,255,.36); border-radius: inherit; }
.portal-ring::after { inset: -8%; border-color: rgba(255,255,255,.21); }
.portal-layout { position: relative; z-index: 1; width: min(100%, 1500px); min-height: 100%; display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(350px, .8fr); align-items: center; gap: clamp(42px, 7vw, 110px); padding: 118px clamp(24px, 6.5vw, 104px) 72px; }
.london-eye { --eye-size: min(91.96vw, 1331px); position: absolute; z-index: 0; width: var(--eye-size); height: var(--eye-size); left: 50%; bottom: calc(var(--eye-size) * -.72); pointer-events: none; opacity: 0; transform: translate(-50%, 35%); transition: transform 1.8s cubic-bezier(.22,1,.36,1), opacity 1s ease; }
.london-eye svg { display: block; width: 100%; height: auto; overflow: visible; }
/* Stacked layers (base, rotor, axle, cabins): the rotor and each cabin turn as whole boxes on the
   compositor instead of repainting the ~800 shapes of the drawing on every frame. */
.eye-layer { position: absolute; top: 0; left: 0; width: 100%; height: 107%; } /* 2140 / 2000 of the square box */
.london-eye .eye-cabin { position: absolute; width: 5%; height: 4.672897%; } /* 100 × 100 of 2000 × 2140 */
.eye-rotor { transform-origin: 50% 44.859813%; animation: eye-turn 100s linear infinite; } /* hub (1000, 960) of 2000 × 2140 */
.eye-cabin { transform-origin: 50% 50%; animation: eye-turn 100s linear infinite reverse; }
.eye-rotor, .eye-cabin { animation-play-state: paused; }
.finale-complete .london-eye { opacity: .65; transform: translate(-50%, 0); }
/* Until the finale the wheel is transparent. After the first frames the script parks it
   (.eye-parked): not rendered, so its paused layers are not layerized on every frame of the
   journey, yet it keeps the rendering state from those frames for its entrance. The discrete
   transition keeps it drawn while it fades out, so this only applies where that transition
   exists; durations repeat the list on .london-eye (1.8s, 1s → 1.8s for content-visibility). */
@supports (transition-behavior: allow-discrete) {
  .london-eye { transition-property: transform, opacity, content-visibility; transition-behavior: normal, normal, allow-discrete; }
  .eye-parked .london-eye { content-visibility: hidden; }
  .finale-complete .london-eye { content-visibility: visible; }
}
.scene-shell > .london-eye { z-index: 2; }
/* The canvas punches the same circle as the portal's clip, so the scene's own
   gradient shows through from the first frame of the wipe: no tone change at the end. */
.motion-ready .portal, .finale-complete .portal { background: transparent; }
.finale-complete .eye-rotor, .finale-complete .eye-cabin { animation-play-state: running; }
@keyframes eye-turn { to { transform: rotate(360deg); } }
/* On phones the wheel (over twice the screen's width) only fades in, where it stands: rising
   300 px behind the form it read as the page scrolling down by itself. */
@media (max-width: 700px) { .london-eye { --eye-size: 220.2vw; transform: translate(-50%, 0); } }
/* Layered wheel (built by landing-2027.js): each part is an HTML box the compositor rotates. */
.eye-layered .eye-layer, .eye-layered .eye-spin { position: absolute; left: 0; top: 0; width: 100%; height: auto; aspect-ratio: 2000 / 2140; }
.eye-layered .eye-spin .eye-layer { position: static; display: block; width: 100%; height: 100%; }
.eye-layered .eye-rotor { transform-origin: 50% 44.8598%; will-change: transform; }
.eye-cabin-slot { position: absolute; width: 4.8%; aspect-ratio: 1; transform: translate(-50%, -50%); }
.eye-layered .eye-cabin { width: 100%; height: 100%; transform-origin: 50% 50%; will-change: transform; }
.eye-layered .eye-cabin-art { display: block; width: 100%; height: 100%; overflow: visible; }
@media (prefers-reduced-motion: reduce) { .london-eye { transition: none; } .eye-rotor, .eye-cabin { animation: none; } }
.portal-copy { max-width: 770px; }
.portal-copy .eyebrow { margin-bottom: 16px; }
.portal-copy h1 { margin: 0; font-size: clamp(48px, 6vw, 96px); line-height: 1.04; letter-spacing: -.035em; font-weight: 400; text-wrap: balance; }
.portal-copy h1 span { display: inline-block; font-weight: 400; letter-spacing: -.035em; }
.portal-copy h1 .brand-neon { position: relative; display: inline-block; font-weight: 700; text-shadow: 0 0 3px rgba(255,255,255,.2); }
.portal-copy h1 .brand-neon::after { content: 'BritCham Tech'; position: absolute; inset: 0; pointer-events: none; color: transparent; text-shadow: 0 0 5px rgba(255,255,255,.65), 0 0 18px rgba(255,255,255,.45), 0 0 34px rgba(255,255,255,.25); animation: brand-neon-pulse 3s ease-in-out infinite; will-change: opacity; }
@keyframes brand-neon-pulse { 0%,100% { opacity: .3; } 50% { opacity: 1; } }
/* Touch screens: a tighter neon (4 and 11 px, not 5/18/34). The first raster of the wide glow
   stalled a phone for 66-132 ms as the title came in; this one costs half and reads the same. */
@media (pointer: coarse) {
  .portal-copy h1 .brand-neon::after { text-shadow: 0 0 4px rgba(255,255,255,.6), 0 0 11px rgba(255,255,255,.35); }
}
@media (prefers-reduced-motion: reduce) {
  .portal-copy h1 .brand-neon::after { animation: none; opacity: .5; }
}
.portal-intro { max-width: 540px; margin: 26px 0 0; font-size: clamp(15px, 1.2vw, 18px); line-height: 1.5; opacity: .9; }
/* The closing statement: its own line, never broken, larger and bold. */
.portal-intro-key { display: block; margin-top: 4px; font-size: 1.6em; font-weight: 700; line-height: 1.2; letter-spacing: -.01em; white-space: nowrap; }
.event-date { margin: 26px 0 0; padding-top: 18px; border-top: 1px solid var(--hairline); max-width: 540px; font-size: 12px; line-height: 1.5; letter-spacing: .02em; }
.event-date a { margin-left: 5px; text-underline-offset: 3px; }

.interest-card { width: 100%; padding: clamp(28px, 3vw, 46px); border: 1px solid rgba(255,255,255,.38); background: rgba(11,106,190,.12); box-shadow: inset 0 1px 0 rgba(255,255,255,.16), 0 18px 48px rgba(0,53,112,.10);  }
.card-number { margin: 0 0 28px; font-size: 10px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase; opacity: .76; }
.interest-card h2 { margin: 0; font-size: clamp(39px, 3.4vw, 54px); line-height: .94; letter-spacing: -.06em; }
.card-intro { margin: 16px 0 27px; max-width: 30em; font-size: 13px; line-height: 1.52; opacity: .86; }
.field { margin-top: 16px; }
.field label { display: block; margin-bottom: 8px; font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; }
.field input { display: block; width: 100%; height: 44px; padding: 0 12px; color: #fff; border: 1px solid rgba(255,255,255,.58); border-radius: 0; background: rgba(255,255,255,.055); outline: none; transition: background .2s, border-color .2s; }
.field input::placeholder { color: rgba(255,255,255,.56); }
.field input:hover { background: rgba(255,255,255,.10); }
.field input:focus { border-color: #fff; background: rgba(255,255,255,.13); box-shadow: 0 0 0 2px rgba(255,255,255,.16); }
.consent { display: flex; align-items: flex-start; gap: 10px; margin: 20px 0; font-size: 11px; line-height: 1.45; cursor: pointer; }
.consent input { flex: 0 0 auto; width: 16px; height: 16px; margin: 0; }
/* Turnstile takes no room unless it has to show its box. */
.captcha.is-open { margin-bottom: 12px; }
.submit { display: flex; align-items: center; justify-content: space-between; gap: 15px; width: 100%; min-height: 50px; padding: 12px 15px; border: 1px solid #fff; color: var(--blue-deep); background: #fff; font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; transition: color .2s, background .2s, transform .2s; }
.submit:hover { color: #fff; background: transparent; transform: translateY(-2px); }
.submit:disabled { opacity: .66; cursor: wait; transform: none; }
.submit span { font-size: 20px; font-weight: 400; }
.form-message { min-height: 1.3em; margin: 12px 0 0; font-size: 12px; line-height: 1.45; }
.form-message[data-state="error"] { color: #fff2c4; }
.trap { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* Interest form in three steps (landing-2027.js): contact details, interest, the mission.
   Without JavaScript all three show at once and the form posts to submit.php as before. */
.form-step[hidden] { display: none; }
.form-step-label:focus { outline: none; }
/* After the first step the card's number and introduction step aside, and on phones and low
   screens its title too, so every step keeps about the height of the first one. */
.interest-card.is-later-step .card-number, .interest-card.is-later-step .card-intro { display: none; }
@media (max-width: 700px), (max-height: 760px) { .interest-card.is-later-step h2 { display: none; } }
.interest-card.is-later-step h2 { margin-bottom: 16px; }
.form-step-label { margin: 0 0 2px; font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; opacity: .72; }
.field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 12px; }
.field select, .field textarea { display: block; width: 100%; color: #fff; border: 1px solid rgba(255,255,255,.58); border-radius: 0; background-color: rgba(255,255,255,.055); outline: none; transition: background-color .2s, border-color .2s; }
.field select { height: 44px; padding: 0 38px 0 12px; -webkit-appearance: none; appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23fff' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center; }
.field select:invalid { color: rgba(255,255,255,.56); }
.field select option { color: #031636; background: #fff; }
.field textarea { min-height: 64px; padding: 10px 12px; resize: vertical; line-height: 1.4; }
.field textarea::placeholder { color: rgba(255,255,255,.56); }
.field select:hover, .field textarea:hover { background-color: rgba(255,255,255,.10); }
.field select:focus, .field textarea:focus { border-color: #fff; background-color: rgba(255,255,255,.13); box-shadow: 0 0 0 2px rgba(255,255,255,.16); }
.choice-group { min-width: 0; margin: 14px 0 0; padding: 0; border: 0; }
.choice-group legend { margin-bottom: 8px; padding: 0; font-size: 10px; font-weight: 700; line-height: 1.35; letter-spacing: .13em; text-transform: uppercase; }
.choices { display: flex; flex-wrap: wrap; gap: 6px; }
.choice { display: inline-flex; align-items: center; gap: 8px; padding: 6px 11px; border: 1px solid rgba(255,255,255,.58); font-size: 12px; line-height: 1.3; cursor: pointer; transition: background .2s, border-color .2s; }
.choice input { flex: 0 0 auto; width: 15px; height: 15px; margin: 0; }
.choice:hover { background: rgba(255,255,255,.10); }
.choice:focus-within { box-shadow: 0 0 0 2px rgba(255,255,255,.3); }
/* Single choice: the chosen chip lights up, a white outline with a neon glow, and its ring
   fills with a glowing dot. */
.choice input[type="radio"] { -webkit-appearance: none; appearance: none; border: 1.5px solid rgba(255,255,255,.65); border-radius: 50%; background: transparent; cursor: pointer; transition: border-color .2s, box-shadow .2s; }
.choice input[type="radio"]:checked { border-color: #fff; box-shadow: inset 0 0 0 2.5px var(--blue), inset 0 0 0 8px #fff, 0 0 6px rgba(255,255,255,.8); }
.choice:has(input[type="radio"]:checked) { border-color: #fff; background: rgba(255,255,255,.12); text-shadow: 0 0 6px rgba(255,255,255,.55);
  box-shadow: 0 0 0 1px #fff, 0 0 10px rgba(255,255,255,.5), 0 0 24px rgba(255,255,255,.22), inset 0 0 12px rgba(255,255,255,.16); }
.choice:has(input[type="radio"]:checked):focus-within { box-shadow: 0 0 0 1px #fff, 0 0 0 4px rgba(255,255,255,.3), 0 0 14px rgba(255,255,255,.55), inset 0 0 12px rgba(255,255,255,.16); }
/* Several choices and the consent: a white box with the brand's blue tick. */
.choices-list input[type="checkbox"], .consent input { -webkit-appearance: none; appearance: none; border: 1.5px solid rgba(255,255,255,.7); border-radius: 2px; background: rgba(255,255,255,.06) no-repeat center / 78% auto; cursor: pointer; transition: background-color .15s, border-color .15s, box-shadow .2s; }
.choices-list input[type="checkbox"]:checked, .consent input:checked { border-color: #fff; background-color: #fff; box-shadow: 0 0 8px rgba(255,255,255,.55);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 10'%3E%3Cpath d='M1.2 5.2l3.2 3.2L10.8 1.6' fill='none' stroke='%23005da9' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.consent input:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
/* Participation: four options in two even columns. */
.choices-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 1fr; }
.choices-grid .choice { min-height: 36px; }
.choices-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px; }
.choices-list .choice { align-items: flex-start; padding: 3px 0; border: 0; background: none; transition: opacity .2s; }
.choices-list .choice span { min-width: 0; overflow-wrap: anywhere; }
.choices-list .choice:hover { background: none; opacity: .85; }
.choices-list .choice:focus-within { box-shadow: none; }
.choices-list .choice input:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.choices-list .choice input { margin-top: 1px; }
.form-actions { display: flex; align-items: stretch; gap: 10px; margin-top: 20px; }
.consent + .form-actions { margin-top: 0; }
.form-actions .submit { flex: 1 1 auto; width: auto; }
/* Continuar rests as an outline and fills white on hover; only the final send stays solid. */
.submit.form-next { color: #fff; background: transparent; }
.submit.form-next:hover { color: var(--blue-deep); background: #fff; }
/* The final send stands apart: solid white with a breathing neon halo (like the title's brand
   neon, only its opacity animates) and a blue sheen crossing it now and then. On hover the halo
   holds at full strength and the arrow lifts toward its corner, but the button stays in place;
   on touch screens pressing it does the same. */
.submit[type="submit"] { position: relative; isolation: isolate; text-align: left; }
.submit[type="submit"]::before { content: ''; position: absolute; inset: -1px; z-index: -1; pointer-events: none; box-shadow: 0 0 6px rgba(255,255,255,.85), 0 0 20px rgba(255,255,255,.5), 0 0 42px rgba(255,255,255,.25); opacity: .35; animation: send-halo 2.8s ease-in-out infinite; will-change: opacity; }
.submit[type="submit"]::after { content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none; background: linear-gradient(105deg, transparent 38%, rgba(0,113,206,.16) 50%, transparent 62%) no-repeat; background-size: 260% 100%; background-position: 130% 0; animation: send-sheen 5s ease-in-out infinite 1s; }
.submit[type="submit"] span { transition: transform .25s ease; }
.submit[type="submit"]:hover { color: var(--blue-deep); background: #fff; transform: none; }
.submit[type="submit"]:hover::before, .submit[type="submit"]:active::before { opacity: 1; animation: none; }
.submit[type="submit"]:hover span, .submit[type="submit"]:focus-visible span { transform: translate(3px, -3px); }
.submit[type="submit"]:disabled::before, .submit[type="submit"]:disabled::after { animation: none; opacity: 0; }
@keyframes send-halo { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
@keyframes send-sheen { 0% { background-position: 130% 0; } 30%, 100% { background-position: -30% 0; } }
@media (prefers-reduced-motion: reduce) { .submit[type="submit"]::before { animation: none; opacity: .7; } .submit[type="submit"]::after { animation: none; opacity: 0; } }
.form-back { flex: 0 0 auto; padding: 0 14px; border: 1px solid rgba(255,255,255,.58); color: #fff; background: transparent; font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; cursor: pointer; transition: background .2s, border-color .2s; }
.form-back:hover { border-color: #fff; background: rgba(255,255,255,.10); }

.scene-progress { position: absolute; z-index: 5; left: clamp(24px, 5vw, 78px); right: clamp(24px, 5vw, 78px); bottom: 30px; display: flex; align-items: center; gap: 15px; font-size: 10px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; pointer-events: none; }
.progress-track { display: block; flex: 1; height: 1px; background: rgba(255,255,255,.38); }
.progress-track i { display: block; width: 100%; height: 2px; background: #fff; transform: translateY(-.5px) scaleX(0); transform-origin: left center; transition: box-shadow .45s ease; }
.progress-track i.is-complete { box-shadow: 0 0 4px #fff, 0 0 12px rgba(255,255,255,.85), 0 0 28px rgba(255,255,255,.55); }
@media (prefers-reduced-motion: reduce) { .progress-track i { transition: none; } }
.scene-progress span:last-child { min-width: 24px; text-align: right; }
/* The bar changes on every frame of the journey. On its own layers the fill and the bus move
   on the compositor, and the counter repaints only this strip, never the full-screen layer
   of the scene behind it. */
.scene-progress { will-change: transform; }
.progress-track i, .progress-bus { will-change: transform; }
/* White double-decker riding the progress line; JS keeps it inside the track
   and adds .is-driving while the progress is moving. Body, wheels and smoke are three
   overlaid SVGs with the same viewBox (484 × 250 units), so the bob and the smoke animate
   whole elements on the compositor instead of re-laying out and repainting the drawing on
   every frame. The wheels are plain rings: turning them changed no pixel, so they stay still. */
.progress-track { position: relative; }
.progress-bus { position: absolute; left: 0; bottom: -1px; width: 101px; aspect-ratio: 484 / 250; }
.progress-bus svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.progress-bus { opacity: 0; transition: opacity .6s ease; }
.progress-bus.is-visible { opacity: 1; }
/* The smoke scales about the viewBox's (0, 0), 50 and 72 units above and left of the box. */
.progress-bus .bus-smoke { opacity: 0; transform-origin: -10.3306% -28.8%; }
.progress-bus.is-driving .bus-body { animation: bus-bob .28s ease-in-out infinite alternate; }
.progress-bus.is-driving .bus-smoke { animation: bus-smoke .6s ease-out infinite; }
/* 6 units of 250 high; 24 units of 484 wide. */
@keyframes bus-bob { from { transform: translateY(0); } to { transform: translateY(-2.4%); } }
@keyframes bus-smoke { 0% { opacity: .55; transform: translateX(0) scale(.6); } 100% { opacity: 0; transform: translateX(-4.9587%) scale(1.25); } }
@media (prefers-reduced-motion: reduce) { .progress-bus { transition: none; } .progress-bus.is-driving .bus-body, .progress-bus.is-driving .bus-smoke { animation: none; } }
.site-footer { min-height: 100px; display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 26px clamp(24px, 5vw, 78px); border-top: 1px solid rgba(255,255,255,.28); background: #005aa8; font-size: 11px; line-height: 1.5; letter-spacing: .04em; }
.site-footer a { text-underline-offset: 3px; }
.motion-ready .site-footer { position: fixed; z-index: 6; inset: auto 0 0; min-height: 40px; padding: 9px clamp(24px, 5vw, 78px); border: 0; background: transparent; color: rgba(255,255,255,.7); font-size: 10px; letter-spacing: .015em; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .5s ease, visibility .5s; }
.motion-ready.finale-complete .site-footer { opacity: 1; visibility: visible; pointer-events: auto; }
.motion-ready .site-footer .privacy-footer-links { margin: 0; gap: 6px 16px; font: inherit; }
.motion-ready .site-footer .privacy-footer-links a, .motion-ready .site-footer .privacy-footer-links button { color: inherit; }
.motion-ready.finale-complete .scene-progress { bottom: 54px; }
.scene-progress { transition: bottom .5s ease; }
.motion-ready .portal-layout { padding-bottom: 110px; }
@media (max-width: 700px) {
  .motion-ready .site-footer { flex-direction: row; align-items: center; justify-content: center; flex-wrap: wrap; gap: 3px 14px; padding: 8px 20px; font-size: 9px; }
  .motion-ready .site-footer > span { width: 100%; text-align: center; }
  .motion-ready.finale-complete .scene-progress { bottom: 76px; }
  .motion-ready .portal-layout { padding-bottom: 125px; }
}
@media (prefers-reduced-motion: reduce) { .scene-progress, .motion-ready .site-footer { transition: none; } }

.motion-ready .portal { opacity: 0; pointer-events: none; }
.motion-ready .portal-layout { opacity: 0; }
.no-js .journey, .static-mode .journey { height: auto; }
.no-js .scene-shell, .static-mode .scene-shell { position: relative; min-height: 100svh; height: auto; overflow: visible; }
.no-js #tower-canvas, .no-js .intro-caption, .no-js .scroll-cue,
.static-mode #tower-canvas, .static-mode #tower-gl, .static-mode .intro-caption, .static-mode .scroll-cue { display: none; }
.no-js .portal, .static-mode .portal { position: relative; opacity: 1; overflow: visible; pointer-events: auto; }
.no-js .portal-layout, .static-mode .portal-layout { opacity: 1; transform: none; }
.no-js .scene-progress, .static-mode .scene-progress { display: none; }
/* The wheel stays transparent here; left in place it would add ~1,400px of empty scroll below the footer. */
.no-js .london-eye, .static-mode .london-eye { display: none; }

@media (max-width: 900px) {
  .portal-layout { grid-template-columns: minmax(0, 1fr) minmax(280px, .84fr); gap: 32px; padding-inline: 32px; }
  .portal-copy h1 { font-size: clamp(46px, 6vw, 72px); }
  .interest-card { padding: 28px; }
  .intro-caption { width: min(74vw, 520px); }
}

@media (max-width: 700px) {
  .journey { height: 445svh; }
  .scene-shell { min-height: 0; }
  .scene-header { padding: 20px 22px; }
  .scene-header img { width: 158px; max-height: 62px; }
  .scene-header > span { max-width: 130px; white-space: normal; text-align: right; font-size: 9px; line-height: 1.35; letter-spacing: .11em; }
  .scene-header i { display: none; }
  .intro-caption { top: calc(50% - var(--bar) / 2); bottom: auto; left: 50%; width: 74vw; transform: translate(-50%, -50%); }
  .intro-headline { font-size: clamp(30px, 8vw, 48px); }
  .intro-caption .eyebrow { margin-bottom: 16px; font-size: 10px; }
  .intro-support { max-width: 210px; font-size: 12px; }
  .scroll-cue { right: 22px; bottom: calc(76px + var(--bar)); gap: 8px; font-size: 9px; }
  .scroll-cue .cue-line { width: 24px; }
  .portal { align-items: flex-start; justify-content: flex-start; overflow-x: hidden; overflow-y: auto; }
  .portal-ring { top: 31%; width: 95vw; }
  .portal-layout { display: block; min-height: 100%; padding: 106px 24px 75px; }
  .portal-copy h1 { font-size: clamp(46px, 11vw, 68px); }
  .portal-intro { margin-top: 18px; font-size: 14px; }
  .event-date { margin-top: 19px; padding-top: 14px; }
  .interest-card { margin-top: 32px; padding: 28px 24px; }
  .interest-card h2 { font-size: 43px; }
  .scene-progress { bottom: calc(21px + var(--bar)); left: 22px; right: 22px; font-size: 9px; }
  .progress-bus { width: 78px; }
  .scene-progress span:first-child { display: none; }
  .site-footer { align-items: flex-start; flex-direction: column; min-height: 0; }
}

@media (max-height: 710px) and (min-width: 701px) {
  .portal { overflow-y: auto; align-items: flex-start; }
  .portal-layout { padding-top: 105px; padding-bottom: 68px; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-arrows, .scroll-wheel { animation: none; }
  *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

/* Camera-following audience fact, kept separate from the tower outlines. */
.audience-caption { position: absolute; z-index: 2; left: 0; top: 0; width: 26vw; max-width: 380px; margin: 0; opacity: 0; visibility: hidden; pointer-events: none; transform-origin: left center; }
#audience-caption { top: 70px; }
#speakers-caption { top: -90px; }
#exhibitors-caption { left: -5px; }
.audience-caption strong { display: block; font-size: clamp(44px, 5.4vw, 82px); line-height: 1; font-weight: 800; letter-spacing: -.055em; }
.audience-caption span { display: block; margin-top: 12px; font-size: clamp(16px, 1.65vw, 24px); line-height: 1.35; }
.audience-caption b { font-weight: 700; white-space: nowrap; }
@media (max-width: 700px) {
  /* No blurred text-shadow on phones: re-rastering it as the facts scale cut the close-up
     of the dial (55–75) to 10–30 fps on a real phone; without it, ~60 fps. */
  .audience-caption { width: calc(100% - 48px); max-width: none; text-align: center; transform-origin: center; }
  .audience-caption strong { font-size: clamp(38px, 10vw, 54px); }
  .audience-caption span { margin-top: 5px; font-size: 16px; }
}
.no-js .audience-caption, .static-mode .audience-caption { display: none; }

.exhibitors-caption { transform-origin: right center; }
.exhibitors-caption > span:first-child { margin: 0 0 4px; }
/* The words round the closing line's key phrase, large enough to read with it (they were .58 of
   its size, now .78): at that size the first ones take two lines, split evenly. */
.festival-caption { font-size: clamp(23px, 2.45vw, 36px); line-height: 1.15; text-wrap: balance; }
.festival-caption strong { margin: 8px 0; font-size: clamp(30px, 3.1vw, 46px); line-height: 1.05; letter-spacing: -.04em; }
@media (max-width: 700px) {
  .exhibitors-caption { transform-origin: center; }
  .exhibitors-caption strong { display: block; font-size: 34px; margin-right: 0; }
  .exhibitors-caption > span:first-child { display: block; margin-right: 0; }
  .exhibitors-caption span { font-size: 14px; }
  .exhibitors-caption br { display: initial; }
  .festival-caption { font-size: 14px; }
  .festival-caption strong { display: inline; font-size: 20px; margin: 0; }
}

/* The closing line's glow (landing-2027.js): a copy of its text behind it that only shows as
   light, on its own layer (will-change), so the blur is drawn once and stretched, not redrawn
   at every size as the line nears the camera. */
.caption-glow { position: absolute; inset: 0; z-index: -1; display: block; font-style: normal; color: transparent; -webkit-text-stroke: 0 transparent; pointer-events: none; will-change: transform; text-shadow: 0 0 5px rgba(255,255,255,.7), 0 0 16px rgba(255,255,255,.5), 0 0 34px rgba(255,255,255,.32); }

/* Perspective captions cross the lens instead of fading into the portal. */
.exhibitors-caption, .festival-caption { z-index: 4; }
@media (min-width: 701px) {
  .exhibitors-caption { width: 27vw; max-width: 410px; text-align: left; }
  .festival-caption { text-align: right; }
  .exhibitors-caption strong { font-size: clamp(60px, 7.2vw, 108px); }
  .exhibitors-caption span { font-size: clamp(19px, 2.1vw, 31px); }
}

.speakers-caption { text-align: right; }
.speakers-caption > span:first-child { margin: 0 0 5px; }
.speakers-caption > span:last-child { font-size: clamp(16px, 1.5vw, 22px); }
@media (max-width: 700px) {
  .speakers-caption { text-align: center; }
  .speakers-caption > span:last-child { font-size: 15px; }
}

.speakers-layer { position: absolute; inset: 0; z-index: 2; pointer-events: none; mask-mode: luminance; mask-repeat: no-repeat; mask-size: 100% 100%; }

/* The form under attack on the finale: threats come at it and break on its border
   (firewall-2027.js draws them on a canvas round the card). The padding keeps room for them. */
.form-attacks { position: absolute; z-index: 2; pointer-events: none; }
.motion-ready .portal-layout { padding-bottom: 180px; }
@media (max-width: 700px) {
  .motion-ready .portal-layout { padding-bottom: 180px; }
}

/* Virus cursor on the finale; it heals into the normal cursor at the form's border (firewall-2027.js). */
.virus-cursor { position: fixed; left: 0; top: 0; width: 200px; height: 200px; z-index: 2147483000; pointer-events: none; opacity: 0; will-change: transform; }
.virus-cursor.is-visible { opacity: 1; }
.virus-cursor-active, .virus-cursor-active * { cursor: none !important; }
.interest-card.firewall-hit { animation: card-firewall .8s ease-out; }
@keyframes card-firewall {
  0% { border-color: #fff; box-shadow: 0 0 0 1px #fff, 0 0 28px rgba(255,255,255,.7), inset 0 0 22px rgba(255,255,255,.3); }
  100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.16), 0 18px 48px rgba(0,53,112,.10); }
}

/* Pause paint-heavy decorative loops while the final panel is hidden. */
.motion-ready .portal-copy h1 .brand-neon::after { animation-play-state: var(--portal-play-state, paused); }

@media (max-width: 700px) {
  .speakers-layer { z-index: 5; }
  .audience-caption,
  #audience-caption, #speakers-caption, #exhibitors-caption, #festival-caption {
    left: 0; right: auto; top: 0; bottom: auto; width: calc(100% - 64px);
    padding: 0; text-align: center; transform-origin: center;
    background: none; border: 0;
    z-index: 4;
  }
  .audience-caption strong { font-size: 40px; }
  .audience-caption span, .speakers-caption > span:last-child { font-size: 16px; line-height: 1.4; }
  .festival-caption { font-size: 17px; line-height: 1.45; }
  .festival-caption strong { display: block; margin: 5px 0; font-size: 25px; }
  .motion-ready.finale-complete .portal { top: 96px; bottom: 180px; overscroll-behavior-y: contain; }
  .motion-ready.finale-complete .portal-ring { display: none; }
  /* 96 + 10: the content stays where it stood (106 px down) as the panel becomes a scroller. */
  .motion-ready.finale-complete .portal-layout { padding-top: 10px; padding-bottom: 28px; }
  .field input, .field select { min-height: 48px; font-size: 16px; }
  .field textarea { font-size: 16px; }
  .consent { font-size: 12px; }
  .consent input { width: 20px; height: 20px; }
  .interest-card h2 { font-size: clamp(32px, 10vw, 43px); }
  /* Fit the full text inside its projected plane on portrait screens. */
  #audience-caption { left: 0; top: 70px; width: calc(100% - 48px); max-width: none; text-align: left; text-shadow: none; z-index: 2; }
  #audience-caption strong { width: max-content; font-size: clamp(38px, 4.65vw, 70px); }
  #audience-caption span { width: max-content; margin-top: 9px; font-size: 16px; line-height: 1.35; text-align: center; white-space: nowrap; }
  #speakers-caption { width: calc(100% - 48px); text-align: left; }
  #speakers-caption strong { font-size: 82px; }
  /* Desktop type ratios, scaled around the existing mobile number sizes. */
  #speakers-caption > span:first-child { margin: 0 0 5px; font-size: 24px; }
  #speakers-caption > span:last-child { margin-top: 12px; font-size: 22px; line-height: 1.35; }
  #exhibitors-caption { width: max-content; max-width: none; text-align: right; }
  #exhibitors-caption strong { font-size: 40px; }
  #exhibitors-caption span { font-size: 11.48px; line-height: 1.35; margin-top: 4.44px; }
  #exhibitors-caption > span:first-child { margin: 0 0 1.48px; }
  #festival-caption { text-align: center; font-size: 23px; line-height: 1.2; }
  #festival-caption strong { font-size: 29.28px; line-height: 1.05; margin: 5.25px 0; }
}

/* Frosted glass is enabled after the camera reveal has completed. */
.interest-card { position: relative; }
.interest-card::before { content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.finale-complete .interest-card, .static-mode .interest-card, .no-js .interest-card { transform: none !important; z-index: 1; }
.finale-complete .interest-card::before, .static-mode .interest-card::before, .no-js .interest-card::before { -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
.finale-complete .london-eye { will-change: transform; }

/* Mobile legibility pass: a blue knockout halo (the scene's own tone) keeps white copy
   readable where it crosses the white wireframe, and the finale footer sits on frosted glass. */
.event-date a { white-space: nowrap; }
@media (max-width: 700px) {
  .scene-header img { filter: drop-shadow(0 0 3px #0070ca) drop-shadow(0 0 8px #0070ca) drop-shadow(0 0 16px rgba(0,107,197,.85)); }
  .audience-caption,
  #audience-caption, #speakers-caption, #exhibitors-caption, #festival-caption {
    /* A crisp blue outline behind the letters, not a blurred halo: re-rastering blurred
       shadows as these facts scale cut the close-up of the dial (55–75) to 10–30 fps on a
       real phone; with no blur it holds ~60 fps. */
    -webkit-text-stroke: 4px #0070ca; paint-order: stroke fill;
  }
  /* Same footer as desktop (small, soft white, copyright · links · London Tech Week), laid out
     in two rows, on the form's frosted glass so the London Eye blurs behind it. Its top edge is
     the progress line's neon: 63 px tall (+ the safe area), and the line's track (5.5 px above
     the progress bar's bottom) lies right on it, just outside the blur. */
  .motion-ready .site-footer { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-content: center; align-items: center; gap: 2px 14px; box-sizing: border-box; min-height: calc(63px + env(safe-area-inset-bottom)); padding: 9px 20px calc(9px + env(safe-area-inset-bottom)); font-size: 10px; line-height: 1.5; letter-spacing: .015em; }
  .motion-ready .site-footer > span { width: auto; text-align: left; }
  .motion-ready .site-footer > a { grid-column: 2; grid-row: 1; justify-self: end; white-space: nowrap; }
  .motion-ready .site-footer .privacy-footer-links { grid-column: 1 / -1; justify-content: space-between; }
  .motion-ready .site-footer a, .motion-ready .site-footer button { display: inline-block; padding: 3px 0; }
  .motion-ready.finale-complete .site-footer { background: rgba(11,106,190,.12); box-shadow: 0 -18px 48px rgba(0,53,112,.10); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
  .motion-ready.finale-complete .scene-progress { bottom: calc(58.5px + env(safe-area-inset-bottom) + var(--bar)); }
  /* The count sits above the line, clear of the glass. */
  .motion-ready.finale-complete .scene-progress span:last-child { transform: translateY(-8px); }
  /* Little room under the form: the panel fills the screen (min-height), so the threats
     (firewall-2027.js) still get the space left below it, but a tall form scrolls no further. */
  .motion-ready.finale-complete .portal-layout { padding-bottom: 24px; }
  .motion-ready.finale-complete .portal { bottom: calc(88px + env(safe-area-inset-bottom) + var(--bar)); }
}
/* Phones: the final panel compact, so the whole form shows at once inside it (659 px of an
   844 px screen) and it takes little scrolling to reach: no card number, the form's title on one
   line, less air between blocks and lower fields (still 16 px type: smaller would make iOS zoom). */
@media (max-width: 700px) {
  .portal-copy .eyebrow { margin-bottom: 8px; font-size: 10px; }
  .portal-copy h1 { font-size: clamp(34px, 9.4vw, 46px); line-height: 1; }
  .portal-intro { margin-top: 10px; font-size: 13px; line-height: 1.4; }
  .portal-intro-key { margin-top: 2px; font-size: 1.45em; }
  .event-date { margin-top: 12px; padding-top: 10px; font-size: 11px; }
  .interest-card { margin-top: 16px; padding: 16px 18px 14px; }
  .card-number { display: none; }
  .interest-card h2 { font-size: clamp(24px, 7.2vw, 30px); line-height: 1; letter-spacing: -.045em; }
  .interest-card h2 br { display: none; }
  .card-intro { margin: 8px 0 12px; font-size: 12px; line-height: 1.4; }
  .field { margin-top: 9px; }
  .field label { margin-bottom: 4px; font-size: 9px; }
  .field input, .field select { height: 40px; min-height: 40px; }
  .consent { margin: 12px 0; font-size: 11px; line-height: 1.35; }
  .consent input { width: 18px; height: 18px; }
  .submit { min-height: 44px; padding-block: 9px; }
  .form-message { min-height: 0; margin-top: 6px; }
  .form-message:empty { margin: 0; }
  .motion-ready.finale-complete .portal-layout { padding-bottom: 20px; }
}
@media (max-width: 370px) {
  /* Narrow phones: a point smaller, so the footer keeps its two rows (and its 63 px). */
  .motion-ready .site-footer { font-size: 9px; gap: 2px 10px; }
}

/* Short desktop screens (laptops: 1440×789, 1366×657…). The finale's sizes followed the width
   only, and the design fills exactly 969 px of height: below it the logos overlapped, the form
   was cut and the firewall's virus crossed the title. Here each size and gap shrinks with the
   height, from its design value at 969 px to a floor at 650 px (value = floor + (100vh − 650px)
   × (design − floor) / 319), and both columns stand on the line above the virus, so the copy
   clears the header's logo. Taller screens keep the design as it is. */
@media (min-width: 701px) and (max-height: 968px) {
  .portal-layout { align-items: end; padding-top: clamp(84px, calc(84px + (100vh - 650px) * .1066), 118px); }
  .motion-ready .portal-layout { padding-bottom: clamp(122px, calc(122px + (100vh - 650px) * .1818), 180px); }
  .portal-copy .eyebrow { margin-bottom: clamp(10px, calc(10px + (100vh - 650px) * .0188), 16px); }
  .portal-copy h1 { font-size: clamp(40px, min(6vw, calc(58px + (100vh - 650px) * .1191)), 96px); }
  .portal-intro { margin-top: clamp(14px, calc(14px + (100vh - 650px) * .0376), 26px); }
  .event-date { margin-top: clamp(14px, calc(14px + (100vh - 650px) * .0376), 26px); padding-top: clamp(10px, calc(10px + (100vh - 650px) * .0251), 18px); }
  .interest-card { padding-block: clamp(18px, calc(18px + (100vh - 650px) * .0878), 46px); }
  .card-number { margin-bottom: clamp(12px, calc(12px + (100vh - 650px) * .0502), 28px); }
  .interest-card h2 { font-size: clamp(32px, min(3.4vw, calc(34px + (100vh - 650px) * .0627)), 54px); }
  .card-intro { margin: clamp(8px, calc(8px + (100vh - 650px) * .0251), 16px) 0 clamp(12px, calc(12px + (100vh - 650px) * .047), 27px); }
  .field { margin-top: clamp(8px, calc(8px + (100vh - 650px) * .0251), 16px); }
  .field label { margin-bottom: clamp(5px, calc(5px + (100vh - 650px) * .0094), 8px); }
  .field input, .field select { height: clamp(36px, calc(36px + (100vh - 650px) * .0251), 44px); }
  .consent { margin-block: clamp(10px, calc(10px + (100vh - 650px) * .0313), 20px); }
  .submit { min-height: clamp(42px, calc(42px + (100vh - 650px) * .0251), 50px); }
  .form-message { margin-top: clamp(6px, calc(6px + (100vh - 650px) * .0188), 12px); }
  /* The London Eye keeps its share of the screen's height (1331 px at 969). */
  .london-eye { --eye-size: min(91.96vw, 1331px, 137.4vh); }
}

/* Loader (loader-2027.js): the BritCham mark turning in 3D, a solid piece: the script stacks copies
   1 px apart through its 12 px depth, the inner ones a shade darker so its sides read as walls; it
   turns over a neon line counting to 100 %, like the page's progress line. It covers the page,
   which cannot scroll, until everything has loaded. */
.site-loader { position: fixed; inset: 0; z-index: 2147483400; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 44px; color: #fff; background: linear-gradient(to right, rgba(255,255,255,.05) 1px, transparent 1px) 0 0 / 72px 72px, linear-gradient(to bottom, rgba(255,255,255,.05) 1px, transparent 1px) 0 0 / 72px 72px, radial-gradient(circle at 50% 42%, rgba(255,255,255,.12), transparent 45%), linear-gradient(145deg, #047bd7, #006bc5 72%, #005aa8); transition: opacity .6s ease, visibility 0s .6s; }
.site-loader.is-done { opacity: 0; visibility: hidden; pointer-events: none; }
html:has(.site-loader:not(.is-done)) { overflow: hidden; }
.loader-mark { width: 62px; height: 108px; perspective: 520px; }
.loader-spin { position: relative; width: 100%; height: 100%; transform-style: preserve-3d; animation: loader-turn 2.8s cubic-bezier(.45, .05, .55, .95) infinite; }
.loader-spin img { position: absolute; inset: 0; width: 100%; height: 100%; backface-visibility: visible; }
.loader-spin img.is-side { filter: brightness(.72); }
.loader-spin img.is-face { filter: drop-shadow(0 0 5px rgba(255,255,255,.55)); }
@keyframes loader-turn { to { transform: rotateY(360deg); } }
.loader-bar { width: min(340px, 74vw); display: flex; align-items: center; gap: 14px; font-size: 10px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; }
.loader-track { flex: 1; height: 1px; background: rgba(255,255,255,.38); }
.loader-track i { display: block; height: 2px; background: #fff; transform: translateY(-.5px) scaleX(0); transform-origin: left center; box-shadow: 0 0 4px #fff, 0 0 10px rgba(255,255,255,.7); transition: box-shadow .3s ease; }
.loader-track i.is-complete { box-shadow: 0 0 4px #fff, 0 0 12px rgba(255,255,255,.85), 0 0 28px rgba(255,255,255,.55); }
.loader-count { min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) { .loader-spin { animation: none; } .site-loader { transition: none; } }

/* Touch screens scroll inside the body (landing-2027.js adds .inner-scroll to <html>): a phone's
   browser slides its address bar away, and the whole page with it, only while the document
   itself scrolls. With the document still the bar stays where it is, so the scene is as tall as
   the screen with the bar showing (100svh) and nothing has to rise clear of it (--bar 0).
   The body is 1 px short of the screen on purpose: Chrome on Android treats a scroller that fills
   the viewport exactly as the document's own (its "implicit root scroller") and would go on
   moving the bar with it. */
html.inner-scroll { height: 100%; overflow: hidden; --bar: 0px; }
html.inner-scroll body { height: calc(100% - 1px); overflow-x: hidden; overflow-y: auto; -webkit-overflow-scrolling: touch; }
html.inner-scroll .motion-ready .scene-shell { height: calc(100svh - 1px); }
html.inner-scroll:has(.site-loader:not(.is-done)) body { overflow: hidden; }
