/* ==========================================================================
   style.css - Version 1.1 - Stand 2026.09.05
   ========================================================================== */

/* ==========================================================================
   Table of contents
   Search for the section heading, not for a line number.

   Configuration - the only place to change a value of the site.
   Dark mode — colour overrides
   Webfonts
   Breakpoints (no CSS here)
   Variables - derived values and values of a single element
   Document and running text
   Headings H1-H6
   Links
   Layout rows and columns
   The widths of a picture
   Site footer
   Content modules
       Content module: article hero
       Content module: article content
       Content module: post card
       Content module: card grid
       Content module: image grid
       Content module: post list
       Content module: site header
   TEST ONLY - remove before the template ships
   ========================================================================== */

/* ==========================================================================
   Configuration
   Root values that are set, not calculated. Derived and single-element values
   live in "Variables" below.
   ========================================================================== */

:root {
  /* --- Colours ---------------------------------------------------------- */

  /* Root values: the colours that hold a hex. Everything below points at one of
     these, so a colour is set in exactly one place. */
  --primary-color: #FF2B05;      /* links, hover plates, menu hover */
  --text-color: #000000;         /* running text, headings; also submenu text */
  --background-color-light: #FFFFFF;
  --background-color-dark: #000000;

  /* Grey scale: five steps named by lightness, not by part. A grey is a pick
     from these five, never a fresh value. Two steps are held in reserve. */
  --gray-lightest-color: #F4F4F4;
  --gray-light-color: #D8D8D8;
  --gray-mid-color: #999999;      /* reserve: secondary or disabled text */
  --gray-dark-color: #333333;
  --gray-darkest-color: #1A1A1A;  /* reserve: near-black that is not the text colour */

  /* Text on a coloured ground. Kept apart though both are white today: if the
     dark ground later changes, the text on orange stays put. */
  --text-color-on-dark: #FFFFFF;     /* text/icon on a dark ground */
  --text-color-on-primary: #FFFFFF;  /* text on the orange hover plate */

  /* References: a part names the ground it carries, and points at the root
     value that says which colour that ground is. */
  --page-background-color: var(--background-color-light);
  --footer-background-color: var(--background-color-dark);
  --footer-text-color: var(--text-color-on-dark);

  /* References: link colours, in one place. They cover content links, the main
     menu and the submenu. The orange hover plate is shared (the same everywhere);
     the resting text is NOT - content and menu are orange, the submenu is black
     on its light card (it reads --text-color, not --link-color). */
  --link-color: var(--primary-color);               /* resting text on a light ground: content + menu (NOT submenu) */
  --link-on-dark-color: var(--text-color-on-dark);  /* resting text on a dark ground: footer + menu over the hero image */
  --link-hover-background-color: var(--primary-color);   /* hover plate: content, menu, submenu */
  --link-hover-color: var(--text-color-on-primary);      /* hover text: the same three */
  --submenu-background-color: var(--gray-lightest-color); /* the open submenu card; resting text on it is --text-color */

  /* References: grounds that stand off from the white page. */
  --container-background-color: var(--gray-lightest-color);
  --icon-box-background-color: var(--gray-dark-color);
  --rule-color: var(--gray-light-color);                  /* post-list divider */

  /* --- Fonts ------------------------------------------------------------ */
  /* Middle name is the metric-corrected fallback (see @font-face), so the
     webfont swap does not reflow. */
  --body-font: "Mulish", "Mulish Fallback", sans-serif;
  --heading-font: "Josefin Sans", "Josefin Sans Fallback", sans-serif;

  /* What one rem reads as; every font size is a ratio of this. */
  --root-font-size: clamp(1.25rem, 1.1rem + 0.625vw, 1.5rem); /* 24px */

  /* --- Type scale ------------------------------------------------------- */
  /* The two dials of the whole size scale. Every one of the nine ramped font
     sizes is derived from these (see THE SIZE SCALE below); change a dial and
     all nine follow, nothing else to touch. */
  --type-scale-ratio: 1.125;  /* Major Second: each desktop step over the one below */
  --type-phone-shrink: 0.82;  /* phone size of the top step as a share of its desktop size */
  --type-ramp-width: 448px;   /* how wide a window the phone sizes grow to desktop over */

  /* --- Line heights ----------------------------------------------------- */

  /* Running text tightens as the window narrows. */
  --line-height-body: clamp(
    1.4,
    1.4 + 0.2 * tan(atan2(100vw - 639px, 385px)),
    1.75
  );
  --line-height-heading: 1.2;
  --line-height-small: 1.4;

  @media (max-width: 639px) {
    --line-height-heading: 1.10;
  }

  /* --- Spacing ---------------------------------------------------------- */

  /* The dial of the layout zone: --space-6 and up are multiples of this. */
  --space-base: clamp(24px, 3.5vw, 40px);

  /* Detail steps: fixed pixels, must not shrink on a phone. */
  --space-1: 8px;
  --space-2: 10px;
  --space-3: 13px;   /* free */
  --space-4: 16px;
  --space-5: 20px;

  /* Space above a heading = its font size times this factor. */
  --rhythm-heading-factor: 1.1;

  /* --- Widths ----------------------------------------------------------- */

  /* THE PAGE WIDTH: share of the window the page takes; the leftover is the
     margin. The margin floor (--page-margin-min) always wins. */
  --page-width: 70vw; /* Dieser Wert darf von CLAUDE nicht geändert werden. Nur RS ändert hier. */
}

/* ==========================================================================
   Dark mode — colour overrides
   Rewrites only the four hex roots and the five-step grey scale; every colour
   reference follows on its own. Sits right after the :root colour block it
   overrides, so the later rule wins.

   Two ways in. The visitor's OS setting is the default; a click on the footer
   toggle overrides it and writes data-theme on <html>. So the override has to
   win from either source:

   - :root[data-theme="dark"]                 the visitor chose dark by hand.
   - the OS is dark AND no light choice was made — :not([data-theme="light"])
     inside the @media, so a hand-picked light theme beats the dark OS.

   The dark hex values live once in a --dark-* store on :root; both triggers
   only map that store onto the real colour roots, so no value is written
   twice. The light theme needs no rule of its own: with data-theme="light"
   set, neither trigger matches and the :root defaults above stand.
   ========================================================================== */

/* The dark values, defined once. These --dark-* names are not read anywhere
   in the stylesheet — they are only the store the two selectors below map onto
   the real colour roots. Written here so a value exists in exactly one place,
   no matter which of the two triggers switches the theme on. */
:root {
  /* The orange stays the brand colour but is lightened a touch so it keeps its
     punch against a dark ground instead of muddying into it. */
  --dark-primary-color: #FF5436;      /* links, hover plates, menu hover */
  --dark-text-color: #EDEDED;         /* running text, headings; also submenu text */
  --dark-background-color-light: #14140F;  /* the page ground — dark, name kept */
  --dark-background-color-dark: #000000;   /* the deepest ground, e.g. hero overlays */

  /* Grey scale: the five steps are inverted in lightness. A light-mode
     "lightest" (a ground that lifts off white) becomes a dark step that lifts
     off the near-black page; "dark" greys become light again so text and icons
     on them stay legible. Names still read by role, not by hex. */
  --dark-gray-lightest-color: #1F1F1A;  /* lifted ground: containers, submenu card */
  --dark-gray-light-color: #333330;     /* dividers, hairlines */
  --dark-gray-mid-color: #8A8A8A;       /* reserve: secondary or disabled text */
  --dark-gray-dark-color: #C9C9C9;      /* was a dark grey, flipped light on dark page */
  --dark-gray-darkest-color: #E0E0E0;   /* reserve: near-white that is not the text colour */

  /* Text on a coloured ground. White still reads on both the orange plate and
     the deepest black ground, so both keep their value. */
  --dark-text-color-on-dark: #FFFFFF;     /* text/icon on a dark ground */
  --dark-text-color-on-primary: #FFFFFF;  /* text on the orange hover plate */
}

/* Map the store onto the real roots. Whatever every rule reads by role —
   --text-color, --gray-light-color and so on — now carries the dark value.
   The one shared declaration block, referenced from both triggers below so the
   two can never fall out of step. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --primary-color: var(--dark-primary-color);
    --text-color: var(--dark-text-color);
    --background-color-light: var(--dark-background-color-light);
    --background-color-dark: var(--dark-background-color-dark);
    --gray-lightest-color: var(--dark-gray-lightest-color);
    --gray-light-color: var(--dark-gray-light-color);
    --gray-mid-color: var(--dark-gray-mid-color);
    --gray-dark-color: var(--dark-gray-dark-color);
    --gray-darkest-color: var(--dark-gray-darkest-color);
    --text-color-on-dark: var(--dark-text-color-on-dark);
    --text-color-on-primary: var(--dark-text-color-on-primary);

    /* Icon-box ground: in light mode it reads --gray-dark-color, which the
       inverted scale flips light — a white icon on a light box. Pointed instead
       at the lifted dark ground, it stays a dark box under a light icon.
       Overriding the reference, not the rule, so the icon-box rule and its
       hover keep reading one value as everywhere else. */
    --icon-box-background-color: var(--gray-light-color);
  }
}

/* Same map, driven by a hand-picked dark theme instead of the OS. Identical
   declarations to the block above; kept as its own rule because a media-query
   selector and a plain one cannot share a selector list. */
:root[data-theme="dark"] {
  --primary-color: var(--dark-primary-color);
  --text-color: var(--dark-text-color);
  --background-color-light: var(--dark-background-color-light);
  --background-color-dark: var(--dark-background-color-dark);
  --gray-lightest-color: var(--dark-gray-lightest-color);
  --gray-light-color: var(--dark-gray-light-color);
  --gray-mid-color: var(--dark-gray-mid-color);
  --gray-dark-color: var(--dark-gray-dark-color);
  --gray-darkest-color: var(--dark-gray-darkest-color);
  --text-color-on-dark: var(--dark-text-color-on-dark);
  --text-color-on-primary: var(--dark-text-color-on-primary);
  --icon-box-background-color: var(--gray-light-color);
}

/* End of configuration. Everything below is derived from these values or from
   the markup - not a setting. */

/* ==========================================================================
   Webfonts
   Google Fonts, served locally. Latin subset, woff2 only, no CDN at runtime.
   Variable fonts: the "400 700" font-weight is a range, not two values.
   ========================================================================== */

/* Body text. */
@font-face {
  font-family: "Mulish";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/mulish-var-400-700.woff2") format("woff2");
}

/* Headings H1-H6. */
@font-face {
  font-family: "Josefin Sans";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/josefin-sans-var-400-600.woff2") format("woff2");
}

/* Fallback families: local Arial with corrected metrics, so the webfont swap
   does not reflow. Numbers measured by scripts/messung-schrift-metriken.py -
   never hand-tuned; re-run it if a font file changes. */

/* Stand-in for Mulish. */
@font-face {
  font-family: "Mulish Fallback";
  src: local("Arial");
  size-adjust: 103.76%;
  ascent-override: 96.85%;
  descent-override: 24.09%;
  line-gap-override: 0%;
}

/* Stand-in for Josefin Sans. */
@font-face {
  font-family: "Josefin Sans Fallback";
  src: local("Arial");
  size-adjust: 101.92%;
  ascent-override: 73.59%;
  descent-override: 24.53%;
  line-gap-override: 0%;
}

/* ==========================================================================
   Breakpoints
   No CSS here. Two shared thresholds separate three sizes:

     Mobile    up to  639px
     Tablet    640px to 1023px
     Desktop   from  1024px

   The two conditions that carry most of the file:

     @media (max-width: 639px)  { ... }   mobile only
     @media (max-width: 1023px) { ... }   mobile and tablet together

   They stack, so tablet falls out on its own. A width only one part of the page
   reacts to is written at that part's own rule. Media queries are nested in the
   rule they belong to, wider condition first.
   ========================================================================== */

/* ==========================================================================
   Variables
   Values calculated from a dial, and values that belong to a single element.
   Nothing here is a setting. Named after their role, not their look.
   ========================================================================== */

:root {
  /* THE SIZE SCALE. Every font size is one of these steps. A "Major Second"
     scale, rem-based; px comments are the desktop reading at the default
     browser setting. Runs smallest to largest (h6 above h1) - do not reorder.

     The four steps at and below running text are a plain rem value. The nine
     above carry a ramp so large headings come down further on a phone; all nine
     share one tan(atan2()) term, so the steps never cross.

     Nothing below is a hand-computed size: every step is derived from the two
     type dials in the configuration block. --type-scale-ratio walks the desktop
     sizes up the Major Second (each step is the one below times the ratio);
     --type-phone-shrink sets how far the TOP step (display-x-large, index 9)
     drops on a phone, and every step drops by an even fraction of that - step n
     shrinks n/9 as much - so small steps move little, large ones more, on one
     smooth curve with no kink. Change a dial there, and all nine sizes follow;
     do not paste a computed rem value back in. */

  /* Per-step share the phone value loses per index: the top step (n=9) loses
     the full (1 - shrink), each step below loses that times n/9. */
  --type-shrink-per-step: calc((1 - var(--type-phone-shrink)) / 9);

  /* The shared ramp: 0 at 320px, 1 well before the top of the range, so the
     phone value grows into the desktop value with no jump at a breakpoint. The
     nine steps all multiply this one term, so they never cross. The ramp width
     (--type-ramp-width) is a dial in the configuration block; the line-height
     ramp is deliberately separate (it starts at a different width from a
     different breakpoint). */
  --type-ramp: tan(atan2(100vw - 320px, var(--type-ramp-width)));

  --font-size-xx-small: 0.702rem;        /* 16.86px - free */
  --font-size-x-small: 0.790rem;         /* 18.96px - free */
  --font-size-small: 0.889rem;           /* 21.33px - small text, header row */
  --font-size-body: 1rem;                /* 24.00px - running text */

  /* Desktop sizes: the Major Second chain, each step = the one below * ratio.
     These are the clamp ceilings; kept as their own variables so the phone
     value and the ramp span can be figured from them per step. The px comments
     read "desktop -> phone": the size actually shown at a 24px root on a wide
     screen, then the size actually shown on the narrowest phone (320px). Two
     shrinks stack into that phone figure - the root itself falls from 24 to
     20px (--root-font-size) AND these steps ramp down on top of it - so a
     heading drops proportionally more than body text. */
  --type-max-h6:              calc(var(--font-size-body) * var(--type-scale-ratio)); /* 27.00 -> 22.05px */
  --type-max-h5:              calc(var(--type-max-h6)    * var(--type-scale-ratio)); /* 30.38 -> 24.30px */
  --type-max-h4:              calc(var(--type-max-h5)    * var(--type-scale-ratio)); /* 34.17 -> 26.77px */
  --type-max-h3:              calc(var(--type-max-h4)    * var(--type-scale-ratio)); /* 38.44 -> 29.47px */
  --type-max-h2:              calc(var(--type-max-h3)    * var(--type-scale-ratio)); /* 43.25 -> 32.44px */
  --type-max-h1:              calc(var(--type-max-h2)    * var(--type-scale-ratio)); /* 48.65 -> 35.68px */
  --type-max-display:         calc(var(--type-max-h1)    * var(--type-scale-ratio)); /* 54.74 -> 39.23px */
  --type-max-display-large:   calc(var(--type-max-display)       * var(--type-scale-ratio)); /* 61.58 -> 43.11px */
  --type-max-display-x-large: calc(var(--type-max-display-large) * var(--type-scale-ratio)); /* 69.28 -> 47.34px */

  /* The nine ramped steps. Per step n: the ceiling is --type-max-*, the phone
     value is ceiling * (1 - shrink-per-step * n), and the ramp span between
     them is ceiling * shrink-per-step * n. Only the ceiling and the index n
     differ from step to step. */
  --font-size-h6: clamp(
    calc(var(--type-max-h6) * (1 - var(--type-shrink-per-step) * 1)),
    calc(var(--type-max-h6) * (1 - var(--type-shrink-per-step) * 1) + var(--type-max-h6) * var(--type-shrink-per-step) * 1 * var(--type-ramp)),
    var(--type-max-h6)
  );                                     /* 27.00px - H6, post list title */
  --font-size-h5: clamp(
    calc(var(--type-max-h5) * (1 - var(--type-shrink-per-step) * 2)),
    calc(var(--type-max-h5) * (1 - var(--type-shrink-per-step) * 2) + var(--type-max-h5) * var(--type-shrink-per-step) * 2 * var(--type-ramp)),
    var(--type-max-h5)
  );                                     /* 30.38px - H5, post card title, hero subtitle */
  --font-size-h4: clamp(
    calc(var(--type-max-h4) * (1 - var(--type-shrink-per-step) * 3)),
    calc(var(--type-max-h4) * (1 - var(--type-shrink-per-step) * 3) + var(--type-max-h4) * var(--type-shrink-per-step) * 3 * var(--type-ramp)),
    var(--type-max-h4)
  );                                     /* 34.17px - H4 */
  --font-size-h3: clamp(
    calc(var(--type-max-h3) * (1 - var(--type-shrink-per-step) * 4)),
    calc(var(--type-max-h3) * (1 - var(--type-shrink-per-step) * 4) + var(--type-max-h3) * var(--type-shrink-per-step) * 4 * var(--type-ramp)),
    var(--type-max-h3)
  );                                     /* 38.44px - H3 */
  --font-size-h2: clamp(
    calc(var(--type-max-h2) * (1 - var(--type-shrink-per-step) * 5)),
    calc(var(--type-max-h2) * (1 - var(--type-shrink-per-step) * 5) + var(--type-max-h2) * var(--type-shrink-per-step) * 5 * var(--type-ramp)),
    var(--type-max-h2)
  );                                     /* 43.25px - H2 */
  --font-size-h1: clamp(
    calc(var(--type-max-h1) * (1 - var(--type-shrink-per-step) * 6)),
    calc(var(--type-max-h1) * (1 - var(--type-shrink-per-step) * 6) + var(--type-max-h1) * var(--type-shrink-per-step) * 6 * var(--type-ramp)),
    var(--type-max-h1)
  );                                     /* 48.65px - H1 */
  --font-size-display: clamp(
    calc(var(--type-max-display) * (1 - var(--type-shrink-per-step) * 7)),
    calc(var(--type-max-display) * (1 - var(--type-shrink-per-step) * 7) + var(--type-max-display) * var(--type-shrink-per-step) * 7 * var(--type-ramp)),
    var(--type-max-display)
  );                                     /* 54.74px - free */
  --font-size-display-large: clamp(
    calc(var(--type-max-display-large) * (1 - var(--type-shrink-per-step) * 8)),
    calc(var(--type-max-display-large) * (1 - var(--type-shrink-per-step) * 8) + var(--type-max-display-large) * var(--type-shrink-per-step) * 8 * var(--type-ramp)),
    var(--type-max-display-large)
  );                                     /* 61.58px - free */
  --font-size-display-x-large: clamp(
    calc(var(--type-max-display-x-large) * (1 - var(--type-shrink-per-step) * 9)),
    calc(var(--type-max-display-x-large) * (1 - var(--type-shrink-per-step) * 9) + var(--type-max-display-x-large) * var(--type-shrink-per-step) * 9 * var(--type-ramp)),
    var(--type-max-display-x-large)
  );                                     /* 69.28px - hero title */

  /* THE SPACING SCALE, layout zone: nine steps, each --space-base times a
     factor, following the window. Detail steps (--space-1..5) are in the
     configuration block. Optical corrections keep their own number, marked
     where they stand. px comments are the desktop reading. */
  --space-6: calc(var(--space-base) * 0.65);  /* layout -  26px desktop */
  --space-7: calc(var(--space-base) * 0.8);   /* layout -  32px desktop */
  --space-8: calc(var(--space-base) * 1);     /* layout -  40px desktop */
  --space-9: calc(var(--space-base) * 1.25);  /* layout -  50px desktop */
  --space-10: calc(var(--space-base) * 1.5);  /* layout -  60px desktop */
  --space-11: calc(var(--space-base) * 2);    /* layout -  80px desktop */
  --space-12: calc(var(--space-base) * 2.5);  /* layout - 100px desktop - free */
  --space-13: calc(var(--space-base) * 3);    /* layout - 120px desktop */
  --space-14: calc(var(--space-base) * 4);    /* layout - 160px desktop - free */

  /* Vertical rhythm of the running text, fractions of the body line height:
     half a line binds a paragraph to the next block, a quarter binds lines that
     belong together. rem; desktop reading kept though the body value tightens
     on a phone. --rhythm-heading-factor is in the configuration block. */
  --rhythm-text: 0.875rem;  /* 21.00px - half a line: paragraph, list, below any heading */
  --rhythm-tight: 0.4375rem; /* 10.50px - quarter of a line: list item, list title */

  /* Fullscreen icon size. A height, not a font size, so not on the scale.
     Its own variable because .fullscreen-toggle needs it twice. */
  --icon-size: 22px;

  /* Padding of the orange hover plate, two versions (the two fonts sit
     differently). Which applies is decided in "Links". The overhang and side
     have their own names because a few places subtract them again. */
  --link-plate-overhang: 4px;
  --link-plate-side: 4px;
  --link-plate-padding-body: 2px var(--link-plate-side) var(--link-plate-overhang) var(--link-plate-side);
  --link-plate-padding-headline: 8px var(--link-plate-side) var(--link-plate-overhang) var(--link-plate-side);

  /* THE SMALLEST SCREEN-EDGE MARGIN. Set by RS; nothing may fall below it. */
  --page-margin-min: 30px;

  /* THE PAGE WIDTH AS DRAWN: RS' setting, held back to what the minimum margin
     leaves room for. Every full-width element reads this, not --page-width. */
  --page-width-effective: min(
    var(--page-width),
    100vw - 2 * var(--page-margin-min)
  );

  /* On a phone the setting has no say: the page takes the whole window less the
     minimum margin. The page jumps here, on purpose (RS, 2026.08.28). */
  @media (max-width: 639px) {
    --page-width-effective: calc(100vw - 2 * var(--page-margin-min));
  }

  /* THE PAGE MARGIN: half of what the page width leaves over, with the floor
     winning against the share. Read by anything that DRAWS the page edge. */
  --page-gutter: max(
    var(--page-margin-min),
    (100vw - var(--page-width-effective)) / 2
  );

  /* THE PAGE MARGIN AS A DISTANCE, with a ceiling: for the two places that use
     it as spacing, not to line up with the edge (reading-width ceiling,
     --layout-gap). The ceiling does not bind at the widths those reach. */
  --page-gutter-capped: min(var(--page-gutter), 160px);

  /* THE WIDE-ROW MARGIN: a share of the window, capped at --space-11, same
     floor as the page margin. Its own number, so it stays put when --page-width
     is turned. */
  --layout-row-padding-inline: max(
    var(--page-margin-min),
    min(8vw, var(--space-11))
  );

  /* THE CONTAINER'S VERTICAL DISTANCES. The row has none; the container draws
     all of it. Padding and row gap match because they do the same job. */
  --container-top-bottom-margin: var(--space-7);
  --container-top-bottom-padding: var(--space-6);
  --container-row-gap: var(--space-6);

  /* THE STACKING ORDER. Modules carry no step (they isolate themselves); header
     above content; the back-to-top button above both but below the open menu,
     so the full-screen menu overlay covers it; open menu above everything, its
     button above the menu. Gaps leave room for a later cookie notice or
     dialogue. */
  --layer-header: 10;
  --layer-scroll-top: 90;
  --layer-menu: 100;
  --layer-menu-button: 101;
}

/* ==========================================================================
   Document and running text
   The tags every page brings, styled once: box model, root size on html, page
   frame on body and main, paragraph and list rhythm. No class named here.
   ========================================================================== */

/* border-box: padding and border count inside the width. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Root size everything sizes against; rem-based. */
html {
  font-size: var(--root-font-size);
}

body {
  margin: 0;
  background-color: var(--page-background-color);
  font-family: var(--body-font);
  font-weight: 400;
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  color: var(--text-color);

  /* Sticky footer: body is a full-height flex column, main (flex: 1) eats the
     rest, so the footer sits at the bottom on a short page. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;

  /* The page measures itself so the parts inside can ask their room (footer is
     first). inline-size, not size, or the sticky footer breaks. */
  container-type: inline-size;
  container-name: page;
}

main {
  flex: 1;

  /* A breakout column steps to 50vw (counts the scrollbar) and overhangs main
     by half a scrollbar. clip cuts the overhang and takes it out of the
     document width; overflow-y stays visible. */
  overflow-x: clip;
}

/* No gap above a paragraph, half a line below. */
p {
  margin-top: 0;
  margin-bottom: var(--rhythm-text);
}

/* Running-text lists follow the paragraph rhythm; .no-text-rhythm exempts a
   layout list (menu, footer, post list) that brings its own distances.
   :where() keeps the exception weightless, so a module rule still wins. */
main :where(ul, ol):not(:where(.no-text-rhythm, .no-text-rhythm *)) {
  margin-top: 0;
  margin-bottom: var(--rhythm-text);
}

main :where(ul, ol):not(:where(.no-text-rhythm, .no-text-rhythm *)) > li {
  margin-bottom: var(--rhythm-tight);
  line-height: var(--line-height-small);  /* tighter than the body, so short items do not read as far apart as paragraphs */
}

main :where(ul, ol):not(:where(.no-text-rhythm, .no-text-rhythm *)) > li:last-child {
  margin-bottom: 0;
}

/* ==========================================================================
   Headings H1-H6
   All Josefin Sans, black, tight line height. H1 is the only weight that
   differs: 600 instead of 400.
   ========================================================================== */

h1, h2, h3, h4, h5, h6 {
  font-family: var(--heading-font);
  font-weight: 400;
  color: var(--text-color);
  line-height: var(--line-height-heading);
  letter-spacing: 0;
  text-transform: none;
}

/* Gap ABOVE = heading size times the rhythm factor; gap BELOW = the paragraph
   gap, the same for all six. calc() against the variable, not em, so nesting
   does not blow the margins up. */
h1 { font-weight: 600; font-size: var(--font-size-h1); margin: calc(var(--font-size-h1) * var(--rhythm-heading-factor)) 0 var(--rhythm-text) 0; }
h2 { font-size: var(--font-size-h2); margin: calc(var(--font-size-h2) * var(--rhythm-heading-factor)) 0 var(--rhythm-text) 0; }
h3 { font-size: var(--font-size-h3); margin: calc(var(--font-size-h3) * var(--rhythm-heading-factor)) 0 var(--rhythm-text) 0; }
h4 { font-size: var(--font-size-h4); margin: calc(var(--font-size-h4) * var(--rhythm-heading-factor)) 0 var(--rhythm-text) 0; }
h5 { font-size: var(--font-size-h5); margin: calc(var(--font-size-h5) * var(--rhythm-heading-factor)) 0 var(--rhythm-text) 0; }
h6 { font-size: var(--font-size-h6); margin: calc(var(--font-size-h6) * var(--rhythm-heading-factor)) 0 var(--rhythm-text) 0; }

/* ==========================================================================
   Links
   Never underlined - the colour carries them. Orange on white; on hover an
   orange plate appears behind the text, letters turning white, padding making
   the plate bigger than the letters. Visited looks unvisited. :focus-visible
   sits next to :hover throughout so a keyboard user gets the same plate.

   Three rules building on one another:
   1. standard colour, no underline, the plate and its padding.
   2. negative side margins in running text, so the plate does not shift the line.
   3. taller padding for links in the heading font.
   ========================================================================== */

/* 1 - the standard for every link. */
a:link,
a:visited {
  color: var(--link-color);
  text-decoration: none;
  padding: var(--link-plate-padding-body);
}

a:hover,
a:active,
a:focus-visible {
  background-color: var(--link-hover-background-color);
  color: var(--link-hover-color);
}

/* 2 - running text only: side margins that take back the plate's side padding,
   so the line stays aligned. Tied to the tags that carry running text. */
p a:link, p a:visited,
li a:link, li a:visited,
blockquote a:link, blockquote a:visited,
figcaption a:link, figcaption a:visited,
td a:link, td a:visited,
th a:link, th a:visited {
  margin-left: calc(-1 * var(--link-plate-side));
  margin-right: calc(-1 * var(--link-plate-side));
}

/* 3 - the taller padding for links in the heading font. Pseudo classes written
   out because the rule competes with the standard above. */
h1 a:link, h1 a:visited,
h2 a:link, h2 a:visited,
h3 a:link, h3 a:visited,
h4 a:link, h4 a:visited,
h5 a:link, h5 a:visited,
h6 a:link, h6 a:visited {
  padding: var(--link-plate-padding-headline);
}

/* 4 - a link that wraps nothing but an image is not a text link and must not get
   the orange hover plate. :has(> img:only-child) hits exactly those links; a link
   with text beside the image (an icon plus a word) keeps its plate. Cut padding
   and plate the way an image-grid item does, block so it leaves no gap below. */
a:has(> img:only-child):link,
a:has(> img:only-child):visited,
a:has(> img:only-child):hover,
a:has(> img:only-child):active,
a:has(> img:only-child):focus-visible {
  display: block;
  padding: 0;
  margin: 0;
  background-color: transparent;
}

/* Skip link: the first thing a keyboard user reaches, the way past the whole
   header straight to the content. It is out of sight until focused - lifted off
   the top edge, not display:none, so it stays in the tab order and can be
   focused. On focus it drops onto the top-left corner as an orange plate. */
.skip-link {
  position: fixed;
  top: 0;
  left: 0;
  transform: translateY(-100%);
  z-index: 3;
  background-color: var(--primary-color);
  color: var(--text-color-on-primary);
  padding: var(--space-2) var(--space-4);
}

.skip-link:focus {
  transform: translateY(0);
}

/* ==========================================================================
   Layout rows and columns
   A page is a sequence of rows. A row holds one or more columns; every module
   sits in a column. Both are <div>: .layout-row and .layout-col.

   Column count and widths are stated once, at the row. The row is capped at the
   page width and centred, so its column begins on the header's line. Breaking
   out is a negative margin of --layout-row-breakout, each kind of row setting
   its own value. No nesting - no row in a row, no column in a column.
   ========================================================================== */

/* THE CONTAINER: a screen-wide shell around one or more rows. Draws a
   background that reaches both edges while the content keeps its margin, and is
   the one place that draws vertical spacing around rows (margin out, padding in,
   gap between rows). A plain block, not a grid (which would collapse the centred
   row). A .layout-row always stands in a .container. */
.container {
  margin-block: var(--container-top-bottom-margin);
  padding-block: var(--container-top-bottom-padding);
}

/* The gap BETWEEN two rows of a container, so it never adds to the padding. */
.container > .layout-row + .layout-row {
  margin-top: var(--container-row-gap);
}

.container--background-color {
  background-color: var(--container-background-color);
}

/* The cap, and one column unless a class below says otherwise. --layout-row-
   breakout is half the overhang, read against window (50vw, counts a scrollbar)
   and row (50%, does not), so nothing has to know --page-width; the overhang on
   a scrolling page is clipped at the column below. */
.layout-row {
  --layout-row-breakout: calc(50vw - 50%);
  display: grid;
  grid-template-columns: 1fr;
  margin-inline: auto;
  max-width: var(--page-width-effective);
}

/* THE WIDE ROW: the whole screen less a margin at each edge. Takes the cap off
   and draws its own margin; reaches further out than an ordinary row but never
   the edge (unlike .layout-col--full-width). Says nothing about the columns. */
.layout-row--full-width {
  --layout-row-breakout: var(--layout-row-padding-inline);
  max-width: none;
  padding-inline: var(--layout-row-padding-inline);
}

/* The one column that reaches screen edge to edge, for a full-bleed image. Steps
   out both sides with a negative margin and takes the width back. Reads one
   variable, so it is right in both kinds of row. Stands alone in its row. */
.layout-row > .layout-col--full-width {
  margin-inline: calc(-1 * var(--layout-row-breakout));
  width: calc(100% + 2 * var(--layout-row-breakout));
}

/* Loose text in the breakout column keeps a gutter from the edge (a full-bleed
   picture wants the edge, running text does not). Direct children only, so a
   module keeps its full bleed. */
.layout-col--full-width > :is(h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote) {
  padding-inline: var(--layout-row-padding-inline);
}

/* THE COLUMN. Every column is a <div>. Width and count are decided at the row,
   so the column sets no width of its own; its one base rule below only lifts a
   browser default.

   A column is a grid item and would therefore have min-width: auto - it would
   never shrink below its widest unbreakable content. On a narrow window the grid
   track then grows past the capped row, and the content overhangs to the right
   (main's overflow-x: clip cuts it off rather than scrolling it). min-width: 0
   lifts that block: the column follows the row, and what does not fit wraps or is
   clipped. It states no width, so width stays decided at the row. */
.layout-row > .layout-col {
  min-width: 0;
}

/* The column decides where its content starts, so the first element does not let
   its top margin out. A column opening with an image begins on the same line as
   its neighbour opening with a heading. The chain of :first-child reaches through
   wrappers that carry no margin of their own (<article>, a module's outer div),
   down to the element that does. Inside the column the rhythm stands: only the
   opening element loses its top margin. */
.layout-col > :first-child,
.layout-col > :first-child > :first-child,
.layout-col > :first-child > :first-child > :first-child {
  margin-top: 0;
}

/* --------------------------------------------------------------------------
   COLUMN COUNT AND COLUMN WIDTHS

   Both stated at the ROW, so a row's shape reads off one class name. The name is
   the split in percent - .layout-row--40-60 is 40% against 60%. Two ways:

     Equal   .layout-row--50-50 and the like - an UPPER LIMIT; the browser fits
             as many equal columns as it can, no threshold measured. Fewer per
             row on a narrow screen, but every column keeps its content - the
             extras wrap to the next grid line, nothing is dropped.
     Unequal .layout-row--40-60 and the like - a fixed RATIO, the count holding
             until the row breaks to one.

   The card grid and image grid work out their own inner count.
   -------------------------------------------------------------------------- */

/* --layout-column-min: narrowest a column may get before the row drops one. One
   number for all counts. It guards a plain text column; a module that sits in a
   column brings its own, higher floor.

   --layout-gap: the gap between columns, one for every count. min() with the
   capped gutter so it still shrinks on a narrow screen.

   THE ROW IS ITS OWN CONTAINER, so the unequal rows below can ask their own room
   rather than the window width. An element cannot be styled by its own container
   query, so the query styles the columns. */
.layout-row {
  --layout-column-min: 200px;
  --layout-gap: min(var(--space-8), var(--page-gutter-capped));
  column-gap: var(--layout-gap);
  container-type: inline-size;
  container-name: layout-row;
  row-gap: var(--space-8);
}

/* EQUAL COLUMNS: as many as fit, up to the count in the class. The formula is
   written once here and reused by every step; a step sets only the two numbers
   that differ, --cols and --gap. With N = --cols and G = --gap:

     (100% - (N-1) * G) / N          the width one of N columns would have
     max(--layout-column-min, ...)   whichever is larger
     repeat(auto-fill, minmax(...))  as many as fit, sharing the remainder

   The max() drops a column wherever one would go under the minimum, no threshold
   measured. auto-fill, not auto-fit, so a short last row keeps its column width. */
.layout-row--50-50,
.layout-row--33-33-33,
.layout-row--25-25-25-25,
.layout-row--20-20-20-20-20,
.layout-row--17-17-17-17-17-17 {
  --gap: var(--layout-gap);
  grid-template-columns:
    repeat(auto-fill, minmax(max(var(--layout-column-min),
                                 (100% - (var(--cols) - 1) * var(--gap)) / var(--cols)), 1fr));
}

/* Each step names only its count; the gap is the same --layout-gap for all. */
.layout-row--50-50                 { --cols: 2; }
.layout-row--33-33-33              { --cols: 3; }
.layout-row--25-25-25-25           { --cols: 4; }
.layout-row--20-20-20-20-20        { --cols: 5; }
.layout-row--17-17-17-17-17-17     { --cols: 6; }  /* the upper limit for a row */

/* The 100% is the row's content box, capped at the page width, so only the gaps
   between columns (one fewer than the columns) still have to be subtracted. */

/* UNEQUAL COLUMNS: the class names the split in percent; fr does the dividing.

     two columns     40-60  60-40  33-67  67-33  25-75  75-25
     three columns   25-25-50  25-50-25  50-25-25

   The gentlest split is 40-60 (2fr 3fr); past 25-75 a fourth part falls under
   --layout-column-min until the window is wider than most desktops.

   Each ratio breaks straight to one column at one width, asked at the row. The
   floor and parts are written down; the gap is measured
   (py scripts/messung-zeilen-umschaltpunkt.py) because --layout-gap depends on
   the window and the groups break at four widths:

     40-60  60-40                     five parts, one gap    525px
     33-67  67-33                     three parts, one gap   631px
     25-75  75-25                     four parts, one gap    839px
     25-25-50  25-50-25  50-25-25     four parts, two gaps   879px

   Mirror images share a number; more parts break earlier. Not a breakpoint - it
   is the row's room, not a window width. */

/* TWO COLUMNS, FIVE PARTS. Two fifths against three - the gentlest split. */
.layout-row--40-60 {
  grid-template-columns: 2fr 3fr;
}

.layout-row--60-40 {
  grid-template-columns: 3fr 2fr;
}

/* TWO COLUMNS, THREE PARTS. Narrow column a third of the row. */
.layout-row--33-67 {
  grid-template-columns: 1fr 2fr;
}

.layout-row--67-33 {
  grid-template-columns: 2fr 1fr;
}

/* TWO COLUMNS, FOUR PARTS. Narrow column a quarter, so these break earlier. */
.layout-row--25-75 {
  grid-template-columns: 1fr 3fr;
}

.layout-row--75-25 {
  grid-template-columns: 3fr 1fr;
}

/* THREE COLUMNS, FOUR PARTS. Two quarters and one half (2fr), in three orders.
   Break later than the two-column quarter - three columns have two gaps. */
.layout-row--25-25-50 {
  grid-template-columns: 1fr 1fr 2fr;
}

.layout-row--25-50-25 {
  grid-template-columns: 1fr 2fr 1fr;
}

.layout-row--50-25-25 {
  grid-template-columns: 2fr 1fr 1fr;
}

/* THE BREAK, one block per group of equal need for room. At the columns (a
   container query cannot style its own container). The number is literal (a
   query cannot read a variable), so the sum stands over each block:

     room needed = floor x parts + gap x (columns - 1)   [--layout-column-min, --layout-gap] */

/* FIVE PARTS, ONE GAP. */
@container layout-row (max-width: 525px) {
  :is(.layout-row--40-60, .layout-row--60-40) > * { grid-column: 1 / -1; }
}

/* THREE PARTS, ONE GAP. */
@container layout-row (max-width: 631px) {
  :is(.layout-row--33-67, .layout-row--67-33) > * { grid-column: 1 / -1; }
}

/* FOUR PARTS, ONE GAP. */
@container layout-row (max-width: 839px) {
  :is(.layout-row--25-75, .layout-row--75-25) > * { grid-column: 1 / -1; }
}

/* FOUR PARTS, TWO GAPS. */
@container layout-row (max-width: 879px) {
  :is(.layout-row--25-25-50, .layout-row--25-50-25,
      .layout-row--50-25-25) > * { grid-column: 1 / -1; }
}

/* --------------------------------------------------------------------------
   ORDER IN THE NARROW LAYOUT

   The one thing a column says about itself: its place once the row has broken to
   one column. Below the break only, so it sits in the container query; no class
   keeps markup order. order moves the boxes only - tab and screen-reader order
   stay with the markup, which must read correctly on its own. Numbers to 6.

   Each family reorders at its own width, so this stands five times, each at the
   width the family is FINALLY one column - the same numbers as the breaks above.
   -------------------------------------------------------------------------- */

/* FIVE PARTS, ONE GAP. */
@container layout-row (max-width: 525px) {
  :is(.layout-row--40-60, .layout-row--60-40) > .layout-col--narrow-1 { order: 1; }
  :is(.layout-row--40-60, .layout-row--60-40) > .layout-col--narrow-2 { order: 2; }
}

/* THREE PARTS, ONE GAP. */
@container layout-row (max-width: 631px) {
  :is(.layout-row--33-67, .layout-row--67-33) > .layout-col--narrow-1 { order: 1; }
  :is(.layout-row--33-67, .layout-row--67-33) > .layout-col--narrow-2 { order: 2; }
}

/* FOUR PARTS, ONE GAP. */
@container layout-row (max-width: 839px) {
  :is(.layout-row--25-75, .layout-row--75-25) > .layout-col--narrow-1 { order: 1; }
  :is(.layout-row--25-75, .layout-row--75-25) > .layout-col--narrow-2 { order: 2; }
}

/* FOUR PARTS, TWO GAPS. */
@container layout-row (max-width: 879px) {
  :is(.layout-row--25-25-50, .layout-row--25-50-25,
      .layout-row--50-25-25) > .layout-col--narrow-1 { order: 1; }
  :is(.layout-row--25-25-50, .layout-row--25-50-25,
      .layout-row--50-25-25) > .layout-col--narrow-2 { order: 2; }
  :is(.layout-row--25-25-50, .layout-row--25-50-25,
      .layout-row--50-25-25) > .layout-col--narrow-3 { order: 3; }
}

/* THE EQUAL ROWS, at the width the second column no longer fits (two floors and
   one gap). Smallest number in the section: the only break on a small screen,
   where --space-base is on its floor; the ratio breaks reach wide windows. */
@container layout-row (max-width: 423px) {
  :is(.layout-row--50-50, .layout-row--33-33-33, .layout-row--25-25-25-25,
      .layout-row--20-20-20-20-20, .layout-row--17-17-17-17-17-17) > .layout-col--narrow-1 { order: 1; }
  :is(.layout-row--50-50, .layout-row--33-33-33, .layout-row--25-25-25-25,
      .layout-row--20-20-20-20-20, .layout-row--17-17-17-17-17-17) > .layout-col--narrow-2 { order: 2; }
  :is(.layout-row--50-50, .layout-row--33-33-33, .layout-row--25-25-25-25,
      .layout-row--20-20-20-20-20, .layout-row--17-17-17-17-17-17) > .layout-col--narrow-3 { order: 3; }
  :is(.layout-row--50-50, .layout-row--33-33-33, .layout-row--25-25-25-25,
      .layout-row--20-20-20-20-20, .layout-row--17-17-17-17-17-17) > .layout-col--narrow-4 { order: 4; }
  :is(.layout-row--50-50, .layout-row--33-33-33, .layout-row--25-25-25-25,
      .layout-row--20-20-20-20-20, .layout-row--17-17-17-17-17-17) > .layout-col--narrow-5 { order: 5; }
  :is(.layout-row--50-50, .layout-row--33-33-33, .layout-row--25-25-25-25,
      .layout-row--20-20-20-20-20, .layout-row--17-17-17-17-17-17) > .layout-col--narrow-6 { order: 6; }
}

/* ==========================================================================
   The widths of a picture
   The two width classes that break out of the reading width, and the spacing a
   picture carries wherever it stands. Both classes are global.
   ========================================================================== */

/* Spacing around an image, the same for every width; the step is defined at
   .module-post-card-grid. The bottom margin is the step less the caption's top
   padding, so the two add to the standard gap. */
main figure {
  margin-top: var(--space-11);
  margin-bottom: calc(var(--space-11) - var(--space-1));
}

/* The two exceptions from the reading width, carried by any element meant to be
   wider than the text. They exclude each other; the reading-width rule steps
   aside for both with a :not(). */

/* Page width: as wide as header and footer, centred. */
.page-width {
  width: var(--page-width-effective);
  margin-left: auto;
  margin-right: auto;
}

/* Full screen width. The side margins are taken back: a bare <img> has none, but
   a <figure> carries the browser default (margin: 0 40px) and there is no global
   reset for it. Works the same on both. */
.full-width {
  width: 100%;
  margin-left: 0;
  margin-right: 0;
}

/* On a phone a picture takes the full width whatever class it carries (the three
   widths have narrowed together, so an inset one only looks smaller). Names the
   width classes, so a bare <img class="page-width"> collapses the same as one in
   a <figure>; grids and headings that carry these classes bring their own phone
   rule and override this. */
@media (max-width: 639px) {
  main .page-width,
  main .full-width {
    width: 100%;
    margin-left: 0;
    margin-right: 0;
  }
}

/* A picture fits its container. Modules override with their own aspect ratios. */
img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* Captions: body font, one step down (the "small" step). */
.page-width figcaption,
.full-width figcaption {
  font-size: var(--font-size-small);
  font-style: italic;
  line-height: var(--line-height-small);
  padding-top: var(--space-1);
}



/* ==========================================================================
   Site footer
   White on black, full screen width. Either all columns side by side or all
   stacked - nothing in between. Everything is centred while stacked.
   ========================================================================== */

/* THE FOOTER IS A SHELL; the row inside carries the page width. As a flex item
   of the body it takes the whole window and paints the band edge to edge. The
   vertical spacing stays here (the container inside is zeroed, below). */
.site-footer {
  background-color: var(--footer-background-color);
  color: var(--footer-text-color);
  margin-top: var(--space-11);
  padding: var(--space-11) 0 var(--space-10) 0;

  /* All footer text is small; the columns inherit it. The social icons size
     from their own width/height. */
  font-size: var(--font-size-small);
  line-height: var(--line-height-small);

  /* Centred while the columns are stacked - a lone right-aligned block would
     read as a mistake. Taken back where the columns are switched on. */
  text-align: center;
}

/* The container's spacing is zeroed: the footer draws it, above. */
.site-footer .container {
  margin-block: 0;
  padding-block: 0;
}

/* All columns side by side or all stacked, no in-between (the blocks are of
   unequal height). The footer sets its own columns rather than .layout-row--33-33-33,
   whose two-column stage is what is not wanted. Stacked is the ground state;
   the full row is switched on below. Shared top line, so the legal column's
   sentence can be any length. */
.site-footer .layout-row {
  align-items: start;
  grid-template-columns: 1fr;
}

/* Side by side, switched on in one place. Asked at the page (do the columns
   fit?), not the window; the shared desktop threshold, set not derived. */
@container page (min-width: 1024px) {
  .site-footer .layout-row {
    /* minmax(0, 1fr), not 1fr: the auto floor of 1fr would let a long word push
       the track past its third. At 0 the word wraps instead (rule below). */
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  /* Take back the inherited centring now the columns stand side by side. */
  .site-footer-column {
    text-align: left;
  }

  /* Now there is a right edge to align to. */
  .site-footer-column:last-child {
    text-align: right;
  }

  .site-footer-column:last-child nav {
    justify-content: flex-end;
  }
}

/* A word longer than its column wraps rather than overhanging the next (the
   footer texts may change). Spaces still preferred. */
.site-footer-column {
  overflow-wrap: break-word;
}

/* The last block keeps no margin under itself: it would add to the footer's
   own bottom padding. */
.site-footer-column > :last-child {
  margin-bottom: 0;
}

/* Footer links: white, and white again on the orange hover plate. */
.site-footer a:link,
.site-footer a:visited,
.site-footer a:hover,
.site-footer a:active,
.site-footer a:focus-visible {
  color: var(--footer-text-color);
}

/* The last column's links side by side, at every width. justify-content, not
   text-align (a flex container places its own children): centred while stacked,
   right where the columns are switched on. */
.site-footer-column nav {
  display: flex;
  gap: var(--space-5);
  justify-content: center;
}

/* The row of social media icons. */
.site-footer-social {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--space-2);
  justify-content: center;

  /* Wrap when the column narrows: without it the fixed-width icon row was the
     widest thing in the footer and pushed the row past the page width. */
  flex-wrap: wrap;
}

/* One icon link: a square box with the icon centred. Exempt from the general
   footer link rule (own hover, no "Links" padding). */
.site-footer .site-footer-social-link:link,
.site-footer .site-footer-social-link:visited,
.site-footer .site-footer-social-link:hover,
.site-footer .site-footer-social-link:active,
.site-footer .site-footer-social-link:focus-visible {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  margin: 0;
  border-radius: 4px;
  background-color: var(--icon-box-background-color);
  color: var(--footer-text-color);
}

/* Hover: the two colours swap. .site-footer named too, to beat the general
   footer link rule that would otherwise keep the icon white on white. */
.site-footer .site-footer-social-link:hover,
.site-footer .site-footer-social-link:active,
.site-footer .site-footer-social-link:focus-visible {
  background-color: var(--footer-text-color);
  color: var(--primary-color);
}

/* The icon inside the box; colour from the link via currentColor. */
.site-footer-social-link svg {
  width: 18px;
  height: 18px;
  display: block;
}

/* Dark-mode toggle: sun and moon side by side, in footer white. A plain icon
   button, not a link, so it stays clear of the general footer link rule and
   the social-icon box. Its own line under the nav above it. */
.dark-mode-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding: 0;
  border: none;
  background: none;
  color: var(--footer-text-color);
  cursor: pointer;
}

/* Larger than the social icons above: those sit in a 32px box that lends them
   size, this button has none, so the icon itself carries it. */
.dark-mode-toggle svg {
  width: 26px;
  height: 26px;
  display: block;
}

/* Both icons always show; the one that matches the theme now in force stands at
   full strength, the other is dimmed so the pair reads as "here, not there".
   The script writes data-theme on the button. */
.dark-mode-toggle .dark-mode-toggle-sun,
.dark-mode-toggle .dark-mode-toggle-moon {
  opacity: 0.4;
}

.dark-mode-toggle[data-theme="light"] .dark-mode-toggle-sun,
.dark-mode-toggle[data-theme="dark"] .dark-mode-toggle-moon {
  opacity: 1;
}

/* Hover: the dimmed icon lifts a little, so the whole control answers the
   pointer without moving or changing colour. */
.dark-mode-toggle:hover .dark-mode-toggle-sun,
.dark-mode-toggle:hover .dark-mode-toggle-moon,
.dark-mode-toggle:focus-visible .dark-mode-toggle-sun,
.dark-mode-toggle:focus-visible .dark-mode-toggle-moon {
  opacity: 0.7;
}

.dark-mode-toggle:hover[data-theme="light"] .dark-mode-toggle-sun,
.dark-mode-toggle:hover[data-theme="dark"] .dark-mode-toggle-moon,
.dark-mode-toggle:focus-visible[data-theme="light"] .dark-mode-toggle-sun,
.dark-mode-toggle:focus-visible[data-theme="dark"] .dark-mode-toggle-moon {
  opacity: 1;
}

/* ==========================================================================
   Content modules
   Reusable blocks that sit inside a page's content. One block per module below,
   so it can be moved as a whole.
   ========================================================================== */

/* ==========================================================================
   Content module: article hero START
   A full-width image with a white plate on top, overlapping its lower edge and
   running into the white page below. The rules that lift the site header over
   this image, and colour its links white on it, live with the header itself:
   the header reacts to a hero page with `body:has(.module-article-hero)`.

   The four text elements are siblings of the image, not wrapped in a box: on a
   narrow screen the image stands BETWEEN the title and the byline, and CSS can
   only reorder within a parent. The plate is drawn by header::before.
   ========================================================================== */

/* The module seals itself off: isolation: isolate makes it a stacking context,
   so nothing inside can lift above the header or menu. Without it, on a phone
   the hero swallowed the menu taps. */
.module-article-hero {
  position: relative;
  isolation: isolate;
}

/* The hero sits flush at the top of the page, its image running up under the
   header. The container it lives in therefore drops its own top margin and
   padding; every other container keeps the spacing above it. */
.container:has(.module-article-hero) {
  margin-block-start: 0;
  padding-block-start: 0;
}

/* The <header> grid: one column, one row per element (each names its row, so
   the phone query can reassign them). justify-items and text-align centre the
   three text elements; neither reaches the image. */
.module-article-hero > header {
  display: grid;
  grid-template-columns: 1fr;
  justify-items: center;
  text-align: center;
  font-family: var(--heading-font);
}

/* The white plate, spanning the three text rows, in front of the image. No
   negative margin of its own; the overlap is made once, at the image. */
.module-article-hero > header::before {
  content: "";
  grid-column: 1;
  grid-row: 2 / span 3;
  width: var(--page-width-effective);
  background-color: var(--page-background-color);
  position: relative;
  z-index: 1;
}

/* The hero image: full width, one screen high. object-fit: cover crops, top
   pins it so the top is never cut. The negative bottom margin makes the overlap
   (it shortens the row, so all four elements below move up). */
.module-article-hero-article-featured-image {
  display: block;
  grid-column: 1;
  grid-row: 1;
  width: 100%;
  height: 100vh;
  margin-bottom: -260px;
  object-fit: cover;
  object-position: top;
}

/* The three text elements share the plate width and its padding. The title is
   the H1 at the largest display step. */
.module-article-hero-article-title {
  grid-column: 1;
  grid-row: 2;
  position: relative;
  z-index: 2;
  width: var(--page-width-effective);
  font-size: var(--font-size-display-x-large);
  margin: 0 0 var(--rhythm-text) 0;
  padding: var(--space-13) var(--space-10) 0 var(--space-10);
}

/* The subtitle reads as large as an H5. */
.module-article-hero-article-subtitle {
  grid-column: 1;
  grid-row: 3;
  position: relative;
  z-index: 2;
  width: var(--page-width-effective);
  padding: 0 var(--space-10);
  font-size: var(--font-size-h5);
  line-height: var(--line-height-heading);
}

/* Byline: "by <author> in <category>", body size. Plain inline text, not a flex
   row, so it wraps on a narrow window. */
.module-article-hero-meta {
  grid-column: 1;
  grid-row: 4;
  position: relative;
  z-index: 2;
  width: var(--page-width-effective);
  padding: 0 var(--space-10) var(--space-5) var(--space-10);
  text-align: center;
}

/* The taller link padding for the category link. The byline only carries the
   heading font, which CSS cannot select on, so it names itself. */
.module-article-hero-meta a:link,
.module-article-hero-meta a:visited {
  padding: var(--link-plate-padding-headline);
}

/* --- The hero on a phone -------------------------------------------------
   The four grid children stand plainly stacked: title, image, byline,
   subtitle. No overlap, no plate, no viewport height. */

/* The rows, and with them the on-screen order: image to row 2, title to row 1;
   subtitle and byline swap so the author stands above the excerpt. */
@media (max-width: 639px) {
  .module-article-hero-article-featured-image {
    grid-row: 2;
    height: auto;        /* own height from the file, whole picture visible */
    margin-bottom: 0;    /* no overlap: it stands between title and byline */
  }

  .module-article-hero-article-title {
    grid-row: 1;
    padding: 0;          /* no plate, so no box padding */
  }

  .module-article-hero-article-subtitle {
    grid-row: 4;
    padding: 0;
  }

  .module-article-hero-meta {
    grid-row: 3;
    padding: 0;
    /* Equal air on both sides, between the picture and the text that follows. */
    margin-top: var(--rhythm-text);
    margin-bottom: var(--rhythm-text);
  }

  /* No plate: nothing left to overlap. */
  .module-article-hero > header::before {
    content: none;
  }
}

/* ==========================================================================
   Content module: article hero END
   ========================================================================== */

/* ==========================================================================
   Content module: article content START
   Brings its content to a comfortable reading width - nothing else. It gives the
   width to its CHILDREN, so a single child can break out with .page-width or
   .full-width. Sets no space above or below (the container and rows do).
   ========================================================================== */

/* --- The reading width -------------------------------------------------- */

/* Four dials and the finished term:
     -max   widest the column may get.
     -min   narrowest, so the line never collapses.
     -grow  how strongly it follows the window (a share of it).
     -base  fixed amount on top of grow; decides where the column leaves its
            maximum. Share plus base = a gentle slope from a wide screen.

   The term: clamp() for min/grow/max, then min() with --page-gutter-capped as a
   second ceiling the clamp cannot break, keeping the column off the edge on a
   narrow window. The ceiling is in vw, so the term is the same inside a narrower
   .page-width figure. Written once so content width and caption cannot drift. */
:root {
  --module-article-content-width-max: 1080px;
  --module-article-content-width-min: 320px;
  --module-article-content-width-grow: 40vw;
  --module-article-content-width-base: 300px;

  --module-article-content-width: min(
    clamp(
      var(--module-article-content-width-min),
      var(--module-article-content-width-grow) + var(--module-article-content-width-base),
      var(--module-article-content-width-max)
    ),
    100vw - 2 * var(--page-gutter-capped)
  );
}

/* Every child sits in the reading width unless it carries a width class. The
   caption is named again because it sits in a <figure>, not directly, and
   belongs on the text line. The width classes are a :not() exception, not left
   to file order. Stepless: no media query, no JavaScript. */
.module-article-content > *:not(.page-width, .full-width),
.module-article-content figcaption {
  width: var(--module-article-content-width);
  margin-left: auto;
  margin-right: auto;
}

/* A grid or list module set right into running text, between paragraphs, has no
   container distance around it - so it carries the running-text step above it
   itself (its own bottom padding already holds the step below). Only here: a
   module standing alone in its own row takes its vertical distance from the row
   and keeps none of its own above. */
.module-article-content > .module-post-card-grid,
.module-article-content > .module-image-grid,
.module-article-content > .module-post-list {
  margin-top: var(--space-11);
}

/* A table wider than the text column scrolls instead of running past the page
   edge - main's overflow-x: clip would otherwise cut the excess off with no
   scrollbar. Two layers:
   - Clean path: wrap the table in <div class="table-scroll">. The wrapper
     scrolls and the table keeps its display: table role, so screen readers
     still announce rows and columns.
   - Safety net: a bare table placed straight in the article (direct child, no
     wrapper) is made to scroll itself. display: block plus width: max-content
     lets it grow to its content, max-width caps it at the text column,
     overflow-x: auto adds the scrollbar. Cost: display: block drops the table
     role in some screen readers - which is why the wrapper is preferred.
   A table inside .table-scroll is no longer a direct child, so the safety net
   leaves it untouched. */
.table-scroll {
  overflow-x: auto;
}

.module-article-content > table {
  display: block;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
}

/* ==========================================================================
   Content module: article content END
   ========================================================================== */

/* ==========================================================================
   Content module: post card START
   Image, category label, title and excerpt, stacked. A plain container, not a
   link (it holds three: photo and title to the post, category to its page). No
   width of its own - it fills the box it is put into. Only the image proportions
   and the plate's least width are fixed here.
   ========================================================================== */

.module-post-card {
  text-align: center;   /* label and title; the excerpt takes it back */

  /* The card takes its four rows from the grid above (subgrid): image, plate,
     title, excerpt. That lines the cards up - every title row is as tall as the
     tallest title, so every excerpt starts at the same height. Outside a grid it
     falls back to four content-sized rows. */
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 4;     /* occupy all four, so the subgrid has something to line up */

  /* On a phone the grid drops its four rows, so the card drops its span too, or
     it would push the next card three rows down. */
  @media (max-width: 639px) {
    grid-template-rows: auto;
    grid-row: auto;
  }

  /* Every part at the top of its row, so a short title's spare space falls
     between title and excerpt, not centred in a row a longer title grew tall. */
  align-items: start;
}

/* The photo's link takes back the general "Links" rules: cut the padding and
   plate (a strip of orange around an image), block so it leaves no gap below. */
.module-post-card-article-featured-image:link,
.module-post-card-article-featured-image:visited,
.module-post-card-article-featured-image:hover,
.module-post-card-article-featured-image:active,
.module-post-card-article-featured-image:focus-visible {
  display: block;
  padding: 0;
  margin: 0;
  background-color: transparent;
}

/* Cropped, not fitted: object-fit: cover crops, no object-position so the cutout
   is taken from the middle of the picture. */
.module-post-card img {
  display: block;
  width: 100%;

  /* height: auto before aspect-ratio can act (the HTML height attribute is a
     real declaration, and a box with both dimensions ignores its ratio). */
  height: auto;
  aspect-ratio: 7 / 8;
  object-fit: cover;
}

/* --- The frame around the category plate ---------------------------------
   An invisible box the width of the card, a MEASURING BOX: the plate type must
   shrink where the card is narrow, which the window cannot tell but a container
   query on a card-width box can. Not the card (container-type breaks the shared
   rows) nor the plate (sized by its own text). Takes no rows, so no conflict. */
.module-post-card-categoryplate-frame {
  display: block;
  position: relative;
  container: categoryplate-frame / inline-size;
}

/* The category label on a white plate overlapping the image edge and running
   into the page below. Pulled up by its negative top margin.

   AS WIDE AS ITS TEXT (fit-content), so the name cannot break or overhang on a
   narrow card. min/max-width keep it a plate, not a stamp or a band. Top padding
   is a fixed pixel (part of the overlap); bottom is rem (comes down with the
   type); side is a spacing step. Letter spacing measured off the reference. */
.module-post-card-categoryplate {
  display: block;
  position: relative;   /* lift in front of the image, else painted behind it */
  width: fit-content;
  min-width: 58%;
  max-width: 85%;
  overflow-wrap: break-word;   /* last resort for one long word */
  margin: -62px auto 0 auto;
  padding: 25px var(--space-4) 1.25rem var(--space-4);
  background-color: var(--page-background-color);
  font-family: var(--heading-font);
  letter-spacing: 0.25em;
}

/* One step smaller on a narrow card, asked against the frame. The width is where
   the plate at body size reads as a bar across the card (measured). The same
   line holds for title and excerpt below, so the card has one narrow state. */
@container categoryplate-frame (max-width: 300px) {
  .module-post-card-categoryplate {
    font-size: var(--font-size-small);
    line-height: var(--line-height-small);
  }
}

/* The taller link padding for the category link. The plate only carries the
   heading font, which CSS cannot select on, so it names itself. */
.module-post-card-categoryplate a:link,
.module-post-card-categoryplate a:visited {
  padding: var(--link-plate-padding-headline);
}

/* --- The frames around the title and the excerpt --------------------------
   Two more measuring boxes, like the plate's frame. Two, not one around both:
   title and excerpt sit in two of the card's four shared rows; wrapped together
   they would occupy one and every excerpt would find its own height. The query
   that reads them sits below the plain title and excerpt rules. */
.module-post-card-title-frame,
.module-post-card-excerpt-frame {
  display: block;
  container: post-card-text-frame / inline-size;
}

/* The title is the article link, dressed as a heading: an <a> is not covered by
   the H1-H6 rule, so heading font, weight and line height are set here; its size
   reads as an H5. fit-content keeps the hover plate as wide as the words, not the
   whole card. No margin above (the plate's padding is the gap to the label);
   below, half a line to the excerpt. */
.module-post-card-article-title {
  display: block;
  width: fit-content;
  font-family: var(--heading-font);
  font-weight: 400;
  line-height: var(--line-height-heading);
  font-size: var(--font-size-h5);
  margin: 0 0 var(--rhythm-text) 0;
}

/* The taller heading-font plate padding (the H1-H6 link rule no longer reaches
   this link) and a position so the title paints after the plate that overlaps
   its top; markup order settles the stack, no z-index needed. */
.module-post-card-article-title:link,
.module-post-card-article-title:visited {
  padding: var(--link-plate-padding-headline);
  position: relative;
}

/* The excerpt: the only body-font part, "small" step, left aligned. Its bottom
   margin is the space to the next row of cards - inside a grid a row gap would
   fall inside the cards, so it hangs on the card's last part. --module-post-card-row-gap
   comes from the grid; zero outside one. */
.module-post-card-article-excerpt {
  font-size: var(--font-size-small);
  line-height: var(--line-height-small);
  text-align: left;
  margin: 0 0 var(--module-post-card-row-gap, 0) 0;
}

/* One step smaller on a narrow card, title and excerpt together (one frame name,
   one query). Same width as the plate. BELOW the plain rules on purpose: a
   container query adds no specificity, so the later equal rule wins. */
@container post-card-text-frame (max-width: 300px) {
  .module-post-card-article-title {
    font-size: var(--font-size-h6);
  }

  .module-post-card-article-excerpt {
    font-size: var(--font-size-x-small);
    line-height: var(--line-height-small);
  }
}

/* The excerpt's bottom margin hangs below the last row of cards too; taken back
   once at the grid - see "THE ONE MARGIN TOO MANY" at .module-post-card-grid. */

/* ==========================================================================
   Content module: post card END
   ========================================================================== */

/* ==========================================================================
   Content module: card grid START
   Cards in rows of equal width. Knows nothing about a card's contents (so a
   product or team card fits too). The count per row is a second class next to
   .module-post-card-grid, naming an UPPER LIMIT. Each step sets its own gap; row
   gap and column gap match. All cards in a row end up the same height. No width
   of its own.
   ========================================================================== */

/* In a full-width column the grid would run to the screen edge (that column
   hands loose text an edge gutter, but not a module). This module wants the
   same gutter as the text above it, so it sets it on itself - the one variable
   that keeps it flush with that text. Only in that column; a normal column
   already holds the page margin. */
.layout-col--full-width > .module-post-card-grid {
  padding-inline: var(--layout-row-padding-inline);
}

.module-post-card-grid {
  display: grid;

  /* The step below the block, as inner padding so it does not add to a
     container margin when the grid stands alone in its row. The value subtracts
     the card's own margin - see "THE ONE MARGIN TOO MANY" below. The top and
     bottom margins in running text are set on the article-content child rule. */
  padding-bottom: calc(var(--space-11) - var(--module-post-card-row-gap));

  /* HOW NARROW A CARD MAY GET before the grid drops one. Set once, the widest
     the most-columns step reaches on the widest screen. The image grid's is
     smaller (a photo just gets smaller; text stops being readable). */
  --card-column-min: 245px;

  /* AS MANY COLUMNS AS FIT, UP TO THE COUNT. The formula is written once here and
     reused by every step; a step sets only the two numbers that differ, --cols and
     --gap. Default when no step class stands next to this: four columns.
     With N = --cols and G = --gap:

       (100% - (N-1) * G) / N          the width one of N columns would have
       max(--card-column-min, ...)     whichever is larger
       repeat(auto-fill, minmax(...))  as many as fit, sharing the remainder

     The max() keeps the count an UPPER LIMIT: below the point N columns fit the
     minimum wins and the count drops, wherever a card would go under its floor.
     auto-fill, not auto-fit, so a short last row keeps the cards an Nth wide. */
  --cols: 4;
  --gap: var(--space-8);
  grid-template-columns:
    repeat(auto-fill, minmax(max(var(--card-column-min),
                                 (100% - (var(--cols) - 1) * var(--gap)) / var(--cols)), 1fr));
  column-gap: var(--gap);

  /* One row per card part: image, plate, title, excerpt. The card takes these
     (subgrid) instead of making its own, lining its parts up with its neighbours.
     Repeated in .module-post-card as "grid-row: span 4". */
  grid-template-rows: repeat(4, auto);

  /* The gap is between cards, not the parts inside one. Zeroed here so the
     subgrid rows do not tear the parts apart; the between-cards space is put back
     as a bottom margin on the card's last part. The row gap tracks --gap. */
  row-gap: 0;
  --module-post-card-row-gap: var(--gap);

  /* THE ONE MARGIN TOO MANY. Every card carries the row gap, the bottom row
     included, so one margin hangs below the block (the bottom cards' margins sit
     in one strip). The block's bottom padding above subtracts what the cards
     laid down, so the visible step below stays the standard one; var() reads
     --module-post-card-row-gap, so every step resets that padding too. */
}

/* Each step names only its count and gap; the track formula and the two gaps
   above read from these. Fewer cards, wider cards, more air the higher up. */
.module-post-card-grid--2 { --cols: 2; --gap: var(--space-10); }
.module-post-card-grid--3 { --cols: 3; --gap: var(--space-9);  }
.module-post-card-grid--4 { --cols: 4; --gap: var(--space-8);  }
.module-post-card-grid--5 { --cols: 5; --gap: var(--space-7);  }
/* Most cards, narrowest cards, tightest gap - any tighter and the row reads as
   one block, not separate cards. */
.module-post-card-grid--6 { --cols: 6; --gap: var(--space-6);  }

/* FEWER COLUMNS, MORE AIR on smaller screens. The same drop for every step, so it
   is written once for the base class and all steps together. Two columns on a
   tablet, one on a phone; --cols and --gap change, the track formula above
   recomputes from them. */
:is(.module-post-card-grid,
    .module-post-card-grid--2, .module-post-card-grid--3,
    .module-post-card-grid--4, .module-post-card-grid--5,
    .module-post-card-grid--6) {
  @media (max-width: 1023px) {
    --cols: 2;
    --gap: var(--space-10);
  }

  /* One column on the phone, a plain 1fr rather than the formula, so a card
     narrower than --card-column-min still fills the row instead of overflowing. */
  @media (max-width: 639px) {
    --gap: var(--space-11);
    grid-template-columns: 1fr;

    /* Subgrid off: one card per row has nothing beside it to line up, and the
       span would claim the whole row set. The card drops its span in its
       matching query - the two belong together. */
    grid-template-rows: auto;
  }
}

/* NO EDGE DISTANCE OF ITS OWN: the row's margin track already holds every column
   off the edge, the grid included. */

/* ==========================================================================
   Content module: card grid END
   ========================================================================== */

/* ==========================================================================
   Content module: image grid START
   Pictures in rows of equal width, nothing below them. The count per row is a
   second class next to .module-image-grid, naming an UPPER LIMIT: the browser
   fits as many as it can and drops one wherever a picture would go under its
   floor. Each step sets its own gap; row gap and column gap match. One image per
   cell, so the space between rows is a plain row-gap. No width of its own.
   ========================================================================== */

/* In a full-width column the grid would run to the screen edge (that column
   hands loose text an edge gutter, but not a module). This module wants the
   same gutter as the text above it, so it sets it on itself - the one variable
   that keeps it flush with that text. Only in that column; a normal column
   already holds the page margin. */
.layout-col--full-width > .module-image-grid {
  padding-inline: var(--layout-row-padding-inline);
}

.module-image-grid {
  display: grid;

  /* The step below the block, as inner padding so it does not add to a
     container margin when the grid stands alone in its row. The top and bottom
     margins in running text are set on the article-content child rule. */
  padding-bottom: var(--space-11);

  /* HOW NARROW A PICTURE MAY GET before the grid drops one: a photo below this
     is a thumbnail, showing too little to be worth a column. */
  --image-column-min: 180px;

  /* AS MANY COLUMNS AS FIT, UP TO THE COUNT. The formula is written once here and
     reused by every step; a step sets only the two numbers that differ, --cols and
     --gap. Default when no step class stands next to this: four columns.
     With N = --cols and G = --gap:

       (100% - (N-1) * G) / N          the width one of N columns would have
       max(--image-column-min, ...)    whichever is larger
       repeat(auto-fill, minmax(...))  as many as fit, sharing the remainder

     The max() keeps the count an UPPER LIMIT: below the point N columns fit the
     minimum wins and the count drops. */
  --cols: 4;
  --gap: var(--space-8);
  grid-template-columns:
    repeat(auto-fill, minmax(max(var(--image-column-min),
                                 (100% - (var(--cols) - 1) * var(--gap)) / var(--cols)), 1fr));
  column-gap: var(--gap);
  row-gap: var(--gap);
}

/* Each step names only its count and gap; the track formula and the two gaps
   above read from these. Fewer images, wider images, more air the higher up. */
.module-image-grid--2 { --cols: 2; --gap: var(--space-10); }
.module-image-grid--3 { --cols: 3; --gap: var(--space-9);  }
.module-image-grid--4 { --cols: 4; --gap: var(--space-8);  }
.module-image-grid--5 { --cols: 5; --gap: var(--space-7);  }
/* Most images, narrowest images, tightest gap - any tighter and the row reads as
   one strip, not separate pictures. */
.module-image-grid--6 { --cols: 6; --gap: var(--space-6);  }

/* FEWER COLUMNS, MORE AIR on smaller screens. Most steps drop to two columns on a
   tablet; the two small-picture steps (5 and 6) drop only to three, so they get
   their own tablet value below. --cols and --gap change, the track formula above
   recomputes from them. */
:is(.module-image-grid,
    .module-image-grid--2, .module-image-grid--3, .module-image-grid--4) {
  @media (max-width: 1023px) {
    --cols: 2;
    --gap: var(--space-10);
  }
}

/* The steps for small pictures in quantity: a smaller drop on the tablet. */
:is(.module-image-grid--5, .module-image-grid--6) {
  @media (max-width: 1023px) {
    --cols: 3;
    --gap: var(--space-9);
  }
}

/* One picture per row on the phone, for every step. A plain 1fr rather than the
   formula, so a picture narrower than --image-column-min still fills the row. */
:is(.module-image-grid,
    .module-image-grid--2, .module-image-grid--3,
    .module-image-grid--4, .module-image-grid--5,
    .module-image-grid--6) {
  @media (max-width: 639px) {
    --gap: var(--space-11);
    grid-template-columns: 1fr;
  }
}

/* NO EDGE DISTANCE OF ITS OWN: the row's margin track already holds every column
   off the edge, the grid included. A strip flush with both edges is a decision
   at the row (.layout-col--full-width). */

/* Each image's link takes back the general "Links" rules: cut the padding and
   plate (a strip of orange around an image), block so it leaves no gap below. */
.module-image-grid-item:link,
.module-image-grid-item:visited,
.module-image-grid-item:hover,
.module-image-grid-item:active,
.module-image-grid-item:focus-visible {
  display: block;
  padding: 0;
  margin: 0;
  background-color: transparent;
}

/* Cropped, not fitted: object-fit: cover crops to a fixed 7:8, so any file comes
   out the same shape and a row keeps a straight bottom edge. Cutout from the
   middle (no object-position). */
.module-image-grid img {
  display: block;
  width: 100%;
  height: auto;   /* before aspect-ratio can act (HTML height is a real declaration) */
  aspect-ratio: 7 / 8;
  object-fit: cover;
}

/* ==========================================================================
   Content module: image grid END
   ========================================================================== */

/* ==========================================================================
   Content module: post list START
   Posts as rows: a small picture left, title, excerpt and category right. For
   archive and category pages. Row and list live here together (a row exists only
   inside a list). No variants - a list is one column. It fills its column; how
   long a row may get is decided there.
   ========================================================================== */

/* In a full-width column the list would run to the screen edge (that column
   hands loose text an edge gutter, but not a module). This module wants the
   same gutter as the text above it, so it sets it on itself - the one variable
   that keeps it flush with that text. Only in that column; a normal column
   already holds the page margin. */
.layout-col--full-width > .module-post-list {
  padding-inline: var(--layout-row-padding-inline);
}

.module-post-list {
  /* The step below the block, as inner padding so it does not add to a container
     margin when the list stands alone in its row. The top and bottom margins in
     running text are set on the article-content child rule. */
  padding-bottom: var(--space-11);
}

/* The <ul> carries no look of its own; it sits inside the module <div> so a
   screen reader announces how many posts there are. .no-text-rhythm exempts it
   from the running-text list spacing, so it keeps only the distances set here. */
.module-post-list ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* One row: fixed picture column, text takes the rest, so every text starts on
   the same line and the picture stays small. align-items: start keeps both at
   the top, so the text's real end shows (it matters for the dividing line). */
.module-post-list-item {
  display: grid;
  grid-template-columns: 180px 1fr;
  column-gap: var(--space-6);
  align-items: start;

  /* Air above and below the text, and so the distance from the dividing line. */
  padding: var(--space-6) 0;

  /* The dividing line, below every row, taken back for the last one. */
  border-bottom: 1px solid var(--rule-color);

  /* Below this module's own turning point the row turns: picture on top, text
     under. A shrinking picture (minmax) does not work - a grid maxes tracks
     before feeding a flexible one, so the picture keeps full width until the
     text cannot fit its longest word. The number is measured for this module,
     NOT a page breakpoint, and moves with the picture width, gap or excerpt
     size. row-gap is the value the one-row grid above never needed. Once the
     picture spans the full width it would take too much height at 7:8, so it
     switches to the wider 3:2 (read by the img rule below). */
  @media (max-width: 799px) {
    grid-template-columns: 1fr;
    row-gap: var(--space-4);
    --module-post-list-image-ratio: 3 / 2;
  }
}

/* No line under the last row. The first row's top padding is left alone - it
   keeps the first picture off whatever stands above the list. */
.module-post-list-item:last-child {
  border-bottom: none;
}

/* The photo's link takes back the general "Links" rules: cut the padding and
   plate (a strip of orange around an image), block so it leaves no gap below. */
.module-post-list-article-featured-image:link,
.module-post-list-article-featured-image:visited,
.module-post-list-article-featured-image:hover,
.module-post-list-article-featured-image:active,
.module-post-list-article-featured-image:focus-visible {
  display: block;
  padding: 0;
  margin: 0;
  background-color: transparent;
}

/* Cropped, not fitted, so any file comes out the same shape and the rows keep a
   straight left edge. */
.module-post-list-article-featured-image img {
  display: block;
  width: 100%;
  height: auto;   /* before aspect-ratio can act (HTML height is a real declaration) */
  aspect-ratio: var(--module-post-list-image-ratio, 7 / 8);
  object-fit: cover;
}

/* The title is the article link, dressed as a heading: an <a> is not covered by
   the H1-H6 rule, so heading font, weight and line height are set here; its size
   reads as the smallest heading level - below a card's, a row being a smaller
   thing. fit-content keeps the hover plate as wide as the words, not the whole
   row. No margin above (the row's padding sets it off); below, a quarter-line to
   the excerpt. */
.module-post-list-article-title {
  display: block;
  width: fit-content;
  font-family: var(--heading-font);
  font-weight: 400;
  line-height: var(--line-height-heading);
  font-size: var(--font-size-h6);
  margin: 0 0 var(--rhythm-tight) 0;
}

/* The taller heading-font plate padding, since the H1-H6 link rule no longer
   reaches this link. */
.module-post-list-article-title:link,
.module-post-list-article-title:visited {
  padding: var(--link-plate-padding-headline);
}

/* The excerpt: body font, running-text size (the list's max width was measured
   at that size; smaller would fit more characters and run the line too long).
   The bottom margin is the gap to the meta line, a little larger than the one
   above so the category sets itself slightly apart. Tight line height. */
.module-post-list-article-excerpt {
  font-size: var(--font-size-body);
  line-height: var(--line-height-small);
  margin: 0 0 var(--space-3) 0;
}

/* The meta line (the category today). A block so the link inside stays inline -
   a block link would stretch its hover plate across the whole row. Line height
   set here (the body value leaves too much space above these smaller letters). */
.module-post-list-meta {
  font-size: var(--font-size-small);
  line-height: 1.2;
  margin: 0;   /* the space under the row is the row's padding */
}

/* The category name, below the excerpt. Small because of that position (the card
   shows it at body size, where it also covers the image edge). Heading font and
   the card's letter spacing - the same label doing the same job. Inline, so its
   hover plate ends with the letters. */
.module-post-list-article-category {
  font-family: var(--heading-font);
  letter-spacing: 0.25em;
}

/* The taller link padding for the category link, which only carries the heading
   font (not selectable in CSS), so it names itself. */
.module-post-list-article-category:link,
.module-post-list-article-category:visited {
  padding: var(--link-plate-padding-headline);
}

/* NO EDGE DISTANCE OF ITS OWN: the row's margin track already holds every column
   off the edge, the list included. */

/* ==========================================================================
   Content module: post list END
   ========================================================================== */

/* ==========================================================================
   Content module: site header START
   Branding left, menu and fullscreen button right. The header ADAPTS TO A PAGE
   THAT CONTAINS AN ARTICLE HERO, asking it itself with
   `body:has(.module-article-hero)`. Two things differ, both marked "HERO OR NONE":
   the header positioning (out of the flow with a hero) and the resting link
   colour (white on the image, black on white).

   The module's outer <div> is only the hull; the <header> landmark inside it
   keeps the class .site-header, which carries the whole header layout. Renaming
   the inner classes to module-site-header-* is a later, separate step.
   ==========================================================================
*/

/* The module hull. Empty on purpose: the layout lives on .site-header (the
   <header> landmark inside), the wrapping <div> only makes the header a module
   that a template system can lift out as a whole. */
.module-site-header {
}

/* Shared whether a hero is present or not.

   THE HEADER SHRINKS OVER FOUR STAGES, widest to narrowest. Stage 1 is here,
   the three narrower stages follow in their own media queries below, each at a
   MEASURED width shared with the check script:

     py scripts/pruefung-menue-umschaltpunkt.py --messen

   Run it after touching anything visible in the header. The three widths are:

     1 -> 2  the four parts no longer fit on page-width: the header grows to
             full-width, still showing all four parts.
     2 -> 3  they no longer fit on full-width: branding-addition and the
             fullscreen button leave together, branding + menu stay.
     3 -> 4  branding + menu no longer fit: the menu folds into the overlay and
             its button appears (the stage the script in menu.js must know).

   Stage 1 (widest): the header stands on page-width and is centred, so its
   branding lines up with the article column. Every full-width element reads
   --page-width-effective, never --page-width, so the screen-edge margin floor
   still holds. margin-inline: auto centres it in the leftover space.

   --header-link-color is the resting colour of every link and of the menu
   button, set here and READ in one single place further down. It is not a
   colour of the global list but a role the header sets on itself: an area
   inside the header that needs a different colour sets the variable for itself
   in one line, and the nearest ancestor wins - no rule has to be made heavy
   enough to beat another one. Default: the header stands on white ground. */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--space-5);

  width: var(--page-width-effective);
  margin-inline: auto;

  --header-link-color: var(--text-color);
}

/* Stage 1 -> 2: below the measured width the four parts no longer fit on
   page-width. The header grows to full window width with the wide-row inset -
   more room, all four parts kept. Nothing is hidden here, only the width
   changes. */
/* pruefmarke: header-umbruch-stufe-1-2 */
@media (max-width: 1348px) {
  .site-header {
    width: auto;
    margin-inline: 0;
    padding-inline: var(--layout-row-padding-inline);
  }
}

/* Header above content on EVERY width. Not in a media query: on a phone the
   header must still outrank the hero, or the open menu and the hero title land
   on one level and the hero swallows the menu taps. */
body:has(.module-article-hero) .site-header {
  position: relative;
  z-index: var(--layer-header);

  /* Menu backgrounds run part-transparent, mixed from the same one source so the
     hover plate and the open submenu card show the ground through them. Only with
     a hero, so the image shows through; a header on white ground keeps the
     full-strength colour from the global list. */
  --link-hover-background-color: color-mix(in srgb, var(--primary-color) 70%, transparent);
  --submenu-background-color: color-mix(in srgb, var(--background-color-light) 70%, transparent);
}

/* HERO OR NONE. With a hero: absolute, out of the flow, so the image reaches the
   top edge, and the links turn white because they lie on the image. Above the
   mobile breakpoint only; below it the header is back in the flow on white
   ground and keeps the black from .site-header. */
@media (min-width: 640px) {
  body:has(.module-article-hero) .site-header {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;

    --header-link-color: var(--link-on-dark-color);
  }
}

/* --------------------------------------------------------------------------
   The branding: the two things no other header link does
   -------------------------------------------------------------------------- */

/* The text lines up with the gutter, not the plate: pull the link left by its
   own left padding, so the letters sit at the gutter and the plate reaches into
   it. Long selector to match the shared header-link rule in every state, so the
   branding does not jump on hover. */
.site-header a.site-branding:link,
.site-header a.site-branding:visited,
.site-header a.site-branding:hover,
.site-header a.site-branding:active,
.site-header a.site-branding:focus-visible {
  margin-left: calc(-1 * var(--space-5));
}

/* Stage 2 -> 3: below the measured width the four parts no longer fit on
   full-width. The addition " | digital renaissance" and the fullscreen button
   leave TOGETHER (RS) - both are the parts that open no door, and the row keeps
   branding + menu. display: none gives back the width and leaves the a11y tree;
   the wording stays in the markup and the button in the row, both returning
   with the window. The fullscreen button carries the same measured width in its
   own rule further down, under the matching pruefmarke. */
/* pruefmarke: header-umbruch-stufe-2-3 */
@media (max-width: 1097px) {
  .site-branding-addition {
    display: none;
  }
}

/* Menu on the right, fullscreen button next to it. */
.site-navigation {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* The menu: a plain list in a row. The gap is an optical correction. */
.site-navigation ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 6px;
}

/* --------------------------------------------------------------------------
   The menu on a narrow window: a full-screen overlay

   The switch is a class on the <nav> (.site-navigation--open): the stylesheet
   reads it, js/menu.js sets it on a tap. Everything the overlay is - covering
   the screen, the button word, the stacking - hangs off that one class.

   This is stage 3 -> 4, the narrowest of the header's three breaks: below the
   measured width branding + menu no longer fit and the menu folds into the
   overlay. THE NUMBER IS MEASURED, same value in CSS and the script:

     py scripts/pruefung-menue-umschaltpunkt.py --messen

   By this width the branding addition and the fullscreen button have already
   gone (stage 2 -> 3 above), so the row here holds branding + menu alone.
   -------------------------------------------------------------------------- */

/* Wide window: the button is hidden, every item is visible in the row. */
/* pruefmarke: header-umbruch-stufe-3-4 */
@media (min-width: 738px) {
  /* The button that opens the overlay has no place here. */
  .menu-toggle {
    display: none;
  }
}

/* pruefmarke: header-umbruch-stufe-3-4 */
@media (max-width: 737px) {
  /* The button that opens and closes the overlay, styled to match a menu item.
     Its resting colour comes from --header-link-color, applied together with
     the links in the one rule further down. */
  .menu-toggle {
    font-family: var(--heading-font);
    font-size: var(--font-size-small);
    line-height: 1.1;

    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-2) var(--space-5);
    border: none;
    background: none;
    border-radius: 7px;
    cursor: pointer;
  }

  /* The button word: "Menu" closed, "Close" open. Generated content so the
     visible and announced name match in both states; the button is empty.
     aria-expanded on the button carries the state to a screen reader. */
  .menu-toggle::before {
    content: "Menu";
  }

  .site-navigation--open .menu-toggle::before {
    content: "Close";
  }

  /* The same orange plate a menu item gets on hover. */
  .menu-toggle:hover,
  .menu-toggle:focus-visible {
    background-color: var(--link-hover-background-color);
    color: var(--link-hover-color);
  }

  /* Closed: the list takes no room in the header row. */
  .site-navigation:not(.site-navigation--open) .site-menu {
    display: none;
  }

  /* Open: the list covers the screen. fixed against the viewport, menu layer so
     it covers the branding too (the button stays reachable, below). White
     ground with black text: the ground of its own that makes the entries black
     (--header-link-color on the <nav>, below). */
  .site-navigation--open .site-menu {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    overflow-y: auto;
    z-index: var(--layer-menu);

    background-color: var(--page-background-color);
    color: var(--text-color);

    /* Items stacked, centred, starting at the top (the finger that tapped the
       button is up there). Top padding = the header row height plus one step,
       from the same variables the header uses, so it follows a header change. */
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    padding-top: calc(
      var(--space-5)
      + var(--font-size-small) * 1.1
      + 2 * var(--space-2)
      + var(--space-5)
    );
  }

  /* The overlay is white whether a hero is present or not, so everything on it is black -
     entries AND the button. Set on the <nav>, not on the list: the button is
     the list's sibling, not its child, and would stay white on a hero page. */
  .site-navigation--open {
    --header-link-color: var(--text-color);
  }

  /* Button lifted above the overlay - the only way back out. */
  .site-navigation--open .menu-toggle {
    position: relative;
    z-index: var(--layer-menu-button);
  }

  /* Open menu: the whole header row stays on screen, so the way out is where it
     was left (pinning the button alone re-packs the row). Two rules, each
     choosing the value that does NOT change the flow: sticky where the header is
     a normal block, fixed where it is already out of the flow (hero page above
     the mobile breakpoint). */
  .site-header:has(.site-navigation--open) {
    position: sticky;
    top: 0;
  }

  @media (min-width: 640px) {
    body:has(.module-article-hero) .site-header:has(.site-navigation--open) {
      position: fixed;
    }
  }
}

/* --------------------------------------------------------------------------
   The second menu level

   A parent item ("Visual Art") is an <li class="has-submenu"> holding its own
   link, a chevron mark inside that link, and a nested <ul class="submenu">.
   The chevron is a mark only - NOT a click target: the whole link stays one
   link to the parent page, and hover opens the dropdown. No script for this
   level; pure CSS.
   -------------------------------------------------------------------------- */

/* The parent item anchors the dropdown; the link itself is unchanged. */
.site-menu li.has-submenu {
  position: relative;
}

/* Chevron: sits INSIDE the link, right after the text, so it lives in the same
   hover plate and reads as part of the item. currentColor follows the link's
   own colour in every state; one word-space of gap to the text. */
.site-menu .submenu-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1em;
  height: 1em;
  margin-left: 0.35em;
  color: inherit;
  vertical-align: middle;
}

.site-menu .submenu-chevron svg {
  width: 100%;
  height: 100%;
}

/* Wide window: the dropdown card. */
/* pruefmarke: header-umbruch-stufe-3-4 */
@media (min-width: 738px) {
  /* The submenu is a stacked card block, hidden until the parent is hovered or
     keyboard-focused. */
  .site-menu .submenu {
    list-style: none;
    margin: 0;
    padding: 0;

    /* Stack the items. The global ".site-navigation ul" makes every menu list a
       horizontal flex row; right for the top level, overridden here so the
       sub-items sit one under the other. */
    display: flex;
    flex-direction: column;
    gap: 0;

    position: absolute;
    top: 100%;
    /* Centred under the parent: shift the block so its own centre lines up with
       the parent's. Width comes from the widest item, so this stays true
       whatever the longest label is. */
    left: 50%;
    transform: translateX(-50%);

    /* As wide as the widest item needs, no wider. */
    width: max-content;

    z-index: var(--layer-menu);

    /* Hidden by default. visibility (not display) so the rounded corners of
       first and last child are already computed when it appears. */
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.12s ease, visibility 0s linear 0.12s;

    /* The four outer corners are rounded on the first and last row directly
       (rules below), NOT by clipping the block: overflow: hidden let the orange
       hover fill square off the rounded corners. Radius is a plain corner value;
       no scale covers radii in this template. */
    border-radius: 8px;
  }

  /* Show the dropdown when the parent is hovered or keyboard-focused. */
  .site-menu li.has-submenu:hover .submenu,
  .site-menu li.has-submenu:focus-within .submenu {
    visibility: visible;
    opacity: 1;
    transition: opacity 0.12s ease;
  }

  /* The dropdown card is light whether a hero is present or not, so its entries are black -
     an area inside the header with a reason of its own. */
  .site-menu .submenu {
    --header-link-color: var(--text-color);
  }

  /* A submenu row on its own light ground. */
  .site-menu .submenu a {
    display: block;
    font-family: var(--heading-font);
    font-size: var(--font-size-small);
    line-height: 1.1;
    white-space: nowrap;
    padding: var(--space-2) var(--space-5);

    background-color: var(--submenu-background-color);
  }

  /* The shared ".site-header a" rules round EVERY header link (7px) - in the
     resting AND the hover state, which is why every row looked fully rounded.
     Those rules score two classes plus one element and sit later in the file,
     so squaring the rows off needs at least the same weight AND a later rule:
     the extra "li" here lifts every selector below to two classes + two
     elements, which wins outright. Square off every row, then round only the
     block's four outer corners back on: top two on the first row, bottom two
     on the last. Inner edges between rows stay square. The radius lives on the
     coloured fill itself, so ground AND the orange hover fill both follow the
     block's outline. Same radius as the block frame. */
  .site-menu .submenu li a {
    border-radius: 0;
  }

  .site-menu .submenu li:first-child a {
    border-top-left-radius: 8px;
    border-top-right-radius: 8px;
  }

  .site-menu .submenu li:last-child a {
    border-bottom-left-radius: 8px;
    border-bottom-right-radius: 8px;
  }

  /* Hover / focus: orange ground, white text - the same plate a top menu item
     gets. */
  .site-menu .submenu a:hover,
  .site-menu .submenu a:focus-visible {
    background-color: var(--link-hover-background-color);
    color: var(--link-hover-color);
  }
}

/* Narrow window: in the full-screen overlay there is room, so nothing folds -
   the submenu items sit under their parent, a step smaller, all visible. The
   chevron has no job here. */
/* pruefmarke: header-umbruch-stufe-3-4 */
@media (max-width: 737px) {
  .site-navigation--open .submenu-chevron {
    display: none;
  }

  .site-navigation--open .submenu {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  /* Submenu entries a step smaller than their parent, so the second level reads
     as subordinate without needing a box or a rule. */
  .site-navigation--open .submenu a {
    font-size: var(--font-size-x-small);
  }
}

/* Every header link looks the same: font, size, button-sized hover plate, so
   branding and menu items match on the one line. margin: 0 keeps the items out
   of the running-text rule. Pseudo classes written out to beat the "Links"
   padding. */
.site-header a:link,
.site-header a:visited,
.site-header a:hover,
.site-header a:active,
.site-header a:focus-visible {
  font-family: var(--heading-font);
  font-size: var(--font-size-small);
  line-height: 1.1;

  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2) var(--space-5);
  margin: 0;
  border-radius: 7px;
}

/* THE ONLY PLACE THAT APPLIES THE RESTING COLOUR - to the links and to the menu
   button alike. Which colour it is was decided further up by whoever is nearest:
   the header, or an area inside it with a reason of its own. A light selector on
   purpose: :hover below only has to beat this one. */
.site-header a:link,
.site-header a:visited,
.menu-toggle {
  color: var(--header-link-color);
}

/* Hover whether a hero is present or not: white on the orange plate. */
.site-header a:hover,
.site-header a:active,
.site-header a:focus-visible {
  color: var(--link-hover-color);
}

/* THE ACTIVE MENU ENTRY: the current page, underlined (not the hover plate, not
   a colour that could vanish on the hero photo). currentColor follows every
   state and whether a hero is present; fixed thickness so it does not thin in the fallback
   font. aria-current="page" says the same to a screen reader. */
.site-header a[aria-current="page"]:link,
.site-header a[aria-current="page"]:visited,
.site-header a[aria-current="page"]:hover,
.site-header a[aria-current="page"]:active,
.site-header a[aria-current="page"]:focus-visible {
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

/* Fullscreen toggle button. In the site header, right of the menu. */
.fullscreen-toggle {
  /* Semi-transparent orange plate, mixed from the one orange source so opacity
     stands beside it. */
  background-color: color-mix(in srgb, var(--primary-color) 70%, transparent);
  border: none;
  font-size: var(--icon-size);  /* the SVG inherits this via 1em */
  color: var(--text-color-on-primary);  /* white on orange; a button, not a link, so it reads the root value */
  /* Side padding as a menu item; top/bottom calc'd so the button ends up
     exactly as tall as a menu item beside it. */
  padding: calc(var(--space-2) + (var(--font-size-small) * 1.1 - var(--icon-size)) / 2) var(--space-5);
  border-radius: 7px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  /* Stage 2 -> 3: hidden together with the branding addition, one step before
     the menu folds. The least useful control (F11 does its job, a phone has no
     window to enlarge) leaves with the addition, so the row shows branding +
     menu on its own before folding into the overlay. Same measured width as the
     addition rule above, under the matching pruefmarke. */
  /* pruefmarke: header-umbruch-stufe-2-3 */
  @media (max-width: 1097px) {
    display: none;
  }
}

/* Hover: the plate goes to full orange. Own rule because a <button> never gets
   the general link hover. */
.fullscreen-toggle:hover,
.fullscreen-toggle:focus-visible {
  background-color: var(--primary-color);
}

.fullscreen-toggle svg {
  width: 1em;
  height: 1em;
  display: block;
}

/* Show only the icon for the current state (script writes data-fullscreen):
   first svg is "enter", second is "exit". */
.fullscreen-toggle[data-fullscreen="off"] svg:last-of-type {
  display: none;
}

.fullscreen-toggle[data-fullscreen="on"] svg:first-of-type {
  display: none;
}

/* ==========================================================================
   Content module: site header END
   ========================================================================== */

/* ==========================================================================
   Back-to-top button
   ========================================================================== */

/* Pinned to the bottom-right corner, above the page but below the open menu
   overlay (its layer sits between). The orange plate matches the fullscreen
   toggle: white icon, full orange on hover. js/back-to-top.js reveals it once
   the page is scrolled a screenful and hides it again near the top. */
.back-to-top {
  position: fixed;
  right: var(--space-6);
  bottom: var(--space-6);
  z-index: var(--layer-scroll-top);

  /* Semi-transparent orange plate, mixed from the one orange source so opacity
     stands beside it - same recipe as the fullscreen toggle. */
  background-color: color-mix(in srgb, var(--primary-color) 70%, transparent);
  border: none;
  font-size: var(--icon-size);  /* the SVG inherits this via 1em */
  color: var(--text-color-on-primary);  /* white on orange; a button, not a link, so it reads the root value */
  padding: var(--space-2);
  border-radius: 7px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  /* Hidden until the script adds the visible class. The button starts with the
     hidden attribute too, so it never shows without the script; the script
     drops that attribute and drives the rest with the class from here on. */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, visibility 0.2s;
}

/* The script adds this class past one screenful of scroll, removes it near the
   top. */
.back-to-top--visible {
  opacity: 1;
  visibility: visible;
}

/* Hover: the plate goes to full orange. Own rule because a <button> never gets
   the general link hover. */
.back-to-top:hover,
.back-to-top:focus-visible {
  background-color: var(--primary-color);
}

.back-to-top svg {
  width: 1em;
  height: 1em;
  display: block;
}

/* ==========================================================================
   TEST ONLY - REMOVE BEFORE THE TEMPLATE SHIPS

   Every column gets a visible ground, so it can be seen where a column ends
   and whether anything inside it reaches past that edge. A module sits IN a
   column and must not stick out of it; with the ground drawn, one that does
   is visible instead of having to be measured.

   .layout-col--full-width steps out to the screen edge, which is a different
   thing - there the column moves, not the module inside it. Its ground moves
   with it, so it still shows the same statement.
   ========================================================================== */
/*
.layout-col {
  background-color: #ffe8b0;
}

 The following markup is for RS. It allows to quickly identify the space each of these elements takes in the final page. Claude is not allowed to remove it.
body { background-color: red !important; }
main { background-color: blue !important; }
.container { background-color: green !important; }
.layout-row { background-color: yellow !important; }
.layout-col { background-color: orange !important; } */
