/* ---------- fonts (IBM Plex, self-hosted; Sans is variable 400–600) ---------- */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans Devanagari';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-devanagari-400-devanagari.woff2') format('woff2');
  unicode-range: U+0900-097F, U+1CD0-1CF4, U+1CF7-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B0A;
}
@font-face {
  font-family: 'IBM Plex Sans Devanagari';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-devanagari-500-devanagari.woff2') format('woff2');
  unicode-range: U+0900-097F, U+1CD0-1CF4, U+1CF7-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B0A;
}

/* ---------- tokens ---------- */
:root {
  --bg: #f6f5f1;
  --bg-raised: #fbfaf7;
  --ink: #18181a;
  --logo: #34332f;
  --muted: #5d5c57;
  --faint: #6b6962;
  --underline: #8a8880;
  --line: #dedbd2;
  --accent: #1f4fd1;
  --on-accent: #ffffff;
  --signal: #c8102e;
  --merged: #8250df;
  --merged-ink: #8250df;
  --signal-ink: #a50d26;
  --band: color-mix(in srgb, var(--accent) 4%, var(--bg));
  --focus: var(--accent);

  --sans: 'IBM Plex Sans', 'IBM Plex Sans Devanagari', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
  --deva: 'IBM Plex Sans Devanagari', 'Noto Sans Devanagari', var(--sans);

  --wrap: 68rem;
  --gutter: clamp(1rem, 4vw, 2.5rem);

  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg: #141413;
    --bg-raised: #1b1b19;
    --ink: #ecebe6;
    --logo: #c4c2bb;
    --muted: #a3a198;
    --faint: #8e8c84;
    --underline: #66645d;
    --line: #2d2c29;
    --accent: #7aa2ff;
    --on-accent: #0f1220;
    --signal: #f0596b;
    --merged: #8957e5;
    --merged-ink: #a371f7;
    --signal-ink: #f47a88;
    --band: color-mix(in srgb, var(--accent) 6%, var(--bg));
    color-scheme: dark;
  }
}
:root[data-theme='dark'] {
  --bg: #141413;
  --bg-raised: #1b1b19;
  --ink: #ecebe6;
  --logo: #c4c2bb;
  --muted: #a3a198;
  --faint: #8e8c84;
  --underline: #66645d;
  --line: #2d2c29;
  --accent: #7aa2ff;
  --on-accent: #0f1220;
  --signal: #f0596b;
  --merged: #8957e5;
  --merged-ink: #a371f7;
  --signal-ink: #f47a88;
  --band: color-mix(in srgb, var(--accent) 6%, var(--bg));
  color-scheme: dark;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Exactly the header's height: a section's top border tucks under the header's own
     hairline, so jumping to a section shows one clean line. */
  scroll-padding-top: 5rem;
  /* Native width (easy to grab), page-coloured track, a quiet thumb from the ink colour. */
  scrollbar-color: color-mix(in srgb, var(--ink) 28%, var(--bg)) var(--bg);
}

body {
  /* Short pages keep the footer at the bottom of the window (main grows). */
  display: flex;
  flex-direction: column;
  min-height: 100svh;
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 1rem/1.6 var(--sans);
  font-feature-settings: 'kern', 'liga';
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, ul, ol, dl, dd, figure {
  margin: 0;
}
ul, ol {
  padding: 0;
  list-style: none;
}
img {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--underline);
  transition: text-decoration-color 0.15s, color 0.15s;
}
a:hover {
  color: var(--accent);
  text-decoration-color: currentColor;
}
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 2px;
}

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

.wrap {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.mono {
  font-family: var(--mono);
}
.deva {
  font-family: var(--deva);
}

.skip {
  position: absolute;
  left: -999px;
  top: 0.5rem;
  z-index: 100;
  padding: 0.5rem 0.75rem;
  background: var(--ink);
  color: var(--bg);
}
.skip:focus {
  left: 0.5rem;
}

/* ---------- logo ---------- */
.logo,
.logo:hover {
  display: block;
  width: 5.75rem;
  /* --logo softens the solid mark a little in both themes: off-black in light,
     off-white in dark. */
  color: var(--logo, var(--ink));
}
@media (max-width: 40rem) {
  .logo {
    width: 4.75rem;
  }
}
.logo svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}
.logo path {
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 6;
  stroke-linejoin: round;
}
/* Same sequence as the original: trace the outline, then fill. */
.logo.is-drawing path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  fill-opacity: 0;
  animation:
    logo-draw 1.2s cubic-bezier(0.55, 0.1, 0.35, 1) forwards,
    logo-fill 0.35s ease-out 1.1s forwards;
}
@keyframes logo-draw {
  to { stroke-dashoffset: 0; }
}
@keyframes logo-fill {
  to { fill-opacity: 1; }
}

/* ---------- header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  /* Same colour as the page, frosted; only a hairline sets it apart. */
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
  transition: border-color 0.25s;
}
.site-header.is-scrolled {
  border-bottom-color: var(--line);
}
.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 5rem;
}
.nav {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 2.5vw, 1.75rem);
  /* 16px, not 15: at 15px the bolder hover weight rounds its letters a pixel
     shorter, so the text looked like it shrank. */
  font-size: 1rem;
}
.nav a {
  text-decoration: none;
  white-space: nowrap;
  color: var(--muted);
}
.nav a:hover {
  color: var(--ink);
}
.theme-toggle {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  margin-right: -0.5rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.theme-toggle:hover {
  color: var(--ink);
  background: var(--line);
}
.theme-toggle svg {
  width: 1.125rem;
  height: 1.125rem;
}
/* Shows what you'll switch to: a moon in light, a sun in dark. */
.theme-toggle .sun {
  display: none;
}
:root[data-theme='dark'] .theme-toggle .sun {
  display: block;
}
:root[data-theme='dark'] .theme-toggle .moon {
  display: none;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .theme-toggle .sun {
    display: block;
  }
  :root:not([data-theme='light']) .theme-toggle .moon {
    display: none;
  }
}
@media (max-width: 40rem) {
  .nav .nav-optional {
    display: none;
  }
}
/* ---------- phone menu (built by main.js) ---------- */
.menu-toggle {
  display: none;
}
@media (max-width: 40rem) {
  /* .js is set in <head>, before first paint, so the link row never flashes in;
     until the script adds the button, its space is held so nothing shifts. */
  .js .nav > a {
    display: none;
  }
  .js .theme-toggle {
    margin-right: 0;
  }
  .js .site-header:not(.has-menu) .nav {
    margin-right: 2.15rem;
  }
  /* 44px target. The old site's icons: staggered lines, and an x with a gap. */
  .menu-toggle {
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    margin-right: -0.6rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ink);
    cursor: pointer;
  }
  .menu-toggle svg {
    grid-area: 1 / 1;
    width: 1.4rem;
    height: 1.4rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    transition: opacity 0.2s, transform 0.3s var(--ease-out);
  }
  .menu-close-icon,
  .menu-toggle[aria-expanded='true'] .menu-open-icon {
    opacity: 0;
    transform: rotate(-45deg) scale(0.8);
  }
  .menu-toggle[aria-expanded='true'] .menu-close-icon {
    opacity: 1;
    transform: none;
  }
  .menu-open {
    overflow: hidden;
  }
}
/* Phones only (the button that opens it shows below 40rem). Full screen under the
   header, the links centred with an icon each, rising in one after another. */
.mobile-menu {
  position: fixed;
  z-index: 9;
  inset: 5rem 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem var(--gutter);
  overflow-y: auto;
  background: var(--bg);
  opacity: 0;
  transition: opacity 0.25s;
}
.mobile-menu[hidden] {
  display: none;
}
.mobile-menu.is-open {
  opacity: 1;
}
.mobile-menu ul {
  display: grid;
  gap: clamp(1.5rem, 5vh, 2.5rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.mobile-menu li {
  opacity: 0;
  transform: translateY(1.25rem);
  transition: opacity 0.4s, transform 0.5s var(--spring);
  transition-delay: calc(0.12s + var(--i) * 0.07s);
}
.mobile-menu.is-open li {
  opacity: 1;
  transform: none;
}
.mobile-menu a {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.85rem;
  color: var(--muted);
  font-size: 1.75rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  text-decoration: none;
  transition: color 0.15s, transform 0.2s var(--spring);
}
.mobile-menu a .i {
  width: 1.5rem;
  height: 1.5rem;
}
.mobile-menu a:hover,
.mobile-menu a[aria-current] {
  color: var(--ink);
}
.mobile-menu a:active {
  transform: scale(0.96);
}
/* The page or section you're on: its label gets the blue line. */
.mobile-menu a[aria-current] span {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.3em;
}
@media (max-width: 24rem) {
  .logo {
    width: 4.25rem;
  }
  .nav {
    gap: 0.7rem;
    font-size: 0.875rem;
  }
}

/* ---------- hero ---------- */
/* The hero fills the first screen: content centred, the chevron at the bottom,
   and the next section starts just below the fold. 5rem + 1px is the header. */
.hero {
  display: flex;
  flex-direction: column;
  min-height: calc(100svh - 5rem - 1px);
  padding-block: clamp(2rem, 5vw, 3rem) 1.5rem;
}
.hero-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  margin-top: auto;
}
/* The name reads as a clear label above the headline: ink and a size up; the
   place stays small and grey beside it. */
.hero-name {
  font-size: clamp(1.125rem, 2vw, 1.375rem);
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.hero-name span {
  color: var(--faint);
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0;
}
.hero-lede {
  max-width: 21em;
  margin-top: 1.25rem;
  font-size: clamp(1.75rem, 4.4vw, 3.15rem);
  font-weight: 500;
  /* 1.2 rather than 1.14 leaves room under line one for the swash. */
  line-height: 1.2;
  letter-spacing: -0.022em;
  text-wrap: balance;
}
/* "Software engineer" sits on an ink brush stroke in the accent blue. The stroke
   is a file in assets/swashes/; to
   try another, change the <use href> in index.html (wave-bristle, long-thin,
   arc-bristle, heavy-arc). */
.swash {
  position: relative;
  z-index: 0;
  display: inline-block;
  white-space: nowrap;
}
.swash-mark {
  position: absolute;
  z-index: -1;
  /* Under the baseline, starting at the S so the left edge stays clean. */
  bottom: -0.14em;
  left: 0;
  width: calc(99% + 4px);
  height: 0.32em;
  overflow: visible;
  fill: var(--accent);
  opacity: 0.55;
  pointer-events: none;
}
/* The stroke is static for now (the "Now" dot already moves). To paint it in on
   arrival again, after the logo:
   .play-intro .swash-mark { animation: swash-wipe 0.9s cubic-bezier(0.6, 0, 0.3, 1) 1.3s both; } */
@keyframes swash-wipe {
  from { clip-path: inset(-60% 100% -60% -10%); }
  to { clip-path: inset(-60% -10% -60% -10%); }
}
.hero-lede em {
  font-style: normal;
  color: var(--muted);
}
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.5rem;
  margin-top: 2.25rem;
}

.button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.1rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 500;
  text-decoration: none;
  transition: transform 0.15s, background 0.15s, box-shadow 0.2s;
}
.button:hover {
  color: var(--on-accent);
  background: color-mix(in srgb, var(--accent) 88%, var(--ink));
  box-shadow: 0 6px 18px -8px color-mix(in srgb, var(--accent) 70%, transparent);
  transform: translateY(-1px);
}
.button:active {
  transform: scale(0.98);
}
.link-out {
  font-weight: 500;
}
.link-out::after,
.arrow-out::after {
  content: '↗';
  display: inline-block;
  margin-left: 0.2em;
  font-family: var(--mono);
  font-size: 0.85em;
  color: var(--faint);
  text-decoration: none;
  transition: transform 0.15s, color 0.15s;
}
.link-out:hover::after,
.arrow-out:hover::after {
  color: var(--accent);
  transform: translate(1px, -1px);
}

/* proof strip */
.facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: clamp(3rem, 7vw, 4.5rem);
  border-top: 1px solid var(--line);
}
.facts > div {
  padding: 1rem 1.25rem 0 0;
}
.facts > div + div {
  padding-left: 1.25rem;
  border-left: 1px solid var(--line);
}
/* Labels: a size up and a shade darker than the faintest grey, so they read. */
.facts dt {
  font: 400 0.8125rem/1.4 var(--mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
/* "Now" is the one live fact: a blue dot with a slow ripple (blue for current,
   not green, which reads as "available"). Still under reduced motion. */
/* Same two columns as the value below: the dot centred over the icon, the label
   over the text (the icon is 1em at the value's 0.9375rem). */
.facts dt.is-live {
  display: grid;
  grid-template-columns: 0.9375rem minmax(0, 1fr);
  column-gap: 0.45rem;
  align-items: center;
}
.live-dot {
  justify-self: center;
  position: relative;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--accent);
}
.live-dot::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--accent);
  animation: live-ripple 2.6s ease-out infinite;
}
@keyframes live-ripple {
  0% { transform: scale(1); opacity: 0.55; }
  70%, 100% { transform: scale(3); opacity: 0; }
}
.facts dd {
  margin-top: 0.4rem;
  font-size: 0.9375rem;
  line-height: 1.45;
}
.facts dd small {
  display: block;
  color: var(--muted);
  font-size: 0.875rem;
}
@media (max-width: 40rem) {
  .facts {
    grid-template-columns: 1fr;
  }
  .facts > div,
  .facts > div + div {
    padding-left: 0;
    border-left: 0;
  }
  .facts > div + div {
    margin-top: 1rem;
    border-top: 1px solid var(--line);
  }
}

/* Each fact's value leads with a small icon: grey like the tech chips, except the
   merge icon, which keeps GitHub's purple. The icon hangs in its own column, so the
   second line lines up with the text above it, not with the icon. */
.facts dd:has(.fact-icon) {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.45rem;
}
.facts dd:has(.fact-icon) small {
  grid-column: 2;
}
.fact-icon {
  align-self: center;
  color: var(--muted);
}
.merge-icon {
  color: var(--merged-ink);
}

/* ---------- sections ---------- */
.section {
  padding-block: clamp(3.25rem, 7vw, 5.25rem);
  border-top: 1px solid var(--line);
}
.section.band {
  background: var(--band);
}
.section-head {
  margin-bottom: clamp(1.75rem, 4vw, 2.75rem);
}
.section-head h2 {
  font-size: clamp(2rem, 4vw, 2.875rem);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.022em;
}
.section-head p {
  max-width: 36rem;
  margin-top: 0.5rem;
  color: var(--muted);
  font-size: 1.0625rem;
}

/* ---------- experience ---------- */
.role-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.25rem 1rem;
}
.role-head h3 {
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.role-head h3 span {
  color: var(--muted);
}
.when {
  font: 400 0.8125rem/1.5 var(--mono);
  color: var(--faint);
}
/* The place drops to its own line on phones rather than running off the edge. */
.where {
  white-space: nowrap;
}
.role-body {
  max-width: 40rem;
  margin-top: 0.75rem;
}
.where::before {
  content: ' · ';
}
/* A long institution name gets its own line instead of wrapping mid-title. */
.role h3 .org {
  display: block;
  margin-top: 0.2rem;
  font-size: 1rem;
}
/* A hairline between roles, the same as the Community list. */
.role + .role {
  margin-top: clamp(2.25rem, 4.5vw, 3rem);
  padding-top: clamp(2.25rem, 4.5vw, 3rem);
  border-top: 1px solid var(--line);
}
/* Wide screens: dates and place in a left rail, the work beside them. */
@media (min-width: 56rem) {
  .role {
    display: grid;
    grid-template-columns: 11rem minmax(0, 40rem);
    column-gap: 2.5rem;
    align-items: baseline;
  }
  .role .role-head {
    display: contents;
  }
  /* Spans both rows so its two lines don't push the body down. */
  .role .when {
    grid-column: 1;
    grid-row: 1 / span 2;
    align-self: start;
    padding-top: 0.5rem;
    white-space: normal;
  }
  .role h3 {
    grid-column: 2;
    grid-row: 1;
  }
  .role .role-body {
    grid-column: 2;
  }
  .where {
    display: block;
    margin-top: 0.15rem;
  }
  .where::before {
    content: none;
  }
}
.ticks {
  display: grid;
  gap: 0.6rem;
  margin-top: 1rem;
}
.ticks li {
  position: relative;
  padding-left: 1.25rem;
}
/* Key phrases in a bullet: a touch bolder and darker, no colour (blue means link). */
.ticks strong {
  font-weight: 600;
  color: var(--ink);
}
/* A small grey dot, centred on the first line. */
.ticks li::before {
  content: '';
  position: absolute;
  left: 0.1rem;
  top: calc(0.8em - 0.175rem);
  width: 0.35rem;
  height: 0.35rem;
  border-radius: 50%;
  background: var(--faint);
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 1.1rem;
}

/* ---------- featured (KDE) ---------- */
.feature {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  padding: clamp(1.25rem, 3vw, 2rem);
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-raised);
}
.feature-meta {
  font: 400 0.8125rem/1.5 var(--mono);
  color: var(--muted);
}
.feature h3 {
  margin-top: 0.6rem;
  font-size: clamp(1.375rem, 2.6vw, 1.75rem);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.feature p {
  margin-top: 0.9rem;
  color: var(--muted);
}
.feature p strong {
  color: var(--ink);
  font-weight: 500;
}
.feature-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin-top: 1.5rem;
}
/* The write-up is the main next step (an outlined pill, like a lighter twin of
   the hero button); the merge request is supporting evidence (grey text link). */
.feature-links {
  align-items: center;
}
.feature-links a {
  color: var(--muted);
  text-decoration-color: var(--underline);
}
.feature-links a:hover {
  color: var(--ink);
}
.button-outline {
  display: inline-flex;
  align-items: center;
  padding: 0.55rem 1.05rem;
  border: 1.5px solid var(--accent);
  border-radius: 999px;
  font-weight: 500;
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}
.feature-links a.button-outline {
  color: var(--accent);
}
.feature-links a.button-outline:hover {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* FiArrowRight from react-icons, centred by the flex row; it hops on hover. */
.button-outline {
  gap: 0.45rem;
}
.button-outline .i {
  width: 1.05em;
  height: 1.05em;
}
.button-outline:hover .i {
  animation: hop-right 0.7s ease-in-out infinite;
}

.bs-today {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-left: clamp(1.25rem, 3vw, 2rem);
  border-left: 1px solid var(--line);
}
.bs-today .kicker {
  font: 400 0.75rem/1.4 var(--mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint);
}
.bs-today .bs-date {
  margin-top: 0.75rem;
  font-family: var(--deva);
  font-size: clamp(1.875rem, 3.4vw, 2.375rem);
  font-weight: 500;
  color: var(--signal-ink);
  line-height: 1.15;
}
.bs-today .bs-date-latin {
  margin-top: 0.35rem;
  color: var(--muted);
}
.bs-today .note {
  margin-top: 1.25rem;
  font-size: 0.875rem;
  color: var(--faint);
}
@media (max-width: 46rem) {
  .feature {
    grid-template-columns: 1fr;
  }
  .bs-today {
    padding: 1.25rem 0 0;
    border-left: 0;
    border-top: 1px solid var(--line);
  }
}

/* ---------- row lists (contributions, projects) ---------- */
/* Merged pull requests: small cards, each shaped like the PR it describes. */
.prs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem;
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
}
.pr {
  display: flex;
  flex-direction: column;
  padding: 1.25rem 1.35rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-raised);
  transition: border-color 0.2s, transform 0.25s var(--ease-out), box-shadow 0.25s;
}
.pr:hover {
  border-color: color-mix(in srgb, var(--merged) 40%, var(--line));
  box-shadow: 0 14px 30px -20px rgb(0 0 0 / 0.4);
  transform: translateY(-2px);
}
.pr-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.pr-repo {
  min-width: 0;
  overflow: hidden;
  font: 400 0.8125rem/1.5 var(--mono);
  color: var(--muted);
  text-decoration: none;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pr-repo:hover {
  color: var(--ink);
}
.pr h3 {
  margin-top: 0.6rem;
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.35;
}
.pr h3 a {
  text-decoration: none;
}
.pr h3 a:hover {
  color: var(--ink);
  text-decoration: underline;
}
.pr p {
  margin-top: 0.4rem;
  color: var(--muted);
  font-size: 0.9375rem;
}
.pr .pr-foot {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: auto;
  padding-top: 1rem;
  font: 400 0.8125rem/1.5 var(--mono);
}
.pr-merged {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--muted);
}
.pr-foot a {
  color: var(--muted);
  text-decoration: none;
}
.pr-foot a:hover {
  color: var(--ink);
}
@media (max-width: 46rem) {
  .prs {
    grid-template-columns: minmax(0, 1fr);
  }
}
.pill {
  display: inline-block;
  padding: 0.05rem 0.45rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font: 400 0.75rem/1.5 var(--mono);
  color: var(--muted);
}
.pill.merged {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.22rem 0.6rem 0.22rem 0.5rem;
  border-color: var(--merged);
  background: var(--merged);
  color: #fff;
  line-height: 1.3;
}
.pill.merged .i {
  width: 0.95rem;
  height: 0.95rem;
}

/* ---------- icons ---------- */
.i {
  display: inline-block;
  flex: none;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}

/* Tech chip: icon in the text colour, brand colour on hover. */
.tech {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--muted);
  transition: border-color 0.15s, color 0.15s;
}
.tech .i {
  color: var(--faint);
  transition: color 0.15s;
}
.tech:hover {
  color: var(--ink);
  border-color: var(--underline);
}
.tech:hover .i {
  color: var(--brand, var(--ink));
}
[data-brand='python'] { --brand: #3776ab; }
[data-brand='fastapi'] { --brand: #009688; }
[data-brand='scrapy'] { --brand: #60a839; }
[data-brand='selenium'] { --brand: #43b02a; }
[data-brand='opencv'] { --brand: #5c3ee8; }
[data-brand='tensorflow'] { --brand: #ff6f00; }
[data-brand='chromium'] { --brand: #4285f4; }
[data-brand='jenkins'] { --brand: #d24939; }
[data-brand='s3'] { --brand: #569a31; }
[data-brand='mysql'] { --brand: #4479a1; }
[data-brand='postgresql'] { --brand: #4169e1; }
[data-brand='sqlalchemy'] { --brand: #d71f00; }
[data-brand='docker'] { --brand: #2496ed; }
[data-brand='linux'] { --brand: #d9a400; }
[data-brand='fedora'] { --brand: #51a2da; }
[data-brand='git'] { --brand: #f05032; }
[data-brand='bash'] { --brand: #4eaa25; }
[data-brand='cmake'] { --brand: #064f8c; }
[data-brand='cplusplus'] { --brand: #00599c; }
[data-brand='javascript'] { --brand: #d4b300; }
[data-brand='qt'] { --brand: #41cd52; }
[data-brand='kde'] { --brand: #1d99f3; }
[data-brand='emacs'] { --brand: #7f5ab6; }
[data-brand='linkedin'] { --brand: #0a66c2; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) [data-brand='cmake'] { --brand: #4f9be0; }
  :root:not([data-theme='light']) [data-brand='cplusplus'] { --brand: #659ad2; }
  :root:not([data-theme='light']) [data-brand='linkedin'] { --brand: #3b8ee6; }
  :root:not([data-theme='light']) [data-brand='emacs'] { --brand: #a688d6; }
}
:root[data-theme='dark'] [data-brand='cmake'] { --brand: #4f9be0; }
:root[data-theme='dark'] [data-brand='cplusplus'] { --brand: #659ad2; }
:root[data-theme='dark'] [data-brand='linkedin'] { --brand: #3b8ee6; }
:root[data-theme='dark'] [data-brand='emacs'] { --brand: #a688d6; }

/* Links that lead with a brand icon (GitHub, LinkedIn, KDE). */
.link-icon {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-weight: 500;
}
.card .link-icon {
  gap: 0.35rem;
}
.link-icon.arrow-out::after {
  margin-left: -0.15rem;
}
.link-icon .i {
  color: var(--muted);
  transition: color 0.15s;
}
.link-icon:hover .i {
  color: var(--brand, var(--ink));
}


/* ---------- projects ---------- */
.cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem;
}
.card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-raised);
}
.card-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 1.1rem 1.25rem 1.25rem;
}
.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.card h3 {
  font-size: 1.125rem;
  font-weight: 500;
}
.card h3 a {
  text-decoration: none;
}
.card h3 a:hover {
  text-decoration: underline;
}
.card p {
  margin-top: 0.45rem;
  color: var(--muted);
}
.card .ticks {
  gap: 0.4rem;
  font-size: 0.9375rem;
  color: var(--muted);
}

/* "Read more" inside a card: native <details>, animated where the browser can. */
:root {
  interpolate-size: allow-keywords;
}
/* Cards match heights while closed; an opened one grows alone. The script pins
   every card at its closed height first, so the neighbours don't shrink, and
   unpins only after the last one has finished closing. */
.cards.is-pinned {
  align-items: start;
}
.more-info {
  margin-top: 0.5rem;
}
.more-info summary {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  list-style: none;
}
.more-info summary::-webkit-details-marker {
  display: none;
}
.more-info summary:hover {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.more-info summary .i {
  transition: transform 0.25s var(--ease-out, ease);
}
.more-info[open] summary .i {
  transform: rotate(180deg);
}
.more-info .when-open,
.more-info[open] .when-closed {
  display: none;
}
.more-info[open] .when-open {
  display: inline;
}
.more-info > :not(summary) {
  margin-top: 0.6rem;
}
.more-info::details-content {
  overflow: hidden;
  block-size: 0;
  transition: block-size 0.3s var(--ease-out, ease), content-visibility 0.3s allow-discrete;
}
.more-info[open]::details-content {
  block-size: auto;
}
.card .tags {
  margin-top: 0.9rem;
}
.card .links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
  margin-top: auto;
  padding-top: 0.9rem;
  font-size: 0.9375rem;
}
.stars {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font: 400 0.8125rem/1.5 var(--mono);
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.stars .i {
  color: #d9a400;
}
.stars:hover {
  color: var(--ink);
}
/* GitHub-style star button on my own projects: [ ★ Star | 49 ]. */
.star-btn {
  display: inline-flex;
  align-self: center;
  flex: none;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-raised);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  transition: border-color 0.15s, background-color 0.15s;
}
.star-btn > span {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem 0.6rem;
}
.star-btn-count {
  border-left: 1px solid var(--line);
  font-family: var(--mono);
  transition: border-color 0.15s;
}
.star-btn .i {
  width: 0.95rem;
  height: 0.95rem;
  color: #d9a400;
}
.star-btn:hover {
  color: var(--ink);
  border-color: var(--faint);
  background: var(--bg);
}
.star-btn:hover .star-btn-count {
  border-left-color: var(--faint);
}
@media (max-width: 46rem) {
  .cards {
    grid-template-columns: 1fr;
  }
}

/* ---------- carousel ---------- */
.carousel {
  position: relative;
  background: var(--shot-bg, #17161f);
  border-bottom: 1px solid var(--line);
}
.carousel-track {
  display: flex;
  aspect-ratio: 16 / 9;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.carousel-track::-webkit-scrollbar {
  display: none;
}
.carousel-track:focus-visible {
  outline-offset: -3px;
}
.carousel-track img {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  padding: var(--shot-pad, 0);
  object-fit: contain;
  scroll-snap-align: center;
}
/* Dots sit in the backdrop's bottom margin, under the framed screenshot, with no
   chrome of their own. The current screenshot's dot stretches into a short pill. */
.carousel-nav {
  position: absolute;
  right: 0;
  bottom: 0.3rem;
  left: 0;
  display: none;
  justify-content: center;
}
.carousel.is-ready .carousel-nav {
  display: flex;
}
.carousel-dots {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}
.carousel-dots span {
  width: 0.3125rem;
  height: 0.3125rem;
  border-radius: 999px;
  background: rgb(61 60 56 / 0.22);
  cursor: pointer;
  transition: width 0.3s var(--ease-out), background 0.3s;
}
.carousel-dots span:hover {
  background: rgb(61 60 56 / 0.45);
}
.carousel-dots span.is-active {
  width: 0.9rem;
  background: rgb(61 60 56 / 0.7);
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- skills ---------- */
.skills {
  display: grid;
  gap: 1.75rem;
}
.skills h3 {
  font-size: 0.9375rem;
  font-weight: 500;
}
.skills .tags {
  margin-top: 0.6rem;
}

/* ---------- simple list (community, education) ---------- */
.list > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.25rem 1.5rem;
  padding-block: 1rem;
  border-bottom: 1px solid var(--line);
}
.list > li:first-child {
  padding-top: 0;
}
.list h3 {
  font-size: 1rem;
  font-weight: 500;
}
.list p {
  grid-column: 1 / -1;
  max-width: 40rem;
  color: var(--muted);
}
/* Wide screens: same left rail as Experience and Education. */
@media (min-width: 56rem) {
  .list > li {
    grid-template-columns: 11rem minmax(0, 40rem);
    column-gap: 2.5rem;
  }
  .list .when {
    grid-column: 1;
    grid-row: 1 / span 2;
    padding-top: 0.15rem;
    white-space: normal;
  }
  .list h3,
  .list p {
    grid-column: 2;
  }
}

/* ---------- contact ---------- */
/* Centred: heading, then the direct links, then the form. */
.contact {
  padding-block: clamp(4rem, 10vw, 7rem) 2rem;
  border-top: 1px solid var(--line);
  text-align: center;
}
.contact h2 {
  font-size: clamp(1.75rem, 4.4vw, 3rem);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.022em;
  text-wrap: balance;
}
.contact-sub {
  margin-top: 0.75rem;
  color: var(--muted);
  font-size: 1.0625rem;
  text-wrap: balance;
}
/* Icon only (the label is for screen readers); it turns into a tick when copied. */
.copy {
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: none;
  color: var(--muted);
  font: 400 0.8125rem/1.5 var(--mono);
  cursor: pointer;
}
.copy:hover {
  color: var(--ink);
  border-color: var(--faint);
}
/* Email, GitHub and LinkedIn in one centred row under the heading. */
.contact-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0.75rem 2rem;
  margin-top: 1.5rem;
}
/* Thin vertical rules between the three, centred in the 2rem gap. Hidden on
   phones, where the row wraps and a rule could start a line. */
.contact-links > * + * {
  position: relative;
}
.contact-links > * + *::before {
  content: '';
  position: absolute;
  top: 15%;
  bottom: 15%;
  left: -1rem;
  width: 1px;
  background: var(--line);
}
@media (max-width: 34rem) {
  .contact-links > * + *::before {
    display: none;
  }
}
.email-pair {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
}
/* The form sits centred below, in a narrow column. */
.contact-form {
  display: grid;
  gap: 1.1rem;
  max-width: 32rem;
  margin: clamp(1.75rem, 3.5vw, 2.25rem) auto 0;
  text-align: left;
}
.contact-form h3 {
  font-size: 1.125rem;
  font-weight: 500;
}
/* Name and email share a row; they stack on phones. */
.field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
}
@media (max-width: 30rem) {
  .field-row {
    grid-template-columns: 1fr;
    gap: 1.1rem;
  }
}
.field {
  display: grid;
  gap: 0.35rem;
}
.field label {
  color: var(--muted);
  font: 400 0.8125rem/1.5 var(--mono);
}
.field input,
.field textarea {
  width: 100%;
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-raised);
  color: var(--ink);
  font: inherit;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field input::placeholder,
.field textarea::placeholder {
  color: var(--faint);
  opacity: 1;
}
.field textarea {
  min-height: 8rem;
  resize: vertical;
}
.field input:hover,
.field textarea:hover {
  border-color: var(--faint);
}
.field input:focus,
.field textarea:focus {
  border-color: var(--accent);
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
/* Holds the reCAPTCHA box once someone starts on the form (304px wide). */
.captcha:empty {
  display: none;
}
@media (max-width: 22rem) {
  .captcha {
    transform: scale(0.9);
    transform-origin: 0 0;
  }
}
.form-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
}
button.button {
  border: 0;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
}
button.button:disabled {
  opacity: 0.6;
  cursor: progress;
  transform: none;
}
.form-status {
  color: var(--muted);
  font-size: 0.9375rem;
}
.form-status.is-ok {
  color: var(--ink);
}
.form-status.is-error {
  color: var(--signal-ink);
}

main {
  flex: 1 0 auto;
  /* A flex item with auto side margins (.wrap) would shrink to fit; keep it full width. */
  width: 100%;
}
.site-footer {
  padding-block: 2.5rem 3rem;
  color: var(--faint);
  font: 400 0.8125rem/1.6 var(--mono);
}
.site-footer a {
  color: inherit;
}
/* Three parts: copyright left, back to top centred, credit right. On phones
   they stack, centred, with back to top last. */
.site-footer .wrap:has(.to-top) {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}
.site-footer .credit {
  justify-self: end;
}
@media (max-width: 40rem) {
  .site-footer .wrap:has(.to-top) {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
  }
  .site-footer .credit {
    justify-self: center;
  }
  .site-footer .to-top {
    order: 1;
  }
}
.to-top {
  text-decoration: none;
  transition: color 0.15s;
}
.to-top:hover {
  color: var(--ink);
}
.to-top span {
  display: inline-block;
  transition: transform 0.2s var(--ease-out);
}
.to-top:hover span {
  transform: translateY(-2px);
}
.site-footer .wrap {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
}

/* ---------- article (writing) ---------- */
.article {
  max-width: 42rem;
  margin-inline: auto;
  padding-block: clamp(3rem, 8vw, 5rem) 3rem;
}
.article-meta {
  font: 400 0.8125rem/1.6 var(--mono);
  color: var(--faint);
}
.article h1 {
  margin-top: 1rem;
  font-size: clamp(2rem, 5vw, 2.9rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.025em;
  text-wrap: balance;
}
.article .standfirst {
  margin-top: 1.25rem;
  font-size: 1.1875rem;
  line-height: 1.55;
  color: var(--muted);
}
.prose {
  margin-top: 2.5rem;
  font-size: 1.0625rem;
  line-height: 1.7;
}
.prose > * + * {
  margin-top: 1.15em;
}
.prose h2 {
  margin-top: 2.4em;
  font-size: 1.3125rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.prose ol,
.prose ul {
  padding-left: 1.25rem;
}
.prose ol {
  list-style: decimal;
}
.prose ul {
  list-style: disc;
}
.prose li + li {
  margin-top: 0.4em;
}
.prose li::marker {
  color: var(--faint);
}
.prose code {
  padding: 0.1em 0.3em;
  border-radius: 3px;
  background: color-mix(in srgb, var(--line) 60%, transparent);
  font: 400 0.875em/1 var(--mono);
}
.prose pre {
  overflow-x: auto;
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-raised);
  font: 400 0.84rem/1.65 var(--mono);
  tab-size: 4;
}
.prose pre code {
  padding: 0;
  background: none;
  font-size: inherit;
}
.prose pre .c {
  color: var(--faint);
}
.prose figure img,
.prose figure video {
  border: 1px solid var(--line);
  border-radius: 6px;
}
.prose figure video {
  display: block;
  width: 100%;
  height: auto;
  background: #1a1b26;
}
.prose figcaption {
  margin-top: 0.6rem;
  font-size: 0.875rem;
  color: var(--faint);
}
.prose blockquote {
  margin: 0;
  padding-left: 1rem;
  border-left: 2px solid var(--signal);
  color: var(--muted);
}

/* converter */
.converter {
  margin-block: 2.5rem;
  padding: 1.25rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-raised);
}
.converter label {
  display: block;
  font: 400 0.75rem/1.4 var(--mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint);
}
.converter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem 1.5rem;
  margin-top: 0.6rem;
}
.converter input {
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--bg);
  color: var(--ink);
  font: 400 1rem/1.4 var(--mono);
}
.converter output {
  font-family: var(--deva);
  font-size: 1.5rem;
  font-weight: 500;
}
.converter output small {
  margin-left: 0.5rem;
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 400;
  color: var(--muted);
}

/* ---------- writing index ---------- */
/* Starts at the same left edge as the logo and footer; the column stays narrow. */
.writing-index {
  max-width: 42rem;
  padding-block: clamp(3rem, 8vw, 5rem) 3rem;
}
.writing-index h1 {
  font-size: clamp(2rem, 5vw, 2.9rem);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.022em;
}
.writing-index .standfirst {
  margin-top: 0.75rem;
  color: var(--muted);
  font-size: 1.1875rem;
}
/* One row per post: date in a narrow rail, then title and summary. */
.posts {
  margin: clamp(2rem, 5vw, 3rem) 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}
.post {
  position: relative;
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr);
  gap: 0.35rem 1.5rem;
  padding-block: 1.5rem;
  border-bottom: 1px solid var(--line);
}
.post time {
  padding-top: 0.3rem;
  color: var(--faint);
  font: 400 0.8125rem/1.5 var(--mono);
}
.post h2 {
  font-size: 1.25rem;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
}
/* The whole row is the link; only the title underlines. */
.post h2 a {
  text-decoration: none;
}
.post h2 a::after {
  content: '';
  position: absolute;
  inset: 0;
}
.post:hover h2 a {
  text-decoration: underline;
  text-decoration-color: var(--underline);
  text-underline-offset: 0.2em;
}
.post p {
  margin-top: 0.35rem;
  color: var(--muted);
}
.post .post-meta {
  margin-top: 0.6rem;
  color: var(--faint);
  font: 400 0.8125rem/1.5 var(--mono);
}
@media (max-width: 36rem) {
  .post {
    grid-template-columns: 1fr;
  }
  .post time {
    padding-top: 0;
  }
}

.back {
  font: 400 0.875rem/1.5 var(--mono);
  text-decoration: none;
  color: var(--muted);
}
.back:hover {
  color: var(--ink);
}

/* ---------- micro-interactions ---------- */
/* Every one of these is switched off by the reduced-motion block below. */
:root {
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* Nav: an underline slides in from the left. */
.nav a {
  position: relative;
}
.nav a::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: -0.3rem;
  left: 0;
  height: 1.5px;
  border-radius: 1px;
  background: var(--accent);
  transform: scaleX(0);
  /* Grows in from the left, leaves toward the right. */
  transform-origin: right;
  transition: transform 0.4s var(--ease-out);
}
.nav a:hover::after,
.nav a[aria-current='location']::after,
.nav a[aria-current='page']::after,
.nav a[aria-current='true']::after {
  transform: scaleX(1);
  transform-origin: left;
}
/* Scrolling back up: the old line leaves to the left, the new one grows from the right. */
.nav.is-backward a::after {
  transform-origin: left;
}
.nav.is-backward a[aria-current='location']::after {
  transform-origin: right;
}
/* Hover and the section you're reading: darker, a touch bolder; only the line is blue. */
.nav a:hover,
.nav a[aria-current='location'],
.nav a[aria-current='page'],
.nav a[aria-current='true'] {
  color: var(--ink);
  font-weight: 500;
}
/* An invisible bold copy reserves the bolder width, so neighbours don't shift. */
.nav a {
  display: inline-flex;
  flex-direction: column;
  /* Plex Sans is a variable font, so the weight eases instead of snapping. */
  transition: color 0.3s, font-weight 0.4s var(--ease-out);
}
.nav a::before {
  content: attr(data-text) / '';
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 500;
}

/* Arrows hop toward where the link goes. */
.arrow-in::after {
  content: '→';
  display: inline-block;
  margin-left: 0.3em;
}
.arrow-in:hover::after {
  animation: hop-right 0.7s ease-in-out infinite;
}
.link-out:hover::after,
.arrow-out:hover::after {
  animation: hop-out 0.7s ease-in-out infinite;
}
/* Hero: "Read my writing" stays a plain text link, so "Get in touch" leads.
   Only the words underline; the small arrow hops on hover. */
.hero-read {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--muted);
  font-weight: 500;
  text-decoration: none;
  transition: color 0.15s;
}
.hero-read span {
  text-decoration: underline;
  text-decoration-color: var(--underline);
  text-underline-offset: 0.2em;
}
.hero-read:hover {
  color: var(--ink);
}
.hero-read .i {
  width: 1rem;
  height: 1rem;
}
.hero-read:hover .i {
  animation: hop-right 0.7s ease-in-out infinite;
}
@keyframes hop-right {
  0%, 100% { transform: translateX(0); }
  50% { transform: translateX(4px); }
}
@keyframes hop-out {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(2px, -2px); }
}

/* Scroll hint: the FiChevronDown bob from the first version of the old site
   (ScrollChevron.tsx: 10px, 0.8s ease-in-out, back and forth). */
.scroll-hint {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  flex: none;
  margin: auto auto 0;
  padding-top: 2.25rem;
  box-sizing: content-box;
  color: var(--ink);
  text-decoration: none;
  animation: bob 0.8s ease-in-out infinite alternate;
  transition: opacity 0.3s;
}
.scroll-hint:hover {
  color: var(--ink);
}
.scroll-hint .i {
  width: 2.25rem;
  height: 2.25rem;
}
@keyframes bob {
  from { transform: translateY(0); }
  to { transform: translateY(10px); }
}
.scroll-hint.is-gone {
  opacity: 0;
  pointer-events: none;
}

/* Sections rise in once as they enter the viewport (only when JS runs). */
.js .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s ease, transform 0.6s var(--ease-out);
}
.js .reveal.is-in {
  opacity: 1;
  transform: none;
}

/* Cards lift slightly. */
.card {
  transition: border-color 0.2s, transform 0.25s var(--ease-out), box-shadow 0.25s;
}
.card:hover {
  border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
  box-shadow: 0 14px 30px -20px rgb(0 0 0 / 0.4);
  transform: translateY(-2px);
}

/* Chip icons pop into their brand colour. */
.tech .i {
  transition: color 0.15s, transform 0.3s var(--spring);
}
.tech:hover .i {
  transform: scale(1.2);
}
.link-icon .i {
  transition: color 0.15s, transform 0.3s var(--spring);
}
.link-icon:hover .i {
  transform: scale(1.15);
}

/* Stars twinkle. */
.stars:hover .i,
.star-btn:hover .i {
  animation: twinkle 0.6s ease;
}
@keyframes twinkle {
  40% { transform: scale(1.35) rotate(-18deg); }
}

/* Copy -> Copied, with a check that pops in. */
.copy {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  transition: color 0.15s, border-color 0.15s;
}
.copy .i {
  width: 0.9rem;
  height: 0.9rem;
}
.copy.is-done {
  color: var(--accent);
  border-color: var(--accent);
}
.copy.is-done .i {
  animation: pop 0.35s var(--spring);
}
@keyframes pop {
  from { transform: scale(0.3); }
}

/* Theme toggle turns over. */
.theme-toggle svg {
  transition: transform 0.45s var(--ease-out);
}
.theme-toggle.is-turned svg {
  transform: rotate(360deg);
}
/* On hover the moon rocks like a cradle and the sun's rays turn a little. */
.theme-toggle:hover .moon {
  animation: moon-wobble 0.7s ease-in-out;
}
.theme-toggle:hover .sun {
  animation: sun-turn 0.7s var(--ease-out);
}
@keyframes moon-wobble {
  25% { transform: rotate(-18deg); }
  55% { transform: rotate(12deg); }
  80% { transform: rotate(-5deg); }
}
@keyframes sun-turn {
  from { transform: rotate(0); }
  60% { transform: rotate(50deg) scale(1.12); }
  to { transform: rotate(45deg); }
}

/* ---------- 404 ---------- */
.notfound {
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Fill the screen between header and footer. */
  min-height: calc(100svh - 5rem - 1px - 8rem);
  padding-block: clamp(3rem, 8vw, 5rem);
  /* Everything centred, like the contact section. */
  align-items: center;
  text-align: center;
}
/* The error code in Nepali numerals, in the colour the site uses for Bikram Sambat. */
.notfound-code {
  font: 500 clamp(5.5rem, 20vw, 10rem)/1 var(--deva);
  letter-spacing: -0.01em;
  color: var(--signal);
}
.notfound .article-meta {
  margin-top: 1.25rem;
}
.notfound h1 {
  margin-top: 0.5rem;
  font-size: clamp(2rem, 5vw, 2.9rem);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.022em;
}
.notfound .standfirst {
  max-width: 34rem;
  margin-top: 1rem;
  text-wrap: balance;
  color: var(--muted);
}
/* The suggestions in one centred row that wraps on phones. */
.notfound-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1.75rem;
  margin-top: 1.5rem;
  padding: 0;
  list-style: none;
}
/* The 404's "Back to home": a long half-headed arrow, and a tinted circle behind
   the end of the label that stretches into a pill on hover while the label slides
   left (after the old site's 404 button). */
.pill-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 0.85rem 1.6rem 0.85rem 0.9rem;
  color: var(--ink);
  font-weight: 500;
  text-decoration: none;
  isolation: isolate;
}
.pill-link-blob {
  position: absolute;
  z-index: -1;
  top: 50%;
  right: 0;
  width: 3rem;
  height: 3rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  transform: translateY(-50%);
  transition: width 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}
.pill-link-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}
.pill-link-arrow {
  width: 3rem;
  height: 1.1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pill-link:hover .pill-link-blob,
.pill-link:focus-visible .pill-link-blob {
  width: 100%;
}
.pill-link:hover .pill-link-label,
.pill-link:focus-visible .pill-link-label {
  transform: translateX(-0.4rem);
}
.notfound ~ .site-footer .wrap {
  justify-content: center;
}
.notfound .pill-link {
  margin-top: 2.25rem;
}

/* ---------- system preferences ---------- */
/* :not([data-hc]) matches the specificity of the dark-theme rules so these win. */
@media (prefers-contrast: more) {
  :root:not([data-hc]),
  :root[data-theme] {
    --muted: var(--ink);
    --faint: var(--ink);
    --underline: currentColor;
    --line: #9a978e;
  }
}
@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --line: #6a6862;
  }
}
@media (prefers-contrast: more) {
  :root[data-theme='dark'] {
    --line: #6a6862;
  }
}
@media (prefers-reduced-transparency: reduce) {
  .site-header {
    background: var(--bg);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* ---------- motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
  .logo path {
    stroke-dasharray: none !important;
    fill-opacity: 1 !important;
  }
  .js .reveal {
    opacity: 1;
    transform: none;
  }
}
