/* KitCode Studios website. One stylesheet, no build step.
   Written phone-first: the base rules are the narrow layout and the
   min-width blocks further down widen it. */

/* ------------------------------------------------------------------ fonts */
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url("../fonts/plus-jakarta-sans-latin-500-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url("../fonts/plus-jakarta-sans-latin-600-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-display: swap;
  font-weight: 800;
  src: url("../fonts/plus-jakarta-sans-latin-800-normal.woff2") format("woff2");
}
/* Arial, resized to Plus Jakarta Sans's proportions (measured: its regular
   is 5.6% narrower than our 500, its bold about level with our 600 and
   800). It shows for the instant before the real font arrives, and because
   it takes up the same room, nothing on the page moves when the swap
   happens. */
@font-face {
  font-family: "Jakarta Fallback";
  font-weight: 100 500;
  src: local("Arial");
  size-adjust: 105.6%;
  ascent-override: 98.5%;
  descent-override: 20.8%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Jakarta Fallback";
  font-weight: 600 900;
  src: local("Arial Bold"), local("Arial-BoldMT");
  size-adjust: 100.5%;
  ascent-override: 103.5%;
  descent-override: 21.9%;
  line-gap-override: 0%;
}

/* ----------------------------------------------------------------- tokens */
:root {
  color-scheme: light dark;

  /* The brand's colours, as given in brand/make-logo.mjs. */
  --blue: #1f6ffa;
  --violet: #744bf8;
  --indigo: #335cf6;
  --navy: #0a1e33;
  --slate: #60738b;

  /* What the page uses them for. Light appearance. */
  --page: #ffffff;
  --ink: var(--navy);
  --ink-2: var(--slate); /* 4.86:1 on white */
  --tint: #f2f5fa;
  --tint-strong: #e6ebf3;
  --line: #dde3ec;
  --action: var(--indigo); /* white on it: 5.25:1 */
  --action-hover: #2a4fe0;
  --on-action: #ffffff;
  --link: var(--indigo);
  --focus: var(--indigo);

  /* The dark panel an app is shown on. Dark in both appearances. */
  --stage: var(--navy);
  --stage-ink: #ffffff;
  --stage-ink-2: #a3b1c6; /* 7.75:1 on navy */
  --stage-line: rgb(255 255 255 / 0.12);
  --stage-edge: transparent;
  --stage-hover: #0f2843;
  --stage-link: #8aa2ff; /* 6.97:1 on navy, 6.02:1 on the dark panel; things you can press on a panel */
  --paper-dim: none;
  --show-in-light: block;
  --show-in-dark: none;

  --font: "Plus Jakarta Sans", "Jakarta Fallback", system-ui, -apple-system, "Segoe UI", sans-serif;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --gutter: 20px;
  --tile-radius: 28px;
  --tile-pad: 24px;
  --card-pad: 24px;
}

/* Dark appearance. It follows the device's setting. The site has no switch
   of its own, but where the page is shown inside a viewer that offers one
   (Claude's Artifacts set data-theme on the page), that choice wins, which
   is why the same values are listed twice. Keep the two lists identical. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --page: var(--navy);
    --ink: #f4f7fb;
    --ink-2: #a3b1c6;
    --tint: #122a45;
    --tint-strong: #1a3555;
    --line: rgb(255 255 255 / 0.12);
    --action: #6d8bff; /* navy on it: 5.45:1 */
    --action-hover: #8aa2ff;
    --on-action: var(--navy);
    --link: #6d8bff; /* 5.45:1 on navy */
    --focus: #8aa2ff;
    --stage: #122a45; /* one step up from the page, so it still reads as a panel */
    --stage-ink-2: #a3b1c6; /* 6.70:1 */
    --stage-edge: rgb(255 255 255 / 0.08);
    --stage-hover: #173252;
    --paper-dim: brightness(0.9); /* a white sheet glares on a dark page */
    --show-in-light: none;
    --show-in-dark: block;
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --page: var(--navy);
  --ink: #f4f7fb;
  --ink-2: #a3b1c6;
  --tint: #122a45;
  --tint-strong: #1a3555;
  --line: rgb(255 255 255 / 0.12);
  --action: #6d8bff; /* navy on it: 5.45:1 */
  --action-hover: #8aa2ff;
  --on-action: var(--navy);
  --link: #6d8bff; /* 5.45:1 on navy */
  --focus: #8aa2ff;
  --stage: #122a45; /* one step up from the page, so it still reads as a panel */
  --stage-ink-2: #a3b1c6; /* 6.70:1 */
  --stage-edge: rgb(255 255 255 / 0.08);
  --stage-hover: #173252;
  --paper-dim: brightness(0.9); /* a white sheet glares on a dark page */
  --show-in-light: none;
  --show-in-dark: block;
  color-scheme: dark;
}
:root[data-theme="light"] {
  color-scheme: light;
}

@media (prefers-contrast: more) {
  :root {
    --ink-2: var(--ink);
    --stage-ink-2: var(--stage-ink);
    --line: currentColor;
    --stage-line: rgb(255 255 255 / 0.5);
  }
}

@media (min-width: 600px) {
  :root {
    --gutter: 32px;
    --tile-radius: 36px;
    --tile-pad: 40px;
    --card-pad: 36px;
  }
}
@media (min-width: 900px) {
  :root {
    --gutter: 40px;
    --tile-radius: 44px;
    --tile-pad: 72px;
    --card-pad: 44px;
  }
}

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

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

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

h1,
h2,
h3,
p,
dl,
dd,
ul,
ol,
figure {
  margin: 0;
  padding: 0;
}

ul,
ol {
  list-style: none;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: inherit;
}

::selection {
  background: var(--indigo);
  color: #ffffff;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  border-radius: 6px;
}

.container {
  width: 100%;
  max-width: calc(1120px + 2 * var(--gutter));
  margin-inline: auto;
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.line {
  display: inline-block;
}

.skip-link {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 10;
  padding: 10px 16px;
  border-radius: 999px;
  background: var(--action);
  color: var(--on-action);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
}
.skip-link:focus {
  transform: none;
}

/* A fact only the owner can supply and hasn't yet. The check script counts
   these, and the site isn't ready to publish while any are left. */
mark.fill {
  background: #ffe9a8;
  color: #3d2c00;
  padding: 0.1em 0.35em;
  border-radius: 6px;
  font-weight: 600;
}

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-grid;
  place-items: center;
  min-height: 44px;
  padding: 0 20px;
  border: 1px solid transparent; /* shows as an outline in forced colours */
  border-radius: 999px;
  font: inherit;
  font-weight: 600;
  font-size: 1rem;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  transition: transform 160ms var(--ease-out), background-color 150ms ease;
}
.btn:active {
  transform: scale(0.97);
}
.btn--primary {
  background: var(--action);
  color: var(--on-action);
}
.btn--quiet {
  background: var(--tint);
  color: var(--ink);
}
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover {
    background: var(--action-hover);
  }
  .btn--quiet:hover {
    background: var(--tint-strong);
  }
}

/* ----------------------------------------------------------------- header */
.site-header__inner {
  display: flex;
  flex-wrap: wrap; /* a last resort at very large text sizes; never sideways scrolling */
  align-items: center;
  justify-content: space-between;
  gap: 8px 16px;
  padding-top: max(20px, env(safe-area-inset-top));
  padding-bottom: 12px;
}

.site-header__brand {
  display: block;
  flex: none;
  border-radius: 8px;
  transition: transform 160ms var(--ease-out);
}
.site-header__brand:active {
  transform: scale(0.98);
}
.site-header__brand img {
  width: auto;
  height: 44px;
}

/* Only in the copy made for Claude's Artifacts (dev/website-artifact.mjs),
   where both logos are in the page and the appearance picks one. The real
   site uses <picture>, which loads just the one it needs. */
.site-header__brand .only-light {
  display: var(--show-in-light);
}
.site-header__brand .only-dark {
  display: var(--show-in-dark);
}

@media (max-width: 359px) {
  .site-header__brand img {
    height: 40px;
  }
}
@media (min-width: 900px) {
  .site-header__inner {
    padding-top: 28px;
  }
  .site-header__brand img {
    height: 52px;
  }
}

/* ------------------------------------------------------------------- hero */
.hero {
  padding-top: clamp(40px, 10vw, 104px);
  padding-bottom: clamp(40px, 7vw, 80px);
}

.hero__title {
  font-size: clamp(2.75rem, 1rem + 8vw, 6rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
  text-wrap: balance;
}

.hero__lede {
  margin-top: clamp(20px, 3vw, 32px);
  max-width: 36rem;
  color: var(--ink-2);
  font-size: clamp(1.125rem, 1.02rem + 0.5vw, 1.375rem);
  line-height: 1.5;
  text-wrap: pretty;
}
/* With room to spare, each sentence starts its own line, so the first
   line never ends on the opening word of the next sentence. */
@media (min-width: 600px) {
  .hero__lede {
    max-width: 44rem;
  }
  .hero__lede .sentence {
    display: inline-block;
  }
}

/* ------------------------------------------------- apps, on the home page
   One card per app. The whole card is the link to that app's page. */
.apps {
  scroll-margin-top: 24px;
}

.apps__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 400px), 1fr));
  gap: var(--gutter);
}

.card {
  position: relative;
  display: grid;
  grid-template-rows: 1fr auto;
  overflow: hidden;
  overflow: clip; /* see .app below */
  border: 1px solid var(--stage-edge);
  border-radius: var(--tile-radius);
  background: var(--stage);
  color: var(--stage-ink);
  transition: transform 200ms var(--ease-out), background-color 200ms ease;
}
.card:active {
  transform: scale(0.985);
}

.card__body {
  padding: var(--card-pad) var(--card-pad) 0;
}
.card__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.card__icon {
  width: 56px;
  height: 56px;
  border-radius: 22.5%;
}
.card__status {
  padding: 6px 12px;
  border-radius: 999px;
  background: rgb(255 255 255 / 0.1);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.3;
}
.card__name {
  margin-top: 20px;
  font-size: clamp(1.75rem, 1.5rem + 1.2vw, 2.25rem);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.03em;
}
.card__text {
  margin-top: 8px;
  max-width: 27rem;
  color: var(--stage-ink-2);
  font-size: 1.125rem;
  line-height: 1.45;
  text-wrap: pretty;
}
.card__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  margin-top: 10px;
  color: var(--stage-link);
  font-weight: 600;
  text-decoration: none;
}
/* Stretches the link over the whole card, so anywhere on it works. */
.card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--tile-radius);
}
.card__link:focus-visible {
  outline: none;
}
.card__link:focus-visible::after {
  outline: 3px solid var(--stage-link);
  outline-offset: -7px;
  border-radius: calc(var(--tile-radius) - 4px);
}
.card__arrow {
  width: 18px;
  height: 18px;
  flex: none;
  transition: transform 200ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    background: var(--stage-hover);
  }
  .card:hover .card__arrow {
    transform: translateX(4px);
  }
}

.card__media {
  position: relative;
  /* Short enough that KerbAlert's screenshot is cut off above its map, so
     the home page shows no map and needs no map credit. */
  height: min(260px, 70vw);
  margin-top: 20px;
  view-timeline: --media block;
}
.card__media .phone {
  width: min(236px, 100% - 80px);
}

/* ------------------------------------------------ an app's own page: top
   The app on its dark panel: name, tagline, where it's up to, and a phone. */
.crumb {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 44px;
  margin: 8px 0 12px -4px;
  color: var(--link);
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
}
.crumb svg {
  width: 18px;
  height: 18px;
  flex: none;
  transition: transform 200ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .crumb:hover svg {
    transform: translateX(-3px);
  }
}

.app {
  position: relative;
  display: grid;
  overflow: hidden;
  /* `clip` cuts the phone off the same way but, unlike `hidden`, doesn't
     make this panel its own scrolling area, which would stop the phone's
     movement from following the page's scrolling. */
  overflow: clip;
  border: 1px solid var(--stage-edge);
  border-radius: var(--tile-radius);
  background: var(--stage);
  color: var(--stage-ink);
}

.app__body {
  padding: calc(var(--tile-pad) + 8px) var(--tile-pad) 0;
  container-type: inline-size; /* lets the tagline size itself to this column */
}

.app__id {
  display: flex;
  align-items: center;
  gap: 14px;
}
.app__icon {
  width: 56px;
  height: 56px;
  border-radius: 22.5%;
}
.app__name {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.app__tagline {
  margin-top: 28px;
  font-size: clamp(1.625rem, 1.3rem + 2.6vw, 3rem);
  /* Sized from the column's width so each half of the tagline always fits
     on a line of its own, on any screen. The longest half ("Your car can't
     tell you.") is 10.08 times the font size wide; 9.5% of the column
     leaves it a little room. The line above is for browsers too old to
     know this unit. A new app's tagline needs halves no longer than that. */
  font-size: clamp(1.25rem, 9.5cqi, 3rem);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.035em;
}
.app__tagline .line {
  text-wrap: balance;
}

.app__text {
  margin-top: 16px;
  max-width: 32rem;
  color: var(--stage-ink-2);
  text-wrap: pretty;
}

.facts {
  margin-top: 32px;
  border-top: 1px solid var(--stage-line);
}
.facts > div {
  display: grid;
  grid-template-columns: minmax(0, 8.25rem) minmax(0, 1fr);
  gap: 16px;
  padding: 13px 0;
  border-bottom: 1px solid var(--stage-line);
}
.facts dt {
  color: var(--stage-ink-2);
}
.facts dd {
  font-weight: 600;
  overflow-wrap: anywhere;
}
/* On the narrowest phones there isn't room for two columns: label above,
   answer below. */
@container (max-width: 270px) {
  .facts > div {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
}

/* The phone runs off the bottom of the panel, so the panel's height comes
   from this box (narrow screens) or from the text beside it (wide ones),
   never from the picture. The phone is drawn whole all the same, so it
   still looks right in the odd layout where all of it fits. */
.app__media {
  position: relative;
  height: min(400px, 105vw);
  margin-top: 40px;
  view-timeline: --media block; /* the phone's small move follows this box coming into view */
}

.phone {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  width: min(272px, 100% - 80px); /* clear of the panel's rounded corners */
  margin-inline: auto;
  padding: 10px;
  border-radius: 48px;
  background: #000000;
  box-shadow: 0 0 0 1px rgb(255 255 255 / 0.18), 0 40px 80px -24px rgb(0 0 0 / 0.6);
}
.phone__screen {
  position: relative;
  overflow: hidden;
  border-radius: 38px;
  background: #0a0a0a; /* KerbAlert's screenshot background */
  container-type: inline-size; /* the cut-out below is sized from the screen's width */
}
.phone__screen--light {
  background: #f2f2f7; /* GasPad's */
}
/* The camera cut-out at the top of a real phone's screen. */
.phone__screen::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: 9px;
  left: 50%;
  width: 78px;
  height: 23px;
  top: 3.2cqi;
  width: 31cqi;
  height: 9.2cqi;
  transform: translateX(-50%);
  border-radius: 999px;
  background: #000000;
}
/* For a screenshot taken without the phone's status bar: the strip a real
   phone keeps clear above the app. */
.phone__top {
  height: 36px;
  height: 11cqi;
}

@media (min-width: 900px) {
  .app {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  }
  .app__body {
    padding: var(--tile-pad) 0 var(--tile-pad) var(--tile-pad);
  }
  .app__icon {
    width: 64px;
    height: 64px;
  }
  .app__media {
    height: auto;
    margin-top: 0;
  }
  .app__media .phone {
    top: var(--tile-pad);
    width: min(360px, 100% - 80px);
  }
}

/* ------------------------------------------- an app's own page: the rest */
.section {
  padding-top: clamp(64px, 9vw, 112px);
}
.section__title {
  font-size: clamp(1.75rem, 1.2rem + 2.8vw, 2.75rem);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.035em;
  text-wrap: balance;
}
.section__lede {
  margin-top: 12px;
  max-width: 42rem;
  color: var(--ink-2);
  font-size: clamp(1.125rem, 1.02rem + 0.5vw, 1.375rem);
  line-height: 1.5;
  text-wrap: pretty;
}

/* The same paragraph style, used after a list rather than under a title. */
.section__lede--after {
  margin-top: 32px;
}

/* Steps that really do happen in this order, so they're numbered. */
.steps {
  display: grid;
  gap: 32px var(--gutter);
  margin-top: 40px;
  counter-reset: step;
}
.steps > li {
  padding-top: 20px;
  border-top: 1px solid var(--line);
  counter-increment: step;
}
.steps > li::before {
  content: counter(step);
  display: block;
  color: var(--ink-2);
  font-weight: 800;
  line-height: 1.2;
}
.steps h3,
.notes h3 {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
}
.steps h3 {
  margin-top: 8px;
}
.steps p,
.notes p {
  margin-top: 6px;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* A plain list of things, each with a line about it. */
.rows {
  display: grid;
  margin-top: 32px;
  border-bottom: 1px solid var(--line);
}
.rows > li {
  padding: 16px 0;
  border-top: 1px solid var(--line);
}
.rows strong {
  display: block;
  font-weight: 600;
}
.rows span {
  color: var(--ink-2);
}

.notes {
  display: grid;
  gap: 28px var(--gutter);
  margin-top: 40px;
}

@media (min-width: 760px) {
  .steps {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .rows,
  .notes {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--gutter);
  }
}

/* A sheet of paper: what the app produces. */
.paper {
  margin-top: 40px;
}
.paper img {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 24px 48px -28px rgb(10 30 51 / 0.35);
  filter: var(--paper-dim);
}
.paper figcaption {
  margin-top: 12px;
  color: var(--ink-2);
  font-size: 0.9375rem;
}
.fineprint {
  margin-top: 32px;
  max-width: 40rem;
  color: var(--ink-2);
  font-size: 0.9375rem;
}

.more {
  padding-top: clamp(64px, 9vw, 112px);
  padding-bottom: clamp(72px, 12vw, 144px);
}
.more > .btn,
.more__links {
  margin-top: 24px;
}
.more__links {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* ------------------------------------------- a page of plain reading:
   GasPad's support page and privacy policy. One narrow column. */
.doc {
  max-width: 44rem;
  padding-bottom: clamp(72px, 12vw, 144px);
}
.doc h2 {
  margin-top: 48px;
  font-size: clamp(1.375rem, 1.2rem + 0.8vw, 1.75rem);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.025em;
  text-wrap: balance;
}
.doc h3 {
  margin-top: 28px;
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.3;
}
.doc p,
.doc ul,
.doc .contact__row {
  margin-top: 12px;
}
.doc ul {
  padding-left: 1.25rem;
  list-style: disc;
}
.doc li + li {
  margin-top: 8px;
}
.doc p a,
.doc li a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
.doc .doc__title {
  margin-top: 8px;
  font-size: clamp(2.25rem, 1.4rem + 4vw, 3.5rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.035em;
  text-wrap: balance;
}
.doc .doc__lede {
  margin-top: 20px;
  font-size: clamp(1.125rem, 1.02rem + 0.5vw, 1.375rem);
  line-height: 1.5;
}
.doc .doc__meta {
  margin-top: 16px;
  color: var(--ink-2);
  font-size: 0.9375rem;
}

/* ---------------------------------------------------------------- contact */
.contact {
  padding-top: clamp(72px, 12vw, 144px);
  padding-bottom: clamp(72px, 12vw, 144px);
}

.contact__title {
  scroll-margin-top: 32px; /* where the header's Contact button lands */
  font-size: clamp(2rem, 1.2rem + 4vw, 3.5rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.035em;
}

.contact__text {
  margin-top: 16px;
  max-width: 40rem;
  color: var(--ink-2);
  font-size: clamp(1.125rem, 1.02rem + 0.5vw, 1.375rem);
  line-height: 1.5;
  text-wrap: pretty;
}

.contact__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 24px;
  margin-top: 28px;
}

.contact__email {
  display: inline-block;
  padding: 9px 0; /* a comfortable 44 pixels to tap, even on one line */
  color: var(--link);
  font-size: clamp(1.25rem, 0.9rem + 1.7vw, 2rem);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
  text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
  transition: text-decoration-color 150ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .contact__email:hover {
    text-decoration-color: currentColor;
  }
}

/* The copy button only exists where copying can work: site.js adds
   `can-copy` before the page is first drawn, so it never pops in late. */
.copy {
  display: none;
}
.can-copy .copy {
  display: inline-grid;
}
.copy__face {
  grid-area: 1 / 1;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: opacity 200ms ease, filter 200ms ease, transform 200ms var(--ease-out);
}
.copy__face svg {
  width: 18px;
  height: 18px;
  flex: none;
}
.copy__face--done,
.copy[data-copied] .copy__face--idle {
  opacity: 0;
  filter: blur(2px);
  transform: scale(0.96);
}
.copy[data-copied] .copy__face--done {
  opacity: 1;
  filter: none;
  transform: none;
}

/* ----------------------------------------------------------------- footer */
.site-footer {
  border-top: 1px solid var(--line);
  padding-top: 32px;
  padding-bottom: max(40px, env(safe-area-inset-bottom));
  color: var(--ink-2);
  font-size: 0.875rem;
  line-height: 1.6;
}
.site-footer p + p {
  margin-top: 6px;
}
.site-footer__made {
  color: var(--ink);
  font-size: 1rem;
  font-weight: 600;
}
.site-footer__made + p {
  margin-top: 12px;
}

/* ------------------------------------------------------------- 404 page */
.notfound {
  padding-top: clamp(56px, 14vw, 160px);
  padding-bottom: clamp(72px, 14vw, 160px);
}
.notfound__title {
  font-size: clamp(2.5rem, 1.2rem + 6vw, 4.5rem);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.04em;
}
.notfound__text {
  margin-top: 20px;
  max-width: 38rem;
  color: var(--ink-2);
  font-size: clamp(1.125rem, 1.02rem + 0.5vw, 1.375rem);
  line-height: 1.5;
}
.notfound .btn {
  margin-top: 32px;
}

/* ----------------------------------------------------------------- motion
   Kept small on purpose. The page arrives once, in order; the phone settles
   into place as its panel scrolls in; buttons answer a press. Nothing loops,
   and nothing is ever left hidden if an animation doesn't run. */
@media (prefers-reduced-motion: no-preference) {
  .hero__title .line,
  .hero__lede,
  .card,
  .crumb,
  .app,
  .doc,
  .notfound > * {
    animation: arrive 600ms var(--ease-out) backwards;
  }
  .hero__title .line:nth-child(2),
  .app,
  .doc,
  .notfound > :nth-child(2) {
    animation-delay: 70ms;
  }
  .hero__lede,
  .notfound > :nth-child(3) {
    animation-delay: 140ms;
  }
  .card {
    animation-delay: 210ms;
  }
  .card + .card {
    animation-delay: 280ms;
  }

  /* Tied to scrolling itself rather than played on a timer, so it moves
     exactly as far as the page has. Browsers without scroll-linked
     animation show the phone already in place. */
  @supports (animation-timeline: view()) {
    .phone {
      animation: settle linear both;
      animation-timeline: --media;
      animation-range: entry 0% cover 40%;
    }
  }
}

@keyframes arrive {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
}
@keyframes settle {
  from {
    transform: translateY(64px);
  }
}

@keyframes appear {
  from {
    opacity: 0;
  }
}

/* Reduced motion: things still fade, but nothing moves or blurs. */
@media (prefers-reduced-motion: reduce) {
  .hero__title,
  .hero__lede,
  .card,
  .app,
  .doc,
  .notfound > * {
    animation: appear 300ms ease backwards;
  }
  .card,
  .card__arrow,
  .crumb svg {
    transition: background-color 200ms ease;
  }
  .card:active,
  .card:hover .card__arrow,
  .crumb:hover svg {
    transform: none;
  }
  .btn,
  .site-header__brand {
    transition: background-color 150ms ease;
  }
  .btn:active,
  .site-header__brand:active {
    transform: none;
  }
  .copy__face {
    transition: opacity 200ms ease;
    filter: none !important;
    transform: none !important;
  }
}
