/* Curly Tail Studio */

/* Fallback for devices without Helvetica Neue (Windows, Android).
   Browsers only download it when it's actually needed. Its letters are
   0.022em further apart than Inter Tight's own, so the site's tracking (set
   for Helvetica Neue) gives the same density (tools/make-web-font.py). */
@font-face {
  font-family: "Inter Tight";
  src: url("/fonts/inter-tight-spaced-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- Tokens ---------- */

:root {
  --bg: #f4f3ef;
  --fg: #121211;
  --muted: #6b6a65;
  --line: rgb(18 18 17 / 0.14);
  --tail: #dc8430;
  --tail-fur: #3f3025;

  --sans: "Helvetica Neue", "Inter Tight", Helvetica, Arial, sans-serif;
  --gutter: clamp(16px, 2vw, 32px);
  --mark: clamp(44px, 4.2vw, 68px);
  --fade: clamp(48px, 6vw, 110px);
  --text: clamp(15px, 0.9rem + 0.15vw, 17px);
  --statement: clamp(28px, 1rem + 2.6vw, 58px);
  --title: clamp(22px, 0.9rem + 1vw, 32px);
  --space: clamp(96px, 16vh, 200px);
  --ease: cubic-bezier(0.19, 1, 0.22, 1);

  color-scheme: light;
}

:root[data-theme="dark"] {
  --bg: #0e0e0d;
  --fg: #ecebe6;
  --muted: #8e8d88;
  --line: rgb(236 235 230 / 0.16);
  --tail-fur: #efdcc0;
  color-scheme: dark;
}

/* No-JS fallback: follow the system setting. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0e0e0d;
    --fg: #ecebe6;
    --muted: #8e8d88;
    --line: rgb(236 235 230 / 0.16);
    --tail-fur: #efdcc0;
    color-scheme: dark;
  }
}

/* ---------- Base ---------- */

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

html {
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent; /* no grey/blue box on tapped links (phones) */
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: var(--text);
  line-height: 1.45;
  font-kerning: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

/* Safari on Mac (class set in <head>): scroll inside <body>, so content is
   clipped at the top edge and never shows through Safari's glass tab bar. */
.inner-scroll { height: 100%; overflow: hidden; }
.inner-scroll body { height: 100%; overflow-y: auto; }
.inner-scroll body:focus { outline: none; } /* focused only so keyboard scrolling works */
[tabindex="-1"]:focus { outline: none; } /* sections focused by in-page links */
@media print {
  .inner-scroll,
  .inner-scroll body { height: auto; overflow: visible; }
}

h1, h2, h3, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }

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

::selection { background: var(--tail); color: #121211; }

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
  border-radius: 2px;
}

/* Contrast flip on hover: a white layer in difference mode sweeps in from the
   left and inverts what's under it: black on light becomes white on black,
   and the other way round in dark mode. It also marks keyboard focus. */
/* The flip needs an opaque colour painted on the same layer as the link. In
   Safari's inner-scroll mode the body's background sits on a separate layer
   from the scrolling content, so give the content its own background. */
main,
.foot { background-color: var(--bg); }

.bar-nav a,
.foot a,
.hero-meta a,
.privacy-list a {
  position: relative;
  display: inline-block;
  line-height: 1; /* glyphs then sit at ~0.14em–1.08em of the box */
}
.email .fit { position: relative; }
.bar-nav a::after,
.foot a::after,
.hero-meta a::after,
.privacy-list a::after,
.email .fit::after {
  content: "";
  position: absolute;
  inset: -0.08em -0.3em -0.3em; /* small text: ~0.22em around the glyphs */
  background: #fff;
  mix-blend-mode: difference;
  pointer-events: none;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}
/* Hover only where there's a real pointer: touch screens keep a tapped link
   in :hover, so the band would stay after the tap. */
@media (hover: hover) {
  .bar-nav a:hover::after,
  .foot a:hover::after,
  .hero-meta a:hover::after,
  .privacy-list a:hover::after,
  .email:hover .fit::after { transform: scaleX(1); }
}
.bar-nav a:focus-visible::after,
.foot a:focus-visible::after,
.hero-meta a:focus-visible::after,
.privacy-list a:focus-visible::after,
.email:focus-visible .fit::after { transform: scaleX(1); }
.bar-nav a:focus-visible,
.foot a:focus-visible,
.hero-meta a:focus-visible,
.privacy-list a:focus-visible,
.email:focus-visible { outline: none; }
/* Touch screens have no hover: a tap plays the flip once instead, the band
   sweeping in and then out to the right (main.js adds .tapped). */
.bar-nav a.tapped::after,
.foot a.tapped::after,
.hero-meta a.tapped::after,
.privacy-list a.tapped::after,
.email.tapped .fit::after { animation: tap-flip 1.1s cubic-bezier(0.65, 0, 0.35, 1); }
.email.tapped .fit::after { animation-duration: 1.8s; } /* the emails' slower sweep */
@keyframes tap-flip {
  from { transform: scaleX(0); transform-origin: left; }
  45% { transform: scaleX(1); transform-origin: left; }
  55% { transform: scaleX(1); transform-origin: right; } /* at full width the switch doesn't show */
  to { transform: scaleX(0); transform-origin: right; }
}

/* ---------- Fixed corners: mark + controls ---------- */

/* Soft edge: a solid strip of page colour behind the corners, fading out
   below them, so content dissolves before it reaches the mark. Not a bar. */
.bar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 10;
  height: calc(var(--gutter) + var(--mark));
  background-color: var(--bg);
  --name-size: calc(var(--mark) * 0.492); /* the site name (letters span 0.936em: half the mark) */
}
.bar::after {
  content: "";
  pointer-events: none; /* content in the fade stays clickable */
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: var(--fade);
  background: linear-gradient(to bottom,
    var(--bg),
    color-mix(in srgb, var(--bg) 82%, transparent) 25%,
    color-mix(in srgb, var(--bg) 50%, transparent) 50%,
    color-mix(in srgb, var(--bg) 20%, transparent) 75%,
    transparent);
}

/* Positioned inside the fixed .bar (not fixed themselves), so they don't form
   their own layers: the links' contrast flip needs the strip's colour behind it. */
.mark,
.bar-nav {
  position: absolute;
  top: var(--gutter);
}

.mark {
  left: var(--gutter);
  display: block;
  width: var(--mark);
  transition: rotate 0.8s var(--ease);
}
@media (hover: hover) { .mark:hover { rotate: -40deg; } }
.mark svg { display: block; width: 100%; height: auto; overflow: visible; will-change: transform; }

/* The site name, next to the mark. It drops in from above once the big name
   has scrolled under the strip, and while "Who we are" is showing (main.js
   sets .show-title). Its letters (the tops of C, l, d to the bottom of the y)
   are half the mark's height and centred on it, a quarter mark to its right.
   The box clips at the screen's top edge, so the hidden name is never painted
   above the page (Safari's glass tab bar). */
.bar-title {
  /* Where the letters start: a quarter of the mark's height (0.921 × --mark) down. */
  --drop: calc(var(--gutter) + var(--mark) * 0.23);
  position: absolute;
  top: 0;
  left: calc(var(--gutter) + var(--mark) * 1.25);
  padding: calc(var(--drop) - 0.13em) 0 0.15em; /* the letters start 0.13em into the line */
  overflow: hidden;
  font-size: var(--name-size);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.035em;
  white-space: nowrap;
  pointer-events: none; /* not clickable while hidden */
}
.bar-title > span {
  display: block;
  translate: 0 calc(-100% - var(--drop));
}
.show-title .bar-title { pointer-events: auto; }
.show-title .bar-title > span { translate: none; }

.bar-nav {
  /* Level with the site name's middle line (halfway up its capitals, 0.714em
     tall): the name's baseline is 0.75em below where its letters start
     (--drop), and this row's text, centred in the 1.25em icon row, has its
     middle line 0.638em down. main.js corrects the last pixel (rounding).
     Not a translate: a transform would isolate the links' contrast flip
     from the strip's colour. */
  top: calc(var(--gutter) + var(--mark) * 0.23 + var(--name-size) * 0.393 - 0.638em);
  right: var(--gutter);
  display: flex;
  gap: clamp(20px, 2.4vw, 40px);
  align-items: center;
  line-height: 1;
}

.theme {
  appearance: none;
  border: 0;
  padding: 0;
  margin: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  display: inline-flex;
  gap: 0.5em;
}
.theme svg {
  display: block;
  width: 1.25em;
  height: 1.25em;
  transition: rotate 0.7s var(--ease);
}
:root[data-theme="dark"] .theme svg { rotate: 180deg; }
@media (hover: hover) {
  .theme:hover svg { rotate: -25deg; }
  :root[data-theme="dark"] .theme:hover svg { rotate: 155deg; }
}

/* ---------- Hero: the name is the header ---------- */

.hero {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--gutter);
  padding-bottom: calc(var(--gutter) * 0.6);
}

.hero-meta {
  display: flex;
  justify-content: space-between;
  gap: var(--gutter);
  padding-bottom: clamp(12px, 2.4vh, 28px);
}

.wordmark {
  font-size: 12.1vw; /* JS refines this to fit the width exactly */
  font-weight: 500;
  line-height: 0.82;
  letter-spacing: -0.05em;
  white-space: nowrap;
  margin-left: -0.045em; /* optical: pull the C to the gutter */
  padding-bottom: 0.2em; /* room for the descender of the y */
}

.fit { display: inline-block; white-space: nowrap; }

/* The tight tracking crowds the arm of the r into the l in "curly": give the r
   its natural spacing back (the name, the site name up top, the emails). */
.kern { margin-right: 0.035em; }

/* Each word rises out of its own clipping box. */
.w {
  display: inline-block;
  overflow: hidden;
  padding: 0 0.06em 0.26em;
  margin: 0 -0.06em -0.26em;
  vertical-align: top;
}
.w > span { display: inline-block; }

/* Desktop home: the name stacks in three lines flush right, sized to fit the
   screen (main.js), with the descriptor bottom-left on the baseline of "Studio". */
@media (min-width: 721px) {
  .hero-home {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--gutter);
    align-items: end;
    padding-top: calc(var(--gutter) + var(--mark) + var(--fade)); /* start below the soft top edge */
  }
  .hero-home .hero-meta {
    grid-column: 1 / span 4;
    grid-row: 1;
    padding-bottom: 0; /* main.js lifts it onto the baseline of "Studio" */
  }
  /* Mirrors the name: four lines flush left, at ~4x the body size, shrinking
     on narrow screens so "& Marketing" stays within its four columns. */
  .hero-home .hero-meta p {
    font-size: min(calc(var(--text) * 4), 5.4vw);
    line-height: 1.05;
    letter-spacing: -0.025em;
  }
  .hero-home .hero-meta .dl { display: block; }
  .hero-home .wordmark {
    grid-column: 5 / -1;
    grid-row: 1;
    font-size: min(20vw, 26vh); /* main.js refines this */
    line-height: 1.02; /* room for the y of Curly above the l of Tail */
    letter-spacing: -0.035em;
    text-align: right;
    margin-left: 0;
    margin-right: -0.04em; /* optical: push the o/y/l to the gutter */
  }
  .hero-home .wordmark .w {
    display: block;
    width: max-content;
    margin-left: auto;
  }
}

/* ---------- Sections ---------- */

.section {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter);
  padding: var(--space) var(--gutter) 0;
}

.statement {
  font-size: var(--statement);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -0.028em;
  text-wrap: pretty;
  max-width: 24ch;
}
.soft { color: var(--tail); }

/* The middle section: the statement and the services list side by side, a
   centred pair with the gap on the page's middle line. Widths and gap are in
   ch of the statement's font size (the section sets it, the list's rows reset
   it); both columns shrink equally when the row is narrower. Phones stack them.
   Height (desktop): just enough that with the pair in the middle of the
   screen, the name above sits under the solid top strip and the email below
   starts at the screen's bottom edge (the email section adds no space of its
   own). The padding keeps at least --space around the pair on short screens. */
.pair { row-gap: calc(var(--space) / 2); } /* phones, where they stack */
@media (min-width: 721px) {
  /* The same two columns on "Who we are" (photo and text) and the privacy
     page, so all pages share their side margins and middle gap. */
  .pair,
  .about-intro,
  .privacy {
    font-size: var(--statement);
    grid-template-columns: repeat(2, minmax(0, 20ch));
    justify-content: center;
    column-gap: 4ch;
  }
  .pair {
    --strip: calc(var(--gutter) + var(--mark)); /* the solid strip behind the corners */
    align-content: center;
    align-items: start;
    min-height: calc(100vh - var(--strip));
    min-height: calc(100svh - var(--strip));
    padding-bottom: calc(var(--space) + var(--strip));
  }
  .pair + .contact { padding-top: 0; }
}
.services { border-bottom: 1px solid var(--line); }
.services li {
  display: grid;
  grid-template-columns: 4fr 5fr;
  column-gap: var(--gutter);
  align-items: center; /* the name sits level with the middle of its description */
  padding: clamp(18px, 2.2vw, 28px) 0;
  border-top: 1px solid var(--line);
  font-size: var(--text);
}
.services h2,
.services h3,
.privacy-list h2 {
  font-size: var(--title);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.02em;
}
.services p { max-width: 36ch; }

/* ---------- Contact ---------- */

.email {
  grid-column: 1 / -1;
  display: block;
  font-size: 9.5vw;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.05em;
  white-space: nowrap;
  margin-left: -0.03em;
}
/* Contrast flip (see top). Glyphs span 0.14em–1.08em of the line box
   (Helvetica Neue); pad 0.09em above and below so the y fits inside. */
.email .fit::after {
  inset: 0.05em -0.08em -0.17em;
  transition-duration: 0.8s;
}

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

/* The copyright on the left, "Privacy" and "Back to top" on the right. */
.foot {
  display: flex;
  justify-content: flex-end;
  column-gap: clamp(20px, 2.4vw, 40px); /* the same gap as the top-right corner */
  padding: clamp(56px, 10vh, 120px) var(--gutter) var(--gutter);
  color: var(--muted);
}
.foot p:first-child { margin-right: auto; }
.foot p + p { flex-shrink: 0; } /* if space runs out, the copyright wraps, not the links */
@media (hover: hover) { .foot a:hover { color: var(--fg); } }

/* ---------- Who we are: a second page that slides in from the right ---------- */

/* With JS (class set in <head>) it's a fixed panel parked off-screen right;
   "Who we are" slides the home page out left and this in. Without JS it's
   just a section after the footer. */
.js .page {
  transition: transform 0.9s cubic-bezier(0.65, 0, 0.35, 1);
}
.js .about {
  position: fixed;
  inset: 0;
  z-index: 5; /* under the corners (.bar is 10) */
  overflow-y: auto;
  overscroll-behavior: contain;
  background-color: var(--bg);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 0.9s cubic-bezier(0.65, 0, 0.35, 1), visibility 0s 0.9s;
}
.js.about-open .page { transform: translateX(-100%); }
/* The home page behind it can't scroll while it's open. Otherwise a wheel or
   trackpad over the top strip moves it unseen (the strip isn't part of this
   panel), and the back arrow returns somewhere else. The scrollbar keeps its
   space while hidden, so nothing shifts where scrollbars take up room. */
.js.about-open,
.about-open.inner-scroll body { overflow: hidden; }
.js:not(.inner-scroll),
.inner-scroll body { scrollbar-gutter: stable; }
.js.about-open .about {
  transform: none;
  visibility: visible;
  transition: transform 0.9s cubic-bezier(0.65, 0, 0.35, 1), visibility 0s;
}
.js.no-slide .page,
.js.no-slide .about { transition: none; } /* opening straight onto /#about */

/* Own background on the scrolling content: Safari paints a scroller's
   background on a separate layer, and the links' contrast flip needs it. */
/* The nav link reads "Who we are" on the home page and becomes a back arrow on this one. */
.about-arrow { display: none; width: 1.25em; height: 1.25em; }
.about-open .about-label { display: none; }
.about-open .about-arrow { display: block; }
/* The arrow gets no contrast flip: collapse the band instantly (beats the
   :hover rule). It isn't display: none, so when "Who we are" comes back under
   the pointer the band sweeps in again instead of appearing already finished. */
.about-open .bar-nav .about-link::after { transform: scaleX(0); transition: none; }
.about-open .about-link:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }

.about-inner { background-color: var(--bg); }

/* Two lines, sized by main.js so the longer one fills the text column
   (the privacy page's title too, in one line). */
.about-title,
.privacy-title {
  font-size: 8vw; /* main.js refines this */
  font-weight: 500;
  line-height: 0.95;
  letter-spacing: -0.045em;
  margin-left: -0.04em;
  margin-bottom: clamp(24px, 4vh, 48px);
}
.about-title:focus { outline: none; } /* focused only so screen readers land here */
.about-intro { padding-top: calc(var(--gutter) + var(--mark) + var(--fade)); }

/* Photo on the left; title and statement beside it, level with its top. */
.about-intro { row-gap: clamp(32px, 6vh, 64px); }
/* The photo stays on the first screen: main.js sets --photo-max to the height
   left below its top, and the width follows the photo's shape (1400 x 1880). */
.about-photo {
  grid-column: 1 / span 5;
  margin: 0;
  align-self: start;
  container-type: inline-size;
}
.about-photo img {
  display: block;
  width: min(100cqi, var(--photo-max, 9999px) * 0.7447);
  height: auto;
}
.about-text { grid-column: 7 / -1; align-self: start; }
.about-text .statement { max-width: none; } /* main.js sizes it to end at the photo's bottom */
@media (min-width: 721px) {
  /* Desktop: the home page's two columns (see .pair): the photo fills the
     left one, the title and statement the right one. The section is a screen
     tall with the pair centred in it (equal padding top and bottom, so it
     never rises into the soft edge), and main.js caps the photo to the space
     between the two, so on short screens it's narrower than its column (still
     flush left). The text then starts one gap after the photo and takes the
     rest (main.js sets --photo-w; the % is of the text's column), so the
     middle gap stays the home page's. The email section adds no space of its
     own, so the email starts at the first screen's bottom edge. */
  .about-photo { grid-column: 1; }
  .about-text {
    grid-column: 2;
    margin-left: calc(var(--photo-w, 100%) - 100%);
  }
  .about-intro {
    min-height: 100vh;
    min-height: 100svh;
    padding-bottom: calc(var(--gutter) + var(--mark) + var(--fade));
    align-content: center;
  }
  .about-intro + .contact { padding-top: 0; }
}

/* ---------- Privacy ---------- */

/* The home page's two columns (see .pair): the title and the short version on
   the left, the details on the right, ruled like the services list. On
   desktop the left side stays in view while the details scroll past. */
.privacy {
  padding-top: calc(var(--gutter) + var(--mark) + var(--fade)); /* below the soft top edge */
  row-gap: calc(var(--space) / 2); /* phones, where they stack */
}
.privacy-intro { align-self: start; }
.privacy-date {
  margin-top: clamp(24px, 4vh, 48px);
  font-size: var(--text);
  color: var(--muted);
}
.privacy-list {
  font-size: var(--text);
  border-bottom: 1px solid var(--line);
}
.privacy-list section {
  padding: clamp(18px, 2.2vw, 28px) 0;
  border-top: 1px solid var(--line);
}
.privacy-list h2 { margin-bottom: 0.5em; }
.privacy-list p { max-width: 64ch; }
.privacy-list p + p { margin-top: 0.75em; }
.privacy-list a {
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--muted);
  text-underline-offset: 0.2em;
}
@media (min-width: 721px) {
  .privacy-intro {
    position: sticky;
    top: calc(var(--gutter) + var(--mark) + var(--fade));
  }
}

/* ---------- Small screens ---------- */

@media (max-width: 720px) {
  .section { grid-template-columns: minmax(0, 1fr); }
  /* Keep the site name at least 20px clear of "Who we are" (which with the
     theme icon is about 118px wide): the name is 6.93em wide and starts
     71px in, so it shrinks a little below 375px wide. */
  .bar { --name-size: min(var(--mark) * 0.492, (100vw - 225px) / 6.93); }
  .statement,
  .services,
  .email,
  .about-photo,
  .about-text { grid-column: 1; }

  /* Stack the name so it fills the screen. */
  .wordmark { font-size: 27vw; line-height: 0.86; }
  .wordmark .w { display: block; width: max-content; }

  /* Home, like desktop: the name flush right at the top, below the soft
     edge, and the descriptor in four lines flush left under it. */
  .hero-home {
    justify-content: flex-start;
    padding-top: calc(var(--gutter) + var(--mark) + var(--fade));
  }
  .hero-home .wordmark {
    line-height: 1.02; /* room for the y of Curly above the l of Tail */
    letter-spacing: -0.035em;
    margin-left: -0.093em; /* optical: pull the S to the gutter (its side bearing plus the .w padding) */
    margin-right: -0.04em; /* optical: push the o/y/l to the gutter */
  }
  .hero-home .wordmark .w { margin-left: auto; }
  .hero-home .hero-meta { order: 1; padding-bottom: 0; }
  .hero-home .hero-meta p {
    font-size: 8vw; /* about a quarter of the name */
    line-height: 1.05;
    letter-spacing: -0.025em;
  }
  .hero-home .hero-meta .dl { display: block; }

  .services li { grid-template-columns: 1fr; row-gap: 6px; }

  /* One line: the text shrinks where the words don't fit beside the padding
     and the two gaps (88px). They're 19.3em wide in Helvetica Neue and 19.42em
     in the fallback, or 22.3em and 22.42em with "Back to the studio". */
  .foot {
    font-size: min(var(--text), (100vw - 88px) / 19.6);
    /* A little in from the edges and up from the bottom (8px, 12px):
       clear of the phone's rounded corners. */
    padding-inline: calc(var(--gutter) + 8px);
    padding-bottom: calc(var(--gutter) + 12px);
  }
  .about .foot { font-size: min(var(--text), (100vw - 88px) / 22.6); }
}

/* ---------- Motion ---------- */

@media (prefers-reduced-motion: no-preference) {
  html,
  .inner-scroll body { scroll-behavior: smooth; }

  .w > span { animation: rise 1.3s var(--ease) both; }
  .w:nth-child(2) > span { animation-delay: 0.08s; }
  .w:nth-child(3) > span { animation-delay: 0.16s; }
  .bar-title > span { transition: translate 0.8s var(--ease); }
  .about-open.show-title .bar-title > span { transition-delay: 0.6s; } /* lands as the page slide ends */
  .hero-meta,
  .mark,
  .bar-title,
  .bar-nav { animation: fade 1s 0.5s backwards; } /* not "both": a finished fill would keep them on their own layer and break the contrast flip */

  @keyframes rise { from { transform: translateY(110%); } }
  @keyframes fade { from { opacity: 0; } }
}

/* Theme switch: the new theme spreads out from the toggle (see main.js). */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}


.wordmark-404 { font-size: min(40vw, 62svh); }

@media (prefers-reduced-motion: reduce) {
  .js .page,
  .js .about { transition: none; }
}
