/* Classic page: dashboard layout after the original look. The page is always light.
   The build appends the accessibility themes (src/a11y/themes.ts, themeCss) as
   :root[data-theme="…"] blocks; they override the variables on :root below.
   Sizes follow docs/superpowers/specs/2026-10-26-classic-measurements.md (Chrome, 1512 px):
   16 px page margin, 8 px tile gap, 20 px tile radius, row 1 at 459 px with 24 px padding,
   row 2 at 528 px with 32 px padding, 42 px pills with 32 px buttons. */
:root {
  --bg: #fcfcfc;
  --fg: #16161a;
  --muted: #4a4a55;
  --surface: #ffffff;
  --border: #e5e5e5;
  --accent: #1d4ed8;
  --font-scale: 1;
  /* Height of the sticky top bar: one row of pills, 4.75rem. The two-row layout below sets 7.25rem.
     Sticky things under the bar (the controls of the Prototype Factory diagram) stop at this line. */
  --site-header-h: 4.75rem;
  --dot: rgba(22, 22, 26, 0.16);
  /* Dark tiles and pills. Text colours keep at least 4.5:1 on --tile-bg. */
  --tile-bg: #191919;
  --tile-fg: #ffffff;
  --tile-muted: #b4b4b4;
  --tile-accent: #9cc0ff;
  --tile-line: #000000;
  --tile-radius: 20px;
  /* Light tiles of the case study detail pages (see .study-tile). Text colours keep at least
     4.5:1 on --study-tile-bg; the accent also serves as focus ring (more than 3:1). */
  --study-tile-bg: #ffffff;
  --study-tile-fg: #16161a;
  --study-tile-muted: #4a4a55;
  --study-tile-border: #d6d6d6;
  --study-tile-accent: #1d4ed8;
  /* Row 1 padding: 24 px from about 1500 px up, down to 16 px on small screens. Row 2 tiles
     take the 32 px variant (see the row rules below). */
  --tile-pad: clamp(1rem, 1.6vw, 1.5rem);
  --pill-bg: #191919;
  --pill-fg: #c8c8c8;
  /* Field behind the pixel figure: light blue, because the cap and the jacket are almost black.
     --portrait-fg is the text colour on it (the name over the profile stage). */
  --portrait-bg: #cfe0f4;
  --portrait-fg: #14213a;
  /* Ground behind the pixel trees in the skills tile */
  --grove-ground: #1f3524;
  --grove-line: #2d4a33;
  --folder-back: color-mix(in srgb, var(--surface) 94%, var(--fg));
  --radius: 1rem;
  --gutter: 1rem;
  --page-width: 100rem;
  color-scheme: light;
}

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

html {
  font-size: calc(100% * var(--font-scale));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background-color: var(--bg);
  background-image: radial-gradient(var(--dot) 1px, transparent 1px);
  background-size: 22px 22px;
  color: var(--fg);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  line-height: 1.6;
  overflow-wrap: break-word;
}

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

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

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

main:focus {
  outline: none;
}

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: -100%;
  z-index: 10;
  padding: 0.75rem 1rem;
  background: var(--fg);
  color: var(--bg);
  border-radius: 0.5rem;
}

.skip-link:focus {
  top: 1rem;
}

/* Top bar: three separate dark pills on a translucent bar (8 px from the top, 68 px high,
   16 px radius, surface colour at 50 %). The pills sit 13 px below the bar's top edge. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 5;
  width: min(var(--page-width), 100%);
  margin: 0 auto;
  padding: 0.5rem var(--gutter) 0;
  pointer-events: none;
}

/* The side columns never get narrower than their content, so the switch never runs under the
   tabs. With enough room both are equal and the tabs sit in the middle. The right column
   holds the display menu button and the avatar. */
.bar {
  display: grid;
  grid-template-columns: minmax(max-content, 1fr) auto minmax(max-content, 1fr);
  grid-template-areas: 'switch tabs actions';
  align-items: center;
  gap: 0.5rem 1rem;
  min-height: 4.25rem;
  padding: 0.5rem 0.8125rem;
  border-radius: 1rem;
  background: color-mix(in srgb, var(--surface) 50%, transparent);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  /* Positioned above the tiles, so the open display menu also covers them when the header
     is not sticky (large text on phones). */
  position: relative;
  z-index: 3;
}

.bar > * {
  pointer-events: auto;
}

/* Logo (public/logo.svg), first in the bar. A mask in the text colour (var(--fg)), so it follows every theme and is not black
   on the black bar of contrast-yellow; forced colours get CanvasText (below). 1.5rem high (the pill is 42 px). It needs room:
   in the one-row bar from 980 px (in English the tabs are cut off below that), in the second row (left of the tabs) from 580 to 800 px. Everywhere else it is hidden, as
   with large text, where the bar has no room. */
.site-logo {
  display: none;
  color: var(--fg);
}

.site-logo-mark {
  display: block;
  height: 1.5rem;
  aspect-ratio: 181 / 49;
  background-color: currentColor;
  -webkit-mask: url(/logo.svg) no-repeat center / contain;
  mask: url(/logo.svg) no-repeat center / contain;
}

@media (forced-colors: active) {
  .site-logo-mark {
    forced-color-adjust: none;
    background-color: CanvasText;
  }
}

@media (min-width: 980px) {
  :root:not([data-theme='large-text']) .site-logo {
    display: block;
    grid-area: switch;
    justify-self: start;
  }

  :root:not([data-theme='large-text']) .pill-switch {
    margin-left: max(6.5rem, clamp(0rem, 16vw - 8rem, 8rem));
  }
}

@media (min-width: 580px) and (max-width: 800px) {
  :root:not([data-theme='large-text']) .site-logo {
    display: block;
    grid-area: tabs;
    justify-self: start;
  }
}

/* Pills: 42 px high, 32 px buttons with 14 px text */
.pill,
.tabbar ul {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  margin: 0;
  padding: 0.3125rem;
  border-radius: 999px;
  background: var(--pill-bg);
  list-style: none;
}

/* The indent grows from 0 at about 800 px to 8rem at 1600 px, so mid widths keep the room. */
.pill-switch {
  grid-area: switch;
  justify-self: start;
  margin-left: clamp(0rem, 16vw - 8rem, 8rem);
}

.pill-item,
.tabbar a {
  display: inline-flex;
  align-items: center;
  min-height: 2rem;
  padding: 0.25rem 1rem;
  border-radius: 999px;
  color: var(--pill-fg);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
}

.pill-item:hover,
.tabbar a:hover {
  color: #ffffff;
}

/* 'page' on /klassisch/; 'true' marks the current mode and tab on the case study pages */
.pill-item:is([aria-current='page'], [aria-current='true']),
.tabbar a:is([aria-current='page'], [aria-current='true']) {
  background: #ffffff;
  color: #191919;
}

.pill :focus-visible,
.tabbar :focus-visible {
  outline-color: var(--tile-accent);
  outline-offset: 1px;
  border-radius: 999px;
}

.tabbar {
  grid-area: tabs;
  min-width: 0;
  max-width: 100%;
}

.tabbar ul {
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}

/* Avatar pill: a 36 px circle in a 3 px dark ring, 42 px in all. The circle clips the head
   file (public/classic/portrait-head.png, made by `npm run portrait-head`): a 36 x 36 px square
   with the head from forehead to chin and a margin around it. The file fills the whole circle,
   so the crop never moves with the root font size and the face shows in every theme. The
   scale is 1 at the default font size and stays crisp (pixelated) at other sizes. */
.avatar-pill {
  flex: none;
  display: inline-flex;
  justify-content: center;
  align-items: flex-start;
  width: calc(2.25rem + 6px);
  height: calc(2.25rem + 6px);
  border: 3px solid var(--pill-bg);
  border-radius: 50%;
  background: var(--portrait-bg);
  overflow: hidden;
}

.avatar-pill img {
  flex: none;
  display: block;
  width: 2.25rem;
  height: 2.25rem;
  max-width: none;
  image-rendering: pixelated;
}

.avatar-pill:focus-visible {
  border-radius: 50%;
}

/* Buttons */
.button {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0.5rem 1.1rem;
  border: 2px solid var(--accent);
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-weight: 700;
  text-decoration: none;
  overflow-wrap: anywhere;
}

/* Views (tabs). Without JavaScript all views show stacked. */
.view {
  max-width: var(--page-width);
  margin-inline: auto;
  padding: 1.75rem var(--gutter) 2.5rem;
  scroll-margin-top: 6rem;
}

.view-hidden {
  display: none;
}

.view-title {
  margin: 1.5rem 0 3.5rem;
  font-size: clamp(2.5rem, 6vw, 4rem);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-align: center;
}

/* Bento grid: row 1 = profile, counter, skills (ratio 1 : 1 : 2); row 2 = three equal tiles.
   12 columns with an 8 px gap give 364 + 364 + 736 px and 3 x 488 px at 1512 px. The grid is
   a size container, so the row heights keep the measured proportion at every desktop width. */
.bento {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 0.5rem;
  container-type: inline-size;
}

.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  grid-column: span 4;
  min-width: 0;
  padding: var(--tile-pad);
  border: 1px solid var(--tile-line);
  border-radius: var(--tile-radius);
  background: var(--tile-bg);
  color: var(--tile-fg);
  overflow: hidden;
  scroll-margin-top: 6rem;
}

.tile > * {
  margin-block: 0;
}

#tile-profile,
#tile-years {
  grid-column: span 3;
}

#tile-skills {
  grid-column: span 6;
}

/* Row heights: 459 / 1480 and 528 / 1480 of the grid width (459 px and 528 px at 1512 px).
   The rem floor keeps room for the text on small desktops. */
#tile-profile,
#tile-years,
#tile-skills {
  min-height: max(20rem, 31.0136cqi);
}

#tile-claim,
#tile-latest,
#tile-experience {
  min-height: max(24rem, 35.6757cqi);
}

/* Row 2 padding: 32 px from about 1450 px up. :where() keeps the specificity at zero, so the
   large-text rules further down still set their own padding. */
:where(#tile-claim, #tile-latest, #tile-experience) {
  --tile-pad: clamp(1.25rem, 2.2vw, 2rem);
}

/* Shared tile title (tileTitle() in template.ts): pixel icon and upper-case label, 12 px,
   weight 600, letter-spacing 0.08em, muted. Every dashboard tile uses it at the top left. */
.tile-label {
  display: flex;
  align-items: center;
  align-self: flex-start;
  gap: 0.5rem;
  margin: 0;
  color: var(--tile-muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  line-height: 1.2;
  text-align: left;
  text-transform: uppercase;
}

.tile-icon {
  flex: none;
  display: block;
  width: 1rem;
  height: 1rem;
  color: var(--tile-fg);
}

@media (forced-colors: active) {
  .tile-icon {
    color: CanvasText;
  }
}

/* Title of the detail page tiles (study-tile) */
.tile-title {
  color: var(--tile-fg);
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1;
}

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

.tile :focus-visible {
  outline-color: var(--tile-accent);
}

.tile .meta,
.tile .eyebrow,
.tile .badge,
.tile .tags span,
.tile .metrics dt,
.tile .process dt,
.tile .placeholder {
  color: var(--tile-muted);
}

.tile .button {
  border-color: var(--tile-accent);
  background: var(--tile-accent);
  color: var(--tile-bg);
}

.tile .card {
  border-color: color-mix(in srgb, var(--tile-fg) 14%, transparent);
  background: rgba(255, 255, 255, 0.04);
}

/* Profile tile: the pixel portrait stands on a colour field at the top of the tile, the text
   sits below it on the tile colour. */
#tile-profile {
  justify-content: flex-end;
}

/* The stage takes the room above the text, edge to edge at the top. Its flex basis is 0 and
   it is a size container, so the portrait never changes the height of the tile. */
#tile-profile .portrait-stage {
  display: flex;
  flex: 1 1 0;
  align-items: flex-end;
  justify-content: center;
  min-height: 0;
  margin: calc(-1 * var(--tile-pad)) calc(-1 * var(--tile-pad)) 0;
  background: var(--portrait-bg);
  container-type: size;
  overflow: hidden;
}

/* Pixel portrait (public/classic/portrait.png). --portrait-w and --portrait-h hold the size of
   the file (portraitCss() in template.ts). --fit is the largest scale that leaves one image
   pixel free at the sides and at the top. Where round() works, the scale is a whole number
   of pixels, so every image pixel covers whole screen pixels. An image larger than the stage
   shrinks to fit. The figure stands on the bottom edge of the stage. */
.portrait {
  --fit: min(100cqw / (var(--portrait-w) + 2), 100cqh / (var(--portrait-h) + 1));
  --k: var(--fit);
  flex: none;
  width: calc(var(--portrait-w) * var(--k));
  max-width: none;
  height: calc(var(--portrait-h) * var(--k));
  image-rendering: pixelated;
}

@supports (width: round(down, 1px, 1px)) {
  .portrait {
    --k: max(round(down, var(--fit), 1px), min(var(--fit), 1px));
  }
}

/* Contrast theme: yellow on black, like the globe. A yellow outline one image pixel wide
   keeps the dark figure apart from the black field, and a yellow floor line closes the stage. */
:root[data-theme='contrast-yellow'] {
  --portrait-bg: #000000;
  --portrait-fg: var(--tile-fg);
}

:root[data-theme='contrast-yellow'] #tile-profile .portrait-stage {
  border-bottom: 2px solid var(--tile-line);
}

:root[data-theme='contrast-yellow'] .portrait {
  filter: drop-shadow(var(--k) 0 0 var(--tile-fg)) drop-shadow(calc(-1 * var(--k)) 0 0 var(--tile-fg))
    drop-shadow(0 calc(-1 * var(--k)) 0 var(--tile-fg));
}

/* Red-green-safe theme keeps the light blue field of the default theme. */

/* Forced colours paint the field in the system background, so the dark figure gets an outline
   in the text colour, one image pixel wide, and a floor line closes the stage. */
@media (forced-colors: active) {
  #tile-profile .portrait-stage {
    border-bottom: 2px solid CanvasText;
  }

  .portrait {
    filter: drop-shadow(var(--k) 0 0 CanvasText) drop-shadow(calc(-1 * var(--k)) 0 0 CanvasText)
      drop-shadow(0 calc(-1 * var(--k)) 0 CanvasText);
  }
}

/* The name title (h1, shared tile-label) sits over the portrait stage at the same offset as the
   titles of the other tiles: one tile padding from the top and from the left. The stage keeps
   room for it above the portrait. The label is dark on the light portrait field. */
#tile-profile .tile-label {
  position: absolute;
  top: var(--tile-pad);
  left: var(--tile-pad);
  right: var(--tile-pad);
  z-index: 1;
  color: var(--portrait-fg);
}

#tile-profile .portrait-stage {
  min-height: 11rem;
  padding-top: calc(var(--tile-pad) + 2rem);
  box-sizing: border-box;
}

#tile-profile .profile-intro {
  margin: 0 0 0.75rem;
  color: var(--tile-fg);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

#tile-profile .profile-roles {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.75rem;
  margin: 0;
  color: var(--tile-muted);
  font-size: 0.8125rem;
  line-height: 1.3;
}

/* Counter tile: label with a pixel clock on top, the design hours in the middle and a pixel
   rainbow with the years at its ends at the bottom. The tile is a size container, so the
   number scales with its width and never leaves the tile. */
#tile-years {
  align-items: center;
  container-type: inline-size;
  text-align: center;
}

.counter > * {
  margin: 0;
}

.counter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  width: 100%;
  margin-top: auto;
}

/* Big number: 60 px from a 364 px tile, weight 300, tabular digits. min-width reserves
   room for seven characters (six digits and a dot), so the line keeps its width when the
   digits change. */
.counter-value {
  min-width: 7ch;
  font-size: clamp(2rem, 19cqi, 3.75rem);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.counter-unit {
  color: var(--tile-muted);
  font-size: 0.9rem;
  font-weight: 500;
}

/* The rainbow is a pixel SVG. On engines with round() it grows in whole multiples of its
   72 pixel grid (3 or 4 screen pixels per grid pixel); the years row has the same width. */
.arc-wrap {
  width: 100%;
  max-width: 288px;
  margin-top: auto;
}

@supports (width: round(down, 1px, 1px)) {
  .arc-wrap {
    width: min(288px, max(72px, round(down, 100cqi, 72px)));
  }
}

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

/* Each year stands under the middle of its arc end (13 / 72 from the sides). */
.arc-years {
  display: flex;
  justify-content: space-between;
  margin: 0.25rem 0 0;
}

.arc-year {
  width: 36%;
  color: var(--tile-fg);
  font-size: clamp(1rem, 6.5cqi, 1.5rem);
  font-weight: 500;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* Contrast theme: yellow stripes with black gaps. The markers turn yellow too: a solid blob
   that fills the gaps stands out from the stripes. */
:root[data-theme='contrast-yellow'] .arc-band,
:root[data-theme='contrast-yellow'] .arc-ring {
  fill: var(--tile-fg);
}

:root[data-theme='contrast-yellow'] :is(.arc-band-2, .arc-band-4, .arc-band-6) {
  display: none;
}

:root[data-theme='contrast-yellow'] .arc-dot {
  fill: var(--tile-fg);
}

:root[data-theme='contrast-yellow'] .arc-glint {
  fill: var(--tile-bg);
}

/* Red-green-safe theme: bands in the Okabe-Ito set, same order from outside to inside. */
:root[data-theme='red-green-safe'] .arc-band-1 {
  fill: #d55e00;
}

:root[data-theme='red-green-safe'] .arc-band-2 {
  fill: #e69f00;
}

:root[data-theme='red-green-safe'] .arc-band-3 {
  fill: #f0e442;
}

:root[data-theme='red-green-safe'] .arc-band-4 {
  fill: #009e73;
}

:root[data-theme='red-green-safe'] .arc-band-5 {
  fill: #56b4e9;
}

:root[data-theme='red-green-safe'] .arc-band-6 {
  fill: #0072b2;
}

:root[data-theme='red-green-safe'] .arc-band-7 {
  fill: #cc79a7;
}

/* Forced colours: system colours, stripes as in the contrast theme, no shimmer. */
@media (forced-colors: active) {
  .arc-band,
  .arc-ring {
    fill: CanvasText;
  }

  :is(.arc-band-2, .arc-band-4, .arc-band-6) {
    display: none;
  }

  .arc-dot {
    fill: CanvasText;
  }

  .arc-glint {
    display: none;
  }
}

/* Skills tile: skills in two columns over the full width, pixel trees at the bottom */

.skill-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.skill-list {
  columns: 2;
  column-gap: 2rem;
}

/* Name left, round score badge right. The name shortens with an ellipsis, the badge stays. */
.skill {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.125rem 0;
  break-inside: avoid;
  font-size: 1.125rem;
  line-height: 1.3;
}

.skill-name {
  min-width: 0;
  overflow: hidden;
  color: color-mix(in srgb, var(--tile-fg) 90%, var(--tile-bg));
  font-weight: 300;
  letter-spacing: -0.01em;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.skill-score {
  flex: none;
  min-width: 2rem;
  padding: 0.0625rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--tile-fg) 18%, var(--tile-bg));
  border-radius: 999px;
  background: color-mix(in srgb, var(--tile-fg) 10%, var(--tile-bg));
  color: var(--tile-muted);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  text-align: center;
}

/* Pixel tool icons (src/classic/toolIcons.ts) in the "Tools" tile: 16 x 16 grids, drawn at a
   whole-number scale (--k). They are decoration; classic.css moves them only without reduced
   motion (see the motion block). The field takes the room between the title and the chips.
   Its flex basis is 0, so the icons never make the tile taller; the min-height keeps room for
   the loose constellation on small tiles. */
.tool-icons {
  --k: 3;
  position: relative;
  flex: 1 1 0;
  min-height: 11rem;
  margin-top: 0.75rem;
  pointer-events: none;
}

/* Each icon has its own place: --x and --y run from 0 to 1 over the free room of the field,
   so an icon never leaves the field, whatever its scale. */
.tool-icon {
  position: absolute;
  left: calc((100% - 16px * var(--k)) * var(--x, 0));
  top: calc((100% - 16px * var(--k)) * var(--y, 0));
  display: block;
  width: calc(16px * var(--k));
  height: calc(16px * var(--k));
  pointer-events: auto;
}

.tool-icon:nth-child(1) {
  --k: 3;
  --x: 0.67;
  --y: 0.46;
}

.tool-icon:nth-child(2) {
  --k: 4;
  --x: 0.31;
  --y: 0.06;
}

.tool-icon:nth-child(3) {
  --k: 4;
  --x: 1;
  --y: 0.23;
}

.tool-icon:nth-child(4) {
  --k: 3;
  --x: 0;
  --y: 0.51;
}

.tool-icon:nth-child(5) {
  --k: 3;
  --x: 0.65;
  --y: 0;
}

.tool-icon:nth-child(6) {
  --k: 3;
  --x: 1;
  --y: 0.85;
}

.tool-icon:nth-child(7) {
  --k: 3;
  --x: 0;
  --y: 1;
}

.tool-icon:nth-child(8) {
  --k: 4;
  --x: 0.31;
  --y: 0.74;
}

.tool-icon:nth-child(9) {
  --k: 4;
  --x: 0.69;
  --y: 0.94;
}

.tool-icon-svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  shape-rendering: crispEdges;
}

/* Sparkle pixels at two corners; they only blink in the motion block. */
.tool-icon::before,
.tool-icon::after {
  content: '';
  position: absolute;
  width: calc(1px * var(--k));
  height: calc(1px * var(--k));
  background: var(--sparkle, #ebede9);
  opacity: 0;
}

.tool-icon::before {
  top: calc(-1px * var(--k));
  right: calc(-1px * var(--k));
}

.tool-icon::after {
  bottom: calc(1px * var(--k));
  left: calc(-2px * var(--k));
}

/* Contrast theme: yellow icons with black details; no white shine. */
:root[data-theme='contrast-yellow'] .tool-icon {
  --sparkle: var(--tile-fg);
}

:root[data-theme='contrast-yellow'] .tool-icon .px {
  fill: var(--tile-fg);
}

:root[data-theme='contrast-yellow'] .tool-icon .px-detail {
  fill: var(--tile-bg);
}

:root[data-theme='contrast-yellow'] .tool-shine {
  display: none;
}

/* Forced colours: the icons take the system text colour, the details the background. */
@media (forced-colors: active) {
  .tool-icon .px {
    fill: CanvasText;
  }

  .tool-icon .px-detail {
    fill: Canvas;
  }

  .tool-shine,
  .tool-icon::before,
  .tool-icon::after {
    display: none;
  }
}

/* Hidden on screen, read by screen readers ("92 von 100") */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  border: 0;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Pixel trees (public/classic/tree-strip.png, 800 x 68 image pixels, made by npm run tree-strip).
   The box reaches the left, right and bottom edge of the tile and clips the strip. The strip
   keeps its centre and a whole-number scale (--k), so every image pixel covers whole screen
   pixels. 1600 px of strip cover the widest tile. The height is fixed, so nothing shifts. */
.grove {
  --k: 2;
  position: relative;
  display: flex;
  justify-content: center;
  width: calc(100% + 2 * var(--tile-pad));
  height: calc(68px * var(--k));
  margin: auto calc(-1 * var(--tile-pad)) calc(-1 * var(--tile-pad));
  overflow: hidden;
  pointer-events: none;
}

/* The ground: a dark green band the trees stand in */
.grove::before {
  content: '';
  position: absolute;
  inset: auto 0 0;
  height: calc(5px * var(--k));
  border-top: calc(var(--k) * 1px) solid var(--grove-line);
  background: var(--grove-ground);
}

.grove-img {
  position: relative;
  flex: none;
  width: calc(800px * var(--k));
  max-width: none;
  height: calc(68px * var(--k));
  image-rendering: pixelated;
}

/* Contrast theme: the trees turn grey, the ground is one yellow line on black. */
:root[data-theme='contrast-yellow'] .grove {
  --grove-ground: #000000;
  --grove-line: var(--tile-fg);
}

:root[data-theme='contrast-yellow'] .grove-img {
  filter: saturate(0);
}

/* Red-green-safe theme: the ground takes the blue of the theme. */
:root[data-theme='red-green-safe'] .grove {
  --grove-ground: #14305f;
  --grove-line: #14305f;
}

/* Forced colours: the system colours paint the ground line; the trees stay as images. */
@media (forced-colors: active) {
  .grove {
    --grove-ground: Canvas;
    --grove-line: CanvasText;
  }

  .skill-score {
    border-color: CanvasText;
  }
}

/* Tools tile (id tile-claim): the icon field fills the tile, the chips close it at the bottom */
#tile-claim {
  text-align: center;
}

#tile-claim .tools {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 0.75rem;
}

/* Tools: small chips below the icons, the last one white */

.tool-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.25rem;
  max-width: 26rem;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

.tool {
  padding: 0 0.375rem;
  border: 1px solid color-mix(in srgb, var(--tile-fg) 18%, var(--tile-bg));
  border-radius: 999px;
  color: var(--tile-muted);
  font-size: 0.75rem;
  line-height: 1.4;
  white-space: nowrap;
}

.tool-last {
  border-color: color-mix(in srgb, var(--tile-fg) 50%, var(--tile-bg));
  color: var(--tile-fg);
}

/* Hover couples an icon with its chip: the chip and the icon share the slug (tool-<slug>, see
   template.ts), so hovering one highlights both. Without motion the highlight is static: a
   bright chip and a dashed frame around the icon. The motion block adds a lift and a pop.
   A new tool with an icon needs its slug in these lists and in the motion block. */
.tool:hover,
.tools:has(.tool-icon-figma:hover) .tool-figma,
.tools:has(.tool-icon-adobe-cs:hover) .tool-adobe-cs,
.tools:has(.tool-icon-miro:hover) .tool-miro,
.tools:has(.tool-icon-cursor:hover) .tool-cursor,
.tools:has(.tool-icon-github-copilot:hover) .tool-github-copilot,
.tools:has(.tool-icon-google-ai-studio:hover) .tool-google-ai-studio,
.tools:has(.tool-icon-claude-code:hover) .tool-claude-code,
.tools:has(.tool-icon-cloudflare:hover) .tool-cloudflare,
.tools:has(.tool-icon-google-analytics:hover) .tool-google-analytics {
  border-color: var(--tile-fg);
  background: color-mix(in srgb, var(--tile-fg) 16%, var(--tile-bg));
  color: var(--tile-fg);
}

.tool-icon:hover,
.tools:has(.tool-figma:hover) .tool-icon-figma,
.tools:has(.tool-adobe-cs:hover) .tool-icon-adobe-cs,
.tools:has(.tool-miro:hover) .tool-icon-miro,
.tools:has(.tool-cursor:hover) .tool-icon-cursor,
.tools:has(.tool-github-copilot:hover) .tool-icon-github-copilot,
.tools:has(.tool-google-ai-studio:hover) .tool-icon-google-ai-studio,
.tools:has(.tool-claude-code:hover) .tool-icon-claude-code,
.tools:has(.tool-cloudflare:hover) .tool-icon-cloudflare,
.tools:has(.tool-google-analytics:hover) .tool-icon-google-analytics {
  outline: 2px dashed var(--tile-fg);
  outline-offset: 4px;
}

/* Contrast theme: the active chip turns yellow with black text. The icons keep black and
   yellow only, so the pop flash leaves their colours alone. */
:root[data-theme='contrast-yellow'] .tool:hover,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-figma:hover) .tool-figma,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-adobe-cs:hover) .tool-adobe-cs,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-miro:hover) .tool-miro,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-cursor:hover) .tool-cursor,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-github-copilot:hover) .tool-github-copilot,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-google-ai-studio:hover) .tool-google-ai-studio,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-claude-code:hover) .tool-claude-code,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-cloudflare:hover) .tool-cloudflare,
:root[data-theme='contrast-yellow'] .tools:has(.tool-icon-google-analytics:hover) .tool-google-analytics {
  background: var(--tile-fg);
  color: var(--tile-bg);
}

:root[data-theme='contrast-yellow'] .tool-icon-svg {
  filter: none !important;
}

/* Forced colours: the pair gets a solid system outline. */
@media (forced-colors: active) {
  .tool:hover,
  .tools:has(.tool-icon-figma:hover) .tool-figma,
  .tools:has(.tool-icon-adobe-cs:hover) .tool-adobe-cs,
  .tools:has(.tool-icon-miro:hover) .tool-miro,
  .tools:has(.tool-icon-cursor:hover) .tool-cursor,
  .tools:has(.tool-icon-github-copilot:hover) .tool-github-copilot,
  .tools:has(.tool-icon-google-ai-studio:hover) .tool-google-ai-studio,
  .tools:has(.tool-icon-claude-code:hover) .tool-claude-code,
  .tools:has(.tool-icon-cloudflare:hover) .tool-cloudflare,
  .tools:has(.tool-icon-google-analytics:hover) .tool-google-analytics,
  .tool-icon:hover,
  .tools:has(.tool-figma:hover) .tool-icon-figma,
  .tools:has(.tool-adobe-cs:hover) .tool-icon-adobe-cs,
  .tools:has(.tool-miro:hover) .tool-icon-miro,
  .tools:has(.tool-cursor:hover) .tool-icon-cursor,
  .tools:has(.tool-github-copilot:hover) .tool-icon-github-copilot,
  .tools:has(.tool-google-ai-studio:hover) .tool-icon-google-ai-studio,
  .tools:has(.tool-claude-code:hover) .tool-icon-claude-code,
  .tools:has(.tool-cloudflare:hover) .tool-icon-cloudflare,
  .tools:has(.tool-google-analytics:hover) .tool-icon-google-analytics {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }

  .tool-icon-svg {
    filter: none !important;
  }
}

/* Latest case study: the folder card sits in the middle of the tile */
#tile-latest {
  align-items: stretch;
}

#tile-latest .folder {
  width: min(100%, 24rem);
  margin: auto;
}

/* Experience tile: title, the pixel globe with the "Welt betreten" button on it, and an optional
   CV link below. */
.cv-link {
  align-self: flex-start;
  font-weight: 600;
}

/* The art area clips the star field of the globe at its edges. */
.experience-art {
  display: grid;
  flex: 1 0 auto;
  place-items: center;
  overflow: hidden;
}

/* Pixel globe (src/classic/globe.js, static frame public/classic/globe-frame.png). The box has
   the size of the former placeholder circle, so the tile keeps its height. Where round() works,
   the box is a multiple of 32 px, so every globe pixel covers whole screen pixels. The frame
   is 1.5 times the box and centred on it: room for the stars and the satellite orbit. */
.globe {
  position: relative;
  grid-area: 1 / 1;
  width: min(70%, 16rem);
  width: round(down, min(70%, 16rem), 32px);
  aspect-ratio: 1;
  pointer-events: none;
}

.globe-frame,
.globe-canvas {
  position: absolute;
  inset: -25%;
  width: 150%;
  max-width: none;
  height: 150%;
  image-rendering: pixelated;
}

/* globe.js marks the box once its canvas shows the first frame. */
.globe[data-globe='live'] .globe-frame {
  visibility: hidden;
}

/* Forced colours keep the static frame; the canvas would ignore the system colours. */
@media (forced-colors: active) {
  .globe-canvas {
    display: none;
  }

  .globe[data-globe='live'] .globe-frame {
    visibility: visible;
  }
}

/* The globe and the button share one grid cell, so the button sits on the globe. Positioned,
   so the button stays above the positioned globe. `.tile a` sets the accent colour, so the
   selector needs `.tile` to keep the text black. */
.tile .world-link {
  position: relative;
  grid-area: 1 / 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0.5rem 1.25rem;
  border-radius: 999px;
  background: #fff;
  color: #000;
  max-width: 100%;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  text-decoration: none;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}

#tile-experience h3 {
  margin: 0 0 0.75rem;
  font-size: 1.75rem;
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.1;
}

#tile-experience h4 {
  margin: 0 0 0.35rem;
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.25;
}

#tile-experience p {
  max-width: 42rem;
  margin: 0 0 0.75rem;
}

#tile-experience .cards {
  grid-template-columns: minmax(0, 1fr);
}

/* Folder cards: a back panel with a tab, two sheets behind the image, a front pocket with the text */
.folders {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 3rem clamp(1.5rem, 6vw, 6rem);
  margin: 0;
  padding: 0 clamp(0rem, 2.5vw, 2.5rem);
  list-style: none;
}

.folder {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding-top: 1.75rem;
  color: var(--fg);
  overflow-wrap: anywhere;
}

/* Tab of the back panel */
.folder::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  width: 42%;
  height: 2rem;
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: 18px 18px 0 0;
  background: var(--folder-back);
}

a.folder {
  text-decoration: none;
}

.folder-stack {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  height: 9rem;
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: 0 var(--tile-radius) 0 0;
  background: var(--folder-back);
}

/* Two sheets behind the image */
.folder-stack::before,
.folder-stack::after {
  content: '';
  position: absolute;
  bottom: -0.5rem;
  height: 82%;
  aspect-ratio: 3 / 4;
  border-radius: 8px;
  background: #f2f2f2 url('/classic/placeholder-sheet.svg') center / cover no-repeat;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}

.folder-stack::before {
  left: 16%;
  transform: rotate(-9deg);
}

.folder-stack::after {
  right: 16%;
  transform: rotate(9deg);
}

.folder .media {
  position: relative;
  z-index: 1;
  width: min(52%, 11rem);
  aspect-ratio: 4 / 3;
  margin-bottom: 0.75rem;
  border: 5px solid #ffffff;
  border-radius: 6px;
  background: var(--border);
  object-fit: cover;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16);
}

.folder-body {
  position: relative;
  z-index: 2;
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
  margin-top: -2.25rem;
  padding: 1.1rem 1.25rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--tile-radius);
  background: var(--surface);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.06);
}

/* Card title: 18 px, weight 600, line-height 22.5 px */
.folder-title,
.folder h3 {
  display: block;
  margin: 0;
  color: var(--fg);
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.25;
}

.folder h3 a {
  color: inherit;
  text-decoration: none;
}

/* The whole front pocket is the link target */
.folder h3 a::after {
  content: '';
  position: absolute;
  inset: 0;
}

.folder .meta,
.tile .folder .meta {
  display: block;
  margin: 0.2rem 0 0;
  color: var(--muted);
  font-size: 0.95rem;
  font-weight: 500;
}

.folder-body p {
  margin: 0.25rem 0 0;
}

.tile .folder:focus-visible {
  outline-color: var(--tile-accent);
  border-radius: var(--tile-radius);
}

/* Shared text pieces */
.lead {
  font-size: 1.2rem;
}

.eyebrow,
.meta,
.badge {
  color: var(--muted);
  font-size: 0.95rem;
  font-weight: 600;
}

.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.block + .block {
  margin-top: 2rem;
}

.block > h3 {
  margin: 0 0 1rem;
}

.key-messages {
  display: grid;
  gap: 0.5rem;
  margin: 0;
  padding-left: 1.25rem;
}

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

.tags li {
  padding: 0.3rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  font-weight: 600;
}

.tags span {
  display: block;
  color: var(--muted);
  font-weight: 400;
}

.metrics,
.process {
  display: grid;
  gap: 0.75rem;
  margin: 0;
}

.metrics dt,
.process dt {
  color: var(--muted);
  font-weight: 600;
}

.metrics dd,
.process dd {
  margin: 0;
}

/* Cards (agencies) */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.card {
  min-width: 0;
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--tile-radius);
  background: var(--surface);
}

.card h4 {
  margin: 0 0 0.5rem;
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.25;
}

.card p {
  margin: 0 0 0.75rem;
}

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

.actions {
  position: relative;
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: auto;
  padding-top: 0.75rem;
}

.links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

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

/* Legal pages: hero and prose sections */
.hero,
.section,
.site-footer {
  max-width: 72rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.hero {
  padding-block: clamp(2.5rem, 8vw, 5rem) clamp(1.5rem, 4vw, 2.5rem);
}

.hero h1 {
  margin: 0;
  line-height: 1.05;
  letter-spacing: -0.03em;
}

.hero-small h1 {
  font-size: clamp(2.25rem, 6vw, 4rem);
}

.section {
  padding-block: clamp(2rem, 5vw, 3.5rem);
  border-top: 1px solid var(--border);
}

.section p {
  max-width: 42rem;
  margin: 0 0 1rem;
}

.prose h2 {
  margin: 2rem 0 0.5rem;
  font-size: 1.5rem;
}

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

address {
  font-style: normal;
}

/* Footer */
.site-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 2rem 3rem;
  border-top: 1px solid var(--border);
  color: var(--muted);
}

.site-footer p {
  margin: 0;
}

.site-footer ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The email entry is the target of the "Kontakt" buttons. The address wraps instead of
   pushing the page wider, also with large text on a 320 px screen. */
.site-footer li {
  min-width: 0;
  overflow-wrap: anywhere;
  scroll-margin-top: 6rem;
}

/* Case study detail page (/klassisch/referenz/<slug>/). Same frame as the dashboard:
   16 px page margin, 8 px gap, 20 px tiles, here in the light variables. The layout follows
   the geometry of a reference portfolio page, measured at 1512 px (grid 1480 px):
     row top       3 equal columns (488 px), height 452 px = 30.4 % of the grid width
     row research  2 equal columns (736 px), height 397 px = 26.8 %
     row brand     3 equal columns (488 px), height 321 px = 21.7 %
     process       screenshots 2 / text tile 1 (965 px / 482 px, 32 px between)
     notes         3 equal columns
   The breakpoints are container queries in rem on .study, so the large-text theme (a larger
   root font size) moves them up by itself: below 44rem one column; from 44rem two columns
   (the logo and a third brand tile take the full row); from 60rem research and insights side
   by side; from 70rem the three-column rows with the reference heights. */
.study {
  container: study / inline-size;
  max-width: var(--page-width);
  margin-inline: auto;
  padding: 2.5rem var(--gutter) 3.5rem;
}

/* Back link on the left, title centred above the tiles, client below. */
.study-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  margin-bottom: 2.5rem;
}

/* Long words wrap instead of widening their item past the page. */
.study-head > * {
  max-width: 100%;
}

.study-back {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  margin-bottom: 0.5rem;
  color: var(--muted);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}

.study-back:hover {
  color: var(--fg);
  text-decoration: underline;
}

.study-title {
  margin: 0;
  font-size: clamp(1.25rem, 1.6vw, 1.75rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.02em;
}

.study-head-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem 1.25rem;
}

.study-client {
  margin: 0;
  font-size: 0.9375rem;
}

.study-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
}

.study-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
}

/* Light tiles: the .tile rules read --tile-*, so the detail page tiles take the light
   variables here. The dashboard tiles keep the dark ones from :root. */
.study .tile {
  --tile-bg: var(--study-tile-bg);
  --tile-fg: var(--study-tile-fg);
  --tile-muted: var(--study-tile-muted);
  --tile-line: var(--study-tile-border);
  --tile-accent: var(--study-tile-accent);
  --tile-pad: clamp(1.375rem, 2.1vw, 2rem);
  grid-column: auto;
  gap: 1.125rem;
}

.study-row-research > .tile {
  --tile-pad: clamp(1.5rem, 2.6vw, 2.5rem);
}

/* The theme palettes of src/a11y/themes.ts set the dark --tile-* variables. Contrast-yellow
   keeps its black tiles with yellow text; red-green-safe gets light blue-white tiles. */
:root[data-theme='contrast-yellow'] {
  --study-tile-bg: var(--tile-bg);
  --study-tile-fg: var(--tile-fg);
  --study-tile-muted: var(--tile-muted);
  --study-tile-border: var(--tile-line);
  --study-tile-accent: var(--tile-accent);
}

:root[data-theme='red-green-safe'] {
  --study-tile-bg: #f4f7ff;
  --study-tile-fg: #0a1f44;
  --study-tile-muted: #2f3f66;
  --study-tile-border: #5b7bb5;
  --study-tile-accent: #0b3a8f;
}

.study-tile .lead {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.7;
}

/* Overview: the text on top, the facts at the bottom as rows of a label and a right-aligned value. */
.study-facts {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin: auto 0 0;
  padding-top: 1.5rem;
}

.study-facts > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.65rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--tile-fg) 12%, transparent);
}

.study-facts > div:last-child {
  border-bottom: 0;
}

.study-facts dt {
  flex: none;
  color: var(--tile-fg);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.study-facts dd {
  min-width: 0;
  margin: 0;
  color: var(--tile-muted);
  font-size: 0.875rem;
  line-height: 1.5;
  text-align: right;
}

.study-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.875rem;
  margin: 0;
  padding-left: 1.1rem;
  font-size: 0.875rem;
  line-height: 1.7;
}

/* Tabs (built by tabs.js, initStudyTabs). They look like the pills of the top bar. */
.study-tabs {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.study-tablist {
  display: inline-flex;
  flex-wrap: wrap;
  align-self: flex-start;
  gap: 0.15rem;
  max-width: 100%;
  padding: 0.3125rem;
  border-radius: calc(1rem + 0.3125rem);
  background: var(--pill-bg);
}

.tile .study-tablist {
  background: color-mix(in srgb, var(--tile-fg) 10%, transparent);
}

.study-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2rem;
  padding: 0.25rem 1rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--pill-fg);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.15;
  text-align: center;
  cursor: pointer;
}

.study-tab:hover {
  color: #ffffff;
}

/* Tabs inside a light tile: the colours come from the tile, not from the dark top bar pill. */
.tile .study-tab {
  color: var(--tile-muted);
}

.tile .study-tab:hover {
  color: var(--tile-fg);
}

.tile .study-tab[aria-selected='true'] {
  background: var(--tile-fg);
  color: var(--tile-bg);
}

.study-tab[aria-selected='true'] {
  background: #ffffff;
  color: #191919;
}

.study-tab:focus-visible {
  outline-color: var(--tile-accent);
  outline-offset: 1px;
  border-radius: 999px;
}

.study-panel[hidden] {
  display: none;
}

.study-panel:focus-visible {
  outline-offset: 2px;
  border-radius: 0.5rem;
}

.study-panel-title {
  margin: 0 0 0.5rem;
}

/* Snapshot: the pills spread over the width, upper case and small; then the big panel title. */
.study-snapshot .study-tablist {
  align-self: stretch;
}

.study-snapshot .study-tab {
  flex: 1 1 auto;
  padding-inline: 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* The tabs stay on one row while they fit. Below that width the tablist becomes a grid that
   never leaves one lonely tab on the last row; in a very narrow tile it becomes one column. The
   widths are container widths in rem, measured from the natural width of the row (3 tabs 19.5rem,
   4 tabs 24.8rem, 5 tabs 31.7rem; large-text is the same or smaller) and the longest tab with its
   padding (9.5rem, "Herausforderung"), plus a margin. The count of tabs comes from :has() on the
   tab list. Those selectors sit in :where(), so a later rule at the same width always wins. One
   value serves all themes: the large-text root font size scales the rem. Steps by tab count:
   5 tabs: row from 32.5rem, 3 + 2 below it, 2 + 2 + 1 (the last one wide) below 29.5rem.
   4 tabs: row from 25.5rem, 2 x 2 below it. 3 tabs: row from 20.5rem.
   Every count: one column below 20.5rem. */
.study-snapshot .study-tabs {
  container: study-snapshot-tabs / inline-size;
}

@container study-snapshot-tabs (max-width: 32.5rem) {
  .study-snapshot .study-tablist:where(:has(> :nth-child(5))) {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 0.25rem;
  }

  .study-snapshot .study-tablist:where(:has(> :nth-child(5))) > * {
    grid-column: span 2;
  }

  .study-snapshot .study-tablist:where(:has(> :nth-child(5))) > :where(:nth-child(n + 4)) {
    grid-column: span 3;
  }
}

@container study-snapshot-tabs (max-width: 29.5rem) {
  .study-snapshot .study-tablist:where(:has(> :nth-child(5))) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .study-snapshot .study-tablist:where(:has(> :nth-child(5))) > * {
    grid-column: span 1;
  }

  .study-snapshot .study-tablist:where(:has(> :nth-child(5))) > :where(:nth-child(5)) {
    grid-column: 1 / -1;
  }
}

@container study-snapshot-tabs (max-width: 25.5rem) {
  .study-snapshot .study-tablist:where(:has(> :nth-child(4)):not(:has(> :nth-child(5)))) {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.25rem;
  }
}

@container study-snapshot-tabs (max-width: 20.5rem) {
  .study-snapshot .study-tablist {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.25rem;
  }

  .study-snapshot .study-tablist > * {
    grid-column: auto;
  }
}

.study-snapshot .study-panel-title {
  margin: 0 0 0.5rem;
  color: var(--tile-fg);
  font-size: 1.75rem;
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.1;
}

/* Snapshot and figures results. The number stands above its caption (stacked) by default. Only a
   container that is wide enough for both puts the number on the left and the caption on the right
   behind a line between the rows. The container width is in rem, so the large-text theme (a larger
   root font size) and every screen width switch by themselves: the number column takes up to 13.5rem
   (the longest value, "unter 30 Min.", fits on one line), the gap 1.5rem, and the caption keeps at
   least 10.5rem, enough for the longest caption word. A value never wraps: it shrinks with the
   container width (cqw) and stays on one line. A caption word never breaks inside the word: a
   caption that does not fit moves below the number. */
.study-results {
  container: study-results / inline-size;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

.study-results > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 0.375rem;
  padding: 1rem 0;
}

@container study-results (min-width: 26rem) {
  .study-results > li {
    grid-template-columns: minmax(6rem, 13.5rem) minmax(0, 1fr);
    align-items: center;
    gap: 0.5rem 1.5rem;
  }
}

.study-results > li + li {
  border-top: 1px solid color-mix(in srgb, var(--tile-fg) 12%, transparent);
}

.study-value {
  margin: 0;
  font-size: clamp(1.5rem, 9cqw, 2.25rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

@container study-results (min-width: 26rem) {
  .study-value {
    font-size: clamp(1.5rem, 6cqw, 1.75rem);
  }
}

.study-value-label {
  min-width: 0;
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.6;
  overflow-wrap: normal;
  word-break: normal;
  -webkit-hyphens: manual;
  hyphens: manual;
}

/* Logo: the logo always sits on white, so it stays readable in every theme. It fills the tile.
   The image shows at its natural size (400 x 200) and shrinks only when the tile is narrower. */
.study-logo {
  padding: 0;
}

.study-logo-box {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  padding: 2.5rem 2rem;
  background: #ffffff;
  color: #16161a;
}

/* The box stays white in every theme, so its text keeps a dark colour of its own
   (the muted tile colour is yellow in contrast-yellow: 1.27:1 on white). */
.study-logo-box .placeholder {
  color: #4a4a55;
}

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

/* Research: stat rows with a round badge on the left, the goals on the right behind a line. */
.study-research {
  container: study-research / inline-size;
}

.study-research-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem;
  margin: 1.5rem 0 auto;
}

.study-stats {
  display: grid;
  align-content: start;
  gap: 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.study-stat {
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr);
  align-items: start;
  gap: 1rem;
}

.study-stat > div {
  min-width: 0;
}

.study-stat-badge {
  display: inline-flex;
  justify-self: center;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.75rem;
  padding-inline: 0.4rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background: color-mix(in srgb, var(--tile-accent) 14%, var(--tile-bg));
  color: var(--tile-fg);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.study-stat-label,
.study-stat-detail {
  margin: 0;
  overflow-wrap: anywhere;
}

.study-stat-label {
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1.45;
}

.study-stat-detail {
  color: var(--tile-muted);
  font-size: 0.8125rem;
  line-height: 1.55;
}

.study-goals {
  display: grid;
  align-content: start;
  gap: 0.5rem;
}

.study-goals > p {
  margin: 0 0 1.5rem;
  font-size: 0.9375rem;
  line-height: 1.7;
}

.study-goals > p:last-child {
  margin-bottom: 0;
}

.study-goal-label {
  margin: 0;
  color: var(--tile-muted);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  line-height: 1.2;
  text-transform: uppercase;
}

@container study-research (min-width: 30rem) {
  .study-research-body {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    gap: 0;
  }

  .study-stats {
    padding-right: 2rem;
  }

  .study-goals {
    padding-left: 2rem;
    border-left: 1px solid color-mix(in srgb, var(--tile-fg) 14%, transparent);
  }

  .study-stats:only-child {
    grid-column: 1 / -1;
  }

  .study-goals:only-child {
    grid-column: 1 / -1;
    padding-left: 0;
    border-left: 0;
  }
}

/* Insights: 2 x 2 percentages. The dots repeat the number as a picture: filled by the
   percentage. Own square dots in an inline SVG (study-dots), coloured by class. */
.study-insights-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2rem 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.study-insight {
  min-width: 0;
}

.study-dots {
  display: block;
  width: 4rem;
  height: auto;
  margin-bottom: 0.625rem;
  forced-color-adjust: none;
}

.dots-on {
  fill: var(--tile-accent);
}

.dots-off {
  fill: color-mix(in srgb, var(--tile-fg) 14%, transparent);
}

.study-insight-value {
  margin: 0;
  font-size: clamp(1.75rem, 2.4vw, 2.25rem);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.study-insight-caption {
  margin: 0.75rem 0 0;
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1.45;
}

.study-insight-detail {
  margin: 0.375rem 0 0;
  color: var(--tile-muted);
  font-size: 0.8125rem;
  line-height: 1.6;
}

@media (forced-colors: active) {
  .dots-on {
    fill: CanvasText;
  }

  .dots-off {
    fill: GrayText;
  }
}

/* Typography: the stated font as text, the sample as a picture (aria-hidden). */
.study-type {
  display: grid;
  gap: 0.5rem;
  margin-top: auto;
}

.study-type-sample {
  margin: 0;
  font-size: clamp(4rem, 8vw, 6rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
}

.study-type-family {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
}

.study-type-weights {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  margin: 0;
  padding: 0;
  color: var(--tile-muted);
  font-size: 0.8125rem;
  list-style: none;
}

/* Colour palette: round swatches with hex and name as text. The colour is an SVG fill attribute. */
.study-swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(4.75rem, 1fr));
  gap: 1.5rem 0.75rem;
  margin: auto 0;
  padding: 0;
  list-style: none;
}

.study-swatch {
  display: grid;
  justify-items: center;
  gap: 0.375rem;
  text-align: center;
}

.study-swatch-chip {
  display: block;
  width: 4rem;
  height: 4rem;
  margin-bottom: 0.25rem;
  forced-color-adjust: none;
}

.study-swatch-chip circle {
  stroke: var(--tile-line);
  stroke-width: 1;
}

.study-swatch-hex {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.75rem;
  font-weight: 600;
}

.study-swatch-name {
  max-width: 100%;
  color: var(--tile-muted);
  font-size: 0.75rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* Key visual: the image fills the tile and is cropped to its shape. */
.study-key-visual {
  position: relative;
  min-height: 14rem;
  padding: 0;
}

.study-key-visual-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  object-position: top left;
}

/* Section heading on the page */
.study-section-title {
  margin: 0;
  color: var(--fg);
  font-size: 1.75rem;
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.1;
}

/* Iterative process: heading left and stage tabs right; below, the stacked screenshots and
   the text tile of the chosen stage. The tabs switch the text only; the images stay. */
.study-process {
  padding-block: 1.75rem;
}

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

.study-tabs-slot {
  max-width: 100%;
}

.study-process .study-tablist {
  align-self: auto;
  border: 1px solid var(--border);
  background: var(--surface);
}

.study-process .study-tab {
  color: var(--muted);
}

.study-process .study-tab:hover {
  color: var(--fg);
}

.study-process .study-tab[aria-selected='true'] {
  background: var(--fg);
  color: var(--bg);
}

.study-process .study-tab:focus-visible {
  outline-color: var(--accent);
}

.study-process-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem;
  align-items: start;
}

.study-shots {
  display: grid;
  gap: 3rem;
  min-width: 0;
}

.study-shot {
  display: grid;
  gap: 1.25rem;
  min-width: 0;
}

.study-shot-title {
  margin: 0;
  color: var(--fg);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.3;
  text-transform: uppercase;
}

/* Light grey frame, as a screenshot mat. Real images show at their own size, never wider
   than the frame, so they stay sharp. */
.study-shot-frame {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(0.75rem, 3.5cqi, 2.5rem);
  border: 1px solid var(--study-tile-border);
  border-radius: var(--tile-radius);
  background: color-mix(in srgb, var(--study-tile-fg) 9%, var(--study-tile-bg));
  overflow: hidden;
}

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

.study-process-text {
  gap: 2rem;
  padding: var(--tile-pad);
}

.study-panel {
  display: grid;
  gap: 2rem;
}

.study-process .study-panel-title {
  margin: 0;
  color: var(--tile-muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* With tabs.js the tab names the stage; the title stays for screen readers. */
.study-process .study-tabs[data-enhanced] .study-panel-title {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.study-block {
  display: grid;
  gap: 1rem;
}

.study-block-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 500;
  letter-spacing: -0.015em;
  line-height: 1.3;
}

/* App screens: portrait screenshots side by side. Up to 70rem the row scrolls sideways in its own
   box, never the page, and snaps to each screen; the box takes focus for the keyboard. From
   70rem the screens share the width in one row. Corners follow a device screen; no shadow. */
.study-screens {
  display: grid;
  gap: 1.75rem;
  padding-block: 1.75rem;
}

.study-screens-scroll {
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}

.study-screens-scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

.study-screens-list {
  display: flex;
  gap: 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.study-screen {
  flex: 0 0 min(15rem, 72cqi);
  min-width: 0;
  scroll-snap-align: start;
}

.study-screen-figure {
  display: grid;
  gap: 1.25rem;
  margin: 0;
}

.study-screen-img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--study-tile-border);
  border-radius: 11% / 5.5%;
}

.study-screen-label {
  color: var(--fg);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.3;
  text-transform: uppercase;
}

/* Validation, reflection, next steps */
.study-notes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: 1.25rem;
}

.study-notes p {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.7;
}

/* Previous and next case study */
.study-pager {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 3.5rem;
  padding-top: 2.5rem;
  border-top: 1px solid var(--border);
}

.study-prev,
.study-next {
  display: inline-flex;
  align-items: center;
  gap: 0.875rem;
  min-height: 2.75rem;
  max-width: 100%;
  color: var(--fg);
  text-decoration: none;
}

.study-next {
  flex-direction: row-reverse;
  margin-left: auto;
  text-align: right;
}

.study-pager-arrow {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  font-size: 1.1rem;
  line-height: 1;
}

.study-pager-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.study-pager-label {
  color: var(--muted);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.study-pager-title {
  font-size: 1.125rem;
  font-weight: 500;
  line-height: 1.25;
}

:is(.study-prev, .study-next):hover .study-pager-title {
  text-decoration: underline;
}

@media (forced-colors: active) {
  .study .tile,
  .study-shot-frame,
  .study-screen-img,
  .study-process .study-tablist {
    border-color: CanvasText;
  }

  .study-swatch-chip circle {
    stroke: CanvasText;
  }

  /* The active snapshot tab is a filled pill; the system colours keep it visible. */
  .study-snapshot .study-tab[aria-selected='true'] {
    forced-color-adjust: none;
    border-color: ButtonText;
    background: Highlight;
    color: HighlightText;
  }
}

/* From 44rem: two columns. The logo and a third brand tile take the full row. */
@container study (min-width: 44rem) {
  .study-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    column-gap: 1rem;
  }

  .study-back {
    grid-column: 1;
    justify-self: start;
  }

  .study-title {
    grid-column: 2;
    text-align: center;
  }

  .study-head-meta {
    grid-column: 1 / -1;
    justify-content: center;
    text-align: center;
  }

  .study-row-top,
  .study-row-brand {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .study-row-top.study-cols-3 > :last-child,
  .study-row-brand.study-cols-3 > :last-child,
  .study-row-brand.study-cols-1 > :last-child {
    grid-column: 1 / -1;
  }

  .study-row-top.study-cols-1 > :only-child {
    grid-column: 1 / -1;
  }

  .study-logo {
    min-height: 12rem;
  }

  .study-key-visual {
    min-height: 0;
    aspect-ratio: 3 / 2;
  }

  .study-row-brand.study-cols-3 > .study-key-visual {
    aspect-ratio: 3 / 1;
  }

  .study-notes {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  }
}

/* From 60rem: research and insights side by side. */
@container study (min-width: 60rem) {
  .study-row-research.study-cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* From 70rem: the three-column rows with the heights of the reference. The key visual fills
   the tile and takes the free columns of its row. */
@container study (min-width: 70rem) {
  .study-row-top {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .study-row-top.study-cols-2,
  .study-row-top.study-cols-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .study-row-top.study-cols-1 {
    grid-template-columns: minmax(0, 1fr);
  }

  .study-row-top.study-cols-3 > :last-child {
    grid-column: auto;
  }

  .study-row-top > .tile {
    min-height: 30.4cqi;
  }

  .study-row-research > .tile {
    min-height: 26.8cqi;
  }

  .study-row-brand {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .study-row-brand.study-cols-3 > :last-child,
  .study-row-brand.study-cols-1 > :last-child {
    grid-column: auto;
  }

  .study-row-brand.study-cols-2 > :last-child {
    grid-column: span 2;
  }

  .study-row-brand.study-cols-1 > :only-child {
    grid-column: 1 / -1;
  }

  .study-row-brand > .tile {
    min-height: 21.7cqi;
  }

  .study-row-brand > .study-key-visual {
    aspect-ratio: auto;
  }

  /* The carousel of the test uses the same two columns and sits in the first one, so it is as
     wide as the flowchart frame of the process block. */
  .study-process-body:not(.study-process-single),
  .study-slider {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    column-gap: 2.5rem;
  }

  .study-process-body:not(.study-process-single) {
    row-gap: 2.5rem;
  }

  .study-screens-scroll {
    overflow: visible;
  }

  .study-screens-list {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 2rem;
  }

  .study-screen {
    flex: none;
  }
}

/* Breakpoints */
@media (max-width: 1100px) {
  #tile-profile,
  #tile-years {
    grid-column: span 6;
    min-height: 24rem;
  }

  #tile-skills {
    grid-column: 1 / -1;
    min-height: 0;
  }

  #tile-claim,
  #tile-latest {
    grid-column: span 6;
  }

  #tile-experience {
    grid-column: 1 / -1;
  }

  #tile-claim,
  #tile-latest,
  #tile-experience {
    min-height: 26rem;
  }

  .folders {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Top bar in two rows up to 800 px: the mode switch, the tabs, the language switch, the display
   button and the avatar need about 790 px in one row. */
@media (max-width: 800px) {
  :root {
    --site-header-h: 7.25rem;
  }

  .bar {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      'switch actions'
      'tabs tabs';
  }

  .pill-switch {
    margin-left: 0;
  }

  .tabbar {
    justify-self: center;
  }

  /* The two-row bar is about 8rem high; jump targets stop below it. */
  .view,
  .tile {
    scroll-margin-top: 9rem;
  }
}

/* The bracket of the English game link ("Interactive (German)") leaves the visible text on phones, like in the game bar; screen
   readers still read it. Without it the English mode switch (about 260 px) pushes the language switch out of the first row. */
@media (max-width: 640px) {
  .pill-hint {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    border: 0;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* Phone bar in the two colour themes and in forced colours, as in the game bar (src/entry/menuBar.css): the dark pills sit on a
   black or dark blue page, so every pill, circle, the open panel and the bar itself get a 2 px edge in the border colour, and the bar a solid
   body. Forced colours drop the pill fills: a frame and an underline carry the shapes and the current item. */
@media (max-width: 800px) {
  :root:is([data-theme='contrast-yellow'], [data-theme='red-green-safe']) .bar {
    background: var(--surface);
    box-shadow: 0 0 0 2px var(--border);
  }

  :root:is([data-theme='contrast-yellow'], [data-theme='red-green-safe']) :is(.pill, .tabbar ul, .display-toggle, .avatar-pill) {
    box-shadow: 0 0 0 2px var(--border);
  }

  :root:is([data-theme='contrast-yellow'], [data-theme='red-green-safe']) .display-panel {
    border-width: 2px;
  }

  :root:is([data-theme='contrast-yellow'], [data-theme='red-green-safe']) .display-menu[open] > .display-toggle {
    box-shadow:
      0 0 0 2px var(--border),
      inset 0 0 0 2px var(--tile-accent);
  }
}

@media (max-width: 800px) and (forced-colors: active) {
  .pill,
  .tabbar ul {
    border: 1px solid CanvasText;
  }

  .pill-switch .pill-item:is([aria-current='page'], [aria-current='true']),
  .tabbar a:is([aria-current='page'], [aria-current='true']) {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.25em;
  }
}

@media (max-width: 720px) {
  .bento {
    grid-template-columns: minmax(0, 1fr);
  }

  .bento > .tile,
  #tile-profile,
  #tile-years,
  #tile-skills,
  #tile-claim,
  #tile-latest,
  #tile-experience {
    grid-column: 1 / -1;
  }

  #tile-profile {
    min-height: 26rem;
  }

  #tile-years,
  #tile-claim,
  #tile-latest {
    min-height: 0;
  }

  /* The tool icons stand in one centred row that wraps; the row gap holds the bob. */
  .tool-icons {
    display: flex;
    flex: none;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
    min-height: 0;
  }

  .tool-icons > .tool-icon {
    --k: 2;
    position: static;
  }

  /* Fixed room for the globe and, when open, for the scrolling panel */
  #tile-experience {
    min-height: 24rem;
  }
}

@media (max-width: 560px) {
  .folders {
    grid-template-columns: minmax(0, 1fr);
    padding: 0;
  }

  .skill-list {
    columns: 1;
  }

  .grove {
    --k: 1;
  }
}

/* Phone top bar, the same as the bar of the game (src/entry/menuBar.css and src/game/phoneHud.css; the game is the reference).
   Below 580 px the three tabs fill the whole second row, one third each. */
@media not all and (min-width: 580px) {
  :root .site-header .bar .tabbar {
    justify-self: stretch;
  }

  :root .site-header .bar .tabbar ul {
    display: flex;
    width: 100%;
  }

  :root .site-header .bar .tabbar li {
    flex: 1 1 auto;
  }

  :root .site-header .bar .tabbar a {
    justify-content: center;
    width: 100%;
  }
}

/* Narrow phones: the bar sits 8 px from the window edge, all three tabs fit in the pill without scrolling, and the mode
   switch, the language switch, the display button and the avatar fit in the first row. */
@media (max-width: 420px) {
  .site-header {
    padding-inline: 0.5rem;
  }

  .bar {
    padding-inline: 0.5rem;
  }

  .pill-item,
  .tabbar a {
    padding-inline: 0.75rem;
  }
}

/* From 421 to 480 px the mode switch also takes the phone padding. */
@media (min-width: 421px) and (max-width: 480px) {
  .pill-switch .pill-item {
    padding-inline: 0.75rem;
  }
}

/* Below 400 px (default text) the first row is tighter still: 350 to 399 px. The tab row has room, only the first row is
   squeezed. Large text stacks its rows and needs none of this. */
@media (max-width: 399px) {
  :root:not([data-theme='large-text']) .bar {
    column-gap: 0.5rem;
  }

  :root:not([data-theme='large-text']) .pill-switch .pill-item {
    padding-inline: 0.5rem;
  }

  :root:not([data-theme='large-text']) :is(.pill-switch, .lang-switch) {
    padding: 0.25rem;
  }

  :root:not([data-theme='large-text']) .bar-actions {
    gap: 0.125rem;
  }

  :root:not([data-theme='large-text']) .lang-switch {
    gap: 0;
  }

  :root:not([data-theme='large-text']) .lang-switch .pill-item {
    min-width: 1.5rem;
    padding-inline: 0.125rem;
  }
}

/* 320 px phones: the tab pill needs about 8 px more than the bar offers, so its last tab ran out of the bar. Tighter tab
   padding makes the three labels fit. From 360 px the look stays as it is. */
@media (max-width: 359px) {
  .tabbar a {
    padding-inline: 0.5rem;
  }
}

/* Below 350 px the first row still lacks room for the switch: the bar padding, the gap and the mode switch shrink once more
   (default text only; large text stacks the rows). The language links stay 24 px wide and 32 px high. */
@media (max-width: 349px) {
  :root:not([data-theme='large-text']) .bar {
    column-gap: 0.25rem;
    padding-inline: 0.25rem;
  }

  :root:not([data-theme='large-text']) .pill-switch .pill-item {
    padding-inline: 0.25rem;
  }
}

/* Motion only for people who did not ask to reduce it */
@media (prefers-reduced-motion: no-preference) {
  /* Pixel shimmer: the 14 sectors of the arc light up one after the other, from the left
     end to the right end, in hard steps. Then the arc rests. */
  .arc-glint {
    animation: arc-glint 3.6s steps(1, end) infinite;
  }

  .arc-glint-1 { animation-delay: 0s; }
  .arc-glint-2 { animation-delay: 0.12s; }
  .arc-glint-3 { animation-delay: 0.24s; }
  .arc-glint-4 { animation-delay: 0.36s; }
  .arc-glint-5 { animation-delay: 0.48s; }
  .arc-glint-6 { animation-delay: 0.6s; }
  .arc-glint-7 { animation-delay: 0.72s; }
  .arc-glint-8 { animation-delay: 0.84s; }
  .arc-glint-9 { animation-delay: 0.96s; }
  .arc-glint-10 { animation-delay: 1.08s; }
  .arc-glint-11 { animation-delay: 1.2s; }
  .arc-glint-12 { animation-delay: 1.32s; }
  .arc-glint-13 { animation-delay: 1.44s; }
  .arc-glint-14 { animation-delay: 1.56s; }

  /* counter.js sets this class when the hour changes: the number drops in by four steps. */
  .counter-roll {
    animation: counter-roll 0.4s steps(4, end) 1;
  }

  @keyframes arc-glint {
    0% {
      opacity: 0.55;
    }
    7%,
    100% {
      opacity: 0;
    }
  }

  @keyframes counter-roll {
    from {
      opacity: 0.3;
      transform: translateY(-0.3em);
    }
    to {
      opacity: 1;
      transform: none;
    }
  }

  html {
    scroll-behavior: smooth;
  }

  .tabbar a,
  .pill-item,
  .chip,
  .button,
  .world-link,
  .folder,
  .study-tab {
    transition:
      background-color 0.2s ease,
      color 0.2s ease,
      transform 0.2s ease;
  }

  a.folder:hover,
  li.folder:hover {
    transform: translateY(-2px);
  }

  /* Photo stack: hover fans the three images out, one after the other (0.1 s apart); focus or
     press makes the card active (scale 1.05, 15 px up). Spring-like overshoot easing. */
  .folders .folder,
  .folders .folder-stack::before,
  .folders .folder-stack::after,
  .folders .folder .media {
    transition-property: transform;
    transition-duration: 0.45s;
    transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  .folders .folder:focus-within,
  .folders .folder:active {
    z-index: 10;
    transform: translateY(-15px) scale(1.05);
  }

  .folders .folder:hover .folder-stack::before {
    transform: translateY(-20px) rotate(-10deg) scale(1.05);
  }

  .folders .folder:hover .media {
    transform: translateY(-20px) scale(1.05);
    transition-delay: 0.1s;
  }

  .folders .folder:hover .folder-stack::after {
    transform: translateY(-20px) rotate(10deg) scale(1.05);
    transition-delay: 0.2s;
  }

  .view:not(.view-hidden) {
    animation: view-in 0.3s ease both;
  }

  .tile {
    animation: tile-in 0.45s ease both;
  }

  .tile:nth-child(2) {
    animation-delay: 0.05s;
  }

  .tile:nth-child(3) {
    animation-delay: 0.1s;
  }

  .tile:nth-child(n + 4) {
    animation-delay: 0.15s;
  }

  .arc {
    animation: arc-sweep 1.2s ease-out 0.2s both;
  }

  .study-tabs[data-enhanced] .study-panel:not([hidden]) {
    animation: view-in 0.25s ease both;
  }

  /* Case study pages: tiles and screenshots appear as they enter the screen (tabs.js,
     initStudyReveal). They hide only while <html> has the class "js", which tabs.js sets once its
     observer runs; without JavaScript everything shows. The tile-in animation would hold the
     transform, so the study tiles take the reveal instead. */
  .study .tile {
    animation: none;
  }

  .study .reveal {
    transition:
      opacity 0.6s ease,
      transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
  }

  .js .study .reveal:not(.is-in) {
    opacity: 0;
    transform: translateY(1rem);
  }

  .study-row > .reveal:nth-child(2),
  .study-notes > .reveal:nth-child(2) {
    transition-delay: 0.08s;
  }

  .study-row > .reveal:nth-child(3),
  .study-notes > .reveal:nth-child(3) {
    transition-delay: 0.16s;
  }

  /* Tool icons: an idle bob by whole image pixels, a shine band and two blinking sparkle
     pixels. Each icon starts at another phase, so the nine icons form a wave. Only
     transform and opacity move. */
  .tool-icon {
    animation: tool-bob 2.4s steps(1, end) infinite;
  }

  .tool-icon::before {
    animation: tool-sparkle 3.2s steps(1, end) infinite;
  }

  .tool-icon::after {
    animation: tool-sparkle 4.1s steps(1, end) 1.3s infinite;
  }

  .tool-shine {
    animation: tool-shine 6s steps(14, end) infinite;
  }

  .tool-icon:nth-child(2) {
    animation-delay: -0.3s;
  }

  .tool-icon:nth-child(3) {
    animation-delay: -0.6s;
  }

  .tool-icon:nth-child(4) {
    animation-delay: -0.9s;
  }

  .tool-icon:nth-child(5) {
    animation-delay: -1.2s;
  }

  .tool-icon:nth-child(6) {
    animation-delay: -1.5s;
  }

  .tool-icon:nth-child(7) {
    animation-delay: -1.8s;
  }

  .tool-icon:nth-child(8) {
    animation-delay: -2.1s;
  }

  .tool-icon:nth-child(9) {
    animation-delay: -2.4s;
  }

  .tool-icon:nth-child(2n) .tool-shine {
    animation-delay: 0.6s;
  }

  .tool-icon:nth-child(3n) .tool-shine {
    animation-delay: 1.4s;
  }

  .tool-icon:nth-child(4n + 1)::before {
    animation-delay: 0.8s;
  }

  .tool-icon:nth-child(3n + 2)::after {
    animation-delay: 2.2s;
  }

  /* Hover on an icon or its chip: the icon pops in three steps with a flash and stays a bit
     larger, the chip lifts by whole pixels in two steps. */
  .tool-icon:hover .tool-icon-svg,
  .tools:has(.tool-figma:hover) .tool-icon-figma .tool-icon-svg,
  .tools:has(.tool-adobe-cs:hover) .tool-icon-adobe-cs .tool-icon-svg,
  .tools:has(.tool-miro:hover) .tool-icon-miro .tool-icon-svg,
  .tools:has(.tool-cursor:hover) .tool-icon-cursor .tool-icon-svg,
  .tools:has(.tool-github-copilot:hover) .tool-icon-github-copilot .tool-icon-svg,
  .tools:has(.tool-google-ai-studio:hover) .tool-icon-google-ai-studio .tool-icon-svg,
  .tools:has(.tool-claude-code:hover) .tool-icon-claude-code .tool-icon-svg,
  .tools:has(.tool-cloudflare:hover) .tool-icon-cloudflare .tool-icon-svg,
  .tools:has(.tool-google-analytics:hover) .tool-icon-google-analytics .tool-icon-svg {
    animation: tool-pop 0.36s steps(1, end) forwards;
  }

  .tool:hover,
  .tools:has(.tool-icon-figma:hover) .tool-figma,
  .tools:has(.tool-icon-adobe-cs:hover) .tool-adobe-cs,
  .tools:has(.tool-icon-miro:hover) .tool-miro,
  .tools:has(.tool-icon-cursor:hover) .tool-cursor,
  .tools:has(.tool-icon-github-copilot:hover) .tool-github-copilot,
  .tools:has(.tool-icon-google-ai-studio:hover) .tool-google-ai-studio,
  .tools:has(.tool-icon-claude-code:hover) .tool-claude-code,
  .tools:has(.tool-icon-cloudflare:hover) .tool-cloudflare,
  .tools:has(.tool-icon-google-analytics:hover) .tool-google-analytics {
    animation: tool-lift 0.2s steps(2, end) forwards;
  }

  @keyframes tool-bob {
    0%,
    100% {
      transform: translateY(0);
    }
    25% {
      transform: translateY(calc(-1px * var(--k)));
    }
    50% {
      transform: translateY(calc(-2px * var(--k)));
    }
    75% {
      transform: translateY(calc(-1px * var(--k)));
    }
  }

  @keyframes tool-shine {
    0% {
      transform: translateX(0);
    }
    35%,
    100% {
      transform: translateX(28px);
    }
  }

  @keyframes tool-sparkle {
    0%,
    70%,
    100% {
      opacity: 0;
    }
    75%,
    85% {
      opacity: 1;
    }
    80% {
      opacity: 0.4;
    }
  }

  @keyframes tool-pop {
    0% {
      transform: scale(1);
    }
    33% {
      transform: scale(1.25);
      filter: brightness(1.5);
    }
    66%,
    100% {
      transform: scale(1.125);
    }
  }

  @keyframes tool-lift {
    from {
      transform: translateY(0);
    }
    to {
      transform: translateY(-2px);
    }
  }

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

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

  @keyframes arc-sweep {
    from {
      clip-path: inset(0 100% 0 0);
    }
    to {
      clip-path: inset(0 0 0 0);
    }
  }
}

/* Display menu (top bar): an icon button next to the avatar opens a native <details> overlay
   with the theme options. src/classic/theme.js shows it, moves the focus in, closes it on
   Escape and applies the choice. The group is the positioning parent of the panel, so the
   panel ends at the right edge of the avatar. */
.bar-actions {
  grid-area: actions;
  justify-self: end;
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Language switcher: a pill like the mode switch, with two links (DE, EN) of equal width. It sits
   left of the display button. The current language is white like the current tab. */
.lang-switch .pill-item {
  justify-content: center;
  min-width: 2.5rem;
  padding-inline: 0.5rem;
}

/* Forced colours drop the white fill of the current language, so an underline marks it. */
@media (forced-colors: active) {
  .lang-switch [aria-current='true'] {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.25em;
  }
}

/* Large text on a phone (up to 620 px): the language switch takes the first line over the width of the actions column; the display
   button and the avatar sit below it, side by side (three in a row would need 283 px at large text). Less padding above and
   below, so the pill is no taller than the circles. The rules that follow the large-text marker below finish the phone bar. */
@media (max-width: 620px) {
  :root[data-theme='large-text'] .bar-actions {
    display: grid;
    grid-template-columns: auto auto;
    justify-items: end;
    gap: 0.25rem;
  }

  :root[data-theme='large-text'] .lang-switch {
    grid-column: 1 / -1;
    padding-block: 0.1875rem;
  }
}

/* Up to 420 px the gap between the actions shrinks (it sits after .bar-actions, so it wins). */
@media (max-width: 420px) {
  .bar-actions {
    gap: 0.25rem;
  }
}

/* Up to 480 px five controls share the first row: the language links get narrower (the other gaps shrink at 420 px and below, see the
   phone rules above). */
@media (max-width: 480px) {
  .lang-switch .pill-item {
    min-width: 1.75rem;
    padding-inline: 0.25rem;
  }
}

.display-menu[hidden] {
  display: none;
}

/* The same 42 px circle as the avatar pill: a 36 px face in a 3 px ring. */
.display-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(2.25rem + 6px);
  height: calc(2.25rem + 6px);
  border-radius: 50%;
  background: var(--pill-bg);
  color: var(--pill-fg);
  list-style: none;
  cursor: pointer;
}

.display-toggle::-webkit-details-marker {
  display: none;
}

.display-toggle:hover {
  color: #ffffff;
}

/* Open: a light ring in the accent colour inside the dark circle. A white fill would
   vanish against the light bar. */
.display-menu[open] > .display-toggle {
  color: #ffffff;
  box-shadow: inset 0 0 0 2px var(--tile-accent);
}

.display-toggle:focus-visible {
  outline-color: var(--tile-accent);
  outline-offset: -5px;
  border-radius: 50%;
}

/* 16 grid pixels at 2x: whole screen pixels */
.display-icon {
  flex: none;
  width: 2rem;
  height: 2rem;
  max-width: none;
}

.display-panel {
  position: absolute;
  top: calc(100% + 1rem);
  right: 0;
  z-index: 3;
  width: min(20rem, calc(100vw - 1rem));
  padding: 0.75rem 1.25rem 0.5rem;
  border: 1px solid var(--tile-line);
  border-radius: var(--tile-radius);
  background: var(--tile-bg);
  color: var(--tile-fg);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  /* The sticky header and the gap above the panel take about 8rem. The rest scrolls. */
  max-height: calc(100dvh - 8rem);
  overflow: auto;
}

.theme-switch {
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.theme-switch legend {
  padding: 0;
  margin-bottom: 0.25rem;
  color: var(--tile-muted);
  font-size: 0.875rem;
  font-weight: 700;
}

.theme-option {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 2.75rem;
}

.theme-option input {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  accent-color: var(--tile-accent);
}

.theme-option label {
  flex: 1;
  min-width: 0;
  cursor: pointer;
}

.theme-option input:checked + label {
  font-weight: 700;
}

.display-panel :focus-visible {
  outline-color: var(--tile-accent);
  outline-offset: 2px;
}

/* Forced colours: system colours for the button, the panel and the icon */
@media (forced-colors: active) {
  .display-toggle,
  .display-menu[open] > .display-toggle {
    border: 1px solid ButtonText;
    background: ButtonFace;
    color: ButtonText;
  }

  .display-menu[open] > .display-toggle {
    border-width: 3px;
    box-shadow: none;
  }

  .display-panel {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    box-shadow: none;
  }
}

/* ==========================================================================================
   Case study "Design System und Prototype Factory" (template.ts, designSystemBlocks.ts,
   factoryDiagram.ts, factory.js). It sits behind the general study rules on purpose, because
   it uses shadows (focus ring, card) that the plain study tiles do not use.
   ========================================================================================== */

/* KI-Projekt badge: next to the title of the study and on its card. Text on a solid pill, so
   the meaning never rests on a colour. */
.ai-badge {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  padding: 0.125rem 0.625rem;
  border: 1px solid var(--fg);
  border-radius: 999px;
  background: var(--fg);
  color: var(--bg);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.4;
  white-space: nowrap;
}

/* Claude Code mark (Überblick tile corner and cards): the white mark sits on a dark chip
   (#141413), so the contrast is the same on the light and the dark tiles in every theme, and
   the chip keeps its colours in forced-colors mode. The image is decoration (alt=""). */
.ai-mark-group {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 0.375rem;
}

.ai-mark {
  flex: none;
  box-sizing: content-box;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0.25rem;
  border-radius: 0.4375rem;
  background: #fff;
  forced-color-adjust: none;
}

@media (forced-colors: active) {
  .ai-mark {
    border: 1px solid CanvasText;
    background: Canvas;
  }
}

/* Überblick tile: a larger chip in the top-right corner, level with the heading row. The
   heading keeps rem-based room on the right, so nothing overlaps (also in large text). */
.ai-mark-corner {
  position: absolute;
  top: var(--tile-pad);
  right: var(--tile-pad);
  width: 2.5rem;
  height: 2.5rem;
  padding: 0.55rem;
  border-radius: 0.6rem;
}

.study-tile-corner > .tile-label {
  min-height: 3.6rem;
  padding-right: 4.6rem;
}

.folder-body .ai-badge-row {
  margin-top: 0.5rem;
}

.folder-body > .ai-badge {
  margin-top: 0.5rem;
}

.study-ai {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin: 0;
}

.study-ai-text {
  color: var(--fg);
  font-size: 0.9375rem;
  font-weight: 500;
}

/* Blocks of the design system study: heading, lead and the tiles. */
.study-ds {
  display: grid;
  gap: 1.25rem;
  align-content: start;
  padding-block: 1.75rem;
}

.study-ds > * {
  min-width: 0;
  margin: 0;
}

.study-ds-lead {
  max-width: 62rem;
  color: var(--muted);
  font-size: 1rem;
  line-height: 1.7;
}

/* ==========================================================================================
   Case study "Logostream" (template.ts, integrationBlock.ts): the dark logo tile and the
   code card. Own section; the rules of the other studies stay untouched.
   ========================================================================================== */

/* A dark logo graphic fills its tile edge to edge (the default logo box is white with
   padding). The box is black in every theme and keeps its colours in forced-colors mode, so
   the graphic never sits in a white frame. */
.study-logo-box.is-dark {
  padding: 0;
  background: #000000;
  color: #ffffff;
}

.study-logo-box.is-dark .study-logo-img {
  width: 100%;
}

@media (forced-colors: active) {
  .study-logo-box.is-dark {
    forced-color-adjust: none;
  }
}

/* Code card: dark box with fixed colours in every theme (like the sample card, it shows a
   look). The text wraps instead of scrolling, so the page never grows sideways. */
.study-integration-tile {
  align-content: start;
  min-width: 0;
}

.code-figure {
  display: grid;
  gap: 0.75rem;
  margin: 0;
}

.code-card {
  margin: 0;
  padding: clamp(1rem, 2.6vw, 1.5rem);
  border: 1px solid var(--tile-line);
  border-radius: 0.75rem;
  background: #0f1014;
  color: #f2f2f5;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9375rem;
  line-height: 1.7;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.code-tag {
  color: #ff9aa2;
}

.code-attr {
  color: #ffc46b;
}

.code-str {
  color: #8fc4ff;
}

.code-figure figcaption {
  color: var(--tile-muted);
  font-size: 0.875rem;
}

.code-paths {
  display: grid;
  gap: 0.625rem;
}

.code-paths-label {
  margin: 0;
  color: var(--tile-muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.code-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.code-chips code {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border: 1px solid var(--tile-line);
  border-radius: 999px;
  color: var(--tile-fg);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.875rem;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

@media (forced-colors: active) {
  .code-card,
  .code-chips code {
    border-color: CanvasText;
  }
}

/* --- Figma file and sample card --------------------------------------------------------- */
/* One column on narrow containers. From 60rem the screenshot takes two thirds and the card
   one third. Both tiles stretch to the same height. */
.study-figma-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
}

@container study (min-width: 60rem) {
  .study-figma-grid.has-sample {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  }
}

.study-figma-tile,
.study-figma-sample {
  align-content: start;
  min-width: 0;
}

.study-figma-figure,
.study-sample-figure {
  display: grid;
  gap: 0.75rem;
  margin: 0;
}

.study-figma-frame {
  overflow: hidden;
  border: 1px solid var(--tile-line);
  border-radius: 0.75rem;
  background: #2c2c2c;
}

.study-figma-img {
  width: 100%;
  height: auto;
}

.study-figma-figure figcaption,
.study-sample-figure figcaption {
  color: var(--tile-muted);
  font-size: 0.875rem;
}

/* The board stays white and keeps its own colours in every theme: it shows how the elements
   look. Each state is named in words, so no colour is the only information. */
.ds-board {
  --ds-text: #1d1e22;
  --ds-muted: #5a5d66;
  --ds-border: #767a84;
  --ds-primary: #cf0a1b;
  --ds-success: #0f5c37;
  --ds-success-bg: #e5f4ec;
  --ds-error: #a31616;
  --ds-error-bg: #fdeaea;
  display: grid;
  gap: 1.25rem;
  padding: clamp(1rem, 2.6vw, 1.5rem);
  border: 1px solid var(--study-tile-border);
  border-radius: var(--tile-radius);
  background: #ffffff;
  color: var(--ds-text);
}

.ds-group {
  display: grid;
  gap: 0.5rem;
  min-width: 0;
}

.ds-group-title {
  margin: 0;
  color: var(--ds-muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.ds-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
}

.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.5rem;
  padding: 0 1.25rem;
  border: 2px solid transparent;
  border-radius: 0.5rem;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.2;
}

.ds-btn-primary {
  border-color: var(--ds-primary);
  background: var(--ds-primary);
  color: #ffffff;
}

.ds-btn-secondary {
  border-color: var(--ds-border);
  background: #ffffff;
  color: var(--ds-text);
}

.ds-ico {
  flex: none;
  width: 1rem;
  height: 1rem;
}

.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.1875rem 0.75rem 0.1875rem 0.5rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.3;
}

.ds-badge-neutral {
  border-color: #c9ccd2;
  background: #eceef0;
  color: #2e3036;
}

.ds-badge-success {
  border-color: #a8d5bb;
  background: var(--ds-success-bg);
  color: var(--ds-success);
}

.ds-badge-error {
  border-color: #eeb0b0;
  background: var(--ds-error-bg);
  color: var(--ds-error);
}

.ds-field {
  display: grid;
  gap: 0.375rem;
}

.ds-field-label {
  font-size: 0.875rem;
  font-weight: 600;
}

.ds-field-box {
  display: block;
  min-height: 2.5rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--ds-border);
  border-radius: 0.5rem;
  background: #ffffff;
  color: var(--ds-muted);
  font-size: 0.9375rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.ds-alert {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.625rem;
  align-items: start;
  padding: 0.75rem 0.875rem;
  border: 1px solid #a8d5bb;
  border-left-width: 0.25rem;
  border-radius: 0.5rem;
  background: var(--ds-success-bg);
  color: var(--ds-success);
}

.ds-alert .ds-ico {
  margin-top: 0.125rem;
}

.ds-alert p {
  margin: 0;
}

.ds-alert-title {
  font-size: 0.9375rem;
  font-weight: 600;
}

.ds-alert-text {
  font-size: 0.875rem;
}

/* --- Prototype Factory diagram ---------------------------------------------------------------- */
.study .study-factory-tile {
  --tile-pad: clamp(0.75rem, 2.1vw, 2rem);
  gap: 1.5rem;
  /* clip keeps the round corners but, unlike hidden, leaves the sticky controls working. */
  overflow: hidden;
  overflow: clip;
}

.fx {
  --fx-band: color-mix(in srgb, var(--tile-fg) 4%, var(--tile-bg));
  --fx-band-line: color-mix(in srgb, var(--tile-fg) 16%, var(--tile-bg));
  --fx-node: var(--tile-bg);
  --fx-line: color-mix(in srgb, var(--tile-fg) 72%, var(--tile-bg));
  --fx-muted: var(--tile-muted);
  --fx-text: var(--tile-fg);
  --fx-flow: #b45309;
  --fx-flow-bg: #fff0dc;
  --fx-feedback: #0f766e;
  /* Faces of a body: the top is light, the lit side a step darker, the shaded side two steps; the active top takes the
     light accent. The rim is the light hairline under the top edge, the shade the colour of the soft floor shadow (used at a
     low opacity), the glow the light of the active body on the floor, done the softer accent of a passed body. */
  --fx-side: color-mix(in srgb, var(--fx-text) 10%, var(--fx-node));
  --fx-side-dark: color-mix(in srgb, var(--fx-text) 20%, var(--fx-node));
  --fx-top-on: var(--fx-flow-bg);
  --fx-rim: var(--fx-node);
  --fx-shade: var(--fx-text);
  --fx-glow: var(--fx-flow);
  --fx-done: color-mix(in srgb, var(--fx-flow) 55%, var(--fx-node));
  /* Accent of the panel: the flow, or the thread in the cycle view. */
  --fx-accent: var(--fx-flow);
  /* A flex column, not a grid: the sticky controls need the whole box as their range. */
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  min-width: 0;
}

.fx > * {
  min-width: 0;
  margin: 0;
}

:root[data-theme='contrast-yellow'] .fx {
  --fx-band: #000000;
  --fx-band-line: #ffe600;
  --fx-line: #ffe600;
  --fx-flow: #ffffff;
  --fx-flow-bg: #2b2b2b;
  --fx-feedback: #ffffff;
  --fx-side: #2b2b2b;
  --fx-side-dark: #1f1f1f;
  --fx-top-on: #5c5c5c;
  --fx-rim: #5c5c5c;
  --fx-shade: #000000;
  --fx-glow: #ffe600;
  --fx-done: #ffffff;
}

:root[data-theme='red-green-safe'] .fx {
  --fx-flow: #a64b00;
  --fx-flow-bg: #ffe9cf;
  --fx-feedback: var(--tile-fg);
  --fx-side: color-mix(in srgb, var(--tile-fg) 14%, var(--tile-bg));
  --fx-side-dark: color-mix(in srgb, var(--tile-fg) 26%, var(--tile-bg));
  --fx-top-on: #ffe9cf;
  --fx-rim: #ffffff;
  --fx-shade: #0a1f44;
  --fx-glow: #a64b00;
  --fx-done: color-mix(in srgb, #a64b00 55%, var(--tile-bg));
}

/* Pixel art in the two colour themes (the forced-colors block has the third set). Contrast-yellow draws icons
   and hero in yellow and white on black. Red-green-safe keeps blue and orange, which tell apart without red and green. */
:root[data-theme='contrast-yellow'] .px-a,
:root[data-theme='contrast-yellow'] .px-d {
  fill: var(--fx-line);
}

:root[data-theme='contrast-yellow'] .px-b,
:root[data-theme='contrast-yellow'] .px-c {
  fill: #ffffff;
}

:root[data-theme='contrast-yellow'] .px-hair,
:root[data-theme='contrast-yellow'] .px-shirt,
:root[data-theme='contrast-yellow'] .px-shoe {
  fill: #ffffff;
}

:root[data-theme='contrast-yellow'] .px-skin,
:root[data-theme='contrast-yellow'] .px-pants {
  fill: #ffe600;
}

:root[data-theme='contrast-yellow'] .px-eye {
  fill: #000000;
}

:root[data-theme='contrast-yellow'] .fx-glow > * {
  fill: none;
  stroke: var(--fx-glow);
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

:root[data-theme='contrast-yellow'] .fx-glow > :last-child {
  stroke-dasharray: 2 4;
}

:root[data-theme='contrast-yellow'] .fx-node.is-active .fx-glow > * {
  opacity: 1;
}

:root[data-theme='contrast-yellow'] .fx-panel-bar > :not(.is-done, .is-active) {
  background: color-mix(in srgb, var(--fx-line) 40%, var(--fx-node));
}

:root[data-theme='red-green-safe'] .px-a {
  fill: var(--fx-flow);
}

:root[data-theme='red-green-safe'] .px-b {
  fill: var(--fx-text);
}

:root[data-theme='red-green-safe'] .px-c {
  fill: #0b5cad;
}

:root[data-theme='red-green-safe'] .px-d {
  fill: var(--fx-muted);
}

:root[data-theme='red-green-safe'] .px-hair {
  fill: #2a1b10;
}

:root[data-theme='red-green-safe'] .px-skin {
  fill: #f2c9a0;
}

:root[data-theme='red-green-safe'] .px-eye {
  fill: #0a1f44;
}

:root[data-theme='red-green-safe'] .px-shirt {
  fill: var(--fx-flow);
}

:root[data-theme='red-green-safe'] .px-pants {
  fill: #0b3a8f;
}

:root[data-theme='red-green-safe'] .px-shoe {
  fill: #0a1f44;
}

/* Controls (added by factory.js) */
.fx-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
  align-items: center;
  justify-content: space-between;
}

/* The drawing is taller than the screen on phones and tablets, and the script scrolls along with the flow.
   The controls stay below the top bar (--site-header-h is its height), so the visitor can step on
   without scrolling back. Not on short screens, where they would leave no room for the drawing. */
@media (min-height: 44rem) {
  .fx-enhanced .fx-controls {
    position: sticky;
    top: var(--site-header-h);
    z-index: 2;
    margin-block: -0.5rem;
    padding-block: 0.5rem;
    border-bottom: 1px solid var(--tile-line);
    background: var(--tile-bg);
  }
}

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

.fx-btn {
  min-height: 2.25rem;
  padding: 0.25rem 1rem;
  border: 1px solid var(--tile-fg);
  border-radius: 999px;
  background: transparent;
  color: var(--tile-fg);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.15;
  cursor: pointer;
}

.fx-btn:hover:not(:disabled, [aria-disabled='true']) {
  background: var(--tile-fg);
  color: var(--tile-bg);
}

.fx-btn:disabled,
.fx-btn[aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

.fx-btn[hidden] {
  display: none;
}

.fx-btn:focus-visible {
  outline-color: var(--tile-accent);
}

/* The explanation of the active step is a tooltip inside the drawing (see .fx-tip). The live region
   with the same text is only for screen readers. */
.fx-live {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Tooltip of the drawing: hidden without the script; the script marks one tooltip with is-shown.
   Position and line breaks come from the generator, in drawing units. */
.fx-tip {
  display: none;
  pointer-events: none;
}

.fx-enhanced .fx-tip.is-shown {
  display: block;
}

/* The narrow drawing has no tooltips: the panel below it shows the step (see .fx-panel). The second selector
   out-ranks the rule above, which the script switches on. */
.fx-svg-narrow .fx-tip,
.fx-enhanced .fx-svg-narrow .fx-tip.is-shown {
  display: none;
}

.fx-tip-box,
.fx-tip-arrow {
  fill: var(--fx-flow-bg);
  stroke: var(--fx-flow);
  stroke-width: 1px;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* A flat offset block below the box: the shadow of pixel UIs, no blur. */
.fx-tip-shadow {
  fill: var(--fx-shade);
  opacity: 0.1;
}

.fx-tip-text {
  fill: var(--tile-fg);
}

.fx-tip-title {
  font-size: 16.5px;
  font-weight: 700;
}

.fx-tip-line {
  font-size: 16.5px;
}

.fx-svg-narrow :is(.fx-tip-title, .fx-tip-line) {
  font-size: 17px;
}

.fx-node:focus {
  outline: none;
}

/* Drawing: the narrow one by default, the wide one from 60rem of page width (container query in rem,
   so the large-text theme moves it up). The scroll box only scrolls when the drawing is wider than it. */
.fx-scroll {
  overflow-x: auto;
}

.fx-svg {
  display: block;
  width: 100%;
  height: auto;
}

.fx-svg-narrow {
  min-width: 16.25rem;
  max-width: 22.5rem;
  margin-inline: auto;
}

/* The text in the tooltips scales with the drawing. These widths keep it between about 13 and 18 px. */
.fx-svg-wide {
  display: none;
  max-width: 80rem;
  margin-inline: auto;
}

@container study (min-width: 60rem) {
  .fx-svg-wide {
    display: block;
  }

  .fx-svg-narrow {
    display: none;
  }
}

.fx-band-box {
  fill: var(--fx-band);
  stroke: var(--fx-band-line);
  stroke-width: 1;
}

.fx-band-label {
  fill: var(--fx-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  paint-order: stroke;
  stroke: var(--fx-band);
  stroke-width: 6px;
  stroke-linejoin: round;
}

.fx-svg-narrow .fx-band-label {
  font-size: 11px;
  letter-spacing: 0;
}

.fx-note {
  fill: var(--fx-muted);
  font-size: 13.5px;
  font-style: italic;
}

/* The bodies. One silhouette (box), the two faces on it (side, top) and a pixel icon on the top face.
   Lines are hairlines in screen pixels (non-scaling-stroke), so they stay fine when the drawing shrinks. The
   top draws its outline in the colour of the box, so a lit body is outlined all round. */
.fx-lift {
  stroke-linejoin: round;
}

.fx-node-box,
.fx-node-top {
  stroke: var(--fx-line);
  stroke-width: 1px;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.fx-node-box {
  fill: var(--fx-side);
}

.fx-node-side {
  fill: var(--fx-side);
  stroke: none;
}

.fx-node-side-r {
  fill: var(--fx-side-dark);
}

/* The rim: a light hairline just under the top edge, where the rounded edge catches the light. */
.fx-node-rim {
  fill: none;
  stroke: var(--fx-rim);
  stroke-width: 1px;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* The floor under a body: a soft shadow from two ellipses (their opacities add up toward the middle, no blur filter)
   and the glow of the active body, two rings that classic.css lights below. Both stay on the floor when the body lifts. */
.fx-shadow,
.fx-glow {
  pointer-events: none;
}

.fx-shadow > * {
  fill: var(--fx-shade);
  opacity: 0.055;
}

.fx-glow > * {
  fill: var(--fx-glow);
  opacity: 0;
}

.fx-node.is-active .fx-glow > * {
  opacity: 0.13;
}

/* The narrow drawing ties each title to its plate with a short hairline. */
.fx-leader {
  fill: none;
  stroke: var(--fx-muted);
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
}

.fx-node-top {
  fill: var(--fx-node);
}

/* Pixel art: crisp edges, no smoothing. The icon classes px-a to px-d are the palette of one icon. */
.fx-icon,
.fx-hero-sprite {
  shape-rendering: crispEdges;
}

.px-a {
  fill: var(--fx-flow);
}

.px-b {
  fill: var(--fx-text);
}

.px-c {
  fill: var(--fx-feedback);
}

.px-d {
  fill: var(--fx-muted);
}

/* The hero: its own palette (hair, skin, eye, shirt, trousers, shoes). The shirt takes the accent. The hero and
   its packet only look like this here; the script moves them. */
.px-hair {
  fill: #3b2a1d;
}

.px-skin {
  fill: #f2c9a0;
}

.px-eye {
  fill: #1a1a1a;
}

.px-shirt {
  fill: var(--fx-flow);
}

.px-pants {
  fill: #264653;
}

.px-shoe {
  fill: #111111;
}

.fx-hero,
.fx-packet {
  pointer-events: none;
}

/* [hidden] does not work on SVG groups. */
.fx-hero[hidden],
.fx-packet[hidden] {
  display: none;
}

.fx-packet > * {
  fill: var(--fx-flow-bg);
  stroke: var(--fx-flow);
  stroke-width: 1px;
  shape-rendering: crispEdges;
  vector-effect: non-scaling-stroke;
}

/* The floor of the wide drawing: a lattice of pixel dots (the pattern is in the drawing). */
.fx-floor {
  pointer-events: none;
}

.fx-floor-dots {
  fill: url(#fx-dots);
}

.fx-floor-dot {
  fill: color-mix(in srgb, var(--fx-line) 35%, transparent);
  shape-rendering: crispEdges;
}

/* The mask of the floor: luminance, white shows the dots, black hides them. Not a colour on screen. */
.fx-floor-fade {
  fill: url(#fx-floor-grad);
}

.fx-fade-in {
  stop-color: white;
}

.fx-fade-out {
  stop-color: black;
}

/* Titles in the monospace face of the panel: a label of the drawing, close to its body. The layout reserves 0.6 em per
   character, which is the width of a monospace character, so a title never runs wider than its box. */
.fx-node-title {
  fill: var(--fx-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: inherit;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.fx-svg-wide .fx-node-title {
  font-size: 14.5px;
}

.fx-svg-narrow .fx-node-title {
  font-size: 14px;
}

/* The title of the active body takes the accent and an underline: a cue that needs no colour. */
.fx-node.is-active .fx-node-title {
  fill: var(--fx-flow);
  text-decoration: underline;
}

.fx-edge-base {
  fill: none;
  stroke: var(--fx-line);
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
}

.fx-edge-head {
  fill: var(--fx-line);
  stroke: none;
}

/* The feedback loop is a faint dotted hairline all the time; it lights up in the cycle view. */
.fx-edge.is-feedback .fx-edge-base {
  stroke: var(--fx-feedback);
  stroke-width: 2px;
  stroke-dasharray: 0.1 6;
  stroke-linecap: round;
  opacity: 0.7;
  vector-effect: none;
}

.fx[data-view='cycle'] .fx-edge.is-feedback .fx-edge-base {
  stroke-width: 2.5px;
  opacity: 1;
}

.fx-edge.is-feedback .fx-edge-head,
.fx-edge-dot {
  fill: var(--fx-feedback);
}

/* The overlay of an edge draws the flow. At rest it is hidden: one dash of the length of the path,
   moved out of the path. */
.fx-edge-flow {
  fill: none;
  stroke: var(--fx-flow);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-dasharray: 1 1;
  stroke-dashoffset: 1;
}

.fx-edge.is-feedback .fx-edge-flow {
  stroke: var(--fx-feedback);
}

.fx-edge.is-active .fx-edge-flow,
.fx-edge.is-done .fx-edge-flow {
  stroke-dashoffset: 0;
}

/* Behind the hero the line cools down to a dotted trail: the solid flow fades, the base turns into accent dots. */
.fx-edge.is-done .fx-edge-flow {
  opacity: 0;
}

.fx-edge.is-done .fx-edge-base {
  stroke: var(--fx-flow);
  stroke-width: 1.75px;
  stroke-dasharray: 0.1 4.5;
  stroke-linecap: round;
}

.fx-edge.is-feedback.is-done .fx-edge-base {
  stroke: var(--fx-feedback);
}

.fx-edge.is-active .fx-edge-head,
.fx-edge.is-done .fx-edge-head {
  fill: var(--fx-flow);
}

.fx-edge.is-feedback.is-active .fx-edge-head,
.fx-edge.is-feedback.is-done .fx-edge-head {
  fill: var(--fx-feedback);
}

/* Active: full accent outline, light top face, glow on the floor, accent title. Done: a thin outline in the softer accent. */
.fx-node.is-active :is(.fx-node-box, .fx-node-top) {
  stroke: var(--fx-flow);
  stroke-width: 2px;
}

.fx-node.is-done :is(.fx-node-box, .fx-node-top) {
  stroke: var(--fx-done);
  stroke-width: 1.25px;
}

.fx-node.is-active .fx-node-top {
  fill: var(--fx-top-on);
}

/* Keyboard focus beats every state. */
.fx-node:focus-visible :is(.fx-node-box, .fx-node-top) {
  stroke: var(--tile-accent);
  stroke-width: 3px;
}

/* A view dims what does not belong to it. Lines, faces and icons fade; the text keeps its colour. */
.fx[data-view='pipeline'] .fx-node:not([data-views~='pipeline']) :is(.fx-node-box, .fx-node-top, .fx-node-side, .fx-node-rim, .fx-icon, .fx-shadow),
.fx[data-view='pipeline'] .fx-edge:not([data-views~='pipeline']) :is(.fx-edge-base, .fx-edge-head, .fx-edge-dot),
.fx[data-view='cycle'] .fx-node:not([data-views~='cycle']) :is(.fx-node-box, .fx-node-top, .fx-node-side, .fx-node-rim, .fx-icon, .fx-shadow),
.fx[data-view='cycle'] .fx-edge:not([data-views~='cycle']) :is(.fx-edge-base, .fx-edge-head, .fx-edge-dot) {
  opacity: 0.35;
}

/* The panel of the narrow layout: the script fills it with the stage number, name and text of the current step and
   with one pixel block per step. It mirrors the live region for the eye (aria-hidden), and it replaces the tooltips,
   which the narrow drawing hides. Without text it is hidden, and in the wide layout the tooltips do its job. */
.fx-panel {
  display: none;
}

/* The panel is a card in the colours of a body: a light face with an accent edge on top, the same monospace labels as
   the drawing, and a progress bar across its full width. In the cycle view its accent is the thread's. */
@container study (not (min-width: 60rem)) {
  .fx-panel:has(.fx-panel-name:not(:empty)) {
    display: block;
    padding: 0.625rem 1rem 0.75rem;
    border: 1px solid var(--fx-band-line);
    border-top: 2px solid var(--fx-accent);
    border-radius: 0.5rem;
    background: var(--fx-node);
    box-shadow: 0 5px 0 color-mix(in srgb, var(--fx-shade) 10%, transparent);
  }

  /* The narrow drawing is taller than the screen, so the panel would lie far below the active node. On tall screens
     it sticks to the bottom of the viewport while the diagram is on screen (factory.js keeps the hero and its node
     above it). For a step whose hero and node would not fit above it, factory.js sets is-unpinned: the panel then
     lies in the page below the drawing. The tile clips instead of hiding its overflow, so sticky works. */
  @media (min-height: 44rem) {
    .fx-panel:has(.fx-panel-name:not(:empty)):not(.is-unpinned) {
      position: sticky;
      bottom: 0.5rem;
      z-index: 2;
    }
  }
}

.fx[data-view='cycle'] .fx-panel {
  --fx-accent: var(--fx-feedback);
}

.fx-panel p {
  margin: 0;
}

/* The count, then a hairline to the right edge: the head row of the card. */
.fx-panel-count {
  display: flex;
  gap: 0.625rem;
  align-items: center;
  color: var(--fx-accent);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.fx-panel-count::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: var(--fx-band-line);
}

.fx-panel-name {
  margin-top: 0.625rem;
  color: var(--fx-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.3;
}

.fx-panel-text {
  margin-top: 0.375rem;
  color: var(--fx-text);
  font-size: 0.9375rem;
  line-height: 1.5;
}

/* Progress: one pixel block per step across the width of the card. Done blocks are filled, the current one is filled,
   taller and carries a light band at its foot. */
.fx-panel-bar {
  display: flex;
  gap: 2px;
  align-items: flex-end;
  height: 0.875rem;
  margin-top: 0.625rem;
}

.fx-panel-bar > * {
  flex: 1 1 0.75rem;
  width: 0.75rem;
  min-width: 0.25rem;
  height: 0.375rem;
  background: color-mix(in srgb, var(--fx-text) 12%, var(--fx-node));
}

.fx-panel-bar > .is-done,
.fx-panel-bar > .is-active {
  background: var(--fx-accent);
}

.fx-panel-bar > .is-active {
  height: 0.875rem;
  box-shadow: inset 0 -3px 0 var(--fx-flow-bg);
}

/* Legend */
.fx-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--tile-muted);
  font-size: 0.875rem;
}

.fx-legend li {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.fx-key {
  flex: none;
  display: block;
}

.fx-key-line {
  stroke-linecap: round;
}

.fx-key-flow .fx-key-line {
  stroke: var(--fx-line);
  stroke-width: 1.75;
}

.fx-key-feedback .fx-key-line {
  stroke: var(--fx-feedback);
  stroke-width: 2.5;
  stroke-dasharray: 0.1 6;
}

/* Step lists: the text alternative of the drawing. The wrapper is .visually-hidden (markup): the
   screen reader reads it, the script reads its data, and it takes no room. Without the script it stays so. */
.fx-flow[hidden] {
  display: none;
}

/* Motion: only with prefers-reduced-motion: no-preference. Without it the diagram never moves. */
@media (prefers-reduced-motion: no-preference) {
  /* Colour and outline of a body ease over about a third of a second; the outline width overshoots a little,
     like a spring. The lift and the hero move by script (their transform), not here. */
  .fx-node-box,
  .fx-node-top,
  .fx-node-side,
  .fx-icon,
  .fx-node-title,
  .fx-glow > *,
  .fx-edge-base,
  .fx-edge-head,
  .fx-panel-bar > * {
    transition:
      fill 0.3s ease,
      stroke 0.3s ease,
      stroke-width 0.4s cubic-bezier(0.34, 1.4, 0.64, 1),
      height 0.4s cubic-bezier(0.34, 1.4, 0.64, 1),
      opacity 0.35s ease;
  }

  /* The flow draws on along the edge; once done it fades into the dotted trail. */
  .fx-edge-flow {
    transition:
      stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1),
      opacity 0.5s ease 0.15s;
  }

  .fx.is-playing .fx-node.is-active .fx-node-box {
    animation: fx-pulse 1s ease-in-out infinite alternate;
  }

  /* The glow of the active body breathes while the flow plays. */
  .fx.is-playing .fx-node.is-active .fx-glow {
    animation: fx-breathe 0.95s ease-in-out infinite alternate;
  }

  @keyframes fx-breathe {
    from {
      opacity: 0.55;
    }
    to {
      opacity: 1;
    }
  }

  /* The thread is alive: its dots creep from Lessons back up, only while the diagram is on screen (is-onscreen, set on
     the stage by the frame loop of the script). In the cycle view it runs faster. */
  .fx-stage.is-onscreen .fx-edge.is-feedback .fx-edge-base {
    animation: fx-march 1s linear infinite;
  }

  .fx[data-view='cycle'] .fx-stage.is-onscreen .fx-edge.is-feedback .fx-edge-base {
    animation: fx-march 0.6s linear infinite;
  }

  @keyframes fx-march {
    to {
      stroke-dashoffset: -6.1;
    }
  }

  /* The tooltip rises in when it changes. */
  .fx-enhanced .fx-tip.is-shown {
    animation: fx-tip-in 0.28s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  }

  @keyframes fx-tip-in {
    from {
      opacity: 0;
      transform: translateY(6px);
    }
  }

  /* The block of the current step pops up in the panel bar (each step makes new blocks). */
  .fx-panel-bar > .is-active {
    animation: fx-cell-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  }

  @keyframes fx-cell-in {
    from {
      height: 0.375rem;
    }
  }

  @keyframes fx-pulse {
    from {
      stroke-width: 1.75px;
    }
    to {
      stroke-width: 3px;
    }
  }
}

/* Forced colours: the system colours paint the diagram. The feedback loop keeps its dotted
   pattern, so nothing rests on a colour. */
@media (forced-colors: active) {
  .fx-svg,
  .fx-key {
    forced-color-adjust: none;
  }

  .fx-band-box {
    fill: Canvas;
    stroke: CanvasText;
  }

  .fx-band-label,
  .fx-node-title,
  .fx-note {
    fill: CanvasText;
  }

  .fx-band-label {
    stroke: Canvas;
  }

  /* The faces keep their shape through the outlines: the top is outlined, the side only by the box. */
  .fx-node-box,
  .fx-node-top,
  .fx-node-side {
    fill: Canvas;
    stroke: CanvasText;
  }

  .fx-node-side {
    stroke: none;
  }

  .fx-node-side-r {
    fill: Canvas;
  }

  /* The dot floor, the shadows and the rims are decoration, and dots behind the titles hurt reading: they go. */
  .fx-floor {
    display: none;
  }

  .fx-shadow,
  .fx-node-rim,
  .fx-tip-shadow {
    display: none;
  }

  .fx-leader {
    stroke: CanvasText;
  }

  /* The active body gets a ring on the floor: a solid one and a dotted one in Highlight. */
  .fx-glow > * {
    fill: none;
    stroke: Highlight;
    stroke-width: 2px;
    vector-effect: non-scaling-stroke;
  }

  .fx-glow > :last-child {
    stroke-dasharray: 2 4;
  }

  .fx-node.is-active .fx-glow > * {
    stroke: Highlight;
    opacity: 1;
  }

  .fx-node.is-active .fx-node-title {
    fill: CanvasText;
  }

  .px-a,
  .px-d,
  .px-hair,
  .px-pants,
  .px-shoe {
    fill: CanvasText;
  }

  .px-b,
  .px-shirt {
    fill: Highlight;
  }

  .px-c {
    fill: LinkText;
  }

  .px-skin {
    fill: GrayText;
  }

  .px-eye {
    fill: Canvas;
  }

  .fx-packet > * {
    fill: Canvas;
    stroke: Highlight;
    stroke-width: 2px;
  }

  .fx-panel {
    border-color: CanvasText;
    background: Canvas;
  }

  .fx-panel-count::after {
    background: CanvasText;
  }

  .fx-panel-count,
  .fx-panel-name,
  .fx-panel-text {
    color: CanvasText;
  }

  .fx-panel-bar > * {
    border: 1px solid CanvasText;
    background: Canvas;
  }

  .fx-panel-bar > .is-done,
  .fx-panel-bar > .is-active {
    background: Highlight;
  }

  .fx-edge-base,
  .fx-edge.is-feedback .fx-edge-base,
  .fx-key-line {
    stroke: CanvasText;
  }

  .fx-edge-head,
  .fx-edge.is-feedback .fx-edge-head,
  .fx-edge-dot {
    fill: CanvasText;
  }

  .fx-edge-flow,
  .fx-edge.is-feedback .fx-edge-flow {
    stroke: Highlight;
  }

  .fx-edge.is-done .fx-edge-base,
  .fx-edge.is-feedback.is-done .fx-edge-base {
    stroke: Highlight;
  }

  .fx-tip-box,
  .fx-tip-arrow {
    fill: Canvas;
    stroke: Highlight;
    stroke-width: 3;
  }

  .fx-tip-text {
    fill: CanvasText;
  }

  .fx-edge.is-active .fx-edge-head,
  .fx-edge.is-done .fx-edge-head,
  .fx-edge.is-feedback.is-active .fx-edge-head,
  .fx-edge.is-feedback.is-done .fx-edge-head {
    fill: Highlight;
  }

  /* Without colour: a lit body is told by its outline width (1, 2 and 3 px), and the active one by a Highlight top. */
  .fx-node.is-active :is(.fx-node-box, .fx-node-top),
  .fx-node.is-done :is(.fx-node-box, .fx-node-top) {
    stroke: Highlight;
  }

  .fx-node.is-done :is(.fx-node-box, .fx-node-top) {
    stroke-width: 2px;
  }

  .fx-node.is-active :is(.fx-node-box, .fx-node-top) {
    stroke-width: 3.5px;
  }

  .fx-node.is-active .fx-node-top {
    fill: Canvas;
  }

  .fx-node:focus-visible :is(.fx-node-box, .fx-node-top) {
    stroke: LinkText;
    stroke-width: 5px;
  }

  .fx .study-tab[aria-selected='true'] {
    forced-color-adjust: none;
    border-color: ButtonText;
    background: Highlight;
    color: HighlightText;
  }

  .fx-btn,
  .study-figma-frame {
    border-color: CanvasText;
  }

  /* The sample card keeps its shapes: borders in system colours, state names stay text. */
  .ds-board,
  .ds-btn,
  .ds-badge,
  .ds-field-box,
  .ds-alert {
    border-color: CanvasText;
  }

  .ds-btn-primary {
    border-width: 3px;
  }

  .ai-badge {
    border-color: CanvasText;
  }
}

/* About me view (aboutView() in src/classic/template.ts, src/classic/about.js): a hero with
   the image slot, the testimonials and the design roadmap. Light page, dark tiles and pills,
   colours only from the theme variables. Every animation and every transition of this region
   sits in its own prefers-reduced-motion block at the end; without it the view is static and
   fully visible. */
.about {
  --about-line: color-mix(in srgb, var(--fg) 24%, transparent);
  /* Roadmap: length of a connector between the node and the chips, width of a node, height
     of a chip and the x position of the line on narrow screens. */
  --rm-gap: 2.75rem;
  --rm-node-w: 13.5rem;
  --rm-chip-h: 2.125rem;
  --rm-x: 0.875rem;
  overflow-x: clip;
}

:root[data-theme='contrast-yellow'] .about {
  --about-line: var(--fg);
}

/* Hero: text left, image slot right */
.about-hero {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  align-items: center;
  gap: clamp(2rem, 6vw, 6rem);
  max-width: 72rem;
  margin: 0 auto;
  padding-top: clamp(0.5rem, 3vw, 3rem);
}

.about-copy {
  min-width: 0;
}

/* The shared tile title on the light page: muted text, the pixel icon in the accent colour */
.about-kicker {
  color: var(--muted);
}

.about-kicker .tile-icon {
  color: var(--accent);
}

.about-headline {
  margin: 1rem 0 1.5rem;
  font-size: clamp(2.5rem, 7vw, 5.25rem);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.04em;
  text-wrap: balance;
}

/* Each word is a box of its own, so it can rise on its own (motion block). */
.about-word {
  display: inline-block;
}

.about-intro {
  display: grid;
  gap: 1rem;
  max-width: 38rem;
}

.about-intro p {
  margin: 0;
  color: var(--muted);
  font-size: 1.0625rem;
}

.about-intro .lead {
  color: var(--fg);
  font-size: 1.25rem;
  line-height: 1.5;
}

.about-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 2rem;
}

.hire-me,
.about-linkedin {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  min-height: 3rem;
  padding: 0.75rem 1.375rem;
  border: 1px solid var(--tile-line);
  border-radius: 999px;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
}

/* Dark pill like the top bar. The shine band waits left of the button (motion block). */
.hire-me {
  overflow: hidden;
  isolation: isolate;
  background: var(--tile-bg);
  color: var(--tile-fg);
}

.hire-me::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(105deg, transparent 30%, color-mix(in srgb, var(--tile-fg) 22%, transparent) 50%, transparent 70%);
  transform: translateX(-120%);
}

.hire-me:hover {
  background: color-mix(in srgb, var(--tile-bg) 82%, var(--tile-fg));
}

.about-linkedin {
  border-color: var(--about-line);
  background: var(--bg);
  color: var(--fg);
}

.about-linkedin:hover {
  border-color: var(--fg);
}

.hire-me .tile-icon,
.about-linkedin .tile-icon {
  color: currentColor;
}

.hire-me:focus-visible,
.about-linkedin:focus-visible {
  border-radius: 999px;
}

/* Image slot: a 4:5 frame on the portrait field with a hard pixel shadow and a pixel sparkle at
   the corner. The pixel figure shows whole (contain), with room around it. */
.about-photo {
  position: relative;
  min-width: 0;
}

.about-photo-frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  border: 1px solid var(--tile-line);
  border-radius: var(--tile-radius);
  padding: 1.5rem;
  box-sizing: border-box;
  background: var(--portrait-bg);
  box-shadow: 0.625rem 0.625rem 0 0 color-mix(in srgb, var(--accent) 22%, transparent);
}

.about-photo-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
}

/* A real photo (square) fills the frame edge to edge, smooth instead of pixelated. */
.about-photo-frame-photo {
  aspect-ratio: 1 / 1;
  padding: 0;
}

.about-photo-frame-photo .about-photo-img {
  object-fit: cover;
  image-rendering: auto;
}

@media (forced-colors: active) {
  .about-photo-img {
    filter: drop-shadow(1px 0 0 CanvasText) drop-shadow(-1px 0 0 CanvasText) drop-shadow(0 -1px 0 CanvasText);
  }
}

.about-photo::after {
  content: '';
  position: absolute;
  top: -0.5rem;
  right: -0.5rem;
  width: 6px;
  height: 6px;
  background: var(--accent);
  box-shadow:
    6px 0 var(--accent),
    -6px 0 var(--accent),
    0 6px var(--accent),
    0 -6px var(--accent);
}

/* Blocks below the hero: a centred head, then the content */
.about-block {
  max-width: 72rem;
  margin: clamp(5rem, 12vw, 9rem) auto 0;
}

.about-block-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
  text-align: center;
}

.about-block-head .tile-label {
  align-self: center;
}

.about-section-title {
  margin: 0.75rem 0 0;
  font-size: clamp(1.875rem, 4.5vw, 3rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

.about-section-sub {
  max-width: 34rem;
  margin: 0.875rem 0 0;
  color: var(--muted);
}

/* Testimonials: two dark tiles in a row */
.quote-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.quote-item {
  display: flex;
  min-width: 0;
}

.quote-card {
  flex: 1;
  gap: 1.25rem;
  min-height: 17rem;
  margin: 0;
  padding: clamp(1.25rem, 2.2vw, 2rem);
}

.quote-text {
  flex: 1;
  margin: 0;
}

.quote-text p {
  margin: 0;
  color: var(--tile-fg);
  font-size: 1.125rem;
  line-height: 1.55;
}

.quote-by {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid color-mix(in srgb, var(--tile-fg) 14%, transparent);
}

.quote-img {
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  object-fit: cover;
  background: var(--portrait-bg);
}

.quote-who {
  display: grid;
  min-width: 0;
}

.quote-name {
  color: var(--tile-fg);
  font-weight: 600;
}

.quote-role {
  color: var(--tile-muted);
  font-size: 0.875rem;
}

.quote-source {
  margin-top: 0.25rem;
  color: var(--tile-muted);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
}

/* Design roadmap: one vertical line per phase, dark nodes on the line, outlined chips left
   and right, joined by thin connectors. Without about.js each definition shows as small text
   below its term; with it the definition opens as a bubble. */
.roadmap {
  max-width: 64rem;
}

.rm-phases,
.rm-nodes,
.rm-side {
  margin: 0;
  padding: 0;
  list-style: none;
}

.rm-phases {
  display: grid;
  gap: clamp(3rem, 7vw, 5rem);
}

.rm-phase {
  min-width: 0;
}

.rm-phase-title {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
  font-size: clamp(1.25rem, 2.5vw, 1.625rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  text-align: center;
}

/* Phase number with a pixel square; the phase in the middle of the screen takes the accent. */
.rm-phase-num {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.25rem 0.625rem;
  border: 1px solid var(--about-line);
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.rm-phase-num::before {
  content: '';
  width: 6px;
  height: 6px;
  background: currentColor;
}

.rm-phase.is-current .rm-phase-num {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--bg);
}

/* The line: a quiet base line, and an accent line on top that draws itself (motion block). */
.rm-nodes {
  position: relative;
  display: grid;
  gap: 1.75rem;
  padding-block: 1.5rem;
}

.rm-nodes::before,
.rm-nodes::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  margin-left: -1px;
  background: var(--about-line);
}

.rm-nodes::after {
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: top;
}

/* A row: left chips, node, right chips. The node comes first in the markup. */
.rm-row {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--rm-node-w) minmax(0, 1fr);
  align-items: center;
}

.rm-item {
  position: relative;
}

.rm-item.is-open {
  z-index: 7;
}

.rm-node-item {
  z-index: 1;
  grid-column: 2;
  grid-row: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.rm-term {
  margin: 0;
  font-family: inherit;
  text-align: left;
}

button.rm-term {
  cursor: pointer;
}

.rm-node {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  min-height: 2.75rem;
  padding: 0.625rem 1rem;
  border: 1px solid var(--tile-line);
  border-radius: 0.75rem;
  background: var(--tile-bg);
  color: var(--tile-fg);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.25;
  text-align: center;
}

/* Pixel dot in front of the node label */
.rm-node::before {
  content: '';
  flex: none;
  width: 6px;
  height: 6px;
  background: var(--tile-accent);
}

.rm-node:hover,
.rm-item.is-open > .rm-node {
  background: color-mix(in srgb, var(--tile-bg) 80%, var(--tile-fg));
}

.rm-side {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 0;
}

.rm-left {
  grid-column: 1;
  grid-row: 1;
  align-items: flex-end;
  padding-right: var(--rm-gap);
}

.rm-right {
  grid-column: 3;
  grid-row: 1;
  align-items: flex-start;
  padding-left: var(--rm-gap);
}

.rm-chip-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  max-width: 100%;
}

.rm-left .rm-chip-item {
  align-items: flex-end;
}

.rm-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  min-height: var(--rm-chip-h);
  padding: 0.375rem 0.875rem;
  border: 1px solid var(--about-line);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.25;
}

.rm-chip:hover,
.rm-item.is-open > .rm-chip {
  border-color: var(--accent);
}

/* Connectors: node to the spine of its side, a spine through the chip centres, one stub per chip */
.rm-side::before,
.rm-chip::before,
.rm-chip-item::after {
  content: '';
  position: absolute;
  background: var(--about-line);
}

.rm-side::before {
  top: 50%;
  width: calc(var(--rm-gap) / 2);
  height: 1px;
}

.rm-right::before {
  left: 0;
  transform-origin: left;
}

.rm-left::before {
  right: 0;
  transform-origin: right;
}

.rm-chip::before {
  top: 50%;
  width: calc(var(--rm-gap) / 2);
  height: 1px;
}

.rm-right .rm-chip::before {
  right: 100%;
  transform-origin: left;
}

.rm-left .rm-chip::before {
  left: 100%;
  transform-origin: right;
}

/* The spine runs from the first chip centre to the last; each chip covers its share and half
   of the gap to its neighbours. A single chip needs none. */
.rm-chip-item::after {
  top: -0.25rem;
  bottom: -0.25rem;
  width: 1px;
  transform-origin: top;
}

.rm-right .rm-chip-item::after {
  left: calc(var(--rm-gap) / -2);
}

.rm-left .rm-chip-item::after {
  right: calc(var(--rm-gap) / -2);
}

.rm-chip-item:first-child::after {
  top: calc(var(--rm-chip-h) / 2);
}

.rm-chip-item:last-child::after {
  bottom: auto;
  height: calc(var(--rm-chip-h) / 2 + 0.25rem);
}

.rm-chip-item:first-child:last-child::after {
  display: none;
}

/* Hover, focus or an open definition light up the connectors of the term. */
.rm-chip:hover::before,
.rm-item.is-open > .rm-chip::before,
.rm-side:has(.rm-chip:hover)::before,
.rm-side:has(.is-open)::before,
.rm-row:has(.rm-node:hover) .rm-side::before,
.rm-row:has(.rm-node-item.is-open) .rm-side::before {
  background: var(--accent);
}

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

.rm-node:focus-visible {
  border-radius: 0.75rem;
}

.rm-chip:focus-visible {
  border-radius: 999px;
}

/* Definition: small text below the term without about.js, a bubble with it. */
.rm-def {
  display: block;
  max-width: 18rem;
  margin-top: 0.375rem;
  color: var(--muted);
  font-size: 0.8125rem;
  line-height: 1.4;
}

.rm-def[hidden] {
  display: none;
}

.rm-node-item .rm-def {
  position: relative;
  padding: 0.25rem 0.5rem;
  background: var(--bg);
  text-align: center;
}

.rm-left .rm-def {
  text-align: right;
}

.rm-enhanced .rm-def {
  position: absolute;
  top: calc(100% + 0.5rem);
  z-index: 6;
  width: max-content;
  max-width: min(18rem, calc(100vw - 2 * var(--gutter) - 2.5rem));
  margin: 0;
  padding: 0.625rem 0.875rem;
  border: 1px solid color-mix(in srgb, var(--tile-fg) 24%, var(--tile-bg));
  border-radius: 0.75rem;
  background: var(--tile-bg);
  color: var(--tile-fg);
  box-shadow: 0 10px 30px color-mix(in srgb, var(--fg) 22%, transparent);
  text-align: left;
  pointer-events: none;
}

/* Node bubbles sit centred below the node; chip bubbles open towards the line, so they stay
   on screen. */
.rm-enhanced .rm-node-item .rm-def {
  left: 50%;
  translate: -50% 0;
}

.rm-enhanced .rm-left .rm-def {
  left: 0;
}

.rm-enhanced .rm-right .rm-def {
  right: 0;
}

/* Breakpoints of the view */
@media (max-width: 960px) {
  .quote-list {
    grid-template-columns: minmax(0, 1fr);
  }

  .quote-card {
    min-height: 0;
  }
}

/* Narrow screens: the hero stacks, and the roadmap becomes one column with the line on the
   left. The node sits on the line, its chips follow below it, each with a stub to the line. */
@media (max-width: 700px) {
  .about-hero {
    grid-template-columns: minmax(0, 1fr);
  }

  .about-photo {
    width: min(100%, 20rem);
  }

  .rm-phase-title {
    align-items: flex-start;
    text-align: left;
  }

  .rm-nodes::before,
  .rm-nodes::after {
    left: var(--rm-x);
  }

  .rm-row {
    display: block;
  }

  .rm-node-item {
    align-items: flex-start;
  }

  .rm-node {
    justify-content: flex-start;
    width: auto;
    text-align: left;
  }

  .rm-node-item .rm-def,
  .rm-left .rm-def {
    text-align: left;
  }

  .rm-side,
  .rm-left,
  .rm-right {
    align-items: flex-start;
    margin-top: 0.5rem;
    padding: 0 0 0 calc(var(--rm-x) + 1.5rem);
  }

  .rm-left .rm-chip-item {
    align-items: flex-start;
  }

  .rm-side::before,
  .rm-chip-item::after {
    display: none;
  }

  .rm-right .rm-chip::before,
  .rm-left .rm-chip::before {
    right: 100%;
    left: auto;
    width: 1.5rem;
    transform-origin: left;
  }

  .rm-enhanced .rm-node-item .rm-def,
  .rm-enhanced .rm-left .rm-def,
  .rm-enhanced .rm-right .rm-def {
    right: auto;
    left: 0;
    translate: none;
  }
}

/* With large text the breakpoints of the view move to 1.5 times their width. */
@media (max-width: 1050px) {
  :root[data-theme='large-text'] .rm-phase-title {
    align-items: flex-start;
    text-align: left;
  }

  :root[data-theme='large-text'] .about-hero {
    grid-template-columns: minmax(0, 1fr);
  }

  :root[data-theme='large-text'] .about-photo {
    width: min(100%, 20rem);
  }

  :root[data-theme='large-text'] .rm-nodes::before,
  :root[data-theme='large-text'] .rm-nodes::after {
    left: var(--rm-x);
  }

  :root[data-theme='large-text'] .rm-row {
    display: block;
  }

  :root[data-theme='large-text'] .rm-node-item {
    align-items: flex-start;
  }

  :root[data-theme='large-text'] .rm-node {
    justify-content: flex-start;
    width: auto;
    text-align: left;
  }

  :root[data-theme='large-text'] :is(.rm-node-item .rm-def, .rm-left .rm-def) {
    text-align: left;
  }

  :root[data-theme='large-text'] :is(.rm-side, .rm-left, .rm-right) {
    align-items: flex-start;
    margin-top: 0.5rem;
    padding: 0 0 0 calc(var(--rm-x) + 1.5rem);
  }

  :root[data-theme='large-text'] .rm-left .rm-chip-item {
    align-items: flex-start;
  }

  :root[data-theme='large-text'] .rm-side::before,
  :root[data-theme='large-text'] .rm-chip-item::after {
    display: none;
  }

  :root[data-theme='large-text'] .rm-side .rm-chip::before {
    right: 100%;
    left: auto;
    width: 1.5rem;
    transform-origin: left;
  }

  :root[data-theme='large-text'] .rm-enhanced :is(.rm-node-item, .rm-left, .rm-right) .rm-def {
    right: auto;
    left: 0;
    translate: none;
  }
}

@media (max-width: 1440px) {
  :root[data-theme='large-text'] .quote-list {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 480px) {
  :root[data-theme='large-text'] :is(.about-headline, .about-section-title, .rm-phase-name) {
    -webkit-hyphens: auto;
    hyphens: auto;
    overflow-wrap: anywhere;
  }

  :root[data-theme='large-text'] :is(.hire-me, .about-linkedin) {
    padding-inline: 1rem;
  }
}

/* Forced colours: system colours for the lines, nodes, chips and bubbles; no decoration. */
@media (forced-colors: active) {
  .rm-nodes::before,
  .rm-side::before,
  .rm-chip::before,
  .rm-chip-item::after {
    background: CanvasText;
  }

  .rm-nodes::after,
  .about-photo::after,
  .hire-me::after {
    display: none;
  }

  .rm-node,
  .rm-chip {
    border: 1px solid ButtonText;
    background: ButtonFace;
    color: ButtonText;
  }

  .rm-node::before {
    background: ButtonText;
  }

  .rm-term:hover,
  .rm-item.is-open > .rm-term {
    outline: 2px solid Highlight;
  }

  .rm-def {
    color: CanvasText;
  }

  .rm-enhanced .rm-def {
    border: 1px solid CanvasText;
    background: Canvas;
    box-shadow: none;
  }

  .rm-phase-num {
    border-color: CanvasText;
  }

  .rm-phase.is-current .rm-phase-num {
    border-color: Highlight;
    background: Highlight;
    color: HighlightText;
  }

  .about-photo-frame {
    border-color: CanvasText;
    box-shadow: none;
  }

  .hire-me,
  .about-linkedin {
    border-color: LinkText;
  }
}

/* Motion of the About me view, only for people who did not ask to reduce it. The hero plays
   with CSS alone each time the view shows. The blocks below wait hidden only while <html> has
   the class "js" (about.js, after its observer runs) and appear when they get "is-in". */
@media (prefers-reduced-motion: no-preference) {
  /* Hero: the words rise one after the other, then the intro, the buttons and the image. */
  .about-word {
    animation: about-rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }

  .about-word:nth-child(2) { animation-delay: 0.07s; }
  .about-word:nth-child(3) { animation-delay: 0.14s; }
  .about-word:nth-child(4) { animation-delay: 0.21s; }
  .about-word:nth-child(5) { animation-delay: 0.28s; }
  .about-word:nth-child(6) { animation-delay: 0.35s; }
  .about-word:nth-child(7) { animation-delay: 0.42s; }
  .about-word:nth-child(n + 8) { animation-delay: 0.49s; }

  .about-kicker,
  .about-intro,
  .about-actions {
    animation: about-rise 0.6s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }

  .about-intro {
    animation-delay: 0.3s;
  }

  .about-actions {
    animation-delay: 0.42s;
  }

  .about-photo {
    animation: about-photo-in 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) 0.15s both;
  }

  /* The pixel sparkle at the corner of the image blinks now and then, in hard steps. */
  .about-photo::after {
    animation: about-sparkle 3.2s steps(1, end) 1.2s infinite;
  }

  /* Hire me: a shine band runs across, the pixel arrow nudges forward. */
  .hire-me,
  .about-linkedin {
    transition: transform 0.2s ease;
  }

  .hire-me:hover,
  .about-linkedin:hover {
    transform: translateY(-2px);
  }

  .hire-me::after {
    transition: transform 0.8s cubic-bezier(0.2, 0.7, 0.2, 1);
  }

  .hire-me:is(:hover, :focus-visible)::after {
    transform: translateX(120%);
  }

  .hire-me .tile-icon,
  .about-linkedin .tile-icon {
    transition: transform 0.24s steps(3, end);
  }

  .hire-me:is(:hover, :focus-visible) .tile-icon {
    transform: translateX(3px);
  }

  .about-linkedin:is(:hover, :focus-visible) .tile-icon {
    transform: translate(2px, -2px);
  }

  /* Blocks below the hero appear as they enter the screen. */
  .js .about-block-head:not(.is-in) {
    opacity: 0;
    transform: translateY(1rem);
  }

  .about-block-head {
    transition:
      opacity 0.6s ease,
      transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
  }

  /* Testimonials: a calm stagger, then a small lift on hover. */
  .js .quote-item.reveal:not(.is-in) {
    opacity: 0;
    transform: translateY(1.5rem);
  }

  .quote-item {
    transition:
      opacity 0.6s ease,
      transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
  }

  .quote-item:nth-child(2) { transition-delay: 0.1s; }
  .quote-item:nth-child(3) { transition-delay: 0.2s; }

  .quote-card {
    transition: translate 0.3s ease;
  }

  .quote-card:hover {
    translate: 0 -4px;
  }

  /* Roadmap: the phase title rises, the accent line draws down the phase. */
  .js .rm-phase.reveal:not(.is-in) .rm-phase-title {
    opacity: 0;
    transform: translateY(0.75rem);
  }

  .rm-phase-title {
    transition:
      opacity 0.5s ease,
      transform 0.5s cubic-bezier(0.2, 0.7, 0.2, 1);
  }

  .rm-phase.is-in .rm-nodes::after {
    transform: scaleY(1);
    transition: transform 1.6s cubic-bezier(0.4, 0, 0.2, 1) 0.2s;
  }

  /* Each row: the node pops in by pixel steps, the connectors grow, then the chips slide in
     from the line, one after the other. */
  .js .rm-row.reveal:not(.is-in) .rm-node-item {
    opacity: 0;
    transform: scale(0.6);
  }

  .js .rm-row.reveal:not(.is-in) .rm-chip-item {
    opacity: 0;
  }

  .js .rm-row.reveal:not(.is-in) .rm-left .rm-chip-item {
    transform: translateX(1rem);
  }

  .js .rm-row.reveal:not(.is-in) .rm-right .rm-chip-item {
    transform: translateX(-1rem);
  }

  .js .rm-row.reveal:not(.is-in) .rm-side::before {
    transform: scaleX(0);
  }

  .js .rm-row.reveal:not(.is-in) .rm-chip-item::after {
    transform: scaleY(0);
  }

  .rm-node-item {
    transition:
      opacity 0.32s steps(4, end),
      transform 0.32s steps(4, end);
  }

  .rm-side::before {
    transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1) 0.18s;
  }

  .rm-chip-item {
    transition:
      opacity 0.45s ease 0.28s,
      transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) 0.28s;
  }

  .rm-chip-item:nth-child(2) { transition-delay: 0.36s; }
  .rm-chip-item:nth-child(3) { transition-delay: 0.44s; }
  .rm-chip-item:nth-child(4) { transition-delay: 0.52s; }

  .rm-chip-item::after {
    transition: transform 0.35s ease 0.24s;
  }

  /* Hover and focus: the term lifts; an opening bubble drops in. */
  .rm-node,
  .rm-chip {
    transition: transform 0.2s ease;
  }

  .rm-node:hover,
  .rm-chip:hover,
  .rm-item.is-open > .rm-term {
    transform: translateY(-2px);
  }

  .rm-enhanced .rm-def:not([hidden]) {
    animation: rm-pop 0.18s ease-out both;
  }

  /* Where the browser has scroll-driven animations, the accent line follows the scroll: its
     tip stays at about two thirds of the screen height. The image drifts a little. */
  @supports (animation-timeline: view()) {
    .rm-nodes {
      view-timeline: --rm-line block;
    }

    .rm-nodes::after,
    .rm-phase.is-in .rm-nodes::after {
      animation-name: rm-draw;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: --rm-line;
      animation-range: cover 35vh cover calc(100% - 65vh);
      transition: none;
    }

    .about-photo-img {
      animation-name: about-drift;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: cover;
    }
  }

  @keyframes about-rise {
    from {
      opacity: 0;
      transform: translateY(0.6em);
    }
  }

  @keyframes about-photo-in {
    from {
      opacity: 0;
      transform: translateY(1.5rem) scale(0.96);
    }
  }

  @keyframes about-sparkle {
    0%,
    100% {
      opacity: 1;
      transform: scale(1);
    }
    6% {
      opacity: 0.4;
      transform: scale(0.5);
    }
    12% {
      opacity: 1;
      transform: scale(1);
    }
  }

  @keyframes about-drift {
    from {
      transform: translateY(-2%);
    }
    to {
      transform: translateY(2%);
    }
  }

  @keyframes rm-draw {
    from {
      transform: scaleY(0);
    }
    to {
      transform: scaleY(1);
    }
  }

  @keyframes rm-pop {
    from {
      opacity: 0;
      transform: translateY(-4px);
    }
  }
}

/* ==========================================================================================
   Case study "Ladival" (template.ts, sliderBlock.ts, slider.js): the carousel of the test
   screens and the full-page designs in scroll boxes. Own section; the rules of the other
   studies stay untouched. Sizes are in rem, so the large-text theme scales them by itself.
   ========================================================================================== */

/* Carousel. Without JavaScript the track is a row that scrolls sideways and snaps to each
   slide; the track takes focus, so the keyboard can scroll it. slider.js adds the controls
   below the track and scrolls the track to the chosen slide. One slide is as wide as the track.
   The carousel has no width of its own: it fills the same grid column as the flowchart frame of the
   process block above (the rule of .study-process-body at 70rem), at the same left edge. */
.study-slider {
  display: grid;
  row-gap: 1.25rem;
  align-content: start;
  padding-block: 1.75rem;
}

.study-slider > * {
  grid-column: 1;
  min-width: 0;
  margin: 0;
}

.slider {
  display: grid;
  gap: 1rem;
  min-width: 0;
}

.slider-track {
  display: flex;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
  border: 1px solid var(--study-tile-border);
  border-radius: var(--tile-radius);
  background: var(--study-tile-bg);
  color: var(--study-tile-fg);
}

.slider-track:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

.slider-slide {
  display: grid;
  flex: 0 0 100%;
  align-content: start;
  min-width: 0;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

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

.slider-caption {
  margin: 0;
  padding: 0.875rem 1.25rem;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.3;
  text-transform: uppercase;
}

/* Controls: previous, dots, next, then pause and the status text. They wrap on narrow screens. */
.slider-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 0.5rem 0.75rem;
}

.slider-btn {
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0.25rem 0.875rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
}

.slider-btn:hover {
  border-color: var(--fg);
}

.slider-btn:focus-visible,
.slider-dot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.slider-dots {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  margin: 0;
  padding: 0;
  list-style: none;
}

.slider-dot {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}

.slider-dot::before {
  width: 0.75rem;
  height: 0.75rem;
  border: 2px solid var(--fg);
  border-radius: 50%;
  background: transparent;
  content: '';
}

.slider-dot[aria-current='true']::before {
  background: var(--fg);
}

.slider-status {
  flex-basis: 100%;
  margin: 0;
  color: var(--muted);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  text-align: left;
}

@media (forced-colors: active) {
  .slider-track,
  .slider-btn,
  .pagescreen-img {
    border-color: CanvasText;
  }

  .slider-dot::before {
    border-color: ButtonText;
  }

  .slider-dot[aria-current='true']::before {
    border-color: Highlight;
    background: Highlight;
  }
}

/* Full-page designs: shown in full, without a scroll box. Up to 60rem the designs stand one
   below the other; from 60rem side by side. */
.study-pagescreens {
  display: grid;
  gap: 1.25rem;
  align-content: start;
  padding-block: 1.75rem;
}

.study-pagescreens > * {
  min-width: 0;
  margin: 0;
}

.pagescreens-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem 1.5rem;
  align-items: start;
}

.pagescreen {
  display: grid;
  gap: 0.75rem;
  min-width: 0;
}

.pagescreen-label {
  margin: 0;
  color: var(--fg);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.3;
  text-transform: uppercase;
}

.pagescreen-img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--study-tile-border);
  border-radius: var(--tile-radius);
}

@container study (min-width: 60rem) {
  .pagescreens-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ==========================================================================================
   Case study "vectorstream" (featureRowBlock.ts): row of small animated tiles. The animations
   are dark images; under prefers-reduced-motion the picture element swaps in a still frame.
   Colours come from the theme variables, so every theme works. Only new rules, appended.
   ========================================================================================== */
.study-features {
  display: grid;
  gap: 1.25rem;
  align-content: start;
  padding-block: 1.75rem;
}

.study-features > * {
  min-width: 0;
  margin: 0;
}

/* The grid wraps by width: four tiles in a row on wide screens, fewer on narrow ones and with
   large text, so a caption never gets squeezed. */
.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  gap: 1.5rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.feature-tile {
  display: grid;
  gap: 0.75rem;
  align-content: start;
  min-width: 0;
  margin: 0;
}

.feature-picture {
  display: block;
  border: 1px solid var(--study-tile-border);
  border-radius: var(--tile-radius);
  overflow: hidden;
}

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

.feature-caption {
  display: grid;
  gap: 0.25rem;
}

.feature-label {
  color: var(--fg);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.3;
  text-transform: uppercase;
}

.feature-text {
  color: var(--muted);
  font-size: 0.9375rem;
  line-height: 1.5;
}

:root[data-theme='contrast-yellow'] .feature-picture {
  border-width: 0.125rem;
  border-color: var(--fg);
}

:root[data-theme='contrast-yellow'] .feature-text {
  color: var(--fg);
}

:root[data-theme='red-green-safe'] .feature-picture {
  border-color: var(--fg);
}

:root[data-theme='large-text'] .feature-grid {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
}

@media (forced-colors: active) {
  .feature-picture {
    forced-color-adjust: none;
    border-color: CanvasText;
  }
}

/* Large text (--font-scale 1.5): the px breakpoints above assume scale 1, so with large text
   the grids collapse at 1.5 times those widths. On very narrow screens long German words in
   headings and labels hyphenate instead of breaking mid-word. */
@media (max-width: 480px) {
  :root[data-theme='large-text'] :is(h1, h2, h3, h4, .eyebrow, .meta, .tags li, .button) {
    -webkit-hyphens: auto;
    hyphens: auto;
  }

  /* Paddings are in rem and grow 1.5 times; give the text the room back. The tree strip
     uses the same variable, so it still reaches the tile edges. */
  :root[data-theme='large-text'] .tile {
    --tile-pad: 0.75rem;
  }

  /* Detail page tiles keep more room: 1rem grows to 24 px with the larger root font. */
  :root[data-theme='large-text'] .study .tile {
    --tile-pad: 1rem;
  }

  :root[data-theme='large-text'] .tile .card {
    padding: 0.75rem 0.5rem;
  }

  :root[data-theme='large-text'] .world-link {
    padding-inline: 0.75rem;
  }

  :root[data-theme='large-text'] .study-tab {
    padding-inline: 0.75rem;
  }

  :root[data-theme='large-text'] :is(.study-value, .study-insight-value, .study-pager-title, .study-stat-label) {
    -webkit-hyphens: auto;
    hyphens: auto;
  }
}

/* Up to 1650 px (1.5 times 1100) the dashboard uses the two-tile rows of tablets. */
@media (max-width: 1650px) {
  :root[data-theme='large-text'] :is(#tile-profile, #tile-years, #tile-claim, #tile-latest) {
    grid-column: span 6;
  }

  :root[data-theme='large-text'] :is(#tile-skills, #tile-experience) {
    grid-column: 1 / -1;
  }

  :root[data-theme='large-text'] .folders {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Up to 840 px every tile takes the full row, as on phones. The profile tile keeps a
   portrait shape, so the image still shows above the text. */
@media (max-width: 840px) {
  :root[data-theme='large-text'] :is(#tile-profile, #tile-years, #tile-skills, #tile-claim, #tile-latest, #tile-experience) {
    grid-column: 1 / -1;
  }

  :root[data-theme='large-text'] #tile-profile {
    min-height: 18rem;
  }

  /* The experience tile keeps its minimum height for the globe. */
  :root[data-theme='large-text'] :is(#tile-years, #tile-claim, #tile-latest) {
    min-height: 0;
  }

  :root[data-theme='large-text'] .skill-list {
    columns: 1;
  }

  :root[data-theme='large-text'] .folders {
    grid-template-columns: minmax(0, 1fr);
    padding: 0;
  }
}

/* Large text, top bar: the four items in one row need about 1180 px at 1.5 times the text
   size. Up to 1200 px (1.5 times 800) the bar uses the two-row layout of narrow screens. The
   mode switch and the tabs wrap instead of running out of their pill on narrow phones. */
:root[data-theme='large-text'] .pill-switch {
  flex-wrap: wrap;
}

@media (max-width: 1200px) {
  :root[data-theme='large-text'] .bar {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      'switch actions'
      'tabs tabs';
  }

  :root[data-theme='large-text'] .pill-switch {
    margin-left: 0;
  }

  :root[data-theme='large-text'] .tabbar {
    justify-self: center;
  }

  /* The radius matches the pill items (half of 2rem, plus the 0.3125rem padding), so one
     row still looks like a pill and two rows get matching corners. */
  :root[data-theme='large-text'] :is(.pill-switch, .tabbar ul) {
    border-radius: calc(1rem + 0.3125rem);
  }

  :root[data-theme='large-text'] .tabbar ul {
    flex-wrap: wrap;
    justify-content: space-between;
  }

  :root[data-theme='large-text'] :is(.tabbar li, .pill-switch .pill-item) {
    flex: 1 0 auto;
  }

  :root[data-theme='large-text'] .tabbar a {
    justify-content: center;
    width: 100%;
  }

  :root[data-theme='large-text'] .pill-switch .pill-item {
    justify-content: center;
  }
}

/* Large text on a phone, as in the game bar (src/entry/menuBar.css): the two rows stay, the mode switch wraps into two lines
   if it must, and the language switch takes the first line over the width of the actions column (see above). Less padding in the
   pill items keeps the switch in one row next to the buttons, and the bar low. */
@media (max-width: 600px) {
  :root[data-theme='large-text'] .pill-switch .pill-item,
  :root[data-theme='large-text'] .tabbar a {
    padding-inline: 0.5rem;
  }

  :root[data-theme='large-text'] .bar {
    min-height: 0;
  }
}

/* The game bar keeps the tabs in one row from 421 px, but they need about 500 px at large text and its last tab is cut off below
   that. Here they wrap into a second row until they fit. */
@media (min-width: 500px) and (max-width: 600px) {
  :root[data-theme='large-text'] .tabbar ul {
    flex-wrap: nowrap;
  }
}

/* With the language switch beside the buttons the switch pill needs 471 px to stay in one row (it wraps into two lines below that). */
@media (min-width: 471px) and (max-width: 600px) {
  :root[data-theme='large-text'] .pill-switch {
    flex-wrap: nowrap;
  }
}

@media (max-width: 420px) {
  :root[data-theme='large-text'] .tabbar a {
    padding-inline: 0.25rem;
  }

  :root[data-theme='large-text'] .tabbar ul {
    gap: 0;
  }
}

/* With large text the same switch is 1.5 times wider: the hint of the English game link stays out of the visible text up to 800 px. */
@media (max-width: 800px) {
  :root[data-theme='large-text'] .pill-hint {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    border: 0;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* The two-row bar is about 8rem high; jump targets stop below it. */
@media (min-width: 601px) and (max-width: 1200px) {
  :root[data-theme='large-text'] :is(.view, .tile) {
    scroll-margin-top: 9rem;
  }
}

/* Height of the top bar, for what sticks below it: two rows up to 1200 px, and on phones the bar
   scrolls away (below). */
@media (max-width: 1200px) {
  :root[data-theme='large-text'] {
    --site-header-h: 7.25rem;
  }
}

@media (max-width: 600px) {
  :root[data-theme='large-text'] {
    --site-header-h: 0px;
  }
}

/* On phones the bar can need two or three rows of tabs. It scrolls away with the page,
   so it never covers a third of the screen or a focused element. */
@media (max-width: 600px) {
  :root[data-theme='large-text'] .site-header {
    position: static;
  }
}

/* Portrait size, generated by portraitCss() in src/classic/template.ts */
:root {
  --portrait-w: 21;
  --portrait-h: 43;
}

/* Accessibility themes, generated from src/a11y/themes.ts */
:root[data-theme="contrast-yellow"] {
  --bg: #000000;
  --fg: #ffe600;
  --muted: #ffe600;
  --surface: #000000;
  --border: #ffe600;
  --accent: #ffe600;
  --focus: #ffffff;
  --font-scale: 1;
  --tile-bg: #000000;
  --tile-fg: #ffe600;
  --tile-muted: #ffe600;
  --tile-accent: #ffe600;
  --tile-line: #ffe600;
  --dot: transparent;
  color-scheme: dark;
}

:root[data-theme="red-green-safe"] {
  --bg: #0a1f44;
  --fg: #ffffff;
  --muted: #c9d6ff;
  --surface: #12305f;
  --border: #5b7bb5;
  --accent: #ffb000;
  --focus: #ffb000;
  --font-scale: 1;
  --tile-bg: #12305f;
  --tile-fg: #ffffff;
  --tile-muted: #c9d6ff;
  --tile-accent: #ffb000;
  --tile-line: #5b7bb5;
  --dot: rgba(255, 255, 255, 0.09);
  color-scheme: dark;
}

:root[data-theme="large-text"] {
  --font-scale: 1.5;
}
