/* One stylesheet for every page. No build step: edit and reload. */

:root {
  color-scheme: light;

  --bg: #fdfdfb;
  --text: #1c1c1a;
  --muted: #6b6b66;
  --rule: #e4e4de;
  --link: #1a4f8b;

  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
}

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

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

body {
  margin: 0;
  padding: 4rem 1.25rem 3rem;
  background: var(--bg);
  color: var(--text);
  font-family: var(--serif);
  font-size: 1.125rem;
  line-height: 1.65;
  text-rendering: optimizeLegibility;
}

/* The narrow text column. Header, main and footer all share it. */
.page {
  max-width: 720px;
  margin: 0 auto;
}

/* ---------- Text ---------- */

h1,
h2 {
  font-weight: 600;
  line-height: 1.25;
  text-wrap: balance;
}

h1 {
  font-size: 1.9rem;
  margin: 0 0 1.25rem;
  letter-spacing: -0.01em;
}

h2 {
  font-size: 1.25rem;
  margin: 0 0 0.75rem;
}

p {
  margin: 0 0 1rem;
}

a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

a:hover {
  text-decoration-thickness: 2px;
}

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

section {
  margin-top: 3.5rem;
}

/* ---------- Homepage ---------- */

/* Intro: text on the left, photo on the right. */
.intro {
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: 2.25rem;
  align-items: start;
}

.intro-text > :last-child {
  margin-bottom: 0;
}

/* Shown whole, at its own aspect ratio: never cropped. It comes first in
   the HTML so it leads on small screens. */
.photo {
  order: 2;
  display: block;
  width: 100%;
  height: auto;
}

.links {
  list-style: none;
  margin: 3rem 0 0;
  padding: 1.5rem 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.4rem 1.5rem;
}

/* ---------- Inner pages ---------- */

.home-link {
  display: inline-block;
  margin-bottom: 2.5rem;
  color: var(--muted);
  font-size: 1rem;
}

.muted {
  color: var(--muted);
}

/* ---------- Best Reads page ---------- */

.books {
  margin: 2rem 0 0;
  padding-left: 1.25rem;
}

.books li {
  padding: 0.3rem 0;
}


/* Pages that should have no dividing lines at all. */
.no-rules footer {
  border-top: 0;
}

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

footer {
  margin-top: 4.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  font-size: 0.95rem;
}

footer a {
  color: var(--muted);
  margin-right: 1.25rem;
}

/* ---------- Events page ---------- */

/* The events pages use the same column width as every other page. */
.page.wide {
  max-width: 720px;
}

/* Same heading size as the other pages. */
.ev-head h1 {
  margin-bottom: 1.5rem;
}

/* The page's opening line: a little larger, and set apart from what follows. */
.ev-lead {
  max-width: 34em;
  margin: 0 0 3.5rem;
  font-size: 1.35rem;
  line-height: 1.5;
}

/* One plain sentence under the heading. */
.ev-line {
  margin: 0 0 2rem;
  font-size: 1.2rem;
}

/* A wall of photos in two columns. Every photo is shown whole, at its own
   shape, so tiles have different heights. The grid uses very thin rows and
   each tile spans as many as its photo needs: --d on wide screens, --m on
   narrow ones. Row height follows the page width, so the whole wall scales
   together and photos are never squeezed. */
.mosaic-wrap {
  container-type: inline-size;
}

.mosaic {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 5.6px;
  grid-auto-rows: 0.5cqw;
  grid-auto-flow: row dense;
  column-gap: 10px;
}

.t-malaysia { --d: 86; --m: 88; }
.t-singapore { --d: 119; --m: 121; }
.t-vietnam { --d: 55; --m: 56; }
.t-bangalore { --d: 54; --m: 55; }
.t-toronto { --d: 55; --m: 56; }
.t-mumbai { --d: 61; --m: 63; }
.t-shanghai { --d: 81; --m: 83; }
.t-beijing { --d: 60; --m: 61; }
.t-beijing-2 { --d: 52; --m: 66; }
.t-shenzhen { --d: 70; --m: 72; }
.t-researcher-meetup { --d: 47; --m: 55; }
.t-researcher-dinner { --d: 67; --m: 69; }

/* Each tile is a card that flips: photo on the front, details on the back.
   A hidden checkbox holds the state on touch screens, so no script. */
.tile {
  position: relative;
  display: block;
  grid-row: span var(--d);
  perspective: 1400px;
}

.tile.left  { grid-column: 1 / span 2; }
.tile.right { grid-column: 3 / span 2; }
.t-vietnam { grid-column: 3; }
.t-toronto  { grid-column: 4; }

@container (max-width: 660px) {
  .mosaic {
    grid-template-columns: 1fr 1fr;
    grid-auto-rows: 1cqw;
  }

  .tile,
  .tile.left,
  .tile.right {
    grid-column: 1 / span 2;
    grid-row: span var(--m);
  }

  .t-vietnam { grid-column: 1; }
  .t-toronto  { grid-column: 2; }
}

.tile input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* The 10px at the bottom is the gap between tiles in a column. */
.tile .inner {
  position: absolute;
  inset: 0 0 10px 0;
  transform-style: preserve-3d;
  transition: transform 0.55s cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* With a mouse the card flips while the pointer is over it. */
@media (hover: hover) {
  .tile:hover .inner,
  .tile:focus-within .inner {
    transform: rotateY(180deg);
  }
}

/* On touch screens there is no hover, so a "Details" tag shows and a tap
   flips the card. */
@media (hover: none) {
  .tile {
    cursor: pointer;
  }

  .tile input:checked + .inner {
    transform: rotateY(180deg);
  }

  .mosaic .tile .tag {
    display: block;
  }
}

.tile input:focus-visible + .inner {
  outline: 2px solid var(--link);
  outline-offset: 3px;
}

.tile .front,
.tile .back {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 8px;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

.tile .front img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tile .front::after {
  content: "";
  position: absolute;
  inset: 55% 0 0 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0));
}

/* The name and date sit above the card and do not turn with it. */
.tile .cap {
  position: absolute;
  left: 1rem;
  right: 1rem;
  bottom: calc(0.8rem + 10px);
  z-index: 2;
  pointer-events: none;
  color: #fff;
  font-family: var(--sans);
  font-size: 0.8rem;
  line-height: 1.3;
}

.tile .cap strong {
  display: block;
  font-family: var(--serif);
  font-size: 1.45rem;
  font-weight: 600;
}

.tile.small .cap strong {
  font-size: 1.15rem;
}

/* A "Luma" button in Luma's own gradient, on the bottom-right corner of the
   card's back. It sits outside the flipping face (so it can be clicked) and
   is shown only while the card is flipped. */
.tile .luma {
  position: absolute;
  right: 0.9rem;
  bottom: calc(0.85rem + 10px);
  z-index: 3;
  padding: 0.32rem 0.75rem;
  border-radius: 7px;
  background: linear-gradient(120deg, #5b6cff 0%, #c44dff 45%, #ff5a8a 75%, #ff9a4d 100%);
  color: #fff;
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-decoration: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s, filter 0.2s;
}

.tile .luma:hover,
.tile .luma:focus-visible {
  filter: brightness(1.12);
  color: #fff;
}

@media (hover: hover) {
  .tile:hover .luma,
  .tile:focus-within .luma {
    opacity: 1;
    pointer-events: auto;
    transition-delay: 0.2s;
  }
}

@media (hover: none) {
  .tile input:checked ~ .luma {
    opacity: 1;
    pointer-events: auto;
    transition-delay: 0.2s;
  }
}

.tile.small .luma {
  right: 0.6rem;
  padding: 0.2rem 0.5rem;
  font-size: 0.7rem;
}

.tile .tag {
  display: none;
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 1;
  padding: 0.2rem 0.5rem;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.9);
  color: var(--text);
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.tile .back {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 1.4rem 1.5rem 4.4rem;
  overflow-y: auto;
  background: var(--text);
  color: var(--bg);
  transform: rotateY(180deg);
}

.tile .back .meta {
  margin: 0 0 0.6rem;
  font-family: var(--sans);
  font-size: 0.78rem;
  opacity: 0.7;
}

.tile .back .desc {
  font-size: 1.05rem;
  line-height: 1.5;
}

.tile .back a {
  margin-top: 0.7rem;
  color: inherit;
  font-family: var(--sans);
  font-size: 0.8rem;
}

/* The two small tiles get tighter text. */
.tile.small .back {
  padding: 0.85rem 0.95rem 3.5rem;
}

.tile.small .back .meta {
  margin-bottom: 0.4rem;
  font-size: 0.72rem;
}

.tile.small .back .desc {
  font-size: 0.8rem;
  line-height: 1.42;
}

.tile.small .back a {
  margin-top: 0.4rem;
  font-size: 0.72rem;
}

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

/* Below the full column width there is no room for text beside the photo:
   stack them, photo first. */
@media (max-width: 760px) {
  .intro {
    grid-template-columns: 1fr;
    gap: 1.75rem;
  }

  .photo {
    order: 0;
    width: 220px;
    max-width: 60%;
  }
}

@media (max-width: 480px) {
  body {
    padding-top: 2.5rem;
    font-size: 1.0625rem;
  }

  h1 {
    font-size: 1.65rem;
  }
}

@media (max-width: 700px) {
  .tile .cap strong {
    font-size: 1.2rem;
  }

  .tile.small .cap strong {
    font-size: 0.95rem;
  }

  .mosaic .tile .back {
    padding: 0.9rem 1rem 3.6rem;
  }

  .mosaic .tile .back .desc {
    font-size: 0.9rem;
  }

  /* The two small tiles are tiny on phones. */
  .mosaic .tile.small .back {
    padding: 0.55rem 0.6rem 2.9rem;
  }

  .mosaic .tile.small .back .meta {
    font-size: 0.6rem;
  }

  .mosaic .tile.small .back .desc {
    font-size: 0.68rem;
    line-height: 1.32;
  }

  .mosaic .tile.small .back a {
    font-size: 0.6rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tile .inner {
    transition: none;
  }
}

/* ---------- Hacker houses ---------- */

/* Shown as printed snapshots, not flip cards, so they read as something
   more personal than the events above. Widths follow each photo's shape,
   so prints in a row come out the same height. */
.houses {
  margin-top: 0;
}

.ev-wall {
  margin-top: 4rem;
}

.prints {
  display: flex;
  gap: 1.75rem;
  align-items: flex-start;
  margin-top: 2rem;
}

.print {
  flex: 1.333 1 0;
  margin: 0;
  padding: 10px 10px 0;
  background: #fff;
  border: 1px solid var(--rule);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 10px 24px rgba(0, 0, 0, 0.08);
  transform: rotate(0.9deg);
}

.print:first-child { transform: rotate(-1.4deg); }
.print:last-child  { transform: rotate(0.9deg); }

/* A see-through "Watch" button on the photo's top-right corner. */
.print {
  position: relative;
}

.print .watch {
  position: absolute;
  top: 22px;
  right: 22px;
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.4rem 0.85rem 0.4rem 0.75rem;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 999px;
  background: rgba(20, 20, 20, 0.32);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: #fff;
  font-family: var(--sans);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-decoration: none;
  transition: background 0.2s;
}

.print .watch span {
  font-size: 0.7em;
}

.print .watch:hover,
.print .watch:focus-visible {
  background: rgba(20, 20, 20, 0.6);
  color: #fff;
}

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

.print figcaption {
  padding: 0.7rem 0 0.85rem;
  font-style: italic;
  font-size: 0.95rem;
  text-align: center;
  color: var(--muted);
  font-weight: 600;
}

@media (max-width: 700px) {
  .prints {
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
  }

  .print {
    flex: none;
    width: 100%;
  }
}

/* The photo that closes the homepage. It is as wide as the text column and
   has no frame or caption; its top fades into the page so it sits behind the
   end of the page instead of looking like part of the story above it. */
.closing {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: 50% 25%;
  margin: 3rem 0 0;
  border-radius: 0 0 10px 10px;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 24%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 24%);
}

/* On phones the homepage links wrap to two rows; keep them left-aligned. */
@media (max-width: 640px) {
  .links {
    justify-content: flex-start;
    gap: 0.4rem 1.4rem;
  }
}

/* Homepage: small contact links in the top-right corner. */
.top-links {
  display: flex;
  justify-content: flex-end;
  gap: 1.25rem;
  margin: -2rem 0 2rem;
  font-size: 0.95rem;
}

.top-links a {
  color: var(--muted);
}

.top-links.icons {
  gap: 1.1rem;
  align-items: center;
}

.top-links.icons a {
  display: inline-flex;
  border: 0;
  text-decoration: none;
}

.top-links.icons svg {
  width: 19px;
  height: 19px;
  fill: currentColor;
}

.top-links.icons svg.line {
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Each icon in its own brand colour. */
.top-links.icons .i-li   { color: #0a66c2; }
.top-links.icons .i-x    { color: #000; }
.top-links.icons .i-gh   { color: #181717; }
.top-links.icons .i-mail { color: #ea4335; }

.top-links.icons a:hover {
  opacity: 0.7;
}

/* ---------- Greeting before "I'm Om" ---------- */

/* "Hello" in ten languages, one at a time, on the same line
   as the name. English returns after every three other languages, so the
   loop has 12 steps of 3s (36s). The words are stacked in one grid cell;
   each fades up into place, holds, and fades out as the next arrives. The
   cell's width glides to fit each word so "I'm Om" slides instead of
   jumping. Pure CSS. Widths are each word's measured width in em. Each
   language has its own colour. */
.hello {
  display: inline-grid;
  align-items: baseline;
  margin-right: 0.3em;
  color: var(--muted);
  white-space: nowrap;
  animation: hello-width 36s infinite ease-in-out;
}

.hello span {
  grid-area: 1 / 1;
  justify-self: start;
  opacity: 0;
  animation: hello 36s infinite both;
}

.hello span:nth-child(1) { animation-delay: 0s; color: #2b59c3; }
.hello span:nth-child(2) { animation-delay: 3s; color: #d62828; }
.hello span:nth-child(3) { animation-delay: 6s; color: #f77f00; }
.hello span:nth-child(4) { animation-delay: 9s; color: #b23a48; }
.hello span:nth-child(5) { animation-delay: 12s; color: #2b59c3; }
.hello span:nth-child(6) { animation-delay: 15s; color: #3a0ca3; }
.hello span:nth-child(7) { animation-delay: 18s; color: #2a9d5c; }
.hello span:nth-child(8) { animation-delay: 21s; color: #c2185b; }
.hello span:nth-child(9) { animation-delay: 24s; color: #2b59c3; }
.hello span:nth-child(10) { animation-delay: 27s; color: #0a9396; }
.hello span:nth-child(11) { animation-delay: 30s; color: #e0a800; }
.hello span:nth-child(12) { animation-delay: 33s; color: #1d6fa5; }

@keyframes hello {
  0%     { opacity: 0; transform: translateY(0.35em); filter: blur(6px); }
  2%     { opacity: 1; transform: translateY(0);      filter: blur(0); }
  6.67%  { opacity: 1; transform: translateY(0);      filter: blur(0); }
  8.33%  { opacity: 0; transform: translateY(-0.3em); filter: blur(6px); }
  100%   { opacity: 0; transform: translateY(-0.3em); filter: blur(6px); }
}

@keyframes hello-width {
  1.00%, 7.33% { width: 2.57em; }
  9.33%, 15.67% { width: 2.13em; }
  17.67%, 24.00% { width: 2.1em; }
  26.00%, 32.33% { width: 3.64em; }
  34.33%, 40.67% { width: 2.57em; }
  42.67%, 49.00% { width: 3.78em; }
  51.00%, 57.33% { width: 2.5em; }
  59.33%, 65.67% { width: 4.98em; }
  67.67%, 74.00% { width: 2.57em; }
  76.00%, 82.33% { width: 1.67em; }
  84.33%, 90.67% { width: 2.26em; }
  92.67%, 99.00% { width: 4.98em; }
  0%, 100% { width: 3.78em; }
}

@media (prefers-reduced-motion: reduce) {
  .hello {
    animation: none;
    width: 2.57em;
  }

  .hello span {
    animation: none;
  }

  .hello span:first-child {
    opacity: 1;
  }
}

/* Two photos side by side at the same height. Each one's share of the row
   follows its shape (16:9 and 9:16), so neither is cropped. */
.pair {
  display: flex;
  gap: 12px;
  margin: 0 0 1.5rem;
}

.pair img {
  display: block;
  min-width: 0;
  height: auto;
  border-radius: 8px;
}

.pair .wide { flex: 1.7778 1 0; }
.pair .tall { flex: 0.5625 1 0; }

/* Strava link: the Strava mark in its orange, with the name beside it. */
.strava {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.8rem 0.4rem 0.65rem;
  border: 1px solid #fc4c02;
  border-radius: 999px;
  color: #fc4c02;
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: none;
}

.strava svg {
  width: 18px;
  height: 18px;
  fill: currentColor;
}

.strava:hover,
.strava:focus-visible {
  background: #fc4c02;
  color: #fff;
}

/* A section heading that matches the page title in size and spacing. */
h2.big {
  font-size: 1.9rem;
  margin: 0 0 1.25rem;
  letter-spacing: -0.01em;
}

@media (max-width: 480px) {
  h2.big {
    font-size: 1.65rem;
  }
}

/* The opening note on the Events page: italic, with a rule down its left. */
.ev-lead.note {
  padding-left: 1.25rem;
  border-left: 2px solid var(--text);
  font-style: italic;
}

/* "I run." with the Strava button beside it. */
.strava-line {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin: 0;
}

/* A small label above a group of photos on the Adventures page. */
h2.kind {
  margin: 0 0 0.85rem;
  font-size: 1.15rem;
}

h2.kind span {
  margin-left: 0.2em;
  font-size: 0.95em;
}

/* The OpenClaw-only page shows nine of the tiles; a slightly taller
   Bangalore tile makes its two columns end level. */
.nights-only .t-bangalore { --d: 58; }

/* ---------- My Story timeline ---------- */

/* A line runs down the left with a circle at each chapter, like a path
   walked backwards from today. The line and circles are drawn dark; a pale
   veil fixed over the lower part of the screen lightens whatever is still
   below the reading point. So as you scroll, the line darkens downwards and
   each circle fills in turn. */
.rail {
  position: relative;
  margin-top: 0.5rem;
  padding-left: 1.75rem;
  border-left: 1px solid var(--text);
}

.stop {
  position: relative;
  margin-top: 2.4rem;
}

.stop:first-of-type {
  margin-top: 0;
}

.stop::before {
  content: "";
  position: absolute;
  left: calc(-1.75rem - 5px);
  top: 0.62em;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--text);
}

/* The ring is a separate layer above the veil, so unread circles keep a
   crisp outline while their centre goes pale. */
.stop::after {
  content: "";
  position: absolute;
  z-index: 2;
  left: calc(-1.75rem - 5px);
  top: 0.62em;
  width: 9px;
  height: 9px;
  border: 1.5px solid var(--text);
  border-radius: 50%;
  box-sizing: border-box;
}

/* The veil. Where the browser can tie an animation to scrolling, the veil
   covers the whole line at the top of the page and shrinks away downwards as
   the page is scrolled: the dark line grows from the very first circle and
   reaches the very bottom at the end of the page. */
.unread {
  position: absolute;
  z-index: 1;
  top: 0;
  bottom: 0;
  left: -12px;
  width: 22px;
  background: #dcdcd6;
  mix-blend-mode: lighten;
  pointer-events: none;
  transform-origin: 50% 100%;
}

/* The first circle is "now": always filled. */
.stop:first-of-type::before {
  z-index: 3;
}

@supports (animation-timeline: scroll()) {
  .unread {
    animation: read-on linear both;
    animation-timeline: scroll(root block);
  }
}

@keyframes read-on {
  from { transform: scaleY(1); }
  to   { transform: scaleY(0); }
}

/* Older browsers: a veil fixed over the lower part of the screen instead,
   so the line still darkens as the text scrolls up past the middle. */
@supports not (animation-timeline: scroll()) {
  .unread {
    position: fixed;
    top: 55vh;
    left: auto;
    margin-left: calc(-1.75rem - 12px);
  }
}

.stop p:last-child {
  margin-bottom: 0;
}

/* Two lines in a row under a heading sit closer together. */
.ev-line:has(+ .ev-line) {
  margin-bottom: 0.6rem;
}

/* The whole hacker house photo opens its video, not just the Watch button. */
.print-photo {
  display: block;
}

/* ---------- Homepage on phones ---------- */

/* A round photo and the name sit on the centre line; the paragraphs stay
   left-aligned for easy reading. The contact icons move to the very bottom
   of the page. */
@media (max-width: 760px) {
  .home .page {
    display: flex;
    flex-direction: column;
  }

  .home .top-links {
    order: 2;
    justify-content: center;
    margin: 2.5rem 0 0;
    padding-top: 1.5rem;
    border-top: 1px solid var(--rule);
  }

  .home .intro {
    justify-items: center;
  }

  .home .photo {
    width: 150px;
    max-width: none;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    object-position: 50% 30%;
    border-radius: 50%;
  }

  .home .intro h1 {
    text-align: center;
  }

  .home .links {
    justify-content: center;
  }
}

/* ---------- Events: highlighted section headings ---------- */

/* A soft highlighter stroke behind the lower half of the words: yellow for
   Hacker Houses, blue for Demo Nights and Sessions. */
.houses h2,
.ev-wall h2 {
  font-size: 1.6rem;
  margin-bottom: 1rem;
}

.houses h2 span,
.ev-wall h2 span {
  padding: 0 0.25em;
  margin-left: -0.25em;
  background: linear-gradient(to top, #ffe58a 0, #ffe58a 42%, transparent 42%);
}

.ev-wall h2 span {
  background: linear-gradient(to top, #c9dcff 0, #c9dcff 42%, transparent 42%);
}

/* Library page: three equal sections (Notes, Books, Resources). */
.shelf {
  margin-top: 3rem;
}

.shelf:first-of-type {
  margin-top: 0.5rem;
}

.shelf h2 {
  font-size: 1.35rem;
  margin-bottom: 0.6rem;
}

/* ---------- Hacker house prints: a little life ---------- */

/* With a mouse: the print lifts, swings the other way and grows slightly. */
.print {
  transition: transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.45s;
}

@media (hover: hover) {
  /* The two prints swing in opposite directions and drift apart. */
  .prints .print:hover {
    transform: rotate(2.6deg) translate(-8px, -10px) scale(1.03);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08), 0 22px 40px rgba(0, 0, 0, 0.16);
  }

  .prints .print:last-child:hover {
    transform: rotate(-3deg) translate(8px, -10px) scale(1.03);
  }
}

/* On touch screens: as a print scrolls into view it swings from a stronger
   tilt into its resting angle, like a photo being set down. */
@media (hover: none) {
  @supports (animation-timeline: view()) {
    .prints .print {
      animation: set-down linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }

    .prints .print:first-child { --from: 8deg;  --to: -2deg; }
    .prints .print:last-child  { --from: -8deg; --to: 2deg; }
  }
}

@keyframes set-down {
  from { transform: rotate(var(--from, 6deg)) translateY(28px) scale(0.96); }
  to   { transform: rotate(var(--to, 0.9deg)) translateY(0) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .print {
    transition: none;
  }

  .prints .print {
    animation: none;
  }
}

/* ---------- Library: highlighted section headings ---------- */

/* The same marker strokes as the Events headings, one colour
   per section, with a hairline between sections. */
.shelf {
  margin-top: 2.25rem;
  padding-top: 2.25rem;
  border-top: 1px solid var(--rule);
}

.shelf:first-of-type {
  margin-top: 0.5rem;
  padding-top: 0;
  border-top: 0;
}

.shelf h2 span {
  padding: 0 0.25em;
  margin-left: -0.25em;
  background: linear-gradient(to top, #ffe58a 0, #ffe58a 42%, transparent 42%);
}

.shelf:nth-of-type(2) h2 span {
  background: linear-gradient(to top, #c9dcff 0, #c9dcff 42%, transparent 42%);
}

.shelf:nth-of-type(3) h2 span {
  background: linear-gradient(to top, #c8ecd2 0, #c8ecd2 42%, transparent 42%);
}

.shelf:nth-of-type(4) h2 span {
  background: linear-gradient(to top, #ffd3c8 0, #ffd3c8 42%, transparent 42%);
}

/* Adventures: every activity gets the same small heading, with space above
   each one after the first. */
h2.kind:not(:first-of-type) {
  margin-top: 2.75rem;
}

/* Adventures: one or two plain lines under each heading. */
.kind-line {
  margin: 0 0 1.1rem;
}

/* Adventures, laid out like a magazine spread: each section has its
   heading and line in a narrow left column and its photos and silent
   clips on the right, with place names set on the pictures. On narrow
   screens the heading sits above the pictures. */
.page.mag{max-width:1000px}
.sec>*{min-width:0}
.sec{display:grid;grid-template-columns:230px minmax(0,1fr);gap:44px;padding:2.4rem 0;border-top:1px solid var(--rule)}
.sec:first-of-type{border-top:0;padding-top:.6rem}
.sec .side{position:sticky;top:2rem;align-self:start}
.sec h2.kind{margin-top:0!important}
.sec .kind-line{margin-bottom:1rem}
.mos{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.mt{position:relative;margin:0;border-radius:8px;overflow:hidden;background:var(--rule)}
.mt img,.mt video{display:block;width:100%;height:auto;aspect-ratio:4/5;object-fit:cover}
.mt.fill img,.mt.fill video{position:absolute;inset:0;height:100%;aspect-ratio:auto}
.mt.big{grid-column:span 2;grid-row:span 2}
.mt.wide{grid-column:span 2}
.mt figcaption{position:absolute;left:0;right:0;bottom:0;padding:1.6rem .7rem .55rem;color:#fff;font-family:var(--sans);font-size:.74rem;background:linear-gradient(to top,rgba(0,0,0,.5),transparent)}
.mos.four{grid-template-columns:repeat(2,1fr)}
.mos.two{grid-template-columns:repeat(2,1fr);max-width:480px}
.fb{display:flex;gap:10px}.fb img{display:block;min-width:0;height:auto;border-radius:8px}
.fb .w{flex:1.7778 1 0}.fb .t{flex:.5625 1 0}
@media (max-width:760px){.sec{grid-template-columns:minmax(0,1fr);gap:0;padding:1.8rem 0}.sec .side{position:static}.sec .side .strava{margin-bottom:1rem}.mos{grid-template-columns:repeat(2,1fr)}.mt.big{grid-column:span 2;grid-row:auto}.mt.big.fill img{position:static;aspect-ratio:4/5}.mt.wide{grid-column:span 1}.mt.wide.fill img{position:static;aspect-ratio:4/5}}

/* ---------- Adventures: the travel map ----------

   Made by tools/build_map.py from data/places.json. A hand-inked map sits
   under the opening line, with a wall of photos and clips beside it. As
   the reader scrolls, the section holds its place on the screen: a plane
   hops from place to place along the dotted route, which is faint at first
   and is inked in behind it, and the wall slides sideways in step, so both
   finish together. The wall can also be scrolled by hand. A few lines of
   script at the end of the page do the pacing; without them, or when the
   reader asks for less motion, the finished map is shown. */
.sec.travel {
  display: block;
}

.tpin {
  display: grid;
  grid-template-columns: 380px minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  column-gap: 44px;
}

.tpin > * { min-width: 0; }

.sec.travel .side {
  position: static;
  grid-column: 1;
  grid-row: 1;
}

.tmap {
  position: relative;
  z-index: 3;
  grid-column: 1;
  grid-row: 2;
  align-self: start;
  margin: -1.2rem 0 0;
  aspect-ratio: 720 / 611;
  container-type: inline-size;
}

.reel-wrap {
  grid-column: 2;
  grid-row: 1 / 3;
  align-self: start;
  overflow-x: auto;
  cursor: grab;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  container-type: inline-size;
  outline-offset: 4px;
}

.reel-wrap::-webkit-scrollbar { display: none; }

.tmap svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent), linear-gradient(to bottom, transparent, #000 8%, #000 90%, transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent), linear-gradient(to bottom, transparent, #000 8%, #000 90%, transparent);
  mask-composite: intersect;
}

.tmap .ink,
.tmap .water,
.tmap .route {
  fill: none;
  stroke: var(--text);
  stroke-linejoin: round;
}

.tmap .ink { stroke-width: 2.3; }
.tmap .route.faint { opacity: 0.26; }

/* What an old chart would carry: the great rivers, peaks for the
   Himalaya, the Great Wall, a few small drawings, a compass, the seas
   lettered in, and an aeroplane that flies the route. */
.tmap .river {
  fill: none;
  stroke: var(--text);
  stroke-width: 1.1;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.5;
}

.tmap .mark {
  fill: none;
  stroke: var(--text);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.8;
}

.tmap .peak,
.tmap .compass {
  fill: none;
  stroke: var(--text);
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tmap .compass { stroke-width: 1.5; opacity: 0.75; }
.tmap .compass .fill { fill: var(--text); }

.tmap .plane {
  fill: var(--text);
  stroke: var(--bg);
  stroke-width: 2.4;
  paint-order: stroke;
  offset-rotate: auto;
  offset-distance: 100%;
}

.tmap .sea {
  position: absolute;
  transform: translate(-50%, -50%);
  color: #9a9a90;
  font-style: italic;
  font-size: clamp(8px, 2.5cqw, 10.5px);
  letter-spacing: 0.24em;
  line-height: 1.45;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
}

/* The route: round dots, shown through a mask that the scroll uncovers. */
.tmap .route {
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-dasharray: 0.1 9.5;
}

.tmap .rv {
  fill: none;
  stroke: #fff;
  stroke-width: 16;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

/* Each place is a small ring at its true position. */
.tmap .nm {
  position: absolute;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  outline: 0;
  cursor: default;
  -webkit-tap-highlight-color: transparent;
}

.tmap .nm::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border: 1.2px solid var(--text);
  border-radius: 50%;
  background: var(--text);
}

.tmap .nm:hover::before,
.tmap .nm:focus::before {
  box-shadow: 0 0 0 4px rgba(28, 28, 26, 0.12);
}

/* Its name, and a one-line note, shown on hover, focus or tap. */
.tmap .tip {
  position: absolute;
  z-index: 6;
  left: 50%;
  bottom: calc(50% + 9px);
  width: max-content;
  max-width: 10.5rem;
  padding: 0.3rem 0.55rem 0.35rem;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--bg);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.07);
  font-size: 0.8rem;
  line-height: 1.35;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.tmap .tip b {
  display: block;
  font-weight: 600;
  font-style: italic;
}

.tmap .tip i {
  display: block;
  color: var(--muted);
  font-style: normal;
}

.tmap .nm:hover .tip,
.tmap .nm:focus .tip {
  opacity: 1;
  visibility: visible;
}

.tmap .nm.top .tip { bottom: auto; top: calc(50% + 9px); }
.tmap .nm.e .tip { left: auto; right: 50%; }

/* The wall of photos and clips: two rows of boxes in mixed sizes, a
   few in view at a time. */
.reel-track {
  --u: 148px;
  display: flex;
  gap: 10px;
  width: max-content;
}

.reel {
  flex: none;
  display: grid;
  grid-auto-flow: column dense;
  grid-template-rows: repeat(2, minmax(0, 1fr));
  grid-auto-columns: var(--u);
  gap: 10px;
  width: max-content;
  height: calc(var(--u) * 2.5 + 10px);
}

.reel figure {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: 8px;
  background: var(--rule);
}

.reel .big { grid-row: span 2; grid-column: span 2; }
.reel .wide { grid-column: span 2; }

.reel-wrap.dragging { cursor: grabbing; }

.reel img,
.reel video {
  -webkit-user-drag: none;
  user-select: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.reel figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 1.6rem 0.7rem 0.55rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.5), transparent);
  color: #fff;
  font-family: var(--sans);
  font-size: 0.74rem;
}

/* The plane's flight and the inking of the line (tm-fly, tm-draw) are
   written next to the map by tools/build_map.py, one hop per place. */
@keyframes tm-reach {
  from { background: var(--bg); }
  to { background: var(--text); }
}

/* The script at the end of the page marks the section "live" and keeps
   --p, how far through the journey we are (0 to 1). The section is then
   taller than the screen and its contents stay put while that height is
   scrolled through; --p follows the scrolling, but never faster than the
   whole journey in eight seconds. Each animation is held still at the
   moment --p points to. Without the script the finished map is shown. */
.sec.travel.live {
  min-height: 240vh;
}

.live .tpin {
  position: sticky;
  top: max(1.5rem, calc(50vh - 15rem));
}

.live .tmap .rv,
.live .tmap .plane {
  animation: tm-draw 1s linear both paused;
  animation-delay: calc(var(--p, 0) * -1s);
}

.live .tmap .plane {
  animation-name: tm-fly;
}

/* A ring fills in as the plane lands on it. */
.live .tmap .nm[style*="--at"]::before {
  animation: tm-reach 1s linear both paused;
  animation-delay: calc((var(--at) - 0.015 - var(--p, 0)) / 0.015 * 1s);
}

@media (max-width: 760px) {
  .tpin {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
  }

  .sec.travel .side,
  .tmap,
  .reel-wrap {
    grid-column: 1;
    grid-row: auto;
  }

  /* Tightened so the whole wall of pictures is on screen before any
     scrolling: less air at the top, and the opening line's last words sit
     over the empty north-west corner of the map. */
  body:has(.tmap) {
    padding-top: 1.5rem;
  }

  body:has(.tmap) .home-pill {
    margin-bottom: 1.1rem;
  }

  body:has(.tmap) h1 {
    margin-bottom: 0;
  }

  .sec.travel {
    margin-top: 0.9rem;
    padding-top: 0;
  }

  .sec.travel .side {
    position: relative;
    top: auto;
    z-index: 4;
  }

  .sec.travel .kind-line {
    margin-bottom: 0;
  }

  .tmap {
    margin: -0.7rem 0 0.5rem;
  }

  .reel-track {
    --u: 100px;
  }
}

@media (max-width: 760px) {
  /* On a short phone the heading slips off the top so that the map
     and the wall both fit. */
  .live .tpin {
    top: min(4rem, calc(50vh - 20.5rem));
  }
}

/* Placeholder under a heading whose photos are not added yet. */
.soon {
  margin: 0;
  color: var(--muted);
  font-style: italic;
  font-size: 1rem;
}

/* ---------- Event cards on touch screens ---------- */

/* When a card is flipped by a tap, its caption steps aside so the text on
   the back has the whole card; only the Luma button stays in the corner. */
@media (hover: none) {
  .tile .cap {
    transition: opacity 0.25s;
  }

  .tile input:checked ~ .cap {
    opacity: 0;
  }

  .mosaic .tile .back {
    padding-bottom: 2.9rem;
  }

  .mosaic .tile .back .desc {
    font-size: 0.86rem;
    line-height: 1.42;
  }

  .mosaic .tile.small .back {
    padding-bottom: 2.2rem;
  }
}

/* Bigger tap areas for the homepage links and contact icons on phones. */
@media (max-width: 760px) {
  .home .links a {
    display: inline-block;
    padding: 0.3rem 0;
  }

  .home .top-links.icons a {
    padding: 0.6rem;
    margin: -0.6rem 0;
  }

  .home .top-links.icons {
    gap: 0.2rem;
  }
}

/* ---------- Back link ---------- */

/* The link back at the top of every inner page: a soft pill whose arrow
   slides left on hover. */
.home-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 2.5rem;
  padding: 0.35rem 0.9rem 0.35rem 0.75rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--text);
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.2s, background 0.2s;
}

.home-pill span {
  transition: transform 0.2s;
}

.home-pill:hover {
  border-color: var(--text);
  background: #fff;
}

.home-pill:hover span {
  transform: translateX(-3px);
}

/* On a flipped card the "Details" tag must not show through on the back
   (some phone browsers let it). Hide it, and the whole front face, while
   the card is flipped. */
.tile input:checked + .inner .tag {
  visibility: hidden;
}

@media (hover: none) {
  .tile input:checked + .inner .front {
    visibility: hidden;
    transition: visibility 0s 0.28s;
  }
}

/* ---------- Event cards at the reading-column width ---------- */

/* The wall now sits in the same 720px column as the rest of the site, so on
   a laptop the cards are smaller. Captions and the text on the backs scale
   down to fit. */
@media (min-width: 701px) {
  .tile .cap {
    left: 0.8rem;
    right: 0.8rem;
    font-size: 0.72rem;
  }

  .tile .cap strong {
    font-size: 1.15rem;
  }

  .tile.small .cap strong {
    font-size: 0.92rem;
  }

  .tile .back {
    padding: 0.85rem 0.95rem 3.1rem;
  }

  .tile .back .meta {
    margin-bottom: 0.4rem;
    font-size: 0.68rem;
  }

  .tile .back .desc {
    font-size: 0.82rem;
    line-height: 1.42;
  }

  .tile.small .back {
    padding: 0.6rem 0.65rem 2.6rem;
  }

  .tile.small .back .meta {
    font-size: 0.6rem;
  }

  .tile.small .back .desc {
    font-size: 0.66rem;
    line-height: 1.32;
  }

  /* Two cards carry longer text than their height allows at this size. */
  .t-beijing-2 .back .desc,
  .t-researcher-meetup .back .desc {
    font-size: 0.76rem;
    line-height: 1.36;
  }

  .tile .luma {
    right: 0.7rem;
    padding: 0.24rem 0.6rem;
    font-size: 0.7rem;
  }

  .tile.small .luma {
    right: 0.45rem;
    padding: 0.16rem 0.42rem;
    font-size: 0.62rem;
  }
}

/* A long caption never runs under the Luma button: it wraps instead. */
.tile:has(.luma) .cap {
  right: 4.6rem;
}

/* Narrow windows on a computer behave like phones: while a card is flipped
   its caption steps aside so the text on the back has room. */
@media (hover: hover) and (max-width: 700px) {
  .tile .cap {
    transition: opacity 0.25s;
  }

  .tile:hover .cap,
  .tile:focus-within .cap {
    opacity: 0;
  }

  .mosaic .tile .back {
    padding-bottom: 2.9rem;
  }

  .mosaic .tile .back .desc {
    font-size: 0.86rem;
    line-height: 1.42;
  }

  .mosaic .tile.small .back {
    padding-bottom: 2.2rem;
  }
}

/* ---------- Site background ---------- */

/* Three soft glows in the highlighter colours used on the Events and
   Library headings. They are painted on the page itself, at the top, so
   they scroll away with it and do not follow the reader down. */
html {
  background-color: var(--bg);
  background-image:
    radial-gradient(44rem 32rem at 84% -4vh, rgba(255, 224, 120, 0.55), transparent 64%),
    radial-gradient(40rem 32rem at -6% 40vh, rgba(186, 211, 255, 0.55), transparent 64%),
    radial-gradient(38rem 30rem at 100% 96vh, rgba(186, 232, 202, 0.5), transparent 64%);
  background-repeat: no-repeat;
}

body {
  background: transparent;
}

/* On phones the glows keep the same proportions as on a laptop: sized to
   the screen, tucked into the corners and fading out smoothly, so the
   reading area stays plain. */
@media (max-width: 760px) {
  html {
    background-image:
      radial-gradient(62vw 34vh at 104% -3vh, rgba(255, 224, 120, 0.36), transparent 72%),
      radial-gradient(72vw 40vh at -8% 40vh, rgba(172, 200, 255, 0.52), transparent 72%),
      radial-gradient(56vw 32vh at 122% 97vh, rgba(186, 232, 202, 0.3), transparent 72%);
  }
}

/* Homepage links on phones: three on the first row, two on the second. */
@media (max-width: 760px) {
  .home .links::after {
    content: "";
    flex-basis: 100%;
    order: 1;
  }

  .home .links li:nth-child(n + 4) {
    order: 2;
  }
}
