/* =============================================================================
   home.css: the homepage. Monochrome: white page, near-black type, one grey,
   hairline rules. Inter only. No colour accent anywhere.
============================================================================= */

/* Inter 4, variable (wght 100-900, opsz 14-32), subset to Latin and
   Latin-1. Built by home/tools/make-font.mjs; shared with the blog. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/fonts/inter-latin.woff2) format('woff2'),
       url(/fonts/inter-latin.woff) format('woff');
}
/* Metric-matched fallback so the swap to Inter does not shift the layout. */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  ascent-override: 90.44%;
  descent-override: 22.52%;
  line-gap-override: 0%;
  size-adjust: 107.12%;
}

:root {
  --bg: #FFFFFF;
  --ink: #0D0D0D;
  --grey: #5E5E5E;        /* 6.5:1 on white */
  --rule: #E5E5E5;
  --surface: #F5F5F5;
  --field: #8A8A8A;       /* input borders, 3.4:1 on white */
  --ink-hover: #333333;

  --font: 'Inter', 'Inter Fallback', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  --wrap: 1200px;
  --gutter: clamp(16px, 5vw, 40px);
  --header-h: 64px;
  --section-pad: clamp(4.5rem, 3rem + 6vw, 8.5rem);
  --radius: 16px;
}

/* ---------------------------------------------------------------- base -- */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Header height plus room for the 5px focus ring (2px outline, 3px
     offset) of whatever lands at the top, so the ring is never under it. */
  scroll-padding-top: calc(var(--header-h) + 16px);
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1.0625rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
/* The hidden attribute always wins. Without this, any rule that sets display
   (.form is a grid) overrides the browser's [hidden] style, and home.js's
   form.hidden = true after a send left the filled form and a live Send
   button on screen. */
[hidden] { display: none !important; }
h1, h2, h3, p, ol, ul, figure { margin: 0; }
ol, ul { padding: 0; list-style: none; }
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }

::selection { background: var(--ink); color: var(--bg); }

:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 4px; }
main:focus { outline: none; }

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: 8px;
  z-index: 100;
  padding: 10px 16px;
  background: var(--ink);
  color: var(--bg);
  border-radius: 999px;
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  transform: translateY(-160%);
}
.skip-link:focus { transform: none; }

.wrap {
  width: 100%;
  max-width: calc(var(--wrap) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Legacy anchors (#speaking from the old site, #who from the scroll build)
   are on slides and QR codes already in circulation. */
.anchor-alias { display: block; height: 0; }

/* A jump to a section lands its top rule exactly under the header's rule
   (y = 64), not 16px below it as a second parallel line. The extra 16px of
   scroll-padding above stays for focusable elements, whose rings need it. */
main > .section, .anchor-alias { scroll-margin-top: -16px; }

/* ------------------------------------------------------------- buttons -- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-height: 48px;
  padding: 0 1.375rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.005em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.btn--primary { background: var(--ink); color: var(--bg); }
.btn--primary:hover { background: var(--ink-hover); }
.btn--secondary { background: transparent; color: var(--ink); border-color: #C4C4C4; }
.btn--secondary:hover { border-color: var(--ink); }
.btn--sm { min-height: 40px; padding: 0 1.125rem; font-size: 0.9375rem; }
.btn[disabled] { cursor: progress; opacity: 0.72; }

/* -------------------------------------------------------------- header -- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.94);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease;
}
.site-header.is-scrolled { border-bottom-color: var(--rule); }
.site-header__inner {
  display: flex;
  align-items: center;
  gap: 24px;
  min-height: var(--header-h);
}
.wordmark {
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  text-decoration: none;
  white-space: nowrap;
  margin-right: auto;
}
.site-menu { display: flex; align-items: center; gap: 28px; }
.site-nav ul { display: flex; gap: 28px; }
.site-nav a, .site-lang {
  font-size: 0.9375rem;
  font-weight: 450;
  color: var(--grey);
  text-decoration: none;
  transition: color .15s ease;
}
.site-nav a:hover, .site-lang:hover { color: var(--ink); }
.site-lang { padding-left: 28px; border-left: 1px solid var(--rule); }

.menu-button {
  display: none;
  position: relative;
  width: 44px;
  height: 44px;
  margin-right: -10px;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: 999px;
  cursor: pointer;
}
.menu-button__icon,
.menu-button__icon::before {
  position: absolute;
  left: 12px;
  width: 20px;
  height: 1.5px;
  background: var(--ink);
  transition: transform .2s ease, top .2s ease;
}
.menu-button__icon { top: 18px; }
.menu-button__icon::before { content: ''; left: 0; top: 7px; }
.menu-button[aria-expanded="true"] .menu-button__icon { top: 21.5px; transform: rotate(45deg); }
.menu-button[aria-expanded="true"] .menu-button__icon::before { top: 0; transform: rotate(-90deg); }
/* Windows High Contrast repaints backgrounds as the page colour, which would
   erase the bars. Paint them in the system text colour instead. */
@media (forced-colors: active) {
  .menu-button__icon, .menu-button__icon::before { forced-color-adjust: none; background: CanvasText; }
}

/* Phones and small tablets. Without JavaScript the links wrap under the
   name; with it they collapse behind the menu button. */
@media (max-width: 899px) {
  .site-header__inner { flex-wrap: wrap; gap: 4px 16px; padding-block: 10px; }
  .site-menu { flex-wrap: wrap; gap: 8px 20px; width: 100%; }
  .site-nav ul { flex-wrap: wrap; gap: 8px 20px; }
  .site-lang { padding-left: 0; border-left: 0; }
  .site-menu__cta { display: none; }

  /* backdrop-filter would make the header the containing block of the
     fixed menu panel below, clipping it to the header's 64px. */
  .js .site-header { background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .js .site-header__inner { flex-wrap: nowrap; padding-block: 0; }
  .js .menu-button { display: block; }
  .js .site-menu {
    display: none;
    position: fixed;
    top: var(--header-h);
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 49;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 8px var(--gutter) 32px;
    background: var(--bg);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .js .site-menu.is-open { display: flex; }
  .js .site-nav ul { flex-direction: column; gap: 0; }
  .js .site-nav a, .js .site-lang {
    display: block;
    padding: 16px 0;
    border-bottom: 1px solid var(--rule);
    font-size: 1.5rem;
    font-weight: 500;
    letter-spacing: -0.02em;
    color: var(--ink);
  }
  .js .site-menu__cta { display: inline-flex; align-self: flex-start; margin-top: 28px; min-height: 48px; font-size: 1rem; padding: 0 1.375rem; }
  .menu-open { overflow: hidden; }
}

/* ---------------------------------------------------------------- hero -- */
.hero { padding-block: clamp(2rem, 1rem + 5vw, 6.5rem) clamp(4rem, 3rem + 5vw, 8rem); }
.hero__grid { display: grid; gap: 48px; align-items: center; }
.hero__title {
  font-size: clamp(2.5rem, 1.55rem + 4vw, 5rem);
  font-weight: 600;
  line-height: 1.03;
  letter-spacing: -0.04em;
  max-width: 14ch;
  text-wrap: balance;
}
.hero__sub {
  margin-top: 28px;
  max-width: 34rem;
  font-size: clamp(1.125rem, 1rem + 0.45vw, 1.3125rem);
  line-height: 1.5;
  color: var(--grey);
  text-wrap: pretty;
}
.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 36px; }
/* 320px phones (iPhone SE 1st gen): at 40px the Spanish H1 runs to four
   lines and pushes the primary button below a 568px screen. 36px sets both
   H1s in three lines and keeps the button in view. */
@media (max-width: 359px) {
  .hero__title { font-size: 2.25rem; }
}
/* The img carries its own rounded corners from the first paint, and nothing
   about it changes after load (a repaint would log a late LCP entry). */
.hero__portrait {
  position: relative;
  margin: 0;
  border-radius: var(--radius);
  overflow: visible;
  background: #777;
  aspect-ratio: 1 / 1;
}
.hero__portrait img {
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
  object-position: 50% 30%;
  filter: grayscale(1) contrast(1.04);
}
/* The dot field (home/src/3d/field.js): a canvas the chunk adds as the
   hero's first child once the page is idle. Under the grid (which is
   positioned so it paints after the canvas), never takes a pointer. */
.hero { position: relative; }
.hero__grid { position: relative; }
.hero__field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
@media print, (forced-colors: active) {
  .hero__field { display: none !important; }
}
@media (min-width: 960px) {
  .hero__grid { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 64px; }
  .hero__title { max-width: none; }
  .hero__portrait { aspect-ratio: 4 / 5; }
}
@media (max-width: 959px) {
  .hero__portrait { max-width: 420px; }
}
/* Tablets: the stacked hero would leave a 420px square flush left with an
   empty band beside it. Fill the column with a wider crop instead. */
@media (min-width: 640px) and (max-width: 959px) {
  .hero__portrait { max-width: none; aspect-ratio: 3 / 2; }
  .hero__portrait img { object-position: 50% 20%; }
}

/* One entrance, on the hero only, and only for people who allow motion. */
@media (prefers-reduced-motion: no-preference) {
  .hero__copy > *, .hero__portrait {
    animation: rise .6s cubic-bezier(.2, .7, .2, 1) both;
  }
  .hero__title { animation-delay: .05s; }
  .hero__sub { animation-delay: .1s; }
  .hero__actions { animation-delay: .15s; }
  .hero__portrait { animation-delay: .1s; }
}
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* ------------------------------------------------------------ sections -- */
.section {
  padding-block: var(--section-pad);
  border-top: 1px solid var(--rule);
}
.section__grid { display: grid; gap: 40px; }
.section__title {
  font-size: clamp(2rem, 1.45rem + 2.2vw, 3.25rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.035em;
  text-wrap: balance;
}
.section__intro {
  margin-top: 20px;
  max-width: 34rem;
  font-size: 1.125rem;
  line-height: 1.55;
  color: var(--grey);
  text-wrap: pretty;
}
.section__more { margin-top: 40px; }
@media (min-width: 960px) {
  .section__grid { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: 32px 64px; }
  .section__head { padding-right: 16px; }
}

/* Experience: a dated list, newest first, and the career globe. Narrow
   screens: title, intro, globe, list. The globe comes last in the source
   (after the list, for crawlers and screen readers), so order puts it above
   the list here. Wide screens: the title and the globe share the left
   column, the intro and the list the right one, so the globe starts right
   under the title and the Work link lands with it and its caption in view. */
.section__grid--work { row-gap: 0; }
.work__col { display: contents; }
.section__grid--work .section__intro { order: 1; }
.section__grid--work .globe { order: 2; }
.section__grid--work .timeline { order: 3; margin-top: 40px; }
@media (min-width: 960px) {
  .section__grid--work { grid-template-rows: auto 1fr; row-gap: 32px; }
  .section__grid--work .section__head { grid-column: 1; grid-row: 1; }
  .work__col { display: block; grid-column: 2; grid-row: 1 / span 2; }
  /* The section's bottom space moves inside the grid (the same whitespace):
     the sticky globe then stays pinned while the last row is read mid-screen
     on a laptop, instead of sliding under the header. */
  #work { padding-bottom: 0; }
  .work__col { margin-bottom: var(--section-pad); }
  .section__grid--work .section__intro { margin-top: 0; }
  .section__grid--work .globe { grid-column: 1; grid-row: 2; align-self: start; }
}

.timeline__item {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  padding-block: 28px;
  border-top: 1px solid var(--rule);
}
.timeline__item:first-child { border-top: 0; padding-top: 0; }
.timeline__item:last-child { padding-bottom: 0; }
.timeline__years {
  order: -1;
  margin-bottom: 8px;
  font-size: 0.9375rem;
  color: var(--grey);
  font-variant-numeric: tabular-nums;
}
.timeline__company {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.timeline__role { margin-top: 2px; color: var(--grey); }
.timeline__line { margin-top: 12px; max-width: 40rem; }
@media (min-width: 640px) {
  .timeline__item { grid-template-columns: 10.5rem minmax(0, 1fr); column-gap: 24px; }
  .timeline__years { order: 0; grid-column: 1; grid-row: 1 / span 3; margin: 3px 0 0; }
  .timeline__company, .timeline__role, .timeline__line { grid-column: 2; }
}

/* Career globe: a dotted globe beside the Experience list (page.mjs
   globeFigure, home/src/3d/globe/). The places and numbers are in the
   caption and the list; the canvas only draws dots. Without JavaScript,
   or if the globe cannot load, the figure shows a poster of the resting
   frame (built at build time) with the resting caption and labels. */
.globe { margin: 32px 0 0; max-width: 420px; }
.globe__stage { position: relative; width: 100%; aspect-ratio: 1 / 1; }
/* The sphere's edge, drawn before the dots arrive and kept as a hairline. */
.globe__stage::before {
  content: '';
  position: absolute;
  inset: 7%;
  border-radius: 50%;
  border: 1px solid #EDEDED;
}
/* The poster is for no-JS visitors and for failures only. With JS it stays
   display:none, so its lazy image is never requested. multiply lets the
   hairline edge show through its white. */
.globe__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  mix-blend-mode: multiply;
}
.js .globe__poster { display: none; }
.js .globe.is-poster .globe__poster { display: block; }
/* A poster that failed to load (home.js hides it): no broken-image icon. */
.globe__poster[hidden] { display: none !important; }
.globe__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity .8s ease;
  /* Sideways drags turn the globe; the page keeps its vertical scroll and
     pinch-zoom. The still globe has no drag at all. */
  touch-action: pan-y pinch-zoom;
  -webkit-tap-highlight-color: transparent;
}
.globe.is-still .globe__canvas { touch-action: auto; }
html:not(.js) .globe__canvas, .globe.is-poster .globe__canvas { display: none; }
.globe.is-ready .globe__canvas { opacity: 1; }
.globe.is-live .globe__canvas { cursor: grab; }
.globe.is-dragging .globe__canvas { cursor: grabbing; }

/* Place labels. The outer span sits on the point (JS moves it with a
   transform); the inner pill sits right of it, left of it, or on it. */
.globe__label {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  opacity: 0;
}
.globe__label > span {
  position: absolute;
  top: 0;
  padding: 2px 6px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.92);
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: 0;
  color: var(--ink);
  white-space: nowrap;
  transform: translateY(-50%);
}
/* The words are in data-text (page.mjs), so the HTML's text has no list of
   bare place names; the pill draws them. */
.globe__label > span::after { content: attr(data-text); }
.globe__label[data-align="r"] > span { left: 9px; }
.globe__label[data-align="l"] > span { right: 9px; }
.globe__label[data-align="c"] > span { left: 0; transform: translate(-50%, -50%); }
/* No JS, or the poster fallback: the resting labels at build-time spots. */
html:not(.js) .globe__label[data-rest], .globe.is-poster .globe__label[data-rest] { left: var(--x); top: var(--y); opacity: 1 !important; transform: none !important; }

.globe__pause {
  position: absolute;
  right: 2%;
  bottom: 2%;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  cursor: pointer;
  transition: border-color .15s ease;
}
.globe__pause[hidden] { display: none; }
.globe__pause:hover { border-color: var(--ink); }
.globe__pause::before, .globe__pause::after {
  content: '';
  position: absolute;
  top: 11px;
  width: 2px;
  height: 9px;
  border-radius: 1px;
  background: var(--ink);
}
.globe__pause::before { left: 12px; }
.globe__pause::after { left: 17px; }
.globe.is-paused .globe__pause::after { display: none; }
.globe.is-paused .globe__pause::before {
  left: 12px;
  top: 10px;
  width: 0;
  height: 0;
  border-radius: 0;
  background: none;
  border-style: solid;
  border-width: 5.5px 0 5.5px 9px;
  border-color: transparent transparent transparent var(--ink);
}
@media (forced-colors: active) {
  /* The dots are drawn in ink: keep them a white disc to sit on, even in a
     dark high-contrast theme. */
  .globe__stage { forced-color-adjust: none; background: #fff; border-radius: 50%; }
  .globe__pause::before, .globe__pause::after { forced-color-adjust: none; background: ButtonText; }
  .globe.is-paused .globe__pause::before { background: none; border-color: transparent transparent transparent ButtonText; }
}

.globe__caption { margin-top: 8px; }
/* minmax(0, 1fr): the caption never widens the column (a 36-dot tally is
   wider than a 320px phone's column). */
.globe__states { display: grid; grid-template-columns: minmax(0, 1fr); }
/* The outgoing caption clears first (.12s), then the new one fades in, so
   the two never sit on top of each other as garbled grey text. */
.globe__state {
  grid-area: 1 / 1;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
  opacity: 0;
  visibility: hidden;
  transition: opacity .12s ease, visibility 0s linear .12s;
}
.globe__state.is-on { opacity: 1; visibility: visible; transition: opacity .25s ease .12s; }
.globe__place { font-size: 1rem; font-weight: 600; line-height: 1.35; letter-spacing: -0.01em; }
.globe__detail { margin-top: 2px; font-size: 0.9375rem; line-height: 1.45; color: var(--grey); text-wrap: pretty; }
/* One dot per market, drawn by the background so there is nothing to read.
   9px apart, or closer where the column is narrower (36 dots on a 320px
   phone): the tile is 1/n of the width, so the count is always n. The
   reveal clips instead of growing the width, so the dots never move. */
.globe__tally {
  display: block;
  width: min(calc(var(--n) * 9px), 100%);
  height: 6px;
  margin-top: 12px;
  /* round: exactly n whole tiles, never a sliver of one more (DPR 3). */
  background: radial-gradient(circle at 3px 3px, var(--ink) 2.3px, transparent 2.9px) 0 0 / calc(100% / var(--n)) 6px round no-repeat;
}
@media (prefers-reduced-motion: no-preference) {
  .globe__state.is-on .globe__tally { animation: globe-tally 1.1s steps(var(--n)) .45s both; }
}
@keyframes globe-tally { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }
.globe__hint { margin-top: 16px; font-size: 0.8125rem; line-height: 1.4; color: var(--grey); }
html:not(.js) .globe__hint, .globe.is-poster .globe__hint { display: none; }
.globe__hint-hover { display: none; }
.globe.is-still .globe__hint-drag { display: none; }
@media (hover: hover) and (min-width: 960px) {
  .globe__hint-hover { display: inline; }
}

/* The row the globe is showing: a dot beside its years, on wide screens
   where the globe sits beside the list. Rows are not focusable; if one ever
   holds a link, focus inside it shows the same dot. */
.timeline__item { position: relative; }
.timeline__years { position: relative; transition: color .2s ease; }
.timeline__years::before {
  content: '';
  position: absolute;
  left: -18px;
  top: 0.62em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink);
  transform: scale(0);
  transition: transform .2s ease;
}
.is-globe-linked .timeline__item.is-active .timeline__years,
.is-globe-linked .timeline__item:focus-within .timeline__years { color: var(--ink); }
.is-globe-linked .timeline__item.is-active .timeline__years::before,
.is-globe-linked .timeline__item:focus-within .timeline__years::before { transform: scale(1); }

@media (min-width: 960px) {
  /* Sticky beside the list, and 40px wider than the column, into the gap,
     but never so tall that the caption falls below the screen when the Work
     link lands here: the height left under the header, the section's top
     padding, the title and the caption (9rem: the gap under the title, the
     tallest caption and a small margin). */
  .globe { position: sticky; top: calc(var(--header-h) + 24px); margin-top: 0; max-width: none; }
  /* The sphere sits 7% inside the stage (.globe__stage::before): shift the
     stage by that much, so the sphere's left edge lines up with the title
     and the caption, and the extra width goes into the gap on the right. */
  .globe__stage {
    width: min(calc(100% + 40px), max(240px, calc(100vh - var(--header-h) - var(--section-pad) - 1.08 * clamp(2rem, 1.45rem + 2.2vw, 3.25rem) - 9rem)));
    transform: translateX(-7%);
  }
}
@media print { .globe { display: none !important; } }

/* Writing: one row per piece. */
.posts { border-bottom: 1px solid var(--rule); }
.post { border-top: 1px solid var(--rule); }
.post:first-child { border-top: 0; }
.post__link {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
  padding-block: 20px;
  text-decoration: none;
}
.posts .post:first-child .post__link { padding-top: 0; }
.post__title {
  font-size: 1.125rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.01em;
  /* pretty, not balance: a list of long titles reads better with full lines
     (balance halved every title and split "AI / Search"); pretty still
     keeps a single word off the last line. */
  text-wrap: pretty;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  transition: text-decoration-color .15s ease;
}
.post__link:hover .post__title { text-decoration-color: currentColor; }
.post__meta {
  display: flex;
  gap: 16px;
  font-size: 0.875rem;
  color: var(--grey);
  font-variant-numeric: tabular-nums;
}
@media (min-width: 640px) {
  .post__link { grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 32px; }
  .post__meta { justify-content: flex-end; gap: 0; }
  .post__source { width: 6.5rem; }
  .post__date { width: 4.5rem; text-align: right; }
}

/* Talks: title, venue, date. */
.talk { padding-block: 28px; border-top: 1px solid var(--rule); }
.talk:first-child { border-top: 0; padding-top: 0; }
.talk__title {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.talk__title a {
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  text-decoration-color: #B5B5B5;
}
.talk__title a:hover { text-decoration-color: currentColor; }
.talk__venue { margin-top: 4px; }
.talk__date { color: var(--grey); font-size: 0.9375rem; }
.talk__line { margin-top: 10px; max-width: 40rem; color: var(--grey); }
.talks__invite {
  margin-top: 8px;
  padding: 28px;
  border-radius: var(--radius);
  background: var(--surface);
}
.talks__invite p:first-child { max-width: 38rem; }
.talks__invite p + p { margin-top: 20px; }
.talks__photo { margin: 0; max-width: 420px; border-radius: var(--radius); overflow: hidden; background: #777; aspect-ratio: 4 / 5; }
.talks__photo img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 45%; filter: grayscale(1) contrast(1.05); }
@media (min-width: 960px) {
  .section__grid--talks { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); grid-template-rows: auto 1fr; }
  .section__grid--talks .section__head { grid-column: 1; grid-row: 1; }
  .section__grid--talks .section__body { grid-column: 2; grid-row: 1 / span 2; }
  .section__grid--talks .talks__photo { grid-column: 1; grid-row: 2; align-self: start; max-width: none; }
}

/* About: set larger, it is read rather than scanned. */
.prose p {
  max-width: 40rem;
  font-size: clamp(1.125rem, 1rem + 0.4vw, 1.3125rem);
  line-height: 1.55;
}
.prose p + p { margin-top: 1.1em; }

/* -------------------------------------------------------------- contact -- */
.section--contact { background: var(--bg); }
.contact__book { margin-top: 24px; color: var(--grey); }
.contact__book a { color: var(--ink); font-weight: 500; text-underline-offset: 4px; text-decoration-thickness: 1px; }

.form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; }
@media (min-width: 640px) {
  .form { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 16px; }
  .field--wide { grid-column: 1 / -1; }
}
.field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.field__label { font-size: 0.9375rem; font-weight: 500; }
.field input, .field select, .field textarea {
  width: 100%;
  min-height: 52px;
  padding: 0 16px;
  border: 1px solid var(--field);
  border-radius: 12px;
  background: var(--bg);
  font-size: 1rem;
  line-height: 1.4;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.field textarea { min-height: 168px; padding-block: 14px; resize: vertical; }
.field select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%230D0D0D' stroke-width='1.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 18px center;
  cursor: pointer;
}
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--ink); }
/* Two states that must not look alike after a failed submit:
   invalid = a 2px ring on the field itself (border plus inner shadow);
   focus   = the site's focus outline, set 2px outside the field.
   A focused invalid field shows both, so the one with focus stands out.
   The transparent outline on plain :focus is for Windows High Contrast,
   which drops box-shadows but paints outlines. */
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--ink);
  outline: 2px solid transparent;
  outline-offset: 2px;
}
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.field [aria-invalid="true"] { border-color: var(--ink); box-shadow: 0 0 0 1px var(--ink); }
.form__note { font-size: 0.9375rem; color: var(--grey); }
.field__error {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  font-weight: 500;
}
.field__error::before {
  content: '!';
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--bg);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
}
.field__error[hidden] { display: none; }

.form__footer { display: flex; flex-wrap: wrap; align-items: center; gap: 16px 24px; margin-top: 4px; }
.form__error { font-weight: 500; }
.form__success {
  padding: 28px;
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  font-size: 1.25rem;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: -0.01em;
}
.form__success:focus { outline: none; }

/* Invisible to people and screen readers, visible to form-filling bots. */
.hp {
  position: absolute !important;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --------------------------------------------------------------- footer -- */
.site-footer { border-top: 1px solid var(--rule); padding-block: 40px 48px; }
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 32px;
  font-size: 0.9375rem;
  color: var(--grey);
}
.site-footer__links { display: flex; flex-wrap: wrap; gap: 8px 24px; }
/* 24px-tall targets (WCAG 2.5.8) without moving the text. */
.site-footer__links a { display: inline-block; padding-block: 3px; }
.site-footer a { text-decoration: none; }
.site-footer a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 4px; }

/* ----------------------------------------------------------------- 404 -- */
.notfound { padding-block: clamp(4rem, 2rem + 8vw, 9rem); }
.notfound__grid { display: grid; gap: 56px; }
.notfound__block { max-width: 36rem; }
.notfound__title {
  font-size: clamp(2rem, 1.45rem + 2.2vw, 3.25rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.035em;
}
.notfound__line { margin-block: 16px 28px; font-size: 1.125rem; line-height: 1.55; color: var(--grey); }
@media (min-width: 960px) {
  .notfound__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 64px; }
}

@media print {
  .site-header, .menu-button, .hero__actions, .form, .site-footer__links { display: none !important; }
  .section { border-top-color: #999; padding-block: 2rem; }
}
