/* Oskar site skeleton (stage 9).
   The starting point every customer project is copied from. It is deliberately quiet:
   the character of a site comes from its content and from what is changed here, not
   from what the skeleton already insists on.

   Re-skinning a project happens in `tokens.css` and nowhere else – colours, type sizes and
   distances are values there, and the panel can write them (stage 17). Everything here refers
   to those names, so a new palette never means a search through this file. */

/* The faces themselves are declared in `fonts.css`, which the panel writes (stage 26): the
   files are in this package and nothing is fetched from another host while the site is read
   (§5). They used to stand here by hand, which meant that changing a typeface was an
   afternoon and that the field in the panel promised something it could not do.

   The values themselves live in `tokens.css`, which the layout loads before this file, and
   which the panel writes (stage 17). What they are called and what each one is for stands in
   `site/tokens.php`; everything below refers to the names and never to a colour or a size.

   Four rules hold this file together, and `tests/test_skeleton.php` enforces every one of
   them, so none of them depends on anybody remembering (stage 34/0):

   - no colour here, ever – a colour is a token and belongs in `tokens.css`
   - every distance is `var(--space-*)`, and every one of those is a whole quarter of
     `--baseline`, which is one line of body text. That is the vertical rhythm: nothing on
     this page stands at a distance the grid does not know
   - every `font-size` is `var(--text-*)`, so there are five sizes on this website and not
     eleven, and they differ by enough to read as a decision
   - the reason `line-height` is 1.5 and not 1.65 is the same: 1.5 is what `--baseline` is
     built from, and a body line that is not exactly one module puts everything below it
     between two lines of the grid */

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

/* 18px, but as a percentage: a browser font size preference still scales everything. */
html {
  font-size: 112.5%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-m);
  line-height: 1.5;   /* one --baseline, which is what every distance is measured in */
  hyphens: auto;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.15;
  text-wrap: balance;
}

p { text-wrap: pretty; }

img {
  max-width: 100%;
  height: auto;
}

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

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

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

/* Skip link: visible the moment it has focus, out of the way otherwise. */
.skip {
  position: absolute;
  top: var(--space-xs);
  left: var(--space-xs);
  z-index: 10;
  transform: translateY(-250%);
  border-radius: var(--radius);
  background: var(--accent);
  padding: var(--space-xs) var(--space-m);
  color: var(--paper);
  text-decoration: none;
}

.skip:focus {
  transform: none;
  color: var(--paper);
}

/* Every direct child of main is one block, and every block is centred on the page.
   Text inside stays at --measure, so a wide screen never produces a wide line. */
.site-footer,
main > * {
  margin-inline: auto;
  padding-inline: var(--space-m);
  max-width: var(--width-page);
}

/* One rhythm for every section, and it is the same above as below (stage 37).

   It used to be two: a plain block had two lines of air, a band had four. Reading down the
   page that meant the distance between two sections changed with what the sections happened
   to be – four lines here, six there, eight where two bands met. Nobody chose those numbers
   either; they fell out of two rules that were written years apart.

   Now every section has four lines above and four below, and where two sections stand on the
   same ground the second gives up its top: the air between them is four lines and not eight.
   Where the ground changes, both keep their own, because that air is no longer a gap between
   two things – it is the inside of the coloured one. */
main > * { padding-block: var(--space-xl); }

/* Every block measures itself, not the window (stage 40).

   A media query asks the window. That was the same question as „wie breit ist der Block",
   as long as a block was the width of the page – and it stopped being the same question the
   day two blocks could share a line. A block that owns a third of the row still sees a wide
   window and lays out two columns in the width of one: the address broken after every
   syllable, the opening times beside it in a column four letters wide.

   So the block is a container, and every layout *inside* a block is written as `@container`
   below. The numbers there are the old ones minus the page padding, which is exactly what
   a block standing alone on the page has: nothing about a full-width page changes.

   `inline-size` and not `size` – the width comes from outside, the height is still whatever
   the contents come to. */
main > *,
.row > * { container-type: inline-size; }

main > :not(.band) + :not(.band),
.band--sunk + .band--sunk,
.band--dark + .band--dark { padding-block-start: 0; }

/* Two blocks in one row (stage 40).

   The row is one section as far as the rhythm above is concerned – it is a child of main like
   any other block, so it carries the four lines and the blocks inside it carry none. What
   decides the widths is `flex-grow`: a row holds four parts, and each block takes as many of
   them as it asked for. Whole parts only, so every row is halves, thirds or quarters, and the
   page never grows a width that belongs to nothing else on it.

   `flex-basis: 0` is what makes that arithmetic true. With the default `auto` the parts would
   be divided over what is left after the content, and two blocks with different amounts of
   text would come out at two widths nobody asked for.

   Under 44rem there is no row: one column, and the blocks stand under each other in the order
   they were written. That is why the field says „beside" and not „half" – „half" would be a
   promise this breakpoint breaks. */
.row {
  display: flex;
  flex-direction: column;
  gap: var(--space-l);
}

.row > * { min-width: 0; }

@media (width >= 44rem) {
  .row { flex-direction: row; }
  .row > * { flex: 1 1 0; }
  .row > [data-share="2"] { flex-grow: 2; }
  .row > [data-share="3"] { flex-grow: 3; }
}

/* A band inside a row is a surface, not a band: it cannot run the full width of the window
   when it only owns a third of the line. It keeps its colour and gives up the bleed. */
.row > .band {
  border-radius: var(--radius);
  padding: var(--space-l);
}

/* Header ------------------------------------------------------------------ */

/* Full width, and it stays. A header held to the page grid puts its background on a
   68-rem island in the middle of the window – invisible while it is white on white, and
   obvious the moment it stands still over a picture. So the band runs to both edges and the
   contents are put back on the grid by the same padding the picture hero uses.

   Sticky, because the telephone number is what most of this trade's visitors came for, and
   scrolled past it is gone. */
.site-header {
  z-index: 10;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-l);
  align-items: center;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  padding-block: var(--space-xs);
  padding-inline: max(var(--space-m), calc(50% - var(--width-page) / 2 + var(--space-m)));
}

/* One cluster on the left, one action on the right. Spread by `space-between`, three items
   leave the menu floating between the mark and the button, tied to neither – the logo and
   the places it leads to belong together, and the call is the only thing on the other side. */
.site-nav { margin-inline-end: auto; }

/* Under 62rem the three parts do not fit on one line – that is where the menu, the mark and
   the number stop fitting side by side, not at some round number. Wrapped in document order
   they would put the menu between the mark and the number; instead the number moves up next
   to the mark, the two things worth a tap, and the menu takes the line below.

   The header stands still only where it is one row high. Two rows pinned to the top of a
   small screen is a fifth of it given to navigation on a page nobody has read yet. */
@media (width < 62rem) {
  .site-header { gap: var(--space-2xs) var(--space-s); }
  .site-header__title { margin-inline-end: auto; }
  .site-nav { order: 1; margin-inline-end: 0; width: 100%; }
  .site-nav__list { gap: 0 var(--space-2xs); }
}

@media (width >= 62rem) {
  .site-header { position: sticky; top: 0; }
}

.site-header__title {
  display: flex;
  color: inherit;
  font-family: var(--font-display);
  font-size: var(--text-l);
  font-weight: 600;
  text-decoration: none;
}

/* Two and a half lines of body text tall. Bigger and the header becomes a title page; the
   logo has a tagline in it that stops being readable below this. */
.site-header__logo {
  display: block;
  width: auto;
  height: calc(var(--baseline) * 2.5);
}

.site-nav__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The link is a target and not a word in a line: the padding is what a thumb hits, and it is
   also what gives the menu the same rhythm as the button beside it. */
.site-nav__link {
  display: block;
  border-radius: var(--radius);
  padding: var(--space-2xs) var(--space-s);
  color: var(--ink-soft);
  font-family: var(--font-display);
  font-weight: 500;
  text-decoration: none;
  transition: color 0.15s, background-color 0.15s;
}

.site-nav__link:hover {
  background: var(--paper-sunk);
  color: var(--ink);
}

/* The current page is marked the way the site marks a beginning everywhere else: by the
   short accent rule. Under a padded link it sits where a rule belongs instead of clinging to
   the letters. */
.site-nav__link[aria-current] {
  position: relative;
  color: var(--ink);
  font-weight: 600;
}

.site-nav__link[aria-current]::after {
  position: absolute;
  bottom: 0;
  left: var(--space-s);
  right: var(--space-s);
  background: var(--accent);
  height: 2px;
  content: "";
}

/* The telephone number in the header (this package). For a roofer it is the thing most
   visitors came for, and on a phone it is one tap – the old site had it in the menu as plain
   text, which is a number nobody can dial. It is not a menu entry here: a menu is places on
   this website, and this one leaves it. */
/* Filled and not outlined: an outline in a row of quiet links is one more quiet thing. This
   is the only action in the header and it is allowed to look like one. The shape itself is
   the site's one button, made further down; what stands here is only what a number in a
   header needs on top of it. */
.site-header__call {
  align-items: center;
  gap: var(--space-2xs);
  white-space: nowrap;
}

/* The word on a wide screen, the mark on a narrow one. „Anrufen: 0421 600 70 80" is 251 of
   the 321 pixels a telephone leaves inside the header – with the logo and the column gap
   beside it that is 391, so the button wrapped to a line of its own, and a control that
   wrapped by accident looks like one. The mark says the same in the width of one character.

   The word is taken out of sight, not out of the document: a link whose whole text is a
   number is one a screen reader hands over without saying what it does. */
.site-header__call .icon { display: none; }

@media (width < 44rem) {
  .site-header { column-gap: var(--space-m); }
  .site-header__call { padding-inline: var(--space-s); }
  .site-header__call .icon { display: block; width: 1.25em; height: 1.25em; }

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

/* Below this there is no arithmetic that helps: 266 pixels inside the header, and the logo
   plus the number want 311 of them even with the word gone. So the break stops being an
   accident and becomes the layout – full width, which is what a telephone number wants to be
   on a screen this size anyway. */
@media (width < 22.5rem) {
  .site-header__call { flex: 1 0 100%; }
}

/* Hero -------------------------------------------------------------------- */

.hero { padding-block: var(--space-xl); }

/* The same hero on a page that is not the front page (stage 36). „Hero (schmal)" stood five
   times in the page list of the craft business, and it is not a second block type: it is the
   opening with less air above it, so what stands under it is on the screen straight away. */
/* The one asymmetry on the page, and the reason it is allowed: what stands above a narrow
   hero is the header and not another section, so the four lines every section keeps above it
   would be four lines against a rule that is already a boundary. Below it, the rhythm is the
   page's again. */
.hero--narrow { padding-block: var(--space-l) var(--space-xl); }
.hero--narrow .hero__title::after { margin-top: var(--space-s); }

/* Display type is set tight. At this size the default line height of body text tears an
   opening sentence into separate lines, and the default letter spacing – drawn for a
   paragraph – leaves holes between the capitals. `balance` is what stops a four-word last
   line from hanging under a full one. */
.hero__title {
  margin: 0;
  max-width: 20ch;
  font-size: var(--text-display);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

/* The one shape that repeats across the site: a short accent rule under the opening
   line. It marks where a page begins without needing a graphic. */
.hero__title::after {
  display: block;
  margin-top: var(--space-m);
  background: var(--accent);
  width: 3.5rem;
  height: 3px;
  content: "";
}

/* The opening with a picture in it -----------------------------------------

   Full bleed, so the two rules that hold a block on the page grid are given back and the
   inline padding is rebuilt the way `.band` does it: the picture runs to both edges of the
   window while the sentence stays on the same left edge as every other block.

   The letters sit at the bottom, which is where a scrim can be made honest: it is opaque
   under the whole text block and fades out above it, so nothing is ever read against the
   photograph itself. Measured against the worst case a photograph can be – pure white – the
   scrim leaves 6.3:1 under the text, and a real sky is darker than that. The old site set
   white type over unscrimmed roof tiles, where the contrast changed with every tile. */
.hero--picture {
  position: relative;
  display: grid;
  align-items: end;
  max-width: none;
  /* Thirty lines of body text tall, or most of the window, whichever is less – a height on
     the module like every other distance, and not a number that looked right once. */
  min-height: min(84vh, calc(var(--baseline) * 30));
  padding-block: var(--space-xl);
  padding-inline: max(var(--space-m), calc(50% - var(--width-page) / 2 + var(--space-m)));
  overflow: hidden;
}

.hero__frame {
  position: absolute;
  inset: 0;
}

.hero__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 45%;
}

/* The ground the letters stand on – and the reason there is no dark box in this hero. A panel
   pasted over a photograph solves the contrast by covering the thing that was paid for; two
   gradients solve it and leave the picture. The upward one carries the text block, the one
   from the left keeps the first words dark-backed even where a photograph is bright there,
   and both are gone in the corner the picture is actually seen in. */
.hero__frame::after {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to top,
      color-mix(in srgb, var(--ink) 88%, transparent) 0%,
      color-mix(in srgb, var(--ink) 72%, transparent) 38%,
      transparent 78%
    ),
    linear-gradient(
      to right,
      color-mix(in srgb, var(--ink) 62%, transparent) 0%,
      transparent 62%
    );
  content: "";
}

.hero--picture .hero__inner {
  position: relative;   /* above the frame, which is absolute and therefore later in paint order */
  max-width: var(--measure);
}

/* The size is `--text-display` and nothing else: the opening line over a photograph is the
   largest line the site has, so what had to change was the token and not this rule. It now
   runs from step 5 of the scale to step 8 instead of stopping at 6. */
.hero--picture .hero__title {
  color: var(--paper);
  text-shadow: 0 1px 24px color-mix(in srgb, var(--ink) 55%, transparent);
}

/* The accent rule under the title is the site's one repeating shape – the same size here as
   everywhere else. On the photograph it takes the yellow out of the logo instead of the blue:
   it carries no text, so it is free to be the one colour that says whose roof this is. */
.hero--picture .hero__title::after { background: var(--band); }

.hero--picture .hero__lead {
  color: color-mix(in srgb, var(--paper) 92%, transparent);
  text-shadow: 0 1px 18px color-mix(in srgb, var(--ink) 55%, transparent);
}

/* `lnk()` writes a plain anchor and takes no class, so the shape is given to it further down,
   where every other button on this site is made. */
.hero__action { margin: var(--space-m) 0 0; }

@media (width < 44rem) {
  .hero--picture { min-height: calc(var(--baseline) * 22); }
}

.hero__lead {
  margin: var(--space-m) 0 0;
  max-width: var(--measure);
  color: var(--ink-soft);
  font-size: var(--text-l);
}

/* Bands -------------------------------------------------------------------- */

/* A section that runs over the full width while its text stays on the page grid. No `100vw`
   anywhere: `main` is as wide as the window, only its children are held back, so a child that
   gives up its own limit is already full width – and the padding puts the text back where
   every other block starts. That is why this needs no `overflow-x` hack, no wrapper element
   and no scrollbar arithmetic.

   Which blocks offer it is a `tone` field on the block, not a second block type: „derselbe
   Abschnitt auf einer eigenen Fläche" is a variant (§7). */
.band {
  max-width: none;
  padding-inline: max(var(--space-m), calc(50% - var(--width-page) / 2 + var(--space-m)));
}

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

.band--dark {
  background: var(--band);
  color: var(--band-ink);
}

/* What the band turns over. Everything named here is something that reads its colour from
   the paper it usually stands on; nothing else in this file needs to know bands exist. */
.band--dark a { color: var(--band-ink); }
/* The card keeps its paper on the band – a surface that took the band's colour would stop
   being a card and become a hole in it. Only its hairline gives way, because a line drawn in
   the paper's own grey is invisible against yellow. */
.band--dark .card { border-color: color-mix(in srgb, var(--band-ink) 25%, transparent); }
.band--dark .contact__label,
.band--dark .contact__hours dt,
.band--dark .entry__date,
.band--dark .entry__lead { color: color-mix(in srgb, var(--band-ink) 72%, transparent); }

/* Section headings -------------------------------------------------------- */

/* Every section opens the way the page opens: the line, and the short rule under it. That
   rule was the hero's alone, which made the opening a decoration rather than a system – one
   shape used twice is a signature, used once it is an ornament.

   `.form__heading` and `.compare__heading` are in the list for the same reason: an editor
   who places a form under a heading has placed a section, and it has to look like one. */
.services__heading,
.entries__heading,
.contact__heading,
.map__heading,
.team__heading,
.prose__heading,
.form__heading,
.compare__heading {
  margin: 0 0 var(--space-l);
  font-size: var(--text-heading);
}

.services__heading::after,
.entries__heading::after,
.contact__heading::after,
.map__heading::after,
.team__heading::after,
.prose__heading::after,
.form__heading::after,
.compare__heading::after {
  display: block;
  margin-top: var(--space-s);
  background: var(--accent);
  width: 3.5rem;
  height: 3px;
  content: "";
}

/* Grids -------------------------------------------------------------------- */

/* How many to a row, decided by how many there are (stage 37).

   `auto-fit` put as many in a row as fitted and left the remainder standing alone in the
   next one: four cards became three and one, seven became three, three and one. Nobody chose
   that – it is what „as many as fit" means – and a row with a single card in it reads as
   something that did not fit rather than as the end of a set. The old answer was to ask the
   editor for a column count, which is a field about the layout in a panel that is otherwise
   about the content, and a field somebody has to get right on every block.

   So the grid counts itself. `:has(> :nth-child(4):last-child)` is true of exactly four
   children, and for every count there is a number of columns that divides it, or the one
   that leaves the smallest remainder: four go two and two, six go three and three, seven go
   four and three, eight go four and four. Ten and more take four, where the remainder is at
   worst two. Five and seven have no even division at all – those are the two counts where the
   content really is the problem, and the grid picks the arrangement that hides it best.

   Three widths, not a hundred: one column on a telephone, the middle number on a tablet, the
   full number on a screen that has room for it. `minmax(0, 1fr)` and not `1fr`, because a
   long word in a card would otherwise push its column wider than its share.

   No script, no field, nothing to keep in sync – an editor adds a card and the row that holds
   it is already right. */
.services__grid,
.team__grid,
.entries__grid,
.gallery__grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--space-m);
  --cols: 1;
  --cols-mid: 1;
  --cols-wide: 1;
}

.services__grid:has(> :nth-child(2):last-child),
.team__grid:has(> :nth-child(2):last-child),
.entries__grid:has(> :nth-child(2):last-child),
.gallery__grid:has(> :nth-child(2):last-child) { --cols-mid: 2; --cols-wide: 2; }

.services__grid:has(> :nth-child(3):last-child),
.team__grid:has(> :nth-child(3):last-child),
.entries__grid:has(> :nth-child(3):last-child),
.gallery__grid:has(> :nth-child(3):last-child) { --cols-mid: 3; --cols-wide: 3; }

.services__grid:has(> :nth-child(4):last-child),
.team__grid:has(> :nth-child(4):last-child),
.entries__grid:has(> :nth-child(4):last-child),
.gallery__grid:has(> :nth-child(4):last-child) { --cols-mid: 2; --cols-wide: 2; }

.services__grid:has(> :nth-child(5):last-child),
.team__grid:has(> :nth-child(5):last-child),
.entries__grid:has(> :nth-child(5):last-child),
.gallery__grid:has(> :nth-child(5):last-child) { --cols-mid: 3; --cols-wide: 3; }

.services__grid:has(> :nth-child(6):last-child),
.team__grid:has(> :nth-child(6):last-child),
.entries__grid:has(> :nth-child(6):last-child),
.gallery__grid:has(> :nth-child(6):last-child) { --cols-mid: 3; --cols-wide: 3; }

.services__grid:has(> :nth-child(7):last-child),
.team__grid:has(> :nth-child(7):last-child),
.entries__grid:has(> :nth-child(7):last-child),
.gallery__grid:has(> :nth-child(7):last-child) { --cols-mid: 3; --cols-wide: 4; }

.services__grid:has(> :nth-child(8):last-child),
.team__grid:has(> :nth-child(8):last-child),
.entries__grid:has(> :nth-child(8):last-child),
.gallery__grid:has(> :nth-child(8):last-child) { --cols-mid: 2; --cols-wide: 4; }

.services__grid:has(> :nth-child(9):last-child),
.team__grid:has(> :nth-child(9):last-child),
.entries__grid:has(> :nth-child(9):last-child),
.gallery__grid:has(> :nth-child(9):last-child) { --cols-mid: 3; --cols-wide: 3; }

/* Ten and more: no count of its own, because from here on the remainder is small whatever
   the number is, and a rule per count would go on for ever. */
.services__grid:has(> :nth-child(10)),
.team__grid:has(> :nth-child(10)),
.entries__grid:has(> :nth-child(10)),
.gallery__grid:has(> :nth-child(10)) { --cols-mid: 3; --cols-wide: 4; }

@container (width >= 41rem) {
  .services__grid,
  .team__grid,
  .entries__grid,
  .gallery__grid { --cols: var(--cols-mid); }
}

@container (width >= 59rem) {
  .services__grid,
  .team__grid,
  .entries__grid,
  .gallery__grid { --cols: var(--cols-wide); }
}

/* What keeps a lone card a card, and every row of them the same width. The ceiling is on the
   grid and not on the card: a card capped inside a wider column stands at the left of a hole,
   and four cards in two columns then read as four cards in four. Capped here, the columns are
   as wide as a card may be and the row closes up.

   One card in one column is the case this is really about – the last page of a paginated
   listing, which is the normal case and not an edge one: seven entries at six to a page leave
   one over, and without a ceiling it would stand there four times the size it has anywhere
   else. */
.services__grid,
.team__grid,
.entries__grid,
.gallery__grid {
  max-width: calc(var(--cols) * var(--width-card) + (var(--cols) - 1) * var(--space-m));
}

/* Services ---------------------------------------------------------------- */

/* The team uses the same grid and the same `.card`: a face is a card too, and a second set
   of rules for the same shape is the drift this file is built to prevent. */

/* A card is a surface and not three loose things under a line. The 2-pixel rule over each
   one was the whole card system, and it did two things badly: it sat *above* the picture,
   where it read as a leftover from a table, and it left the text with no edge at all – so
   six cards were six ragged columns whose bottoms ended wherever the sentence did.

   A hairline and a corner cost the same and say what a card is: one thing, of a set. The
   picture runs flush into the top edge, which is what `overflow: hidden` is for; only the
   words are set in from it. */
.card,
.entry {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  padding-bottom: var(--space-s);
  overflow: hidden;
}

/* A card without a picture would otherwise start at its own edge. */
.card:not(:has(.card__image)) { padding-top: var(--space-s); }

/* The words are set in from the edge; the picture is not, it is the edge. In the listing the
   link wraps picture and title together, so the title is named on its own. */
.card > :not(.card__image),
.entry > :not(.entry__link),
.entry__title { padding-inline: var(--space-s); }

/* The picture belongs above the words, hence the order – its corners are the card's. */
.card__image {
  display: block;
  order: -1;
  margin-bottom: var(--space-s);
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* Where the picture is, when a slot shows less of it than there is.
   `img()` writes the position it was given onto the element, and the attribute's value is
   the CSS value – so this is eight lines of the same line. Written out rather than computed
   from the attribute, because `attr()` can do that in exactly one browser generation and
   these rules have to hold on whatever a customer's visitor is using. The middle is not
   among them: it is the default, and a picture without an opinion carries no attribute. */
img[data-focus="left top"]      { object-position: left top; }
img[data-focus="center top"]    { object-position: center top; }
img[data-focus="right top"]     { object-position: right top; }
img[data-focus="left center"]   { object-position: left center; }
img[data-focus="right center"]  { object-position: right center; }
img[data-focus="left bottom"]   { object-position: left bottom; }
img[data-focus="center bottom"] { object-position: center bottom; }
img[data-focus="right bottom"]  { object-position: right bottom; }

.card__title {
  margin: 0 0 var(--space-2xs);
  font-size: var(--text-l);
}

.card p { margin-block: 0 var(--space-s); }
.card > :last-child { margin-bottom: 0; }

/* What somebody does, under their name: quieter than the sentence that follows it. */
.team__role {
  margin-block: 0 var(--space-xs);
  color: var(--ink-soft);
}

/* Running text ------------------------------------------------------------ */

.prose__body { max-width: var(--measure); }
.prose__body p { margin-block: 0 var(--space-m); }
.prose__body > :last-child { margin-bottom: 0; }

/* The same block with a picture beside it. Two columns of equal weight, because a picture
   that is the smaller half of a row reads as an illustration and one that is the larger half
   reads as the point – and here neither is true, they are two halves of one statement.

   Where the two columns give up is asked of nothing: `flex-basis` is half of the room they
   need, so the picture wraps under the text exactly when it no longer fits beside it. That is
   not a matter of taste here – **this element is the block**, and a block is the container, so
   it cannot ask its own width. A `@container` on this selector would be a question with no
   answer, and no answer is one column at every width. What a block says about itself has to
   size itself. */
.prose--media {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-l);
  align-items: center;
}

.prose--media > * { flex: 1 1 calc((49rem - var(--space-l)) / 2); }

/* One format for the half of the row that is a picture. Uncropped, an upright photograph
   decides how tall the whole section is – the moss picture made a band of four sentences as
   tall as a screen and a half, with the yellow standing empty beside it. The same 3:2 the
   cards and the gallery are cut to, so the page has one picture shape and not four. */
.prose--media .prose__image {
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* The picture first in the source would put it above the text on a telephone, where the text
   is what somebody came for. So it stands second and moves – but only where there are two
   columns to move it between, and *that* the picture may ask, because the block it stands in
   is the container and the picture is inside it. The 49rem is the number above. */
@container (width >= 49rem) {
  .prose--start .prose__image { order: -1; }
}

.prose--media .prose__body { max-width: none; }

.prose__image {
  display: block;
  border-radius: var(--radius);
  width: 100%;
}

/* Pictures ---------------------------------------------------------------- */

/* A single picture needs no rule of its own: the figure is as wide as the block and the
   picture as wide as the figure. What follows is the two arrangements for several. */
.gallery__item { margin: 0; }

/* No width here, and that is the point: a single picture is as wide as it is, at most as wide
   as the block. `width: 100%` would blow a 900 pixel photograph up to 1200 and make it soft –
   the global `img` rule already keeps it from overflowing. Side by side it is different: a
   cell that the picture does not fill is a hole in the row, so the two arrangements ask for
   the full width themselves. */
.gallery__image {
  display: block;
  border-radius: var(--radius);
}

/* Side by side the pictures are cropped to one format, because a row of different heights is
   the thing that makes a page look unfinished. How many stand in a row is the grid's own
   arithmetic, further up.
*/

.gallery__grid .gallery__image,
.gallery__masonry .gallery__image { width: 100%; }

.gallery__grid .gallery__image {
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* And the arrangement for pictures that must not be cropped – portraits and landscapes
   mixed. `columns` is the whole feature: the browser fills them, and `break-inside` keeps a
   picture and its caption together. The reading order runs down a column and not across,
   which is why this is the choice for a set of pictures and not for a sequence. */
.gallery__masonry {
  columns: 16rem;
  column-gap: var(--space-m);
}

.gallery__masonry .gallery__item {
  break-inside: avoid;
  margin-bottom: var(--space-m);
}

.gallery__caption {
  margin-top: var(--space-2xs);
  max-width: var(--measure);
  color: var(--ink-soft);
  font-size: var(--text-s);
}

/* The button `lightbox.js` wraps every picture in. It has to leave the layout exactly as it
   found it – a browser's own button styling would put a border, a background and a font size
   between the figure and the picture. The two arrangements ask for the full width the same
   way the pictures inside them do. */
.gallery__zoom {
  display: block;
  width: fit-content;
  max-width: 100%;
  border: 0;
  padding: 0;
  background: none;
  cursor: zoom-in;
}

.gallery__grid .gallery__zoom,
.gallery__masonry .gallery__zoom { width: 100%; }

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

/* One picture, filling the window ------------------------------------------ */

/* Escape, the backdrop, the focus trap and handing the focus back are `<dialog>`'s own.
   What is left is where the picture sits and how big it may get. */
.lightbox {
  max-width: 100vw;
  max-height: 100vh;
  width: 100%;
  height: 100%;
  border: 0;
  padding: 0;
  background: none;
  overflow: hidden;
}

.lightbox::backdrop { background: color-mix(in srgb, var(--band) 88%, transparent); }

.lightbox__figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-s);
  /* The frame the picture is measured against: the whole window less the air around it. */
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: var(--space-m);
}

/* `min-height: 0` is what lets a flex child actually shrink – without it a tall photograph
   pushes its own caption off the bottom of the screen. */
.lightbox__image {
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius);
}

.lightbox__caption {
  max-width: var(--measure);
  color: var(--band-ink);
  font-size: var(--text-s);
  text-align: center;
}

/* Over the picture rather than beside it, because the picture is as large as the window.
   Two and a half modules square: a thumb target, not a typographic character. */
.lightbox__close {
  position: absolute;
  top: var(--space-s);
  inset-inline-end: var(--space-s);
  z-index: 1;
  width: 2.75rem;
  height: 2.75rem;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--band) 65%, transparent);
  color: var(--band-ink);
  font-size: var(--text-l);
  line-height: 1;
  cursor: pointer;
}

.lightbox__close:focus-visible {
  outline: 2px solid var(--band-ink);
  outline-offset: 2px;
}

/* Before and after ---------------------------------------------------------- */

/* One number does all of it: the upper picture is clipped to it and the handle stands at it.
   50% until the script moves it, which is why the block is worth looking at with no script
   at all. */
.compare { --compare: 50%; }

.compare__frame {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  line-height: 0;
}

.compare__image {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* The upper picture lies exactly on the lower one and is cut at the handle. `inset-inline-end`
   rather than a width, so the two never disagree by a rounding error. */
.compare__clip {
  position: absolute;
  inset: 0;
  inset-inline-end: calc(100% - var(--compare));
  overflow: hidden;
}

.compare__clip .compare__image {
  position: absolute;
  inset: 0;
  height: 100%;
  /* The clip is narrower than the frame, so the picture inside it has to keep the frame's
     width – otherwise it squeezes as the handle moves and the two stop lining up. */
  width: 100cqw;
}

.compare__frame { container-type: inline-size; }

.compare__tag {
  position: absolute;
  top: var(--space-xs);
  border-radius: var(--radius);
  padding: var(--space-2xs) var(--space-xs);
  background: var(--band);
  color: var(--band-ink);
  font-size: var(--text-s);
  line-height: 1;
}

.compare__tag--before { inset-inline-start: var(--space-xs); }

.compare__tag--after {
  inset-inline-end: var(--space-xs);
  background: var(--accent);
  color: var(--paper);
}

/* The real control, lying over the whole picture: keyboard, screen reader and thumb all work
   because this is an `<input type="range">` and not a div with listeners on it. Its own
   appearance is taken off; what the eye follows is the line and the grip below. */
.compare__range {
  position: absolute;
  inset: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  background: none;
  appearance: none;
  cursor: ew-resize;
}

.compare__range::-webkit-slider-thumb {
  appearance: none;
  width: var(--space-xl);
  height: 100vh;
}

.compare__range::-moz-range-thumb {
  border: none;
  background: none;
  width: var(--space-xl);
  height: 100vh;
}

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

/* The line and the grip. `pointer-events: none`, because the input underneath is what is
   being operated – this only shows where it stands. */
.compare__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--compare);
  translate: -50%;
  background: var(--paper);
  width: 2px;
  pointer-events: none;
}

.compare__handle::after {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  border-radius: 50%;
  background: var(--paper);
  width: var(--space-l);
  height: var(--space-l);
  box-shadow: 0 2px 10px var(--shadow);
  content: "";
}

.compare__caption {
  margin: var(--space-s) 0 0;
  max-width: var(--measure);
  color: var(--ink-soft);
  font-size: var(--text-s);
}

/* Questions and answers ---------------------------------------------------- */

/* `<details>` and `<summary>` do all of this. No script, no `aria-expanded` to keep in
   sync, no state to lose – and the browser's own find-on-page opens a folded answer that
   holds the search term, which no hand written accordion has ever managed. */
.faq__list {
  max-width: 48rem;
  border-top: 1px solid var(--line);
}

.faq__item { border-bottom: 1px solid var(--line); }

.faq__question {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-m);
  padding-block: var(--space-s);
  font-family: var(--font-display);
  font-size: var(--text-l);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

/* Safari still draws its own triangle unless this is said, and it belongs on the left of a
   line whose marker is on the right. */
.faq__question::-webkit-details-marker { display: none; }

.faq__question::after {
  flex: none;
  color: var(--accent);
  content: "+";
  font-size: var(--text-l);
}

.faq__item[open] .faq__question::after { content: "\2013"; }
.faq__question:hover { color: var(--accent); }

.faq__answer {
  padding-bottom: var(--space-m);
  max-width: var(--measure);
  color: var(--ink-soft);
}

.faq__answer > :first-child { margin-top: 0; }
.faq__answer > :last-child { margin-bottom: 0; }
.faq__answer p { margin-block: 0 var(--space-s); }

/* Contact ----------------------------------------------------------------- */

/* Two columns and not three: the address and the times are read side by side, the map is not
   read at all at a third of the page. It stands under them now, over the full width, and the
   row is the two things that are words. */
.contact__grid {
  display: grid;
  gap: var(--space-l);
}

@container (width >= 41rem) {
  .contact__grid { grid-template-columns: 1fr 1fr; }
}



/* The small capitals that name a column: over the address, over the opening times, over the
   date of an entry, over each foot of the page. It stood written out in three places and had
   drifted apart in two of them – one shape, one rule. */
.contact__label,
.project__meta,
.site-footer__heading {
  font-family: var(--font-display);
  font-size: var(--text-s);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.contact__label {
  display: block;
  margin-bottom: var(--space-xs);
  color: var(--ink-soft);
}

/* An address is read line by line and not as running text, so it is allowed the extra
   air – one and a third modules, not a number that fits nothing. */
.contact__address {
  font-style: normal;
  line-height: calc(var(--baseline) * 4 / 3);
}

.contact__hours {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2xs) var(--space-m);
  margin: 0;
}

.contact__hours dt { color: var(--ink-soft); }
/* An opening time is one thing and never breaks in the middle. */
.contact__hours dd { margin: 0; white-space: nowrap; }

.contact__note,
.map__note {
  max-width: var(--measure);
  margin-bottom: var(--space-l);
}

/* The place, over the full width of the section. The one picture on this site that is not
   cut to 3:2: a map is not a photograph of a thing, it is a piece of ground, and a wide strip
   of it holds the streets that lead to the address above without becoming a wall. */
.place {
  margin: var(--space-l) 0 0;
}

/* In the map block the figure is the section, so it needs no gap to what stands above it. */
.map .place { margin-top: 0; }

.place__canvas {
  display: block;
  border: 0;
  border-radius: var(--radius);
  width: 100%;
  aspect-ratio: 8 / 3;
  object-fit: cover;
}

/* The line under it carries three things at once: what a click will do, whose data this is,
   and – where a click would fetch something – that it leaves this server. */
.place__note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-s);
  margin-top: var(--space-s);
  color: var(--ink-soft);
  font-size: var(--text-s);
}

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

/* On a telephone the frame is what decides the scale, not the picture: `object-fit: cover`
   fills the height and crops the sides, so a taller frame shows less ground at a larger size.
   At 4:3 the street names stood at 56 % of the size they were drawn for and were guesswork;
   square puts them at 75 %, and what is lost at the sides is the part nobody came for. */
@container (width < 41rem) {
  .place__canvas { aspect-ratio: 1 / 1; }
}

/* The loaded map, which is a different thing from the picture it replaces: a picture is
   looked at, a map is looked around in. Zooming out in a small frame is arithmetic against
   the visitor – every step out halves what is left – so the frame grows the moment it stops
   being a preview. After the rules above, because it has to win against them and a container
   query adds no specificity of its own. */
.place__canvas--live { aspect-ratio: 2 / 1; }

@container (width < 41rem) {
  .place__canvas--live { aspect-ratio: 3 / 4; }
}

/* Collection listing ------------------------------------------------------ */

/* Same grid as the service cards, and the same arithmetic decides how many stand in a row.
   What is left here is that it is a list and must not look like one. */
.entries__grid {
  margin: 0;
  padding: 0;
  list-style: none;
}

.entries__empty { color: var(--ink-soft); }

.entry__link {
  display: block;
  color: inherit;
  text-decoration: none;
}

.entry__image {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.entry__title {
  margin: var(--space-s) 0 0;
  font-size: var(--text-l);
}

/* The whole card answers, not just the line that is the link: an entry is one target, and a
   pointer that changes only the title reads as if the picture belonged to something else. */
.entry { transition: border-color 0.15s; }
.entry:has(.entry__link:hover) { border-color: var(--accent); }
.entry__link:hover .entry__title { color: var(--accent); }

.entry__date,
.entry__lead {
  margin: var(--space-2xs) 0 0;
  color: var(--ink-soft);
}

.entry__date { font-size: var(--text-s); }

.pager {
  display: flex;
  gap: var(--space-xs);
  margin-top: var(--space-l);
}

.pager__here,
.pager__link {
  border-radius: var(--radius);
  padding: var(--space-2xs) var(--space-xs);
  text-decoration: none;
}

.pager__here {
  background: var(--accent);
  color: var(--paper);
}

.pager__link:hover { background: var(--paper-sunk); }

/* One entry --------------------------------------------------------------- */

.project__title {
  margin: 0 0 var(--space-m);
  max-width: var(--measure);
  font-size: var(--text-heading);
}

/* The entry picture is bound by the screen and not by the column (this package). `width:100%`
   stretched every photograph to the full content width, which for the square ones out of the
   telephone meant a 1080 pixel picture blown up to 1200 and standing as a wall the whole
   window high – soft, and pushing the text that explains it below the fold.

   `max-height` rather than a fixed width, because the trouble is the shape and not the size:
   a wide picture may still run the full width, a square one stops when it has had its share
   of the screen. */
.project__image {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 70vh;
  border-radius: var(--radius);
}

.project__meta {
  margin: var(--space-m) 0 0;
  color: var(--ink-soft);
}

.project__lead {
  margin: var(--space-xs) 0 var(--space-m);
  max-width: var(--measure);
  font-size: var(--text-l);
}

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

/* Legal pages ------------------------------------------------------------- */

.legal h1 { font-size: var(--text-heading); }

.legal h2 {
  margin-top: var(--space-l);
  font-size: var(--text-l);
}

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

/* Downloads --------------------------------------------------------------- */

.download__meta {
  color: var(--ink-soft);
  font-size: var(--text-s);
}

/* Footer ------------------------------------------------------------------ */

/* A dark foot under the page (this package). It closes the document instead of letting it
   fade out into paper, and it is the same near-black the text is set in – not a new colour,
   the ink used as a plane. Full bleed by the same arithmetic `.band` uses. */
.site-footer {
  max-width: none;
  margin-top: var(--space-xl);
  padding-block: var(--space-xl);
  padding-inline: max(var(--space-m), calc(50% - var(--width-page) / 2 + var(--space-m)));
  background: var(--ink);
  color: color-mix(in srgb, var(--paper) 88%, transparent);
  font-size: var(--text-s);
}

/* Everything in the foot is read against the ink, so the accent – which is a colour for
   paper – would drop to about 2:1 here. The links take the paper instead, and the hover is
   the brand's yellow, which is the one other thing on this site that may sit on dark.

   The bottom bar is excluded by name: it is rendered inside the footer, but it is not footer
   text – it is fixed to the bottom of the screen and stands on `--paper`. `.site-footer a` is
   a class and an element and therefore outweighs `.bottom-nav__link` on its own, so without
   this the bar would be white on white in every entry except the one that carries
   `[aria-current]` – which is the only reason it looked half empty rather than empty. */
.site-footer a:not(.bottom-nav__link) { color: var(--paper); }
.site-footer a:not(.bottom-nav__link):hover { color: var(--band); }

.site-footer__nav { margin-bottom: var(--space-m); }

/* The footer columns come from blocks on the page `fusszeile`. However many an editor
   places, they share the width; below the breakpoint they stack. */
.site-footer__columns {
  display: grid;
  gap: var(--space-m);
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  margin-bottom: var(--space-m);
}

.site-footer__heading {
  margin-block: 0 var(--space-xs);
  color: var(--paper);
}

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

.site-footer__list a {
  color: inherit;
  text-decoration: none;
}

.site-footer__list a:hover { color: var(--band); }
.site-footer p { margin-block: 0 var(--space-xs); }

/* Buttons ----------------------------------------------------------------- */

/* One button on this website, in three places: the form, the opening over the photograph and
   the telephone number in the header. They had three sets of rules, which is how two of them
   ended up with a different weight and a different corner from the third – so the shape is
   written once and the three places say only how large they are.

   `inline-flex` rather than `inline-block`, because the header's number puts an icon beside
   its label and a block would set it on its own line. */
.button,
.hero__action a,
.site-header__call {
  display: inline-flex;
  justify-content: center;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: var(--space-xs) var(--space-m);
  background: var(--accent);
  color: var(--paper);
  font-family: var(--font-display);
  /* Said out loud, because a `<button>` that is not told otherwise takes the browser's own
     size and not the page's – the submit button was two thirds of the text around it. */
  font-size: var(--text-m);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s, border-color 0.15s;
}

.button:hover,
.hero__action a:hover,
.site-header__call:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: var(--paper);
}

/* The one on the photograph is the largest thing a visitor is asked to do, and it is read
   from further away than a field label. */
.hero__action a {
  padding: var(--space-s) var(--space-l);
  font-size: var(--text-l);
}

/* The one in the header sits in a line of menu links and takes their height. */
.site-header__call { padding-block: var(--space-2xs); }

.button--quiet {
  background: transparent;
  border-color: var(--line);
  color: var(--ink);
}

.button--quiet:hover {
  background: var(--paper-sunk);
  border-color: var(--ink-soft);
  color: var(--ink);
}

/* Contact form ------------------------------------------------------------ */

/* The measure sits on what holds text, never on the block itself: a block is a child of
   main, and main centres its children – so a narrow root drifts to the middle of the page
   while every other block starts at its left edge. */
.form__lead { max-width: var(--measure); margin-bottom: var(--space-m); }

/* The fields stand on a surface of their own. A form is a column one measure wide, and on a
   68-rem page that column had two thirds of the width standing empty beside it – which reads
   as a section that did not finish rather than as something to fill in. The panel gives the
   column an edge; the fields inside it keep the measure, because that is what a line of type
   may be and a box has no business being wider than the label above it. */
.form__body {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper-sunk);
  padding: var(--space-l);
  max-width: calc(var(--measure) + var(--space-xl));
}

/* On a telephone the same distance is a quarter of the screen. Two of them plus the page's
   own margin left 211 of 375 pixels for a field to be typed into, which is a box that looks
   like it expects one word. The surface keeps its edge, it just stops holding the fields at
   arm's length – one step down the scale, not a number picked for this one place. */
@media (width < 44rem) {
  .form__body { padding: var(--space-m); }
}

.form__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  margin-block: 0 var(--space-m);
}

/* Two fields on one line (stage 40): Vorname neben Nachname, PLZ neben Ort.

   The form is a grid of two columns and everything in it spans both, so a field that says
   nothing keeps the line it always had. A field marked half spans one, and the browser's own
   placement puts the next one beside it – two halves that meet are a pair, and a half with
   nobody after it stands where it stands, with space to its right, which is what a lone
   postcode field looks like on every form ever printed.

   Two and not four, unlike the blocks: this is reading width, and a third of it is not a
   field anybody can type into. Under 44rem there is one column and the question does not
   arise. */
/* `minmax(0, 1fr)` and not `1fr`, and that is not a flourish: a `1fr` track is allowed to be
   as narrow as its content is *willing* to be, and a form control is not willing. A file
   chooser draws its own button and the sentence beside it („Keine Datei ausgewählt"), a
   `textarea` reserves its `cols`, and none of them shrinks below that. The track then grows
   past the box, the fields hang out over the right edge of a telephone, and the page scrolls
   sideways – which also drags the fixed bottom bar out of view, because that bar is anchored
   to the layout viewport and not to the screen. The zero says the track may be as narrow as
   the box, and `min-width: 0` says the same to the controls inside it. */
.form__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--space-m);
}

.form__body > * { grid-column: 1 / -1; min-width: 0; }

.form__field input,
.form__field select,
.form__field textarea {
  min-width: 0;
  max-width: 100%;
}

@container (width >= 41rem) {
  .form__body { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .form__field--half { grid-column: auto; }
}

.form__field label { font-weight: 600; }
.form__optional { font-weight: 400; color: var(--ink-soft); }

.form__field input,
.form__field select,
.form__field textarea {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-xs) var(--space-s);
  background: var(--paper);
  color: inherit;
  font: inherit;
}

.form__field input:focus-visible,
.form__field select:focus-visible,
.form__field textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* The file picker. The browser draws the button itself, and left alone it draws it in the
   system font on a grey nobody chose – the one control on the page that came from somewhere
   else. `::file-selector-button` is the part of it a stylesheet may reach, so it becomes the
   quiet button the page already has, and the sentence beside it („Keine ausgewählt") is the
   browser's own and stays. */
.form__field input[type="file"] {
  padding: var(--space-xs);
  cursor: pointer;
}

.form__field input[type="file"]::file-selector-button {
  margin-inline-end: var(--space-s);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-2xs) var(--space-s);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-display);
  font-size: var(--text-s);
  font-weight: 600;
  cursor: pointer;
}

/* Side by side there is no room on a telephone: the button and the sentence the browser puts
   beside it („Keine Datei ausgewählt") do not both fit, and the browser answers by cutting its
   own text to „Kei…ählt". A block button puts that sentence on the line below, where it has
   the whole width and stays a sentence. */
@media (width < 44rem) {
  .form__field input[type="file"]::file-selector-button {
    display: block;
    width: 100%;
    margin-inline-end: 0;
    margin-block-end: var(--space-2xs);
  }
}

.form__field input[type="file"]::file-selector-button:hover {
  border-color: var(--ink-soft);
  background: var(--paper-sunk);
}

/* What the field cannot say itself: which formats, how large. Under the input rather than
   in the label, because it is read once and the label is read every time. */
.form__hint {
  color: var(--ink-soft);
  font-size: var(--text-s);
}

/* Off screen rather than display:none: a bot reads the markup, not the stylesheet,
   and a field that is not rendered at all is one some of them skip. */
.form__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__privacy,
.form__notice {
  max-width: var(--measure);
  font-size: var(--text-s);
  color: var(--ink-soft);
}

.form__notice {
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: var(--space-s) var(--space-m);
  background: var(--paper-sunk);
  color: var(--ink);
  font-size: var(--text-m);
}

/* An error is not a matter of taste, which is why this red is a token of its own rather
   than the accent: the accent is what a project changes, and „das ging nicht raus" must
   not turn green because somebody liked green. */
.form__notice--error { border-left-color: var(--alert); }

/* Bottom bar (telephone) --------------------------------------------------- */

/* The row of places at the foot of the screen, the way an app does it. Only on a telephone:
   on a desktop the menu is where the eye already looks, and a bar pinned to the bottom of a
   1400 pixel window is a bar in the middle of nowhere. */
.bottom-nav { display: none; }

@media (width < 44rem) {
  .bottom-nav {
    display: block;
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 20;
    border-top: 1px solid var(--line);
    background: var(--paper);
    /* The strip a modern telephone keeps for its own gesture bar. Zero everywhere else. */
    padding-bottom: env(safe-area-inset-bottom, 0);
  }

  .bottom-nav__list {
    display: flex;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .bottom-nav__list li { flex: 1; }

  .bottom-nav__link {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2xs);
    /* The bar above the mark is what marks the current place, and it is transparent rather
       than absent – otherwise every entry moves by two pixels when it becomes the current
       one. */
    border-top: 2px solid transparent;
    padding-block: var(--space-xs);
    color: var(--ink-soft);
    font-size: var(--text-s);
    line-height: 1;
    text-align: center;
    text-decoration: none;
  }

  .bottom-nav__link[aria-current] {
    border-top-color: var(--accent);
    color: var(--accent);
    font-weight: 600;
  }

  .bottom-nav .icon {
    width: calc(var(--baseline) * 0.9);
    height: calc(var(--baseline) * 0.9);
  }

  /* Two menus saying the same thing, one of them under the thumb. `:has()` asks whether the
     bar is actually there – it is a block and a site may not have placed it, and then the
     menu in the header is the only one there is. */
  body:has(.bottom-nav) .site-nav { display: none; }

  /* Everything else has to end above the bar: the last line of the footer, an anchor
     jumped to, and the cookie banner, which would otherwise sit on top of it. */
  body { padding-bottom: var(--space-xl); }
  html { scroll-padding-bottom: var(--space-xl); }
  .consent { bottom: var(--space-xl); }
}

/* Back to the top --------------------------------------------------------- */

/* Only after the first screenful: a control that offers to undo something nobody has done
   yet is a control in the way. It is built by `totop.js` and stands in no template – without
   the script there is no button, which is better than a button that does nothing.

   `href="#top"` is the browser's own. The fragment „top" means the top of the document even
   though no element carries that id, so the scrolling is not ours either – smooth where the
   page says so, and instantly where the visitor has asked for less movement. */
.to-top {
  position: fixed;
  z-index: 10;
  inset-block-end: var(--space-m);
  inset-inline-end: var(--space-m);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--space-l);
  height: var(--space-l);
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  font-size: var(--text-l);
  line-height: 1;
  text-decoration: none;
}

.to-top:hover {
  border-color: var(--ink-soft);
  background: var(--paper-sunk);
  color: var(--ink);
}

/* Said out loud because the rule above gives it a `display`, and `hidden` is a weaker word
   than a class. Hidden it is also out of the tab order, which is the point. */
.to-top[hidden] { display: none; }

/* The first screenful, as something an observer can watch leave. No width and out of the
   flow: it is a measurement and not an element on the page. */
.to-top__mark {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 0;
  height: 100vh;
  pointer-events: none;
}

/* Where a telephone carries a bar of its own at the foot of the screen, the button stands
   above it rather than on it. */
@media (width < 44rem) {
  body:has(.bottom-nav) .to-top { inset-block-end: var(--space-xl); }
}

/* Cookie banner ----------------------------------------------------------- */

.consent {
  position: fixed;
  bottom: var(--space-m);
  z-index: 10;
  max-width: var(--measure);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-m);
  background: var(--paper);
  box-shadow: 0 4px 24px var(--shadow);
}

/* Where the banner sits is a field on the block, because it depends on the design of the
   site: a right hand corner is wrong when something else already lives there. */
.consent--right { right: var(--space-m); }
.consent--left { left: var(--space-m); }

.consent--center {
  left: 50%;
  translate: -50%;
}

.consent--full {
  left: var(--space-m);
  right: var(--space-m);
  max-width: none;
}

/* Below the measure there is no corner to sit in: the banner spans the width whatever
   position was chosen, because anything else would be a column the width of a thumb. */
@media (width < 34rem) {
  .consent {
    left: var(--space-xs);
    right: var(--space-xs);
    bottom: var(--space-xs);
    max-width: none;
    translate: none;
  }
}

.consent__text {
  margin-bottom: var(--space-s);
  font-size: var(--text-s);
}

.consent__text > :first-child { margin-top: 0; }
.consent__text > :last-child { margin-bottom: 0; }

.consent__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin: 0;
}

/* The way back to the cookie decision. Underlined, unlike the footer navigation next to
   it: it stands alone in a block of prose, and something clickable has to look clickable. */
.site-footer__consent {
  margin-top: var(--space-m);
}

.site-footer__consent a {
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

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