/* Schibsted Grotesk, self-hosted (SIL Open Font License 1.1). */
@font-face {
  font-display: swap;
  font-family: "Schibsted Grotesk";
  font-style: normal;
  font-weight: 400 700;
  src: url("fonts/schibsted-grotesk-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-display: swap;
  font-family: "Schibsted Grotesk";
  font-style: normal;
  font-weight: 400 700;
  src: url("fonts/schibsted-grotesk-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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Fonts for the alternative styles (SIL Open Font License 1.1). Browsers
   only download them when a page uses them. */
@font-face {
  font-display: swap;
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  src: url("fonts/space-grotesk-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-display: swap;
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  src: url("fonts/space-grotesk-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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-display: swap;
  font-family: Newsreader;
  font-style: normal;
  font-weight: 200 800;
  src: url("fonts/newsreader-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-display: swap;
  font-family: Newsreader;
  font-style: normal;
  font-weight: 200 800;
  src: url("fonts/newsreader-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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-display: swap;
  font-family: Newsreader;
  font-style: italic;
  font-weight: 200 800;
  src: url("fonts/newsreader-italic-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-display: swap;
  font-family: Newsreader;
  font-style: italic;
  font-weight: 200 800;
  src: url("fonts/newsreader-italic-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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-display: swap;
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 200 800;
  src: url("fonts/bricolage-grotesque-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-display: swap;
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 200 800;
  src: url("fonts/bricolage-grotesque-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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-display: swap;
  font-family: "Press Start 2P";
  font-style: normal;
  font-weight: 400;
  src: url("fonts/press-start-2p-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-display: swap;
  font-family: "Press Start 2P";
  font-style: normal;
  font-weight: 400;
  src: url("fonts/press-start-2p-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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Style picker swatches show "Aa" in each style's font. These tiny subsets
   (just those two letters) keep the picker from loading every style's full
   font on first visit. */
@font-face {
  font-display: swap;
  font-family: "Swatch Jungle";
  font-style: normal;
  font-weight: 650;
  src: url("fonts/swatch-jungle.woff2") format("woff2");
}

@font-face {
  font-display: swap;
  font-family: "Swatch Brutal";
  font-style: normal;
  font-weight: 800;
  src: url("fonts/swatch-brutal.woff2") format("woff2");
}

@font-face {
  font-display: swap;
  font-family: "Swatch Minimal";
  font-style: italic;
  font-weight: 500;
  src: url("fonts/swatch-minimal.woff2") format("woff2");
}

@font-face {
  font-display: swap;
  font-family: "Swatch Mario";
  font-style: normal;
  font-weight: 400;
  src: url("fonts/swatch-mario.woff2") format("woff2");
}

:root {
  --bg: #111210;
  --ink: #e9e8e3;
  --muted: #9d9c95;
  --faint: #85847d;
  --rule: #2a2b28;
  --accent: #a3e635;
  --accent-dim: #6b9a24;
  --font-body: "Schibsted Grotesk", system-ui, sans-serif;
  --font-display: var(--font-body);

  color-scheme: dark;
}

/* Light theme: chosen with the toggle, or the system default without JS. */
:root[data-theme="light"] {
  --bg: #f7f6f2;
  --ink: #1a1a18;
  --muted: #55544f;
  --faint: #6f6e68;
  --rule: #e2e0da;
  --accent: #4d7c0f;
  --accent-dim: #6f8f45;

  color-scheme: light;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --bg: #f7f6f2;
    --ink: #1a1a18;
    --muted: #55544f;
    --faint: #6f6e68;
    --rule: #e2e0da;
    --accent: #4d7c0f;
    --accent-dim: #6f8f45;

    color-scheme: light;
  }
}

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

@media screen and (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  overflow-x: clip;
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
  text-decoration: none;
  transition: color 0.15s;
}

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

.page {
  max-width: 760px;
  margin: 0 auto;
  padding: 0 40px 96px;
}

/* Header */

/* Headings use the style's display font (the body font unless a style sets
   one). */
.name,
.section-title,
.page-title,
.post-title {
  font-family: var(--font-display);
}

.header {
  position: relative;
  display: flex;
  min-height: 92vh;
  min-height: 92svh;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 0 20px;
  isolation: isolate;
  text-align: center;
}

/* The name block is centred in the space above the style picker, which sits
   at the bottom of the header in the normal flow (so it can never overlap
   the name, however many rows of cards it needs). */
.header:has(.hero-foot)::before {
  flex: 1;
  content: "";
}

/* Decorative, style-specific scenery behind the header (full width). */
.hero-bg {
  display: none;
}

@media screen {
  .hero-bg {
    position: absolute;
    z-index: -1;
    top: 0;
    bottom: 0;
    left: 50%;
    overflow: hidden;
    width: 100vw;
    pointer-events: none;
    translate: -50% 0;
  }

  .hero-bg > span {
    position: absolute;
    background-repeat: no-repeat;
    background-size: contain;
  }

  /* The Mario style's running hero (mario.js); hidden in the other styles. */
  .hero-bg > .hero-sprite {
    display: none;
  }
}

.name {
  margin: 0;

  /* The name plus ".io" is ~9.8em wide: 8.6vw keeps it on one line on every
     screen, so the intro animation can open it up without wrapping mid-way. */
  font-size: clamp(1.6rem, 8.6vw, 4.3rem);
  font-weight: 650;
  white-space: nowrap;
  letter-spacing: -0.045em;
  line-height: 1;
}

.mark {
  color: var(--accent);
}

/* ".io" after the name, completing the tojar.io domain. The animated name
   (.name-art) is hidden from assistive tech, which reads the plain
   visually-hidden "Tomasz Jarnutowski" instead. */
.name .word + .word::after {
  display: inline-block;
  margin-left: 0.03em;
  color: var(--accent-dim);
  content: ".io" / "";
  font-size: 0.5em;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.word {
  white-space: nowrap;
}

.intro {
  margin: 1.5rem 0 0;
  text-wrap: balance;
  color: var(--muted);
  font-size: clamp(1.35rem, 4vw, 1.9rem);
  line-height: 1.3;
}

/* Shown only when printing (a one-line summary instead of the roles). */
.print-only {
  display: none;
}

/* Recruitment data-processing consent: only in the printed CV / PDF. */
.consent {
  display: none;
}

/* Rotating role: a full-width strip where roles are stacked in one grid cell,
   each centred on its own, and clipped vertically so one can roll out
   upwards while the next rolls in from below (see hero.js). No width
   animation is needed, whatever the lengths of the roles. */
.roles {
  display: grid;
  justify-items: center;
  overflow: visible clip;
}

.role {
  grid-area: 1 / 1;
  white-space: nowrap;
}

.contact {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 2.25rem;
  gap: 0.6rem;
}

.contact a,
.contact .location,
.cv-download {
  display: inline-flex;
  align-items: center;
  padding: 0.5rem 0.95rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--bg);
  color: var(--ink);
  cursor: pointer;
  font: inherit;
  font-size: 0.95rem;
  gap: 0.5rem;
  transition:
    color 0.15s,
    border-color 0.15s,
    transform 0.15s;
}

.contact a:hover,
.cv-download:hover {
  border-color: var(--accent);
  color: var(--accent);
  transform: translateY(-1px);
}

/* The style pickers only work with JavaScript: theme-init.js marks the page
   with .js before the first paint, so they take their space from the start. */
:root:not(.js) .style-picker {
  display: none;
}

/* Plain text shaped like the contact buttons: not clickable. */
.contact .location {
  cursor: default;
}

/* A link back to the website, only in the printed CV / PDF. */
.contact .site-link {
  display: none;
}

.cv-download:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Bottom of the header: the style picker above the scroll arrow. */
.hero-foot {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding-top: 3rem;
  gap: 14px;
}

.scroll-cue {
  display: grid;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  color: var(--faint);
  place-items: center;
}

.scroll-cue:hover {
  color: var(--accent);
}

.scroll-cue .icon {
  width: 22px;
  height: 22px;
}

/* Intro animation: "To" + "Jar" appear first, then the rest of the name
   opens up between them, then the tagline and buttons rise in. */
@media screen and (prefers-reduced-motion: no-preference) {
  .mark,
  .rest {
    display: inline-block;
    vertical-align: top;
  }

  .mark {
    animation: fade-in 0.5s ease-out both;
  }

  /* ".io" appears with "To" and "Jar" ("ToJar.io") and moves along as the
     rest of the name opens up. */
  .name .word + .word::after {
    animation: fade-in 0.5s ease-out both;
  }

  .rest {
    overflow: hidden;
    animation: open-up 0.9s cubic-bezier(0.65, 0, 0.35, 1) 0.55s both;
  }

  .rest-first {
    --open: 2.6em;
  }

  .rest-last {
    --open: 4.4em;
  }

  .intro,
  .contact {
    animation: rise-in 0.6s cubic-bezier(0.2, 0, 0, 1) both;
  }

  .intro {
    animation-delay: 1.2s;
  }

  .contact {
    animation-delay: 1.35s;
  }

  .hero-foot .style-picker {
    animation: rise-in 0.6s cubic-bezier(0.2, 0, 0, 1) 1.5s both;
  }

  .scroll-cue {
    animation:
      fade-in 0.6s ease-out 1.8s both,
      nudge 2.4s ease-in-out 2.4s infinite;
  }

  /* hero.js adds .intro-done once the intro has played (or when the style
     or theme is switched), so no browser can replay it on a style change. */
  .header.intro-done .name,
  .header.intro-done .mark,
  .header.intro-done .rest,
  .header.intro-done .intro,
  .header.intro-done .contact,
  .header.intro-done .hero-foot .style-picker,
  .header.intro-done .name .word + .word::after {
    animation: none;
  }

  .header.intro-done .scroll-cue {
    animation: nudge 2.4s ease-in-out infinite;
  }

  .name {
    animation: close-gap 0.9s cubic-bezier(0.65, 0, 0.35, 1) 0.55s both;
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
}

@keyframes open-up {
  from {
    max-width: 0;
    opacity: 0;
  }

  to {
    max-width: var(--open);
    opacity: 1;
  }
}

/* Hides the space between the two halves while they are collapsed. */
@keyframes close-gap {
  from {
    word-spacing: var(--gap-close, -0.27em);
  }
}

@keyframes nudge {
  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(6px);
  }
}

.icon {
  width: 1.05em;
  height: 1.05em;
  flex-shrink: 0;
  fill: currentcolor;
}

.icon-stroke {
  fill: none;
  stroke: currentcolor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.8;
}

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

/* Theme toggle */

.theme-toggle {
  position: fixed;
  z-index: 3;
  top: 9px;
  right: 16px;
  display: grid;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
  place-items: center;
  transition:
    color 0.15s,
    border-color 0.15s;
}

.theme-toggle[hidden] {
  display: none;
}

.theme-toggle:hover {
  border-color: var(--faint);
  color: var(--ink);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.theme-toggle svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentcolor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.8;
}

/* Show the icon of the theme the button switches to. */
[data-theme="light"] .icon-sun,
[data-theme="dark"] .icon-moon {
  display: none;
}

/* Section navigation: sits under the header and sticks to the top on scroll. */

.topnav {
  position: sticky;
  z-index: 2;
  top: 0;
  width: 100vw;
  margin-bottom: 72px;
  margin-left: calc(50% - 50vw);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: var(--bg);
}

/* Frosted glass where supported; the solid background above is the fallback. */
@supports (backdrop-filter: blur(1px)) {
  .topnav,
  .theme-toggle {
    background: color-mix(in srgb, var(--bg) 80%, transparent);
    backdrop-filter: blur(16px) saturate(160%);
  }
}

.topnav-inner {
  display: flex;
  max-width: 760px;
  align-items: center;
  justify-content: space-between;
  margin: 0 auto;
  padding: 0 40px;
  gap: 1.5rem;
}

.topnav-links {
  display: flex;
  flex: 1;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Items grow by the same amount, so the gaps between labels are equal. */
.topnav-links li {
  flex: 1 1 auto;
}

.topnav-links a {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.9rem 0;
  gap: 0.45rem;
  text-align: center;
  color: var(--muted);
  font-size: 0.95rem;
}

.topnav-links .icon {
  width: 1em;
  height: 1em;
}

.topnav-links a:hover,
.topnav-links a[aria-current] {
  color: var(--ink);
}

.topnav-links a[aria-current] .icon {
  color: var(--accent);
}

.topnav-links a[aria-current] {
  box-shadow: inset 0 -2px var(--accent);
}

/* Room for the fixed theme toggle. nav.js adds .is-docked only when the bar
   gets close to the toggle, so until then the links use the full width; the
   transition is enabled after the first check (.dock-ready) so a page load
   never animates. Without JS the toggle is hidden, so no room is needed. */
@media (width <= 840px) {
  .topnav.is-docked .topnav-inner {
    padding-right: 64px;
  }
}

@media (width <= 840px) and (prefers-reduced-motion: no-preference) {
  .topnav.dock-ready .topnav-inner {
    transition: padding-right 0.35s cubic-bezier(0.2, 0, 0, 1);
  }
}

@media (width <= 640px) {
  .topnav-inner {
    padding-right: 20px;
    padding-left: 20px;
  }

  .topnav-links a {
    flex-direction: column;
    padding: 0.55rem 0;
    font-size: 0.8rem;
    gap: 0.2rem;
  }

  .topnav-links .icon {
    width: 1.15em;
    height: 1.15em;
  }
}

/* Sections */

.section + .section {
  margin-top: 120px;
}

.section {
  scroll-margin-top: 80px;
}

.section-title {
  display: flex;
  align-items: center;
  margin: 0 0 1rem;
  color: var(--ink);
  font-size: 1.6rem;
  font-weight: 600;
  gap: 0.6rem;
  letter-spacing: -0.025em;
  line-height: 1.2;
}

.section-title .icon {
  width: 0.9em;
  height: 0.9em;
  color: var(--accent);
}

.list {
  margin: 0;
  padding: 0;
  border-bottom: 1px solid var(--rule);
  list-style: none;
}

.item {
  display: grid;
  padding: 1.25rem 0;
  border-top: 1px solid var(--rule);
  gap: 1.5rem;
  grid-template-columns: 120px 1fr;
}

#projects .item {
  padding: 2rem 0;
}

.item p,
.item h2,
.item h3 {
  margin: 0;
}

.meta {
  display: flex;
  align-items: flex-start;
  color: var(--faint);
  gap: 0.5rem;
  font-size: 0.95rem;
  line-height: 1.75;
}

.meta .icon {
  width: 1.05em;
  height: 1.05em;
  margin-top: 0.35em;
}

.item-title {
  font-size: 1.15rem;
  font-weight: 500;
}

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

.item-text {
  max-width: 60ch;
  color: var(--muted);
}

.item .item-sub + .item-text,
.item .item-title + .item-text {
  margin-top: 0.5rem;
}

.parts {
  display: grid;
  margin: 1rem 0 0;
  padding: 0;
  gap: 1rem;
  list-style: none;
}

.parts .part-name {
  margin: 0;
  font-size: 1rem;
  font-weight: 500;
}

.part-text {
  max-width: 60ch;
  margin-top: 0.15rem;
  color: var(--muted);
}

.item .parts .stack {
  margin-top: 0.4rem;
}

.tag {
  display: inline-block;
  padding: 0.05rem 0.5rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  margin-left: 0.6rem;
  color: var(--faint);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0;
  vertical-align: 0.2em;
}

.see-projects,
.filters button {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.8rem;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 0.9rem;
  transition:
    color 0.15s,
    border-color 0.15s;
}

.see-projects {
  margin-top: 0.9rem;
  gap: 0.3rem;
}

.see-projects:hover,
.filters button:hover {
  border-color: var(--faint);
  color: var(--ink);
}

.filters button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
  gap: 0.75rem 1.5rem;
}

.section-head .section-title {
  margin: 0;
}

.filters[hidden] {
  display: none;
}

.filters button[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--ink);
}

@media screen {
  .is-filtered-out {
    display: none;
  }
}

/* Links to a project's website or source: small outlined buttons, so they
   read as actions rather than part of the technology list. */
.source-link {
  display: inline-flex;
  align-items: center;
  padding: 0.35rem 0.85rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  margin-top: 0.9rem;
  color: var(--ink);
  font-size: 0.875rem;
  gap: 0.45rem;
  transition:
    color 0.15s,
    border-color 0.15s;
}

.source-link + .source-link {
  margin-left: 0.5rem;
}

.source-link svg {
  width: 1em;
  height: 1em;
  color: var(--accent);
  opacity: 1;
}

.source-link:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.item-title a:hover {
  color: var(--accent);
}

.item-title .arrow {
  color: var(--faint);
  font-size: 0.85em;
}

.stack,
.tech-list {
  display: flex;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  gap: 0.35rem 1rem;
  list-style: none;
}

.stack li,
.tech-list li {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.tech-icon {
  width: 0.95em;
  height: 0.95em;
  flex-shrink: 0;
  fill: currentcolor;
  opacity: 0.85;
}

.item .stack {
  margin-top: 0.6rem;
  color: var(--faint);
  font-size: 0.9rem;
}

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

.tech-list .tech-icon {
  color: var(--faint);
}

@media (width <= 640px) {
  .page {
    padding: 0 20px 64px;
  }

  .section + .section {
    margin-top: 88px;
  }

  .item {
    gap: 0.25rem;
    grid-template-columns: 1fr;
  }
}

.text-link {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 0.2em;
}

.text-link:hover {
  color: var(--accent);
  text-decoration-color: currentcolor;
}

/* Footer */

.footer {
  border-top: 1px solid var(--rule);
}

.footer-inner {
  display: flex;
  max-width: 760px;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  margin: 0 auto;
  padding: 28px 40px 36px;
  gap: 1rem 2rem;
}

.footer-copy {
  margin: 0;
  color: var(--faint);
  font-size: 0.9rem;
}

.footer-sep {
  margin: 0 0.3rem;
  color: var(--rule);
}

.footer-brand {
  color: var(--accent);
  font-weight: 500;
}

.footer-links {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.footer-links a {
  display: grid;
  min-width: 36px;
  height: 36px;
  border-radius: 6px;
  color: var(--muted);
  place-items: center;
}

.footer-links a:hover {
  color: var(--accent);
}

.footer-links .icon {
  width: 18px;
  height: 18px;
}

.footer-links .back-to-top {
  display: inline-flex;
  padding: 0 0.5rem;
  margin-left: 0.5rem;
  font-size: 0.9rem;
  gap: 0.3rem;
  white-space: nowrap;
}

@media (width <= 640px) {
  .footer-inner {
    flex-direction: column;
    padding: 24px 20px 32px;
  }
}

/* Blog */

.page-blog {
  padding-top: 96px;
}

.breadcrumbs {
  margin-bottom: 2.5rem;
  color: var(--faint);
  font-size: 0.9rem;
}

.breadcrumbs a {
  color: var(--muted);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 0.25em;
}

.breadcrumbs a:hover {
  color: var(--accent);
}

.page-title,
.post-title {
  margin: 0;
  font-size: clamp(2.2rem, 6vw, 3.25rem);
  font-weight: 650;
  letter-spacing: -0.035em;
  line-height: 1.1;
}

.page-lead {
  margin: 0.9rem 0 3rem;
  color: var(--muted);
  font-size: 1.1rem;
}

.post-list .item-title a:hover {
  color: var(--accent);
}

.post-tags {
  display: flex;
  flex-wrap: wrap;
  margin: 0.75rem 0 0;
  padding: 0;
  gap: 0.4rem;
  list-style: none;
}

.post-tags .tag {
  margin-left: 0;
}

.empty {
  padding: 2rem 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  margin: 0;
  color: var(--muted);
}

.post-header {
  padding-bottom: 2rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 2.5rem;
}

.post-meta {
  margin: 1rem 0 0;
  color: var(--faint);
  font-size: 0.95rem;
}

.section-more {
  margin: 1.25rem 0 0;
}

.post-back {
  margin: 3.5rem 0 0;
}

/* Article body typography. */
.prose {
  font-size: 1.075rem;
  line-height: 1.75;
}

.prose > :first-child {
  margin-top: 0;
}

.prose p,
.prose ul,
.prose ol,
.prose blockquote,
.prose pre,
.prose table,
.prose figure {
  margin: 0 0 1.4rem;
}

.prose h2,
.prose h3,
.prose h4 {
  margin: 2.5rem 0 0.9rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.25;
  scroll-margin-top: 24px;
}

.prose h2 {
  font-size: 1.6rem;
}

.prose h3 {
  font-size: 1.3rem;
}

.prose h4 {
  font-size: 1.1rem;
}

.prose a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-underline-offset: 0.2em;
}

.prose a:hover {
  color: var(--accent);
}

.prose ul,
.prose ol {
  padding-left: 1.4rem;
}

.prose li + li {
  margin-top: 0.35rem;
}

.prose li::marker {
  color: var(--faint);
}

.prose blockquote {
  padding: 0.1rem 0 0.1rem 1.2rem;
  border-left: 3px solid var(--accent);
  color: var(--muted);
}

.prose blockquote > :last-child {
  margin-bottom: 0;
}

.prose code {
  padding: 0.12em 0.4em;
  border: 1px solid var(--rule);
  border-radius: 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88em;
}

.prose pre {
  overflow-x: auto;
  padding: 1rem 1.2rem;
  border: 1px solid var(--rule);
  border-radius: 8px;
  line-height: 1.55;
}

.prose pre code {
  padding: 0;
  border: 0;
  font-size: 0.9rem;
}

.prose img {
  max-width: 100%;
  height: auto;
  border-radius: 8px;
}

.prose hr {
  margin: 2.5rem 0;
  border: 0;
  border-top: 1px solid var(--rule);
}

.prose table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}

.prose th,
.prose td {
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--rule);
  text-align: left;
}

.prose th {
  font-weight: 600;
}

.footer-links .footer-text-link {
  display: inline-flex;
  padding: 0 0.5rem;
  margin-left: 0.5rem;
  font-size: 0.9rem;
}

@media (width <= 640px) {
  .page-blog {
    padding-top: 72px;
  }
}

/* Style picker (cards built by style.js): the header shows a panel of the
   three styles, the footer a compact row of the same cards. */
.style-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
}

.style-picker[hidden] {
  display: none;
}

.hero-foot .style-picker {
  max-width: 100%;
  justify-content: center;
  padding: 0.45rem 0.5rem 0.5rem;
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: var(--bg);
  box-shadow: 0 10px 30px rgb(0 0 0 / 8%);
}

.style-picker-label {
  display: inline-flex;
  align-items: center;
  color: var(--faint);
  font-size: 0.8rem;
  gap: 0.35rem;
}

.style-picker-label .icon {
  width: 14px;
  height: 14px;
}

.hero-foot .style-picker-label {
  justify-content: center;
  padding-top: 0.15rem;
  grid-column: 1 / -1;
}

.hero-foot .style-picker:not([hidden]) {
  display: grid;
  grid-template-columns: repeat(4, auto);
}

@media (width <= 480px) {
  .hero-foot .style-picker:not([hidden]) {
    grid-template-columns: repeat(2, auto);
  }
}

.style-option {
  display: flex;
  min-width: 88px;
  flex-direction: column;
  align-items: center;
  padding: 0.55rem 0.6rem 0.6rem;
  border: 1px solid transparent;
  border-radius: 10px;
  background: none;
  color: var(--ink);
  cursor: pointer;
  font: inherit;
  font-size: 0.85rem;
  gap: 0.3rem;
  transition:
    translate 0.2s cubic-bezier(0.2, 0, 0, 1),
    border-color 0.2s,
    background-color 0.2s;
}

.style-option:hover {
  border-color: var(--rule);
  translate: 0 -2px;
}

.style-option[aria-pressed="true"] {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  cursor: default;
  translate: none;
}

.style-option:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.style-option-name {
  font-weight: 600;
}

.style-option-hint {
  color: var(--muted);
  font-size: 0.75rem;
  white-space: nowrap;
}

/* Footer: its own row under the links, small swatches and no hints. */
.footer-style-picker {
  flex-basis: 100%;
  justify-content: flex-start;
}

.footer-style-picker .style-picker-label {
  margin-right: 0.4rem;
  font-size: 0.9rem;
}

.footer-style-picker .style-option {
  min-width: 0;
  flex-direction: row;
  padding: 0.3rem 0.6rem 0.3rem 0.35rem;
  font-size: 0.85rem;
  gap: 0.45rem;
}

.footer-style-picker .style-swatch {
  width: 30px;
  height: 22px;
  margin: 0;
  font-size: 0.8rem;
}

.footer-style-picker .style-option-hint {
  display: none;
}

@media (width <= 640px) {
  .footer-style-picker {
    justify-content: center;
  }

  .footer-style-picker .style-picker-label {
    flex-basis: 100%;
    justify-content: center;
    margin: 0 0 0.2rem;
  }

  .footer-style-picker .style-option {
    flex-direction: column;
    padding: 0.4rem 0.55rem;
    gap: 0.3rem;
  }
}

/* Swatches preview each style regardless of the current one. */
.style-swatch {
  display: grid;
  width: 60px;
  height: 42px;
  margin-bottom: 0.2rem;
  font-size: 1.3rem;
  line-height: 1;
  place-items: center;
}

/* Leaf-shaped, in jungle greens. */
.style-swatch[data-preview="jungle"] {
  border-radius: 22px 4px;
  background: #1b4428;
  color: #b6f08a;
  font-family: "Swatch Jungle", sans-serif;
  font-weight: 650;
}

.style-swatch[data-preview="brutal"] {
  border: 2px solid #111;
  background: #ffd23f;
  box-shadow: 3px 3px 0 #111;
  color: #111;
  font-family: "Swatch Brutal", sans-serif;
  font-weight: 800;
}

.style-swatch[data-preview="minimal"] {
  border: 1px solid #ddd;
  background: #fff;
  color: #a33a2e;
  font-family: "Swatch Minimal", georgia, serif;
  font-style: italic;
  font-weight: 500;
}

.style-swatch[data-preview="mario"] {
  border: 2px solid #000;
  background: #6b8cff url("bg/mario-ground.svg") 0 100% / 12px 12px repeat-x;
  color: #fff;
  font-family: "Swatch Mario", monospace;
  font-size: 0.85rem;
  text-shadow: 2px 2px 0 #000;
}

.footer-style-picker .style-swatch[data-preview="mario"] {
  font-size: 0.5rem;
}

@media (width <= 380px) {
  .style-option {
    min-width: 0;
    padding-inline: 0.5rem;
  }

  .style-option-hint {
    display: none;
  }

  .hero-foot .style-option {
    font-size: 0.8rem;
  }

  .hero-foot .style-swatch {
    width: 48px;
    height: 36px;
    font-size: 1.1rem;
  }
}

@media screen and (prefers-reduced-motion: no-preference) {
  /* Switching style or theme: the new look is revealed as a growing circle
     from the button that was pressed (see withReveal in style.js). */
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
    mix-blend-mode: normal;
  }

  ::view-transition-new(root) {
    animation: reveal 0.7s cubic-bezier(0.4, 0, 0.2, 1);
  }
}

@keyframes reveal {
  from {
    clip-path: circle(0 at var(--reveal-x, 50%) var(--reveal-y, 50%));
  }

  to {
    clip-path: circle(150vmax at var(--reveal-x, 50%) var(--reveal-y, 50%));
  }
}

/* ------------------------------------------------------------------------
   Alternative styles, chosen at random on each reload by theme-init.js or
   with the Style button (data-style on <html>). Each style has a light and
   a dark palette on screen and a paper palette in print (see @media print),
   so "Save CV as PDF" matches the style being viewed.
   ------------------------------------------------------------------------ */

/* Style fonts apply on screen and in print. */
:root[data-style="brutal"] {
  --font-body: "Space Grotesk", system-ui, sans-serif;
  --font-display: "Bricolage Grotesque", "Space Grotesk", system-ui, sans-serif;
  --pop-yellow: #ffd23f;
  --pop-pink: #ff7eb6;
  --pop-cyan: #5ce1f0;
  --pop-green: #7ee08a;
  --pop-orange: #ff9a4d;
  --pop-purple: #b99cff;
}

:root[data-style="minimal"] {
  --font-body: "Newsreader", georgia, serif;
}

@media screen {
  /* Neobrutalist: bold grotesque, thick black borders, hard offset shadows. */
  :root[data-style="brutal"] {
    --bg: #151515;
    --ink: #f5f5f0;
    --muted: #d6d6d0;
    --faint: #b0b0a8;
    --rule: #f5f5f0;
    --accent: #ffd23f;
    --accent-dim: #c9a52c;
    --mark-bg: #ffd23f;
    --mark-ink: #111;
    --surface: #1f1f1f;
  }

  :root[data-style="brutal"][data-theme="light"] {
    --bg: #fff8e7;
    --ink: #111;
    --muted: #2e2e2e;
    --faint: #4d4d4d;
    --rule: #111;
    --accent: #1d3fd6;
    --accent-dim: #4a63d8;
    --mark-bg: #ffd23f;
    --mark-ink: #111;
    --surface: #fff;
  }

  /* Minimal: serif, black and white, no boxes, underlined links. */
  :root[data-style="minimal"] {
    --bg: #0f0f0f;
    --ink: #ededea;
    --muted: #b8b8b3;
    --faint: #8f8f8a;
    --rule: #2a2a28;
    --accent: #f08c7f;
    --accent-dim: #b9766c;
    --font-body: "Newsreader", georgia, serif;
  }

  :root[data-style="minimal"][data-theme="light"] {
    --bg: #fff;
    --ink: #141414;
    --muted: #4a4a4a;
    --faint: #6b6b6b;
    --rule: #e4e4e2;
    --accent: #a33a2e;
    --accent-dim: #b9675d;
  }
}

@media screen and (prefers-color-scheme: light) {
  :root[data-style="brutal"]:not([data-theme]) {
    --bg: #fff8e7;
    --ink: #111;
    --muted: #2e2e2e;
    --faint: #4d4d4d;
    --rule: #111;
    --accent: #1d3fd6;
    --accent-dim: #4a63d8;
    --surface: #fff;
  }

  :root[data-style="minimal"]:not([data-theme]) {
    --bg: #fff;
    --ink: #141414;
    --muted: #4a4a4a;
    --faint: #6b6b6b;
    --rule: #e4e4e2;
    --accent: #a33a2e;
    --accent-dim: #b9675d;
  }
}

/* ---- Neobrutalist components ---- */
[data-style="brutal"] .mark {
  background: var(--mark-bg);
  color: var(--mark-ink);
}

[data-style="brutal"] .item,
[data-style="brutal"] .topnav,
[data-style="brutal"] .footer {
  border-width: 2px;
}

[data-style="brutal"] .list {
  border-bottom-width: 2px;
}

[data-style="brutal"] .topnav,
[data-style="brutal"] .theme-toggle {
  background: var(--bg);
  backdrop-filter: none;
}

[data-style="brutal"] .contact a,
[data-style="brutal"] .contact .location,
[data-style="brutal"] .cv-download,
[data-style="brutal"] .see-projects,
[data-style="brutal"] .filters button,
[data-style="brutal"] .source-link,
[data-style="brutal"] .theme-toggle {
  border: 2px solid var(--ink);
  border-radius: 0;
  background: var(--surface);
  box-shadow: 3px 3px 0 var(--ink);
  color: var(--ink);
  font-weight: 500;
  transition:
    transform 0.1s,
    box-shadow 0.1s,
    background-color 0.1s;
}

[data-style="brutal"] .contact a:hover,
[data-style="brutal"] .cv-download:hover,
[data-style="brutal"] .see-projects:hover,
[data-style="brutal"] .filters button:hover,
[data-style="brutal"] .source-link:hover,
[data-style="brutal"] .theme-toggle:hover {
  border-color: var(--ink);
  box-shadow: 5px 5px 0 var(--ink);
  color: var(--ink);
  transform: translate(-2px, -2px);
}

[data-style="brutal"] .contact a:active,
[data-style="brutal"] .cv-download:active,
[data-style="brutal"] .see-projects:active,
[data-style="brutal"] .filters button:active,
[data-style="brutal"] .source-link:active,
[data-style="brutal"] .theme-toggle:active {
  box-shadow: 0 0 0 var(--ink);
  transform: translate(3px, 3px);
}

[data-style="brutal"] .filters button[aria-pressed="true"] {
  border-color: var(--ink);
  background: var(--mark-bg);
  color: var(--mark-ink);
}

[data-style="brutal"] .source-link svg {
  color: currentcolor;
}

[data-style="brutal"] .tag {
  padding: 0.05rem 0.45rem;
  border: 2px solid var(--ink);
  border-radius: 0;
  background: var(--mark-bg);
  color: var(--mark-ink);
  font-weight: 600;
}

[data-style="brutal"] .prose code,
[data-style="brutal"] .prose pre {
  border: 2px solid var(--ink);
  border-radius: 0;
}

[data-style="brutal"] .prose pre {
  box-shadow: 4px 4px 0 var(--ink);
}

/* ---- Neobrutalist colour and type ---- */
[data-style="brutal"] .name {
  font-weight: 800;
  letter-spacing: -0.035em;
}

[data-style="brutal"] .name-art > .word:first-child .mark {
  background: var(--pop-yellow);
}

[data-style="brutal"] .name-art > .word + .word .mark {
  background: var(--pop-pink);
}

[data-style="brutal"] .section-title {
  font-weight: 800;
  letter-spacing: -0.02em;
}

[data-style="brutal"] .section-title .icon {
  width: 1.15em;
  height: 1.15em;
  box-sizing: content-box;
  padding: 0.14em;
  border: 2px solid var(--ink);
  background: var(--pop-yellow);
  box-shadow: 3px 3px 0 var(--ink);
  color: #111;
}

[data-style="brutal"] #experience .section-title .icon {
  background: var(--pop-pink);
}

[data-style="brutal"] #projects .section-title .icon {
  background: var(--pop-cyan);
}

[data-style="brutal"] #skills .section-title .icon {
  background: var(--pop-green);
}

[data-style="brutal"] #blog .section-title .icon {
  background: var(--pop-orange);
}

[data-style="brutal"] .contact a,
[data-style="brutal"] .cv-download,
[data-style="brutal"] .see-projects,
[data-style="brutal"] .source-link {
  color: #111;
}

[data-style="brutal"] .contact a:nth-of-type(1) {
  background: var(--pop-yellow);
}

[data-style="brutal"] .contact a:nth-of-type(2) {
  background: var(--pop-pink);
}

[data-style="brutal"] .contact a:nth-of-type(3) {
  background: var(--pop-cyan);
}

[data-style="brutal"] .cv-download,
[data-style="brutal"] .contact .cv-download {
  background: var(--pop-green);
}

[data-style="brutal"] .contact .location {
  background: var(--pop-orange);
  color: #111;
}

[data-style="brutal"] .see-projects[data-filter="samsung"],
[data-style="brutal"]
  .filters
  button[data-filter="samsung"][aria-pressed="true"],
[data-style="brutal"] .item[data-company="samsung"] .tag {
  background: var(--pop-cyan);
}

[data-style="brutal"] .see-projects[data-filter="voxsoft"],
[data-style="brutal"]
  .filters
  button[data-filter="voxsoft"][aria-pressed="true"],
[data-style="brutal"] .item[data-company="voxsoft"] .tag {
  background: var(--pop-pink);
}

[data-style="brutal"]
  .filters
  button[data-filter="personal"][aria-pressed="true"],
[data-style="brutal"] .item[data-company="personal"] .tag {
  background: var(--pop-green);
}

[data-style="brutal"] .filters button[data-filter="all"][aria-pressed="true"] {
  background: var(--pop-yellow);
}

[data-style="brutal"] .source-link {
  background: var(--pop-orange);
}

[data-style="brutal"] .theme-toggle {
  background: var(--pop-purple);
  color: #111;
}

[data-style="brutal"] .hero-foot .style-picker {
  border: 2px solid var(--ink);
  border-radius: 0;
  background: var(--surface);
  box-shadow: 5px 5px 0 var(--ink);
}

[data-style="brutal"] .style-option {
  border-width: 2px;
  border-radius: 0;
}

[data-style="brutal"] .style-option:hover {
  border-color: var(--ink);
}

[data-style="brutal"] .style-option[aria-pressed="true"] {
  border: 2px solid var(--ink);
  background: var(--pop-yellow);
  color: #111;
}

[data-style="brutal"] .style-option[aria-pressed="true"] .style-option-hint {
  color: #2e2e2e;
}

[data-style="minimal"] .hero-foot .style-picker {
  border-radius: 0;
  box-shadow: none;
}

[data-style="minimal"] .style-option {
  border-radius: 0;
}

/* ---- Minimal components ---- */
[data-style="minimal"] .name {
  font-weight: 500;
  letter-spacing: -0.02em;
}

[data-style="minimal"] .mark {
  font-style: italic;
}

[data-style="minimal"] .section-title {
  font-size: 1.9rem;
  font-weight: 500;
  letter-spacing: -0.015em;
}

[data-style="minimal"] .section-title .icon,
[data-style="minimal"] .meta .icon,
[data-style="minimal"] .topnav-links .icon {
  display: none;
}

[data-style="minimal"] .topnav,
[data-style="minimal"] .theme-toggle {
  background: var(--bg);
  backdrop-filter: none;
}

[data-style="minimal"] .contact a,
[data-style="minimal"] .contact .location,
[data-style="minimal"] .cv-download,
[data-style="minimal"] .see-projects,
[data-style="minimal"] .filters button,
[data-style="minimal"] .source-link {
  padding: 0.2rem 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.3em;
  transform: none;
}

[data-style="minimal"] .contact {
  gap: 0.4rem 1.6rem;
}

[data-style="minimal"] .contact .location {
  color: var(--muted);
  text-decoration: none;
}

[data-style="minimal"] .filters {
  gap: 1.2rem;
}

[data-style="minimal"] .contact a:hover,
[data-style="minimal"] .cv-download:hover,
[data-style="minimal"] .see-projects:hover,
[data-style="minimal"] .filters button:hover,
[data-style="minimal"] .source-link:hover {
  color: var(--accent);
  text-decoration-color: currentcolor;
  transform: none;
}

[data-style="minimal"] .filters button {
  color: var(--muted);
  text-decoration: none;
}

[data-style="minimal"] .filters button[aria-pressed="true"] {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--accent);
}

[data-style="minimal"] .theme-toggle {
  border-color: transparent;
  border-radius: 0;
}

/* Hairline icons, to match the serif. */
[data-style="minimal"] .icon-stroke,
[data-style="minimal"] .theme-toggle svg {
  stroke-width: 1.25;
}

[data-style="minimal"] .tag {
  padding: 0;
  border: 0;
  color: var(--faint);
  font-size: 0.85rem;
  font-style: italic;
}

[data-style="minimal"] .tag::before {
  content: "— " / "";
}

[data-style="minimal"] .prose {
  font-size: 1.2rem;
}

/* ------------------------------------------------------------------------
   Mario style, plus the header scenery (Neobrutalist and Mario). The
   decoration is screen-only: printing keeps the plain CV layout with the
   style's fonts and accent colour.
   ------------------------------------------------------------------------ */

:root[data-style="mario"] {
  --font-display: "Press Start 2P", monospace;
  --gap-close: -1em;
}

@media screen {
  :root[data-style="mario"] {
    --bg: #0b0b18;
    --ink: #f2f2f5;
    --muted: #d0d0e0;
    --faint: #a9a9c2;
    --rule: #2a3c86;
    --accent: #fcbc3c;
    --accent-dim: #d99a2b;
    --surface: #15152b;
    --sky: #0c1440;
    --brick: #1f3f9e url("bg/mario-brick-blue.svg");
    --ground: url("bg/mario-ground-blue.svg");
    --block: #fca044;
  }

  :root[data-style="mario"][data-theme="light"] {
    --bg: #fcf3dc;
    --ink: #1b1b1b;
    --muted: #3d362c;
    --faint: #5a5145;
    --rule: #e2cc9f;
    --accent: #b8231a;
    --accent-dim: #c4572a;
    --surface: #fff;
    --sky: #6b8cff;
    --brick: #9c3a0a url("bg/mario-brick.svg");
    --ground: url("bg/mario-ground.svg");
    --block: #fca044;
  }
}

/* ---- Jungle: the Schibsted layout in jungle greens, with a canopy of
   palms, vines and big leaves, a perched macaw and birds flying by ---- */
@media screen {
  :root[data-style="jungle"] {
    --bg: #0c1a11;
    --ink: #e8f0e4;
    --muted: #b9cbb3;
    --faint: #93a88d;
    --rule: #213828;
    --accent: #8bd65c;
    --accent-dim: #5f9e3c;
    --leaf-far: #132d1b;
    --leaf-near: #1b4428;
    --bird: #9fd48a;
  }

  :root[data-style="jungle"][data-theme="light"] {
    --bg: #f0f5e9;
    --ink: #14231a;
    --muted: #3f5140;
    --faint: #56695a;
    --rule: #d3dfc8;
    --accent: #2f6b1f;
    --accent-dim: #5b8a3e;
    --leaf-far: #dcead0;
    --leaf-near: #b8d6a3;
    --bird: #24452b;
  }

  /* A size container, so the giant trees can be sized from the header
     height (cqh). */
  [data-style="jungle"] .hero-bg {
    display: block;
    container-type: size;
  }

  .hero-bg > .jg {
    display: none;
  }

  /* Silhouettes are masks filled with a leaf colour, so they follow the
     theme; "far" plants are paler than "near" ones for a sense of depth. */
  [data-style="jungle"] .hero-bg > .jg {
    --sway: 1.5deg;

    display: block;
    background: var(--leaf-near);
    mask: var(--art) center bottom / contain no-repeat;
    transform-origin: 50% 100%;
  }

  [data-style="jungle"] .hero-bg > .jg-far {
    background: var(--leaf-far);
  }

  /* Palms hanging in from the top corners, swaying from the corner. */
  [data-style="jungle"] .hero-bg > .jg-palm {
    --art: url("bg/jungle-palm.svg");
    --sway: 2deg;

    top: 0;
    left: 0;
    width: min(520px, 46vw);
    height: min(360px, 32vw);
    mask-position: left top;
    transform-origin: 0 0;
  }

  [data-style="jungle"] .hero-bg > .jg-palm-r {
    right: 0;
    left: auto;
    scale: -1 1;
    transform-origin: 100% 0;
  }

  [data-style="jungle"] .hero-bg > .jg-vine {
    --art: url("bg/jungle-vine.svg");
    --sway: 3deg;

    top: 0;
    left: 14%;
    width: 90px;
    height: 240px;
    mask-position: center top;
    transform-origin: 50% 0;
  }

  [data-style="jungle"] .hero-bg > .jg-vine-2 {
    right: 17%;
    left: auto;
    width: 70px;
    height: 180px;
  }

  [data-style="jungle"] .hero-bg > .jg-vine-3 {
    left: 31%;
    width: 60px;
    height: 130px;
  }

  /* Giant rainforest trees, as tall as the header: one in front on the
     left, a paler one behind on the right. */
  [data-style="jungle"] .hero-bg > .jg-tree {
    --art: url("bg/jungle-tree-tall.svg");
    --sway: 0.8deg;
    --tree-left: max(-110px, calc(50% - 780px));

    bottom: 0;
    left: var(--tree-left);
    width: 46.6cqh;
    height: 100cqh;
  }

  [data-style="jungle"] .hero-bg > .jg-tree-far {
    right: max(-150px, calc(50% - 820px));
    left: auto;
    height: 92cqh;
    scale: -1 1;
  }

  /* Perched on the big tree's low branch. */
  [data-style="jungle"] .hero-bg > .jg-parrot {
    bottom: calc(29.4cqh - 22px);
    left: calc(max(-110px, calc(50% - 780px)) + 36.9cqh - 20px);
    width: 40px;
    height: 75px;
    background: url("bg/jungle-parrot.svg") center / contain no-repeat;
    mask: none;
  }

  [data-style="jungle"] .hero-bg > .jg-monstera {
    --art: url("bg/jungle-monstera.svg");
    --sway: 2.5deg;

    right: 7%;
    bottom: 40px;
    width: 230px;
    height: 306px;

    /* The tilt uses transform, so the sway (rotate) adds to it. */
    transform: rotate(-16deg);
  }

  [data-style="jungle"] .hero-bg > .jg-fern {
    --art: url("bg/jungle-fern.svg");
    --sway: 2.5deg;

    right: 1%;
    bottom: 0;
    width: 320px;
    height: 213px;
  }

  [data-style="jungle"] .hero-bg > .jg-fern-2 {
    right: auto;
    left: max(150px, calc(50% - 520px));
    width: 190px;
    height: 127px;
  }

  /* Dense undergrowth along the bottom, with a few flowers in it. */
  [data-style="jungle"] .hero-bg > .jg-undergrowth {
    --art: url("bg/jungle-undergrowth.svg");

    right: 0;
    bottom: 0;
    left: 0;
    height: 120px;
    background: var(--leaf-far);
    mask: var(--art) 0 100% / 480px 120px repeat-x;
    animation: none;
  }

  [data-style="jungle"] .hero-bg > .jg-flower {
    bottom: 64px;
    left: 30%;
    width: 30px;
    height: 30px;
    background: url("bg/jungle-flower.svg") center / contain no-repeat;
    mask: none;
    transform-origin: 50% 50%;
  }

  [data-style="jungle"] .hero-bg > .jg-flower-2 {
    bottom: 36px;
    left: 72%;
    width: 24px;
    height: 24px;
  }

  [data-style="jungle"] .hero-bg > .jg-flower-3 {
    bottom: 96px;
    left: 18%;
    width: 26px;
    height: 26px;
  }

  /* Arching fronds, and a taller, paler band of undergrowth behind. */
  [data-style="jungle"] .hero-bg > .jg-frond {
    --art: url("bg/jungle-banana.svg");
    --sway: 2deg;

    bottom: 20px;
    left: max(120px, calc(50% - 560px));
    width: 330px;
    height: 236px;
    transform-origin: 8% 100%;
  }

  [data-style="jungle"] .hero-bg > .jg-frond-2 {
    right: max(90px, calc(50% - 600px));
    bottom: 140px;
    left: auto;
    width: 280px;
    height: 200px;
    scale: -1 1;
  }

  [data-style="jungle"] .hero-bg > .jg-undergrowth-far {
    height: 200px;
    mask-size: 800px 200px;
    mask-position: 140px 100%;
  }

  [data-style="jungle"] .hero-bg > .jg-leaf {
    --art: url("bg/jungle-leaf.svg");

    top: 40%;
    left: max(60px, calc(50% - 600px));
    width: 18px;
    height: 30px;
    background: var(--accent-dim);
    opacity: 0;
    transform-origin: 50% 50%;
  }

  /* A bird (two-frame flapping sprite) now and then flying across above the
     name. */
  [data-style="jungle"] .hero-bg > .jg-bird {
    --art: url("bg/jungle-bird.svg");

    top: 16%;
    left: 0;
    width: 40px;
    height: 29px;
    background: var(--bird);
    mask-position: 0 0;
    mask-size: 200% 100%;
    opacity: 0;
  }

  @media (width <= 640px) {
    [data-style="jungle"] .hero-bg > .jg-palm {
      width: 230px;
      height: 160px;
    }

    [data-style="jungle"] .hero-bg > .jg-vine-3,
    [data-style="jungle"] .hero-bg > .jg-fern-2,
    [data-style="jungle"] .hero-bg > .jg-monstera {
      display: none;
    }

    [data-style="jungle"] .hero-bg > .jg-vine {
      left: 38%;
      height: 120px;
    }

    [data-style="jungle"] .hero-bg > .jg-vine-2 {
      right: 24%;
      height: 90px;
    }

    [data-style="jungle"] .hero-bg > .jg-tree {
      left: -150px;
      height: 80cqh;
      width: 37.3cqh;
    }

    [data-style="jungle"] .hero-bg > .jg-tree-far {
      right: -180px;
      height: 70cqh;
      width: 32.6cqh;
    }

    [data-style="jungle"] .hero-bg > .jg-parrot {
      bottom: calc(23.5cqh - 15px);
      left: calc(-150px + 29.5cqh - 13px);
      width: 26px;
      height: 49px;
    }

    [data-style="jungle"] .hero-bg > .jg-frond {
      left: 30%;
      width: 200px;
      height: 143px;
    }

    [data-style="jungle"] .hero-bg > .jg-frond-2 {
      display: none;
    }

    [data-style="jungle"] .hero-bg > .jg-undergrowth-far {
      height: 130px;
      mask-size: 520px 130px;
    }

    [data-style="jungle"] .hero-bg > .jg-fern {
      right: -40px;
      width: 190px;
      height: 127px;
    }

    [data-style="jungle"] .hero-bg > .jg-leaf {
      top: 58%;
      left: 30px;
    }

    [data-style="jungle"] .hero-bg > .jg-undergrowth {
      height: 80px;
      mask-size: 320px 80px;
    }

    [data-style="jungle"] .hero-bg > .jg-flower {
      bottom: 40px;
      left: 12%;
      width: 22px;
      height: 22px;
    }

    [data-style="jungle"] .hero-bg > .jg-flower-2 {
      left: 80%;
    }

    [data-style="jungle"] .hero-bg > .jg-flower-3 {
      bottom: 64px;
      left: 40%;
    }
  }
}

/* ---- Neobrutalist scenery: chunky shapes floating around the name ---- */
@media screen {
  [data-style="brutal"] .hero-bg {
    display: block;
  }

  [data-style="brutal"] .hero-bg > span:nth-child(1) {
    top: 13%;
    left: 8%;
    width: 96px;
    height: 96px;
    background-image: url("bg/brutal-star.svg");
  }

  [data-style="brutal"] .hero-bg > span:nth-child(2) {
    top: 17%;
    right: 9%;
    width: 84px;
    height: 84px;
    background-image: url("bg/brutal-ring.svg");
  }

  [data-style="brutal"] .hero-bg > span:nth-child(3) {
    bottom: 30%;
    left: 5%;
    width: 132px;
    height: 58px;
    background-image: url("bg/brutal-zigzag.svg");
  }

  [data-style="brutal"] .hero-bg > span:nth-child(4) {
    right: 13%;
    bottom: 24%;
    width: 70px;
    height: 70px;
    background-image: url("bg/brutal-plus.svg");
  }

  [data-style="brutal"] .hero-bg > span:nth-child(5) {
    top: 44%;
    right: 3%;
    width: 96px;
    height: 88px;
    background-image: url("bg/brutal-blob.svg");
  }

  @media (width <= 900px) {
    [data-style="brutal"] .hero-bg > span:nth-child(5) {
      display: none;
    }
  }

  @media (width <= 640px) {
    [data-style="brutal"] .hero-bg > span:nth-child(1) {
      top: 14px;
      left: 16px;
      width: 46px;
      height: 46px;
    }

    [data-style="brutal"] .hero-bg > span:nth-child(2) {
      top: 12px;
      right: 76px;
      width: 38px;
      height: 38px;
    }

    [data-style="brutal"] .hero-bg > span:nth-child(3) {
      bottom: 38%;
      left: -24px;
      width: 88px;
      height: 40px;
    }

    [data-style="brutal"] .hero-bg > span:nth-child(4) {
      right: 4%;
      bottom: 40%;
      width: 40px;
      height: 40px;
    }
  }
}

/* ---- Mario: an 8-bit level ---- */
@media screen {
  [data-style="mario"] .name,
  [data-style="mario"] .section-title,
  [data-style="mario"] .page-title,
  [data-style="mario"] .post-title {
    font-weight: 400;
    letter-spacing: 0;
    line-height: 1.35;
  }

  [data-style="mario"] .name {
    color: #fff;
    font-size: clamp(1rem, 3.3vw, 2.1rem);
    text-shadow: 0.12em 0.12em 0 #000;
  }

  [data-style="mario"] .mark {
    color: var(--block);
  }

  [data-style="mario"] .name .word + .word::after {
    color: #fff;
    font-weight: 400;
    letter-spacing: 0;
  }

  [data-style="mario"] .rest-first {
    --open: 4.2em;
  }

  [data-style="mario"] .rest-last {
    --open: 8.4em;
  }

  /* The rotating role is pixel text too, white with a hard shadow so it
     reads on the sky. */
  [data-style="mario"] .intro {
    color: #fff;
    font-family: var(--font-display);
    font-size: clamp(0.68rem, 3.3vw, 1.4rem);
    line-height: 1.6;
    text-shadow: 0.15em 0.15em 0 #000;
  }

  [data-style="mario"] .page-title,
  [data-style="mario"] .post-title {
    font-size: clamp(1.3rem, 4.5vw, 2rem);
  }

  [data-style="mario"] .section-title {
    font-size: 1.1rem;
    gap: 0.9rem;
  }

  /* Section icons sit in "?" blocks. */
  [data-style="mario"] .section-title .icon {
    width: 1.1em;
    height: 1.1em;
    box-sizing: content-box;
    padding: 0.3em;
    border: 3px solid #000;
    background: var(--block);
    box-shadow: inset -3px -3px 0 #c84c0c;
    color: #000;
  }

  /* Buttons are blocks too, and jump on hover. */
  [data-style="mario"] .contact a,
  [data-style="mario"] .contact .location,
  [data-style="mario"] .cv-download,
  [data-style="mario"] .see-projects,
  [data-style="mario"] .filters button,
  [data-style="mario"] .source-link {
    border: 3px solid #000;
    border-radius: 0;
    background: var(--block);
    box-shadow:
      inset -3px -3px 0 #c84c0c,
      inset 3px 3px 0 #ffd89c;
    color: #000;
    font-weight: 700;
    transition: transform 0.16s steps(3);
  }

  [data-style="mario"] .contact a:hover,
  [data-style="mario"] .cv-download:hover,
  [data-style="mario"] .see-projects:hover,
  [data-style="mario"] .filters button:hover,
  [data-style="mario"] .source-link:hover {
    border-color: #000;
    color: #000;
    transform: translateY(-5px);
  }

  [data-style="mario"] .contact a:active,
  [data-style="mario"] .cv-download:active,
  [data-style="mario"] .see-projects:active,
  [data-style="mario"] .source-link:active {
    transform: none;
  }

  [data-style="mario"] .filters button {
    background: var(--surface);
    box-shadow: none;
    color: var(--ink);
  }

  [data-style="mario"] .filters button[aria-pressed="true"] {
    background: var(--block);
    box-shadow:
      inset -3px -3px 0 #c84c0c,
      inset 3px 3px 0 #ffd89c;
    color: #000;
  }

  [data-style="mario"] .source-link svg {
    color: currentcolor;
  }

  [data-style="mario"] .theme-toggle {
    border: 3px solid #000;
    border-radius: 0;
    background: var(--block);
    box-shadow:
      inset -3px -3px 0 #c84c0c,
      inset 3px 3px 0 #ffd89c;
    color: #000;
    backdrop-filter: none;
  }

  [data-style="mario"] .theme-toggle:hover {
    border-color: #000;
    color: #000;
  }

  [data-style="mario"] .theme-toggle svg {
    stroke-width: 2.4;
  }

  [data-style="mario"] .tag {
    border: 2px solid #000;
    border-radius: 0;
    background: #177326;
    color: #fff;
    font-weight: 700;
  }

  [data-style="mario"] .item,
  [data-style="mario"] .list {
    border-width: 2px;
  }

  /* The section nav is a row of bricks, the footer stands on the ground. */
  [data-style="mario"] .topnav {
    border-top: 3px solid #000;
    border-bottom: 3px solid #000;
    background: var(--brick);
    background-size: 32px 16px;
    backdrop-filter: none;
  }

  [data-style="mario"] .topnav-links a,
  [data-style="mario"] .topnav-links .icon {
    color: #fff;
    font-weight: 700;
    text-shadow: 2px 2px 0 #000;
  }

  [data-style="mario"] .footer {
    padding-top: 48px;
    border-top: 0;
    background: var(--ground) 0 0 / 48px 48px repeat-x;
  }

  [data-style="mario"] .hero-foot .style-picker {
    border: 3px solid #000;
    border-radius: 0;
    background: var(--surface);
    box-shadow: 5px 5px 0 #000;
  }

  [data-style="mario"] .style-option {
    border-width: 3px;
    border-radius: 0;
  }

  [data-style="mario"] .style-option:hover {
    border-color: #000;
  }

  [data-style="mario"] .style-option[aria-pressed="true"] {
    border-color: #000;
    background: var(--block);
    color: #000;
  }

  [data-style="mario"] .style-option[aria-pressed="true"] .style-option-hint {
    color: #2e2e2e;
  }

  [data-style="mario"] .scroll-cue {
    color: #fff;
    filter: drop-shadow(2px 2px 0 #000);
  }

  [data-style="mario"] .prose pre,
  [data-style="mario"] .prose code {
    border-radius: 0;
  }

  /* Scenery: sky, drifting clouds, hills, a pipe, blocks with a coin that
     pops out, and the ground at the bottom of the header. */
  [data-style="mario"] .hero-bg {
    display: block;
    background: var(--sky);
  }

  :root[data-style="mario"]:not([data-theme="light"]) .hero-bg {
    background:
      url("bg/mario-stars.svg") 0 0 / 480px 360px,
      var(--sky);
  }

  [data-style="mario"] .hero-bg > span:nth-child(1) {
    top: 12px;
    left: 0;
    width: calc(100% + 960px);
    height: 220px;
    background: url("bg/mario-clouds.svg") 0 0 / 960px 220px repeat-x;
  }

  [data-style="mario"] .hero-bg > span:nth-child(2) {
    right: 0;
    bottom: 96px;
    left: 0;
    height: 176px;
    background: url("bg/mario-hills.svg") 0 100% / 960px 176px repeat-x;
  }

  [data-style="mario"] .hero-bg > span:nth-child(3) {
    right: 0;
    bottom: 0;
    left: 0;
    height: 96px;
    background: var(--ground) 0 0 / 48px 48px repeat;
  }

  [data-style="mario"] .hero-bg > span:nth-child(4) {
    z-index: 2;
    right: 9%;
    bottom: 96px;
    width: 128px;
    height: 120px;
    background-image: url("bg/mario-pipe.svg");
  }

  [data-style="mario"] .hero-bg > span:nth-child(5) {
    top: 12%;
    left: 8%;
    width: 240px;
    height: 48px;
    background-image: url("bg/mario-blocks.svg");
  }

  /* The hero drops in, hops across the Email and GitHub buttons and dives
     into the pipe (mario.js). Hidden until the script moves it. */
  [data-style="mario"] .hero-bg > .hero-sprite {
    z-index: 1;
    top: 0;
    left: 0;
    display: block;
    width: 36px;
    height: 48px;
    background: url("bg/mario-hero.svg") 0 0 / 300% 100% no-repeat;
    filter: drop-shadow(2px 0 0 #000) drop-shadow(-2px 0 0 #000)
      drop-shadow(0 2px 0 #000) drop-shadow(0 -2px 0 #000);
    opacity: 0;
  }

  /* Coins the header hero collects at the top of his jumps (mario.js). */
  [data-style="mario"] .hero-bg > .hero-coin {
    z-index: 1;
    width: 18px;
    height: 31px;
    background: url("bg/mario-coin.svg") center / contain no-repeat;
  }

  [data-style="mario"] .hero-bg > .hero-sprite.is-running {
    opacity: 1;
  }

  [data-style="mario"] .hero-bg > .hero-sprite[data-pose="jump"] {
    background-position: 50% 0;
  }

  [data-style="mario"] .hero-bg > span:nth-child(5)::before {
    position: absolute;
    top: -46px;
    left: 60px;
    width: 24px;
    height: 42px;
    background: url("bg/mario-coin.svg") center / contain no-repeat;
    content: "";
    opacity: 0;
  }

  :root[data-style="mario"]:not([data-theme="light"])
    .hero-bg
    > span:nth-child(1) {
    opacity: 0.25;
  }

  :root[data-style="mario"]:not([data-theme="light"])
    .hero-bg
    > span:nth-child(n + 2):not(.hero-sprite, .hero-coin) {
    filter: brightness(0.62) saturate(0.85);
  }

  @media (width <= 900px) {
    [data-style="mario"] .hero-bg > span:nth-child(5) {
      top: 60px;
      left: 4%;
      width: 160px;
      height: 32px;
    }

    [data-style="mario"] .hero-bg > span:nth-child(5)::before {
      top: -34px;
      left: 40px;
      width: 16px;
      height: 28px;
    }

    [data-style="mario"] .hero-bg > span:nth-child(4) {
      right: 3%;
      width: 80px;
      height: 75px;
    }
  }

  @media (width <= 640px) {
    [data-style="mario"] .hero-bg > span:nth-child(5) {
      top: 16px;
      left: 14px;
      width: 120px;
      height: 24px;
    }

    [data-style="mario"] .hero-bg > span:nth-child(5)::before {
      display: none;
    }

    /* Press Start 2P is wide: put the two words on their own lines. */
    [data-style="mario"] .name {
      font-size: 6.4vw;
    }

    [data-style="mario"] .name .word {
      display: block;
    }

    [data-style="mario"] .name .word + .word {
      margin-top: 0.35em;
    }
  }
}

@media screen and (prefers-reduced-motion: no-preference) {
  [data-style="jungle"] .hero-bg > .jg {
    animation: sway 8s ease-in-out infinite alternate;
  }

  [data-style="jungle"] .hero-bg > .jg-palm {
    animation-duration: 9s;
  }

  [data-style="jungle"] .hero-bg > .jg-palm-r {
    animation-delay: -4s;
  }

  [data-style="jungle"] .hero-bg > .jg-vine {
    animation-delay: -1.5s;
    animation-duration: 6.5s;
  }

  [data-style="jungle"] .hero-bg > .jg-vine-2 {
    animation-delay: -3.5s;
    animation-duration: 5.5s;
  }

  [data-style="jungle"] .hero-bg > .jg-fern,
  [data-style="jungle"] .hero-bg > .jg-monstera {
    animation-delay: -2s;
    animation-duration: 5s;
  }

  [data-style="jungle"] .hero-bg > .jg-parrot {
    animation: perch 3.4s ease-in-out infinite;
  }

  [data-style="jungle"] .hero-bg > .jg-flower {
    animation: bloom 7s ease-in-out infinite alternate;
  }

  [data-style="jungle"] .hero-bg > .jg-flower-2 {
    animation-delay: -3s;
  }

  [data-style="jungle"] .hero-bg > .jg-undergrowth {
    animation: none;
  }

  [data-style="jungle"] .hero-bg > .jg-leaf {
    animation: leaf-fall 12s ease-in-out 2s infinite;
  }

  [data-style="jungle"] .hero-bg > .jg-bird {
    animation:
      flap 0.42s steps(1) infinite,
      fly 75s linear 6s infinite;
  }

  [data-style="brutal"] .hero-bg > span {
    animation: bob 6s ease-in-out infinite alternate;
  }

  [data-style="brutal"] .hero-bg > span:nth-child(2) {
    animation-delay: -2s;
    animation-duration: 7s;
  }

  [data-style="brutal"] .hero-bg > span:nth-child(3) {
    animation-delay: -4s;
    animation-duration: 8s;
  }

  [data-style="brutal"] .hero-bg > span:nth-child(4) {
    animation: spin 14s linear infinite;
  }

  [data-style="brutal"] .hero-bg > span:nth-child(5) {
    animation-delay: -1s;
    animation-duration: 5s;
  }

  [data-style="mario"] .hero-bg > span:nth-child(1) {
    animation: drift-960 70s linear infinite;
  }

  [data-style="mario"] .hero-bg > span:nth-child(5)::before {
    animation: coin-pop 3.2s ease-out 1.5s infinite;
  }
}

@keyframes bob {
  from {
    rotate: -5deg;
    translate: 0 0;
  }

  to {
    rotate: 6deg;
    translate: 0 -16px;
  }
}

@keyframes sway {
  from {
    rotate: calc(var(--sway) * -1);
  }

  to {
    rotate: var(--sway);
  }
}

@keyframes bloom {
  from {
    rotate: -12deg;
    scale: 0.92;
  }

  to {
    rotate: 14deg;
    scale: 1.05;
  }
}

/* A gentle bob and head tilt. */
@keyframes perch {
  0%,
  100% {
    rotate: 0deg;
  }

  40% {
    rotate: -6deg;
  }

  55% {
    rotate: 3deg;
  }
}

@keyframes flap-bg {
  50% {
    background-position: 100% 0;
  }
}

@keyframes flap {
  50% {
    mask-position: 100% 0;
  }
}

/* Across the header and out on a wavy line; mostly off screen, so the bird
   passes by only now and then. */
@keyframes fly {
  0% {
    opacity: 0;
    translate: -60px 0;
  }

  2% {
    opacity: 1;
  }

  10% {
    translate: 25vw -30px;
  }

  20% {
    translate: 50vw 30px;
  }

  30% {
    translate: 75vw -20px;
  }

  38% {
    opacity: 1;
    translate: 100vw 10px;
  }

  40%,
  100% {
    opacity: 0;
    translate: 100vw 10px;
  }
}

@keyframes leaf-fall {
  0% {
    opacity: 0;
    rotate: 0deg;
    translate: 0 0;
  }

  8% {
    opacity: 0.4;
  }

  30% {
    rotate: 70deg;
    translate: 70px 110px;
  }

  55% {
    rotate: -20deg;
    translate: 20px 220px;
  }

  80% {
    opacity: 0.35;
    rotate: 110deg;
    translate: 120px 320px;
  }

  100% {
    opacity: 0;
    rotate: 40deg;
    translate: 90px 400px;
  }
}

@keyframes spin {
  to {
    rotate: 360deg;
  }
}

@keyframes drift-960 {
  to {
    transform: translateX(-960px);
  }
}

@keyframes coin-pop {
  0% {
    opacity: 1;
    translate: 0 30px;
  }

  18% {
    opacity: 1;
    translate: 0 -34px;
  }

  32% {
    opacity: 0;
    translate: 0 -10px;
  }

  100% {
    opacity: 0;
    translate: 0 -10px;
  }
}

/* ------------------------------------------------------------------------
   Section nav, per style: each style draws its own nav items.
   ------------------------------------------------------------------------ */

/* Minimal: serif links with dot separators; an underline fades in on hover
   and the current section is italic with an accent underline. */
[data-style="minimal"] .topnav-links a {
  font-size: 1.02rem;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.4em;
  transition:
    color 0.2s,
    text-decoration-color 0.2s;
}

[data-style="minimal"] .topnav-links a:hover {
  text-decoration-color: var(--rule);
}

[data-style="minimal"] .topnav-links a[aria-current] {
  box-shadow: none;
  font-style: italic;
  text-decoration-color: var(--accent);
}

[data-style="minimal"] .topnav-links li + li {
  position: relative;
}

[data-style="minimal"] .topnav-links li + li::before {
  position: absolute;
  top: 50%;
  left: 0;
  color: var(--faint);
  content: "\00B7" / "";
  translate: -50% -50%;
}

/* Jungle: leaf-shaped pills (the current one filled) that tilt like a
   leaf on hover, and a vine growing along the bottom of the bar. */
@media screen {
  [data-style="jungle"] .topnav {
    border-bottom-color: transparent;
  }

  [data-style="jungle"] .topnav::after {
    position: absolute;
    right: 0;
    bottom: -12px;
    left: 0;
    height: 18px;
    background: var(--accent-dim);
    content: "";
    mask: url("bg/jungle-nav-vine.svg") 0 0 / 240px 18px repeat-x;
    pointer-events: none;
  }
}

[data-style="jungle"] .topnav-links a {
  padding: 0.55rem 0;
  margin: 6px 4px;
  border-radius: 16px 4px;
  transition:
    color 0.25s,
    background-color 0.25s,
    rotate 0.3s cubic-bezier(0.2, 0, 0, 1);
}

[data-style="jungle"] .topnav-links a:hover {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  rotate: -2deg;
}

[data-style="jungle"] .topnav-links a[aria-current] {
  background: var(--accent);
  box-shadow: none;
  color: var(--bg);
}

[data-style="jungle"] .topnav-links a[aria-current] .icon {
  color: var(--bg);
}

/* Neobrutalist: every item is a chunky button. Inactive ones are gray;
   hover and the current section show the section's colour (as its icon). */
[data-style="brutal"] .topnav-links {
  --nav-idle: #e4ded0;
  --nav-idle-ink: #3a3a3a;
}

:root[data-style="brutal"]:not([data-theme="light"]) .topnav-links {
  --nav-idle: #3a3a3a;
  --nav-idle-ink: #e6e6e0;
}

[data-style="brutal"] .topnav-links li:nth-child(1) {
  --nav-pop: var(--pop-yellow);
}

[data-style="brutal"] .topnav-links li:nth-child(2) {
  --nav-pop: var(--pop-pink);
}

[data-style="brutal"] .topnav-links li:nth-child(3) {
  --nav-pop: var(--pop-cyan);
}

[data-style="brutal"] .topnav-links li:nth-child(4) {
  --nav-pop: var(--pop-green);
}

[data-style="brutal"] .topnav-links li:nth-child(5) {
  --nav-pop: var(--pop-orange);
}

/* The box border is an inset shadow, so inactive items take no extra room. */
[data-style="brutal"] .topnav-links a {
  padding: 0.5rem 0;
  margin: 7px 5px;
  background: var(--nav-idle);
  box-shadow:
    inset 0 0 0 2px var(--ink),
    2px 2px 0 var(--ink);
  color: var(--nav-idle-ink);
  font-weight: 600;
  transition:
    translate 0.1s,
    box-shadow 0.1s,
    background-color 0.1s;
}

[data-style="brutal"] .topnav-links a .icon {
  color: var(--nav-idle-ink);
}

[data-style="brutal"] .topnav-links a:hover,
[data-style="brutal"] .topnav-links a[aria-current] {
  background: var(--nav-pop);
  box-shadow:
    inset 0 0 0 2px var(--ink),
    4px 4px 0 var(--ink);
  color: #111;
}

[data-style="brutal"] .topnav-links a:hover {
  translate: -2px -2px;
}

[data-style="brutal"] .topnav-links a:hover .icon,
[data-style="brutal"] .topnav-links a[aria-current] .icon {
  color: #111;
}

/* Mario: the current section is a "?" block; items jump on hover. */
@media screen {
  [data-style="mario"] .topnav-links a {
    padding: 0.5rem 0;
    margin: 5px 3px;
    transition: transform 0.16s steps(3);
  }

  [data-style="mario"] .topnav-links a:hover {
    transform: translateY(-4px);
  }

  [data-style="mario"] .topnav-links a[aria-current] {
    background: var(--block);
    box-shadow:
      inset 0 0 0 3px #000,
      inset -6px -6px 0 #c84c0c,
      inset 6px 6px 0 #ffd89c;
    color: #000;
    text-shadow: none;
  }

  [data-style="mario"] .topnav-links a[aria-current] .icon {
    color: #000;
    text-shadow: none;
  }
}

@media (width <= 640px) {
  [data-style="jungle"] .topnav-links a,
  [data-style="brutal"] .topnav-links a,
  [data-style="mario"] .topnav-links a {
    padding: 0.4rem 0;
    margin: 5px 1px;
  }

  [data-style="minimal"] .topnav-links a {
    font-size: 0.85rem;
  }

  /* Bold labels in boxes: a touch smaller so all five fit. */
  [data-style="brutal"] .topnav-links a,
  [data-style="mario"] .topnav-links a {
    padding-inline: 0.2rem;
    font-size: 0.72rem;
  }
}

/* Phones: the theme toggle is as tall as the nav items next to it. */
@media (width <= 640px) {
  [data-style="brutal"] .theme-toggle {
    top: 7px;
    height: 46.5px;
  }

  [data-style="mario"] .theme-toggle {
    top: 8px;
    height: 46.5px;
  }

  /* A taller bar, with links and toggle the same height. */
  [data-style="minimal"] .topnav-links a {
    min-height: 48px;
  }

  [data-style="minimal"] .theme-toggle {
    top: 1px;
    width: 44px;
    height: 48px;
  }
}

@media (width <= 360px) {
  [data-style="brutal"] .topnav-links a,
  [data-style="mario"] .topnav-links a {
    font-size: 0.66rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-style="jungle"] .topnav-links a:hover,
  [data-style="brutal"] .topnav-links a:hover,
  [data-style="mario"] .topnav-links a:hover {
    rotate: none;
    transform: none;
    translate: none;
  }
}

/* ------------------------------------------------------------------------
   Side level (side-level.js), on wide screens: a level runs down both side
   margins behind the content. Mario has brick platforms, pipes and coins;
   the Jungle has vines hanging down the margins and a macaw that flies past now
   and then. Decorative,
   screen only.
   ------------------------------------------------------------------------ */
.side-level {
  display: none;
}

@media screen and (width >= 1100px) {
  [data-style="mario"] .page,
  [data-style="jungle"] .page {
    position: relative;
  }

  /* Full screen width, behind the content (see .toybox). */
  [data-style="mario"] .side-level,
  [data-style="jungle"] .side-level {
    position: absolute;
    z-index: -1;
    top: 0;
    bottom: 0;
    left: calc(50% - 50vw);
    display: block;
    overflow: clip;
    width: 100vw;
    pointer-events: none;

    /* Everything passes behind the content column: faint where it is. */
    mask: linear-gradient(
      to right,
      #000 calc(50% - 392px),
      rgb(0 0 0 / 30%) calc(50% - 376px),
      rgb(0 0 0 / 30%) calc(50% + 376px),
      #000 calc(50% + 392px)
    );
  }

  .side-level .station,
  .side-level .level-coin,
  .side-level .level-actor {
    position: absolute;
  }

  .side-level .level-actor {
    top: 0;
    left: 0;
    opacity: 0;
  }

  .side-level .level-actor.is-shown {
    opacity: 1;
  }

  [data-style="mario"] .station-brick {
    background: var(--ground) 0 0 / 24px 24px repeat-x;
    filter: drop-shadow(0 3px 0 rgb(0 0 0 / 25%));
  }

  /* Pipes are drawn over the hero, so he can go down into them. */
  [data-style="mario"] .station-pipe {
    z-index: 2;
    background: url("bg/mario-pipe.svg") 0 0 / 100% 100% no-repeat;
  }

  [data-style="mario"] .level-coin {
    width: 18px;
    height: 31px;
    background: url("bg/mario-coin.svg") center / contain no-repeat;
  }

  [data-style="mario"] .level-actor {
    z-index: 1;
    width: 36px;
    height: 48px;
    background: url("bg/mario-hero.svg") 0 0 / 300% 100% no-repeat;
    filter: drop-shadow(2px 0 0 #000) drop-shadow(-2px 0 0 #000)
      drop-shadow(0 2px 0 #000) drop-shadow(0 -2px 0 #000);
  }

  [data-style="mario"] .level-actor[data-pose="jump"] {
    background-position: 50% 0;
  }

  [data-style="jungle"] .level-actor {
    width: 60px;
    height: 42px;
    background: url("bg/jungle-macaw-fly.svg") 0 0 / 200% 100% no-repeat;
  }

  /* Four vines hanging down the margins, at fixed distances from the
     content so they look the same on any screen width. */
  [data-style="jungle"] .side-level::before {
    position: absolute;
    background: var(--leaf-far);
    content: "";
    inset: 0;
    mask:
      url("bg/jungle-side-vine.svg") calc(50% - 470px) 0 / 80px 400px repeat-y,
      url("bg/jungle-side-vine.svg") calc(50% - 720px) 160px / 80px 400px
        repeat-y,
      url("bg/jungle-side-vine.svg") calc(50% + 470px) 90px / 80px 400px
        repeat-y,
      url("bg/jungle-side-vine.svg") calc(50% + 720px) 250px / 80px 400px
        repeat-y;
  }
}

@media screen and (width >= 1100px) and (prefers-reduced-motion: no-preference) {
  [data-style="mario"] .level-coin:not(.is-taken),
  [data-style="mario"] .hero-bg > .hero-coin {
    animation: coin-spin 0.9s steps(4) infinite;
  }

  [data-style="jungle"] .level-actor {
    animation: flap-bg 0.3s steps(1) infinite;
  }
}

@keyframes coin-spin {
  50% {
    scale: 0.25 1;
  }
}

/* Neobrutalist toys (toybox.js): fixed to the screen, behind the content
   (a negative z-index paints them above the page background but under the
   text). Only the shapes take the pointer; the body lets clicks through so
   shapes can be grabbed anywhere the content does not cover them. */
.toybox {
  position: fixed;
  z-index: -1;
  display: none;
  inset: 0;
  pointer-events: none;
}

@media screen and (width >= 1100px) {
  [data-style="brutal"] .toybox {
    display: block;
  }

  [data-style="brutal"] body {
    pointer-events: none;
  }

  [data-style="brutal"] body > :not(.toybox) {
    pointer-events: auto;
  }

  /* The toys replace the header's floating shapes. */
  [data-style="brutal"] .hero-bg > span:not(.jg, .hero-sprite) {
    display: none;
  }
}

.toy {
  position: absolute;
  top: 0;
  left: 0;
  display: grid;
  border: 3px solid var(--ink);
  background: var(--toy-color, #ffd23f);
  box-shadow: 4px 4px 0 var(--ink);
  color: #111;
  cursor: grab;
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 800;
  line-height: 1;
  place-items: center;
  pointer-events: auto;
  touch-action: none;
  transition: opacity 0.5s;
  user-select: none;
  white-space: nowrap;
  will-change: transform;
}

.toy svg {
  width: 56%;
  height: 56%;
  fill: currentcolor;
}

.toy-circle {
  border-radius: 50%;
}

.toy-square {
  border-radius: 6px;
}

.toy-pill {
  border-radius: 999px;
}

.toy.is-behind {
  opacity: 0.3;
}

.toy.is-held {
  box-shadow: 8px 8px 0 var(--ink);
  cursor: grabbing;
}

/* Print: a clean, light, single-page-flow CV. */
@media print {
  :root,
  :root[data-theme] {
    --bg: #fff;
    --ink: #000;
    --muted: #333;
    --faint: #555;
    --rule: #ccc;
    --accent: #3f6212;

    color-scheme: light;
  }

  :root[data-style="brutal"],
  :root[data-style="brutal"][data-theme] {
    --ink: #111;
    --muted: #2e2e2e;
    --faint: #4d4d4d;
    --rule: #111;
    --accent: #1d3fd6;
    --mark-bg: #ffd23f;
    --mark-ink: #111;
  }

  :root[data-style="minimal"],
  :root[data-style="minimal"][data-theme] {
    --ink: #141414;
    --muted: #4a4a4a;
    --faint: #6b6b6b;
    --rule: #ddd;
    --accent: #a33a2e;
  }

  :root[data-style="mario"],
  :root[data-style="mario"][data-theme] {
    --accent: #b8231a;
  }

  [data-style="mario"] .name {
    font-size: 18pt;
  }

  [data-style="mario"] .section-title {
    font-size: 12pt;
  }

  /* Keep the neobrutalist highlight colours even when the browser does not
     print backgrounds. */
  [data-style="brutal"] .mark,
  [data-style="brutal"] .tag {
    print-color-adjust: exact;
  }

  @page {
    size: a4;
    margin: 12mm 13mm;
  }

  html {
    font-size: 9.5pt;
  }

  .theme-toggle,
  .style-picker,
  .hero-foot,
  .footer,
  #blog,
  .scroll-cue,
  .cv-download,
  .contact a.cv-download,
  [data-style] .contact a.cv-download,
  .topnav,
  .filters,
  .see-projects {
    display: none;
  }

  .page {
    max-width: none;
    padding: 0;
  }

  .header {
    min-height: 0;
    padding: 0 0 5mm;
  }

  .name {
    font-size: 30pt;
  }

  .intro,
  [data-style] .intro {
    max-width: none;
    color: var(--muted);
    font-family: var(--font-body);
    font-size: 12pt;
    text-shadow: none;
  }

  .roles {
    display: none;
  }

  .name .word + .word::after {
    content: none;
  }

  .intro .print-only {
    display: inline;
  }

  .contact a,
  .contact .location,
  [data-style] .contact a,
  [data-style] .contact .location {
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    text-decoration: none;
    transform: none;
  }

  .source-link,
  [data-style] .source-link {
    display: none;
  }

  .contact a[data-print]::after {
    color: var(--muted);
    content: " " attr(data-print);
    font-size: 0.85em;
  }

  .contact a[href^="http"]::after {
    color: var(--muted);
    content: " " attr(href);
    font-size: 0.85em;
  }

  .section + .section {
    margin-top: 5mm;
  }

  .section-title {
    break-after: avoid;
  }

  .contact {
    margin-top: 4mm;
  }

  .contact .site-link,
  [data-style] .contact .site-link {
    display: inline-flex;
  }

  .section-title,
  .section-head {
    margin-bottom: 1.5mm;
  }

  /* A narrower date/kind column and full-width text. The rule between
     entries is drawn on each column, so it moves to the next page with the
     entry instead of staying behind at the bottom of the previous one. */
  .item {
    border-top: 0;
    gap: 0;
    grid-template-columns: 26mm 1fr;
  }

  .item > * {
    padding-top: 1.5mm;
    border-top: 1px solid var(--rule);
  }

  .item > .meta {
    padding-right: 4mm;
  }

  .item-text,
  .part-text {
    max-width: none;
  }

  .item,
  #projects .item {
    padding: 0 0 1.5mm;
    break-inside: avoid;
  }

  .item .item-sub + .item-text,
  .item .item-title + .item-text {
    margin-top: 1mm;
  }

  .stack,
  .tech-list {
    gap: 0.5mm 3mm;
  }

  .item .stack {
    margin-top: 1mm;
  }

  /* Projects with several parts may break between parts, not inside one. */
  .item:has(.parts),
  #projects .item:has(.parts) {
    break-inside: auto;
  }

  .parts {
    margin-top: 1.5mm;
    break-before: avoid;
    gap: 2mm;
  }

  .parts > li {
    break-inside: avoid;
  }

  /* Never leave a project title alone at the bottom of a page. */
  .item-title,
  .item-title + .item-text {
    break-after: avoid;
  }

  .item .parts .stack {
    margin-top: 0.5mm;
  }

  .consent {
    display: block;
    margin: 6mm 0 0;
    break-inside: avoid;
    color: var(--faint);
    font-size: 7pt;
    line-height: 1.4;
  }
}
