/* ===========================================================================
   Tomáš Haufer — quantitative economic history
   Design system, v2. Full-bleed bands, CU palette, light only.

   One stylesheet, no build step, no dependencies. The only JavaScript on the
   site is the ~30-line pointer readout on the home-page figure; see .chart.

   Colour comes from th_style (CU palette), the same constants used for the
   figures in previous projects, so plots and page agree:
     !!!CERGE/.Thesis/Legions/utils/plotting/th_style_v113.py
   th_style also sets font.family: serif, which is why this is a serif site.

   Contents
     0. Fonts
     1. Tokens
     2. Reset & base
     3. Layout: bands and the shell
     4. Site header
     5. Typography
     6. Ornaments (line art)
     7. Hero
     8. Cards & panels
     9. Chart
    10. Skyline
    11. Research entries
    12. CV rows
    13. Site footer
    14. Responsive
    15. Print
   ======================================================================== */

/* --- 0. Fonts ----------------------------------------------------------- */

/* EB Garamond, self-hosted. SIL Open Font License 1.1 — see
   /assets/fonts/OFL.txt. Declared here rather than in a separate file so the
   site still makes exactly one stylesheet request and no third-party request.

   These are the VARIABLE font files: the CDN served byte-identical files for
   weight 400 and 600, so each subset ships once and the weight range is
   declared on the face. Requesting 600 interpolates from the same file.

   Two subsets per style. latin-ext is the one that carries á š č ř ž ě ů ý —
   without it every Czech name on the site falls back to another face
   mid-word. Do not drop it. To regenerate, see "Fonts" in README.md. */

@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/ebgaramond-var-normal-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/ebgaramond-var-normal-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "EB Garamond";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ebgaramond-var-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "EB Garamond";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ebgaramond-var-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- 1. Tokens ---------------------------------------------------------- */

:root {
  /* CU palette, lifted from th_style_v113.py. Names match the Python
     constants so the two stay traceable to each other. */
  --cu-navy:       #003657;  /* CU_NAVY      — headings, nav, links */
  --cu-blue:       #325C78;  /* CU_BLUE      — second chart series */
  --cu-red:        #D22D40;  /* CU_RED       — bright accent, used rarely */
  --cu-red-dark:   #AE2F3C;  /* CU_RED_DARK  — display headlines, ornament */
  --cu-ochre:      #d4a017;  /* CU_OCHRE     — ornament highlights */
  --cu-wheat:      #f0c84a;  /* CU_WHEAT     — ornament fills */
  --cu-grey-light: #B4BCC2;  /* CU_GREY_LIGHT— hairlines */
  --cu-grey:       #6E6E6E;  /* CU_GREY      — metadata */
  --cu-midnight:   #0A163F;  /* CU_MIDNIGHT  — deepest text */
  --cu-black:      #1a1a1a;  /* CU_BLACK     — body text */

  /* Derived. The CU palette has no paper colour, so these two are mine:
     a warm cream close to the reference, and a slightly deeper tint to
     separate alternating bands. Documented as derived so nobody mistakes
     them for official CU values. */
  --cream:        #FAF7F0;
  --cream-deep:   #F4F0E6;
  --rule-faint:   #E2DDD1;  /* derived: hairline for dense grids, where
                               CU_GREY_LIGHT reads too heavy */

  /* Semantic aliases — change the mapping here, not at every use site. */
  --paper:      var(--cream);
  --paper-alt:  var(--cream-deep);
  --ink:        var(--cu-black);
  --ink-soft:   #4A4A4A;
  --ink-faint:  var(--cu-grey);
  --heading:    var(--cu-navy);
  --display:    var(--cu-red-dark);
  --link:       var(--cu-navy);
  --accent:     var(--cu-red-dark);
  --ornament:   var(--cu-ochre);
  --rule:       var(--cu-grey-light);

  --serif: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino,
           Georgia, "Times New Roman", serif;

  --shell:   1440px;                        /* inner width of every band */
  --pad:     clamp(1.25rem, 4vw, 3.5rem);   /* band side padding */
  --measure: 34em;                          /* prose line length (~68 chars) */
  --hair:    1px;
  --step:    1.6rem;
}

/* --- 2. Reset & base --------------------------------------------------- */

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

html {
  /* Garamond runs small: its x-height is 0.425 of the em against Georgia's
     0.475, so at a nominal 17px it reads about 11% smaller than the scale was
     tuned for. Compensating at the root rather than per-rule keeps every
     rem-based value in the system proportionate. Measured, not guessed. */
  font-size: 111%;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Light only, by choice. This also stops the browser darkening form
     controls and scrollbars for users whose OS is in dark mode. */
  color-scheme: light;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 1.075rem;
  line-height: 1.7;
  font-kerning: normal;
  font-variant-ligatures: common-ligatures;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;

  /* Hold the footer at the bottom on short pages — 404 and, on a wide screen,
     Writing. Without this the footer stops halfway down and the cream below
     it reads as the page having failed to load. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

body > main { flex: 1 0 auto; }

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

a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-thickness: var(--hair);
  text-decoration-color: var(--rule);
  text-underline-offset: 0.18em;
  transition: text-decoration-color 120ms ease, color 120ms ease;
}

a:hover,
a:focus-visible {
  color: var(--accent);
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
}

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

/* Links that leave the site or open a PDF do so in a new tab. They carried a
   small ↗ to announce it; it has been removed by choice. The pills still show
   their own icon, so the only links now opening a tab unannounced are the ones
   set in running prose, which is a deliberate trade of a signal for a cleaner
   line of text. Restore by giving a[target="_blank"] an ::after of "\2197". */

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--paper);
  border: var(--hair) solid var(--rule);
  padding: 0.5rem 0.9rem;
  z-index: 20;
  text-decoration: none;
}
.skip:focus { left: 1rem; top: 1rem; }

/* --- 3. Layout: bands and the shell ------------------------------------ */

/* A band is full-bleed: its background and its hairline borders run the whole
   width of the viewport. The shell inside it caps the content. This is what
   makes the page "screenwide" without letting text lines grow unreadable. */

.band {
  width: 100%;
  border-bottom: var(--hair) solid var(--rule);
  /* Published so anything sitting on the band can paint its own ground in the
     band's colour. The chamfered pills need it: their outline is a filled
     shape with a smaller shape knocked out of the middle, and that knockout
     has to be the colour of whatever is behind it. */
  --band-bg: var(--paper);
}

.band:last-of-type { border-bottom: 0; }

.band--alt {
  background: var(--paper-alt);
  --band-bg: var(--paper-alt);
}

.shell {
  max-width: var(--shell);
  margin: 0 auto;
  padding-inline: var(--pad);
}

.band-pad { padding-block: clamp(2.5rem, 5vw, 4.5rem); }

/* Column sets. The vertical hairlines between columns are the band system's
   equivalent of the old manuscript ruling: they divide the writing area. */

.cols-3,
.cols-2 {
  display: grid;
  gap: 0;
}

.cols-3 { grid-template-columns: repeat(3, 1fr); }
.cols-2 { grid-template-columns: 0.85fr 1.15fr; }

.col {
  padding: clamp(2rem, 3.5vw, 3.25rem) clamp(1.5rem, 2.5vw, 2.75rem);
  border-left: var(--hair) solid var(--rule);
  position: relative;
  overflow: hidden; /* contains the watermark ornaments */
}

.col:first-child { border-left: 0; padding-left: 0; }
.col:last-child  { padding-right: 0; }

/* Inner pages. A measure-limited column alone on a 1440px screen looks
   stranded, so the page title, its ornament and a short standfirst sit in a
   narrower left column and the content runs down the right — the usual
   academic arrangement, and it uses the width the redesign asked for. */

.page-grid {
  display: grid;
  grid-template-columns: 0.42fr 1fr;
  gap: clamp(2rem, 4vw, 5rem);
  align-items: start;
}

.page-aside { position: relative; }

/* "As of July 2026" under the title column on the inner pages. Deliberately
   the quietest thing in that column — it is provenance, not content — but it
   is there, because a CV or a research list with no date on it makes a reader
   guess how stale it is, and they always guess worse than the truth.

   Kept as a hand-written line rather than a build-time stamp: this site has no
   build step, and a date generated from a file's mtime would go stale in the
   opposite direction, claiming freshness for a commit that fixed a typo. */
.as-of {
  margin: 1.5rem 0 0;
  font-size: 0.68rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.as-of time { font-variant-numeric: lining-nums; }

/* position: relative is kept for the column, not for anything in it today: the
   Research lion used to be anchored here, and it is what any future absolutely
   positioned decoration would resolve against. Harmless meanwhile. */
.page-body { max-width: 46em; position: relative; }

@media (min-width: 62.01em) {
  /* The title column tracks the reader down the page on tall screens. */
  .page-aside { position: sticky; top: 2rem; }
}

/* --- 4. Site header ---------------------------------------------------- */

.site-header {
  border-bottom: var(--hair) solid var(--rule);
  background: var(--paper);
}

.header-inner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem 2rem;
  flex-wrap: wrap;
  padding-block: 1.15rem;
}

/* The mark, and this time it earns its place. Three earlier attempts did not:
   a Gothic arch, a plotted series and a drawn monogram each asserted something
   about the work that a header is the wrong place to assert. This one asserts
   nothing — it is the two letters of the name beside it, set in the site's own
   EB Garamond, inside a hairline square. Its whole job is to be the same mark
   as the browser tab, so the page and the favicon read as one thing.

   /assets/img/th-mark.svg, and see the note in that file for why it is an
   <img> rather than inline SVG. */
.brand {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  text-decoration: none;
  color: inherit;
}

/* Sized on height alone. The mark's viewBox is the glyph's own bounding box
   rather than a square, so it carries no invisible padding and `width: auto`
   lets the 544:945 ratio settle the rest — which is what allows it to sit this
   close to the name without a gap opening up beside it. */
.brand-mark {
  width: auto;
  height: 3.05rem;
  flex: none;
}

.brand-name {
  display: block;
  font-size: 1.52rem;
  line-height: 1.15;
  color: var(--cu-navy);
  letter-spacing: 0.005em;
}

.brand:hover .brand-name { color: var(--accent); }

/* The name used to sit on a short red rule of its own. It has been removed,
   because the mark beside it now carries the identical rule from the favicon:
   two red bars of the same length, twenty pixels apart horizontally and eight
   vertically, read as a printing error rather than as a system. The mark keeps
   the red; the name is just the name. To put it back, restore
   `.brand-name::after` with width 2.15rem, height 2px, margin-top 0.42rem. */

.brand-sub {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.68rem;
  line-height: 1.5;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.site-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 2.1rem;
}

.site-nav a {
  display: inline-block;
  padding-bottom: 0.3rem;
  font-size: 0.76rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--cu-navy);
  border-bottom: 2px solid transparent;
}

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

/* Current page: a short rule beneath, in CU red. */
.site-nav a[aria-current="page"] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* --- 5. Typography ----------------------------------------------------- */

h1, h2, h3, h4 { font-weight: 400; }

/* The display headline. Red, large, tight — carries the page. */
.display {
  font-size: clamp(2.35rem, 1.4rem + 3.6vw, 4.1rem);
  line-height: 1.08;
  letter-spacing: -0.018em;
  color: var(--display);
  margin: 0;
}

.display em {
  font-style: italic;
  display: block;
}

/* Page titles on inner pages — navy, smaller than the home display. */
h1.page-title {
  font-size: clamp(1.95rem, 1.4rem + 1.9vw, 2.8rem);
  line-height: 1.16;
  letter-spacing: -0.012em;
  color: var(--heading);
  margin: 0 0 var(--step);
}

/* Section label: small letterspaced caps, the workhorse heading.

   Used on two kinds of element, and the difference is not cosmetic. Where the
   label is the section's heading — the lists on Research, CV and Writing — it
   is an <h2>, so the page outline runs h1 → h2 → h3 and a screen-reader user
   can jump between sections. Where it is an eyebrow sitting above a real <h2>,
   as in the home-page columns and the footer, it is a <p>. The line-height is
   pinned here so both elements render identically. */
.label {
  font-size: 0.7rem;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 1.1rem;
}

/* Subsection heading. Theses, Education, Research experience, Skills — the
   second level of structure on the long pages.

   At plain .label size these lost to the entry titles beneath them, so a
   reader scrolling the CV saw one undifferentiated column of rows and no
   seams. Slightly larger and wider-tracked, and carrying the same short red
   rule as the name in the header — the site now has one gesture for "a new
   part starts here" and uses it in both places. */
.label--accent {
  color: var(--accent);
  font-size: 0.78rem;
  letter-spacing: 0.2em;
}

.label--accent::before {
  content: "";
  display: block;
  width: 2.15rem;
  height: 2px;
  margin-bottom: 0.62rem;
  background: var(--accent);
}

/* Air above a subsection, but only where something precedes it — the first
   label in a column must still sit flush with the top of its column. */
* + .label--accent { margin-top: 2.4rem; }

h2 {
  font-size: clamp(1.3rem, 1.05rem + 0.7vw, 1.72rem);
  line-height: 1.22;
  color: var(--heading);
  margin: 0 0 var(--step);
  letter-spacing: -0.008em;
}

h3 {
  font-size: 1.16rem;
  line-height: 1.35;
  color: var(--heading);
  margin: 0 0 0.3rem;
}

p { margin: 0 0 var(--step); }

.prose { max-width: var(--measure); }

.lede {
  font-size: 1.14rem;
  line-height: 1.62;
}

.small { font-size: 0.9rem; line-height: 1.6; }
.muted { color: var(--ink-faint); }

/* Eyebrow: letterspaced caps under a display headline. */
.eyebrow {
  font-size: 0.82rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--ink-soft);
  line-height: 1.85;
  margin: 0 0 var(--step);
}

/* Tabular figures wherever numbers must line up in a column. */
.tnum {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}

/* Call to action — letterspaced caps with a moving arrow. */
.cta {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  font-size: 0.78rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--cu-navy);
  border-bottom: var(--hair) solid var(--rule);
  padding-bottom: 0.3rem;
}

.cta:hover { color: var(--accent); border-bottom-color: var(--accent); }

.cta span { transition: transform 160ms ease; }
.cta:hover span { transform: translateX(4px); }

/* --- 6. Ornaments (line art) ------------------------------------------- */

/* All ornaments are original inline SVG line drawings — no bitmaps, no
   third-party assets, nothing to license or attribute. They inherit colour
   through currentColor, so a token change restyles every one of them.

   The arches are drawn with quadratic Béziers rather than SVG arc commands.
   Arcs would be more "correct" for a lancet arch, but the flag semantics are
   error-prone and a Bézier is visually indistinguishable at ornament scale. */

.orn { display: block; }
.orn line, .orn path, .orn circle, .orn polyline, .orn rect {
  vector-effect: non-scaling-stroke;
}

/* Divider: two hairlines with a small accent lozenge between them. */
.divider {
  display: block;
  width: min(13rem, 100%);
  height: 12px;
  margin: var(--step) 0;
  overflow: visible;
  color: var(--rule);
}

.divider line { stroke: currentColor; stroke-width: 1; }
.divider .lozenge { fill: var(--accent); }

.divider--center { margin-inline: auto; }

/* Watermark ornaments sit behind content, very faint, and never intercept
   clicks. Decorative only, so aria-hidden with an empty alt in the markup. */
.watermark {
  position: absolute;
  pointer-events: none;
  z-index: 0;
}

/* The Charles University seal, now behind the hero rather than behind the
   Background column — Prague took that column over, and two watermarks in one
   column is one too many. The hero is where an institutional watermark reads
   as context: it is the first band a reader meets, and the seal sits behind
   the biography that explains the affiliation.

   Referenced with <img src> rather than inlined: it is 102 KB of intricate
   vector, so inlining would put it in every HTML page instead of being fetched
   and cached once. CU_NAVY is baked into the file because an <img> renders in
   an isolated context where currentColor would resolve to black.

   Large and very faint on purpose. The seal is dense — at a small size and a
   readable opacity it turns into grey noise; big and nearly invisible, it
   reads as a watermark. */
/* Left, and running off the edge. Centred behind the biography it read as a
   backdrop the text was sitting on top of; anchored left and cropped by the
   shell it reads as a watermark on the page itself — which is what it was
   when it worked. .hero-inner carries position: relative so these percentages
   resolve against the content column and not the full-bleed band. */
.watermark--emblem {
  --cut: 38%;
  width: min(560px, 46vw);
  height: auto;
  /* Cut at the text border, not at the window edge.

     `left` on an absolutely positioned element resolves against the containing
     block's PADDING box, so left: 0 would be the shell's outer edge — the text
     begins one --pad further in. Setting left to --pad puts the origin exactly
     on the text's left margin.

     The element is then pushed --cut of its own width to the left and the same
     --cut is clipped away again. Both are percentages of the same width, so
     they stay in step at every viewport and the visible edge always lands on
     the text margin. Clipping here rather than relying on the band's
     overflow: hidden is the whole point: the band's edge is the window, and
     the window is not where the cut should fall. */
  left: var(--pad);
  top: 50%;
  transform: translate(calc(-1 * var(--cut)), -50%);
  clip-path: inset(0 0 0 var(--cut));
  /* 0.07 until November 2026; lowered so the seal sits further back behind
     the biography. */
  opacity: 0.045;
}

/* The Bohemian lion — the same device as the seal above, and cut the same way.

   Which SIDE it is cut on is not a free choice. The lion is rampant to dexter,
   so the crowned head sits upper-centre and the outstretched forepaws run to
   the left, while the double tail sweeps up the right. Cutting the left costs
   part of the forepaws; cutting the right would take the second tail, which is
   the one feature that makes this the Bohemian lion rather than any other
   lion. So it is cut on the left, which is also where the seal is cut.

   Mirroring it to make the crop fall more kindly is not an option either: a
   heraldic beast turned to sinister is a mark of disgrace, and on a page about
   Czech economic history that is a detail somebody will notice.

   As with the seal, both the shift and the clip are percentages of the
   element's own width, so they stay in step at every viewport and the visible
   edge lands on the text margin at any size. At 0.06: this is 159 paths of
   line art rather than an engraved disc, and at 0.07 the hatching started to
   read as dirt on the page. (The home-page seal is fainter still, 0.045.) */
.watermark--lion {
  --cut: 18%;
  height: auto;
  /* Belt and braces against the global img cap: the sizes below are all
     comfortably inside their containers today, but a watermark that silently
     reflows to fit its column is no longer a watermark. */
  max-width: none;
  transform: translateX(calc(-1 * var(--cut)));
  clip-path: inset(0 0 0 var(--cut));
  opacity: 0.06;
}

/* Content must sit above the watermark.

   The :not() is load-bearing. `.col > *` and `.watermark` have identical
   specificity (0,1,0), so whichever comes later in the file wins — and this
   one does. Without the exclusion the watermark was forced to position:
   relative, dropped into the normal flow, and pushed the whole Background
   column down by its own 460px height. */
.col > *:not(.watermark),
.hero-text > *:not(.watermark),
.page-body > *:not(.watermark),
.lacuna > *:not(.watermark) { position: relative; z-index: 1; }

/* The 404, which is four lines of text and otherwise an empty page — so the
   lion gets the room here that it does not get anywhere else, and the emptiness
   stops being an accident and becomes the point.

   min-height, because the band would otherwise be shorter than the drawing and
   overflow: hidden would slice the lion through the middle. overflow: hidden,
   because .band — unlike .hero — does not clip, and without it the tail runs
   down into the footer.

   left: var(--pad) and not 0: this element IS the shell, and `left` resolves
   against its padding box, so 0 would be the window edge rather than where the
   text starts. Same correction as .watermark--emblem. */
.lacuna {
  position: relative;
  overflow: hidden;
  /* Paired with the width below, not chosen independently. The drawing is
     1:1.235, so the two clamps are set to keep this taller than the lion at
     every viewport — 19rem against 247px of lion on a phone, 31rem against
     420px on a desktop. If either clamp moves, check the other. */
  min-height: clamp(19rem, 40vw, 31rem);
}

.lacuna .watermark--lion {
  left: var(--pad);
  top: 50%;
  /* clamp and not min(): a bare 30vw collapsed to 112px on a 375px phone,
     which left a thumbnail adrift in a tall empty band. The floor keeps it at
     half the screen, which is the size at which it still reads as a lion. */
  width: clamp(200px, 30vw, 340px);
  /* Rewritten in full rather than added to: the base rule's translateX would
     be replaced by this declaration anyway, so both axes belong here. */
  transform: translate(calc(-1 * var(--cut)), -50%);
}

/* --- 7. Hero ----------------------------------------------------------- */

.hero { position: relative; overflow: hidden; }

/* The text column takes the larger share now. It was 0.92 against 1.08, which
   left the prose in a narrower measure than the photograph beside it — the
   biography is the substance of this page, so it gets the width. */
.hero-inner {
  /* So .watermark--emblem's percentages resolve against the content column
     rather than the full-bleed band behind it. */
  position: relative;
  display: grid;
  /* The name is its own grid item rather than the first line of .hero-text,
     so that the photograph can be placed between the name and the biography
     when this stacks. row-gap stays 0 because the h1 already carries its own
     margin-bottom; only the columns need separating. */
  grid-template-areas:
    "id   figure"
    "text figure";
  grid-template-columns: 1.32fr 0.68fr;
  align-items: center;
  /* column-gap, not gap: a shorthand here would reset row-gap back to the same
     clamp and drive the name five rems clear of the text beneath it. */
  column-gap: clamp(2rem, 4.5vw, 5rem);
  row-gap: 0;
  padding-block: clamp(3rem, 6vw, 6rem);
}

.hero-inner > .hero-id { grid-area: id; }
.hero-inner > .hero-text { grid-area: text; }
.hero-inner > .hero-figure { grid-area: figure; }

/* The identity block and the text block are separate grid items now, so both
   need lifting above the seal watermark rather than only the old wrapper. */
.hero-inner > *:not(.watermark) { position: relative; z-index: 1; }

/* The eyebrow is the last thing in the identity block and carries the gap down
   to whatever follows — the biography on a wide screen, the photograph on a
   narrow one. row-gap stays 0 so this margin is the only thing setting it. */
.hero-id > :last-child { margin-bottom: var(--step); }

/* The second rule is a phone-only device: on a wide screen the photograph has
   its own column and needs no fence. */
.hero-rule { display: none; }

.hero-text { position: relative; }

.hero-lede {
  font-size: 1.1rem;
  line-height: 1.68;
  max-width: 38em;
  color: var(--ink);
}

.hero-text .small { max-width: 40em; }

/* Research interests. Navy and slightly larger than body, so the line reads
   as a standing statement rather than as another sentence of the biography. */
.interests {
  font-size: 1.04rem;
  line-height: 1.55;
  color: var(--cu-navy);
  max-width: 34em;
}

.hero-figure {
  position: relative;
  color: var(--cu-navy);
}

.hero-figure .orn { width: 100%; height: auto; }

/* The portrait is matted, like a plate in a book.

   The arch that used to sit behind it is gone. It was drawn larger than the
   photograph so its apex rose above the picture and its legs ran down either
   side — which read not as a frame but as a second, unrelated outline that
   happened to be nearby. Two frames competing is worse than one.

   What replaces it is a proper mount: a hairline outer frame, a wide margin of
   paper, and the photograph inside on a hairline of its own. The bottom margin
   is deeper than the top, which is how mounts have always been cut — an
   optically centred picture sits slightly above the geometric centre. */
/* One hairline around the photograph and a small red lozenge at each corner,
   just outside it. The lozenge is the site's own ornament — it is the divider's
   centre mark, the disclosure bullet, and the rubricator's mark — so the
   photograph is framed in a shape the page already uses rather than in a new
   one invented for it.

   The four marks are drawn as background gradients rather than elements: an
   element has two pseudo-elements and this needs four. Each is a tiny square
   rotated 45° by a pair of crossed gradients — cheaper than four SVGs and it
   inherits the accent colour. They sit in the figure's padding, clear of the
   image's own hairline.

   Earlier: midpoint tick marks, which read as registration but very quietly;
   and before that a wide paper mount, which looked like a placeholder.

   The lozenge is one small inline SVG repeated at the four corners. The colour
   is baked into the data URI because a CSS custom property cannot reach inside
   one — it is --accent, #AE2F3C; change it in both places or not at all. */
.hero-portrait {
  --pip: 8px;
  --box: 22px;
  --lozenge: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M5 0l5 5-5 5-5-5z' fill='%23AE2F3C'/%3E%3C/svg%3E");
  /* A hairline square concentric with each lozenge, so the four corners read
     as registration marks rather than as loose diamonds. Colour is baked into
     the data URI because a CSS custom property cannot reach inside one — it is
     --rule, #B4BCC2. Change it in both places or not at all. */
  --corner: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Crect x='0.5' y='0.5' width='21' height='21' fill='none' stroke='%23B4BCC2'/%3E%3C/svg%3E");
  position: relative;
  max-width: 430px;
  margin-inline: auto;
  padding: 0.8rem;
  background-image:
    var(--lozenge), var(--lozenge), var(--lozenge), var(--lozenge),
    var(--corner), var(--corner), var(--corner), var(--corner);
  background-repeat: no-repeat;
  background-size:
    var(--pip) var(--pip), var(--pip) var(--pip),
    var(--pip) var(--pip), var(--pip) var(--pip),
    var(--box) var(--box), var(--box) var(--box),
    var(--box) var(--box), var(--box) var(--box);
  /* The lozenge sits flush in each corner, so its centre is --pip/2 in from
     the edge. The square is centred on that same point: offset by
     (--box - --pip) / 2 = 7px outwards on each axis. */
  background-position:
    0 0, 100% 0, 0 100%, 100% 100%,
    -7px -7px,
    calc(100% + 7px) -7px,
    -7px calc(100% + 7px),
    calc(100% + 7px) calc(100% + 7px);
}

.hero-portrait .portrait {
  display: block;
  width: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
  background: none;
  border: var(--hair) solid var(--rule);
}

/* Two calls to action side by side. They wrap rather than shrink, because a
   letterspaced cap line that has to break mid-phrase stops reading as a
   button and starts reading as an accident. */
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem 2rem;
}

/* A feature block: prose on the left, a figure held beside it. Used for the
   current-research band, which earns the full width of the shell rather than
   a third of it. */
.feature {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(2rem, 4vw, 4.5rem);
  /* Top, not centre: the right column (diagram, chart, map) is far taller than
     the text, and centring floated the title halfway down the band and made it
     move whenever the figures changed height. */
  align-items: start;
}

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

/* min-width: 0 is load-bearing. A grid item defaults to min-width: auto, so it
   refuses to shrink below its content's min-content width — and the chart
   inside carries min-width: 560px on narrow screens. Without this the column
   blew out to 560px and took the whole page's horizontal scroll with it,
   which is exactly what .chart-scroll exists to prevent. */
.feature > div { min-width: 0; }

.feature-figure { color: var(--cu-navy); }
.feature-figure .orn { width: 100%; height: auto; }

/* --- The identification schematic -------------------------------------

   Labels are typed in capitals in the markup rather than set with
   text-transform: SVG text support for it is inconsistent across renderers,
   and a diagram that loses its labels in one browser is worse than one that
   is slightly less tidy in the source. */

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

.schematic text {
  font-family: var(--serif);
  fill: var(--cu-navy);
}

.schematic .sch-rule line { stroke: var(--rule); stroke-width: 1; }

.schematic .sch-stage {
  font-size: 11px;
  letter-spacing: 0.18em;
  fill: var(--ink-faint);
}

.schematic .sch-node rect {
  fill: var(--paper-alt);
  stroke: var(--rule);
  stroke-width: 1;
}

.schematic .sch-node text {
  font-size: 12.5px;
  letter-spacing: 0.13em;
  text-anchor: middle;
}

.schematic .sch-node--accent rect { stroke: var(--cu-red-dark); }
.schematic .sch-node--accent text { fill: var(--cu-red-dark); }

.schematic .sch-node--muted rect { stroke-dasharray: 4 4; opacity: 0.6; }
.schematic .sch-node--muted text { fill: var(--ink-faint); }

.schematic .sch-flow { fill: none; stroke-width: 1.5; }
.schematic .sch-flow--navy  { stroke: var(--cu-navy); }
.schematic .sch-flow--red   { stroke: var(--cu-red-dark); }
.schematic .sch-flow--muted { stroke: var(--cu-grey-light); stroke-dasharray: 4 4; }

.schematic .sch-flowlabel rect { fill: var(--paper-alt); }

.schematic .sch-flowlabel text {
  font-size: 12px;
  font-style: italic;
  text-anchor: middle;
  fill: var(--cu-red-dark);
}

.schematic .sch-note text {
  font-size: 11.5px;
  fill: var(--ink-faint);
  text-anchor: start;
}

/* --- 8. Cards & panels ------------------------------------------------- */

.stack > * + * { margin-top: 1.15rem; }

/* The portrait — the only bitmap on the site, so it needs help to sit among
   line ornaments rather than crash into them: a hairline frame with a little
   air inside it.

   No filter. It previously carried grayscale(0.85) sepia(0.12) to pull the
   photograph toward the cream-and-navy palette; the result was a muddy
   half-tinted image that matched neither the page nor a real photograph.
   Full colour, and the frame does the reconciling instead. */
.portrait {
  width: 100%;
  max-width: 420px;
  height: auto;
  margin: 0 0 1.5rem;
  padding: 7px;
  background: var(--paper);
  border: var(--hair) solid var(--rule);
}

/* --- Link pills --------------------------------------------------------

   One component for every outbound link on the site, so a reader learns the
   vocabulary once. Colour carries the KIND of destination, not its importance:

     .pill--doc   navy  — something to read: PDF, position paper, thesis
     .pill--ext   red   — leaves the site: published article, Substack, a
                          coauthor's page
     .pill--data  grey  — code, data, replication, documentation

   The icon repeats the same distinction, so the categories survive both
   greyscale printing and colour-blindness.

   Each modifier sets --pill-color and nothing else. Hover then swaps which
   side of the pair is ink and which is ground. Doing it through a custom
   property rather than currentColor matters: with `background: currentColor`
   the label would be painted in the same colour as its own background and
   vanish, and a bare text node cannot be recoloured by a child selector. The
   icon inherits through currentColor, so it needs no rule of its own.

   Usage: <a class="pill pill--doc" href="…"><svg class="pill-icon">…</svg>PDF</a>
   wrapped in <p class="pill-row"> or <div class="pill-row">.  */

.pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem 0.6rem;
  margin: 0.9rem 0 0;
  padding: 0;
  list-style: none;
}

/* The shape is a cartouche: the corners are cut off at 45° rather than
   rounded, which is what a printer's label looks like and what a 999px pill
   emphatically does not.

   It cannot be done with `border` — a border follows the box, and clipping the
   box to an octagon clips the border away with it, leaving four bare diagonals.
   So the outline is drawn as two stacked shapes: the element itself is filled
   solid in --pill-color and clipped to the octagon, and ::before is inset by a
   hairline, filled in the band's own colour and clipped to the same octagon one
   pixel smaller. What is left visible between them is a 1px chamfered rule.

   That is why .band publishes --band-bg. Hover then only has to repaint the
   knockout in --pill-color and the shape fills solid, which is the same
   inversion the rounded version had. */

.pill {
  --pill-color: var(--cu-navy);
  --chamfer: 0.4rem;
  --pill-shape:
    polygon(var(--chamfer) 0, calc(100% - var(--chamfer)) 0,
            100% var(--chamfer), 100% calc(100% - var(--chamfer)),
            calc(100% - var(--chamfer)) 100%, var(--chamfer) 100%,
            0 calc(100% - var(--chamfer)), 0 var(--chamfer));
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.34rem 0.9rem 0.36rem;
  color: var(--pill-color);
  background: var(--pill-color);
  border: 0;
  border-radius: 0;
  clip-path: var(--pill-shape);
  font-size: 0.7rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  text-decoration: none;
  line-height: 1.35;
  white-space: nowrap;
  transition: color 130ms ease;
}

/* The knockout. One pixel in on every side, and its chamfer is a pixel
   shallower so the diagonal stays parallel to the outer one. */
.pill::before {
  content: "";
  position: absolute;
  inset: var(--hair);
  z-index: -1;
  background: var(--band-bg, var(--paper));
  clip-path:
    polygon(calc(var(--chamfer) - 1px) 0, calc(100% - var(--chamfer) + 1px) 0,
            100% calc(var(--chamfer) - 1px), 100% calc(100% - var(--chamfer) + 1px),
            calc(100% - var(--chamfer) + 1px) 100%, calc(var(--chamfer) - 1px) 100%,
            0 calc(100% - var(--chamfer) + 1px), 0 calc(var(--chamfer) - 1px));
  transition: background-color 130ms ease;
}

.pill-icon {
  width: 12px;
  height: 12px;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.4;
}

.pill--doc  { --pill-color: var(--cu-navy); }
.pill--ext  { --pill-color: var(--cu-red-dark); }
.pill--data { --pill-color: var(--cu-grey); }

.pill:hover,
.pill:focus-visible {
  color: var(--paper);
  text-decoration: none;
}

.pill:hover::before,
.pill:focus-visible::before { background: var(--pill-color); }

/* One filled pill, for the single action worth pushing: Subscribe. It arrives
   already in the state the others reach on hover, and inverts back on hover so
   it still responds to the pointer. Use sparingly — a page of filled pills has
   no emphasis at all. */
.pill--filled { color: var(--paper); }
.pill--filled::before { background: var(--pill-color); }

.pill--filled:hover,
.pill--filled:focus-visible { color: var(--pill-color); }

.pill--filled:hover::before,
.pill--filled:focus-visible::before { background: var(--band-bg, var(--paper)); }

/* Publication list on the home page: a thumbnail ornament, then the title. */
.pub-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pub {
  display: grid;
  grid-template-columns: 62px 1fr;
  gap: 0 1.15rem;
  padding: 1.3rem 0;
  border-top: var(--hair) solid var(--rule-faint);
  align-items: start;
}

.pub:first-child { border-top: 0; padding-top: 0; }

/* Nudged down by three pixels. Grid alignment puts the top of the thumbnail's
   box level with the top of the text block, but the title's cap line starts
   below that, so a box aligned to the box read as floating. Optical alignment
   beats geometric alignment here, as it usually does. */
.pub-thumb {
  width: 62px;
  height: 62px;
  margin-top: 3px;
  color: var(--cu-navy);
  opacity: 0.62;
  border: var(--hair) solid var(--rule-faint);
}

.pub-title {
  font-size: 1.06rem;
  line-height: 1.32;
  margin: 0 0 0.28rem;
  color: var(--heading);
}

.pub-venue {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.35rem;
}

.pub-links {
  margin: 0;
  font-size: 0.72rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

/* --- 9. Chart ---------------------------------------------------------- */

/* The figure under the identification diagram, and the one place the site
   uses real estimates. It replaces a chart built from invented data, which was
   removed on the grounds that on a research page a fabricated figure is worse
   than no figure — every number here is recomputed from the thesis repository
   using notebook 03's own definitions.

   Hand-written inline SVG, deliberately: a charting library would be several
   hundred kilobytes of dependency for one figure. Series colours are the same
   CU constants th_style uses, so this chart and the matplotlib one agree. */

/* The figure sits in the right-hand column of .feature, under the diagram and
   beside the prose. The hairline above it separates the two figures without
   implying the second belongs to a new section. */
.chart-figure {
  margin: clamp(1.5rem, 2.6vw, 2.4rem) 0 0;
  padding-top: clamp(1.4rem, 2.2vw, 2rem);
  border-top: var(--hair) solid var(--rule);
}

.chart {
  display: block;
  width: 100%;
  height: auto;
  margin: 0 0 0.9rem;
  overflow: visible;
}

/* The HTML hidden attribute does not hide SVG elements in every browser; without
   this the cursor's two dots sit at the chart's origin before any hover. */
.chart-cursor[hidden] { display: none; }

.chart .grid    { stroke: var(--rule-faint); stroke-width: 1; fill: none; }
.chart .axis    { stroke: var(--rule);       stroke-width: 1; fill: none; }

/* Navy is enrolment, red is desertion — the same assignment th_style makes,
   so a reader who has seen the paper's figures reads this one the same way. */
.chart .series-a { stroke: var(--cu-navy);     fill: none; stroke-width: 1.4; }
.chart .series-b { stroke: var(--cu-red-dark); fill: none; stroke-width: 1.4; }
.chart .fill-a   { fill: var(--cu-navy);     stroke: none; opacity: 0.12; }
.chart .fill-b   { fill: var(--cu-red-dark); stroke: none; opacity: 0.12; }
.chart .dot-a   { fill: var(--cu-navy); }
.chart .dot-b   { fill: var(--cu-red-dark); }

/* The four dated events, annotated above the plot rather than inside it. The
   rule crosses the data dotted; the leader from the label down to the plot is
   solid and fainter, so the two lines never read as the same mark. */
.chart .chart-mile .mile-rule {
  stroke: var(--cu-grey);
  stroke-width: 0.9;
  stroke-dasharray: 2 4;
  opacity: 0.7;
  fill: none;
}

.chart .chart-mile .mile-lead {
  stroke: var(--rule);
  stroke-width: 0.9;
  fill: none;
}

/* Each axis named in its series' colour. With two scales on one frame this is
   the whole of the disambiguation — the numbers alone cannot say which side
   belongs to which line. */
/* All three label sizes below are in SVG USER UNITS, and this chart renders at
   0.551 of its viewBox width in the column it sits in — measured, not
   assumed. Divide by it to get the size on screen: 22 units lands near 12px,
   25 near 13.5px. Treating these as pixels is what made the first two
   attempts at enlarging them barely show. */
.chart .axis-title {
  font-size: 22px;
  letter-spacing: 0.17em;
  text-anchor: middle;
}

.chart .axis-title--a { fill: var(--cu-navy); }
.chart .axis-title--b { fill: var(--cu-red-dark); }

/* Set upright and these collided: the Russian Revolution, Zborov and the
   Bolshevik coup fall within eight months of each other, so at this width the
   three labels overlapped by up to thirty units. Turned on their side they run
   up their own milestone lines and cannot collide at all — which is also what
   the matplotlib version of this figure does. */
/* The three staggered rows are 24 units apart to carry this size — "Russian
   Revolution" on row one sits directly above "Zborov" on row two, so the gap
   has to exceed the type's own height. Verified on screen with client rects,
   which is the only way to catch it: getBBox() reports pre-transform boxes. */
.chart .chart-mile text {
  font-size: 21px;
  letter-spacing: 0.1em;
  text-anchor: middle;
  fill: var(--ink-faint);
}

.chart .tick--l text { text-anchor: end;    dominant-baseline: middle; }
.chart .tick--r text { text-anchor: start;  dominant-baseline: middle; }
.chart .tick--x text { text-anchor: middle; }

/* touch-action is the whole fix for the readout on a phone.

   Below 40em the chart is wider than the column and .chart-scroll pans it.
   With the default touch-action, a finger dragged across the plot was claimed
   by the scroll container: the reader tried to move the cursor along the dates
   and slid the entire figure sideways instead — and because that pan persists,
   the chart was then sitting scrolled with 1914, 1915 and the ENROLMENTS axis
   pushed off the left edge, which is how it appears in the screenshots.

   pan-y hands vertical gestures back to the page, so the reader can still
   scroll past the figure by dragging on it, while horizontal movement goes to
   the readout where it was always meant to go. Panning the chart itself is
   still possible from the margins outside this rect, and from the scrollbar. */
.chart .chart-hit {
  fill: transparent;
  cursor: crosshair;
  touch-action: pan-y;
}

.chart .chart-cursor-line {
  stroke: var(--cu-navy);
  stroke-width: 1;
  opacity: 0.4;
  fill: none;
}

.chart-readout {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 1.4rem;
  margin: 0;
  font-size: 0.9rem;
  color: var(--ink-soft);
  /* Reserve the line so the block does not jump as the readout changes. */
  min-height: 1.6em;
}

.chart-when {
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  min-width: 9.5em;
}

.chart-vals { font-variant-numeric: tabular-nums lining-nums; }
.chart-vals b { font-weight: 600; color: var(--heading); }
.chart-vals .swatch { display: inline-block; margin: 0 0.35em 0 1.1em; }
.chart-vals .swatch:first-child { margin-left: 0; }

.chart text {
  font-family: var(--serif);
  font-size: 25px;
  fill: var(--ink-faint);
  font-variant-numeric: tabular-nums lining-nums;
}

/* The figure's own title. Set in HTML above the SVG rather than as SVG text,
   so it scales with the reader's font size and stays selectable. */
.chart-title {
  margin: 0 0 0.15rem;
  font-size: 1.02rem;
  line-height: 1.3;
  color: var(--heading);
}

.chart-sub {
  margin: 0 0 0.85rem;
  font-size: 0.76rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.4rem;
  margin: 0 0 1.1rem;
  padding: 0;
  list-style: none;
  font-size: 0.76rem;
  color: var(--ink-soft);
}

.chart-legend li { display: flex; align-items: center; gap: 0.5em; }

.swatch {
  width: 15px;
  height: 2px;
  flex: none;
}

.swatch--a { background: var(--cu-navy); }
.swatch--b { background: var(--cu-red-dark); }

/* --- The district map under the chart -----------------------------------

   Generated by tools/build_maps.py. One map with four views (Matice, theatre,
   legionnaires, desertion). Each census district's outline sits once in the
   map's <defs>; each view is a layer of <use> references with its fills
   written in, so the first view is complete without JavaScript. The view
   buttons only work with it, so they stay hidden until the script runs. */
.maps-figure {
  margin: clamp(1.5rem, 2.6vw, 2.4rem) 0 0;
  padding-top: clamp(1.4rem, 2.2vw, 2rem);
  border-top: var(--hair) solid var(--rule);
}

/* The view switch: set like the navigation, the current view underlined in red. */
.map-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 1.3rem;
  margin: 0 0 0.7rem;
}
.map-tabs[hidden] { display: none; }

.map-tab {
  padding: 0.15rem 0 0.3rem;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  font: inherit;
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  cursor: pointer;
}
.map-tab:hover { color: var(--heading); }
.map-tab[aria-pressed="true"] {
  color: var(--heading);
  border-bottom-color: var(--accent);
}
.map-tab:focus-visible { outline: 1px solid var(--cu-navy); outline-offset: 2px; }

.map { display: block; width: 100%; height: auto; margin: 0 0 0.4rem; }

.map-layer.off,
.map-scale.off { display: none; }

/* Hairlines between districts in the background colour, so the low end of each
   scale (which is the background too) reads as empty ground. The outer frontier
   gets a dark line of its own, so the shape of the Czech lands holds even where
   the districts along it are pale. */
.map-layer use {
  stroke: var(--band-bg, var(--paper));
  stroke-width: 0.4px;
  stroke-linejoin: round;
  fill-rule: evenodd;
}

/* Border widths in screen pixels, whatever size the map is drawn at: the map
   runs at about two pixels per unit, so a width in map units came out thick. */
.map defs path { vector-effect: non-scaling-stroke; }

/* The hovered district: one outline, moved by the script. */
.map-hl {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.5px;
  stroke-linejoin: round;
  pointer-events: none;
}
.map-hl.off { display: none; }

.map-frame {
  fill: none;
  stroke: var(--ink-soft);
  stroke-width: 0.8px;
  stroke-linejoin: round;
  pointer-events: none;
}

.map-scale {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.45rem;
  margin: 0 0 0.8rem;
  font-size: 0.72rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums lining-nums;
}
/* Reserve the caption's line(s), so switching views does not move the page. */
.map-scale .map-unit { flex-basis: 100%; min-height: 1.35em; color: var(--ink-soft); }
.map-scale i {
  flex: 1;
  height: 0.42rem;
  border: var(--hair) solid var(--rule);
}

/* The readout holds a district name and four values, so it wraps; its height
   is reserved for the wrapped form, so hovering never pushes the page down. */
.maps-readout { min-height: 3.5em; align-content: flex-start; }
.maps-readout .chart-when { min-width: 0; }

@media (max-width: 560px) {
  .maps-readout { min-height: 5.2em; }
  .map-scale .map-unit { min-height: 3.4em; }
}

.maps-note {
  margin: 0.8rem 0 0;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--ink-faint);
}

/* --- 10. Skyline ------------------------------------------------------- */

/* The home page used to carry a 1000–1500 timeline here. It was removed: a
   periodisation of medieval Europe is a claim about the field, not about the
   person, and the space is better spent on training a reader can check. The
   .timeline rules went with it. */

/* The Prague skyline that used to close this column is gone. It was a drawing
   of a city, which is to say a drawing of nowhere in particular: the same
   towers appear on every Czech tourist page, it said nothing about the person
   whose page this is, and getting eight landmarks right by hand at 200px tall
   was never going to happen.

   What replaces it is the Legion's route east — the sequence of stations along
   the Trans-Siberian that the thesis is built on. It is drawn as a railway
   diagram rather than a map, because a diagram claims only what it can
   support: that these places came in this order. No coastlines, no distances,
   no borders that would have to be right. */
/* The Prague drawing that closes the Background column.

   Held back to 0.62 rather than shown at full strength. It is a genuinely
   detailed illustration and the last thing in a column of biography — at full
   contrast it stopped being a tailpiece and started competing with the CV link
   above it. The hairline is 0.4pt in the source, which at this width lands
   just under a device pixel, so the browser's own antialiasing does the rest
   of the softening. */
/* Prague, as a watermark rather than a tailpiece.

   It sits behind the foot of the Background column, wider than the column and
   bled off both edges, with the Full CV link sitting on top of it. Widening it
   past the column is what makes it read as a watermark and not as a picture
   that happens to be last: a watermark has no edges you can see.

   .col already carries overflow: hidden, which does the bleeding for us.

   It carries the .watermark class as well as .prague, and that is load-bearing:
   `.col > *:not(.watermark)` forces every other child to position: relative,
   which silently cancelled the absolute positioning here until the class was
   added. */
.prague {
  left: 50%;
  bottom: -3%;
  width: 126%;
  max-width: none;
  height: auto;
  transform: translateX(-50%);
  /* Three passes at a single opacity all failed, because one number cannot
     answer two different questions. 0.2 drew the city well but ran the spires
     through the Full CV link; 0.1 kept the text clean and left the city barely
     there; 0.16 split the difference badly enough to look like nothing at all.

     So: back to the density that worked, with a mask taking the edge off only
     where it has to. Measured rather than assumed — the column's text runs to
     y=3055 and the drawing spans 2506 to 3168, so the words sit over its upper
     five sixths, not its lower one. The gradient is therefore lightest at the
     TOP and reaches full strength at the base, which is the opposite of the
     first attempt at this and the reason that one made no difference. */
  opacity: 0.2;
  -webkit-mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.72) 45%, #000 80%);
  mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.72) 45%, #000 80%);
}

/* --- 11. Research entries --------------------------------------------- */

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

.entry {
  padding: calc(var(--step) * 0.95) 0;
  border-top: var(--hair) solid var(--rule-faint);
  max-width: var(--measure);
}

.entry:first-child { border-top: 0; padding-top: 0; }

/* Flex rather than a run of inline spans, for the sake of the outlined
   .status tags at the end of the row. Inline, a tag that wrapped to a second
   line carried its own left margin onto that line, so it started seven pixels
   inside the text margin instead of on it, and the two lines were separated by
   nothing but leading — a bordered box hanging below and to the right of the
   metadata it belongs to. As flex items the tags wrap flush left and row-gap
   gives the second line room to clear the boxes above it.

   align-items: baseline, not the stretch default: the tags have a border and
   padding and the plain fields do not, and only baseline alignment sets the
   two kinds of text on one line. */
.entry-meta {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 0.55em;
  row-gap: 0.4rem;
  font-size: 0.88rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums lining-nums;
}

/* Separators between metadata fields come from CSS, so adding or removing a
   <span> never leaves a stray middot behind.

   Only the right side is a margin now — the column-gap above supplies the
   space on the left, and the pair together comes to the same 0.55em either
   side the middot had when this row was inline. */
.entry-meta > * + *::before {
  content: "·";
  margin-right: 0.55em;
  color: var(--rule);
}

.entry details { margin-top: 0.7rem; font-size: 0.95rem; }

.entry summary {
  cursor: pointer;
  font-size: 0.76rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--cu-navy);
  width: fit-content;
  /* The native disclosure triangle is a filled arrow that belongs to no part
     of this design. Suppress it (three ways, for three browser families) and
     substitute the site's lozenge: hollow when closed, filled when open. */
  display: flex;
  align-items: center;
  gap: 0.6em;
  list-style: none;
}

.entry summary::-webkit-details-marker { display: none; }
.entry summary::marker { content: ""; }

.entry summary::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border: var(--hair) solid var(--accent);
  transform: rotate(45deg);
  transition: background-color 140ms ease;
}

.entry details[open] summary::before { background: var(--accent); }
.entry summary:hover { color: var(--accent); }
.entry details[open] summary { margin-bottom: 0.6rem; }

.entry details p {
  margin: 0;
  padding-left: 1rem;
  border-left: var(--hair) solid var(--rule);
  color: var(--ink-soft);
}

.entry-links {
  margin: 0.8rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.15rem;
  font-size: 0.76rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.status {
  font-size: 0.7rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--accent);
  border: var(--hair) solid var(--rule);
  padding: 0.1rem 0.45rem;
  white-space: nowrap;
}

/* No middot on either side of a tag: it is a box, not another field of the
   sentence. The gap between it and its neighbours is the container's
   column-gap, which — unlike the margin this used to carry — does not reappear
   at the start of a line the tag has wrapped onto. */
.entry-meta > .status + *::before,
.entry-meta > * + .status::before { content: none; margin: 0; }

/* State tags inside a CV row, between the headline and the description. They
   wrap as a group rather than inline with the text, so two of them on a phone
   break onto their own line instead of pushing the row's measure out. */
.status-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.4rem;
  margin: 0.45rem 0 0.15rem;
}

/* --- 12. CV rows ------------------------------------------------------ */

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

.cv-row {
  display: grid;
  grid-template-columns: 8em 1fr;
  gap: 0 1.5rem;
  padding: 0.85rem 0;
  border-top: var(--hair) solid var(--rule-faint);
}

.cv-row:first-child { border-top: 0; }

.cv-when {
  font-size: 0.86rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
  padding-top: 0.28em;
}

.cv-what > strong { font-weight: 600; color: var(--heading); }

/* A collapsed detail on a CV row: the full description from the PDF CV, and
   the module lists under each degree. Same lozenge disclosure as the research
   abstracts, so the site has one "there is more here" signal rather than two.
   Kept as native <details> — still no JavaScript anywhere. */
.cv-detail { margin-top: 0.5rem; }

.cv-detail summary {
  cursor: pointer;
  font-size: 0.7rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--cu-navy);
  width: fit-content;
  display: flex;
  align-items: center;
  gap: 0.55em;
  list-style: none;
}

.cv-detail summary::-webkit-details-marker { display: none; }
.cv-detail summary::marker { content: ""; }

.cv-detail summary::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border: var(--hair) solid var(--accent);
  transform: rotate(45deg);
  transition: background-color 140ms ease;
}

.cv-detail[open] summary::before { background: var(--accent); }
.cv-detail summary:hover { color: var(--accent); }
.cv-detail[open] summary { margin-bottom: 0.65rem; }

.cv-detail > :not(summary) {
  padding-left: 1rem;
  border-left: var(--hair) solid var(--rule);
}

.cv-detail p {
  margin: 0 0 0.6rem;
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--ink-soft);
}

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

/* Module lists. Two columns where there is room — a single column of eleven
   course names is a wall, and these are meant to be scanned. */
.course-list {
  margin: 0 0 0.6rem;
  padding-left: 1rem;
  border-left: var(--hair) solid var(--rule);
  list-style: none;
  columns: 2;
  column-gap: 2rem;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

.course-list li {
  break-inside: avoid;
  margin-bottom: 0.32rem;
  padding-left: 0.85rem;
  position: relative;
}

/* A small navy lozenge as the bullet, matching the rest of the ornament. */
.course-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 4px;
  height: 4px;
  background: var(--cu-navy);
  opacity: 0.55;
  transform: rotate(45deg);
}

.course-year {
  display: block;
  margin: 0 0 0.45rem;
  font-size: 0.7rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

@media (max-width: 48em) {
  .course-list { columns: 1; }
}

.cv-where {
  display: block;
  font-size: 0.92rem;
  color: var(--ink-soft);
}

/* A second .cv-where under the first is the "what I actually did there" line:
   the post is one thing, the work is another, and running them together behind
   a middot made a single unreadable sentence out of two facts. */
.cv-where + .cv-where { margin-top: 0.4rem; }

/* --- 13. Site footer -------------------------------------------------- */

.site-footer {
  background: var(--paper-alt);
  border-top: var(--hair) solid var(--rule);
}

.footer-inner {
  display: grid;
  /* The last column carries THREE institutional marks side by side and needs
     real width for them. At 1.1fr it had roughly 330px against the ~390px the
     row wants, so the Academy mark was wrapping onto a line of its own and the
     three logos were rendering across four rows. Measured, then widened. */
  grid-template-columns: 0.85fr 0.44fr 0.72fr 2fr;
  gap: clamp(1.75rem, 3vw, 3.5rem);
  padding-block: clamp(2.25rem, 4vw, 3.5rem);
  align-items: start;
}

/* Signature block: the page signs off with the name and one line of field
   and place, the way a letter does. It replaced a film quote, which read as
   a student touch on a site meant for admissions readers. */
.footer-sig {
  font-size: 0.86rem;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--cu-navy);
  margin: 0 0 0.45rem;
}

.footer-sig-line {
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--ink-faint);
  margin: 0 0 0.9rem;
}

.footer-list {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.86rem;
}

.footer-list li + li { margin-top: 0.4rem; }

.footer-contact {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.9rem;
}

.footer-contact li {
  display: grid;
  grid-template-columns: 1.35rem 1fr;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
  line-height: 1.5;
}

.footer-contact .orn {
  width: 15px;
  height: 15px;
  margin-top: 0.42em;
  color: var(--accent);
}

/* Charles University emblem, in the footer beside the affiliation line.

   Referenced with <img src> rather than inlined: it is 102 KB of intricate
   vector, so inlining would put it in every one of the five HTML pages instead
   of being fetched and cached once. That is also why CU_NAVY is baked into the
   file — an <img> renders in an isolated context where currentColor would
   resolve to black.

   Sized generously at 132px because the seal is genuinely detailed: the gothic
   legend ring and the diaper background turn to mush much below that, and a
   muddy institutional emblem is worse than none. */
/* Centred against the footer's full height rather than pinned to its top.
   The other three columns are blocks of text that start at the same baseline;
   this one is a row of marks of three different heights, and top-aligning it
   left the group sitting high with the whole lower half of the column empty. */
.footer-affil {
  justify-self: end;
  align-self: center;
  text-align: center;
}

/* Two marks, and they are not the same shape: the Charles seal is tall and
   circular with a legend ring, the CERGE-EI wordmark is wide and horizontal.
   Matching them on width would leave the seal tiny; they are matched on
   optical weight instead, and separated by a hairline so neither reads as a
   modifier of the other. */
/* `flex: none` and `max-width: none` are load-bearing, not tidying.

   Both marks are sized on one axis and left `auto` on the other, so the
   browser derives the missing dimension from the file's aspect ratio. But
   these are flex items, and flex items shrink: when the column was too narrow
   the anchors were compressed, `img { max-width: 100% }` from the reset then
   capped the image's width — and because the height was still pinned at its
   explicit value, nothing preserved the ratio. The Charles seal was rendering
   at 53px wide by 132px tall against a natural ratio of 0.92, i.e. squashed to
   less than half its width. It had been arriving that way at every size. */
.footer-logos {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0.85rem;
  margin-bottom: 0.7rem;
}

/* The hairline separators disappear when the row wraps — a vertical rule
   between two marks that are no longer side by side is just a stray mark. */
@media (max-width: 62em) {
  .footer-logo-rule { display: none; }
}

.footer-logos a { flex: none; }
.footer-logos img { max-width: none; }

/* The marks link to the two institutions. No underline — an underlined logo
   looks like a broken image — so the affordance is carried by opacity instead:
   they sit back at 0.8 and come forward to full strength on hover and on
   keyboard focus. */
.footer-logos a {
  display: block;
  text-decoration: none;
  border-radius: 2px;
}

.footer-logos img {
  opacity: 0.8;
  transition: opacity 140ms ease;
}

.footer-logos a:hover img,
.footer-logos a:focus-visible img { opacity: 1; }

/* Two of the three marks are now the same construction — a device above its
   wordmark — so they are matched on HEIGHT and centred on a common axis. The
   Academy's horizontal lockup was swapped for its centred one precisely to
   make this possible; side by side with two stacked marks it had been sitting
   on no shared line at all.

   CERGE-EI is the exception and stays smaller: it is a wordmark with no device
   above it, so matching its height to the other two would set the type
   enormous. It is matched on optical weight instead. */
.footer-logo-cu    { width: auto; height: 122px; }
.footer-logo-cas   { width: auto; height: 106px; }
.footer-logo-cerge { width: 158px; height: auto; }

.footer-logo-rule {
  width: var(--hair);
  height: 92px;
  align-self: center;
  background: var(--rule);
}

.footer-affil-note {
  margin: 0;
  font-size: 0.68rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 1.55;
}


.footer-bar {
  border-top: var(--hair) solid var(--rule);
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.footer-bar .shell {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  justify-content: space-between;
  padding-block: 1rem;
}

.footer-bar p { margin: 0; }

.footer-bar ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 1.5rem;
}

/* --- 14. Responsive --------------------------------------------------- */

/* Two steps. At 62em the hero and the multi-column bands stack, because three
   columns of prose stop being readable. At 40em the remaining two-column
   grids collapse and the type tightens. */

@media (max-width: 62em) {
  .hero-inner,
  .cols-3,
  .cols-2,
  .feature,
  .page-grid {
    grid-template-columns: 1fr;
  }

  /* Identity block — name, ornament, and what he is — then the photograph,
     then the biography. Ordering cannot do this: the figure is a sibling of
     the whole text block, so `order` could only put it before or after all of
     it. The identity block is its own grid item precisely so the areas can
     interleave the three. */
  .hero-inner {
    grid-template-areas:
      "id"
      "figure"
      "rule"
      "text";
  }

  /* Two rules, fencing the photograph.
     The first one moves: in the wide layout it sits between the name and the
     affiliation, but stacked it belongs BELOW the affiliation, because name
     and affiliation are one thought and the rule's job here is to close them
     off from the picture. Flex order does it without touching the markup, so
     the source order stays the one that reads correctly to a screen reader.
     The second rule then closes the picture off from the prose. */
  .hero-id { display: flex; flex-direction: column; }
  .hero-id .divider { order: 1; margin: var(--step) 0; }
  .hero-id .eyebrow { margin-bottom: 0; }
  /* :last-child is DOM order, so this is the eyebrow, not the rule above. */
  .hero-id > :last-child { margin-bottom: 0; }

  .hero-rule {
    display: block;
    grid-area: rule;
    margin: var(--step) 0;
  }


  /* Zero, and deliberately so. This used to be 0.4rem top / 2.25rem bottom,
     from when the photograph sat between the name and the prose with nothing
     between them and had to make its own air. The two rules do that job now,
     and the leftover asymmetry was putting 87px under the picture against 55
     above it. The <figure> inside still carries its own symmetric margin. */
  .hero-figure { margin-block: 0; }

  /* The corner marks are BACKGROUND images on .hero-portrait, positioned at
     0 and 100% of its padding box — so the frame is only in the right place
     if the container is the same width as the photograph inside it.

     Capping the image at 280px while the container stayed at 430px left the
     lozenges floating about 75px clear of the picture on each side, which is
     what they were doing on every phone and every tablet held upright. Cap the
     CONTAINER instead and let the image fill it.

     The mat is kept, just narrower — 0.55rem against 0.8rem. Removing it
     altogether made the marks sit half on top of the picture; keeping it means
     the FRAME lines up with the text margin and the photograph is inset evenly
     inside it on all four sides, which is the same logic as the wide layout.

     margin-inline: 0 because everything else in this column is flush left. */
  .hero-portrait {
    padding: 0.55rem;
    max-width: min(340px, 100%);
    margin-inline: 0;
  }

  .feature { gap: 2.25rem; }

  .page-grid { gap: 1.75rem; }

  /* column-gap only, and row-gap to zero, so the two rules fencing the
     photograph sit in an even rhythm. With the `gap` shorthand here the four
     gaps in the stacked hero came from three sources stacking — this row-gap,
     each rule's own margin, and the <figure>'s default block margin — which
     put 113px under the photograph against 28 above it. Every gap is one
     --step now, with only the figure's own margin adding to it, symmetrically
     on both sides. */
  .hero-inner { column-gap: 2.5rem; row-gap: 0; }

  /* Stacked columns need horizontal rules instead of vertical ones. */
  .col {
    border-left: 0;
    border-top: var(--hair) solid var(--rule);
    padding-inline: 0;
    padding-block: clamp(2.5rem, 6vw, 3.25rem);
  }

  /* Only the RULE comes off the first stacked column. It used to lose its top
     padding as well, which left "Selected work" pressed against the band above
     it with almost no air while every other stacked column got a full measure.
     The border is what would have been redundant at the top of the band — the
     space never was. */
  .col:first-child { border-top: 0; }

  .footer-inner { grid-template-columns: 1fr 1fr; }
  .footer-affil { justify-self: start; text-align: left; }

  /* All three marks on one line, as on a wide screen.

     The sizing belongs at THIS breakpoint, not at 40em: the footer collapses to
     two columns here, which leaves the affiliation block about 340px, and the
     full-size marks want roughly 480. Left at desktop size they paired two and
     dropped the third onto a row of its own — an arrangement that reads as an
     accident. Roughly 70 + 100 + 86 plus two gaps is about 285, which fits
     every width from 320px up.

     The marks no longer shrink to fit (see .footer-logos), so they have to be
     told to be smaller rather than allowed to squash. Heights are matched
     between the two stacked marks; the wordmark is set on optical weight,
     exactly as on the wide layout. */
  .footer-logos {
    justify-content: flex-start;
    gap: 0.85rem;
    align-items: center;
    flex-wrap: nowrap;
  }

  .footer-logo-cu    { height: 76px; }
  .footer-logo-cas   { height: 62px; }
  .footer-logo-cerge { width: 100px; }
}

@media (max-width: 40em) {
  .header-inner { padding-block: 1rem; }

  .brand { gap: 0.45rem; }
  .brand-mark { height: 2.4rem; }
  .brand-name { font-size: 1.2rem; }

  .site-nav ul { gap: 0.4rem 1.5rem; }
  .site-nav a { font-size: 0.72rem; letter-spacing: 0.13em; }

  .footer-inner { grid-template-columns: 1fr; }

  /* Logo sizing lives in the 62em block — that is where the footer collapses
     and where the row has to be made to fit. Nothing to add here. */

  .cv-row { grid-template-columns: 1fr; gap: 0.15rem; }
  .cv-when { padding-top: 0; }

  /* Selected work, on a phone. Each entry's title runs to three lines at this
     width, so the entries need clearly more air BETWEEN them than the lines
     have within them — otherwise three stacked three-line titles read as one
     paragraph. More padding on the row, and a deliberate rhythm inside it:
     title, then venue, then a wider gap before the buttons, which are the one
     thing in the entry a thumb is aiming for. */
  .pub {
    grid-template-columns: 40px 1fr;
    gap: 0 0.85rem;
    padding: 1.6rem 0;
  }

  .pub-thumb { width: 40px; height: 40px; margin-top: 2px; }
  .pub-title { font-size: 1.02rem; line-height: 1.34; margin-bottom: 0.4rem; }
  .pub-venue { line-height: 1.5; margin-bottom: 0; }
  .pub .pill-row { margin-top: 0.85rem; }

  /* Nine years of monthly data cannot shrink to a phone's width — at 320px the
     axis labels land at about three pixels. The figure scrolls sideways inside
     its own box instead, which keeps it readable without the page itself ever
     scrolling horizontally. */
  .chart-scroll { overflow-x: auto; }
  .chart { min-width: 560px; }
}

/* --- 15. Print -------------------------------------------------------- */

/* Aimed squarely at the CV: black on white, no navigation, no ornament, and
   link targets spelled out because paper has no hover. */

@media print {
  :root {
    --paper: #FFFFFF;
    --paper-alt: #FFFFFF;
    --ink: #000000;
    --ink-soft: #222222;
    --ink-faint: #444444;
    --rule: #999999;
    --rule-faint: #BBBBBB;
    --heading: #000000;
    --display: #000000;
    --accent: #000000;
    --link: #000000;
  }

  /* Back to normal flow: flex containers break across printed pages badly,
     and there is no viewport to fill. */
  body {
    display: block;
    min-height: 0;
    font-size: 10.5pt;
    line-height: 1.45;
  }

  .skip,
  .site-nav,
  .divider,
  .hero-figure,
  .site-footer,
  /* The base class, not the individual ones. This said `.skyline` and had
     said so since before that element was renamed to .prague, so the Prague
     drawing had quietly been printing; naming .watermark instead fixes that
     and covers the seal and the lion without needing a third entry here.
     A 6%-opacity drawing is a grey smear on paper at the best of times, and
     an inkjet renders it as nothing at all. */
  .watermark,
  .brand-mark,
  .feature-figure,
  .pub-thumb,
  .no-print { display: none !important; }

  .band { border-bottom: 0; }
  .shell { max-width: none; padding-inline: 0; }
  .band-pad { padding-block: 0; }
  /* The height was reserved for a drawing that print has just hidden. */
  .lacuna { min-height: 0; }

  /* The CV prints from .page-grid. Keeping the two columns would spend a
     third of the sheet on a title, and a sticky aside means nothing on
     paper — so the title runs above the content instead. */
  .page-grid { grid-template-columns: 1fr; gap: 0; }
  .page-aside { position: static; }
  .page-body { max-width: none; }

  .col {
    border: 0;
    padding: 0;
    break-inside: avoid;
  }

  .site-header { border-bottom: 1px solid #999; margin-bottom: 1.2rem; }

  a { text-decoration: none; }

  /* Spell out external targets only — internal nav is hidden anyway. */
  main a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    color: #444;
    word-break: break-all;
  }

  .entry details,
  .cv-detail { display: block; }
  .entry summary,
  .cv-detail summary { display: none; }

  .cv-detail > :not(summary),
  .course-list { border-left: 0; padding-left: 0; }

  /* Pills lose their fill and border on paper and become plain labels; the
     URL is spelled out by the rule below, which is what actually matters when
     the reader cannot click. */
  .pill {
    border: 0;
    padding: 0;
    color: #000;
    background: none;
    clip-path: none;
  }

  /* The cartouche knockout would print as a white rectangle over the label. */
  .pill::before { display: none; }

  .pill-icon { display: none; }
  .pill-row { gap: 0.2rem 1.2rem; }

  .entry, .cv-row, .pub, h2, h3 { break-inside: avoid; }
  h2, .label { break-after: avoid; }
}
