:root {
  --void: #000000;
  /* The three plates the sections are built from, after the logo's own tile and
     wordmark panel. All are held back from opaque so the traffic reads through
     them, and they step down in turn — heading, subtitle, contents — so each one
     stands off the one below it. */
  --shell: rgba(46, 47, 48, 0.62);
  --ledge: rgba(43, 45, 47, 0.62);
  --deck: rgba(30, 33, 35, 0.58);
  --text: #f3f5f6;
  --text-soft: #c2c7ca;
  --rule: rgba(255, 255, 255, 0.12);
  --accent: #6ff0ff;
  --gutter: clamp(1.5rem, 7vw, 6rem);
  /* Logo geometry, shared by the sticky logo, the marker that reports when it
     has landed, and the scrim that sits behind it once it has. 3.93 is the
     wordmark's own aspect ratio. --brand-w is the size it settles at as a
     header; it stands larger than that on the splash. */
  --brand-w: clamp(10.5rem, 17vw, 15rem);
  --brand-splash: 1.35;
  --brand-top: clamp(1rem, 2.2vw, 1.5rem);
  --brand-drop: 26svh;
  /* Where the address starts out on the splash: at the foot of the first screen,
     its lower edge level with the arrow's. From there it has a screen of scrolling
     to climb, which is the flight to the second section. */
  --hail-drop: calc(100svh - var(--brand-w) / 3.93 - clamp(1.4rem, 4vh, 2.6rem));
  /* One inset serving both edges: left-hand things start this far in from the
     left, right-hand things end the same distance in from the right. The logo
     and the sections beneath it share it, so they line up. */
  --flush: clamp(var(--gutter), 15vw, 21rem);
  /* Capped short of the span between the two insets, so a left-hand block and a
     right-hand one land in visibly different places. The cap also sets how large
     a section's one-line subtitle can be set before it runs out of room. */
  --block-w: min(58rem, calc(100vw - var(--flush) * 2));
  /* The illustration standing on the side of a section the copy leaves free.
     Set from the page inset, since that is what the room beside the copy is
     worth. A section can take a size of its own with --art-scale. */
  --art-w: calc(var(--flush) * 2.5 - 2.5rem);
  /* How wide the copy is allowed to be set. On a narrow window the block is the
     limit and this does nothing; on a wide one it stops the plates stretching
     the whole way across, which keeps the lines readable and leaves the far side
     of the block to the illustration. Set clear of the longest one-line subtitle
     at the size that line stops growing, since it is not allowed to wrap. */
  --copy-w: 48rem;
  --stride: clamp(9rem, 86vh, 46rem);
}

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

/* Snapping is driven from ui.js rather than by scroll-snap-type. The sections
   sit about a screen apart, which is far enough that CSS snapping ignores a
   single wheel notch and rolls the page back to where it started — and since
   the camera is tied to the scroll position, each of those aborted scrolls
   threw the view down the road and back again. */

body {
  position: relative;
  margin: 0;
  min-height: 100%;
  color: var(--text);
  background: var(--void);
  font-family: Calibri, Carlito, "Segoe UI", system-ui, -apple-system,
    "Helvetica Neue", Arial, sans-serif;
  font-size: 1.05rem;
  line-height: 1.6;
  /* Clip rather than hide: hiding an axis makes the body a scroll container,
     which would strand the sticky logo. */
  overflow-x: clip;
}

.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

canvas#roads {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

.grain {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.00;
  mix-blend-mode: screen;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.hero,
main,
footer {
  position: relative;
  z-index: 2;
}

/* The logo rides a rail the height of the page: it sits mid-left on the splash,
   then sticks to the top for everything below it. */
.brand-rail {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

/* Sits where the logo comes to rest, so scrolling past it is the signal that
   the logo has stopped travelling and become a header. */
.brand-pin {
  position: absolute;
  top: calc(var(--brand-drop) - var(--brand-top));
  left: 0;
  width: 1px;
  height: 1px;
}

.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  /* Clears the logo: its own height plus the gap either side. */
  height: calc(var(--brand-top) * 2 + var(--brand-w) / 3.93);
  background: linear-gradient(
    to bottom,
    rgba(16, 16, 16, 0.94),
    rgba(16, 16, 16, 0.78) 58%,
    rgba(16, 16, 16, 0)
  );
  opacity: 0;
  transition: opacity 0.4s ease;
}

.pinned .topbar {
  opacity: 1;
}

.brand {
  position: sticky;
  top: var(--brand-top);
  display: flex;
  width: calc(var(--brand-w) * var(--brand-splash));
  margin: var(--brand-drop) 0 0 var(--flush);
  overflow: hidden;
  backdrop-filter: blur(18px) saturate(125%);
  -webkit-backdrop-filter: blur(18px) saturate(125%);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  pointer-events: auto;
  transition: width 0.55s ease;
}

/* Full size on the splash, then it draws itself in as it becomes a header. */
.pinned .brand {
  width: var(--brand-w);
}

/* The two halves carry the section colours: the mark sits on the same plate as
   a section heading, the wordmark on the same plate as a section's contents.
   26.22% is the mark's share of the full logo, so the pair reassembles it at
   the original proportions. */
.brand-mark {
  flex: 0 0 26.22%;
  background: var(--shell);
}

.brand-word {
  flex: 1 1 auto;
  background: var(--deck);
}

.brand img {
  display: block;
  width: 100%;
  height: auto;
}

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

/* The address is the logo's opposite number, and travels the same way: it stands
   at the far corner of the splash — out as far as the illustration reaches, down
   level with the arrow — then rides its own rail up to sit across the header from
   the logo, and stays there for the rest of the page. */
.hail-rail {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.hail {
  position: sticky;
  top: var(--brand-top);
  display: flex;
  width: fit-content;
  margin: var(--hail-drop) var(--flush) 0 auto;
  overflow: hidden;
  color: var(--text);
  text-decoration: none;
  backdrop-filter: blur(18px) saturate(125%);
  -webkit-backdrop-filter: blur(18px) saturate(125%);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  pointer-events: auto;
  transition: opacity 0.5s ease, color 0.25s ease;
}

/* Nothing to offer on the last section: it carries the same address, set large,
   as the one thing it is for. */
.arrived .hail {
  opacity: 0;
  pointer-events: none;
}

/* Built from the logo's two plates in the logo's order, and standing exactly as
   tall, so the pair reads as one header once both have landed. */
.hail-mark,
.hail-word {
  display: flex;
  align-items: center;
  height: calc(var(--brand-w) / 3.93);
}

.hail-mark {
  justify-content: center;
  width: calc(var(--brand-w) / 3.93);
  background: var(--shell);
  color: var(--accent);
}

.hail-word {
  padding-inline: clamp(0.7rem, 1.15vw, 1.05rem);
  background: var(--deck);
  font-size: clamp(0.85rem, 1.05vw, 1rem);
  font-weight: 600;
  letter-spacing: 0.015em;
  white-space: nowrap;
}

.hail:hover {
  color: var(--accent);
}

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

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  /* Hung from the top rather than centred, so the space left for the logo above
     it is exactly the padding and nothing else. That space is measured from the
     logo itself — where it stands, plus its height at splash size — so the gap
     between the two holds however short the window is. */
  align-items: flex-start;
  padding: calc(
      var(--brand-drop) + var(--brand-w) * var(--brand-splash) / 3.93 +
        clamp(2.5rem, 10svh, 6rem)
    )
    var(--gutter) 8rem;
}

.hero::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: -1;
  width: min(62rem, 98vw);
  height: min(36rem, 82vh);
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(16, 16, 16, 0.66) 24%, rgba(16, 16, 16, 0) 70%);
}

main {
  padding: 40vh var(--gutter) 20rem;
  display: flex;
  flex-direction: column;
}

/* Sections sit about a screen apart, which is a matter of how the page reads and
   nothing else. The camera plan in coaster.js says where each section's view of
   the road is taken from, so this spacing only decides how much scrolling it
   takes to fly there. It used to decide both, which meant a section could not be
   moved or added without re-composing every view after it. */
main > .block + .block {
  margin-top: var(--stride);
}

/* A section is three plates stacked: a title, a line about it, then the four
   points. The title is only as wide as the words on it; the other two are as
   wide as the wider of the pair, so the subtitle and the points share an edge.
   Neither the block nor the body paints anything, so the glass sits on the
   plates instead — a backdrop filter covers its element's whole box, and on
   these wrappers that box would be a rectangle of blur standing out past the
   ends of the writing. */
.block {
  position: relative;
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: var(--block-w);
}

/* The illustration takes the side of the section the copy leaves free: it stands
   against the page's own margin on that side and reaches back behind the plates,
   which is where the room is once the copy has filled the near side of the
   block. Behind them rather than beside them, so the two read as one thing and
   the glass has something to blur. */
.block-art {
  position: absolute;
  z-index: -1;
  top: 40%;
  width: calc(var(--art-w) * var(--art-scale, 1));
  height: auto;
  transform: translateY(-50%);
  pointer-events: none;
  filter: drop-shadow(0 26px 46px rgba(0, 0, 0, 0.55));
}

/* Out as far as the margin the rest of the page keeps, which is not always the
   edge of the block: where the block is held to its cap it stops short of the
   margin, and the illustration is welcome to the room in between. */
.block.left .block-art {
  right: calc(var(--block-w) + var(--flush) * 2 - 100vw);
}

.block.right .block-art {
  left: calc(var(--block-w) + var(--flush) * 2 - 100vw);
}
.block-body {
  display: flex;
  flex-direction: column;
  width: fit-content;
  max-width: min(100%, var(--copy-w));
}

.block-title,
.block-lead,
.points {
  max-width: min(100%, var(--copy-w));
  backdrop-filter: blur(18px) saturate(125%);
  -webkit-backdrop-filter: blur(18px) saturate(125%);
  box-shadow: 0 22px 55px rgba(0, 0, 0, 0.42);
}

/* Left blocks start on the logo's line; right blocks end as far in from the
   right as the logo stands in from the left. */
.block.left {
  margin-left: calc(var(--flush) - var(--gutter));
}

.block.right {
  margin-left: auto;
  margin-right: calc(var(--flush) - var(--gutter));
  /* So the plates hug the right-hand edge of the block, the one that lines up
     with the other right-hand blocks. */
  align-items: flex-end;
}

.block-title {
  margin: 0;
  padding: clamp(0.6rem, 1.05vw, 0.95rem) clamp(1rem, 1.7vw, 1.5rem);
  background: var(--shell);
  white-space: nowrap;
  text-transform: uppercase;
  font-size: clamp(1.05rem, 1.55vw, 1.45rem);
  font-weight: 700;
  letter-spacing: 0.045em;
  line-height: 1.2;
}

/* Held to one line, which is what decides the size: a sentence this long only
   stays on one line if it is set from the width of the window. Below the point
   where a phone takes over the layout it is allowed to wrap. */
.block-lead {
  margin: 0;
  padding: clamp(0.5rem, 0.85vw, 0.8rem) clamp(1rem, 1.7vw, 1.5rem);
  background: var(--ledge);
  white-space: nowrap;
  font-size: clamp(0.82rem, 1.12vw, 1.05rem);
  line-height: 1.5;
}

.points {
  list-style: none;
  margin: 0;
  padding: clamp(1rem, 1.5vw, 1.4rem) clamp(0.9rem, 1.4vw, 1.25rem);
  background: var(--deck);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.points li {
  display: grid;
  justify-items: center;
  align-content: start;
  gap: 0.45rem;
  padding-inline: clamp(0.7rem, 1.15vw, 1.15rem);
  text-align: center;
}

.points li + li {
  border-left: 1px solid var(--rule);
}

/* Set well down from the subtitle, so the four points read as captions under
   their icons and the plate they sit on stays narrower than the line above it. */
.points strong {
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.35;
}

.points p {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--text-soft);
}

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

.points .icon {
  width: 1.8rem;
  height: 1.8rem;
  color: var(--accent);
  margin-bottom: 0.2rem;
}

/* The last plate carries one point instead of four, and it is an address rather
   than a caption: it takes the whole plate instead of a quarter of it, and the
   deeper padding gives it the room a closing line wants. */
.points.contact {
  grid-template-columns: auto;
  justify-items: center;
  padding-block: clamp(1.4rem, 2.4vw, 2.2rem);
}

/* Set to be read at a glance and clicked: the envelope stands beside the address
   rather than above it, and is sized in em so the two scale as one. */
.mailto {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  color: var(--text);
  text-decoration: none;
  /* An address is one thing to read, and it is what sets how wide the plate has
     to be. Left to break, it breaks at the @ and hangs off the side of the plate,
     since the plate is sized from the longest line the copy can be squeezed to. */
  white-space: nowrap;
  font-size: clamp(1.05rem, 5vw, 2.1rem);
  font-weight: 600;
  letter-spacing: 0.005em;
  line-height: 1.2;
  transition: color 0.25s ease;
}

.points.contact .icon {
  width: 1.35em;
  height: 1.35em;
  margin-bottom: 0;
}

.mailto:hover {
  color: var(--accent);
}

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

/* Rides the bottom of the screen rather than sitting in the splash, so the way
   on to the next section is in the same place from any section. */
.advance {
  position: fixed;
  left: 50%;
  bottom: clamp(1.4rem, 4vh, 2.6rem);
  transform: translateX(-50%);
  z-index: 4;
  display: grid;
  place-items: center;
  width: 3.25rem;
  height: 3.25rem;
  padding: 0;
  border: 0;
  background: var(--shell);
  backdrop-filter: blur(18px) saturate(125%);
  -webkit-backdrop-filter: blur(18px) saturate(125%);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  color: var(--accent);
  cursor: pointer;
  transition: opacity 0.45s ease, background 0.25s ease, color 0.25s ease;
}

.advance:hover {
  background: rgba(60, 62, 63, 0.72);
  color: var(--text);
}

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

/* Nothing below the last section to move on to. */
.advance.spent {
  opacity: 0;
  pointer-events: none;
}

.advance .icon {
  width: 1.5rem;
  height: 1.5rem;
  animation: nudge 2.2s ease-in-out infinite;
}

/* Going somewhere far off — the logo home, or either end of the page — is a
   jump rather than a journey, so the screen is covered while it happens instead
   of the camera driving the whole road back. Out quickly and in more gently, so
   the arrival is the part that is felt. */
.blink {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: var(--void);
  opacity: 0;
  transition: opacity 0.42s ease;
}

.blinking .blink {
  opacity: 1;
  transition-duration: 0.24s;
}

/* The logo is a different size on the splash than in the header, and a covered
   jump can change which of the two it should be. It is resized under the cover
   so it is not caught mid-stretch on the way in. */
.blinking .brand,
.blinking .hail {
  transition: none;
}

@keyframes nudge {
  0%,
  100% {
    transform: translateY(-0.18rem);
    opacity: 0.6;
  }
  50% {
    transform: translateY(0.18rem);
    opacity: 1;
  }
}

/* The legal set, riding the bottom-left of the screen the way the arrow rides
   the bottom-middle: reachable from any section rather than only from the end of
   the page. It lives in the footer and is lifted out of it by being fixed, so
   the colophon lays out as though it were not there and a window too narrow to
   float it has only to put it back. Set like the colophon around it, and left
   plain rather than plated — four small lines in a corner need no furniture, and
   a shadow is enough to hold them off the traffic. Two columns, so they read as
   a block of four rather than a line that has wrapped. */
.fine {
  position: fixed;
  left: var(--flush);
  bottom: clamp(1.4rem, 4vh, 2.6rem);
  z-index: 4;
  display: grid;
  grid-template-columns: repeat(2, auto);
  gap: 0.4rem 1.6rem;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.fine a {
  color: var(--text-soft);
  text-decoration: none;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.9);
  transition: color 0.25s ease;
}

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

.fine a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Any narrower and the second column reaches the arrow, so they stack. */
@media (max-width: 1080px) {
  .fine {
    grid-template-columns: auto;
    gap: 0.3rem;
  }
}

.colophon {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  /* Bottoms out level with the floating legal set, so at the end of the page the
     two read as one line along the foot of the screen. */
  padding: 0 var(--gutter) clamp(1.4rem, 4vh, 2.6rem);
  font-size: 0.76rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.colophon p {
  margin: 0;
}

/* Between here and the width the layout stops alternating at, a section is not
   wide enough for both a subtitle set on one line and an illustration standing
   clear beside it. The subtitle gives way: it is allowed to wrap, and the copy
   is held to a share of the window so the illustration keeps its side. */
@media (max-width: 1180px) {
  :root {
    --copy-w: 52vw;
  }

  .block-lead {
    white-space: normal;
  }
}

@media (max-width: 900px) {
  /* No room to stand a logo beside the copy, so it starts at the top — and at
     one size, since there is no splash for it to preside over. */
  :root {
    --brand-w: 10rem;
    --brand-splash: 1;
    --brand-drop: var(--brand-top);
    --block-w: calc(100vw - var(--gutter) * 2);
    /* The illustration drops below the copy at this width, so there is no side
       being kept clear for it any more and the copy can have the block. Held to
       a share of the window it leaves half the screen empty, and the plates come
       out narrower than the lines they have to carry. */
    --copy-w: var(--block-w);
  }

  /* Nothing to alternate against at this width: logo and sections all share the
     gutter. */
  .brand,
  .block.left,
  .block.right {
    margin-left: 0;
  }

  .block.right {
    margin-right: 0;
    /* Nothing alternates at this width, so every plate reads from the same edge
       rather than half the titles hanging off the right. */
    align-items: flex-start;
  }

  .brand {
    margin-left: var(--gutter);
  }

  /* Too narrow to stand the logo and a written-out address in the same header, so
     the address keeps its envelope and drops the words. It starts at the top here
     as well, since there is no splash for it to preside over either. */
  :root {
    --hail-drop: var(--brand-top);
  }

  .hail {
    margin-right: var(--gutter);
  }

  .hail-word {
    display: none;
  }

  .hero {
    padding: 9rem var(--gutter) 8rem;
  }

  :root {
    --stride: clamp(5rem, 24vh, 11rem);
  }

  .block-title,
  .block-lead {
    white-space: normal;
  }

  .points {
    grid-template-columns: repeat(2, 1fr);
    gap: 1.4rem;
  }

  /* Stacked or two abreast, the columns have no neighbour to be divided from. */
  .points li,
  .points li + li {
    padding: 0;
    border-left: 0;
  }

  /* The copy takes the whole width here, so there is no side left over for the
     illustration to stand on. It falls in below instead — last, wherever it
     happens to sit in the markup — and is held to a share of the screen so a
     section stays something that can be read in one go. */
  .block-art {
    position: static;
    z-index: auto;
    order: 1;
    width: 100%;
    max-height: 30vh;
    object-fit: contain;
    margin-top: 1.7rem;
    transform: none;
  }

  /* A phone has no bottom-left corner to spare: the sections run the full width
     of it, so a set floating there would sit over their plates as they passed.
     It goes back into the footer instead, where there is width for two columns
     again — and where a phone's reader looks for it anyway. */
  .fine {
    position: static;
    grid-template-columns: repeat(2, auto);
    justify-content: start;
    gap: 0.5rem 1.6rem;
  }

  .colophon {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.1rem;
  }
}

@media (max-width: 560px) {
  .points {
    grid-template-columns: 1fr;
  }
}

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

  .brand,
  .hail,
  .blink {
    transition: none;
  }
}
