/* Geist Pixel (Square) has one weight: every weight uses it as it is, so
   the browser never thickens the pixels into a fake bold */
@font-face {
  font-family: 'Geist Pixel';
  src: url('geist-pixel.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Oxygen Mono';
  src: url('oxygen-mono.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

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

:root {
  --bg: #000;
  --fg: #fff;
  --font-sans: 'Geist Pixel', system-ui, sans-serif;
  --font-mono: 'Oxygen Mono', ui-monospace, monospace;

  /* One Figma pixel. The 1440×800 frame is scaled to fit the viewport,
     so at 1440×800 every value below is exactly the design value. */
  --u: min(100vw / 1440, 100svh / 800);
  /* Small copy never drops below a readable size */
  --text: max(12px, calc(16 * var(--u)));
  /* deep purple of the bottom of the bust: menu, button and speaker hover */
  --accent: #7a12d6;
}

html,
body {
  background: var(--bg);
  color: var(--fg);
  /* clip, not hidden: hidden would make the body a scroll box of its own
     and stop the "Ready to join?" stage from sticking */
  overflow-x: clip;
}

body {
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

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

ul {
  list-style: none;
}

img {
  display: block;
}

/* ---------- Hero ---------- */

.hero {
  position: relative;
  width: 100%;
  height: 100svh;
  overflow: hidden;
}

/* Sizes come from the 1440×800 frame scaled by --u; each element is pinned
   to the screen edge it sits closest to in the design, so at 1440×800 the
   layout is the Figma frame 1:1 and other screens keep the same composition. */
.hero__stage {
  position: absolute;
  inset: 0;
  isolation: isolate; /* backdrop for the title blend */
}

/* Video frame: full screen height, centred — puts the bust where the
   "Liquid Chrome Faceless Bust" image sits in the design. */
.hero__media {
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  aspect-ratio: 16 / 9;
  transform: translateX(-50%);
  pointer-events: none;
}

.hero__frame {
  visibility: hidden;
}

/* Wireframe bust (plasma-bust.js). Pinned to the screen: it stays put while
   the hero scrolls away, the camera moving in until the face fills the screen. */
.hero__plasma {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* the tallest the screen gets: Safari on iPhone keeps 100% to the screen
     with its toolbar, and left a bare strip at the bottom once it hid */
  height: 100lvh;
  display: block;
  pointer-events: none;
  /* Faint glow of the top and bottom ring colours, as in Plasma Ring */
  background:
    radial-gradient(55% 40% at 68% 6%, #ff330028 0%, transparent 72%),
    radial-gradient(50% 38% at 25% 94%, #e200ff28 0%, transparent 70%);
}

.hero__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

/* Hides the generator watermark in the bottom-right corner of the clip */
.hero__watermark-cover {
  position: absolute;
  left: 81%;
  top: 91%;
  width: 19%;
  height: 9%;
  background: #000;
  filter: blur(calc(10 * var(--u)));
}

.hero__darkening {
  position: absolute;
  bottom: calc(-197 * var(--u));
  left: calc(50% + 1 * var(--u));
  width: calc(840 * var(--u));
  height: calc(840 * var(--u));
  transform: translateX(-50%);
  pointer-events: none;
  /* only the lower half, around the button — the top would dim the title */
  -webkit-mask-image: linear-gradient(to bottom, transparent 40%, #000 52%);
          mask-image: linear-gradient(to bottom, transparent 40%, #000 52%);
}

.hero__logo {
  position: fixed; /* stays while the page scrolls */
  z-index: 10; /* above every section */
  top: calc(27.43 * var(--u));
  left: calc(51 * var(--u));
  width: calc(40 * var(--u));
  height: calc(39 * var(--u));
}

.hero__logo img {
  width: 100%;
  height: 100%;
  animation: logo-spin 12s linear infinite;
}

@keyframes logo-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__logo img {
    animation: none;
  }
}

.hero__date,
.hero__nav {
  position: absolute;
  font-size: var(--text);
  line-height: 2.5;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* the menu stays while the page scrolls, like the logo, above every section */
.hero__nav {
  position: fixed;
  z-index: 10;
}

.hero__date {
  bottom: calc(118 * var(--u)); /* above the button */
  left: calc(52 * var(--u));
}

.hero__nav {
  /* the tops of "About"'s capitals (0.8em below the menu's top: half the
     line's leading plus the link's padding) line up with the logo's top */
  top: calc(27.43 * var(--u) - 0.8 * var(--text));
  right: calc(49 * var(--u));
  text-align: right;
}

/* Underline to background (port of fancy's UnderlineToBackground): a thin
   purple underline under each item grows into a background behind it on
   hover; an arrow slides in on its left, where Tickets has its arrow. The
   underline sits in the padding, so the text keeps its place. */
.underline-fill {
  position: relative;
  z-index: 0;
  display: inline-block;
  padding: 0.15em 0.35em 0.25em;
  line-height: 1.2;
}

.underline-fill::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: -1;
  height: max(1px, 0.1em);
  background: var(--accent);
  transition: height 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}

.underline-fill:hover::after,
.underline-fill:focus-visible::after {
  height: 100%;
}

.hero__nav a {
  margin-right: -0.35em; /* the padding hangs past the right edge */
}

/* every row as tall as a line of the menu, the link centred in it */
.hero__nav li {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: 2.5em;
}

.hero__nav a::before {
  content: '';
  position: absolute;
  top: 50%;
  right: calc(100% + 25 * var(--u) - 0.35em);
  width: calc(11.21 * var(--u));
  height: calc(11.21 * var(--u));
  background: var(--fg);
  -webkit-mask: url('arrow-tickets.svg') center / contain no-repeat;
          mask: url('arrow-tickets.svg') center / contain no-repeat;
  opacity: 0;
  transform: translate(calc(-8 * var(--u)), -50%);
  transition: opacity 0.25s ease, transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

.hero__nav a:hover::before,
.hero__nav a:focus-visible::before {
  opacity: 1;
  transform: translate(0, -50%);
}

/* Tickets' own arrow gives way to the sliding one */
.hero__nav-tickets img {
  transition: opacity 0.25s ease;
}

.hero__nav-tickets:has(a:hover, a:focus-visible) img {
  opacity: 0;
}

.hero__nav-tickets {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: calc(25 * var(--u));
}

.hero__nav-tickets img {
  width: calc(11.21 * var(--u));
  height: calc(11.21 * var(--u));
}

.hero__title {
  position: absolute;
  top: 27%; /* across the chin: the video is full height here */
  left: 50%;
  font-size: calc(262 * var(--u)); /* "Synthetica" about 1310 wide */
  font-weight: 700;
  line-height: 0.9;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transform: translateX(-50%);
  pointer-events: none;
}

.hero__year {
  display: block;
  margin-top: 0.12em;
  font-size: 0.4em;
  line-height: 1;
  text-align: right;
}

/* Bust cut out of the current video frame (see script.js), drawn over the title */
.hero__foreground {
  display: block;
}

.hero__title--ghost {
  color: transparent;
  -webkit-text-stroke: max(1px, calc(1.5 * var(--u))) rgba(255, 255, 255, 0.45);
  user-select: none;
}

/* under the date; its text starts where the date's does, the padding hangs
   past it */
.hero__button {
  position: absolute;
  bottom: calc(48 * var(--u));
  left: calc(52 * var(--u) - 0.9em);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(15 * var(--u));
  padding: 0.6em 0.9em 0.7em; /* the purple fills this box on hover */
  font-size: calc(16 * var(--u));
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.hero__button img {
  width: calc(26.41 * var(--u));
  height: calc(25.71 * var(--u));
}

/* ---------- Portrait: phones, tablets, narrow windows ---------- */

@media (max-aspect-ratio: 1 / 1) {
  :root {
    --text: 13px;
    --pad: 20px;
    /* bust fits the width but never takes more than ~60% of the height
       (plasma-bust.js draws it smaller inside this box on phones) */
    --media-h: min(118vw, 62svh);
    /* its centre, below the screen's: the smaller bust (lowered in
       plasma-bust.js) sits between the menu and the button */
    --media-y: calc(50% + 17svh);
  }

  .hero__media {
    top: var(--media-y);
    height: var(--media-h);
    transform: translate(-50%, -50%);
    -webkit-mask-image: linear-gradient(to bottom, #000 85%, transparent);
            mask-image: linear-gradient(to bottom, #000 85%, transparent);
  }

  .hero__darkening {
    bottom: -160px;
    left: 50%;
    width: 560px;
    height: 560px;
  }

  .hero__logo {
    top: var(--pad);
    left: var(--pad);
    width: 32px;
    height: 31px;
  }

  .hero__date,
  .hero__nav {
    line-height: 2;
  }

  .hero__nav li {
    min-height: 2em;
  }

  .hero__date {
    bottom: calc(var(--pad) - 6px); /* the last line's text on the padding */
    left: var(--pad);
  }

  .hero__nav {
    top: calc(var(--pad) - 7px); /* capitals level with the logo's top */
    right: var(--pad);
  }

  .hero__nav-tickets {
    gap: 12px;
  }

  .hero__nav-tickets img {
    width: 10px;
    height: 10px;
  }

  .hero__nav a::before {
    right: calc(100% + 12px);
    width: 10px;
    height: 10px;
  }

  .hero__title {
    top: calc(var(--pad) + 16svh); /* below the logo; the head starts halfway down it */
    font-size: calc((100vw - 2 * var(--pad)) / 5.1); /* "Synthetica" spans the width */
  }

  .hero__button {
    /* above the date's three lines: 140px up on a tall phone, less on a
       short one, where the bust needs the room */
    bottom: calc(var(--pad) + max(84px, 30svh - 113px));
    left: 50%;
    transform: translateX(-50%);
    gap: 16px;
    font-size: 18px;
  }

  .hero__button img {
    width: 27px;
    height: 26.4px;
  }
}

/* Digits of the counting year (year-ticker.js) share one width, so the
   number does not jitter while it counts — the font has no tabular figures */
.hero__year .year-digit {
  display: inline-block;
  width: 0.62em;
  text-align: center;
}


/* ---------- About (second section) ---------- */

/* The 1440px-wide Figma frame scaled to the page width: --s is one Figma
   pixel. 100cqw is the section's width, so a scrollbar never pushes the
   layout sideways. The photo, arrow and square of the design are left out
   for now. */
.about {
  /* transparent: the bust stays visible behind it; positioned so its
     content paints over the canvas */
  position: relative;
  container-type: inline-size;
  --s: calc(100cqw / 1440);
  /* starts 40% of a screen below the hero and is followed by 20% of a
     screen, so the text rests in the lower part of the screen just before
     the next section comes up */
  margin-top: 40svh;
  padding-bottom: 20svh;
}

.about__inner {
  position: relative;
  height: calc(574 * var(--s)); /* text bottom (514) + top margin (60) */
}

.about__title {
  position: absolute;
  /* the baseline of "Imagination." (3.347em down the title) sits at 216 */
  top: calc((216 - 64 * 3.347) * var(--s));
  left: calc(50 * var(--s));
  width: calc(618 * var(--s));
  font-size: calc(64 * var(--s));
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.05em;
  /* written in title case in the HTML: text-transform: capitalize would
     capitalise every letter span made by scramble-in.js */
  white-space: nowrap;
}

.about__text {
  position: absolute;
  /* the tops of the first line's capitals (0.396em down the text) sit 160px
     below the title's last baseline, about the text's height lower
     than in the design */
  top: calc((216 + 160 - 16 * 0.396) * var(--s));
  /* same right margin as the menu */
  right: calc(49 * var(--u));
  width: calc(497 * var(--s));
  font-family: var(--font-mono);
  font-size: calc(16 * var(--s));
  line-height: 1.623;
  /* the design stretches every line to the column edge */
  text-align: justify;
}

/* Portrait: one column — title, then text */
@media (max-aspect-ratio: 1 / 1) {
  /* 300px from the text to the speakers' title */
  .about {
    padding-bottom: 0;
  }

  .about__inner {
    height: auto;
    padding: 64px var(--pad) 236px;
  }

  .about__title,
  .about__text {
    position: static;
  }

  .about__title {
    width: auto;
    font-size: min(64px, (100vw - 2 * var(--pad)) / 7); /* "Meets Human" fits */
  }

  .about__text {
    width: auto;
    max-width: 497px;
    margin-top: 96px;
    font-size: 14px;
    text-align: left;
  }
}

/* ---------- Speakers (third section) ---------- */

/* The bust behind scatters while this section scrolls in (plasma-bust.js).
   Figma frame 1440×1178 scaled to the page width, --s is one Figma pixel. */
.speakers {
  position: relative;
  container-type: inline-size;
  --s: calc(100cqw / 1440);
  --dim: #51565a;
  /* spring-like settle, for the open/close and the photo slides */
  --spring: cubic-bezier(0.22, 1, 0.36, 1);
  min-height: 100svh;
}

.speakers__inner {
  position: relative;
  padding: calc(110 * var(--s)) calc(78 * var(--s)) calc(159 * var(--s)) calc(162 * var(--s));
}

.speakers__title {
  margin-bottom: calc(126 * var(--s)); /* list starts at 325 */
  font-size: calc(74 * var(--s));
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.05em;
  text-align: center;
  /* centred on the frame, not on the padded column */
  margin-left: calc(-162 * var(--s));
  margin-right: calc(-78 * var(--s));
}

.speaker + .speaker {
  margin-top: calc(96 * var(--s)); /* rows 173 apart when closed */
}

.speaker__head {
  position: relative;
  display: flex;
  align-items: flex-start;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: default;
}

.speaker__head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
}

/* Name: the copy below slides up in the accent colour when the row opens */
.speaker__name {
  position: relative;
  display: block;
  overflow: hidden;
  flex: none;
  font-size: calc(64 * var(--s));
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.05em;
  white-space: pre;
  color: var(--fg);
  transition: color 0.2s ease;
}

.speaker__name > span {
  display: block;
  transition: transform 0.6s var(--spring);
}

.speaker__name > span + span {
  position: absolute;
  top: 100%;
  left: 0;
  color: var(--accent);
}

/* Role: in its own column when closed, next to the name when open */
.speaker__role {
  position: absolute;
  top: calc(10 * var(--s));
  left: calc(676 * var(--s)); /* 838 in the frame */
  white-space: nowrap; /* lines break where the design breaks them */
  font-family: var(--font-mono);
  font-size: calc(24 * var(--s));
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.5);
  transition: transform 0.6s var(--spring);
}

.speaker__arrow {
  position: absolute;
  top: calc(83 * var(--s));
  right: 0;
  width: calc(21.2 * var(--s));
  height: calc(21.2 * var(--s));
  transform: scaleX(-1); /* the svg points down-left; the design down-right */
  transition: opacity 0.3s ease;
}

/* Topic below the name, opened by speakers.js */
.speaker__info {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.6s var(--spring);
}

.speaker__info > div {
  overflow: hidden;
}

.speaker__topic {
  max-width: calc(680 * var(--s));
  padding-top: calc(40 * var(--s));
  font-size: calc(36 * var(--s));
  line-height: 1.623;
  letter-spacing: 0.06em;
  opacity: 0;
  transform: translateY(calc(24 * var(--s)));
  transition: opacity 0.4s ease, transform 0.6s var(--spring);
}

.speaker__photo {
  display: none;
}

/* States: one row open, the others dimmed */
.speakers.has-open .speaker:not(.is-open) .speaker__name {
  color: var(--dim);
}

.speaker.is-open .speaker__name > span {
  transform: translateY(-100%);
}

.speaker.is-open .speaker__role {
  transform: translateX(var(--role-shift, 0px));
  max-width: var(--role-room, none);
  white-space: normal;
}

.speaker.is-open .speaker__arrow {
  opacity: 0;
}

.speaker.is-open .speaker__info {
  grid-template-rows: 1fr;
}

.speaker.is-open .speaker__topic {
  opacity: 1;
  transform: none;
}

/* Photo of the open speaker: 399×514 with corner marks; it follows the
   cursor (speakers.js), and the photos slide past each other vertically */
.speakers__float {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  width: calc(320 * var(--s));
  height: calc(412 * var(--s)); /* 399×514 in the design, smaller to stay clear of the menu */
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.speakers.has-open .speakers__float {
  opacity: 1;
}

.speakers__float::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  --c: calc(22 * var(--s));
  --w: 2px;
  background:
    linear-gradient(#fff, #fff) top left / var(--c) var(--w),
    linear-gradient(#fff, #fff) top left / var(--w) var(--c),
    linear-gradient(#fff, #fff) top right / var(--c) var(--w),
    linear-gradient(#fff, #fff) top right / var(--w) var(--c),
    linear-gradient(#fff, #fff) bottom left / var(--c) var(--w),
    linear-gradient(#fff, #fff) bottom left / var(--w) var(--c),
    linear-gradient(#fff, #fff) bottom right / var(--c) var(--w),
    linear-gradient(#fff, #fff) bottom right / var(--w) var(--c);
  background-repeat: no-repeat;
}

.speakers__slide {
  position: absolute;
  inset: 0;
  overflow: hidden;
  transform: translateY(100%);
  transition: transform 0.6s var(--spring);
  will-change: transform;
}

.speakers__float {
  overflow: hidden;
  /* its own layer, moved without repainting the page: otherwise the
     browser could leave traces of its corners and edges where it was */
  will-change: transform;
}

.speakers__slide.is-above {
  transform: translateY(-100%);
}

.speakers__slide.is-current {
  transform: none;
}

/* --focus (set on each img) keeps the speaker's face in the frame */
.speakers__slide img,
.speaker__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50%) 50%;
}

/* Portrait: no hover, rows open on tap and show the photo inside */
@media (max-aspect-ratio: 1 / 1) {
  .speakers {
    min-height: 0; /* the schedule follows closely */
  }

  .speakers__inner {
    padding: 64px var(--pad) 16px;
  }

  .speakers__title {
    margin: 0 0 88px;
    font-size: min(44px, (100vw - 2 * var(--pad)) / 9);
    text-align: left;
  }

  .speaker + .speaker {
    margin-top: 32px;
  }

  .speaker__head {
    flex-direction: column;
    gap: 8px;
    padding-right: 32px;
  }

  .speaker__name {
    font-size: min(40px, (100vw - 2 * var(--pad)) / 9);
  }

  .speaker__role {
    position: static;
    width: auto;
    font-size: 14px;
  }

  .speaker.is-open .speaker__role {
    transform: none;
    max-width: none;
  }

  .speaker__arrow {
    top: 12px;
    width: 16px;
    height: 16px;
  }

  .speaker__topic {
    max-width: none;
    padding-top: 20px;
    font-size: 20px;
  }

  .speaker__photo {
    display: block;
    aspect-ratio: 399 / 514;
    max-height: 60svh;
    margin-top: 20px;
  }

  .speakers__float {
    display: none;
  }
}

/* ---------- Event Schedule (fourth section) ---------- */

/* The section is as tall as its cards need to scroll by; the stage sticks
   to the screen meanwhile: the title on top, the stack of cards under it,
   the day dashes at the bottom. schedule.js sets --count and moves the cards. --s is one px of the 1440 wide design. */
.schedule {
  position: relative;
  height: calc(100svh + (var(--count, 6) - 1) * 70svh);
  container-type: inline-size;
  --s: calc(100cqw / 1440);
}

.schedule__stage {
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 100svh;
  padding: calc(48 * var(--s)) calc(162 * var(--s)) calc(56 * var(--s));
  overflow: hidden;
}

.schedule__title {
  font-size: calc(64 * var(--s));
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.05em;
  white-space: nowrap;
}

/* The cards take the room left between the title and the dashes, with some
   above them for the card that sinks back */
.schedule__deck {
  flex: 1;
  min-height: 0;
  width: 100%;
  container-type: size;
  display: grid;
  place-items: center;
  padding-top: 4%;
}

.schedule__stacks {
  display: grid;
}

/* Day 1 / Day 2 under the cards: one dash per day */
.schedule__days {
  display: flex;
  gap: calc(8vw / 14.4); /* as the testimonials' dashes */
  margin-top: calc(24 * var(--s));
}

/* Dashes: the days of the schedule, the quotes of the testimonials. The
   chosen one is purple; the padding makes them easier to hit than they look. */
.schedule__day,
.testimonials__dot {
  padding: 12px 6px;
  border: 0;
  background: none;
  cursor: pointer;
}

.schedule__day::before,
.testimonials__dot::before {
  content: '';
  display: block;
  width: max(14px, calc(20vw / 14.4)); /* in vw: --s differs between the two sections */
  height: 2px;
  background: rgba(255, 255, 255, 0.35);
  transition: background-color 0.3s ease;
}

.schedule__day:hover::before,
.testimonials__dot:hover::before {
  background: rgba(255, 255, 255, 0.6);
}

.schedule__day[aria-selected='true']::before,
.testimonials__dot[aria-current='true']::before {
  background: var(--accent);
}

.schedule__day:focus-visible,
.testimonials__dot:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 0;
}

.stack {
  grid-area: 1 / 1;
  position: relative;
  width: min(100cqw, 90cqh * 16 / 9);
  aspect-ratio: 16 / 9;
  list-style: none;
}

/* Each card fills the stack; later ones lie on top. schedule.js uncovers
   the next one from the bottom (clip-path) and sinks the last one back. */
.stack-card {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #111;
  container-type: inline-size;
  will-change: transform, clip-path;
}

/* --x (on each photo) is where the people are across it: the taller cards
   on phones show under half its width, and keep that part */
.stack-card__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--x, 50%) 50%;
}

/* At the bottom, over a shade that keeps them readable on any photo: time
   and kind, the name, a line about it. Sizes are in cqi of the card, so
   the text grows with it. */
.stack-card__body {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  padding: 3.2cqi 3.6cqi;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.85), transparent 60%);
}

/* in the headings' font */
.stack-card__meta {
  display: flex;
  align-items: baseline;
  gap: 0.7em;
  margin-top: auto;
  font-size: max(13px, 2.2cqi);
  font-weight: 400;
  letter-spacing: 0.03em;
}

.stack-card__title {
  margin-top: 0.8cqi;
  max-width: 80%;
  font-size: max(20px, 4.2cqi);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: 0.02em;
}

.stack-card__text {
  margin-top: 1.2cqi;
  max-width: 70%;
  font-family: var(--font-mono);
  font-size: max(12px, 1.7cqi);
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.75);
}

/* Without motion the cards simply follow one another */
@media (prefers-reduced-motion: reduce) {
  .schedule {
    height: auto;
  }

  .schedule__stage {
    position: static;
    height: auto;
  }

  .schedule__deck {
    container-type: inline-size;
  }

  .stack {
    display: flex;
    flex-direction: column;
    gap: 24px;
    width: 100%;
    aspect-ratio: auto;
  }

  .stack[hidden] {
    display: none;
  }

  .stack-card {
    position: relative;
    aspect-ratio: 16 / 9;
  }
}

/* Portrait: taller cards across the width */
@media (max-aspect-ratio: 1 / 1) {
  /* Title, cards and dashes together, the group centred on the screen.
     --card-w is the card's width: the screen's, or less on a short one,
     so the title still starts 64px down, clear of the logo.
     The group is 166px plus the card: title 48, 80 to the card, dashes
     12 + 26. The margin leaves 95px from the speakers' last line (16px of
     their padding) to the title. */
  :root {
    --card-w: min(100vw - 2 * var(--pad), (100svh - 294px) * 0.8);
    /* the room above the title and under the dashes */
    --stage-gap: calc((100svh - 166px - var(--card-w) * 1.25) / 2);
  }

  .schedule {
    margin-top: calc(79px - var(--stage-gap));
  }

  .schedule__stage {
    justify-content: center;
    padding: 0 var(--pad);
  }

  .schedule__deck {
    flex: none;
    height: calc(var(--card-w) * 1.25 + 80px);
    place-items: end center;
    padding-top: 0;
  }

  .schedule__title {
    font-size: min(40px, (100vw - 2 * var(--pad)) / 8);
  }

  .schedule__days {
    gap: 4px;
    margin-top: 12px;
  }

  .stack {
    width: var(--card-w);
    aspect-ratio: 4 / 5;
  }

  .stack-card__body {
    padding: 20px;
  }

  .stack-card__meta {
    font-size: 14px;
  }

  .stack-card__title {
    margin-top: 6px;
    max-width: none;
    font-size: min(28px, 7.5cqi);
  }

  .stack-card__text {
    margin-top: 10px;
    max-width: none;
    font-size: 13px;
  }

}

/* ---------- Testimonials (fifth section) ---------- */

/* A quote in the middle, the person under it, dots below. --s is one px of
   the 1440 wide design. */
.testimonials {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: calc(120 * var(--s)) calc(162 * var(--s)) calc(140 * var(--s));
  container-type: inline-size;
  --s: calc(100cqw / 1440);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  text-align: center;
}

/* The quotes sit in a row and the row slides; testimonials.js moves it
   with the finger or cursor (--drag) while it is dragged */
.testimonials__viewport {
  width: 100%;
  max-width: calc(1000 * var(--s));
  overflow: hidden;
  cursor: grab;
  touch-action: pan-y; /* vertical swipes still scroll the page */
  user-select: none;
  -webkit-user-select: none;
}

.testimonials__viewport.is-dragging {
  cursor: grabbing;
}

.testimonials__track {
  display: flex;
  transform: translateX(calc(var(--index, 0) * -100% + var(--drag, 0px)));
  transition: transform 0.8s var(--ease);
}

.testimonials__viewport.is-dragging .testimonials__track {
  transition: none;
}

/* the ones beside the current quote fade as they slide away */
.testimonial {
  flex: 0 0 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 calc(24 * var(--s));
  opacity: 0.15;
  transition: opacity 0.6s ease;
}

.testimonial.is-current {
  opacity: 1;
}

.testimonial__quote {
  font-size: calc(40 * var(--s));
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: 0.01em;
  text-wrap: balance;
}

/* Typewriter cursor (testimonials.js moves it after the last typed letter).
   It is an empty inline span, so it never changes where lines break; the
   bar hangs off it. Solid while typing, blinking while it waits. */
.typewriter-caret {
  position: relative;
  visibility: visible; /* shows even inside a letter not typed yet */
}

.typewriter-caret::after {
  content: '';
  position: absolute;
  top: 0.08em;
  left: 0.04em;
  width: max(2px, 0.07em);
  height: 1.05em;
  background: var(--accent);
  animation: caret-blink 1s steps(1) infinite;
}

.testimonial.is-typing .typewriter-caret::after {
  animation: none;
}

.testimonial:not(.is-current) .typewriter-caret {
  display: none;
}

@keyframes caret-blink {
  50% { opacity: 0; }
}

.testimonial__author {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: calc(48 * var(--s));
}

.testimonial__avatar {
  display: block;
  width: calc(108 * var(--s));
  height: calc(108 * var(--s));
  border-radius: 50%;
  object-fit: cover;
  pointer-events: none; /* a drag starting on it moves the row */
}

.testimonial__name {
  margin-top: calc(18 * var(--s));
  font-size: calc(22 * var(--s));
  font-weight: 700;
  letter-spacing: 0.03em;
}

.testimonial__role {
  margin-top: calc(6 * var(--s));
  font-family: var(--font-mono);
  font-size: calc(14 * var(--s));
  color: rgba(255, 255, 255, 0.5);
}

/* the dashes themselves are styled with the schedule's */
.testimonials__dots {
  display: flex;
  gap: calc(8vw / 14.4);
  margin-top: calc(72 * var(--s));
}

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

/* Portrait: sizes for a phone's width */
@media (max-aspect-ratio: 1 / 1) {
  /* pulled up into the empty bottom of the schedule's stage (--stage-gap
     under its dashes): 200px from the dashes to the quote, which sits 33px
     into the section */
  .testimonials {
    margin-top: calc(167px - var(--stage-gap));
    padding: 0 var(--pad) 88px;
  }

  .testimonials__viewport {
    max-width: none;
  }

  .testimonial {
    padding: 0 4px;
  }

  .testimonial__quote {
    font-size: min(26px, (100vw - 2 * var(--pad)) / 14);
  }

  .testimonial__author {
    margin-top: 32px;
  }

  .testimonial__avatar {
    width: 72px;
    height: 72px;
  }

  .testimonial__name {
    margin-top: 14px;
    font-size: 18px;
  }

  .testimonial__role {
    font-size: 12px;
  }

  .testimonials__dots {
    gap: 4px;
    margin-top: 56px;
  }
}

/* ---------- Ready to join? (before the prices) ---------- */

/* The stage stays on screen while the section scrolls by; join.js grows
   the phrase toward the hole of its "o" until the screen is inside it,
   80% of the way through. The prices are pulled up under the stage by
   45% of a screen, so they already rise behind the letters and show
   through the hole as the camera flies in. --s is one px of the 1440 wide
   design. */
.join {
  position: relative;
  height: 200svh;
  container-type: inline-size;
  --s: calc(100cqw / 1440);
}

.join__stage {
  position: sticky;
  top: 0;
  z-index: 1; /* over the prices rising behind it */
  display: grid;
  place-items: center;
  height: 100svh;
  overflow: hidden;
  pointer-events: none; /* the prices under it stay clickable */
}

.join__title {
  font-size: calc(150 * var(--s));
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: 0.02em;
  white-space: nowrap;
  transform-origin: 0 0; /* join.js scales around the hole of the "o" */
}

@media (prefers-reduced-motion: no-preference) {
  .join + .pricing {
    margin-top: -45svh;
  }
}

/* without motion the phrase just sits there */
@media (prefers-reduced-motion: reduce) {
  .join {
    height: auto;
  }

  .join__stage {
    position: static;
    height: auto;
    padding: calc(120 * var(--s)) 0 calc(40 * var(--s));
  }
}

/* ---------- Ticket Pricing (sixth section) ---------- */

/* Title, two cards side by side, the button under them. --s is one px of
   the 1440 wide design; small copy stops at 12px. It comes up out of the
   dark behind "Ready to join?": join.js sets --reveal from 0 to 1. */
.pricing {
  opacity: var(--reveal, 1);
  transform: scale(calc(0.8 + 0.2 * var(--reveal, 1)));
  transform-origin: 50% 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: calc(80 * var(--s)) calc(162 * var(--s)) calc(160 * var(--s));
  container-type: inline-size;
  --s: calc(100cqw / 1440);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

.pricing__title {
  font-size: calc(74 * var(--s));
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.05em;
  text-align: center;
}

.pricing__cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: calc(32 * var(--s));
  width: 100%;
  max-width: calc(1000 * var(--s));
  margin-top: calc(64 * var(--s));
}

/* A thin outline that turns purple, with a glow rising from the bottom,
   under the cursor; the VIP card has both from the start */
.price-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: calc(44 * var(--s)) calc(48 * var(--s)) calc(48 * var(--s));
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: radial-gradient(120% 70% at 50% 120%, rgba(122, 18, 214, 0.45), transparent 70%) no-repeat;
  background-size: 100% 0%;
  background-position: bottom;
  transition: border-color 0.4s ease, background-size 0.6s var(--ease), transform 0.6s var(--ease);
}

.price-card:hover {
  border-color: var(--accent);
  background-size: 100% 100%;
  transform: translateY(calc(-6 * var(--s)));
}

.price-card--vip {
  border-color: var(--accent);
  background-size: 100% 100%;
}

.price-card__name {
  font-size: calc(28 * var(--s));
  font-weight: 700;
  letter-spacing: 0.03em;
}

/* sits on the card's top edge, so both cards keep their lines level */
.price-card__flag {
  position: absolute;
  top: 0;
  right: calc(48 * var(--s));
  transform: translateY(-50%);
  padding: 0.35em 0.8em 0.4em;
  background: var(--accent);
  font-family: var(--font-mono);
  font-size: max(11px, calc(12 * var(--s)));
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}

.price-card__price {
  margin-top: calc(28 * var(--s));
}

.price-card__amount {
  font-size: calc(88 * var(--s));
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.01em;
}

.price-card__per {
  font-family: var(--font-mono);
  font-size: max(12px, calc(14 * var(--s)));
  color: rgba(255, 255, 255, 0.5);
}

.price-card__note {
  margin-top: calc(16 * var(--s));
  font-family: var(--font-mono);
  font-size: max(12px, calc(14 * var(--s)));
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.65);
}

.price-card__list {
  margin-top: calc(32 * var(--s));
  padding-top: calc(32 * var(--s));
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  font-family: var(--font-mono);
  font-size: max(12px, calc(15 * var(--s)));
  line-height: 1.5;
}

/* each line behind a small purple square, like the menu's accent */
.price-card__list li {
  position: relative;
  padding-left: 1.6em;
}

.price-card__list li + li {
  margin-top: 0.9em;
}

.price-card__list li::before {
  content: '';
  position: absolute;
  top: 0.55em;
  left: 0;
  width: 0.5em;
  height: 0.5em;
  background: var(--accent);
}

.price-card--vip .price-card__list li::before {
  background: var(--fg);
}

/* like the hero's button, its arrow turned to point on */
.pricing__button {
  display: flex;
  align-items: center;
  gap: calc(15 * var(--s));
  margin-top: calc(72 * var(--s));
  padding: 0.6em 0.9em 0.7em;
  font-size: calc(18 * var(--s));
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.pricing__button img {
  width: calc(26 * var(--s));
  height: calc(25 * var(--s));
  transform: rotate(-90deg);
  transition: transform 0.45s var(--ease);
}

.pricing__button:hover img,
.pricing__button:focus-visible img {
  transform: rotate(-90deg) translateY(calc(6 * var(--s)));
}

@media (prefers-reduced-motion: reduce) {
  .price-card,
  .pricing__button img {
    transition: none;
  }

  .price-card:hover {
    transform: none;
  }
}

/* Portrait: the cards stack */
@media (max-aspect-ratio: 1 / 1) {
  .pricing {
    padding: 8px var(--pad) 96px; /* the testimonials end with room enough */
  }

  .pricing__title {
    font-size: min(40px, (100vw - 2 * var(--pad)) / 8);
  }

  /* on two lines, "Ready to" / "join?": a line is narrower than the
     phrase is wide (7.2em) but wider than "Ready to" (4.1em) */
  .join__title {
    max-width: 5em;
    font-size: calc((100vw - 2 * var(--pad)) / 4.4);
    line-height: 1;
    text-align: center;
    white-space: normal;
  }

  .pricing__cards {
    grid-template-columns: 1fr;
    gap: 20px;
    max-width: 520px;
    margin-top: 40px;
  }

  .price-card {
    padding: 28px 24px 32px;
  }

  .price-card:hover {
    transform: none;
  }

  .price-card__name {
    font-size: 22px;
  }

  .price-card__flag {
    right: 24px;
    font-size: 11px;
  }

  .price-card__price {
    margin-top: 20px;
  }

  .price-card__amount {
    font-size: 56px;
  }

  .price-card__per,
  .price-card__note {
    font-size: 13px;
  }

  .price-card__note {
    margin-top: 12px;
  }

  .price-card__list {
    margin-top: 24px;
    padding-top: 24px;
    font-size: 14px;
  }

  .pricing__button {
    gap: 12px;
    margin-top: 48px;
    font-size: 15px;
  }

  .pricing__button img {
    width: 21px;
    height: 20px;
  }
}

/* ---------- Footer ---------- */

/* The page slides up off the footer: main lies over it (z-index, its own
   background) and the footer sticks to the bottom of the screen under it,
   so it shows as the end of main scrolls up. clip-path keeps main's fixed
   layers (the bust canvas) inside main, off the footer. */
main {
  position: relative;
  z-index: 1;
  background: var(--bg);
  clip-path: inset(0);
}

/* The logo and menu leave with the page instead of lying over the footer:
   footer.js sets --lift to how far the end of main has risen */
.hero__logo,
.hero__nav {
  transform: translateY(calc(-1 * var(--lift, 0px)));
}

/* The 1440×547 Figma frame scaled to the page width (--s, as in About) */
.footer {
  position: sticky;
  bottom: 0;
  z-index: 0;
  height: calc(547 * var(--s));
  background: #8600df;
  container-type: inline-size;
  --s: calc(100cqw / 1440);
  font-size: max(12px, calc(16 * var(--s)));
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.footer__nav,
.footer__contacts,
.footer__social {
  position: absolute;
  top: calc(39 * var(--s));
  line-height: 2.5;
}

.footer__nav {
  left: calc(51 * var(--s));
}

.footer__contacts {
  left: calc(501 * var(--s));
}

.footer__social {
  left: calc(1211 * var(--s));
}

/* links and icons turn black on hover (a purple fill would vanish on the
   purple); the links' padding hangs past the text's left edge */
.footer .underline-fill {
  margin-left: -0.35em;
  transition: color 0.3s ease;
}

.footer .underline-fill::after {
  display: none;
}

.footer a:hover,
.footer a:focus-visible {
  color: var(--bg);
}

.footer__social ul {
  display: flex;
  align-items: center;
  gap: calc(24 * var(--s));
  margin-top: calc(5 * var(--s));
}

.footer__social a {
  display: block;
  transition: color 0.3s ease;
}

.footer__social a:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 4px;
}

.footer__social svg {
  display: block;
  width: calc(21 * var(--s));
  min-width: 18px;
  height: auto;
}

/* Right-aligned to 47 from the edge; the last letter's tracking hangs past
   it. With a line-height of 1 the baseline is 0.86em below the top in Geist
   Pixel: "SYNTHETICA" sits on 401, "2026" on 499. */
.footer__title {
  position: absolute;
  top: calc(219 * var(--s));
  right: calc(47 * var(--s));
  margin-right: -0.05em;
  font-size: calc(212 * var(--s));
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.05em; /* of its own size: the footer's would come in px */
  text-align: right;
  white-space: nowrap;
}

/* its last digit ends where the name's last letter does */
.footer__year {
  display: block;
  margin-top: calc(8 * var(--s));
  margin-right: calc(0.05 * 212 * var(--s) - 0.05em);
  font-size: calc(70 * var(--s));
  font-weight: 700;
  letter-spacing: 0.05em;
}

/* Portrait: the columns in a grid, the name across the width under them */
@media (max-aspect-ratio: 1 / 1) {
  .footer {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px 20px;
    height: auto;
    padding: 32px var(--pad) 24px;
    font-size: 13px;
  }

  .footer__nav,
  .footer__contacts,
  .footer__social,
  .footer__title {
    position: static;
  }

  .footer__nav {
    grid-row: span 2;
  }

  .footer__social ul {
    gap: 20px;
  }

  .footer__title {
    grid-column: 1 / -1;
    margin-top: 24px;
    font-size: calc((100vw - 2 * var(--pad)) / 6.4);
  }

  .footer__year {
    margin-top: 8px;
    margin-right: calc((100vw - 2 * var(--pad)) / 6.4 * 0.05 - 0.05em);
    font-size: 36px;
  }
}

/* ---------- Burger menu (tablets and phones) ---------- */

/* menu.js toggles .is-open on the header; the query is the same there */
.menu-toggle,
.hero__nav-date {
  display: none;
}

@media (max-aspect-ratio: 1 / 1), (max-width: 1024px), (hover: none) and (pointer: coarse) {
  /* Two lines that cross into an X when open; the button is centred on the
     logo's height and its lines end where the menu's text did */
  .menu-toggle {
    display: block;
    position: fixed;
    z-index: 11;
    top: calc(46.93 * var(--u) - 20px);
    right: calc(49 * var(--u) - 8px);
    width: 40px;
    height: 40px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--fg);
    cursor: pointer;
    transform: translateY(calc(-1 * var(--lift, 0px)));
  }

  .menu-toggle::before,
  .menu-toggle::after {
    content: '';
    position: absolute;
    left: 8px;
    width: 24px;
    height: 2px;
    background: currentColor;
    transition: transform 0.35s cubic-bezier(0.65, 0, 0.35, 1);
  }

  .menu-toggle::before {
    top: 15px;
  }

  .menu-toggle::after {
    top: 23px;
  }

  .is-open .menu-toggle::before {
    transform: translateY(4px) rotate(45deg);
  }

  .is-open .menu-toggle::after {
    transform: translateY(-4px) rotate(-45deg);
  }

  .menu-toggle:focus-visible {
    outline: 2px solid var(--fg);
    outline-offset: -2px;
  }

  /* the logo stays over the open menu */
  .hero__logo {
    z-index: 11;
  }

  /* The menu fills the screen, its links large on the left; it fades in and
     the links come up one after another */
  .hero__nav {
    inset: 0;
    z-index: 10;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 0 max(var(--pad, 0px), calc(51 * var(--u)));
    background:
      radial-gradient(60% 45% at 15% 100%, #e200ff28, transparent 70%),
      radial-gradient(55% 40% at 90% 0%, #ff330028, transparent 72%),
      var(--bg);
    font-size: clamp(32px, 9vw, 64px);
    line-height: 1.3;
    letter-spacing: 0.02em;
    text-align: left;
    visibility: hidden;
    opacity: 0;
    transform: none;
    transition: opacity 0.35s ease, visibility 0s 0.35s;
  }

  .is-open .hero__nav {
    visibility: visible;
    opacity: 1;
    transition: opacity 0.35s ease;
  }

  .hero__nav li {
    justify-content: flex-start;
    min-height: 0;
    opacity: 0;
    transform: translateY(0.5em);
    transition: opacity 0.3s ease, transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  }

  .is-open .hero__nav li {
    opacity: 1;
    transform: none;
  }

  .is-open .hero__nav li:nth-child(2) { transition-delay: 0.05s; }
  .is-open .hero__nav li:nth-child(3) { transition-delay: 0.1s; }
  .is-open .hero__nav li:nth-child(4) { transition-delay: 0.15s; }

  .hero__nav a {
    margin: 0 0 0 -0.35em; /* the padding hangs past the left edge */
  }

  /* a thin line under the large links, still filling on hover */
  .hero__nav a:not(:hover, :focus-visible)::after {
    height: 3px;
  }

  .hero__nav a::before,
  .hero__nav-tickets img {
    display: none;
  }

  /* the date leaves the hero for the bottom of the open menu */
  .hero__date {
    display: none;
  }

  .hero__nav-date {
    display: block;
    position: absolute;
    bottom: max(var(--pad, 0px), calc(48 * var(--u)));
    left: max(var(--pad, 0px), calc(51 * var(--u)));
    font-size: max(13px, var(--text));
    line-height: 2;
    letter-spacing: 0.05em;
    opacity: 0;
    transition: opacity 0.3s ease;
  }

  .is-open .hero__nav-date {
    opacity: 0.7;
    transition-delay: 0.2s;
  }
}

/* portrait: centred on the logo there (20px down, 31 tall) */
@media (max-aspect-ratio: 1 / 1) {
  .menu-toggle {
    top: calc(var(--pad) - 4.5px);
    right: calc(var(--pad) - 8px);
  }
}

/* Text that only screen readers get (scramble-in.js keeps the plain text
   here while the visible copy animates) */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
