/* ---------------------------------------------------------------------------
   Ocean & Cloud

   Each page has one hand-drawn background in two crops: landscape for wide
   screens, portrait for phones held upright. Orientation rather than device
   type decides, so a phone turned sideways and a tablet held upright both get
   the crop that fits. Browsers only download the background whose media query
   matches. The home page (and error page) show the ocean; playground.html
   swaps in the island playground via body.playground-page.

   On load the home page plays a sunrise: it starts as a dim, colourless dusk,
   then the colour fades in as it brightens to day, while a warm glow rises
   from where the sun is drawn and fades into daylight.

   An off-white header carries the site name and a menu button; the menu
   slides in from the right. It is a native popover, so opening, closing,
   Escape and click-outside need no JavaScript.
   --------------------------------------------------------------------------- */

:root {
  /* Blank drawing paper: the header, the menu, and the page behind the image.
     Also the colour Safari tints its toolbar with, as the page sets no
     theme-color, so it should stay close to the browser's own. */
  --paper: #f6f3ec;
  --white: #ffffff;
  /* Deep blue from the top of the drawn sky, for text on paper (12:1). */
  --ink: #1f2d4a;

  --header-height: 4rem;

  --background: url("assets/ocean-landscape.webp");
  /* A 64px copy of the same image, blurred, inlined so it paints with the
     first frame and holds the scene until the full image arrives. */
  --background-placeholder: url("data:image/webp;base64,UklGRj4CAABXRUJQVlA4IDICAABwDgCdASpAACQAPr1Um0qnJSIhsBgNUOAXiWwAnTKEdQfAeYxY2sLFftx/6H1S/mLoa/V95k/Oq9G3/dvnt9pGMYF67Kh2p8lQi4scgdobgOm6M7XqUiCxilEwYDSCSbCZnLXSF1FlKuZj43RAwBfD1OiFKVzLNLqmY4PAAP7wP05czxFgw3DjfGwQWCimpD/BPSpWcLsynKOrdaaByDkk/3+EMKia9zZl92DmaJdaFmX50fZbIj7OxYedS2ywrr4lrZtBZd4S29QHnhgkSektJxMMBDXZ1teWba+7fSFPFYb/+Voo4IzXSRSNeu3O8wtOaMMW0x90CReulXGOgJ9BxBYOUuNcN7SaySjMNMFNHcL6k9TYR67/qdnwiDTUQtDE/MiH39ZLRKevhvmx8/lHySIsUoj3iJCFVy8GeTcDaC1pEh+8ekQtzp8Rbr0YEW9EttJRTl3vPLGw0ORQltnNytITrgMF2+0p90gfy/jO4nWiV94YMGpPtH2o/m+lnCjiTHtES9OaucaSFISPGeJr+jpJ9ITyqjlaqavWc1iq9XQZxI9vwzJj7Cy7XkQLU+RAx9U4aXOIOgT4MmQdL16fo8YVUaLdu8m+CnVU3/oUMSJdJWgYpEFEKZQBMh2xQjQmLXW7okQgD+q42n1hpEQFBYxKgfIYYERNCTxJNr5o5lOAzHtEswlSFHVRHIZyXiHVqUX34yFK1+wrQ2skA4wC9MjJd2B0xRIB2ON/3mqiAAAA");

  /* Where the sun sits in each crop, as a share of the viewport. Approximate:
     background-size: cover crops differently per screen shape, and the glow
     is wide and soft enough that it does not need to be exact. Only the home
     page's sunrise uses them. */
  --sun-x: 67%;
  --sun-y: 54%;

  color-scheme: light;
}

@media (orientation: portrait) {
  :root {
    --background: url("assets/ocean-portrait.webp");
    --background-placeholder: url("data:image/webp;base64,UklGRvICAABXRUJQVlA4IOYCAAAQEQCdASorAEAAPr1OnEqnJKKhsrbcyOAXiWwAnTKEd9+2eYJWX8VrqhDq7nqA/Ou8U8zn7Leul6HP8z6UHUG7zdZyrXb3IzLKxF1TvkJVRQEBwE+/6i5l8SWeBTD/EVzFRBHNUYX1AY4PUuQUFsf0u5Y61wFA4rXwWLAYyvb04C55w0dPTw9zatuA15jl7TEAAP7ViXSr71WCCrCmh0g+qdY7aeOK0eF9zVD2NeAAF+E/mkd3icSePiFrx+Tgm1QEym0n5ZLv+fACHCuacwwvXWuo8YadydlG/yQkT1fw2MfA/oKIzc4+USSGlDxRJn/AxmFivWtAcZCPrDO4NNJKVmQEaUFSJmLxq9w8WyLfagQJjxBEUYmH2oB49psTEl0od8uXEbhy2DOuX0fG5v3r3PMkOiW/z8N+VXJVOEw8j+kIGvvbDt4uWWaxVJXEd7Zx8pJg/lAadbL6uW+HaelRbtCkKuGVBTpYfrR1yDcnbMUpgjHR/In3xR3w6DrBul3S1+u8cq2piuNIjK2cy1oQRShMnJfW6kabmwak2FJJgLb52LOLDNyE81W7tVQcjdnhIyuvVJlb3dReRLPZM6le+bzgV0vVREi9UKhQ9EBsKac+kye66tI1iPOqC0eUc9Gpuj1tlQKbrpdV580WkVGnwnZhpZbBlJ8TLZcVjv3m+iUxti0vYhDcL+IW8tC5fBFDpAdYug3mHkp1Jg5w+LnqhAUxGaHnwuIZh+V/gqcqp4YMJSmgLxj/j/+4rqG8ee9XdKzC7+7yhOX8lNSIcRxdF3GSN1X1QcSkXua/kbbMc913BGtJW3qNRTW3ZVOsIBg/3HjwwpT2mTgpSEEc+TJ7Xx/5FFJwWS8lSf3tM29+7Arrt56p4Vont5EKWju8jqTL/MUlYIgzOBUzBEtdaAbepS0WRJoReEg1T9JmGwWxKDwp9VM6DNHgrY0C2AZOdkVrZ2djS8LYouGMo5DTw6Sa4K5o3wAA");
    --sun-x: 60%;
    --sun-y: 50%;
  }
}

.playground-page {
  --background: url("assets/playground-landscape.webp");
  --background-placeholder: url("data:image/webp;base64,UklGRmICAABXRUJQVlA4IFYCAAAwDgCdASpAACQAPsFQo0unpKMhsBVaqPAYCWwAnTKEhofEeaBa+t3Eyhe7ajxgPW087y+kn4gZQGb6dfqUyurCwaxMEw4vO1cCwY8SNVjd2Wcv1oziDrTpnq/lwQO+yDgTgWg2xXf+FFifTPwAA4KAkm5ih3XDBN+sAAD+szvwghapJZe/I4BWjiFI8w6NsM2u29Nb/Gkb8dcHrEtpu3/h3U2Ng8nKdU7+Ubvt1GiBQ6oIsoPlvC0B+XWnFt0XsxKa6FpGhrVM+evRUC6SMRcN9zgBVeyCu49zUpGERzIVNDOAP9xCMwVytcpRo06ShWKhQ1/CdLweXy/DZy14YHvTB9O1yDEtB09Pp3pmquSpjW4jxb270NfeKuDBaFCHnzsVe1s9Q/1qahAhO6JjqsLaJ9dkMMXRj/dWeUVOpvEhEyouzUDxUHvS9aFXLc8WMmbcrPFT4zRXNlhye2E+6oXPPdccFFFXYo3VnmhZx0puxW8DUN8Dpva/ZE/fATZ3pqRJ6M6uBBJTnxlQ6TN4RfrNRwA70SpqPy0b4I47dfKOE7qdPfXjYpKubvwaMuZBlbmHOE4eeDYGGjSOxt2b+/lh1E4tPnf4zUnhV8Sds0McgWOlnGiyoNaAjBN66xk0WjJhFhs9WjNz3OEfuJp0aEA7BBjsG0j9ezjt7xTTyZZo/N1vfTgAX1bWBnqS3m0kMaYxy0d5oSbYpxroBw3Q7Wfus9vFeJt5bZau9G1bUg5TI92h3Xb+ZTAQyQzVtNs3tpLHJQ8lDqAgXs8dP+DKA2gxDBvHImAA");
}

@media (orientation: portrait) {
  .playground-page {
    --background: url("assets/playground-portrait.webp");
    --background-placeholder: url("data:image/webp;base64,UklGRqYCAABXRUJQVlA4IJoCAABQDgCdASorAEAAPsFMn0snpCKhsrScyPAYCWwAnTKEdeSBK3/btYRJHbv9Ee2p8xv7QetH53O+eb0xZ0cywuL0IZ9kiZj9+TzvjMbotCwQrJpP/lKqB6LbodO9+Bnjr1b9SWN8SYq6qf0B7ZyLwA8AnBRaIOk5jnAvC/AA/slB4ub4w55Xv5tZEc+uDUtRwy0br9mcPOFwuO9NsAb/6Bi7+KccD9Hjf4qF7XelxBpwwkLsAj1J/Vq/mqoY/AUgy6oiIuJaL43EomeNPCMhiYMBZbcyjNjBuIHGiw31IojjMnzjvWxvuinMyNYIoh6b5++SVOnlZoEV9St5yLEr1D2WzO0zK9m+miS2dIh/BDtf2VBRPgSiuiQu+odiq1r36V2TVoFU6Scxq0jaSGKPOemn7glTJZIs/r8SUJfLxCHb5nF2JVE6R57zSX0FZtVIqZU2bAQ00fYLepz838yMowVHeLKJHRzK/gFvhgSDBumOwdJvYbGYAnm/Jl8IGJfvcS27UMpI3BbM1XrwV44giVDFIR6Cs30TaHDL0zhn+qEGdtniKTzMNy6MRJpWsAAoGiFSHsC//HIrjyjI1uug0zWgY1a8PVWwGURjqSGBVVBgSLtoalflLIB03IfuKRY10M5+PE1UJKhCVhZngOtKaT5fQwDZy1rmkB4PdF6eWYr2uoXxVq5b9wqOfcITk/u+RT+E7sgFzBOr9J3ArBFFqjH9JsN0fRR8oHkkAzEBF3BW3j2RPtsMwjKyF5RsPeAyUpxUqa5GYjCSeXx37UjZtKGfdDMG88N1A1HQXuS5tPbaFvE/Sz5o8pQX69Poe7QS2Kf+FwGseWMTzPU6Z/Y6BD1yCwpYNt+baEFvZtnMofA8+S6FSeO2VnoAAAA=");
  }
}

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

html {
  -webkit-text-size-adjust: 100%;
}

/* Header on top, the scene filling the rest of the screen. svh excludes mobile
   browser chrome; the vh line is the fallback for browsers without it. */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
  margin: 0;
  /* Shown while the image loads, and if it never does. */
  background-color: var(--paper);
  color: var(--white);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  line-height: 1.5;
  /* Lets the layers below sit behind the page content but in front of the
     body's own background colour. */
  isolation: isolate;
}

/* ::before is the artwork; ::after is the dusk and sun glow drawn over it,
   which is fully transparent once the sunrise has finished (or without
   motion). Both start below the header so it hides none of the sky. */
body::before,
body::after {
  content: "";
  position: fixed;
  inset: var(--header-height) 0 0 0;
  z-index: -1;
}

/* The full image is layered over its placeholder, so it simply covers it
   once loaded. */
body::before {
  background-image: var(--background), var(--background-placeholder);
  background-size: cover;
  background-position: center bottom;
  background-repeat: no-repeat;
}

/* --- header ---------------------------------------------------------------- */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: var(--header-height);
  padding: 0 clamp(1rem, 4vw, 2rem);
  background-color: var(--paper);
  color: var(--ink);
  box-shadow: 0 1px 0 rgb(31 45 74 / 0.08), 0 2px 12px rgb(31 45 74 / 0.06);
}

.brand {
  margin: 0;
  color: inherit;
  font-size: clamp(1.15rem, 1rem + 0.8vw, 1.5rem);
  font-weight: 800;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
}

/* Hamburger and close buttons: 44px targets, icon drawn by an inline SVG. */
.icon-button {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-right: -0.5rem;
  padding: 0;
  border: 0;
  border-radius: 0.5rem;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

.icon-button:hover {
  background-color: rgb(31 45 74 / 0.08);
}

/* --- menu ------------------------------------------------------------------ */

/* A popover pinned to the right edge as a full-height drawer. The browser
   hides it (display: none) until it is opened. */
.menu {
  inset: 0 0 0 auto;
  width: min(20rem, 85vw);
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0 clamp(1rem, 4vw, 2rem) 2rem;
  border: 0;
  background-color: var(--paper);
  color: var(--ink);
  box-shadow: -8px 0 32px rgb(16 20 40 / 0.2);
}

.menu::backdrop {
  background-color: rgb(16 20 40 / 0.35);
}

.menu-top {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  height: var(--header-height);
}

.menu-heading {
  margin: 0.5rem 0 0.75rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgb(31 45 74 / 0.7);
}

.menu-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.menu-item {
  display: block;
  padding: 0.75rem 0;
  border-top: 1px solid rgb(31 45 74 / 0.12);
  color: inherit;
  font-size: 1.15rem;
  font-weight: 600;
  text-decoration: none;
}

.menu-item[href]:hover {
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

/* Slide in from the right. @starting-style gives the open state somewhere to
   animate from; browsers without it simply show the menu. */
@media (prefers-reduced-motion: no-preference) {
  .menu,
  .menu::backdrop {
    transition:
      transform 280ms cubic-bezier(0.22, 1, 0.36, 1),
      background-color 280ms ease,
      overlay 280ms allow-discrete,
      display 280ms allow-discrete;
  }

  .menu {
    transform: translateX(100%);
  }

  .menu:popover-open {
    transform: none;
  }

  @starting-style {
    .menu:popover-open {
      transform: translateX(100%);
    }

    .menu:popover-open::backdrop {
      background-color: rgb(16 20 40 / 0);
    }
  }
}

/* --- main ------------------------------------------------------------------ */

/* The scene below the header. */
.stage {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  padding: max(7vh, 2.5rem) clamp(1rem, 5vw, 3rem) 2rem;
  text-align: center;
}

.title {
  margin: 0;
  max-width: 14ch;
  font-size: clamp(3.25rem, 1rem + 9vw, 7.5rem);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.01em;
  text-wrap: balance;
  text-shadow:
    0 2px 4px rgb(0 0 0 / 0.35),
    0 4px 28px rgb(0 0 0 / 0.3);
}

/* The home page's way on to the playground: a paper sign low over the waves,
   clear of the sun and sky. */
.home-page .stage {
  justify-content: flex-end;
  padding-bottom: max(14vh, 3rem);
}

.signpost {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.5rem;
  border-radius: 999px;
  background-color: var(--paper);
  color: var(--ink);
  font-size: clamp(1.05rem, 1rem + 0.4vw, 1.25rem);
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 4px 20px rgb(16 20 40 / 0.25);
}

.signpost svg {
  flex: none;
}

@media (prefers-reduced-motion: no-preference) {
  .signpost svg {
    transition: transform 200ms ease;
  }

  .signpost:hover svg {
    transform: translateX(4px);
  }
}

/* Visible to screen readers only. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- page changes ---------------------------------------------------------- */

/* Moving between pages crossfades the scene while the header stays put.
   Browsers without cross-document view transitions just load the next page. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }

  .site-header {
    view-transition-name: site-header;
  }
}

/* --- entrance -------------------------------------------------------------- */

/* Registered so they animate smoothly. Browsers without @property flip each
   value halfway through instead, so the sunrise simply jumps to day. */
@property --dusk {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

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

@property --sun-rise {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

@media (prefers-reduced-motion: no-preference) {
  /* Only the scene animates; the header is there from the first paint.
     0.0s  dusk: dim and colourless
     0.0s  colour fades in as the scene brightens to day (2.4s)
     0.3s  warm glow rises from the sun and fades into daylight */
  body::before {
    animation: daybreak 2400ms cubic-bezier(0.4, 0, 0.3, 1) both;
  }

  /* --dusk scales a cool night wash; --glow and --sun-rise drive a warm
     radial glow centred on the drawn sun, starting a little below it. */
  body::after {
    background-image:
      radial-gradient(
        circle at var(--sun-x) calc(var(--sun-y) + 12% - var(--sun-rise)),
        rgb(255 196 120 / calc(var(--glow) * 0.55)),
        rgb(255 150 90 / calc(var(--glow) * 0.25)) 18vmax,
        transparent 42vmax
      ),
      linear-gradient(
        to bottom,
        rgb(16 20 52 / calc(var(--dusk) * 0.7)),
        rgb(58 36 78 / calc(var(--dusk) * 0.45))
      );
    animation:
      night-lifts 2400ms cubic-bezier(0.4, 0, 0.3, 1) both,
      sun-glow 2400ms ease-in-out 300ms both;
  }

  /* Set by main.js until the background has downloaded, so a slow connection
     waits at dusk rather than playing the sunrise over blank paper. */
  .is-loading body::before,
  .is-loading body::after {
    animation-play-state: paused;
  }

  @keyframes daybreak {
    from {
      filter: grayscale(1) brightness(0.45) contrast(1.1);
    }
    to {
      filter: none;
    }
  }

  @keyframes night-lifts {
    from {
      --dusk: 1;
    }
    to {
      --dusk: 0;
    }
  }

  @keyframes sun-glow {
    0% {
      --glow: 0;
      --sun-rise: 0%;
    }
    45% {
      --glow: 1;
    }
    100% {
      --glow: 0;
      --sun-rise: 12%;
    }
  }
}

/* --- playground and error pages ------------------------------------------- */

/* The sunrise belongs to the home page. These selectors outrank the entrance
   rules above. The error page also leaves its scene colourless. */
.playground-page::before,
.playground-page::after,
.error-page::before,
.error-page::after {
  animation: none;
}

.error-page::before {
  filter: grayscale(1);
}

/* The error page puts white text over the pale artwork, so it darkens the top
   of the scene behind it. */
.error-page .stage {
  background-image: linear-gradient(to bottom, rgb(18 32 64 / 0.45), rgb(18 32 64 / 0) 55%);
}

.message {
  margin: 0;
  max-width: 32ch;
  font-size: clamp(1.1rem, 1rem + 0.6vw, 1.4rem);
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.45);
}

.home-link {
  color: var(--white);
  font-weight: 600;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.25em;
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.45);
}

.home-link:hover {
  text-decoration-thickness: 3px;
}

:focus-visible {
  outline: 3px solid var(--white);
  outline-offset: 4px;
}

/* White would vanish against the off-white header and menu. */
.site-header :focus-visible,
.menu :focus-visible {
  outline-color: var(--ink);
  outline-offset: 2px;
}
