/* css/tokens.css */
/*
 * tokens.css – the design system as CSS variables (docs/05-design/tokens.md).
 *
 * Two levels:
 *   1. Base values: the palette, without meaning (--bordeaux-600).
 *   2. Semantic values: what a value stands for (--color-accent).
 * Components only ever use semantic values. Dark mode swaps semantic values only.
 */

/* Order of the cascade layers: later layers win, no specificity tricks. */
@layer tokens, base, layout, components, utilities;

@layer tokens {
  :root {
    /* ---------- Base values: palette "Samt" (decided 28.09.2026) ---------- */
    --canvas-50: #FBF8F2;
    --canvas-100: #F6F1E7;
    --canvas-200: #E4DCCD;
    --ink-500: #6B625A;
    --ink-900: #1C1917;
    --night-900: #141110;
    --night-800: #1E1A18;
    --night-700: #322B27;
    --night-300: #A89E92;
    --night-100: #EDE6D8;
    --bordeaux-600: #7A1F2B;
    --bordeaux-300: #D46A74;
    --daisy-500: #E3B23C;
    --daisy-400: #E8BD52;

    /* Status colours: muted green, muted ochre, and a red that is
       lighter and warmer than bordeaux so errors never look like the accent. */
    --green-600: #3F6B4A;
    --green-300: #8DBF98;
    --ochre-600: #8A6414;
    --ochre-300: #D9B25E;
    --red-600: #B8412C;
    --red-300: #F08A74;

    /* Age rating labels in the familiar FSK colours (decided 29.09.2026).
       A marking, not a "moment": this yellow is FSK's, not the daisy's. */
    --fsk-0-bg: #FFFFFF;
    --fsk-0-text: #1C1917;
    --fsk-6-bg: #FFE500;
    --fsk-6-text: #1C1917;
    --fsk-12-bg: #00A650;
    --fsk-12-text: #FFFFFF;
    --fsk-16-bg: #0075BE;
    --fsk-16-text: #FFFFFF;
    --fsk-18-bg: #E2001A;
    --fsk-18-text: #FFFFFF;

    /* ---------- Semantic values: light (default) ---------- */
    --color-bg: var(--canvas-100);
    --color-surface: var(--canvas-50);
    --color-text: var(--ink-900);
    --color-muted: var(--ink-500);
    --color-accent: var(--bordeaux-600);
    --color-on-accent: var(--canvas-50);
    --color-line: var(--canvas-200);
    /* Daisy yellow: only for the loving moments. Never text on a light background. */
    --color-moment: var(--daisy-500);
    --color-success: var(--green-600);
    --color-warning: var(--ochre-600);
    --color-error: var(--red-600);
    --color-focus: var(--bordeaux-600);
    /* On images and videos: the same in light and dark, since pictures do not change. */
    --color-on-image: var(--canvas-50);
    /* Quieter marks on pictures (the ring that chooses a work, card 2.13b):
       the warm tone of the lines, a little see-through. */
    --color-on-image-soft: color-mix(in srgb, var(--canvas-200) 80%, transparent);
    --color-scrim: rgb(20 17 16 / 0.6);
    --color-video-bg: var(--night-900);

    /* ---------- Spacing: 4 px grid, in rem so it grows with the font size ---------- */
    --space-1: 0.25rem;  /*  4 px */
    --space-2: 0.5rem;   /*  8 px */
    --space-3: 0.75rem;  /* 12 px */
    --space-4: 1rem;     /* 16 px */
    --space-5: 1.5rem;   /* 24 px */
    --space-6: 2rem;     /* 32 px */
    --space-7: 3rem;     /* 48 px */
    --space-8: 4rem;     /* 64 px */
    --space-9: 6rem;     /* 96 px */

    /* ---------- Spacing roles: what a distance means (card 2.5) ----------
       Components use these, not the numbered steps. Between two roles lies
       a visible jump, so the eye sees what belongs together. Values here are
       for desktops ("airy"); phones get condensed values below, and lists
       marked data-density="compact" keep fixed, dense ones. */
    --space-tight: var(--space-1);      /*   4: symbol and word, field and its hint */
    --space-related: var(--space-2);    /*   8: lines of one group: title and original title, label and field */
    --space-group: var(--space-5);      /*  24: groups within a component: title block and facts */
    --space-siblings: var(--space-6);   /*  32: equal items one below another: fields, comments */
    --space-component: 2.5rem;          /*  40: different blocks: facts and my entry, status and note */
    --space-section: var(--space-8);    /*  64: sections of a page: recommend, credits */
    --space-frame: 8rem;                /* 128: content and the platform's frame: visibility, sources */
    --space-page-start: var(--space-7); /*  48: from the header to the page title */
    --space-page-end: var(--space-9);   /*  96: the calm end of a page */
    --gutter: var(--space-7);           /* page margin: 48 on desktops, 24 on tablets, 16 on phones */

    /* ---------- Widths: three named measures ---------- */
    --width-read: var(--measure); /* sentences, conversations, essays */
    --width-form: 32rem;  /* forms and settings */
    --width-page: 72rem;  /* the whole page; dense views use all of it */
    /* The left column of every page with two (the poster on the work page,
       the lists, the friends), always the --space-section away from the
       main area: one ratio on every page (decided on 13.10.2026 by the
       initiator, after the work page). It gives way down to 12rem. */
    --width-side: minmax(12rem, 20rem);
    --width-short: 40ch;  /* short texts typed in place: a note, a name */

    /* ---------- Targets and controls ---------- */
    --target-min: 2.75rem;            /* 44 px: anything tappable is at least this large */
    --control-height: 2.75rem;        /* buttons and fields in forms and dialogs */
    --control-height-compact: 2rem;   /* buttons and chips in cards and lists (target stays 44 px) */
    --control-height-small: 1.75rem;  /* small chips: tags (card 4.3b; target stays 44 px) */
    --icon-small: 0.875rem;           /* the × on a small chip */

    /* ---------- Radii ---------- */
    --radius-s: 4px;
    --radius-m: 8px;
    --radius-l: 16px;
    --radius-full: 9999px;
    --radius-poster: 2px; /* posters and covers: objects, not app tiles */
    /* Shape says what it is: buttons act, choices choose (card 2.5e). */
    --radius-button: var(--radius-full); /* buttons: capsules */
    --radius-choice: var(--radius-m);    /* chips and segments: soft rectangles */

    /* ---------- Shadows: warm, a touch of brown instead of black ---------- */
    --shadow-rest: 0 1px 2px rgb(60 36 20 / 0.08);
    --shadow-raised: 0 2px 4px rgb(60 36 20 / 0.08), 0 6px 16px rgb(60 36 20 / 0.08);
    --shadow-floating: 0 8px 24px rgb(60 36 20 / 0.12), 0 24px 48px rgb(60 36 20 / 0.12);

    /* ---------- Fonts (docs/05-design/typografie.md) ---------- */
    --font-read: "Newsreader", Georgia, "Noto Serif", "DejaVu Serif", "Times New Roman", serif;
    --font-ui: "Instrument Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", "Helvetica Neue", Arial, sans-serif;

    /* Type scale, ratio about 1.25, grows gently from phone to desktop.
       Reading text never below 17 px (decided 28.09.2026). */
    --text-xs: 0.8125rem;                                  /* 13 px */
    --text-s: 0.9375rem;                                   /* 15 px */
    --text-m: clamp(1.0625rem, 1.03rem + 0.16vw, 1.125rem); /* 17 → 18 px */
    --text-l: clamp(1.3125rem, 1.25rem + 0.31vw, 1.4375rem); /* 21 → 23 px */
    --text-xl: clamp(1.625rem, 1.53rem + 0.47vw, 1.8125rem); /* 26 → 29 px */
    --text-2xl: clamp(2.0625rem, 1.69rem + 1.88vw, 2.8125rem); /* 33 → 45 px */
    --text-3xl: clamp(2.25rem, 1.5rem + 3.2vw, 3.5rem); /* 36 → 56 px */

    /* Type roles (docs/05-design/typografie.md, section 4a): which size means what.
       At most three of them in one component. */
    --text-hero: var(--text-3xl);         /* the one big sentence of the start page (card 3.1) */
    --text-work-title: var(--text-2xl);   /* the title on the work page */
    --text-page-title: var(--text-xl);    /* the title of any other page */
    --text-sentence: var(--text-xl);      /* the one sentence of a recommendation */
    --text-heading: var(--text-l);        /* sections of a page, dialog titles */
    --text-card-title: var(--text-l);     /* a work's title in lists */
    --text-body: clamp(1.125rem, 1.05rem + 0.3vw, 1.25rem); /* body text: 18 → 20 px (12.10.2026) */
    --text-read: var(--text-m);           /* reading text, notes, comments; never smaller */
    --text-control: var(--text-m);        /* buttons and fields in forms */
    --text-meta: var(--text-s);           /* facts, labels, hints, compact buttons and chips */
    --text-fine: var(--text-xs);          /* sources and fine print, sparingly */

    /* Weights: three, as tokens. */
    --weight-regular: 400;
    --weight-medium: 500;   /* headings in the reading face */
    --weight-semibold: 600; /* labels, buttons, the name of a person */

    --leading-read: 1.6;
    --leading-ui: 1.35;
    --measure: 66ch; /* line length for short reading text in the UI */

    /* Body text (Fließtext: biographies, descriptions, essays; typografie.md
       5, decided on 12.10.2026): the measure in em of the text itself, so it
       is Newsreader's own width at its own size. Newsreader runs narrow:
       28em are about 66 characters, 66ch would be 87. */
    --leading-body: 1.55;
    --measure-body: 28em;

    /* ---------- Motion (docs/05-design/bewegung-klang-haptik.md) ---------- */
    --motion-instant: 100ms;
    --motion-quick: 200ms;
    --motion-gentle: 350ms;
    --motion-slow: 600ms;
    /* Curves: how a movement speeds up and slows down. */
    --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
    --ease-enter: cubic-bezier(0, 0, 0.2, 1);
    --ease-exit: cubic-bezier(0.4, 0, 1, 1);
    --ease-curtain: cubic-bezier(0.7, 0, 0.2, 1);
    /* Springs (card 3.1, decided 08.10.2026): a damped spring, sampled. Lively
       goes about 25 % beyond its target and swings back (petals); soft about
       13 % (turning, words). Browsers without linear() simply arrive. */
    --ease-spring: linear(0, 0.116, 0.384, 0.695, 0.965, 1.152, 1.241, 1.248, 1.199, 1.123, 1.047, 0.986, 0.949, 0.936, 0.941, 0.957, 0.977, 0.995, 1.008, 1.015, 1.016, 1.013, 1.009, 1.004, 1, 0.997, 0.996, 0.996, 0.997, 0.998, 1, 1, 1.001, 1.001, 1.001, 1.001, 1);
    --ease-spring-soft: linear(0, 0.062, 0.212, 0.402, 0.596, 0.771, 0.913, 1.017, 1.083, 1.117, 1.126, 1.117, 1.098, 1.074, 1.049, 1.027, 1.01, 0.997, 0.989, 0.985, 0.984, 0.985, 0.988, 0.991, 0.994, 0.997, 0.999, 1, 1.001, 1.002, 1.002, 1.002, 1.002, 1.001, 1.001, 1, 1);

    /* "only": daizee has finished colours for light and dark; browsers and
       systems must not darken it on their own (Samsung did, and the petals
       of the app's start turned brown; initiator, 08.10.2026). */
    color-scheme: light dark only;
  }

  /* ---------- Roles on smaller screens: the same order, condensed ---------- */
  @media (max-width: 47.99rem) {
    :root {
      --gutter: var(--space-5);
      --space-group: var(--space-4);      /* 16 */
      --space-siblings: 1.25rem;          /* 20 */
      --space-component: 1.75rem;         /* 28 */
      --space-section: var(--space-7);    /* 48 */
      --space-frame: 5rem;                /* 80 */
      --space-page-start: var(--space-5); /* 24 */
      --space-page-end: var(--space-8);   /* 64 */
      --leading-body: 1.5;                /* shorter lines, a little less air */
    }
  }

  /* ---------- Dense lists: result lists, the collection, menus ----------
     Density is a decision: here the roles keep small, fixed values, so a
     long list stays easy to scan on every screen. */
  [data-density="compact"] {
    --space-group: var(--space-4);      /* 16 */
    --space-siblings: var(--space-5);   /* 24 */
    --space-component: var(--space-6);  /* 32 */
    --space-section: var(--space-7);    /* 48 */
  }

  @media (max-width: 29.99rem) {
    :root {
      --gutter: var(--space-4);
    }
  }

  /* ---------- Semantic values: dark ---------- */
  /* Follows the system setting; data-theme lets a later user setting override it. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --color-bg: var(--night-900);
      --color-surface: var(--night-800);
      --color-text: var(--night-100);
      --color-muted: var(--night-300);
      --color-accent: var(--bordeaux-300);
      --color-on-accent: var(--night-900);
      --color-line: var(--night-700);
      --color-moment: var(--daisy-400);
      --color-success: var(--green-300);
      --color-warning: var(--ochre-300);
      --color-error: var(--red-300);
      --color-focus: var(--bordeaux-300);
      /* In the dark a slightly lighter surface replaces the shadow. */
      --shadow-rest: none;
      --shadow-raised: none;
      --shadow-floating: none;
    }
  }

  :root[data-theme="dark"] {
    --color-bg: var(--night-900);
    --color-surface: var(--night-800);
    --color-text: var(--night-100);
    --color-muted: var(--night-300);
    --color-accent: var(--bordeaux-300);
    --color-on-accent: var(--night-900);
    --color-line: var(--night-700);
    --color-moment: var(--daisy-400);
    --color-success: var(--green-300);
    --color-warning: var(--ochre-300);
    --color-error: var(--red-300);
    --color-focus: var(--bordeaux-300);
    --shadow-rest: none;
    --shadow-raised: none;
    --shadow-floating: none;
    color-scheme: dark only;
  }

  :root[data-theme="light"] {
    color-scheme: light only;
  }

  /* Reduced motion: movements become (almost) instant. */
  @media (prefers-reduced-motion: reduce) {
    :root {
      --motion-instant: 0ms;
      --motion-quick: 0ms;
      --motion-gentle: 0ms;
      --motion-slow: 0ms;
    }
  }
}

/* css/fonts.css */
/*
 * fonts.css – self-hosted fonts (docs/05-design/typografie.md).
 * Newsreader for reading, Instrument Sans for the interface.
 * Variable fonts: one file covers all weights. The browser only downloads a
 * file when the page uses characters from its unicode-range. Characters
 * outside these ranges (e.g. Cyrillic) come from the fallback fonts in tokens.css.
 * Licence: SIL Open Font License, see fonts/*-OFL.txt.
 */

@layer base {
  /* Instrument Sans, normal, latin-ext */
  @font-face {
    font-family: "Instrument Sans";
    font-style: normal;
    font-weight: 400 700;
    font-stretch: 75% 100%;
    font-display: swap;
    src: url("/static/fonts/instrument-sans-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }

  /* Instrument Sans, normal, latin */
  @font-face {
    font-family: "Instrument Sans";
    font-style: normal;
    font-weight: 400 700;
    font-stretch: 75% 100%;
    font-display: swap;
    src: url("/static/fonts/instrument-sans-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

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

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

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

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

}

/* css/base.css */
/*
 * base.css – the typeface of the whole page: body text, headings, links, focus.
 * Interface text uses Instrument Sans; anything meant to be read uses Newsreader
 * (class .prose or the reading elements below).
 */

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

  html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
  }

  body {
    margin: 0;
    background: var(--color-bg);
    color: var(--color-text);
    font-family: var(--font-ui);
    font-size: var(--text-m);
    line-height: var(--leading-ui);
    /* Long words, links and mail addresses break instead of running off the screen. */
    overflow-wrap: break-word;
    -webkit-font-smoothing: antialiased;
    transition: background-color var(--motion-gentle) var(--ease-standard);
  }

  h1,
  h2,
  h3 {
    font-family: var(--font-read);
    font-weight: 500;
    line-height: 1.15;
    margin: 0 0 var(--space-4);
    text-wrap: balance;
  }

  /* Type roles (typografie.md, 4a): page titles one step calmer, so the
     work title on the work page is the one big type of daizee. */
  h1 { font-size: var(--text-page-title); }
  h2 { font-size: var(--text-heading); }
  h3 { font-size: var(--text-heading); }

  p {
    margin: 0 0 var(--space-4);
  }

  /* Reading text: serif, generous leading, pleasant line length,
     old-style figures, hyphenation in the text's own language. */
  .prose {
    font-family: var(--font-read);
    font-size: var(--text-m);
    line-height: var(--leading-read);
    max-width: var(--measure);
    font-variant-numeric: oldstyle-nums;
    hyphens: auto;
    text-wrap: pretty;
  }

  /* Body text (Fließtext): longer texts to read, set as in a good
     magazine (typografie.md 5, decided on 12.10.2026): 18 → 20 px, about
     66 characters a line, hyphenation only in longer words (at least 8
     letters, 4 before and after the break), so names stay whole. */
  .body-text {
    font-family: var(--font-read);
    font-size: var(--text-body);
    line-height: var(--leading-body);
    max-inline-size: var(--measure-body);
    font-variant-numeric: oldstyle-nums;
    hyphens: auto;
    hyphenate-limit-chars: 8 4 4;
    text-wrap: pretty;
  }

  .body-text p {
    margin: 0 0 var(--space-group);
  }

  .body-text p:last-child {
    margin-block-end: 0;
  }

  a {
    color: var(--color-accent);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.2em;
    transition: color var(--motion-quick) var(--ease-standard);
  }

  a:hover {
    text-decoration-thickness: 2px;
  }

  button {
    font: inherit;
    color: inherit;
  }

  /* Clearly visible focus ring in the accent colour, for keyboard users.
     It follows the element's own corners and never changes its shape. */
  :focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 3px;
  }

  a:focus-visible {
    border-radius: var(--radius-s);
  }

  ::selection {
    background: var(--color-accent);
    color: var(--color-on-accent);
  }

  /* Page transitions: a short cross-fade between pages (bewegung-klang-haptik.md). */
  @view-transition {
    navigation: auto;
  }

  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: var(--motion-gentle);
    animation-timing-function: var(--ease-standard);
  }

  /* Arrived at a place inside a page (landing.js): the work or the newest
     answer is tinted like an unread line of news and fades back. The tint
     reaches a little beyond the edges, so it frames without moving
     anything. */
  [data-landed] {
    border-radius: var(--radius-m);
    animation: landed calc(4 * var(--motion-slow)) var(--ease-standard) forwards;
  }

  @keyframes landed {
    0%,
    40% {
      background: color-mix(in srgb, var(--color-accent) 7%, var(--color-surface));
      box-shadow: 0 0 0 var(--space-related) color-mix(in srgb, var(--color-accent) 7%, var(--color-surface));
    }

    100% {
      background: transparent;
      box-shadow: 0 0 0 var(--space-related) transparent;
    }
  }

  /* The header stands above whatever one jumps to. */
  [id^="item-"],
  [id^="talk-"],
  #comments {
    scroll-margin-block-start: calc(var(--target-min) + var(--space-group));
  }
}

/* css/layout.css */
/*
 * layout.css – the page grid: header, main content, footer.
 * The content column stays narrow and calm; the works will stand in the light.
 */

@layer layout {
  /* Margins, widths and the distances at the top and the bottom come from
     the spacing roles in tokens.css (card 2.5a): one place for all pages. */
  .page {
    min-height: 100dvh;
    display: grid;
    /* One fixed column: the page is never wider than the screen. Without it,
       anything with a minimum width (a search field, a long address) would
       widen the whole page on a phone (card 2.5a). */
    grid-template-columns: minmax(0, 1fr);
    /* A safety net: whatever might stick out sideways is cut off, so the
       page never moves sideways on a phone (initiator, 08.10.2026, on
       Discover). "clip" makes no scrolling box of the page, so the sticky
       header keeps working. */
    overflow-x: clip;
    grid-template-rows: auto 1fr auto;
  }

  .page-header,
  .page-main {
    width: 100%;
    max-width: var(--width-page);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  .page-header {
    padding-block: var(--space-4);
  }

  .page-main {
    padding-block: var(--space-page-start) var(--space-page-end);
  }

  /* The footer uses outer margins instead of inner padding, so its top line
     is exactly as wide as the text column above it. */
  .page-footer {
    margin-inline: max(var(--gutter), (100% - var(--width-page)) / 2 + var(--gutter));
    padding-block: var(--space-6);
    border-top: 1px solid var(--color-line);
    color: var(--color-muted);
    font-size: var(--text-s);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    justify-content: space-between;
    align-items: center;
  }
}

/* css/components/account.css */
/* account.css – the steps under Settings → Konto (card 2.19): changing the
   address and deleting the account, in the dialog (or, without JavaScript,
   on a page). Like the recommend form in its dialog: label, field and its
   error close together, the buttons clearly apart. */

@layer components {
  .dialog .account-form,
  .account-step .account-form {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-related);
    margin-block: var(--space-group) 0;
  }

  /* An error sits right under its field. */
  .account-form > input + .form-error {
    margin-block-start: calc(var(--space-tight) - var(--space-related));
  }

  .account-form > .dialog-actions {
    margin-block-start: calc(var(--space-component) - var(--space-related));
  }

  /* A step without a field (asking, confirming): the buttons follow the
     text at the distance of a new part. */
  .dialog .account-form:not(:has(label)),
  .account-step .account-form:not(:has(label)) {
    margin-block-start: var(--space-component);
  }

  .account-form:not(:has(label)) > .dialog-actions {
    margin-block-start: 0;
  }

  /* Several sentences in one dialog (deleting): one thought each. */
  .dialog-text + .dialog-text {
    margin-block-start: var(--space-group);
  }

  /* "Keine Mail da? Neuen Code schicken": one quiet line below. */
  .account-again {
    margin-block-start: var(--space-group);
  }

  .account-again .note-text {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-tight);
    margin: 0;
  }

  /* Without JavaScript the step is a page of its own, as wide as a form. */
  .account-step {
    max-width: var(--width-form);
  }

  .account-step .dialog-title {
    font-size: var(--text-page-title);
  }
}

/* css/components/add.css */
/* add.css – the one field, the filter, the result list (its cards: work-card.css). */

@layer components {
  /* A dense view: it uses the whole page width (card 2.5a). */
  .add-page {
    min-width: 0;
  }

  /* ---------- The field, its hint and the chips (card 2.5c) ---------- */
  .add-form {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-group);
  }

  .add-form noscript .btn {
    justify-self: start;
  }

  /* The hint helps only while the field is empty. */
  .search-hint {
    margin-block-start: calc(var(--space-tight) - var(--space-group));
  }

  .add-form:has(#q:not(:placeholder-shown)) .search-hint {
    display: none;
  }

  .medium-filter {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    border: 0;
    margin: 0;
    padding: 0;
  }

  /* On a phone the chips stay in one row that can be swiped; the soft edge
     shows that it goes on. */
  @media (max-width: 47.99rem) {
    .chip-row {
      flex-wrap: nowrap;
      overflow-x: auto;
      scrollbar-width: none;
      margin-inline: calc(-1 * var(--gutter));
      padding-inline: var(--gutter);
      mask-image: linear-gradient(to right, transparent 0, #000 var(--gutter), #000 calc(100% - var(--gutter)), transparent 100%);
    }

    .chip-row .choice {
      flex: none;
    }
  }

  /* ---------- Results: head, list, foot ---------- */
  /* A deep breath between the filters and the results (as in the
     collection, initiator 04.10.2026). */
  #results {
    margin-block-start: var(--space-section);
  }

  /* Without results (browsing Discover) the place keeps no room of its own
     (initiator, 08.10.2026: too much space below the search field). */
  #results:not(:has(> *)) {
    margin-block-start: 0;
  }

  .results-head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-related) var(--space-group);
  }

  .results-count {
    margin: 0;
    font-size: var(--text-meta);
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
  }

  .results-more {
    margin: var(--space-related) 0 0;
  }

  .result-list {
    list-style: none;
    margin: var(--space-group) 0 0;
    padding: 0;
    border-top: 1px solid var(--color-line);
  }

  /* Sorting: a quiet menu, "Sortieren: Relevanz", the system's own picker. */
  .sort-menu {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--target-min);
    font-size: var(--text-meta);
    font-weight: var(--weight-regular);
    color: var(--color-muted);
  }

  .sort-menu select {
    appearance: none;
    field-sizing: content; /* as wide as the chosen option, the arrow right beside it */
    font: inherit;
    font-weight: var(--weight-semibold);
    color: var(--color-accent);
    background: transparent;
    border: 0;
    border-radius: var(--radius-s);
    padding: var(--space-1) 1.25rem var(--space-1) var(--space-1);
    cursor: pointer;
  }

  .sort-menu::after {
    content: "";
    position: absolute;
    inset-inline-end: 0.35rem;
    top: 50%;
    width: 0.4rem;
    height: 0.4rem;
    border: solid var(--color-accent);
    border-width: 0 1.5px 1.5px 0;
    translate: 0 -70%;
    rotate: 45deg;
    pointer-events: none;
  }

  .sort-menu select:hover {
    background: color-mix(in srgb, var(--color-accent) 8%, transparent);
  }

  .results-foot {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-group);
    margin-block-start: var(--space-component);
  }

  .pagination {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .pagination-where {
    padding-inline: var(--space-2);
    font-variant-numeric: tabular-nums;
  }

  .pagination-where:first-child {
    padding-inline-start: 0;
  }

  /* Order and works per page, on the right of the results head. */
  .results-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related) var(--space-group);
  }

  .per-page {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .per-page .seg {
    margin: 0;
  }

  /* On a phone: the number and the order in one line (the word
     "Sortieren" goes, the chosen order with its arrow says enough), the
     works per page below. */
  @media (max-width: 47.99rem) {
    .results-head {
      justify-content: space-between;
    }

    .results-tools {
      display: contents;
    }

    .sort-menu > span {
      position: absolute;
      width: 1px;
      height: 1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    .sort-menu {
      max-width: 100%;
    }

    .sort-menu select {
      max-width: 100%;
      text-overflow: ellipsis;
    }

    .per-page {
      flex-basis: 100%;
    }
  }

  @media (max-width: 36rem) {
    .results-foot {
      justify-content: center;
    }
  }

  .free-entry {
    margin-block-start: var(--space-6);
  }

  .free-entry h2 {
    font-size: var(--text-l);
    margin-block-start: var(--space-6);
  }

  select {
    font: inherit;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    padding: var(--space-2) var(--space-3);
    justify-self: start;
  }

  input[type="url"] {
    font: inherit;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    padding: var(--space-3) var(--space-4);
    width: 100%;
  }
}


/* ---------- Discover on a phone: the search field travels along (card 3.2,
   initiator's wish 08.10.2026). It sticks below the header while one scrolls
   up, and glides away with it on the way down (header.js marks the header
   "is-away"). The two boxes around it dissolve on a phone (display:
   contents), so the field belongs to the whole page and may travel along
   all the way down. While searching it stays where it is. ---------- */
@layer components {
  @media (max-width: 47.99rem) {
    .discover .discover-top,
    .discover .with-lists-main {
      display: contents;
    }

    /* "Listen" at the right of the title (lists.css) needs a box to sit in. */
    .discover {
      position: relative;
    }

    .discover:not(:has(#results > *)) .add-form {
      --header-height: calc(var(--target-min) + 2 * var(--space-3)); /* site-nav.css */
      position: sticky;
      top: var(--header-height);
      z-index: 30; /* below the header (40) */
      container-type: scroll-state;
      margin-inline: calc(-1 * var(--gutter));
      padding: var(--space-related) var(--gutter);
      background: var(--color-bg);
      transition: top var(--motion-gentle) var(--ease-enter);
    }

    .page:has(> .page-header.is-away) .discover:not(:has(#results > *)) .add-form {
      top: -6rem; /* more than its height: gone with the header */
      transition-timing-function: var(--ease-exit);
    }

    /* Travelling along, the field alone: its hint stays at the top. */
    @container scroll-state(stuck: top) {
      .search-hint {
        display: none;
      }
    }
  }
}

/* css/components/app-start.css */
/* app-start.css – the start of the installed app (card 3.1, freigegeben in
   docs/05-design/vorschau/daizee-marke.html, section 5, on 08.10.2026).
   After Android's own splash (the light icon on the same dark ground)
   the stamp pops up, the petals spring out of it one by one while the
   flower turns into place; it bounces once while the last petals still
   grow; then "daizee" appears letter by letter, and everything fades into
   the page. About 1.9 seconds; a tap skips it (app-start.js). The flower
   stands in the middle of the screen, where Android's splash showed the
   icon. The colours are those of the dark mode, whatever the theme: the
   splash is dark, so are Android's bars around the app (light for one day,
   09.10.2026; back to dark on 10.10.2026, as Chrome took the light ground
   for the navigation bar too). Then the page comes in the theme one chose. */

@layer components {
  .app-start {
    --app-start-flower: min(50vw, 13rem);
    /* Android's own splash fades out for a moment after the page appears;
       the daisy waits on the same ground until it is gone, so no petal grows
       beneath it (initiator, 08.10.2026: the petals looked see-through). */
    --app-start-wait: 300ms;
    position: fixed;
    inset: 0;
    z-index: 1000;
    color-scheme: dark only; /* never darkened by the browser */
    display: grid;
    place-items: center;
    background: var(--night-900);
    color: var(--night-100);
    cursor: pointer;
    animation: app-start-out 300ms ease-out calc(var(--app-start-wait) + 1600ms) both;
  }

  .app-start-turn,
  .app-start-pulse {
    width: var(--app-start-flower);
    height: var(--app-start-flower);
  }

  .app-start-bloom {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
  }

  .app-start-petal,
  .app-start-center {
    transform-box: view-box;
    transform-origin: 24px 24px;
  }

  .app-start-petal {
    fill: var(--daisy-400);
  }

  .app-start-center {
    fill: var(--bordeaux-300);
  }

  .app-start-word {
    position: absolute;
    left: 50%;
    top: calc(50% + var(--app-start-flower) / 2 + 1.75rem);
    width: min(57vw, 15rem);
    translate: -50% 0;
  }

  .app-start-word .logo {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
  }

  .app-start-word .logo-letters {
    fill: currentColor;
  }

  /* The stamp pops up, the petals spring out of it one by one. */
  .app-start-center {
    animation: app-start-pop 400ms var(--ease-spring) var(--app-start-wait) both;
  }

  .app-start-petal {
    animation: app-start-petal 600ms var(--ease-spring) both;
  }

  .app-start-petals > :nth-child(1) .app-start-petal { animation-delay: calc(var(--app-start-wait) + 80ms); }
  .app-start-petals > :nth-child(2) .app-start-petal { animation-delay: calc(var(--app-start-wait) + 140ms); }
  .app-start-petals > :nth-child(3) .app-start-petal { animation-delay: calc(var(--app-start-wait) + 200ms); }
  .app-start-petals > :nth-child(4) .app-start-petal { animation-delay: calc(var(--app-start-wait) + 260ms); }
  .app-start-petals > :nth-child(5) .app-start-petal { animation-delay: calc(var(--app-start-wait) + 320ms); }
  .app-start-petals > :nth-child(6) .app-start-petal { animation-delay: calc(var(--app-start-wait) + 380ms); }
  .app-start-petals > :nth-child(7) .app-start-petal { animation-delay: calc(var(--app-start-wait) + 440ms); }
  .app-start-petals > :nth-child(8) .app-start-petal { animation-delay: calc(var(--app-start-wait) + 500ms); }

  /* Meanwhile the flower turns into place, and bounces once while the
     last petals still grow (petals 5 to 8 start at 320 to 500 ms). */
  .app-start-turn {
    animation: app-start-turn 1100ms var(--ease-spring-soft) var(--app-start-wait) both;
  }

  .app-start-pulse {
    animation: app-start-pulse 750ms ease-in-out calc(var(--app-start-wait) + 350ms) both;
  }

  /* "daizee", letter by letter. */
  .app-start-word path {
    animation: app-start-letter 500ms var(--ease-spring-soft) both;
  }

  .app-start-word path:nth-of-type(1) { animation-delay: calc(var(--app-start-wait) + 600ms); }
  .app-start-word path:nth-of-type(2) { animation-delay: calc(var(--app-start-wait) + 650ms); }
  .app-start-word path:nth-of-type(3) { animation-delay: calc(var(--app-start-wait) + 700ms); }
  .app-start-word path:nth-of-type(4) { animation-delay: calc(var(--app-start-wait) + 750ms); }
  .app-start-word path:nth-of-type(5) { animation-delay: calc(var(--app-start-wait) + 800ms); }
  .app-start-word path:nth-of-type(6) { animation-delay: calc(var(--app-start-wait) + 850ms); }

  @keyframes app-start-pop {
    from { scale: 0; }
  }

  @keyframes app-start-petal {
    from { scale: 0.2 0; }
  }

  @keyframes app-start-turn {
    from { rotate: -150deg; }
  }

  @keyframes app-start-pulse {
    30% { scale: 1.2; }
    55% { scale: 0.95; }
    78% { scale: 1.03; }
  }

  @keyframes app-start-letter {
    from {
      opacity: 0;
      translate: 0 10px;
    }
  }

  @keyframes app-start-out {
    to {
      opacity: 0;
      visibility: hidden;
    }
  }

  /* Without motion the finished picture stands still for a moment. */
  @media (prefers-reduced-motion: reduce) {
    .app-start * {
      animation: none !important;
    }

    .app-start {
      animation-delay: calc(var(--app-start-wait) + 900ms);
    }
  }
}

/* css/components/buttons.css */
/*
 * buttons.css – the four families (card 2.5e, docs/05-design/tokens.md, 4):
 *
 *   Handlung (act):   .btn with -primary / -secondary / -quiet, normal or -compact.
 *                     Capsules (--radius-button).
 *   Wahl (choose):    .choice (filter chips, status), .seg (segments, up to five).
 *                     Soft rectangles (--radius-choice). Chosen = tinted surface,
 *                     border and text in the accent; the width never changes.
 *   Hinweis (note):   .badge, small, no border.
 *   Symbol:           .icon-button.
 *
 * Everything tappable has a target of at least 44 × 44 px (--target-min),
 * also when it looks smaller: an invisible ::after enlarges it.
 */

@layer components {
  /* ---------- Handlung ---------- */
  .btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-related);
    justify-self: start;
    min-height: var(--control-height);
    padding: 0 var(--space-5);
    border: 1px solid transparent;
    border-radius: var(--radius-button);
    font: inherit;
    font-size: var(--text-control);
    font-weight: var(--weight-semibold);
    line-height: 1.2;
    /* Never wider than the space it has: on very narrow screens with large
       text a long label wraps instead of running off the screen. */
    max-width: 100%;
    flex-shrink: 0;
    text-align: center;
    text-wrap: balance;
    text-decoration: none;
    cursor: pointer;
    transition:
      background-color var(--motion-quick) var(--ease-standard),
      box-shadow var(--motion-quick) var(--ease-standard),
      color var(--motion-quick) var(--ease-standard),
      translate var(--motion-instant) var(--ease-standard);
  }

  .btn:active {
    translate: 0 1px;
  }

  /* Haupt: one per view. */
  .btn-primary {
    background: var(--color-accent);
    color: var(--color-on-accent);
    box-shadow: var(--shadow-rest);
  }

  .btn-primary:hover {
    background: color-mix(in srgb, var(--color-accent) 88%, var(--color-text));
    box-shadow: var(--shadow-raised);
  }

  /* Zweit: a tinted surface. */
  .btn-secondary {
    background: color-mix(in srgb, var(--color-accent) 10%, transparent);
    color: var(--color-accent);
  }

  .btn-secondary:hover {
    background: color-mix(in srgb, var(--color-accent) 17%, transparent);
  }

  /* Leise: only the word, no underline (links are ways, buttons act). */
  .btn-quiet {
    background: transparent;
    color: var(--color-accent);
    padding-inline: var(--space-2);
  }

  .btn-quiet:hover {
    background: color-mix(in srgb, var(--color-accent) 8%, transparent);
  }

  /* At the start or the end of a row, the hover surface reaches outwards,
     so the word itself stands on the common edge. */
  /* Hidden fields (the CSRF token) do not count: the first visible one. */
  .btn-quiet:nth-child(1 of :not(input[type="hidden"])) {
    margin-inline-start: calc(-1 * var(--space-2));
  }

  .btn-quiet:nth-last-child(1 of :not(input[type="hidden"])) {
    margin-inline-end: calc(-1 * var(--space-2));
  }

  /* For what has consequences: sign out everywhere, remove. */
  .btn-danger {
    color: var(--color-error);
  }

  .btn-danger:hover {
    background: color-mix(in srgb, var(--color-error) 8%, transparent);
  }

  /* The main button of a question before something with consequences. */
  .btn-primary.btn-danger {
    background: var(--color-error);
    color: var(--color-on-accent);
  }

  .btn-primary.btn-danger:hover {
    background: color-mix(in srgb, var(--color-error) 88%, var(--color-text));
  }

  .btn-compact {
    min-height: var(--control-height-compact);
    padding-inline: var(--space-4);
    font-size: var(--text-meta);
  }

  .btn-compact.btn-quiet {
    padding-inline: var(--space-2);
  }

  .btn-wide {
    width: 100%;
  }

  .btn[disabled],
  .btn[aria-disabled="true"] {
    opacity: 0.45;
    cursor: not-allowed;
    box-shadow: none;
  }

  /* While a request runs, the button keeps its width and shows a small ring. */
  .btn.htmx-request,
  .btn[aria-busy="true"] {
    color: transparent;
  }

  .btn.htmx-request::before,
  .btn[aria-busy="true"]::before {
    content: "";
    position: absolute;
    width: 1.1em;
    height: 1.1em;
    border-radius: var(--radius-full);
    border: 2px solid currentColor;
    border-color: var(--color-accent) var(--color-accent) transparent transparent;
    animation: btn-spin 800ms linear infinite;
  }

  .btn-primary.htmx-request::before,
  .btn-primary[aria-busy="true"]::before {
    border-color: var(--color-on-accent) var(--color-on-accent) transparent transparent;
  }

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

  /* Summaries styled as buttons lose the disclosure triangle. */
  summary.btn {
    list-style: none;
  }

  summary.btn::-webkit-details-marker {
    display: none;
  }

  /* ---------- Symbol ---------- */
  .icon-button {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    flex: none;
    color: var(--color-muted);
    background: transparent;
    border: 0;
    border-radius: var(--radius-full);
    cursor: pointer;
    transition:
      color var(--motion-quick) var(--ease-standard),
      background-color var(--motion-quick) var(--ease-standard);
  }

  .icon-button:hover,
  .icon-button[aria-expanded="true"] {
    color: var(--color-text);
    background: color-mix(in srgb, var(--color-text) 7%, transparent);
  }

  .icon-button-small {
    width: 2rem;
    height: 2rem;
  }

  .icon-button-small .icon {
    width: 1rem;
    height: 1rem;
  }

  .icon-button.is-disabled {
    opacity: 0.45;
    pointer-events: none;
  }

  /* The invisible target: at least 44 × 44 px around everything tappable. */
  .btn::after,
  .icon-button::after,
  .choice::after,
  .seg button::after,
  .seg label::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    translate: -50% -50%;
    width: max(100%, var(--target-min));
    height: max(100%, var(--target-min));
  }

  /* ---------- Wahl: choice chips ----------
     Used as a button (status: aria-pressed) or as a label around a hidden
     radio/checkbox (filters: .choice > input + span). */
  .choice {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--control-height-compact);
    padding: 0 var(--space-3);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-choice);
    background: var(--color-surface);
    color: var(--color-text);
    font: inherit;
    font-size: var(--text-meta);
    font-weight: var(--weight-regular);
    max-width: 100%;
    text-align: center;
    cursor: pointer;
    transition:
      background-color var(--motion-quick) var(--ease-standard),
      border-color var(--motion-quick) var(--ease-standard),
      color var(--motion-quick) var(--ease-standard);
  }

  .choice:hover {
    border-color: var(--color-muted);
  }

  .choice[aria-pressed="true"],
  .choice:has(input:checked) {
    background: color-mix(in srgb, var(--color-accent) 11%, var(--color-surface));
    border-color: var(--color-accent);
    color: var(--color-accent);
  }

  .choice:has(input:focus-visible) {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  .choice input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  .choice-normal {
    min-height: var(--control-height);
  }

  /* Filter chips with a tick that needs no extra room: the chip is wide
     enough from the start; the tick fades in on the left and the word
     moves aside a little. Only padding moves, so the width stays. */
  .choice-filter {
    padding: 0;
  }

  .choice-filter span {
    position: relative;
    padding-inline: 1.25rem;
    line-height: var(--control-height-compact);
    transition: padding var(--motion-quick) var(--ease-standard);
  }

  .choice-filter span::before {
    content: "";
    position: absolute;
    left: 0.55rem;
    top: 50%;
    width: 0.3rem;
    height: 0.6rem;
    border: solid currentColor;
    border-width: 0 2px 2px 0;
    translate: 0 -62%;
    rotate: 45deg;
    opacity: 0;
    scale: 0.5;
    transition:
      opacity var(--motion-quick) var(--ease-standard),
      scale var(--motion-quick) var(--ease-standard);
  }

  .choice-filter:has(input:checked) span {
    padding-inline: calc(1.25rem + 0.4375rem) calc(1.25rem - 0.4375rem);
  }

  .choice-filter:has(input:checked) span::before {
    opacity: 1;
    scale: 1;
  }

  /* ---------- Wahl: segments (up to five, one row) ---------- */
  /* Never wider than its place: in a very narrow column the words in the
     fields wrap instead of widening the page. */
  .seg {
    position: relative;
    display: inline-flex;
    max-width: 100%;
    gap: 2px;
    padding: 3px;
    border: 1px solid var(--color-line);
    border-radius: calc(var(--radius-choice) + 3px);
    background: var(--color-surface);
  }

  .seg button,
  .seg a,
  .seg label {
    position: relative;
    z-index: 1;
    flex: 0 1 auto;
    text-align: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-width: 2.5rem;
    min-height: calc(var(--control-height-compact) - 4px);
    padding: 0 var(--space-3);
    border: 0;
    border-radius: var(--radius-choice);
    background: transparent;
    color: var(--color-muted);
    font: inherit;
    font-size: var(--text-meta);
    text-decoration: none;
    cursor: pointer;
    transition: color var(--motion-quick) var(--ease-standard);
  }

  .seg button:hover,
  .seg a:hover,
  .seg label:hover {
    color: var(--color-text);
  }

  .seg [aria-checked="true"],
  .seg [aria-current="true"],
  .seg label:has(input:checked) {
    background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
    color: var(--color-accent);
  }

  /* With segment.js the chosen surface glides to the new field. */
  .seg.has-glide [aria-checked="true"],
  .seg.has-glide [aria-current="true"],
  .seg.has-glide label:has(input:checked) {
    background: transparent;
  }

  /* A segment may hold radio buttons (a form without JavaScript). */
  .seg input[type="radio"] {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  .seg label:has(input:focus-visible) {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  fieldset.seg {
    margin: 0;
  }

  .seg-glide {
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 0;
    width: 0;
    border-radius: var(--radius-choice);
    background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
    transition:
      translate var(--motion-gentle) var(--ease-standard),
      width var(--motion-gentle) var(--ease-standard);
  }

  .seg-glide.is-instant {
    transition: none;
  }

  /* ---------- Hinweis ---------- */
  .badge {
    display: inline-flex;
    align-items: center;
    padding: 0 var(--space-2);
    border-radius: var(--radius-s);
    background: color-mix(in srgb, var(--color-text) 7%, transparent);
    color: var(--color-muted);
    font-size: var(--text-fine);
    font-weight: var(--weight-semibold);
    line-height: 1.7;
    white-space: nowrap;
  }

  /* "Vorschlag": daizee may have found the work of a free entry; in the
     accent, so it can be found in a long list (card 2.12c). */
  .badge-suggestion {
    background: color-mix(in srgb, var(--color-accent) 12%, transparent);
    color: var(--color-accent);
  }
}

/* css/components/collection.css */
/* collection.css – status buttons with the daisy, the entry on the work
   page, and the collection list (card 1.5). */

@layer components {
  /* ---------- Status buttons (as in the design sample) ---------- */
  .status-group {
    margin: var(--space-3) 0 0;
  }

  .status-group > div {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  /* The status buttons are choices (buttons.css); compact in lists, normal
     size on the work page, where they are the main personal choice. */
  .work .status-chip {
    min-height: var(--control-height);
  }

  /* Loved: the daisy's yellow as a soft wash; the text stays dark (never
     yellow text on a light background). */
  .status-chip.status-loved[aria-pressed="true"] {
    border-color: var(--color-moment);
    color: var(--color-text);
    background: color-mix(in srgb, var(--color-moment) 20%, var(--color-surface));
  }

  /* The button just tapped nods once: a small, springy confirmation. */
  .status-chip.tapped {
    animation: status-nod var(--motion-quick) var(--ease-standard);
  }

  @keyframes status-nod {
    50% {
      scale: 0.94;
    }
  }

  /* ---------- The daisy ---------- */
  .daisy {
    width: 1.25rem;
    height: 1.25rem;
    flex: none;
    overflow: visible;
  }

  .daisy-petals {
    transform-origin: 12px 12px;
  }

  .daisy-petal {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
  }

  .daisy-center {
    fill: var(--color-surface);
    stroke: currentColor;
    stroke-width: 1.4;
  }

  [aria-pressed="true"] .daisy-petal {
    fill: var(--color-moment);
    stroke: var(--color-moment);
  }

  [aria-pressed="true"] .daisy-center {
    fill: var(--color-accent);
    stroke: var(--color-accent);
  }

  /* It blooms once when loved is switched on: the petals unfold, overshoot
     a little and settle (bewegung-klang-haptik.md, section 4). With
     "reduce motion" the duration token is 0 and it simply appears. */
  .bloom .daisy-petals {
    animation: daisy-bloom var(--motion-slow) var(--ease-curtain);
  }

  @keyframes daisy-bloom {
    0% {
      transform: scale(0.55) rotate(-30deg);
    }
    60% {
      transform: scale(1.2) rotate(6deg);
    }
    100% {
      transform: scale(1) rotate(0);
    }
  }

  /* ---------- Icons (Lucide, drawn into the page) ---------- */
  .icon {
    width: 1.25rem;
    height: 1.25rem;
    flex: none;
  }

  /* ---------- The bar above the work: the way back ---------- */
  .work-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-4);
  }

  .back-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-s);
    color: var(--color-muted);
    text-decoration: none;
  }

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

  /* The eye's menu: a popover (opens and closes without a script). */
  .menu {
    margin: 0;
    padding: var(--space-2);
    min-width: 16rem;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-floating);
  }

  .menu form {
    display: grid;
    gap: var(--space-1);
  }

  .menu-title {
    font-size: var(--text-xs);
    color: var(--color-muted);
    margin: var(--space-1) var(--space-2) var(--space-2);
  }

  .menu-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    font: inherit;
    font-size: var(--text-s);
    text-align: start;
    color: var(--color-text);
    background: transparent;
    border: 0;
    border-radius: var(--radius-s);
    padding: var(--space-2);
    cursor: pointer;
  }

  .menu-item:hover {
    background: var(--color-bg);
  }

  .menu-item[aria-checked="true"] {
    color: var(--color-accent);
    font-weight: 600;
  }

  /* Wider than the collection allows: visible, but not choosable. */
  .menu-item:disabled {
    color: var(--color-muted);
    opacity: 0.55;
    cursor: not-allowed;
  }

  .menu-item:disabled:hover {
    background: transparent;
  }

  .menu-hint {
    font-size: var(--text-xs);
    color: var(--color-muted);
    margin: var(--space-2) var(--space-2) var(--space-1);
    max-width: 16rem;
  }

  /* ---------- Dialogs: note and remove ---------- */
  @keyframes entry-in {
    from {
      opacity: 0;
    }
  }

  /* ---------- The note on the work page (card 2.5d, variant 3) ----------
     "Notiz:" and beside it a field one types into; when not in use and
     filled, it reads like quiet text with a pencil beside it. */
  .note-inline {
    display: grid;
    grid-template-columns: max-content minmax(0, var(--width-short));
    column-gap: var(--space-group);
    align-items: start;
    position: relative;
  }

  .note-label {
    padding-block-start: calc(var(--space-tight) + 1px);
    line-height: var(--leading-read);
    font-size: var(--text-meta);
  }

  .note-box {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--space-related);
  }

  .note-box textarea {
    --rows: 1;
    flex: 1;
    min-width: 0;
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: var(--leading-read);
    padding: var(--space-tight) var(--space-3);
    margin-inline-start: calc(-1 * var(--space-3));
    min-block-size: calc(1lh + 2 * var(--space-tight) + 2px);
    max-block-size: calc(6lh + 2 * var(--space-tight) + 2px);
    transition:
      background-color var(--motion-quick) var(--ease-standard),
      border-color var(--motion-quick) var(--ease-standard);
  }

  .note-box textarea::placeholder {
    color: var(--color-muted);
    font-style: italic;
  }

  /* Filled and not being written: quiet text, the pencil beside it. */
  .note-box:not(:focus-within) textarea:not(:placeholder-shown) {
    flex: 0 1 auto;
    width: auto;
    field-sizing: content; /* as wide as its text, so the pencil follows it */
    background: transparent;
    border-color: transparent;
    cursor: text;
  }

  .note-box:not(:focus-within) textarea:not(:placeholder-shown):hover {
    background: color-mix(in srgb, var(--color-text) 4%, transparent);
  }

  .note-pen {
    display: none;
    margin-block-start: calc(var(--space-tight) * -1);
  }

  .note-box:not(:focus-within):has(textarea:not(:placeholder-shown)) .note-pen {
    display: inline-grid;
  }

  /* A quiet "Gespeichert" after leaving the field; it fades by itself. */
  .note-saved {
    position: absolute;
    inset-block-start: 100%;
    grid-column: 2;
    margin-block-start: var(--space-tight);
    font-size: var(--text-fine);
    color: var(--color-success);
    animation: note-saved 2400ms var(--ease-standard) forwards;
  }

  @keyframes note-saved {
    0%,
    70% {
      opacity: 1;
    }
    100% {
      opacity: 0;
    }
  }

  /* ---------- The collection page ---------- */
  /* A dense view: it uses the whole page width (card 2.5a). */
  .collection-page {
    min-width: 0;
  }

  /* Title on the left, who sees the collection on the right. */
  .collection-head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-2) var(--space-4);
  }

  /* Search, then the chip rows (initiator, 04.10.2026): search → chips the
     same distance as the number → the results ("group"); the two chip
     rows closer ("related"); then a deep breath before the results
     ("section"). */
  .collection-filters {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-related);
    margin-block-start: var(--space-group);
  }

  /* The segment "In meiner Sammlung · Neue Werke finden" belongs to the
     field (close below it, card 2.13b); the filters are the next group. */
  .collection-filters > .find-switch {
    justify-self: start;
    margin-block-end: calc(var(--space-group) - var(--space-related));
  }

  /* The part the answer swaps when the direction changes; its rows stay
     in the form's grid. */
  .filter-area {
    display: contents;
  }

  .find-hint {
    margin: 0;
  }

  #collection-list {
    margin-block-start: var(--space-section);
  }

  /* "Art" and "Status" on the left of their chips. */
  .filter-group {
    display: grid;
    grid-template-columns: 4rem minmax(0, 1fr);
    align-items: center;
    column-gap: var(--space-related);
  }

  .filter-legend {
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  /* On a phone the label stands above its row of chips, which can be
     swiped like on the search page (.chip-row). */
  @media (max-width: 47.99rem) {
    .filter-group {
      grid-template-columns: minmax(0, 1fr);
      row-gap: var(--space-tight);
    }

    .filter-group + .filter-group {
      margin-block-start: calc(var(--space-group) - var(--space-related));
    }
  }

  /* ---------- More filters (card 2.13a, initiator 06.10.2026) ----------
     "Art" stays open; status and origin lie one level deeper,
     so the head never pushes the works out of sight (tags join later).
     Closed, the toggle shows what is chosen inside, a small note per
     filter. The device remembers the
     filters and whether this part is open. */
  .filter-more {
    margin-block-start: calc(var(--space-group) - var(--space-related));
    interpolate-size: allow-keywords;
  }

  .filter-more-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-tight);
    min-height: var(--target-min);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-text);
    cursor: pointer;
    list-style: none;
  }

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

  .filter-more-toggle .icon {
    width: 1rem;
    height: 1rem;
    color: var(--color-muted);
    transition: rotate var(--motion-quick) var(--ease-standard);
  }

  .filter-more[open] .filter-more-toggle .icon {
    rotate: 90deg;
  }

  .filter-more-toggle:hover {
    color: var(--color-accent);
  }

  .filter-chosen {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--space-tight);
    margin-inline-start: var(--space-related);
  }

  .filter-chosen .badge {
    gap: var(--space-tight);
    background: color-mix(in srgb, var(--color-accent) 11%, transparent);
    color: var(--color-accent);
  }

  .filter-more[open] .filter-chosen {
    display: none;
  }

  /* Opening glides where the browser can (elsewhere it simply opens). */
  .filter-more::details-content {
    block-size: 0;
    overflow: clip;
    transition:
      block-size var(--motion-gentle) var(--ease-standard),
      content-visibility var(--motion-gentle) allow-discrete;
  }

  .filter-more[open]::details-content {
    block-size: auto;
  }

  .filter-more-body {
    display: grid;
    gap: var(--space-related);
    padding-block-start: var(--space-related);
  }

  @media (max-width: 47.99rem) {
    .filter-more-body {
      row-gap: var(--space-group);
    }
  }

  /* The view switch (list · tiles) closes the results head; on a phone it
     shares the line of "Pro Seite". */
  .view-switch {
    margin-inline-start: auto;
  }

  @media (max-width: 47.99rem) {
    .results-head:has(.view-switch) .per-page {
      flex-basis: auto;
    }
  }

  /* The search field with its magnifier inside. */
  /* The empty collection offers the add field right here. */
  .add-field-row {
    display: flex;
    gap: var(--space-2);
  }

  .add-field-row input {
    flex: 1;
    width: 100%;
    min-width: 0;
    font: inherit;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    padding: var(--space-3) var(--space-4);
  }

  .collection-filters .list-controls {
    justify-content: flex-end;
  }

  .collection-empty {
    display: grid;
    gap: var(--space-4);
    margin-block-start: var(--space-5);
  }
}

/* ---------- Someone else's collection (card 2.13c) ---------- */
@layer components {
  /* "Das liebt ihr beide": one row of tiles above the collection. The
     number of columns follows the window (6, 4, on a phone 2 × 2), so it
     stays one row without clipping the tiles that open on hover. Window
     widths, not a container query: inside a size container Chrome styles
     the tiles twice on loading, and their transitions would flicker. */
  .both-row {
    margin-block: var(--space-group) var(--space-section);
  }

  .both-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-related) var(--space-group);
  }

  .both-head h2 {
    margin: 0;
    font-size: var(--text-heading);
  }

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

  .both-row .tile-wide {
    grid-column: span 1; /* in this one row a video takes one place */
  }

  .both-row .tile:nth-child(n + 5) {
    display: none;
  }

  @media (min-width: 48rem) {
    .both-row .tile-grid {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }
  }

  @media (min-width: 90rem) {
    .both-row .tile-grid {
      grid-template-columns: repeat(6, minmax(0, 1fr));
    }

    .both-row .tile:nth-child(n + 5) {
      display: grid;
    }
  }
}

/* css/components/contact.css */
/* contact.css – the contact page of a friend (card 2.9, freunde.md 4).
   Since Alex's feedback after 2.19b sidebar and main area, like Discover,
   the collection and Friends: on the left the shared lists and the
   friends, on the right the person, the works in common, the coffee list
   (the main thing) and the conversations. On a phone one column, the
   sidebar's parts at the end. "Besprochen" is a tick in the card's side
   column; ticking it is a small moment, the one place here where daisy
   yellow may light up. */

@layer components {
  .contact-page {
    max-width: var(--width-page);
  }

  .contact-layout {
    display: grid;
    grid-template-columns: var(--width-side) minmax(0, 1fr);
    grid-template-areas:
      "side head"
      "side common"
      "side main";
    grid-template-rows: auto auto 1fr;
    gap: var(--space-section);
    align-items: start;
  }

  .contact-layout:not(:has(.contact-common)) {
    grid-template-areas:
      "side head"
      "side main";
    grid-template-rows: auto 1fr;
  }

  /* Nothing for the sidebar: the main area takes the whole width. */
  .contact-layout:not(:has(.contact-side)) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "head"
      "common"
      "main";
  }

  .contact-layout:not(:has(.contact-side)):not(:has(.contact-common)) {
    grid-template-areas:
      "head"
      "main";
  }

  /* The person: picture, name and short name; on the right how you stand
     and "…"; below "about me" and the way into their collection. */
  .contact-head {
    grid-area: head;
    display: grid;
    justify-items: start;
    gap: var(--space-group);
  }

  .contact-id {
    justify-self: stretch;
    display: flex;
    align-items: center;
    gap: var(--space-related) var(--space-group);
  }

  .contact-names {
    flex: 1;
    min-width: 0;
  }

  .contact-names h1 {
    margin: 0;
  }

  .contact-names .note-text {
    margin: var(--space-tight) 0 0;
  }

  .contact-status {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
    white-space: nowrap;
  }

  .contact-head .profile-bio,
  .contact-head .profile-actions {
    max-width: var(--width-read);
    margin: 0;
  }

  .contact-common {
    grid-area: common;
  }

  /* The body lends its two parts to the page's grid. */
  .contact-body {
    display: contents;
  }

  .contact-main {
    grid-area: main;
    display: grid;
    gap: var(--space-section);
    min-width: 0;
  }

  .contact-body h2 {
    margin: 0;
    font-size: var(--text-heading);
  }

  .coffee-empty {
    margin-block: var(--space-group) 0;
    max-width: var(--width-read);
  }

  .coffee-discussed-title {
    margin: var(--space-component) 0 0;
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .coffee-list.is-discussed .work-card-image {
    opacity: 0.75;
  }

  .coffee-text {
    flex: 1 1 100%;
    min-width: 0;
    display: grid;
    gap: var(--space-tight);
    scroll-margin-block-start: var(--space-component);
  }

  .coffee-origin {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .coffee-quote {
    margin: 0;
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: var(--leading-read);
    max-width: var(--width-read);
  }

  .talk-toggle > summary,
  .more-comments > summary {
    justify-self: start;
    width: fit-content;
    white-space: nowrap;
  }

  .talk-toggle[open] > summary {
    margin-block-end: var(--space-related);
  }

  /* "Kommentare" opens like a drawer: the chevron says so and turns. */
  .talk-chevron {
    display: inline-flex;
    transition: rotate var(--motion-quick) var(--ease-standard);
  }

  .talk-toggle[open] .talk-chevron {
    rotate: 180deg;
  }

  .conversation {
    display: grid;
    gap: var(--space-related);
    max-width: var(--width-read);
  }

  .conversation .comment-list {
    margin: 0;
  }

  .conversation .comment-form {
    margin: 0;
  }

  .contact-talks {
    display: grid;
    gap: var(--space-group);
  }

  .talk {
    display: grid;
    gap: var(--space-related);
  }

  .talk-title {
    margin: 0;
    font-size: var(--text-card-title);
  }

  /* "Besprochen": a tick, quiet until it is on. */
  .discussed-toggle.is-on {
    color: var(--color-accent);
    background: color-mix(in srgb, var(--color-accent) 11%, transparent);
  }

  .discussed-toggle.is-static {
    cursor: default;
  }

  /* The moment: once, the tick glows daisy yellow and settles. */
  .discussed-toggle.is-moment {
    animation: discussed-moment var(--motion-slow) var(--ease-enter);
  }

  @keyframes discussed-moment {
    0% {
      scale: 0.8;
      background: var(--color-moment);
      color: var(--color-on-accent);
      box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-moment) 60%, transparent);
    }
    60% {
      scale: 1.12;
      box-shadow: 0 0 0 0.6rem color-mix(in srgb, var(--color-moment) 0%, transparent);
    }
    100% {
      scale: 1;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .discussed-toggle.is-moment {
      animation: none;
    }
  }

  /* The sidebar: shared lists, then the friends, one per line. */
  .contact-side {
    grid-area: side;
    display: grid;
    gap: var(--space-component);
  }

  .contact-side h2 {
    margin-block-end: var(--space-group);
    font-size: var(--text-heading);
  }

  /* As beside the collection: the sidebar stays in view while the main
     area scrolls, and scrolls on its own where it is longer (Alex,
     08.10.2026). */
  @media (min-width: 48rem) {
    .contact-side {
      position: sticky;
      top: calc(var(--space-component) + var(--target-min));
      max-block-size: calc(100dvh - var(--space-component) - var(--target-min) - var(--space-group));
      overflow-y: auto;
      overscroll-behavior: contain;
      scrollbar-width: thin;
    }

    /* A short page still gives the sidebar a window's height to stay in. */
    .contact-layout {
      min-block-size: calc(100dvh - var(--space-component) - var(--target-min) - var(--space-group));
    }
  }

  @media (max-width: 47.99rem) {
    .contact-layout,
    .contact-layout:not(:has(.contact-common)),
    .contact-layout:not(:has(.contact-side)) {
      grid-template-columns: minmax(0, 1fr);
      grid-template-areas:
        "head"
        "common"
        "main"
        "side";
      grid-template-rows: none;
    }

    .contact-layout:not(:has(.contact-common)) {
      grid-template-areas:
        "head"
        "main"
        "side";
    }

    .contact-id {
      flex-wrap: wrap;
    }

    .contact-id .profile-picture {
      inline-size: 4.5rem;
      block-size: 4.5rem;
    }
  }

  /* The dialog "Auf die Kaffee-Liste". */
  .coffee-pick {
    display: grid;
    gap: var(--space-group);
  }

  .coffee-pick form {
    display: grid;
    gap: var(--space-related);
  }

  .pick-list {
    list-style: none;
    margin: var(--space-related) 0 0;
    padding: 0;
    display: grid;
    gap: var(--space-related);
    max-block-size: 22rem;
    overflow-y: auto;
  }

  .pick-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
  }

  .pick-row img,
  .pick-row-empty {
    width: 2.5rem;
    height: 3.75rem;
    flex: none;
    object-fit: cover;
    border-radius: var(--radius-s);
    background: var(--color-line);
  }

  .pick-row.is-wide img {
    height: 1.4rem;
  }

  .pick-row-text {
    display: grid;
    flex: 1 1 10rem;
    min-width: 0;
  }

  .pick-row-title {
    font-size: var(--text-control);
    font-weight: var(--weight-semibold);
  }

  .pick-row-meta {
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

}

/* css/components/dialog.css */
/* dialog.css – dialogs (card 2.5f). On a computer a calm box in the middle;
   on a phone a sheet that comes up from the bottom, like in the apps people
   use every day: a small grip, buttons over the full width, the main one
   first, near the thumb. Swiping it down closes it (dialog.js). */

@layer components {
  .dialog {
    width: min(32rem, 100% - 2 * var(--space-4));
    padding: var(--space-5);
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-floating);
  }

  .dialog::backdrop {
    background: var(--color-scrim);
  }

  .dialog[open] {
    animation: dialog-in var(--motion-gentle) var(--ease-enter);
  }

  .dialog[open]::backdrop {
    animation: dialog-fade var(--motion-gentle) var(--ease-enter);
  }

  /* Opened in the page (without JavaScript): part of the flow. */
  .entry-note .dialog[open]:not(:modal),
  .entry-actions .dialog[open]:not(:modal),
  .dialog-link[open]:not(:modal) {
    position: static;
    margin-block: var(--space-group);
  }

  .dialog form {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-group);
  }

  .dialog-title {
    font-size: var(--text-heading);
    margin: 0;
  }

  .dialog-hint {
    display: flex;
    gap: var(--space-2);
    align-items: flex-start;
    font-size: var(--text-meta);
    color: var(--color-muted);
    margin: 0;
  }

  .dialog-hint .icon {
    width: 1rem;
    height: 1rem;
    margin-block-start: 0.15em;
  }

  .dialog-text {
    margin: var(--space-related) 0 0;
    color: var(--color-muted);
  }

  .dialog textarea {
    font-family: var(--font-read);
    font-size: var(--text-read);
  }

  .dialog-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-related);
    margin-block-start: var(--space-related);
  }

  /* A form that is only the answers to a question (delete a list, take a
     work off it): a row of buttons too, not the grid of forms above. */
  .dialog form.dialog-actions {
    display: flex;
    gap: var(--space-related);
  }

  @keyframes dialog-in {
    from {
      opacity: 0;
      translate: 0 var(--space-2);
    }
  }

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

  /* ---------- On a computer: fixed at the top, growing downwards ----------
     A dialog whose content changes (a second field appears, an error, the
     next step) keeps its upper edge where it is and grows down; centred,
     it would jump up and down. Taller than the screen, it scrolls inside. */
  @media (min-width: 48rem) {
    .dialog:modal {
      margin-block: min(var(--space-frame), 10dvh) auto;
      max-block-size: calc(100dvh - min(var(--space-frame), 10dvh) - var(--space-group));
    }
  }

  /* ---------- On a phone: a sheet from below ---------- */
  @media (max-width: 47.99rem) {
    .dialog:modal {
      width: 100%;
      max-width: 100%;
      margin: auto 0 0;
      padding: var(--space-6) var(--gutter) calc(var(--space-5) + env(safe-area-inset-bottom));
      border: 0;
      border-radius: var(--radius-l) var(--radius-l) 0 0;
      max-height: 90dvh;
      touch-action: pan-y;
    }

    /* The grip: shows that the sheet can be swiped down. */
    .dialog:modal::before {
      content: "";
      position: absolute;
      top: var(--space-2);
      left: 50%;
      translate: -50% 0;
      width: 2.5rem;
      height: 4px;
      border-radius: var(--radius-full);
      background: var(--color-line);
    }

    .dialog:modal[open] {
      animation: sheet-up var(--motion-gentle) var(--ease-enter);
    }

    .dialog:modal .dialog-actions {
      flex-direction: column-reverse;
      align-items: stretch;
    }

    .dialog:modal .dialog-actions .btn {
      width: 100%;
      margin-inline: 0;
    }

    @keyframes sheet-up {
      from {
        translate: 0 100%;
      }
    }
  }
}

/* css/components/error-page.css */
/* error-page.css – "not found" and "something went wrong". */

@layer components {
  .error-page {
    max-width: var(--width-read);
  }
}

/* css/components/feed.css */
/* feed.css – Discover as a feed of rows (card 2.14a; feed-und-algorithmus.md 1.1 to 1.5),
   and each row's own page as a grid (card 2.14b; 1.4).

   Each row is a shelf seen from the side: work tiles (tile.css) stand
   next to each other and go on beyond the right edge; the last visible
   one is cut, which says "there is more". Finger and trackpad swipe it
   as the browser does, with the system's swing and without snapping; the
   mouse gets two round arrows at the edges while it is over the row
   (rows.js). Between the rows the space of page
   sections.

   The open card of a tile must lie over the next row, but a row that
   scrolls sideways clips everything outside it. So the track reaches
   further than it looks: room above, beside and below (padding), taken
   back with a negative margin, so nothing moves. The room above and below
   is cut off (clip-path), which also lets every pointer through it, so the
   row below stays usable; the gaps between the tiles still belong to the
   row, so a swipe works anywhere on it (Alex's feedback after 2.19b: not
   only on a tile). While a card is open the cut is lifted and its row lies
   above the next one. */

@layer components {
  /* Searching: the results stand in the feed's place. The feed stays in
     the page, only not drawn, so every row keeps how far it was scrolled. */
  .discover:has(#results > *) .feed {
    content-visibility: hidden;
  }

  /* The kinds of works only matter while searching. */
  .discover .add-form:has(#q:placeholder-shown) .medium-filter {
    display: none;
  }

  /* On top the lists beside the search: three lists in full and the
     fourth cut in half, so one sees that there are more and that they
     scroll (lists.js keeps the cut where lists differ); the head stays.
     While searching, the column is the usual one beside the results. */
  @media (min-width: 48rem) {
    .discover:not(:has(#results > *)) .discover-top .list-side-body {
      max-block-size: calc(3.5 * var(--target-min) + 3 * var(--space-tight));
    }
  }

  /* The rows two steps closer than sections (initiator, 08.10.2026): a row's
     title and tiles 24/16, rows among themselves 32/20, the search field
     to the first row 40/28. */
  .feed {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-siblings);
    margin-block-start: var(--space-component);
  }

  .feed:empty {
    display: none;
  }

  .feed-row {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-group);
  }

  .feed-row:has(.tile:hover, .tile :focus-visible) {
    z-index: 2;
  }

  /* The title on the left, "Alle ansehen" on the right, on one baseline
     (as "Das liebt ihr beide" in someone else's collection); both lead to
     the row's own page (card 2.14b). */
  .feed-row-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-related) var(--space-group);
  }

  .feed-row-title {
    margin: 0;
    font-size: var(--text-heading);
  }

  .feed-row-title a {
    color: inherit;
    text-decoration: none;
    transition: color var(--motion-quick) var(--ease-standard);
  }

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

  .feed-row-all .icon {
    width: 1.125rem;
    height: 1.125rem;
  }

  .feed-row-all {
    gap: var(--space-1);
  }

  .feed-row-frame {
    position: relative;
  }

  /* ---------- The track: tiles side by side ---------- */
  .row-track {
    --row-gap: var(--space-group);
    --row-full: 2; /* whole tiles in view, and the cut one after them */
    --row-show: 2.3;
    --bleed-x: var(--gutter);
    --bleed-top: var(--space-group);
    --bleed-bottom: 16rem; /* room for the open card below the tiles */
    position: relative; /* rows.js measures the tiles from here */
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: auto auto; /* the shelf, the titles (tile.css) */
    grid-auto-columns: calc((100% - var(--row-gap) * var(--row-full)) / var(--row-show));
    column-gap: var(--row-gap);
    row-gap: var(--space-related);
    list-style: none;
    padding: var(--bleed-top) var(--bleed-x) var(--bleed-bottom);
    margin: calc(-1 * var(--bleed-top)) calc(-1 * var(--bleed-x)) calc(-1 * var(--bleed-bottom));
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain; /* a swipe to the end does not go "back" */
    scroll-padding-inline: var(--bleed-x);
    scrollbar-width: none;
    clip-path: inset(var(--bleed-top) 0 var(--bleed-bottom) 0);
  }

  .row-track:has(.tile:hover, .tile :focus-visible, .tile:focus-visible) {
    clip-path: none;
  }

  .row-track::-webkit-scrollbar {
    display: none;
  }

  /* In a row (feedback of 17.10.2026): the medium's symbol before the
     title, in its line and colour; tight below the title the main people
     (direction, author, artist, channel), in one quiet line, so one sees
     why a work is there. The open card covers both. */
  .row-track .tile-kind {
    display: inline;
    margin-inline-end: var(--space-tight); /* symbol and word */
    color: var(--color-muted);
  }

  .row-track .tile-kind .icon {
    width: 0.9em;
    height: 0.9em;
    vertical-align: -0.08em;
  }

  .row-track .tile-makers {
    display: block;
    margin: var(--space-tight) 0 0;
    overflow: hidden;
    font-size: var(--text-meta);
    line-height: var(--leading-ui);
    color: var(--color-muted);
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  /* A poster keeps about 10 to 11 rem, as in the collection's grid. */
  @media (min-width: 30rem) {
    .row-track {
      --row-full: 3;
      --row-show: 3.3;
    }
  }

  @media (min-width: 48rem) {
    .row-track {
      --bleed-x: var(--space-group);
    }
  }

  @media (min-width: 56rem) {
    .row-track {
      --row-full: 4;
      --row-show: 4.3;
    }
  }

  @media (min-width: 72rem) {
    .row-track {
      --row-full: 5;
      --row-show: 5.3;
    }
  }

  /* ---------- Arrows for the mouse ---------- */
  .row-arrow {
    position: absolute;
    z-index: 3;
    top: calc(50% - var(--target-min) / 2 - 1.75rem); /* the middle of the pictures */
    display: grid;
    place-items: center;
    width: var(--target-min);
    height: var(--target-min);
    padding: 0;
    border: 0;
    border-radius: var(--radius-full);
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: var(--shadow-floating);
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--motion-quick) var(--ease-standard);
  }

  .row-arrow[hidden] {
    display: none;
  }

  .row-prev {
    inset-inline-start: calc(var(--target-min) / -2);
  }

  .row-next {
    inset-inline-end: calc(var(--target-min) / -2);
  }

  .row-arrow .icon {
    width: 1.25rem;
    height: 1.25rem;
  }

  @media (hover: hover) {
    .feed-row-frame:hover .row-arrow {
      opacity: 1;
    }

    .row-arrow:hover {
      background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface));
    }
  }

  .row-arrow:focus-visible {
    opacity: 1;
  }

  /* Without a mouse the rows are swiped; the arrows, half outside the row,
     only widened the page on a phone by a few pixels (initiator,
     08.10.2026: Discover scrolled sideways). */
  @media (hover: none) {
    .row-arrow {
      display: none;
    }
  }

  /* The next row is on its way: it keeps a row's room, so nothing jumps. */
  .feed-next {
    min-block-size: 16rem;
  }

  /* ---------- A list in a row ("Neue Listen") ---------- */
  .list-tile .tile-media {
    aspect-ratio: 2 / 3;
  }

  .list-covers {
    position: absolute;
    inset: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
    gap: 2px;
    overflow: hidden;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
    transition:
      translate var(--motion-quick) var(--ease-standard),
      box-shadow var(--motion-quick) var(--ease-standard);
  }

  .list-covers img,
  .list-cover-empty {
    display: block;
    width: 100%;
    height: 100%;
    min-height: 0;
    object-fit: cover;
  }

  .list-cover-empty {
    background: color-mix(in srgb, var(--color-line) 75%, var(--color-bg));
  }

  @media (hover: hover) {
    .list-tile:hover .list-covers {
      translate: 0 -2px;
      box-shadow: var(--shadow-raised);
    }
  }

  .list-tile:has(.tile-title:focus-visible) .list-covers {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  .list-tile-text {
    grid-row: 2;
    display: grid;
    align-content: start;
    gap: var(--space-tight);
    min-width: 0;
  }

  .list-tile-meta {
    margin: 0;
    font-size: var(--text-meta);
    line-height: var(--leading-ui);
    color: var(--color-muted);
  }

  /* ---------- A row's own page (card 2.14b) ---------- */
  .category-grid {
    position: relative; /* holds the marker below */
  }

  /* The marker for the next step: invisible, no place in the grid, a
     screen high, standing on the grid's lower end. It comes into view a
     screen before one reaches the last tiles, so the next ones are there
     in time; on a short page at once. */
  .category-next {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    block-size: 100vh;
    pointer-events: none;
  }

  .category-grid + #category-end > .category-end {
    margin-block-start: var(--space-section);
  }

  /* ---------- The end ---------- */
  .feed-end {
    display: grid;
    justify-items: start;
    gap: var(--space-related);
    max-inline-size: var(--width-read);
    padding-block-start: var(--space-group);
  }

  .feed-end p {
    margin: 0;
  }

  .feed-end-lead {
    font-family: var(--font-read);
    font-size: var(--text-heading);
    font-weight: var(--weight-regular);
  }

  .feed-end-ways {
    font-family: var(--font-read);
    font-size: var(--text-read);
    color: var(--color-muted);
  }

  /* The whole width: sized to its content, the quiet buttons' outward
     margins (buttons.css) would make the row too narrow for their words. */
  .feed-end-links {
    justify-self: stretch;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-related);
    margin-block-start: var(--space-group) !important;
  }
}

/* css/components/first-look.css */
/* first-look.css – the calm sentence while daizee fetches something on a
   page for the first time (card 4.16). It stands in the page once and
   shows only while a placeholder marked data-first-look still waits; when
   the last one has come, it goes by itself. No script: the page's own
   state decides (:has). */

@layer components {
  .first-look {
    display: none;
    margin: var(--space-related) 0 0;
    font-size: var(--text-meta);
    line-height: var(--leading-ui);
    color: var(--color-muted);
  }

  body:has([data-first-look][aria-busy="true"]) .first-look {
    display: block;
    animation: first-look-in var(--motion-gentle) var(--ease-standard);
  }

  @keyframes first-look-in {
    from {
      opacity: 0;
    }
  }
}

/* css/components/fold.css */
/* fold.css – a box that grows up to a limit, then scrolls and opens to its
   full length (components/fold.templ, fold.js; Alex's feedback after
   2.19b). The limit cuts a row, so one sees there is more. fold.js sets
   the exact height for boxes counted in rows; the values here hold
   without JavaScript. */

@layer components {
  .fold {
    display: grid;
    justify-items: start;
    gap: var(--space-related);
  }

  .fold-box {
    justify-self: stretch;
    min-height: 0;
    overflow-y: auto;
    scrollbar-width: thin;
  }

  /* At its end a box hands the wheel or the finger on to the page
     (overscroll-behavior stays "auto"): the page scrolls on wherever one
     is (Alex, 08.10.2026 over a description; after stage 3 at the end of
     "Neuigkeiten von Freunden", on the computer and the phone). */

  .fold-box:focus {
    outline: none;
  }

  /* Opened: the whole length, the page scrolls. */
  .fold[data-open] .fold-box {
    max-block-size: none;
    overflow: visible;
  }

  /* A grid of tiles (card 4.5a): no height and no scrolling; fold.js
     hides the tiles past the last line, so an open card is never cut. */
  .fold-tiles .fold-box {
    max-block-size: none;
    overflow: visible;
  }

  .fold-tiles .fold-box li[hidden] {
    display: none;
  }

  /* About four and a half lines of news, three and a half requests. */
  .fold-news .fold-box {
    max-block-size: 18rem;
  }

  .fold-requests .fold-box {
    max-block-size: 14rem;
  }

  /* An album's tracks: twenty open (fold.js measures; this holds without
     it). Its people: about fourteen lines. */
  .fold-tracks .fold-box {
    max-block-size: 44rem;
  }

  .fold-credits .fold-box {
    max-block-size: 22rem;
  }

  /* Half the window (the contact page's sidebar). */
  .fold-half .fold-box {
    max-block-size: 50vh;
  }

  /* A text (card 2.21): six lines of its own size, the last one fading,
     clipped instead of scrolling. Only once fold.js is there (data-ready):
     without it the whole text stands. */
  .fold-text .fold-box {
    font-size: var(--text-read);
    line-height: var(--leading-read);
    overflow: visible; /* a text never scrolls in itself, the page does */
  }

  /* Body text: the box measures its six lines in the text's own lines. */
  .fold-text .fold-box:has(> .body-text) {
    font-size: var(--text-body);
    line-height: var(--leading-body);
  }

  .fold-text[data-ready]:not([data-open]) .fold-box {
    max-block-size: 6lh;
    overflow: hidden;
  }

  .fold-text[data-ready][data-overflows]:not([data-open]) .fold-box {
    mask-image: linear-gradient(to bottom, black calc(100% - 1.2lh), transparent);
  }

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

  .fold-toggle .icon {
    width: 1rem;
    height: 1rem;
    transition: rotate var(--motion-quick) var(--ease-standard);
  }

  .fold-less .icon,
  .fold[data-open] .fold-more .icon {
    rotate: 180deg;
  }
}

/* css/components/forms.css */
/*
 * forms.css – labels, fields and buttons.
 * Large, calm fields; one clear main button per form; errors in words,
 * never only in colour.
 */

@layer components {
  .narrow-page {
    max-width: var(--width-form);
  }

  /* ---------- A form: fields one below the other (card 2.5f) ----------
     Each field is a unit: label, field and hint stand close together
     (8 px, 4 px); between two fields lies the role "siblings"; the button
     that sends stands apart ("component"). The order of the elements gives
     the distances, so forms need no wrapper per field. */
  .stack-form {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-related);
    margin-block: var(--space-component);
  }

  .stack-form > :is(input, textarea, select, .code-input, .recipients) + :is(.note-text, .form-error) {
    margin-block-start: calc(var(--space-tight) - var(--space-related));
  }

  .stack-form > :is(label, fieldset, .prose, .note-text + .checkbox):not(:first-child) {
    margin-block-start: calc(var(--space-siblings) - var(--space-related));
  }

  .stack-form > :is(.checkbox, .form-error) + .note-text {
    margin-block-start: calc(var(--space-tight) - var(--space-related));
  }

  .stack-form > button[type="submit"]:last-child,
  .stack-form > .btn-primary:last-child {
    margin-block-start: calc(var(--space-component) - var(--space-related));
  }

  /* One kind of label: small, semibold, in the text colour. */
  label,
  legend {
    font-size: var(--text-meta);
    font-weight: var(--weight-semibold);
    color: var(--color-text);
  }

  legend {
    padding: 0;
  }

  /* A group of fields may shrink like everything else (by default a
     fieldset insists on the width of its widest content). */
  /* On a phone the main button of a form spans the width, near the thumb. */
  @media (max-width: 47.99rem) {
    .stack-form > .btn-primary:last-child {
      width: 100%;
    }
  }

  fieldset {
    min-inline-size: 0;
  }

  .note-text,
  .form-error {
    max-width: var(--width-read);
  }

  /* ---------- Ticks and switches ---------- */
  .checkbox {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: var(--target-min);
    font-size: var(--text-control);
    font-weight: var(--weight-regular);
    cursor: pointer;
  }

  .checkbox input,
  .choice-row input {
    width: 1.25rem;
    height: 1.25rem;
    margin: 0;
    flex: none;
    accent-color: var(--color-accent);
  }

  /* An on/off setting: a switch (role="switch" on a checkbox). */
  .checkbox input[role="switch"] {
    appearance: none;
    position: relative;
    width: 2.75rem;
    height: 1.5rem;
    border-radius: var(--radius-full);
    background: var(--color-line);
    cursor: pointer;
    transition: background-color var(--motion-quick) var(--ease-standard);
  }

  .checkbox input[role="switch"]::before {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: calc(1.5rem - 6px);
    height: calc(1.5rem - 6px);
    border-radius: var(--radius-full);
    background: var(--color-surface);
    box-shadow: var(--shadow-rest);
    transition: translate var(--motion-quick) var(--ease-standard);
  }

  .checkbox input[role="switch"]:checked {
    background: var(--color-accent);
  }

  .checkbox input[role="switch"]:checked::before {
    translate: 1.25rem 0;
  }

  .checkbox input[role="switch"]:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  input[type="email"],
  input[type="text"],
  input[type="time"],
  textarea,
  .code-input {
    font: inherit;
    font-size: var(--text-m);
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    padding: var(--space-3) var(--space-4);
    width: 100%;
    transition: border-color var(--motion-quick) var(--ease-standard);
  }

  /* Text areas grow with their text up to about one line more than their
     starting size, then scroll; nobody drags their corner (card 2.5f). */
  textarea {
    --rows: 3;
    resize: none;
    field-sizing: content;
    line-height: var(--leading-read);
    min-block-size: calc(var(--rows) * 1lh + 2 * var(--space-3) + 2px);
    max-block-size: calc((var(--rows) + 1) * 1lh + 2 * var(--space-3) + 2px);
  }

  textarea[rows="4"] {
    --rows: 4;
  }

  textarea[rows="6"] {
    --rows: 6;
  }

  input:not([type="range"]):hover,
  textarea:hover {
    border-color: var(--color-muted);
  }

  /* Fields show focus inside their own border: the border takes the accent
     colour and a ring of the same colour lies on its inside. Nothing grows,
     nothing moves, the corners stay as they are (card 2.5). Everywhere. */
  :is(input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"]), textarea, select):focus-visible {
    outline: none;
    border-color: var(--color-focus);
    box-shadow: inset 0 0 0 1px var(--color-focus);
  }

  input[aria-invalid="true"] {
    border-color: var(--color-error);
  }

  /* Six digits, set wide apart like on a ticket. */
  .code-input {
    font-size: var(--text-xl);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.4em;
    max-width: 14ch;
  }

  .form-error {
    color: var(--color-error);
    font-size: var(--text-s);
    margin: 0;
  }

  .note-text {
    color: var(--color-muted);
    font-size: var(--text-s);
  }
}

/* css/components/friends.css */
/*
 * friends.css – the area Friends (card 2.8, freunde.md section 1) and the
 * friendship buttons on profiles.
 *
 * On a computer sidebar and main area, like Discover and the collection
 * (Alex's feedback after 2.19b): on the left requests, the search for
 * friends and the contacts; on the right, wide, the news and the
 * recommendations. On a phone a segment shows one part at a time.
 */

@layer components {
  /* The title, and "Jemanden einladen" on the same line at the right. */
  .friends-head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-related) var(--space-group);
    margin-bottom: var(--space-component);
  }

  .friends-head h1 {
    margin: 0;
  }

  .friends h2 {
    margin: 0 0 var(--space-group);
    font-size: var(--text-heading);
  }

  /* Heading and its hint are one group; the field starts the next. */
  .part-find {
    display: grid;
    gap: var(--space-group);
  }

  .friends-part-head {
    display: grid;
    gap: var(--space-related);
  }

  .part-find h2 {
    margin: 0;
  }

  .part-find .friends-part-head .note-text {
    margin: 0;
  }

  #found:empty {
    display: none;
  }

  .friends-subtitle {
    margin: var(--space-group) 0 var(--space-related);
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .friends-new {
    padding: var(--space-group);
    border-radius: var(--radius-l);
    background: var(--color-surface);
  }

  .friends-new .note-text {
    margin: 0;
  }

  /* "Neu für dich" (card 2.15): picture, one sentence in the interface
     face, when it happened muted. Unread lines carry a light tint. */
  .news-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-tight);
  }

  .news-row {
    display: flex;
    align-items: center;
    gap: var(--space-related) var(--space-group);
    flex-wrap: wrap;
    padding: var(--space-related) var(--space-3);
    border-radius: var(--radius-m);
  }

  .news-row.is-unread {
    background: color-mix(in srgb, var(--color-accent) 7%, var(--color-surface));
  }

  .news-text {
    flex: 1 1 14rem;
    min-width: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--space-related);
    font-size: var(--text-control);
  }

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

  .news-text a:hover {
    text-decoration: underline;
  }

  .news-when {
    font-size: var(--text-meta);
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  .friends-grid {
    display: grid;
    grid-template-columns: var(--width-side) minmax(0, 1fr);
    gap: var(--space-section);
    align-items: start;
  }

  .friends-side {
    display: grid;
    gap: var(--space-component);
  }

  .friends-main {
    display: grid;
    gap: var(--space-section);
  }

  /* No requests either way: on a computer the part is left out (freunde.md
     1.2); the phone's segment still says so in words. The sidebar stays
     in view while the main area scrolls, and scrolls on its own where it
     is longer than the window, as the lists beside the collection (Alex,
     08.10.2026). */
  @media (min-width: 48rem) {
    .part-requests.is-empty {
      display: none;
    }

    .friends-side {
      position: sticky;
      top: calc(var(--space-component) + var(--target-min));
      max-block-size: calc(100dvh - var(--space-component) - var(--target-min) - var(--space-group));
      overflow-y: auto;
      overscroll-behavior: contain;
      scrollbar-width: thin;
    }

    /* A short main area still gives the sidebar a window's height to
       stay in, so it is never cut off at the bottom. */
    .friends-main {
      min-block-size: calc(100dvh - var(--space-component) - var(--target-min) - var(--space-group));
    }
  }

  .friends-recs .results-head {
    margin-bottom: var(--space-group);
  }

  .friends-recs .results-head h2 {
    margin: 0;
  }

  /* The segment for the parts is for phones only. */
  .friends-parts {
    display: none;
  }

  .contact-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related) var(--space-group);
    margin-bottom: var(--space-related);
  }

  .contact-tools .search-field {
    flex: 1 1 14rem;
  }

  .find-form {
    display: grid;
    gap: var(--space-tight);
  }

  .person {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-inline-size: 0;
  }

  .person-text {
    display: grid;
    min-inline-size: 0;
  }

  .person-name {
    font-weight: var(--weight-semibold);
    color: var(--color-text);
    text-decoration: none;
    overflow-wrap: anywhere;
  }

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

  .row-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-related);
    align-items: center;
  }

  /* In the narrow sidebar a person keeps name and date on their lines;
     where the buttons do not fit beside them, they stand below, right. */
  .friends-side .settings-row {
    flex-wrap: wrap;
    row-gap: var(--space-related);
  }

  .friends-side .settings-row > .row-actions,
  .friends-side .settings-row > form {
    flex-wrap: nowrap;
    margin-inline-start: auto;
  }

  .empty-note {
    color: var(--color-muted);
  }

  /* ---------- Recommendations: the work small, who to whom, the sentence ---------- */
  .rec-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: 1px solid var(--color-line);
  }

  .rec-row {
    border-bottom: 1px solid var(--color-line);
  }

  .rec-line {
    display: grid;
    grid-template-columns: 3rem minmax(0, 1fr);
    gap: var(--space-group);
    align-items: start;
    padding-block: var(--space-4);
    color: var(--color-text);
    text-decoration: none;
  }

  .rec-line.is-wide {
    grid-template-columns: 5.25rem minmax(0, 1fr);
  }

  .rec-line img,
  .rec-line-empty {
    width: 100%;
    aspect-ratio: 2 / 3;
    object-fit: cover;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
    background: var(--color-line);
  }

  .rec-line.is-wide img {
    aspect-ratio: 16 / 9;
  }

  .rec-line-empty {
    display: grid;
    place-items: center;
    color: var(--color-muted);
  }

  .rec-line-text {
    display: grid;
    gap: var(--space-tight);
  }

  .rec-line-who {
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .rec-line-who em {
    font-family: var(--font-read);
    font-style: italic;
    color: var(--color-text);
  }

  .rec-line-who .nav-dot {
    position: static;
    display: inline-block;
    margin-inline-end: var(--space-2);
    vertical-align: middle;
  }

  .rec-line-sentence {
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: 1.45;
  }

  /* What became of one's own recommendation (card 2.16b). */
  .rec-line-outcome {
    display: flex;
    align-items: center;
    gap: var(--space-related);
    margin-block-start: var(--space-tight);
    font-size: var(--text-meta);
    color: var(--color-text);
  }

  .rec-line:hover .rec-line-sentence {
    color: var(--color-accent);
  }

  .friends-new .rec-line {
    padding-block: 0;
  }

  .friends-blocked-link {
    margin-top: var(--space-section);
    font-size: var(--text-meta);
  }

  .friends-blocked-link a {
    color: var(--color-muted);
  }

  .friends-back {
    margin: 0 0 var(--space-group) calc(-1 * var(--space-2));
    gap: var(--space-1);
  }

  .friends-back .icon {
    width: 1.125rem;
    height: 1.125rem;
  }

  /* ---------- On a phone: one part at a time ---------- */
  @media (max-width: 47.99rem) {
    .friends-grid {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--space-section);
    }

    .friends-side,
    .friends-main {
      display: contents;
    }

    .friends-parts {
      display: inline-flex;
      margin-bottom: var(--space-siblings);
    }

    .friends-parts .nav-dot {
      position: static;
      margin-inline-start: var(--space-1);
    }

    .friends:has(.friends-parts input[value="news"]:checked) .friends-part:not(.part-news),
    .friends:has(.friends-parts input[value="contacts"]:checked) .friends-part:not(.part-contacts),
    .friends:has(.friends-parts input[value="requests"]:checked) .friends-part:not(.part-requests) {
      display: none;
    }

    /* "Neues" first: the news, then the recommendations. */
    .part-news {
      order: -1;
    }

    .friends-new {
      padding: var(--space-group) var(--space-4);
      margin-inline: calc(-1 * var(--space-2));
    }
  }

  /* ---------- On profiles ---------- */
  /* Quiet actions that ask once more (end, block): fold out in place. */
  .quiet-details summary {
    list-style: none;
    cursor: pointer;
  }

  .quiet-details summary::-webkit-details-marker {
    display: none;
  }

  .quiet-details[open] {
    display: grid;
    justify-items: start;
    gap: var(--space-related);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    background: var(--color-surface);
  }

  .profile-actions {
    display: grid;
    justify-items: start;
    gap: var(--space-related);
    margin-block-start: var(--space-group);
  }
}

/* css/components/home.css */
/* home.css – the start page for visitors (card 3.1, design 3 "Poster-Wand"
   of the preview docs/05-design/vorschau/daizee-startseite.html): rows
   of posters drift slowly and muted behind the large logo; one sentence,
   a few words, one button. Without motion everything stands still. */

@layer components {
  /* The start page uses the whole width: the wall runs from edge to edge. */
  .page-main:has(> .home) {
    max-width: none;
    padding: 0;
  }

  .home {
    position: relative;
    display: grid;
    place-items: center;
    min-height: max(40rem, 80dvh);
    padding: var(--space-section) var(--gutter);
    overflow: hidden;
  }

  /* ---------- The wall ---------- */
  .home-wall {
    position: absolute;
    inset: -4rem 0;
    display: grid;
    align-content: center;
    gap: var(--space-4);
    rotate: -4deg;
    scale: 1.1;
    opacity: 0.55;
  }

  .home-wall-row {
    display: flex;
    gap: var(--space-4);
    width: max-content;
    animation: home-drift 90s linear infinite;
  }

  .home-wall-row:nth-child(even) {
    animation-direction: reverse;
    animation-duration: 110s;
  }

  .home-wall img {
    width: 9rem;
    height: auto;
    aspect-ratio: 2 / 3;
    object-fit: cover;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
  }

  @keyframes home-drift {
    to {
      transform: translateX(-50%);
    }
  }

  /* The light in the middle: the wall fades towards the words. */
  .home::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(
      ellipse 62% 60% at 50% 52%,
      var(--color-bg) 42%,
      color-mix(in srgb, var(--color-bg) 75%, transparent) 70%,
      color-mix(in srgb, var(--color-bg) 35%, transparent)
    );
  }

  /* ---------- The words ---------- */
  .home-copy {
    position: relative;
    z-index: 1;
    display: grid;
    justify-items: center;
    max-width: var(--width-read);
    text-align: center;
  }

  .home-logo {
    width: clamp(10rem, 22vw, 15rem);
    margin-block-end: var(--space-component);
    color: var(--color-text);
  }

  .home-logo .logo {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
  }

  .home-claim {
    margin: 0;
    font-family: var(--font-read);
    font-weight: var(--weight-regular);
    font-size: var(--text-hero);
    line-height: 1.08;
    letter-spacing: -0.01em;
    text-wrap: balance;
  }

  .home-lead {
    margin: var(--space-group) 0 0;
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: 1.55;
    text-wrap: pretty;
  }

  .home-quiet {
    margin: var(--space-related) 0 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .home-sign-in {
    justify-self: center;
    margin-block-start: var(--space-component);
  }

  /* "Wie komme ich hinein?": a quiet way below the one button. */
  .home-how {
    margin-block-start: var(--space-group);
    font-size: var(--text-meta);
  }

  .home-how summary {
    display: inline-flex;
    align-items: center;
    min-height: var(--target-min);
    padding-inline: var(--space-3);
    color: var(--color-accent);
    font-weight: var(--weight-medium);
    cursor: pointer;
    list-style: none;
  }

  .home-how summary::-webkit-details-marker {
    display: none;
  }

  .home-how p {
    margin: var(--space-related) 0 0;
    padding: var(--space-4) var(--space-5);
    background: var(--color-surface);
    border-radius: var(--radius-m);
    line-height: 1.55;
    text-align: start;
  }

  /* ---------- Arriving: the daisy blooms, the words rise ---------- */
  .home-logo .logo-petals,
  .home-logo .logo-center {
    transform-box: fill-box;
    transform-origin: center;
  }

  .home-logo .logo-petals {
    animation: home-petals 1.4s var(--ease-spring-soft) 0.35s both;
  }

  .home-logo .logo-center {
    animation: home-center 0.7s var(--ease-spring) 0.2s both;
  }

  .home-claim,
  .home-lead,
  .home-quiet,
  .home-sign-in,
  .home-how {
    animation: home-rise 0.9s var(--ease-enter) both;
  }

  .home-claim { animation-delay: 0.15s; }
  .home-lead,
  .home-quiet { animation-delay: 0.3s; }
  .home-sign-in,
  .home-how { animation-delay: 0.45s; }

  @keyframes home-petals {
    from {
      transform: scale(0.2) rotate(-90deg);
      opacity: 0;
    }
    40% {
      opacity: 1;
    }
  }

  @keyframes home-center {
    from {
      transform: scale(0);
    }
  }

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

  @media (prefers-reduced-motion: reduce) {
    .home *,
    .home *::before,
    .home *::after {
      animation: none !important;
    }
  }

  /* ---------- Phones ---------- */
  @media (max-width: 47.99rem) {
    .home-wall img {
      width: 7rem;
    }

    .home-sign-in {
      justify-self: stretch;
    }
  }
}

/* css/components/install.css */
/* install.css – the offer to install daizee (card 3.2): in the onboarding
   and under Settings → daizee App installieren (round 3 of the feedback
   after stage 3), a button or the way by hand (pwa.js). */

@layer components {
  .install-offer {
    display: grid;
    justify-items: start;
    gap: var(--space-related);
    margin-block-start: var(--space-component);
  }

  .install-offer-note {
    margin: 0;
    max-width: var(--width-read);
    font-size: var(--text-meta);
    color: var(--color-muted);
    line-height: 1.55;
  }

  /* The settings page: what the app is for, the offer, then the note on
     notifications, a group apart. */
  .app-intro {
    margin: 0;
    max-width: var(--width-read);
  }

  .app-intro + .install-offer {
    margin-block-start: var(--space-group);
  }

  .app-push-note {
    margin-block-start: var(--space-siblings);
  }

  @media (max-width: 47.99rem) {
    .install-offer .btn {
      justify-self: stretch;
    }
  }
}

/* css/components/language-switch.css */
/* language-switch.css – "Deutsch · English" in the footer, "DE / EN" in
   the header. */

@layer components {
  .language-switch {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    /* The buttons have inner padding; pull back so the text lines up with the page. */
    margin-inline-start: calc(-1 * var(--space-2));
  }

  .language-switch button {
    background: none;
    border: 0;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-s);
    color: var(--color-muted);
    font-size: var(--text-s);
    cursor: pointer;
    transition: color var(--motion-quick) var(--ease-standard);
  }

  .language-switch button:hover {
    color: var(--color-accent);
  }

  .language-switch button[aria-current="true"] {
    color: var(--color-text);
    font-weight: 600;
    cursor: default;
  }

  .language-switch .separator {
    color: var(--color-line);
  }

  /* The short form at the right end of the header: "DE / EN", the current
     language bold (Alex, feedback after stage 3). Quiet like the way to sign
     in; each button is a full target (44 px) whose word sits next to the
     slash, so the line reads close together. */
  .language-switch-short {
    gap: 0;
    margin-inline: 0 calc(-1 * var(--space-2));
  }

  .language-switch-short button {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    min-block-size: var(--target-min);
    min-inline-size: var(--target-min);
    padding: 0 var(--space-1);
    font-size: var(--text-meta);
    letter-spacing: 0.02em;
  }

  .language-switch-short button:first-of-type {
    align-items: flex-end;
  }

  .language-switch-short button:last-of-type {
    align-items: flex-start;
  }

  /* The bold word's width is held by an invisible copy, so the line never
     shifts when the language changes. */
  .language-switch-short button::after {
    content: attr(data-label);
    font-weight: var(--weight-semibold);
    block-size: 0;
    overflow: hidden;
    visibility: hidden;
  }

  .language-switch-short button[aria-current="true"] {
    font-weight: var(--weight-semibold);
  }

  .language-switch-short .separator {
    color: var(--color-muted);
    font-size: var(--text-meta);
  }

  /* On a phone the onboarding's header is full with its points and
     "Später"; there the step "Du" asks for the language itself (preset
     from the browser), so the header leaves it out. */
  @media (max-width: 47.99rem) {
    .page-header:has(.welcome-dots) .language-switch-short {
      display: none;
    }
  }
}

/* css/components/legal.css */
/* legal.css – imprint, privacy, sources and thanks; the report form and
   links; the links in the footer (card 1.7). Quiet pages, easy to read. */

@layer components {
  .legal h2 {
    font-size: var(--text-l);
    margin-block: var(--space-6) var(--space-2);
  }

  .legal .prose {
    margin-block: 0 var(--space-3);
  }

  .operator {
    display: grid;
    gap: var(--space-1);
    font-style: normal;
    margin-block: var(--space-4) var(--space-5);
  }

  .source-logo {
    height: 1.25rem;
    width: auto;
  }

  .sources-list {
    padding-inline-start: var(--space-5);
  }

  .report-reasons {
    display: grid;
    gap: var(--space-2);
    border: 0;
    margin: 0;
    padding: 0;
  }

  .report-reasons legend {
    font-size: var(--text-s);
    font-weight: 600;
    margin-block-end: var(--space-2);
  }

  .report-reasons input {
    accent-color: var(--color-accent);
  }

  .report-link {
    font-size: var(--text-xs);
    color: var(--color-muted);
  }

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

  /* The footer: legal links on the left, the language switch on the right. */
  .page-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-3) var(--space-5);
  }

  .legal-links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-5);
    font-size: var(--text-s);
  }

  .legal-links a {
    color: var(--color-muted);
    text-decoration: none;
  }

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

/* css/components/list-drop.css */
/* list-drop.css – a list as a goal to drop works on (components.ListDrop,
   card 2.17; approved in the onboarding preview). drag.js marks it while a
   work hovers (.is-drop-target) and when one lands (.is-dropped, with the
   nod of lists.css, list-took). */

@layer components {
  .list-drop {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-group);
    margin: 0;
    padding: var(--space-3) var(--space-4);
    border: 1.5px dashed transparent;
    border-radius: var(--radius-m);
    background: var(--color-surface);
    transition:
      border-color var(--motion-quick) var(--ease-standard),
      background-color var(--motion-quick) var(--ease-standard),
      scale var(--motion-quick) var(--ease-standard);
  }

  /* While a work is carried, every goal shows its edge. */
  body.is-dragging-work .list-drop[data-drop-list] {
    border-color: var(--color-line);
  }

  .list-drop.is-drop-target {
    border-color: var(--color-accent);
    background: color-mix(in srgb, var(--color-accent) 7%, var(--color-surface));
    scale: 1.01;
  }

  .list-drop.is-dropped {
    animation: list-took var(--motion-gentle) var(--ease-enter);
  }

  /* The symbol on a soft accent surface (the light bordeaux in the dark). */
  .list-drop-icon {
    display: grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--radius-m);
    background: color-mix(in srgb, var(--color-accent) 10%, transparent);
    color: var(--color-accent);
  }

  .list-drop-text {
    display: grid;
    gap: var(--space-tight);
    min-width: 0;
  }

  .list-drop-name {
    overflow: hidden;
    font-size: var(--text-control);
    font-weight: var(--weight-semibold);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .list-drop-meta {
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  /* What lies on it: up to three pictures, fanned, and the number. */
  .list-drop-side {
    display: flex;
    align-items: center;
    gap: var(--space-related);
  }

  .list-drop-stack {
    position: relative;
    width: 2.6rem;
    height: 2.25rem;
  }

  .list-drop-stack img {
    position: absolute;
    top: 0;
    width: 1.5rem;
    height: 2.25rem;
    object-fit: cover;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
  }

  .list-drop-stack img:nth-child(1) { left: 0; rotate: -5deg; }
  .list-drop-stack img:nth-child(2) { left: 0.55rem; rotate: 0deg; }
  .list-drop-stack img:nth-child(3) { left: 1.1rem; rotate: 5deg; }

  .list-drop-count {
    min-width: 1ch;
    font-size: var(--text-meta);
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  /* A new list: the name is written right in the card (more specific than
     the general field rules of forms.css, which would frame it). */
  .list-drop .list-drop-input {
    width: 100%;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
    border-block-end: 1px solid transparent;
    background: transparent;
    color: var(--color-text);
    font: inherit;
    font-size: var(--text-control);
    font-weight: var(--weight-semibold);
    outline: none;
  }

  .list-drop .list-drop-input:hover {
    border-block-end-color: var(--color-line);
  }

  .list-drop .list-drop-input:focus,
  .list-drop .list-drop-input:focus-visible {
    box-shadow: none;
    border-block-end-color: var(--color-accent);
  }

  /* Too long for a narrow field: it ends in "…", not mid-letter. */
  .list-drop .list-drop-input {
    text-overflow: ellipsis;
  }

  /* A suggestion to write over, not a name (italic, quiet). */
  .list-drop .list-drop-input::placeholder {
    color: var(--color-muted);
    font-style: italic;
    font-weight: var(--weight-regular);
  }
}

/* css/components/lists.css */
/* lists.css – lists (card 2.10, listen.md): the sidebar beside Discover
   and the collection, dragging works onto it, and the list page.

   On a computer the lists stand in a narrow column on the left; the
   active one is a tinted surface. On a phone a small button opens them as
   a sheet from below (card 2.14a; dragging is rare there, the bookmark
   does the same).
   While a work is dragged, the lists show they are targets; the one under
   the pointer is tinted with an accent edge. */

@layer components {
  .with-lists {
    display: grid;
    grid-template-columns: var(--width-side) minmax(0, 1fr);
    gap: var(--space-section);
    align-items: start;
  }

  /* The left column: the way back (if any) above the lists, together
     sticky, so the page's own content starts at the top (13.10.2026). */
  .with-lists-side {
    position: sticky;
    top: calc(var(--space-component) + var(--target-min));
    display: grid;
    gap: var(--space-group);
    justify-items: start;
    min-width: 0;
  }

  .with-lists-side > .list-side {
    justify-self: stretch;
  }

  .with-lists-back {
    gap: var(--space-1);
    max-inline-size: 100%;
    margin-inline-start: calc(-1 * var(--space-2)); /* the word on the column's edge */
  }

  .with-lists-back .icon {
    width: 1.125rem;
    height: 1.125rem;
    flex: none;
  }

  /* A long work title keeps to one line. */
  .with-lists-back-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .with-lists-main {
    min-width: 0;
  }

  /* The phone's way to the lists (below); a computer has the column. */
  .list-side-open.btn {
    display: none;
  }

  /* A popover for the phone's sheet; here everything a popover brings is
     taken back, so on a computer it is simply the column. */
  .list-side {
    inset: auto;
    width: auto;
    height: auto;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: visible;
    background: none;
    color: inherit;
    position: static; /* in the column's flow; the column sticks (.with-lists-side) */
    display: grid;
    grid-template-rows: auto minmax(0, 1fr); /* the head stays, the lists scroll */
    gap: var(--space-related);
  }

  /* "Listen" on the left, "+ Neu" on the right, on one line. */
  .list-side-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-related);
    anchor-name: --list-side-head;
  }

  .list-side-body {
    min-height: 0;
    max-block-size: 60vh;
    overflow-y: auto;
    overflow-x: hidden; /* never sideways */
    overscroll-behavior: contain;
    scrollbar-width: thin;
  }

  /* The name field for a new list: a small floating field right below
     the head, as wide as it (anchor positioning; where a browser cannot
     do that yet, it stands in the middle of the screen). */
  .list-new-form {
    inset: auto;
    position-anchor: --list-side-head;
    top: anchor(bottom);
    left: anchor(left);
    width: anchor-size(width);
    margin: var(--space-related) 0 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    overflow: visible;
  }

  @supports not (anchor-name: --a) {
    .list-new-form {
      inset: 0;
      margin: auto;
      width: min(20rem, calc(100vw - 2 * var(--gutter)));
      height: fit-content;
    }
  }

  .list-new-form input {
    width: 100%;
    box-shadow: var(--shadow-floating);
  }

  .list-new-form input:focus-visible {
    box-shadow: inset 0 0 0 1px var(--color-focus), var(--shadow-floating); /* the focus ring of all fields, still floating */
  }

  .list-side-title {
    margin: 0;
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .list-side-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-tight);
  }

  .list-link {
    display: flex;
    align-items: center;
    gap: var(--space-related);
    min-height: var(--target-min);
    padding: 0 var(--space-3);
    border-radius: var(--radius-m);
    border: 1px solid transparent;
    color: var(--color-text);
    text-decoration: none;
    font-size: var(--text-control);
    transition:
      background-color var(--motion-quick) var(--ease-standard),
      border-color var(--motion-quick) var(--ease-standard);
  }

  .list-link .icon {
    flex: none;
    color: var(--color-muted);
  }

  .list-link span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .list-link:hover {
    background: color-mix(in srgb, var(--color-text) 6%, transparent);
  }

  .list-link[aria-current="page"] {
    background: color-mix(in srgb, var(--color-accent) 11%, var(--color-surface));
    color: var(--color-accent);
  }

  .list-side-empty {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .list-new > summary {
    width: fit-content;
  }

  .list-new form {
    margin-block-start: var(--space-related);
  }

  /* ---------- Dragging ---------- */
  .work-card[data-drag-work],
  .work-card[data-drag-ref] {
    touch-action: pan-y;
  }

  .work-card.is-dragging {
    opacity: 0.5;
  }

  body.is-dragging-work,
  body.is-dragging-work * {
    cursor: grabbing;
    user-select: none;
  }

  body.is-dragging-work .list-link[data-drop-list] {
    border-color: var(--color-line);
    border-style: dashed;
  }

  .list-link.is-drop-target {
    background: color-mix(in srgb, var(--color-accent) 14%, var(--color-surface));
    border: 1px solid var(--color-accent);
    color: var(--color-accent);
  }

  .list-link.is-dropped {
    animation: list-took var(--motion-gentle) var(--ease-enter);
  }

  @keyframes list-took {
    40% {
      scale: 1.04;
    }
  }

  .drag-ghost {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    gap: var(--space-related);
    max-width: 16rem;
    padding: var(--space-related) var(--space-3);
    border-radius: var(--radius-m);
    background: var(--color-surface);
    box-shadow: var(--shadow-floating);
    border: 1px solid var(--color-line);
    font-size: var(--text-meta);
    font-weight: var(--weight-semibold);
    pointer-events: none;
  }

  .drag-ghost img {
    width: 1.75rem;
    height: 2.6rem;
    object-fit: cover;
    border-radius: var(--radius-s);
  }

  .drag-ghost > span:not(.drag-ghost-stack) {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Many works at once (card 2.13b): their pictures staggered, like files
     taken together in a file browser. */
  .drag-ghost-stack {
    position: relative;
    display: block;
    flex: none;
    width: 1.75rem;
    height: 2.6rem;
  }

  .drag-ghost-many .drag-ghost-stack {
    width: calc(1.75rem + 2 * var(--space-tight));
    height: calc(2.6rem + 2 * var(--space-tight));
  }

  .drag-ghost-stack img {
    position: absolute;
    left: calc(var(--i, 0) * var(--space-tight));
    bottom: calc(var(--i, 0) * var(--space-tight));
    box-shadow: var(--shadow-rest);
    outline: 1px solid var(--color-surface);
  }

  @media (prefers-reduced-motion: reduce) {
    .list-link.is-dropped {
      animation: none;
    }
  }

  /* ---------- The list page ---------- */
  .list-page {
    min-width: 0;
  }

  /* The top row: the name on the left, the members on the right, each
     half the width; both wrap within their half (card 2.11 Nacharbeit). */
  .list-top {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-group);
    align-items: start;
  }

  .list-title {
    margin: 0;
    overflow-wrap: anywhere;
  }

  .list-name-form {
    display: grid;
    gap: var(--space-tight);
  }

  /* The owner's name field looks like the page title until touched; it
     grows with a long name instead of cutting it. */
  .list-name-form textarea.list-title-field {
    --rows: 1;
    font-family: var(--font-read);
    font-size: var(--text-page-title);
    line-height: 1.2;
    padding: var(--space-tight) var(--space-related);
    margin-inline-start: calc(-1 * var(--space-related) - 1px);
    border-color: transparent;
    background: transparent;
    max-block-size: none;
  }

  .list-name-form textarea.list-title-field:hover,
  .list-name-form textarea.list-title-field:focus {
    border-color: var(--color-line);
  }

  .list-intro-form {
    display: grid;
    gap: var(--space-tight);
    max-width: var(--width-read);
    margin-block-start: var(--space-group);
  }

  .list-page .list-intro {
    margin-block-start: var(--space-group);
  }

  .list-intro,
  .list-intro-field {
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: var(--leading-read);
    max-width: var(--width-read);
  }

  .list-intro {
    margin: 0;
    white-space: pre-line;
  }

  /* ---------- Texts of the list one writes in place ----------
     The introduction and the companion texts show their whole text, as
     members read it, never a scrolling box (Alex, feedback after stage 4).
     Filled and not being written, a pencil stands at the top right, the
     same as beside the note on a work page (collection.css). */
  .list-text {
    display: flex;
    align-items: flex-start;
    gap: var(--space-related);
  }

  .list-text textarea {
    flex: 1;
    min-width: 0;
    max-block-size: none; /* grows with the whole text */
  }

  .list-text .note-pen {
    display: none;
  }

  .list-text:not(:focus-within):has(textarea:not(:placeholder-shown)) .note-pen {
    display: inline-grid;
  }

  /* The introduction: empty, a field with its prompt; filled, it loses
     its surface and reads like text, its border only on hover and while
     writing. Padding and the outdent stay the same, so nothing jumps with
     the first letter. */
  .list-text .list-intro-field {
    padding-inline: var(--space-3);
    margin-inline-start: calc(-1 * var(--space-3) - 1px);
    transition:
      background-color var(--motion-quick) var(--ease-standard),
      border-color var(--motion-quick) var(--ease-standard);
  }

  .list-text .list-intro-field:not(:placeholder-shown) {
    background: transparent;
    border-color: transparent;
  }

  .list-text .list-intro-field:not(:placeholder-shown):hover {
    border-color: var(--color-line);
  }

  /* While writing, the border shows the field (focus, forms.css). */
  .list-text .list-intro-field:not(:placeholder-shown):focus-visible {
    border-color: var(--color-focus);
  }

  .list-intro-label {
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .list-progress {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  .list-empty {
    max-width: var(--width-read);
  }

  .list-works {
    list-style: none;
  }

  .list-item-extra {
    flex: 1 1 100%;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related);
  }

  /* The lead column of a card on a list: its number at the height of the
     title (in the reading face, as large as the title: a top-10 list at a
     glance), the grip in the middle for whoever may arrange the list. */
  .work-card.has-lead {
    padding-inline-start: 3.25rem;
  }

  /* Number and grip share one column, both centred in it (card 2.11
     Nacharbeit). The number is one step above the work's title in a list:
     the role of a page title (26-29 px), in the reading face. */
  .work-card > .work-card-number {
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: var(--space-group);
    width: 2.25rem;
    text-align: center;
    font-family: var(--font-read);
    font-size: var(--text-page-title);
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--color-text);
  }

  .work-card > .work-card-grip {
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: 50%;
    translate: 0 -50%;
    width: 2.25rem;
    color: var(--color-muted);
    cursor: grab;
    touch-action: none;
  }

  .work-card-number + .work-card-grip {
    inset-block-start: calc(50% + var(--space-related));
  }

  .list-works [data-drag-item] {
    cursor: grab;
  }

  /* The card that is being moved: the card itself follows the pointer,
     half see-through, so the line and the cards below show where it will
     land (card 2.11 Nacharbeit). */
  .list-works .work-card.is-moving {
    z-index: 10;
    cursor: grabbing;
    opacity: 0.6;
    background: var(--color-surface);
    box-shadow: var(--shadow-floating);
    border-radius: var(--radius-m);
    transition: none;
  }

  /* After the drop the cards glide to their new places (drag.js, FLIP),
     in human time: --motion-slow. */
  .list-works .work-card.is-reordering {
    position: relative;
    z-index: 5;
    transition: translate var(--reorder-time, var(--motion-slow)) var(--ease-standard);
  }

  /* The card that was moved lights up softly once, so the eye finds it. */
  .list-works .work-card.is-just-moved {
    z-index: 6;
  }

  .list-works .work-card.is-just-moved::before {
    opacity: 1;
    background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
    transition: opacity var(--motion-slow) var(--ease-standard);
  }


  .list-works .is-drop-before {
    box-shadow: inset 0 2px 0 var(--color-accent);
  }

  .list-works .is-drop-after {
    box-shadow: inset 0 -2px 0 var(--color-accent);
  }

  .list-note-form {
    flex: 1 1 16rem;
    min-width: 0;
    display: grid;
    gap: var(--space-tight);
  }

  .list-note,
  .list-note-field {
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: var(--leading-read);
  }

  .list-note {
    margin: 0;
    flex: 1 1 100%;
  }

  /* The companion text looks like text until touched. Padding and border
     stay the same on hover, only the border shows, so nothing jumps
     (card 2.11 Nacharbeit); the negative margin keeps the words in line
     with the title above. */
  .list-note-form textarea.list-note-field {
    --rows: 1;
    border-color: transparent;
    background: transparent;
    padding-inline: var(--space-3);
    margin-inline-start: calc(-1 * var(--space-3) - 1px);
  }

  .list-note-form textarea.list-note-field:hover,
  .list-note-form textarea.list-note-field:focus {
    border-color: var(--color-line);
  }

  /* On the cards a pencil on each filled text would be restless with many
     works: with a mouse it shows while the pointer is over the card; on a
     touch screen (no hovering) always (Alex, feedback after stage 4). */
  @media (hover: hover) {
    .list-note-form .note-pen {
      opacity: 0;
      transition: opacity var(--motion-quick) var(--ease-standard);
    }

    .work-card:hover .list-note-form .note-pen,
    .list-note-form .note-pen:focus-visible {
      opacity: 1;
    }
  }

  .list-danger {
    margin-block-start: var(--space-section);
  }

  /* "In folgenden Listen: …" on the work page (card 2.23). Symbol and
     label are one word (tight); the names follow a little apart, as a
     field follows its label (related). */
  .work-in-lists {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--space-related);
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  /* The words carry the baseline the names share; the symbol is centred. */
  .work-in-lists-label {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-tight);
  }

  .work-in-lists-label .icon {
    align-self: center;
  }

  /* "Speichern in …": the group of coffee lists. */
  .save-group {
    margin: var(--space-3) var(--space-2) var(--space-tight);
    font-size: var(--text-fine);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .dialog .list-new {
    margin-block-start: var(--space-tight);
  }

  @media (max-width: 47.99rem) {
    .list-top {
      grid-template-columns: minmax(0, 1fr);
    }

    .list-members {
      justify-content: flex-start;
    }

    .with-lists {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--space-group);
    }

    /* On a phone the way back and "Listen" share one line. */
    .with-lists-side {
      position: static;
      display: flex;
      align-items: center;
      gap: var(--space-related);
    }

    /* Without a way back, "Listen" stands at the right of the page's
       first line (its title), as before, and takes no line of its own. */
    .with-lists:not([data-back-way]) {
      position: relative;
    }

    .with-lists:not([data-back-way]) > .with-lists-side {
      position: absolute;
      inset-block-start: 0;
      inset-inline-end: 0;
      z-index: 1;
    }

    /* The first line keeps its right end free for it. */
    .with-lists:not([data-back-way]) > .with-lists-main > :first-child {
      padding-inline-end: calc(var(--target-min) * 2.25);
    }

    .with-lists-back {
      min-width: 0;
    }

    /* The sheet from below, as the dialogs on a phone (dialog.css). */
    .list-side:not(:popover-open) {
      display: none;
    }

    .list-side:popover-open {
      position: fixed;
      inset: auto 0 0;
      width: 100%;
      max-height: 80dvh;
      overflow: hidden;
      padding: var(--space-6) var(--gutter) calc(var(--space-5) + env(safe-area-inset-bottom));
      border-radius: var(--radius-l) var(--radius-l) 0 0;
      background: var(--color-surface);
      box-shadow: var(--shadow-floating);
      gap: var(--space-group);
      animation: lists-up var(--motion-gentle) var(--ease-enter);
    }

    .list-side::backdrop {
      background: var(--color-scrim);
    }

    .list-side:popover-open::before {
      content: "";
      position: absolute;
      top: var(--space-2);
      left: 50%;
      translate: -50% 0;
      width: 2.5rem;
      height: 4px;
      border-radius: var(--radius-full);
      background: var(--color-line);
    }

    .list-side-title {
      font-family: var(--font-read);
      font-size: var(--text-heading);
      font-weight: var(--weight-medium);
      color: var(--color-text);
    }

    .list-side-body {
      max-block-size: none;
    }

    .list-side-open.btn {
      display: inline-flex;
      margin-inline-start: auto;
    }

    @keyframes lists-up {
      from {
        translate: 0 100%;
      }
    }
  }
}

/* ---------- Shared lists (card 2.11) ---------- */
@layer components {
  .list-share {
    margin-block: var(--space-group) var(--space-component);
  }

  .list-share-form {
    display: grid;
    gap: var(--space-group);
  }

  /* Numbers, comments and who sees the list: one quiet row. */
  .list-settings-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-related) var(--space-group);
  }

  /* Buttons first, the words below them: the words may grow with the
     level, the row keeps its width (card 2.11 Nacharbeit). In the
     collection the visibility row keeps words and buttons side by side. */
  .list-visibility {
    display: grid;
    justify-items: start;
    gap: var(--space-tight);
    /* The switches stay on the left, who sees the list goes to the right. */
    margin-inline-start: auto;
  }

  .list-visibility-text {
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .list-members {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: var(--space-related) var(--space-group);
    padding-block-start: var(--space-related);
  }

  /* Members as overlapping small pictures. */
  .avatar-stack {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
  }

  .avatar-stack li + li {
    margin-inline-start: calc(-1 * var(--space-related));
  }

  .avatar-stack .avatar {
    box-shadow: 0 0 0 2px var(--color-bg);
  }

  .list-talk {
    display: grid;
    gap: var(--space-group);
    margin-block-start: var(--space-section);
    max-width: var(--width-read);
  }

  .list-talk h2 {
    margin: 0;
    font-size: var(--text-heading);
  }

  .list-body .talk-toggle {
    flex: 1 1 100%;
  }

  /* The dialog "+ Freunde": rows with picture and name, a tick on the right. */
  .member-rows {
    list-style: none;
    margin: var(--space-group) 0 0;
    padding: 0;
    display: grid;
    gap: var(--space-tight);
    max-block-size: 22rem;
    overflow-y: auto;
  }

  .member-rows form {
    margin: 0;
  }

  .member-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    min-height: var(--target-min);
    padding: var(--space-related) var(--space-3);
    border: 0;
    border-radius: var(--radius-m);
    background: transparent;
    color: var(--color-text);
    font: inherit;
    font-size: var(--text-control);
    text-align: start;
    cursor: pointer;
  }

  .member-row:hover {
    background: color-mix(in srgb, var(--color-text) 6%, transparent);
  }

  .member-row.is-member {
    background: color-mix(in srgb, var(--color-accent) 11%, var(--color-surface));
  }

  .member-name {
    flex: 1;
    min-width: 0;
  }

  .member-tick {
    color: var(--color-accent);
    opacity: 0;
  }

  .member-row.is-member .member-tick {
    opacity: 1;
  }

  .shared-lists {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-tight);
  }
}

/* css/components/notify.css */
/* notify.css – Settings → Benachrichtigungen (card 2.15): the table of
   events, grouped, with a column of switches for mail and one for the
   app. It stays a table on a phone: the two switch columns are narrow, the
   event takes the rest. Rank comes from type, not from lines. */

@layer components {
  .notify-table-wrap {
    margin-block: var(--space-section) var(--space-group);
  }

  .notify-lock-note {
    margin: 0 0 var(--space-group);
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .notify-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-control);
  }

  .notify-table th,
  .notify-table td {
    padding-block: var(--space-related);
    text-align: start;
    vertical-align: middle;
  }

  .notify-table thead th {
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .notify-table .notify-way {
    width: 4.5rem;
    text-align: center;
  }

  .notify-table td.notify-way .checkbox {
    justify-content: center;
  }

  .notify-group {
    padding-block-start: var(--space-group);
    font-size: var(--text-card-title);
    font-weight: var(--weight-semibold);
  }

  .notify-event {
    font-weight: var(--weight-regular);
    padding-inline-end: var(--space-group);
  }

  /* Held by a lock: grey and not usable, in its position. */
  .checkbox input[role="switch"]:disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }

  @media (max-width: 29.99rem) {
    .notify-table .notify-way {
      width: 3.5rem;
    }
  }
}

/* css/components/passkeys.css */
/*
 * passkeys.css – passkeys in Settings → Security, the offer after
 * signing in, and the passkey button on the sign-in page (card 2.2).
 */

@layer components {
  .passkeys {
    margin-block: var(--space-siblings) var(--space-section);
  }

  .passkeys > .btn {
    margin-block-start: var(--space-group);
  }

  /* Renaming opens in place, under the name. */
  .passkey-rename {
    margin-block-start: var(--space-tight);
  }

  .passkey-rename summary {
    list-style: none;
    font-size: var(--text-meta);
  }

  .passkey-rename summary::-webkit-details-marker {
    display: none;
  }

  .inline-form {
    display: flex;
    gap: var(--space-related);
    align-items: center;
    margin-block-start: var(--space-related);
  }

  .inline-form input[type="text"] {
    min-height: var(--control-height-compact);
    padding: 0 var(--space-3);
    font-size: var(--text-meta);
  }

  /* Asking to confirm it is you: set apart, but calm. */
  .confirm-panel {
    display: grid;
    gap: var(--space-related);
    margin-block-start: var(--space-group);
    padding: var(--space-5);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-l);
    background: var(--color-surface);
  }

  .confirm-panel h3 {
    margin: 0;
    font-size: var(--text-card-title);
  }

  .confirm-panel .prose,
  .confirm-panel .stack-form {
    margin: 0;
  }

  .offer-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-group);
    align-items: center;
    margin-block: var(--space-group);
  }

  .passkey-login {
    display: grid;
    justify-items: start;
    gap: var(--space-related);
    margin-block-end: var(--space-group);
  }
}

/* css/components/person-link.css */
/* person-link.css – names of people who made a work, each a quiet link
   (components/person_link.templ; Alex's wish, 08.10.2026). They read as
   text. With a mouse, hovering shows the link: the accent colour and a
   fine underline with some air, faded in. Without a mouse (phone, tablet)
   there is no hovering, so a faint dotted line says they can be tapped.
   On cards and tiles they lie above the link that covers the whole card
   (position and z-index), so a tap on a name searches for the name. */

@layer components {
  .person-link {
    position: relative;
    z-index: 1;
    color: inherit;
    text-decoration-line: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: transparent;
    text-underline-offset: 0.22em;
    transition:
      color var(--motion-quick) var(--ease-standard),
      text-decoration-color var(--motion-quick) var(--ease-standard);
  }

  @media (hover: hover) {
    .person-link:hover {
      color: var(--color-accent);
      text-decoration-color: currentColor;
    }
  }

  @media (hover: none) {
    .person-link {
      text-decoration-style: dotted;
      text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
    }
  }
}

/* css/components/person.css */
/* person.css – the person page (card 4.5a, personen-seiten.md).

   Since 13.10.2026 (the initiator: modern like the work page) it borrows
   the work page's parts and ladder: the name in the size of a work's
   title (.work-title), the name in the original as the original title
   (.work-original); a component below, portrait and facts in two equal
   columns (the facts: the line of years and roles as .work-meta, then
   word and value as the work's credits, then "Anderswo"); a component
   below, the short biography as body text (.body-text); the awards to
   open; then the roles, each a heading with its count a group above its
   tiles, the roles a section apart. */

@layer components {
  .person-head {
    margin-block-end: var(--space-component);
  }

  .person-title {
    overflow-wrap: anywhere;
  }

  /* Portrait and facts side by side, the same width each; without a
     portrait the facts take the whole width. */
  .person-figure {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-component);
    align-items: start;
    margin-block-start: var(--space-component);
  }

  .person-figure:not(:has(.person-portrait)) {
    grid-template-columns: minmax(0, 1fr);
  }

  .person-portrait {
    display: grid;
    gap: var(--space-related);
    margin: 0;
  }

  .person-portrait img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    border-radius: var(--radius-m);
    background: var(--color-surface);
    box-shadow: var(--shadow-rest);
  }

  .person-portrait figcaption {
    font-size: var(--text-fine);
    color: var(--color-muted);
    text-wrap: pretty;
  }

  .person-portrait figcaption a,
  .person-source a,
  .person-elsewhere a {
    color: inherit;
  }

  .person-info {
    display: grid;
    gap: var(--space-group);
    justify-items: start;
    min-width: 0;
  }

  /* The parts of the work page start below a title; here they start the
     column. */
  .person-info .person-meta,
  .person-info .person-facts {
    margin: 0;
  }

  .person-elsewhere {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .person-elsewhere .separator {
    margin-inline: var(--space-1);
  }

  /* While Wikidata and Wikipedia are being asked: quiet lines. */
  .person-info .description-skeleton {
    width: 100%;
  }

  /* ---------- The short biography: body text, folded ---------- */
  .person-bio {
    display: grid;
    gap: var(--space-related);
  }

  .person-source {
    margin: 0;
    font-size: var(--text-fine);
    color: var(--color-muted);
  }

  /* ---------- Awards and nominations, to open ---------- */
  .person-awards {
    margin-block: var(--space-component) 0;
    max-width: var(--width-read);
  }

  .person-awards > summary {
    display: flex;
    align-items: center;
    gap: var(--space-related);
    min-height: var(--target-min);
    font-size: var(--text-control);
    font-weight: var(--weight-semibold);
    cursor: pointer;
    list-style: none;
  }

  .person-awards > summary::-webkit-details-marker {
    display: none;
  }

  .person-awards > summary .icon {
    color: var(--color-muted);
    transition: rotate var(--motion-quick) var(--ease-standard);
  }

  .person-awards[open] > summary .icon {
    rotate: 90deg;
  }

  .person-awards-count,
  .person-course-count,
  .person-chips .choice-count {
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-regular);
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  .person-awards-body {
    display: grid;
    gap: var(--space-group);
    padding-block-start: var(--space-related);
  }

  .person-awards-body section {
    display: grid;
    gap: var(--space-related);
  }

  .person-awards-label {
    margin: 0;
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-semibold);
    color: var(--color-muted);
  }

  .person-awards-list {
    display: grid;
    gap: var(--space-tight);
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--text-meta);
  }

  .person-awards-list li {
    display: grid;
    grid-template-columns: 3rem minmax(0, 1fr);
    gap: var(--space-related);
  }

  .person-awards-year {
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  /* ---------- The works: one course (decided on 13.10.2026, preview
     daizee-personen.html round 4) ----------
     "Werke" and how many, the role filter a group below, then the tiles,
     four a row (two on a phone), each with its own height: the lines
     below the title follow it at once, no line kept free for a second line
     of the title. */
  .person-course {
    margin-block-start: var(--space-section);
  }

  /* "Werke" and how many on the left, "Auswählen" on the right. */
  .person-course-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-related) var(--space-group);
  }

  .person-course-title {
    display: flex;
    align-items: baseline;
    gap: var(--space-related);
    margin: 0;
    font-size: var(--text-heading);
  }

  /* The filters: the roles a group below the heading, the other filters
     (kind, "Noch nicht gesehen", "Von Freunden geliebt") right below them
     ("related"), as one group of filters. The chips wrap on a computer; on
     a phone each row is a row to swipe, as the collection's (.chip-row,
     add.css). */
  .person-chips {
    display: flex;
    gap: var(--space-related);
    margin-block-start: var(--space-group);
  }

  .person-chips + .person-chips {
    margin-block-start: var(--space-related);
  }

  @media (min-width: 48rem) {
    .person-chips {
      flex-wrap: wrap;
    }
  }

  .person-chips .choice {
    text-decoration: none; /* a chip, though a link (as the tags') */
  }

  /* With these filters nothing is left: one calm line, the way out is
     the chips above. */
  .person-none {
    margin: var(--space-group) 0 0;
    font-family: var(--font-read);
    font-size: var(--text-read);
    color: var(--color-muted);
  }

  .person-chips .choice-count {
    margin-inline-start: var(--space-tight);
  }

  .tile-grid.person-grid {
    position: relative; /* holds the marker for the next step (feed.css) */
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .person-grid .tile {
    grid-row: auto;
    grid-template-rows: auto auto 1fr;
    align-content: start;
  }

  .person-grid .tile-media {
    align-self: start;
  }

  /* Below the poster two groups (design principle 10, tokens 3a): the
     title with year and kind "tight" below it; a step apart ("related"),
     the person's part: roles, the character, the viewer's own status
     ("Geliebt" in the accent). Two type roles only: the title, and the
     facts (meta, muted). */
  .tile-person .tile-below {
    grid-row: 3;
    display: grid;
    align-content: start;
    gap: var(--space-related);
    margin: calc(var(--space-tight) - var(--space-related)) 0 0; /* title -> year: tight */
    font-size: var(--text-meta);
    line-height: var(--leading-ui);
    color: var(--color-muted);
  }

  .tile-person .tile-below p {
    margin: 0;
  }

  .tile-person .tile-year {
    font-variant-numeric: tabular-nums;
  }

  .tile-person .tile-part {
    display: grid;
  }

  .tile-status[data-loved] {
    color: var(--color-accent);
  }

  /* While the sources are asked for all of the person's works (card
     4.5b): a heading and two rows of tiles in the shape of the works to
     come, pulsing like the description's lines (work.css). */
  .person-course-skeleton {
    display: block;
    inline-size: 9rem;
    block-size: var(--text-heading);
    border-radius: var(--radius-s);
    background: color-mix(in srgb, var(--color-text) 8%, transparent);
    animation: description-pulse 1.6s var(--ease-standard) infinite alternate;
  }

  .tile-skeleton {
    display: grid;
    gap: var(--space-related);
    align-content: start;
  }

  .tile-skeleton > span {
    display: block;
    background: color-mix(in srgb, var(--color-text) 8%, transparent);
    animation: description-pulse 1.6s var(--ease-standard) infinite alternate;
  }

  .tile-skeleton-image {
    aspect-ratio: 2 / 3;
    border-radius: var(--radius-poster);
  }

  .tile-skeleton-title {
    inline-size: 70%;
    block-size: var(--text-read);
    border-radius: var(--radius-s);
  }

  .tile-skeleton:nth-child(4n + 2) > span { animation-delay: 0.2s; }
  .tile-skeleton:nth-child(4n + 3) > span { animation-delay: 0.4s; }
  .tile-skeleton:nth-child(4n + 4) > span { animation-delay: 0.6s; }

  @media (prefers-reduced-motion: reduce) {
    .person-course-skeleton,
    .tile-skeleton > span {
      animation: none;
    }
  }

  /* ---------- The timeline (timeline.js) ----------
     At the right edge beside the content, two rows high (since
     16.10.2026, preview round 5), at the height where the left column
     sticks: the year of each row's first work at the middle of its poster
     passes along it while scrolling. Soft at the top as before, a long
     fade at the bottom from a little above the second year (the script
     sets both points). Only where there is room beside the content. */
  .timeline {
    position: fixed;
    z-index: 3;
    width: 6.5rem;
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--motion-gentle) var(--ease-standard);
    --fade-in: 11%;
    --fade-out: 40%;
    mask-image: linear-gradient(to bottom, transparent, black var(--fade-in), black var(--fade-out), transparent);
  }

  .timeline[data-on] {
    opacity: 1;
  }

  .timeline-rail {
    position: absolute;
    inset-block: 0;
    inset-inline-end: 0;
    width: 1px;
    background: color-mix(in srgb, var(--color-text) 30%, transparent);
  }

  .timeline-year {
    position: absolute;
    inset-inline-end: 0;
    display: flex;
    align-items: center;
    gap: var(--space-related);
    translate: 0 -50%;
    font-size: var(--text-meta);
    font-variant-numeric: tabular-nums;
    color: var(--color-muted);
  }

  .timeline-year::after {
    content: "";
    width: var(--space-group);
    height: 1px;
    background: var(--color-line);
  }

  @media (max-width: 74.99rem) {
    .timeline {
      display: none;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .timeline {
      transition: none;
    }
  }

  /* A phone: the name in the size of a page title (as the work page);
     portrait and facts still side by side, each word above its value. */
  @media (max-width: 47.99rem) {
    .tile-grid.person-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .person-title {
      font-size: var(--text-xl);
    }

    .person-head .work-original {
      font-size: var(--text-read);
    }

    .person-figure {
      gap: var(--space-group);
      margin-block-start: var(--space-group);
    }

    .person-facts {
      grid-template-columns: minmax(0, 1fr);
      row-gap: 0;
    }

    .person-facts dd {
      margin-block-end: var(--space-related);
    }
  }
}

/* css/components/pick.css */
/* pick.css – choosing many works at once (card 2.13b, sammlung.md 4): the
   round tick on a picture, the chosen works, and the bar at the bottom
   (pick.js keeps the choice). */

@layer components {
  /* ---------- The tick: top left on the picture ---------- */
  /* The tick keeps its place on the picture: in a tile it sits inside the
     card, whose padding grows when it opens, so it moves along with it,
     in the same time and curve (tile.css), and stays at the picture's
     corner. */
  .pick {
    --pick-inset: var(--space-related);
    position: absolute;
    z-index: 2;
    top: var(--pick-inset);
    left: var(--pick-inset);
    display: inline-grid;
    place-items: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 1.5px solid var(--color-on-image-soft);
    border-radius: var(--radius-full);
    background: var(--color-scrim);
    color: var(--color-on-image-soft);
    cursor: pointer;
    opacity: 0;
    transition:
      opacity var(--motion-quick) var(--ease-standard),
      background-color var(--motion-quick) var(--ease-standard),
      border-color var(--motion-quick) var(--ease-standard),
      top var(--motion-quick) var(--ease-exit),
      left var(--motion-quick) var(--ease-exit);
  }

  .pick .icon {
    width: 1rem;
    height: 1rem;
    stroke-width: 2;
    opacity: 0; /* an empty ring until chosen */
  }

  /* A target of 44 px around the small ring. */
  .pick::after {
    content: "";
    position: absolute;
    inset: 50% auto auto 50%;
    translate: -50% -50%;
    width: var(--target-min);
    height: var(--target-min);
  }

  @media (hover: hover) {
    [data-pick-work]:hover .pick {
      opacity: 1;
    }
  }

  .pick:focus-visible,
  .is-picking .pick {
    opacity: 1;
  }

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

  .is-picked .pick {
    border-color: var(--color-accent);
    background: var(--color-accent);
    color: var(--color-on-accent);
  }

  .is-picked .pick .icon {
    opacity: 1;
  }

  /* In an open tile the card has its padding around the picture: the
     tick moves by the same, with the card's timing (tile.css). */
  .tile:has(:focus-visible) .pick {
    --pick-inset: calc(var(--tile-lift) + var(--space-related));
    transition-timing-function: var(--ease-standard), var(--ease-standard), var(--ease-standard), var(--ease-enter), var(--ease-enter);
  }

  @media (hover: hover) {
    .tile:hover .pick {
      --pick-inset: calc(var(--tile-lift) + var(--space-related));
      transition-delay: 0s, 0s, 0s, var(--tile-intent), var(--tile-intent);
      transition-timing-function: var(--ease-standard), var(--ease-standard), var(--ease-standard), var(--ease-enter), var(--ease-enter);
    }

    .is-picking .tile:hover .pick {
      --pick-inset: var(--space-related);
    }
  }

  /* In the list the tick sits on the poster (the card's first column). */
  .work-card > .pick {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    align-self: start;
    position: relative;
    top: var(--space-tight);
    left: var(--space-tight);
  }

  /* ---------- Chosen works ---------- */
  .is-picked .work-card-image img,
  .is-picked .work-card-image-empty,
  .is-picked .tile-image img,
  .is-picked .tile-image-empty {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }

  /* While choosing, a click chooses: the tiles do not open, the cards
     show no hover surface that promises a way to the work. */
  .is-picking [data-pick-work] {
    cursor: pointer;
  }

  @media (hover: hover) {
    .is-picking .tile:hover .tile-card {
      top: 0;
      inset-inline: 0;
      grid-template-rows: auto 0fr;
      padding: 0;
      background: transparent;
      box-shadow: none;
    }

    .is-picking .tile:hover .tile-more {
      opacity: 0;
      visibility: hidden;
    }
  }

  /* ---------- The bar at the bottom (placed like the short note) ---------- */
  .pick-bar {
    position: fixed;
    left: 50%;
    bottom: calc(var(--space-siblings) + env(safe-area-inset-bottom));
    translate: -50% 0;
    z-index: 40; /* below the short note, which says what happened */
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-related) var(--space-group);
    width: max-content;
    max-width: min(46rem, 100% - 2 * var(--gutter));
    margin: 0;
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
    border-radius: var(--radius-m);
    background: var(--color-surface);
    box-shadow: var(--shadow-floating);
    outline: 1px solid var(--color-line);
    font-size: var(--text-meta);
    animation: pick-bar-in var(--motion-gentle) var(--ease-enter);
  }

  .pick-bar[hidden] {
    display: none;
  }

  .pick-count {
    margin: 0;
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
  }

  .pick-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related);
  }

  .pick-levels {
    display: flex;
    gap: 0;
    padding-inline-end: var(--space-related);
    border-inline-end: 1px solid var(--color-line);
  }

  .pick-levels .icon-button[disabled] {
    opacity: 0.35;
    cursor: not-allowed;
  }

  /* The last works stay reachable above the bar. */
  .is-picking :is(.collection-page, .person-page) {
    padding-block-end: calc(var(--space-page-end) + 4rem);
  }

  @keyframes pick-bar-in {
    from {
      opacity: 0;
      translate: -50% var(--space-2);
    }
  }

  /* ---------- The results head: the number above, "Auswählen" and the
     tools on one line below it (initiator, 06.10.2026) ---------- */
  .results-head-pick > .results-count {
    flex-basis: 100%;
  }

  .results-head-pick > .results-tools {
    margin-inline-start: auto;
    justify-content: flex-end;
  }

  /* Pressed while choosing: a second tap ends it. */
  .pick-start[aria-pressed="true"] {
    background: color-mix(in srgb, var(--color-accent) 11%, transparent);
  }

  /* ---------- "In eine Liste legen": one tap per list ---------- */
  .pick-lists {
    display: grid;
    margin: 0 calc(-1 * var(--space-2));
  }

  .pick-lists form {
    margin: 0;
  }

  .pick-list-row {
    width: 100%;
    border: 0;
    background: transparent;
    color: var(--color-text);
    font: inherit;
    text-align: start;
    cursor: pointer;
  }

  .pick-list-row .icon {
    color: var(--color-muted);
  }

  @media (max-width: 47.99rem) {
    .pick-bar {
      width: calc(100% - 2 * var(--gutter));
    }
  }
}

/* css/components/poster-pick.css */
/* poster-pick.css – a work's poster to choose, not to open
   (components.PosterPick, card 2.17; approved in the onboarding preview).
   The picture is the tile's (tile.css: .tile-image), the daisy and its
   blooming are the status buttons' (collection.css). Here: the grid, the
   poster as one big target, the daisy on its corner, the warm wash when
   just loved, the small title below. */

@layer components {
  .poster-picks {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-group) var(--space-related);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .poster-pick {
    min-width: 0;
  }

  .poster-pick-form {
    margin: 0;
  }

  .poster-pick-choice {
    display: grid;
    align-content: start;
    gap: var(--space-related);
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-align: start;
    text-decoration: none;
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
  }

  .poster-pick-image {
    position: relative;
    border-radius: var(--radius-poster);
    transition: translate var(--motion-quick) var(--ease-standard);
  }

  .poster-pick-image img {
    transition: box-shadow var(--motion-quick) var(--ease-standard);
  }

  .poster-pick-choice:hover .poster-pick-image {
    translate: 0 -2px;
  }

  .poster-pick-choice:hover .poster-pick-image img {
    box-shadow: var(--shadow-raised);
  }

  .poster-pick-choice:focus-visible {
    outline: none;
  }

  .poster-pick-choice:focus-visible .poster-pick-image {
    outline: 2px solid var(--color-focus);
    outline-offset: 3px;
  }

  /* Shapes: an album is square, a video wide (as on tiles). */
  .poster-pick-square .tile-image img,
  .poster-pick-square .tile-image-empty {
    aspect-ratio: 1 / 1;
  }

  .poster-pick-wide .tile-image img,
  .poster-pick-wide .tile-image-empty {
    aspect-ratio: 16 / 9;
  }

  /* The daisy on the picture's corner: outlined, or filled when loved. */
  .poster-pick-daisy {
    z-index: 1;
    align-self: end;
    justify-self: end;
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    margin: var(--space-2);
    border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-surface) 88%, transparent);
    color: var(--color-text);
    box-shadow: var(--shadow-rest);
  }

  /* Just loved: a warm wash over the picture fades (welcome.js adds .bloom,
     which also lets the daisy bloom, collection.css). */
  .poster-pick-choice.bloom .poster-pick-image::after {
    content: "";
    grid-area: 1 / 1;
    border-radius: var(--radius-poster);
    background: var(--color-moment);
    pointer-events: none;
    animation: poster-pick-wash calc(var(--motion-slow) * 2.4) var(--ease-exit) both;
  }

  @keyframes poster-pick-wash {
    0%,
    30% {
      opacity: 0.28;
    }
    100% {
      opacity: 0;
    }
  }

  .poster-pick-title {
    display: -webkit-box;
    overflow: hidden;
    font-size: var(--text-meta);
    line-height: 1.3;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }

  /* While drag.js carries it, the poster waits faintly in its place. */
  .poster-pick.is-dragging {
    opacity: 0.35;
  }

  @media (max-width: 47.99rem) {
    .poster-picks {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }
  }
}

/* css/components/profile.css */
/*
 * profile.css – profile page, profile settings, activation (card 2.1).
 * The picture is round and calm; without one, the first letter of the
 * name stands in its place.
 */

@layer components {
  .profile-head {
    display: flex;
    align-items: center;
    gap: var(--space-group);
    margin-block-end: var(--space-group);
  }

  .profile-head > div {
    flex: 1;
    min-width: 0;
  }

  .profile-edit {
    white-space: nowrap;
    margin-block-start: var(--space-related);
    margin-inline-start: calc(-1 * var(--space-2));
    gap: var(--space-2);
  }

  .profile-edit .icon {
    width: 1rem;
    height: 1rem;
  }

  .profile-head h1 {
    margin: 0;
  }

  .profile-head .note-text {
    margin: var(--space-tight) 0 0;
  }

  .profile-bio {
    white-space: pre-line; /* the person's own line breaks */
  }

  .profile-picture {
    inline-size: 6rem;
    block-size: 6rem;
    flex: none;
    border-radius: var(--radius-full);
    object-fit: cover;
    background: var(--color-line);
  }

  .profile-picture-empty {
    display: grid;
    place-items: center;
    font-family: var(--font-read);
    font-size: var(--text-xl);
    color: var(--color-muted);
  }

  /* ---------- Choosing a picture (avatar.js) ---------- */
  .picture-area {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-3) var(--space-5);
    align-items: center;
    margin-block-end: var(--space-6);
  }

  .picture-form {
    display: grid;
    gap: var(--space-2);
  }

  /* The field to drop a picture on, or to click. */
  .picture-drop {
    display: grid;
    gap: var(--space-1);
    padding: var(--space-4) var(--space-5);
    border: 1.5px dashed var(--color-line);
    border-radius: var(--radius-l);
    background: var(--color-surface);
    cursor: pointer;
    font-weight: 400;
    transition:
      border-color var(--motion-quick) var(--ease-standard),
      background var(--motion-quick) var(--ease-standard);
  }

  .picture-drop:hover,
  .picture-drop.is-over {
    border-color: var(--color-accent);
  }

  .picture-drop.is-over {
    background: var(--color-bg);
  }

  .picture-drop-title {
    font-weight: 600;
    color: var(--color-accent);
  }

  .picture-drop .note-text {
    margin: 0;
  }

  /* The real file field stays in the page for keyboards and screen
     readers; the dashed field shows where its focus is. */
  .picture-file {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
  }

  .picture-form:focus-within .picture-drop {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  /* With avatar.js the picture is saved from the preview. */
  .picture-enhanced .picture-no-js {
    display: none;
  }

  .picture-remove {
    grid-column: 2;
  }

  /* The preview: a square with the round part in it. */
  .cropper {
    width: min(26rem, 100% - 2 * var(--space-4));
    display: grid;
    gap: var(--space-4);
  }

  .cropper:not([open]) {
    display: none;
  }

  .cropper-canvas {
    --cropper-shade: var(--color-scrim);
    inline-size: 100%;
    aspect-ratio: 1;
    border-radius: var(--radius-m);
    background: var(--color-line);
    cursor: grab;
    touch-action: none; /* fingers move the picture, not the page */
  }

  .cropper-canvas:active {
    cursor: grabbing;
  }

  .cropper-zoom {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-3);
    align-items: center;
  }

  .cropper-zoom input {
    accent-color: var(--color-accent);
  }

  .cropper .note-text {
    margin: 0;
  }

  .cropper-actions {
    display: flex;
    gap: var(--space-5);
    align-items: center;
  }

  .cropper[aria-busy="true"] {
    cursor: progress;
  }

  .choice-row {
    border: 0;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-5);
  }

  .choice-row legend {
    margin-block-end: var(--space-related);
    inline-size: 100%;
  }

  .choice-row label {
    font-weight: var(--weight-regular);
    font-size: var(--text-control);
    display: inline-flex;
    gap: var(--space-3);
    align-items: center;
    min-height: var(--target-min);
  }

  .form-success {
    color: var(--color-success);
    font-size: var(--text-s);
    margin: 0;
  }

  @media (max-width: 30rem) {
    .picture-area {
      grid-template-columns: 1fr;
    }

    .picture-remove {
      grid-column: 1;
    }
  }
}

/* The way into someone's collection, under the head (card 2.13c): a way,
   so a link, with what one may see of it. */
@layer components {
  .profile-collection {
    display: inline-flex;
    align-items: center;
    gap: var(--space-related);
    min-height: var(--target-min);
    margin-block-start: var(--space-group);
    font-weight: var(--weight-semibold);
    text-decoration: none;
  }

  .profile-collection-count {
    font-weight: var(--weight-regular);
    color: var(--color-muted);
  }
}

/* css/components/push.css */
/* push.css – push notifications (card 3.4): the question whether daizee may
   send them (after recommending, in the onboarding), and their place in
   Settings → Benachrichtigungen with the quiet hours. */

@layer components {
  .push-offer {
    display: grid;
    gap: var(--space-related);
  }

  .push-offer[hidden] {
    display: none;
  }

  .push-offer-text {
    margin: 0;
  }

  .push-offer-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-related);
  }

  .push-offer-actions[hidden] {
    display: none;
  }

  /* In the short note after recommending: a second line below the first. */
  .toast:has(.push-offer:not([hidden])) {
    flex-wrap: wrap;
    row-gap: var(--space-related);
  }

  .toast .push-offer {
    flex-basis: 100%;
    padding-block-end: var(--space-1);
  }

  /* In the onboarding, below the offer to install. */
  .welcome-step .push-offer {
    margin-block-start: var(--space-component);
    justify-items: start;
    max-width: var(--width-read);
  }

  /* ---------- Settings → Benachrichtigungen ---------- */
  .push-settings {
    margin-block-start: var(--space-section);
  }

  .push-settings h2 {
    margin: 0;
    font-size: var(--text-heading);
  }

  .quiet-times {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related) var(--space-group);
    margin: var(--space-related) 0 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .quiet-times label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-related);
    font-weight: var(--weight-regular);
  }

  /* A field like every other, as wide as its time. */
  .quiet-times .quiet-time {
    width: auto;
    min-block-size: var(--target-min);
    padding-block: var(--space-2);
    font-variant-numeric: tabular-nums;
  }

  /* The device, bundling and the quiet hours are one list (three forms,
     one line between). */
  .push-settings > .settings-list {
    margin-block-end: 0;
  }

  .quiet-form .settings-list {
    margin-block-start: 0;
    border-top: 0;
  }

  .quiet-form:has(#quiet-on:not(:checked)) .quiet-times {
    opacity: 0.5;
  }
}

/* css/components/range.css */
/* range.css – the slider with stops (card 2.14c; approved in the system
   preview, section 7, on 06.10.2026).

   The browser's own range input, so the arrow keys, the finger and screen
   readers come with it. A thin line, in the accent up to the thumb; below
   it a fine mark at every stop, right under the thumb's centre there. The
   thumb's centre travels from half a thumb in to half a thumb before the
   end, so stops and fill are placed on exactly that way. The level comes
   as data-val (set by the page, kept up by the island while sliding): no
   inline style, as the content security policy wants. Hit area 44 px. */

@layer components {
  .range {
    --val: 2;
    --stops: 4;
    --thumb: 1.375rem;
    --tick: var(--color-line);
    --at: calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--val) / var(--stops));
    --mark-y: calc(50% + 0.875rem);
    display: block;
    width: 100%;
    height: var(--target-min);
    margin: 0;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    background:
      linear-gradient(var(--tick), var(--tick)) calc(var(--thumb) / 2 - 1px) var(--mark-y) / 2px 0.375rem no-repeat,
      linear-gradient(var(--tick), var(--tick)) calc(var(--thumb) / 2 - 1px + (100% - var(--thumb)) * 0.25) var(--mark-y) / 2px 0.375rem no-repeat,
      linear-gradient(var(--tick), var(--tick)) calc(var(--thumb) / 2 - 1px + (100% - var(--thumb)) * 0.5) var(--mark-y) / 2px 0.375rem no-repeat,
      linear-gradient(var(--tick), var(--tick)) calc(var(--thumb) / 2 - 1px + (100% - var(--thumb)) * 0.75) var(--mark-y) / 2px 0.375rem no-repeat,
      linear-gradient(var(--tick), var(--tick)) calc(100% - var(--thumb) / 2 - 1px) var(--mark-y) / 2px 0.375rem no-repeat;
  }

  .range[data-val="0"] { --val: 0; }
  .range[data-val="1"] { --val: 1; }
  .range[data-val="2"] { --val: 2; }
  .range[data-val="3"] { --val: 3; }
  .range[data-val="4"] { --val: 4; }

  /* The focus shows as a ring around the thumb (below), not around the
     whole line. */
  .range:focus,
  .range:focus-visible {
    outline: none;
  }

  .range::-webkit-slider-runnable-track {
    height: 0.25rem;
    border-radius: var(--radius-full);
    background: linear-gradient(to right, var(--color-accent) var(--at), var(--color-line) var(--at));
  }

  .range::-moz-range-track {
    height: 0.25rem;
    border-radius: var(--radius-full);
    background: linear-gradient(to right, var(--color-accent) var(--at), var(--color-line) var(--at));
  }

  .range::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: var(--thumb);
    height: var(--thumb);
    margin-top: calc((0.25rem - var(--thumb)) / 2);
    border: 2px solid var(--color-accent);
    border-radius: 50%;
    background: var(--color-surface);
    box-shadow: var(--shadow-rest);
    transition: scale var(--motion-instant) var(--ease-standard);
  }

  .range::-moz-range-thumb {
    box-sizing: border-box;
    width: var(--thumb);
    height: var(--thumb);
    border: 2px solid var(--color-accent);
    border-radius: 50%;
    background: var(--color-surface);
    box-shadow: var(--shadow-rest);
  }

  .range:active::-webkit-slider-thumb {
    scale: 1.12;
  }

  .range:focus-visible::-webkit-slider-thumb {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  .range:focus-visible::-moz-range-thumb {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  /* "aus": the thumb grey, the line without accent. */
  .range[data-val="0"]::-webkit-slider-thumb {
    border-color: var(--color-muted);
  }

  .range[data-val="0"]::-moz-range-thumb {
    border-color: var(--color-muted);
  }
}

/* css/components/recommend.css */
/* recommend.css – the recommend form and the recommendation page (card 1.6a).
   The sentence stands in the light, in the reading font (principle 4:
   friends before Alfred). */

@layer components {
  .recommend-page {
    max-width: var(--width-form);
  }

  /* The work as a small line on top: it is the heading of the form. */
  .recommend-head {
    display: flex;
    align-items: center;
    gap: var(--space-group);
  }

  /* A problem with the whole form (the weekly limit) stands between head and form. */
  .recommend-head + .form-error {
    margin-block: var(--space-component) calc(-1 * var(--space-group));
  }

  .recommend-head img {
    width: 3rem;
    height: auto;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
  }

  .recommend-head.is-wide img {
    width: 6rem;
    aspect-ratio: 16 / 9;
    object-fit: cover;
  }

  .recommend-title {
    margin: 0;
    font-size: var(--text-heading);
    line-height: 1.25;
  }

  .recommend-meta {
    margin: var(--space-tight) 0 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  /* ---------- "An": one field with chips (contacts.js) ---------- */
  .to-field {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--control-height);
    padding: var(--space-1) var(--space-2);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    cursor: text;
    transition: border-color var(--motion-quick) var(--ease-standard);
  }

  .to-field:hover {
    border-color: var(--color-muted);
  }

  .to-field:focus-within {
    border-color: var(--color-focus);
    box-shadow: inset 0 0 0 1px var(--color-focus);
  }

  .to-chips {
    display: contents;
    list-style: none;
  }

  .to-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    max-width: 100%;
    min-height: var(--control-height-compact);
    padding: 0 var(--space-1) 0 var(--space-1);
    border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
    font-size: var(--text-meta);
  }

  .to-chip.is-error {
    background: color-mix(in srgb, var(--color-error) 12%, var(--color-surface));
    box-shadow: inset 0 0 0 1px var(--color-error);
  }

  .to-chip-label {
    font-weight: var(--weight-semibold);
  }

  .to-chip-mail {
    color: var(--color-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Someone by mail: name and address are one button that brings both
     back into the fields for changing (card 2.5k). */
  .to-chip-edit {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-align: start;
    cursor: pointer;
  }

  .to-chip-edit:hover .to-chip-label {
    text-decoration: underline;
    text-underline-offset: 0.2em;
  }

  /* The name for a new address: its own field below "An" (card 2.5k), with
     the hint and, in the reading font, how the mail will begin. */
  .to-name {
    display: grid;
    gap: var(--space-related);
  }

  /* Its own display rule would otherwise beat the hidden attribute. */
  .to-name[hidden] {
    display: none;
  }

  /* As far from "An" and its hints as "Dein Satz dazu" is: a field of its own. */
  .recommend-form > .to-name {
    margin-block-start: calc(var(--space-siblings) - var(--space-related));
  }

  .to-name > :is(.form-error, .note-text, .to-name-preview) {
    margin-block-start: 0;
  }

  /* The lines inside sit close, the last one too: every field group stands
     one sibling distance from the next. */
  .to-name > :is(.form-error, .note-text, .to-name-preview) {
    margin-block-end: 0;
  }

  .to-name > input + :is(.form-error, .note-text),
  .to-name > :is(.form-error, .note-text) + :is(.note-text, .to-name-preview) {
    margin-block-start: calc(var(--space-tight) - var(--space-related));
  }

  .to-name-preview {
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .to-name-greeting {
    font-family: var(--font-read);
    font-size: var(--text-read);
    color: var(--color-text);
  }

  .to-chip-remove {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    border: 0;
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--color-muted);
    font: inherit;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
  }

  .to-chip-remove::after {
    content: "";
    position: absolute;
    inset: 50% auto auto 50%;
    translate: -50% -50%;
    width: var(--target-min);
    height: var(--target-min);
  }

  .to-chip-remove:hover {
    color: var(--color-text);
    background: color-mix(in srgb, var(--color-text) 8%, transparent);
  }

  .to-field .to-input {
    flex: 1;
    min-width: 8rem;
    min-height: var(--control-height-compact);
    font: inherit;
    font-size: var(--text-control);
    color: var(--color-text);
    background: transparent;
    border: 0;
    padding: 0 var(--space-2);
  }

  /* A search field for Safari's sake (contacts.js), without the look of one. */
  .to-field .to-input {
    appearance: none;
  }

  .to-field .to-input::-webkit-search-cancel-button,
  .to-field .to-input::-webkit-search-decoration {
    appearance: none;
  }

  .to-field .to-input:focus-visible {
    outline: none;
    box-shadow: none;
  }

  .to-suggest {
    list-style: none;
    margin: var(--space-tight) 0 0;
    padding: var(--space-2);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-floating);
  }

  .to-offer {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    min-height: var(--target-min);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-s);
    cursor: pointer;
  }

  .to-offer small {
    color: var(--color-muted);
    font-size: var(--text-meta);
  }

  .to-offer[aria-selected="true"],
  .to-offer:hover {
    background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  }

  /* Groups (card 2.16a): a symbol where people have their initial; the
     size in the second line. One that is too large waits, quiet. */
  .to-offer.is-group {
    align-items: center;
  }

  .to-offer-words {
    display: grid;
  }

  .to-offer[aria-disabled="true"] {
    color: var(--color-muted);
    cursor: default;
  }

  .to-offer[aria-disabled="true"]:hover {
    background: transparent;
  }

  .to-offer-symbol,
  .to-chip-symbol {
    display: inline-grid;
    place-items: center;
    flex: none;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-accent) 16%, var(--color-surface));
    color: var(--color-accent);
  }

  .to-offer-symbol .icon,
  .to-chip-symbol .icon {
    width: 0.95rem;
    height: 0.95rem;
  }

  .to-offer-symbol {
    width: 2rem;
    height: 2rem;
  }

  .to-offer-symbol .icon {
    width: 1.1rem;
    height: 1.1rem;
  }

  /* The checkboxes for groups: only without JavaScript (contacts.js hides them). */
  .recipient-groups {
    display: grid;
    gap: var(--space-related);
    border: 0;
    margin: var(--space-related) 0 0;
    padding: 0;
  }

  .recipient-groups[hidden] {
    display: none;
  }

  .recipient-groups small {
    display: block;
  }

  /* Hints under "An" belong together: tight, like field and hint. */
  .recommend-form > .note-text:has(+ .note-text:not([hidden])) {
    margin-block-end: 0;
  }

  .recommend-form > .note-text + .note-text {
    margin-block-start: calc(var(--space-tight) - var(--space-related));
  }

  /* The sentence is the heart: the largest field, in the reading font
     (of a recommendation and of an invitation, card 2.17). */
  .recommend-form :is(#sentence, #invite-sentence) {
    font-family: var(--font-read);
    font-size: var(--text-read);
  }

  /* ---------- In the dialog over the work page (card 2.5j) ---------- */
  .dialog-recommend {
    width: min(36rem, 100% - 2 * var(--space-4));
  }

  /* The form keeps its own rhythm (label, field, hint close; fields apart). */
  .dialog .recommend-form {
    gap: var(--space-related);
    margin-block: var(--space-siblings) 0;
  }

  /* Compact enough to fit a laptop screen at the start (07.10.2026): "An"
     follows the head at one sibling distance, not more ... */
  .stack-form.recommend-form > .recipients:not(:first-child) {
    margin-block-start: 0; /* as specific as the rule in forms.css it replaces */
  }

  /* ... and the hints close their field group without a paragraph's extra
     room, so every group stands one sibling distance from the next. */
  .recommend-form > .note-text {
    margin-block-end: 0;
  }

  .recommend-form > .dialog-actions {
    margin-block-start: calc(var(--space-component) - var(--space-related));
  }

  .recommend-form .recipients {
    display: grid;
    gap: var(--space-3);
    border: 0;
    margin: 0;
    padding: 0;
  }

  .recommend-form legend {
    margin-block-end: var(--space-related);
  }

  /* Name and address side by side; on a phone one below the other. */
  .recipient-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    gap: var(--space-2) var(--space-3);
  }

  .recipient-row label {
    display: grid;
    gap: var(--space-related);
  }

  .recipient-row input {
    width: 100%;
  }

  .recipient-row .form-error {
    grid-column: 1 / -1;
  }

  @media (max-width: 36rem) {
    .recipient-row {
      grid-template-columns: minmax(0, 1fr);
    }
  }

  .recommend-form input[type="email"] {
    font: inherit;
    font-size: var(--text-m);
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    padding: var(--space-3) var(--space-4);
  }

  .recommend-form textarea {
    font-family: var(--font-read);
    font-size: var(--text-l);
    line-height: var(--leading-read);
  }

  .recommend-form .recipients > button {
    justify-self: start;
  }

  /* ---------- The recommendation page (card 2.5g): a small letter ---------- */
  .recommendation {
    max-width: var(--width-read);
  }

  .recommendation-sentence {
    margin: 0;
    font-family: var(--font-read);
    font-size: var(--text-sentence);
    line-height: 1.4;
    text-wrap: pretty;
  }

  /* Who wrote it to whom and when: one line right under the sentence. */
  .recommendation-from {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-block-start: var(--space-group);
  }

  .recommendation-from-line {
    flex: 1;
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .recommendation-from-line a,
  .recommendation-sender {
    color: var(--color-text);
    font-weight: var(--weight-semibold);
    text-decoration: none;
  }

  .recommendation-from-line a:hover {
    color: var(--color-accent);
  }

  .recommendation-from-line .separator {
    margin-inline: var(--space-1);
  }

  /* The delivery: a quiet second line (later the moment of the closed loop). */
  .recommendation-delivery {
    list-style: none;
    margin: var(--space-tight) 0 0;
    padding: 0 0 0 calc(2rem + var(--space-3));
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  /* What became of it (card 2.16b): quiet lines; "loved" with the daisy,
     in the text colour and the control size, the one line that matters. */
  .recommendation-delivery li {
    display: flex;
    align-items: center;
    gap: var(--space-related);
  }

  .recommendation-delivery .loop-loved {
    margin-block: var(--space-tight);
    margin-inline: calc(-1 * var(--space-3));
    padding: var(--space-related) var(--space-3);
    border-radius: var(--radius-m);
    color: var(--color-text);
    font-size: var(--text-control);
  }

  .loop-daisy {
    display: inline-flex;
  }

  .loop-daisy .daisy-petal {
    fill: var(--color-moment);
    stroke: var(--color-moment);
  }

  .loop-daisy .daisy-center {
    fill: var(--color-accent);
    stroke: var(--color-accent);
  }

  /* The moment, once: the line comes across the table like a card pushed
     over, the daisy unfolds, a warm wash fades (bewegung-klang-haptik.md
     4, approved in the system preview, section 8). With "reduce motion"
     the duration tokens are 0: it is simply there. */
  .loop-loved.is-moment {
    animation:
      loop-slide var(--motion-slow) var(--ease-curtain) both,
      loop-wash calc(var(--motion-slow) * 2.7) var(--ease-exit) both;
  }

  .loop-loved.is-moment .daisy-petals {
    animation: daisy-bloom var(--motion-slow) var(--ease-curtain) calc(var(--motion-slow) / 4) both;
  }

  @keyframes loop-slide {
    from {
      opacity: 0;
      translate: -1.25rem 0.75rem;
      rotate: -1.5deg;
    }
    to {
      opacity: 1;
      translate: 0 0;
      rotate: 0deg;
    }
  }

  @keyframes loop-wash {
    0%,
    40% {
      background: color-mix(in srgb, var(--color-moment) 22%, transparent);
    }
    100% {
      background: transparent;
    }
  }

  /* The quick answers (card 2.16b): under the work, as its own group. */
  .rec-answers {
    display: grid;
    gap: var(--space-related);
    margin-block-start: var(--space-component);
  }

  .rec-answers-title {
    margin: 0;
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-semibold);
    color: var(--color-text);
  }

  .rec-answers .status-group {
    margin: 0;
  }

  .rec-answers .status-chip {
    min-height: var(--control-height);
  }

  .rec-answers .note-text {
    margin: 0;
  }

  /* The work: the same work card as in lists, a little larger. */
  .recommendation-work {
    list-style: none;
    margin: var(--space-section) 0 0;
    padding: 0;
    border-top: 1px solid var(--color-line);
  }

  .recommendation-work .work-card {
    grid-template-columns: 6.5rem minmax(0, 1fr) 2.75rem;
  }

  .recommendation-work .work-card-wide {
    grid-template-columns: 11rem minmax(0, 1fr) 2.75rem;
  }

  .recommendation-work .work-card:not(:has(.work-card-side)) {
    grid-template-columns: 6.5rem minmax(0, 1fr);
  }

  .recommendation-work .work-card-title {
    font-size: var(--text-heading);
  }

  .recommendation-work .work-card-facts {
    grid-template-columns: minmax(0, 1fr);
  }

  /* ---------- People: picture or initial ---------- */
  .avatar {
    display: inline-grid;
    place-items: center;
    flex: none;
    border-radius: var(--radius-full);
    object-fit: cover;
    background: var(--color-line);
    color: var(--color-muted);
    font-family: var(--font-read);
    line-height: 1;
  }

  .avatar-m {
    width: 2rem;
    height: 2rem;
    font-size: var(--text-meta);
  }

  .avatar-s {
    width: 1.5rem;
    height: 1.5rem;
    font-size: var(--text-fine);
  }

  /* Large: the friends around the daisy in the onboarding (card 2.17). */
  .avatar-l {
    width: 3.5rem;
    height: 3.5rem;
    font-size: var(--text-heading);
  }

  /* ---------- The menu "Mehr" ---------- */
  .menu-more {
    min-width: 15rem;
  }

  .menu-more form {
    display: grid;
    gap: var(--space-1);
  }

  .menu-item-danger {
    color: var(--color-error);
  }

  a.menu-item {
    text-decoration: none;
  }

  @supports (position-area: bottom) {
    .menu-more {
      position-area: block-end span-inline-start;
      margin: var(--space-1) 0 0;
    }
  }

  /* ---------- The conversation ---------- */
  .comments {
    margin-block-start: var(--space-section);
  }

  .comments-title {
    margin: 0 0 var(--space-group);
  }

  .comment-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-siblings);
  }

  .comment-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .comment-meta {
    flex: 1;
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .comment-meta .separator {
    margin-inline: var(--space-1);
  }

  .comment-author {
    font-weight: var(--weight-semibold);
    color: var(--color-text);
  }

  .comment-body {
    margin: var(--space-related) 0 0;
    padding-inline-start: calc(1.5rem + var(--space-2));
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: var(--leading-read);
    white-space: pre-line;
    overflow-wrap: anywhere;
  }

  /* The reply: a field that grows, "Antworten" at its right below. */
  .comment-form {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-related);
    margin-block-start: var(--space-siblings);
  }

  .comment-form textarea {
    font-family: var(--font-read);
    font-size: var(--text-read);
  }

  .comment-form .btn {
    justify-self: end;
  }

  .comment-form:has(textarea:placeholder-shown) .btn {
    opacity: 0.45;
  }
}

/* css/components/save.css */
/* save.css – the bookmark and the quick action "+ / −" (card 2.5b; tokens.md, 4; listen.md, 3 and 7). */

@layer components {
  /* ---------- The bookmark: a ribbon hanging from the top edge ----------
     One continuous outline (sides and notch in a single path, real corners),
     as long as the pulled-out ribbon. Its upper part hides behind the card's
     edge (clipped at the top only), so it grows on hover without distorting.
     Filled = saved, outline = nowhere; the number of own lists inside. */
  .mark {
    display: block;
    color: var(--color-accent);
    text-decoration: none;
    cursor: pointer;
  }

  .rb {
    --rb-length: 2rem;   /* 26 px of ribbon + 6 px notch */
    --rb-grow: 0.75rem;  /* it grows 12 px on hover */
    position: relative;
    display: block;
    width: 1.375rem;
    height: var(--rb-length);
    clip-path: inset(0 -4px -8px -4px);
    transition: height var(--motion-quick) var(--ease-standard);
  }

  .rb svg {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: calc(var(--rb-length) + var(--rb-grow));
    overflow: visible;
  }

  /* Nowhere saved: the outline in the colour of the card's lines, quiet
     even when it repeats down a long list (initiator, 04.10.2026). */
  .rb path {
    fill: transparent;
    stroke: var(--color-line);
    stroke-width: 1.5;
    stroke-linejoin: miter;
    stroke-miterlimit: 10;
    transition:
      fill var(--motion-quick) var(--ease-standard),
      stroke var(--motion-quick) var(--ease-standard);
  }

  .mark:hover .rb,
  .mark:focus-visible .rb {
    height: calc(var(--rb-length) + var(--rb-grow));
  }

  .mark:hover .rb path,
  .mark:focus-visible .rb path {
    stroke: var(--color-accent);
  }

  .mark:focus-visible {
    outline: none;
  }

  .mark:focus-visible .rb {
    outline: 2px solid var(--color-focus);
    outline-offset: 3px;
  }

  .is-saved .rb path {
    fill: var(--color-accent);
    stroke: var(--color-accent);
  }

  .mark-count {
    position: absolute;
    z-index: 1;
    top: 0.375rem;
    left: 50%;
    translate: -50% 0;
    font-size: 0.6875rem;
    font-weight: var(--weight-semibold);
    line-height: 1;
    color: var(--color-on-accent);
    font-variant-numeric: tabular-nums;
  }

  /* A target of 44 px around the slim ribbon. */
  .mark::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 0;
    translate: -50% 0;
    width: var(--target-min);
    height: var(--target-min);
  }

  /* ---------- The quick action "+ / −": only the sign changes ---------- */
  .qa {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: var(--control-height-compact);
    height: var(--control-height-compact);
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-accent) 10%, transparent);
    color: var(--color-accent);
    cursor: pointer;
    transition:
      background-color var(--motion-quick) var(--ease-standard),
      border-color var(--motion-quick) var(--ease-standard),
      color var(--motion-quick) var(--ease-standard),
      translate var(--motion-instant) var(--ease-standard);
  }

  .qa:hover {
    background: color-mix(in srgb, var(--color-accent) 17%, transparent);
  }

  .qa:active {
    translate: 0 1px;
  }

  .qa .icon {
    width: 1.125rem;
    height: 1.125rem;
  }

  .is-in .qa {
    background: transparent;
    border-color: var(--color-line);
    color: var(--color-muted);
  }

  .is-in .qa:hover {
    border-color: var(--color-muted);
    color: var(--color-text);
  }

  .qa::after {
    content: "";
    position: absolute;
    inset: 50% auto auto 50%;
    translate: -50% -50%;
    width: var(--target-min);
    height: var(--target-min);
  }

  .qa.htmx-request,
  .qa-form.htmx-request .qa {
    opacity: 0.6;
  }

  /* ---------- The dialog "Speichern in …" (tightened, card 2.11 Nacharbeit) ----------
     Hierarchy by distance: the head (work, then the dialog's purpose)
     stands apart; the rows of a group touch; groups are a step apart. */
  .dialog-sub {
    margin: var(--space-tight) 0 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .save-head {
    display: grid;
    gap: var(--space-tight);
    margin-block-end: var(--space-related);
  }

  .save-work-title {
    margin: 0;
    font-family: var(--font-read);
    font-size: var(--text-card-title);
    line-height: 1.25;
    text-wrap: balance;
  }

  .save-work-title:focus {
    outline: none;
  }

  .save-dialog-title {
    margin: 0;
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .dialog form.save-lists {
    display: grid;
    gap: 0;
    margin: 0 calc(-1 * var(--space-2));
  }

  .check-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 2.5rem;
    padding: 0 var(--space-2);
    border-radius: var(--radius-m);
    font-size: var(--text-control);
    font-weight: var(--weight-regular);
    cursor: pointer;
  }

  .check-row:hover {
    background: color-mix(in srgb, var(--color-text) 5%, transparent);
  }

  .check-row input {
    width: 1.25rem;
    height: 1.25rem;
    margin: 0;
    accent-color: var(--color-accent);
  }

  .check-row small {
    margin-inline-start: auto;
    font-size: var(--text-fine);
    color: var(--color-muted);
  }

  .check-row.is-locked {
    color: var(--color-muted);
    cursor: not-allowed;
  }

  .lock-msg {
    margin: 0 var(--space-2) var(--space-related);
    padding: var(--space-tight) var(--space-3);
    border-radius: var(--radius-m);
    background: color-mix(in srgb, var(--color-text) 5%, transparent);
    font-size: var(--text-meta);
  }
}

/* css/components/saved-mark.css */
/* saved-mark.css – "Gespeichert" beside a field that saves at once
   (components/saved_mark.templ, the one place that makes it).

   It fades in, stands for a moment and fades away; then it takes no room
   any more. How long it stands is the one adjusting screw, --saved-for:
   2.5 s, the usual span for a quiet confirmation (long enough to notice
   from the corner of the eye, short enough not to linger; decided on
   06.10.2026 by the initiator). Every answer brings a fresh mark, so the
   next change shows it again. A problem stays until the field is right:
   it says what to do. Screen readers hear it once (aria-live). */

@layer components {
  .saved-mark {
    --saved-for: 2.5s;
    margin-inline-start: var(--space-2);
    font-size: var(--text-fine);
    font-weight: var(--weight-regular);
    color: var(--color-muted);
  }

  .saved-mark:empty {
    display: none;
  }

  /* The state is data-state, not a class (saved_mark.templ says why). */
  .saved-mark[data-state="saved"] {
    animation: saved-for calc(var(--saved-for) + 2 * var(--motion-gentle)) var(--ease-standard) forwards;
  }

  .saved-mark[data-state="problem"] {
    color: var(--color-error);
  }

  @keyframes saved-for {
    0% {
      opacity: 0;
    }

    12% {
      opacity: 1;
    }

    88% {
      opacity: 1;
    }

    100% {
      opacity: 0;
      visibility: hidden;
      display: none;
    }
  }
}

/* css/components/search.css */
/* search.css – the search field (card 2.5c; components.SearchField since
   card 2.13b Nacharbeit): one building block for every search in daizee,
   one height everywhere (the large variant of Discover is gone, initiator
   06.10.2026). Lens inside, the clear button as soon as there is text,
   a thin line while searching. */

@layer components {
  .search-field {
    position: relative;
  }

  .search-field > .icon {
    position: absolute;
    inset-inline-start: var(--space-3);
    top: 50%;
    translate: 0 -50%;
    color: var(--color-muted);
    pointer-events: none;
  }

  .search-field input {
    width: 100%;
    min-width: 0;
    font: inherit;
    font-size: var(--text-control);
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-full);
    min-height: var(--control-height);
    /* room for the lens on the left and the clear button on the right */
    padding: 0 var(--target-min) 0 calc(var(--space-3) + 1.25rem + var(--space-2));
  }

  /* The browser's own clear button does not fit the palette; ours does. */
  .search-field input::-webkit-search-cancel-button {
    appearance: none;
  }

  .search-clear {
    position: absolute;
    inset-inline-end: var(--space-2);
    top: 50%;
    translate: 0 -50%;
  }

  .search-field:has(input:placeholder-shown) .search-clear {
    display: none;
  }

  /* While results load: a thin line under the field, no spinner. */
  .search-progress {
    position: absolute;
    inset-inline: var(--space-6);
    bottom: -1px;
    height: 2px;
    overflow: hidden;
    border-radius: var(--radius-full);
    opacity: 0;
    transition: opacity var(--motion-quick) var(--ease-standard);
  }

  .search-progress::before {
    content: "";
    position: absolute;
    inset-block: 0;
    width: 30%;
    background: var(--color-accent);
    animation: search-slide 1.2s var(--ease-standard) infinite;
  }

  .htmx-request .search-progress {
    opacity: 1;
  }

  @keyframes search-slide {
    from {
      translate: -100% 0;
    }
    to {
      translate: 340% 0;
    }
  }
}

/* css/components/settings-list.css */
/* settings-list.css – the list in the settings (card 2.5i): signed-in
   devices, passkeys, friends and blocked people. Every row looks the same:
   on the left the name (with a badge where it helps) and a quiet second
   line, on the right the action as a quiet button. Lines between the rows;
   the rows breathe with 16 px above and below. */

@layer components {
  .settings-list {
    list-style: none;
    margin: var(--space-group) 0;
    padding: 0;
    border-top: 1px solid var(--color-line);
  }

  .settings-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-group);
    padding-block: var(--space-4);
    border-bottom: 1px solid var(--color-line);
  }

  .settings-row p {
    margin: 0;
  }

  .settings-row form {
    margin: 0;
  }

  .settings-row-name {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related);
    font-size: var(--text-control);
    font-weight: var(--weight-semibold);
  }

  .settings-row-meta {
    margin-block-start: var(--space-tight);
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  /* A weighty action under the list ("Überall abmelden", "Konto löschen",
     card 2.19): a quiet button in the error colour, its hint close below. */
  .danger-action {
    display: grid;
    justify-items: start;
    gap: var(--space-tight);
    margin-block-start: var(--space-siblings);
  }

  .danger-action .note-text {
    margin: 0;
  }
}

/* css/components/settings.css */
/* settings.css – the settings area (card 2.7, aufbau-und-navigation.md 4).

   On a computer the categories on the left, the chosen one on the right.
   On a phone /settings is the list of categories (rows like the settings
   list, with an arrow); each category is a page of its own with
   "‹ Einstellungen" above it. */

@layer components {
  .settings {
    display: grid;
    grid-template-columns: 13rem minmax(0, var(--width-form));
    gap: var(--space-section);
    align-items: start;
  }

  .settings-side {
    position: sticky;
    top: calc(var(--space-component) + var(--target-min));
    display: grid;
    gap: var(--space-group);
  }

  .settings-side-title {
    margin: 0;
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    font-family: var(--font-ui);
    color: var(--color-muted);
  }

  .settings-cats {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-tight);
  }

  .settings-cats a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: var(--target-min);
    padding-inline: var(--space-3);
    border-radius: var(--radius-choice);
    color: var(--color-text);
    text-decoration: none;
    font-size: var(--text-control);
  }

  .settings-cats a:hover {
    background: color-mix(in srgb, var(--color-text) 5%, transparent);
  }

  /* The current category: a tinted surface, as a chosen chip. */
  .settings-cats a[aria-current="page"] {
    background: color-mix(in srgb, var(--color-accent) 10%, transparent);
    color: var(--color-accent);
  }

  .settings-cats .icon {
    display: none;
    width: 1.125rem;
    height: 1.125rem;
    color: var(--color-muted);
  }

  .settings-signout {
    margin: 0;
    padding-top: var(--space-group);
    border-top: 1px solid var(--color-line);
  }

  .settings-signout .btn {
    gap: var(--space-2);
  }

  .settings-signout .icon,
  .settings-back .icon {
    width: 1.125rem;
    height: 1.125rem;
  }

  .settings-back {
    display: none;
  }

  .settings-main > h1 {
    margin: 0 0 var(--space-group);
  }

  /* Sections within a category: one step down, with room before them.
     Not the title of a dialog that opens here (it keeps dialog.css). */
  .settings-main h2:not(.dialog-title) {
    margin-block: var(--space-section) var(--space-related);
    font-size: var(--text-heading);
  }

  .settings-main .narrow-page {
    max-width: none;
  }

  .settings-choice {
    border: 0;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-related);
    justify-items: start;
  }

  .settings-choice legend {
    padding: 0;
    margin-bottom: var(--space-related);
    font-weight: var(--weight-medium);
  }

  .settings-preview {
    margin-top: var(--space-component);
  }

  /* ---------- On a phone: the list, then the page ---------- */
  @media (max-width: 47.99rem) {
    .settings {
      grid-template-columns: minmax(0, 1fr);
      gap: 0;
    }

    .settings-side {
      position: static;
    }

    .settings:not(.is-index) .settings-side,
    .settings.is-index .settings-main {
      display: none;
    }

    .settings-side-title {
      font-family: var(--font-read);
      font-size: var(--text-page-title);
      font-weight: var(--weight-regular);
      color: var(--color-text);
    }

    .settings-cats {
      gap: 0;
      border-top: 1px solid var(--color-line);
    }

    .settings-cats a {
      min-height: 3.5rem;
      padding-inline: 0;
      border-radius: 0;
      border-bottom: 1px solid var(--color-line);
    }

    .settings-cats a[aria-current="page"] {
      background: none;
      color: var(--color-text);
    }

    .settings-cats .icon {
      display: block;
    }

    .settings-signout {
      border-top: 0;
      padding-top: 0;
    }

    .settings-back {
      display: inline-flex;
      gap: var(--space-1);
      margin: 0 0 var(--space-group) calc(-1 * var(--space-2));
    }
  }
}

/* ---------- Dein Algorithmus (card 2.14c) ----------
   On top the preview: Discover's rows in small, the medium rows in the
   text colour with tinted pictures (they are what the sliders move), the
   others quiet. Below a slider per medium (range.css), its name and the
   level as a word on one line. */
@layer components {
  .algo {
    display: grid;
    gap: var(--space-component);
  }

  .algo-head {
    display: grid;
    gap: var(--space-related);
  }

  .algo-head h1,
  .algo-head p {
    margin: 0;
  }

  .algo-head p {
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: var(--leading-read);
    color: var(--color-muted);
  }

  .algo-preview {
    display: grid;
    gap: var(--space-related);
    padding: var(--space-group);
    border-radius: var(--radius-m);
    background: var(--color-surface);
    box-shadow: var(--shadow-rest);
  }

  .algo-preview-label {
    margin: 0;
    font-size: var(--text-fine);
    color: var(--color-muted);
  }

  .algo-rows {
    display: grid;
    gap: var(--space-tight);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .algo-row {
    display: grid;
    grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
    gap: var(--space-related);
    align-items: center;
    min-height: 1.75rem;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .algo-row.is-medium {
    color: var(--color-text);
  }

  .algo-row.is-off {
    display: none;
  }

  .algo-posters {
    display: flex;
    gap: 3px;
    overflow: hidden;
  }

  .algo-posters :is(i, img) {
    flex: none;
    object-fit: cover;
    width: 1rem;
    height: 1.5rem;
    border-radius: 2px;
    background: color-mix(in srgb, var(--color-line) 80%, var(--color-bg));
  }

  .algo-row.is-medium .algo-posters i {
    background: color-mix(in srgb, var(--color-accent) 22%, var(--color-surface));
  }

  .algo-row.is-wide .algo-posters :is(i, img) {
    width: 2.25rem;
    height: 1.25rem;
  }

  .algo-row.is-square .algo-posters :is(i, img) {
    width: 1.5rem;
    height: 1.5rem;
  }

  /* The parts (Medien, Genres since card 4.4c, Stimmungen und Tags since
     4.4e) are sections of the page: one section's space above the first
     (not the component's gap and the section's margin together) and
     between them (Prinzip 10: between groups clearly more than within). */
  .algo-parts {
    display: grid;
    gap: var(--space-section);
    margin-block-start: calc(var(--space-section) - var(--space-component));
  }

  /* A part's sliders are a form of their own; it adds no box. */
  .algo-form {
    display: contents;
  }

  .algo-part {
    display: grid;
    gap: var(--space-group);
    margin: 0;
  }

  /* A part's heading and its one sentence belong together. */
  .algo-part-head {
    display: grid;
    gap: var(--space-related);
  }

  .algo-part-lead {
    margin: 0;
    max-width: var(--width-read);
    font-size: var(--text-meta);
    color: var(--color-muted);
    text-wrap: pretty;
  }

  /* "Weitere Genres": one level below the sliders (Prinzip 9), a small
     title over the chips per medium (components.TasteSet). */
  .algo-more {
    display: grid;
    gap: var(--space-group);
  }

  /* Below the sliders: more space than between them, the way to add more
     is a group of its own. */
  .algo-part > .algo-more {
    margin-block-start: calc(var(--space-component) - var(--space-group));
  }

  /* The search for tags (tag-search.js), as wide as a form; what it finds
     stands right below it. */
  .algo-tag-search {
    display: grid;
    gap: var(--space-related);
    max-width: var(--width-form);
  }

  .algo-tag-found[hidden] {
    display: none;
  }

  .algo-more-title {
    margin: 0;
    font-family: var(--font-ui);
    font-size: var(--text-control);
    font-weight: var(--weight-semibold);
  }

  .settings-main .algo-section {
    margin: 0;
    font-size: var(--text-heading);
  }

  .algo-sliders {
    display: grid;
    gap: var(--space-group);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .slider {
    display: grid;
    gap: var(--space-tight);
  }

  .slider-top {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-related);
  }

  /* A genre's name stays the main thing; beside it, quieter, why it is
     there ("Lieblingsgenre · 9 in deiner Sammlung"); on a phone the hint
     wraps under the name. */
  .slider-label {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: var(--space-related);
    min-width: 0;
  }

  .slider-hint {
    font-size: var(--text-fine);
    color: var(--color-muted);
  }

  .slider-value {
    display: flex;
    align-items: center;
    gap: var(--space-related);
    font-size: var(--text-meta);
    color: var(--color-accent);
  }

  /* "Gespeichert" before the word, so the word keeps its edge. */
  .slider-value .saved-mark {
    margin: 0;
  }

  .slider.is-off :is(.slider-name, .slider-word) {
    color: var(--color-muted);
  }

  /* The × of a favourite genre: a quiet symbol after the level's word; it
     makes the row no taller (its 44 px target reaches over the edges). */
  .slider-remove {
    margin-block: calc(-1 * var(--space-related));
    margin-inline-end: calc(-1 * var(--space-related));
  }

  /* A genre just chosen from the chips blooms in (spring: only where
     something appears, bewegung-klang-haptik.md 2). */
  .slider[data-arrived] {
    animation: genre-arrive var(--motion-gentle) var(--ease-spring-soft);
  }

  @keyframes genre-arrive {
    from {
      opacity: 0;
      translate: 0 calc(-1 * var(--space-related));
    }
  }

  .algo-reset-form {
    margin: 0;
  }

  .algo-reset-form[hidden] {
    display: none;
  }

  /* Settings → Schreib uns (card 5.0e): the form, then the receipt in its
     place. */
  .write-us {
    display: grid;
    gap: var(--space-group);
    max-width: var(--width-form);
  }

  .write-us > .prose {
    margin: 0;
  }

  .write-us-thanks {
    display: grid;
    justify-items: start;
    gap: var(--space-related);
  }

  .write-us-thanks h2,
  .write-us-thanks .prose {
    margin: 0;
  }
}

/* css/components/side-sheet.css */
/* side-sheet.css – the side sheet (card 4.4b, aufbau-und-navigation.md 6):
   for choosing while one watches the result. On a computer it slides in
   from the right edge and lies over the page, which stays as it is, not
   darkened and still usable; on a phone it is a sheet from below, like
   the dialogs (dialog.css). A popover: the browser brings Escape, the
   click beside it and the top layer. */

@layer components {
  .side-sheet {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(24rem, 100vw);
    height: 100dvh;
    max-height: none;
    margin: 0;
    padding: var(--space-5) var(--gutter) var(--space-6);
    border: 0;
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: var(--shadow-floating);
    overflow-y: auto;
    overscroll-behavior: contain;
    translate: 100% 0;
    transition:
      translate var(--motion-gentle) var(--ease-enter),
      overlay var(--motion-gentle) allow-discrete,
      display var(--motion-gentle) allow-discrete;
  }

  .side-sheet:popover-open {
    display: grid;
    align-content: start;
    gap: var(--space-group);
    translate: 0 0;
  }

  @starting-style {
    .side-sheet:popover-open {
      translate: 100% 0;
    }
  }

  /* The page stays in view: no dimming. */
  .side-sheet::backdrop {
    background: transparent;
  }

  .side-sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-related);
  }

  .side-sheet-title {
    margin: 0;
    font-size: var(--text-heading);
  }

  .side-sheet-body {
    display: grid;
    gap: var(--space-group);
  }

  /* A phone: a sheet from below, as the dialogs and the lists. */
  @media (max-width: 47.99rem) {
    .side-sheet {
      inset: auto 0 0;
      width: 100%;
      height: auto;
      max-height: 85dvh;
      padding: var(--space-6) var(--gutter) calc(var(--space-5) + env(safe-area-inset-bottom));
      border-radius: var(--radius-l) var(--radius-l) 0 0;
      translate: 0 100%;
    }

    @starting-style {
      .side-sheet:popover-open {
        translate: 0 100%;
      }
    }

    .side-sheet::backdrop {
      background: var(--color-scrim);
    }
  }
}

/* css/components/site-nav.css */
/* site-nav.css – the main menu (card 2.7, aufbau-und-navigation.md 2 and 3).

   On a computer: the wordmark on the left, the three areas as words, the
   round picture on the right. The header stays at the top and glides away
   while scrolling down, back on the way up (header.js). On a phone the
   same menu is the bar at the bottom: symbol and short word, the picture
   as the fourth field, always there, within reach of the thumb. */

@layer components {
  .page-header {
    position: sticky;
    top: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-group);
    background: var(--color-bg);
    transition: translate var(--motion-gentle) var(--ease-enter);
  }

  /* The wordmark takes the free room, so the menu and the language switch
     (language-switch.css) sit together at the right end. */
  .page-header .wordmark {
    margin-inline-end: auto;
  }

  .page-header.is-away {
    translate: 0 -100%;
    transition-timing-function: var(--ease-exit);
  }

  .site-nav {
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }

  .nav-item {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-height: var(--target-min);
    padding-inline: var(--space-3);
    border-radius: var(--radius-button);
    font-size: var(--text-control);
    color: var(--color-muted);
    text-decoration: none;
    transition: color var(--motion-quick) var(--ease-standard);
  }

  .nav-item:hover {
    color: var(--color-text);
  }

  /* The current area: the text colour and a fine line, nothing that changes the width. */
  .nav-item[aria-current="page"] {
    color: var(--color-text);
  }

  .nav-item[aria-current="page"]::after {
    content: "";
    position: absolute;
    inset-inline: var(--space-3);
    bottom: 0.4rem;
    height: 2px;
    border-radius: var(--radius-full);
    background: var(--color-accent);
  }

  .nav-item > .icon {
    display: none;
  }

  /* An add-on's entry (the Werkstatt for its people): apart from daizee's
     own areas by a fine line, its symbol beside the word. */
  .nav-addon {
    margin-inline-start: var(--space-2);
    gap: var(--space-2);
  }

  .nav-addon::before {
    content: "";
    position: absolute;
    inset-block: 0.75rem;
    inset-inline-start: calc(-1 * var(--space-2));
    width: 1px;
    background: var(--color-line);
  }

  .nav-addon > .icon {
    display: block;
    width: 1.125rem;
    height: 1.125rem;
  }

  /* The round picture opens the settings. */
  .nav-me {
    margin-inline-start: var(--space-3);
    padding-inline: var(--space-1);
  }

  .nav-me[aria-current="page"]::after {
    display: none;
  }

  .nav-me[aria-current="page"] .avatar {
    box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-accent);
  }

  .nav-me .nav-word {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .nav-dot {
    position: absolute;
    top: 0.55rem;
    right: 0.2rem;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: var(--radius-full);
    background: var(--color-accent);
  }

  /* The same dot in a line of text: at a list, a person, a button. */
  .nav-dot.is-inline {
    position: static;
    display: inline-block;
    flex: none;
    margin-inline-start: var(--space-2);
    vertical-align: middle;
  }

  /* Signed out (or still activating): one quiet way. */
  .site-nav-plain a,
  .site-nav-plain .nav-button {
    font: inherit;
    font-size: var(--text-control);
    color: var(--color-muted);
    text-decoration: none;
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
  }

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

  .site-nav form {
    margin: 0;
  }

  /* ---------- On a phone: the bar at the bottom ---------- */
  @media (max-width: 47.99rem) {
    .page-header {
      padding-block: var(--space-3);
      transition: top var(--motion-gentle) var(--ease-enter);
    }

    /* The bar at the bottom lives inside the header. A moved (translated)
       header would become the frame of that fixed bar, and the bar jumped
       to the top while the header glided away (initiator, 08.10.2026). So
       on a phone the header glides by its place (top), which moves nothing
       else. 6rem is more than its height. */
    .page-header.is-away {
      translate: none;
      top: -6rem;
    }

    .site-nav:not(.site-nav-plain) {
      position: fixed;
      inset-inline: 0;
      bottom: 0;
      z-index: 45;
      display: grid;
      grid-template-columns: repeat(4, minmax(0, 1fr));
      grid-auto-flow: column;
      grid-auto-columns: minmax(0, 1fr);
      gap: 0;
      padding: var(--space-1) var(--space-2) calc(var(--space-1) + env(safe-area-inset-bottom));
      background: color-mix(in srgb, var(--color-bg) 92%, transparent);
      backdrop-filter: blur(12px);
      border-top: 1px solid var(--color-line);
    }

    .nav-item {
      flex-direction: column;
      justify-content: center;
      gap: 2px;
      min-height: 3.25rem;
      margin: 0;
      padding: 0;
      border-radius: var(--radius-m);
      font-size: var(--text-fine);
    }

    .nav-item > .icon {
      display: block;
      width: 1.5rem;
      height: 1.5rem;
    }

    .nav-item[aria-current="page"] {
      color: var(--color-accent);
    }

    .nav-item[aria-current="page"]::after {
      display: none;
    }

    .nav-addon {
      margin: 0;
    }

    .nav-addon::before {
      display: none;
    }

    .nav-me .avatar {
      width: 1.5rem;
      height: 1.5rem;
      font-size: 0.75rem;
    }

    .nav-me .nav-word {
      position: static;
      width: auto;
      height: auto;
      overflow: visible;
      clip-path: none;
    }

    .nav-dot {
      top: 0.4rem;
      right: calc(50% - 1rem);
    }

    /* The page and the short notes keep clear of the bar. */
    .page:has(.site-nav:not(.site-nav-plain)) {
      padding-bottom: calc(3.75rem + env(safe-area-inset-bottom));
    }

    body:has(.site-nav:not(.site-nav-plain)) .toast-region,
    body:has(.site-nav:not(.site-nav-plain)) .pick-bar {
      bottom: calc(4.75rem + env(safe-area-inset-bottom));
    }
  }
}

/* css/components/tags.css */
/* tags.css – the tag area of a work page (card 4.3b,
   tags-und-tag-seiten.md 3): small chips, the viewer's own in the
   accent's tone, the platform's quiet; folded to one row (fold.css,
   fold.js); "+" opens the field with its suggestions (tags.js).

   The small chip is a new size of the family "Hinweis" (tokens.md 4):
   no larger than a line of fine print, so many tags stay calm. What can
   be tapped on it still has a target of 44 px (an invisible ::after). */

@layer components {
  .work-tags {
    display: grid;
    gap: var(--space-related);
  }

  .tag-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .tag-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--control-height-small);
    padding: 0 var(--space-2);
    border-radius: var(--radius-choice);
    font-size: var(--text-fine);
    line-height: 1.2;
    white-space: nowrap;
  }

  /* The viewer's own: the tone of the accent, as chosen chips. */
  .tag-own {
    background: color-mix(in srgb, var(--color-accent) 11%, var(--color-surface));
    color: var(--color-accent);
  }

  /* The platform's: quiet, on the line's tone. */
  .tag-platform {
    background: color-mix(in srgb, var(--color-line) 55%, var(--color-surface));
    color: var(--color-muted);
  }

  /* Hinted chips while the platform looks for tags (card 4.3f): the
     pulse of the description's skeleton, three widths like real tags. */
  .tag-skeleton {
    inline-size: 4.5rem;
    background: color-mix(in srgb, var(--color-text) 8%, transparent);
    animation: description-pulse 1.6s var(--ease-standard) infinite alternate;
  }

  .tag-skeleton + .tag-skeleton {
    inline-size: 6.5rem;
    animation-delay: 0.2s;
  }

  .tag-skeleton + .tag-skeleton + .tag-skeleton {
    inline-size: 5rem;
    animation-delay: 0.4s;
  }

  @media (prefers-reduced-motion: reduce) {
    .tag-skeleton {
      animation: none;
    }
  }

  /* Nothing found: one calm line, like the description's (work.css). */
  .tag-none {
    margin: 0;
    max-inline-size: var(--width-read);
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .tag-chip form {
    display: contents;
  }

  .tag-remove,
  .tag-add {
    position: relative;
    display: inline-grid;
    place-items: center;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    cursor: pointer;
  }

  .tag-remove::after,
  .tag-add::after {
    content: "";
    position: absolute;
    inset: 50% auto auto 50%;
    width: var(--target-min);
    height: var(--target-min);
    transform: translate(-50%, -50%);
  }

  .tag-remove svg {
    width: var(--icon-small);
    height: var(--icon-small);
    opacity: 0.7;
  }

  .tag-remove:hover svg,
  .tag-remove:focus-visible svg {
    opacity: 1;
  }

  /* "+": a small round chip of its own. */
  .tag-add {
    width: var(--control-height-small);
    height: var(--control-height-small);
    border: 1px dashed var(--color-muted);
    border-radius: var(--radius-full);
    color: var(--color-muted);
    transition: color var(--motion-quick) var(--ease-standard), border-color var(--motion-quick) var(--ease-standard);
  }

  .tag-add svg {
    width: var(--icon-small);
    height: var(--icon-small);
  }

  .tag-add:hover,
  .tag-add[aria-expanded="true"] {
    border-color: var(--color-accent);
    color: var(--color-accent);
  }

  /* Folded: one row of chips, cut cleanly (not scrolling). */
  .fold-tags .fold-box {
    max-block-size: var(--control-height-small);
    overflow: hidden;
  }

  .fold-tags[data-open] .fold-box {
    max-block-size: none;
  }

  /* The field, above the chips. */
  .tag-field {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-1);
    max-width: var(--width-form);
  }

  .tag-field[hidden] {
    display: none;
  }

  .tag-field-hash {
    color: var(--color-muted);
  }

  .tag-field-input {
    flex: 1;
    min-width: 0;
  }

  .tag-suggest {
    position: absolute;
    inset: calc(100% + var(--space-1)) 0 auto 0;
    z-index: 20;
    max-block-size: 18rem;
    overflow-y: auto;
    margin: 0;
    padding: var(--space-1) 0;
    list-style: none;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    background: var(--color-surface);
    box-shadow: var(--shadow-floating);
  }

  .tag-suggest[hidden] {
    display: none;
  }

  .tag-suggest-item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    min-height: var(--target-min);
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
  }

  .tag-suggest-item[hidden] {
    display: none;
  }

  .tag-suggest-item[aria-selected="true"],
  .tag-suggest-item:hover {
    background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface));
  }

  .tag-suggest-facet {
    color: var(--color-muted);
    font-size: var(--text-fine);
  }

  .tag-suggest-new .tag-suggest-facet {
    color: var(--color-accent);
  }

  /* One's own tags, one level deeper in the collection's side sheet (card
     4.4d; until then the page Sammlung → Tags, 4.3b). Prinzip 10: the
     level's head (the way back and one sentence) is one group; the rows
     follow a group apart. A row: the tag (a tap filters the collection),
     how many works, and the pen. Opened, two groups a group apart, each
     close in itself: the new name (label over field, as in every form),
     and deleting, quiet until asked; then one sentence and the answers as
     in a dialog, the red only on the last step. Every edge on one line:
     the chip, the label, the field, the buttons. */
  .own-tags-head {
    display: grid;
    gap: var(--space-related);
    justify-items: start;
  }

  /* With the levels the sheet's own title says "Deine Tags". */
  .own-tags-title {
    margin: 0;
    font-size: var(--text-heading);
  }

  .side-sheet[data-levels] .own-tags-title {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .own-tags-intro {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
    text-wrap: pretty;
  }

  .own-tag-rows {
    display: grid;
    gap: var(--space-tight);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .own-tag-line {
    display: flex;
    align-items: center;
    gap: var(--space-related);
    min-height: var(--target-min);
    cursor: pointer;
    list-style: none;
  }

  .own-tag-line::-webkit-details-marker,
  .own-tag-delete > summary::-webkit-details-marker {
    display: none;
  }

  .own-tag-count {
    font-size: var(--text-fine);
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  .own-tag-line .saved-mark {
    margin: 0;
  }

  .own-tag-open {
    display: inline-grid;
    place-items: center;
    margin-inline-start: auto;
    color: var(--color-muted);
  }

  .own-tag-line:hover .own-tag-open {
    color: var(--color-text);
  }

  .own-tag-row[open] .own-tag-open {
    color: var(--color-accent);
  }

  .own-tag-body {
    display: grid;
    gap: var(--space-group);
    padding-block: var(--space-related) var(--space-group);
  }

  .own-tag-rename {
    display: grid;
    gap: var(--space-related);
  }

  .own-tag-rename .tag-field {
    max-width: none;
  }

  .own-tag-delete {
    display: grid;
    justify-items: start;
  }

  .own-tag-delete > summary {
    list-style: none;
    margin-inline-start: calc(-1 * var(--space-2));
  }

  /* Asked: the question takes the place of the quiet button (with the
     island; without it the button stays, a second tap closes). */
  .side-sheet[data-levels] .own-tag-delete[open] > summary,
  .side-sheet:not([data-levels]) [data-details-close] {
    display: none;
  }

  .own-tag-confirm {
    display: grid;
    gap: var(--space-related);
  }

  .own-tag-warn {
    margin: 0;
    font-size: var(--text-meta);
    text-wrap: pretty;
  }

  .own-tag-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related);
  }

  /* Levels in a side sheet (side-sheet.js): one level at a time; without
     the island both stand one below the other. */
  .side-sheet[data-levels] [data-sheet-panel],
  .side-sheet[data-level] [data-sheet-main],
  .side-sheet[data-level] .sheet-level-open,
  .side-sheet:not([data-levels]) .sheet-level-back {
    display: none;
  }

  .side-sheet[data-level] [data-sheet-panel] {
    display: grid;
    animation: sheet-level-in var(--motion-gentle) var(--ease-standard);
  }

  .sheet-level {
    gap: var(--space-group);
  }

  /* Quiet buttons start on the text's edge (their inner space outside). */
  .sheet-level-open,
  .sheet-level-back,
  .own-tag-actions .btn-quiet {
    justify-self: start;
    margin-inline-start: calc(-1 * var(--space-2));
  }

  /* The way down reads like a line of text, not a centred button, even
     where it wraps. */
  .sheet-level-open {
    justify-content: flex-start;
    text-align: start;
    text-wrap: pretty;
  }

  @keyframes sheet-level-in {
    from {
      opacity: 0;
      translate: var(--space-group) 0;
    }
  }

  /* The tag filter of the collection (card 4.4d): the chosen ones with ×
     and how they combine, then the way to the side sheet. */
  .filter-tags {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related) var(--space-group);
    min-width: 0;
  }

  .filter-tags .tag-chosen {
    margin: 0;
  }

  /* "Ein Tag passt nicht" (card 4.3d): the platform tags as choice chips,
     wrong or inaccurate as two segments, an optional sentence. */
  .tag-report-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-related);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
  }

  .tag-report-reason {
    justify-self: start;
  }

  /* The menu "Mehr" at the end of the tags' first line, always in view:
     the tags take the room left of it (feedback after stage 4). */
  .work-tags-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-related);
  }

  .work-tags-row > :first-child {
    flex: 1;
    min-width: 0;
  }

  /* As high as the first line of chips, centred on it. */
  .work-more {
    display: inline-flex;
    margin-block-start: calc((var(--control-height-small) - 2rem) / 2);
  }

  /* ---------- The tag page (card 4.4a, the preview "Tag-Seiten") ----------
     One main thing: the tag's name, as page title (the hash quieter, it is
     punctuation, not the word). Directly under it, as one group (related):
     what kind of tag it is and how many people use it, then what it means,
     in the reading face. Narrower tags of a broader one follow a group
     further down; the works a component further. */
  .tag-page-head {
    display: grid;
    gap: var(--space-related);
    margin-block: var(--space-group) var(--space-component);
  }

  .tag-page-head h1 {
    margin: 0;
    overflow-wrap: anywhere;
  }

  .tag-hash {
    color: var(--color-muted);
    font-weight: var(--weight-regular);
    margin-inline-end: 0.02em;
  }

  .tag-page-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-tight) var(--space-related);
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .tag-page-meta i {
    font-style: normal;
    color: var(--color-text);
  }

  .tag-page-definition {
    margin: var(--space-tight) 0 0; /* the rest: body text (base.css) */
  }

  .tag-page-note {
    margin: var(--space-tight) 0 0;
    max-inline-size: var(--width-read);
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .tag-page-private {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--color-text);
  }

  .tag-page-private .icon {
    width: 1rem;
    height: 1rem;
  }

  /* "gehört zu #Klassik": a way, so a link in the accent. */
  .tag-page-broader {
    color: var(--color-accent);
    text-decoration: none;
  }

  .tag-page-broader:hover {
    text-decoration: underline;
    text-underline-offset: 0.2em;
  }

  .tag-page-narrower {
    display: grid;
    gap: var(--space-related);
    margin-block-start: var(--space-group);
  }

  .tag-page-narrower-label {
    margin: 0;
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .tag-chips-links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  /* A tag that leads to its page: the quiet chip of the work page, which
     takes the accent's tint under the pointer. */
  a.tag-link,
  a.tag-chip-label {
    text-decoration: none;
    color: inherit;
  }

  .tag-link {
    transition: background-color var(--motion-quick) var(--ease-standard), color var(--motion-quick) var(--ease-standard);
  }

  .tag-link:hover,
  .tag-link:focus-visible {
    background: color-mix(in srgb, var(--color-accent) 11%, var(--color-surface));
    color: var(--color-accent);
  }

  a.tag-chip-label:hover {
    text-decoration: underline;
    text-underline-offset: 0.2em;
  }

  /* As in the collection: the filters, then a group further the count,
     right above the tiles it counts. */
  .tag-page-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related) var(--space-group);
    margin-block-end: var(--space-group);
  }

  .tag-page-tools {
    margin-block-end: var(--space-related);
  }

  /* "Getaggt von [allen | Freunden]": the label quiet, before its choice. */
  .tag-page-who {
    display: flex;
    align-items: center;
    gap: var(--space-related);
  }

  .tag-page-who-label {
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  /* Under a broader tag: which narrower one brought the work. */
  .tile-tag-via {
    color: var(--color-muted);
  }


  /* ---------- Discover with tags (card 4.4b, the preview "Tag-Seiten" 4b) ----------
     Under the search a row of tags to swipe, "Alle Tags" at its end; the
     chosen ones below it with × and how they combine; their works in the
     rows' place. While searching, the search has the page. */
  .tag-bar {
    display: flex;
    align-items: center;
    gap: var(--space-related);
    min-width: 0;
    margin-block-start: var(--space-group);
  }

  /* The soft edge at the end says "there is more" when the row runs on;
     it lies over room of its own (padding), so a row that fits shows
     every tag whole. */
  /* Sideways only: overflow-x alone would let the row scroll a little up
     and down too, by the chips' invisible tap areas (44 px around a 32 px
     chip). Room above and below exactly as large, taken back by the
     margin, holds them and the focus ring; nothing is left to scroll. */
  .tag-bar-track {
    --tap-reach: calc((var(--target-min) - var(--control-height-compact)) / 2);
    display: flex;
    gap: var(--space-2);
    min-width: 0;
    padding-block: var(--tap-reach);
    margin-block: calc(-1 * var(--tap-reach));
    padding-inline-end: var(--space-6);
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    mask-image: linear-gradient(to right, #000 calc(100% - var(--space-6)), transparent);
  }

  .tag-bar-track > * {
    flex: none;
  }

  .tag-bar-all {
    flex: none;
    gap: var(--space-1);
    margin-inline-start: auto;
  }

  .tag-bar-all .icon {
    width: 1rem;
    height: 1rem;
  }

  a.tag-pick {
    text-decoration: none;
  }

  .tag-chosen {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-related) var(--space-group);
    margin-block-start: var(--space-group);
  }

  .tag-chosen[hidden] {
    display: none;
  }

  /* The chosen tags by facet, its name above its chips, the groups one
     below the other; how they combine and "Alle abwählen" below them. */
  .tag-chosen-groups {
    flex: 1 0 100%;
    display: grid;
    gap: var(--space-group);
  }

  /* How they combine belongs to all of them: a group's distance below. */
  .tag-chosen:has(.tag-chosen-groups) {
    row-gap: var(--space-group);
  }

  .tag-chosen-group .tag-side-label {
    margin-block-end: var(--space-related);
  }

  /* What the side sheet's search finds, by facet as below it. */
  .tag-sheet-found [data-tag-pool]:has(> .tag-sheet-group) {
    display: grid;
    gap: var(--space-group);
  }

  .tag-results:not(:empty) {
    display: grid;
    gap: var(--space-group);
    margin-block-start: var(--space-component);
  }

  .discover .add-form:not(:has(#q:placeholder-shown)) ~ .tag-bar,
  .discover .add-form:not(:has(#q:placeholder-shown)) ~ .tag-chosen,
  .discover:has(#results > *) .tag-results {
    display: none;
  }

  /* Tags chosen: their works stand in the rows' place; the feed stays in
     the page (not drawn), so every row keeps how far it was scrolled. */
  .discover:has(#tag-results > *) .feed {
    content-visibility: hidden;
  }

  /* ---------- All tags in the side sheet ---------- */
  .tag-side-label {
    margin: 0 0 var(--space-related);
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .tag-side-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  .tag-side-facets {
    display: grid;
  }

  .tag-side-facet summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--target-min);
    font-size: var(--text-meta);
    cursor: pointer;
    list-style: none;
  }

  .tag-side-facet summary::-webkit-details-marker {
    display: none;
  }

  .tag-side-facet summary .icon {
    width: 1rem;
    height: 1rem;
    color: var(--color-muted);
    transition: rotate var(--motion-quick) var(--ease-standard);
  }

  .tag-side-facet[open] summary .icon {
    rotate: 90deg;
  }

  .tag-side-count {
    margin-inline-start: auto;
    font-size: var(--text-fine);
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  .tag-side-facet .tag-side-chips {
    padding-block-end: var(--space-group);
  }

  .tag-sheet-search input {
    min-height: var(--control-height-compact);
  }

  /* The sheet's first level (search, found, all): its parts apart as
     the sheet's body sets them; a page may add a level below (card 4.4d). */
  .tag-sheet-main {
    display: grid;
    gap: var(--space-group);
  }

  /* The friends' tags, then the facets: a group apart. */
  .tag-sheet-all {
    display: grid;
    gap: var(--space-group);
  }

  .tag-sheet-all[hidden],
  .tag-sheet-found[hidden] {
    display: none;
  }
}

/* css/components/taste.css */
/* taste.css – chips to choose genres and moods (components.TasteSet; card
   2.17, the onboarding; since card 4.4c also "Weitere Genres" in Settings
   → Entdecken). A small label over the chips; the rarer ones fold away
   behind "Mehr" (taste.js). */

@layer components {
  .taste-set {
    display: grid;
    gap: var(--space-related);
  }

  .taste-set[hidden] {
    display: none;
  }

  .taste-label {
    margin: 0;
    font-size: var(--text-fine);
    color: var(--color-muted);
    text-wrap: pretty;
  }

  .taste-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-related);
  }

  .taste-chip-form {
    display: contents;
  }

  .taste-more {
    justify-self: start;
  }

  /* Folded: the rarer chips wait, except the ones chosen. */
  .taste-set.is-folding:not(.is-open) .taste-chip-form.is-extra:not(:has([aria-pressed="true"])) {
    display: none;
  }
}

/* css/components/tile.css */
/* tile.css – the work tile, a work in a grid (card 2.13a; feed-und-algorithmus.md, 1.3).

   The grid is a shelf: in every row the pictures stand on one shared lower
   edge, as books and records do, and the titles below all start on one
   line. A poster stands taller than a square album cover; a video's wide
   picture spans two columns, so it is not tiny next to the posters.

   One tile, one picture, nothing copied: the tile keeps its place in the
   grid as an empty frame in the medium's proportions (.tile-media). In it
   lies the card with the picture, exactly as large as the frame. Hovering
   (after a short pause, so passing over the grid stays calm) or the
   keyboard focus lets this very card grow beyond its frame by 10 %, gives
   it a surface, and unrolls the facts and actions below the picture. The
   frame stays, so the card lies over the next row and nothing moves.
   CSS only. On touch screens there is no hover: a tap opens the work page,
   a long press lifts the tile to drag it onto a list (drag.js). */

@layer components {
  .tile-grid {
    --tile-min: 9.5rem;
    display: grid;
    /* Two columns at the least, even at 320 px; never wider than the screen. */
    grid-template-columns: repeat(auto-fill, minmax(min(var(--tile-min), calc(50% - var(--space-group) / 2)), 1fr));
    grid-auto-flow: row dense; /* a wide video leaves no hole at the end of a row */
    column-gap: var(--space-group);
    row-gap: var(--space-siblings);
    margin: var(--space-group) 0 0;
    padding: 0;
    list-style: none;
  }

  /* ---------- The tile: picture on the shelf, title below ---------- */
  .tile {
    /* The wait before the card opens on hover, and how far it grows. */
    --tile-intent: 150ms;
    --tile-grow: 1.1;
    --tile-lift: var(--space-related); /* the open card's padding around the picture */
    position: relative;
    display: grid;
    grid-row: span 2;
    grid-template-rows: subgrid; /* the rows of the grid: one shelf, one line of titles */
    row-gap: var(--space-related);
    min-width: 0;
  }

  .tile-wide {
    grid-column: span 2;
  }

  .tile:hover,
  .tile:has(:focus-visible) {
    z-index: 2; /* the open card lies over the next row */
  }

  /* The tile's place on the shelf, in the medium's proportions. It never
     changes size; the card in it may grow beyond it. */
  .tile-media {
    grid-row: 1;
    position: relative;
    align-self: end;
    aspect-ratio: 2 / 3;
  }

  .tile-square .tile-media {
    aspect-ratio: 1 / 1;
  }

  .tile-wide .tile-media {
    aspect-ratio: 16 / 9;
  }

  /* The picture lies on the symbol of the medium (as on the work card):
     while it loads, or when a source has none, the symbol shows. */
  .tile-image {
    display: grid;
  }

  .tile-image > * {
    grid-area: 1 / 1;
  }

  .tile-image img,
  .tile-image-empty {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 2 / 3;
    object-fit: cover;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
  }

  .tile-square .tile-image img,
  .tile-square .tile-image-empty {
    aspect-ratio: 1 / 1;
  }

  .tile-wide .tile-image img,
  .tile-wide .tile-image-empty {
    aspect-ratio: 16 / 9;
  }

  .tile-image-empty {
    display: grid;
    place-items: center;
    color: var(--color-muted);
    background: color-mix(in srgb, var(--color-line) 75%, var(--color-bg));
    box-shadow: none;
  }

  .tile-image-empty .icon {
    width: 1.5rem;
    height: 1.5rem;
    stroke-width: 1.5;
  }

  /* Below the picture: the title, and in the rows one swipes the main
     people (feed.css). */
  .tile-head {
    grid-row: 2;
    min-width: 0;
  }

  /* The medium's symbol before the title and the line of main people:
     only in the rows one swipes (feedback of 17.10.2026). */
  .tile-kind,
  .tile-makers {
    display: none;
  }

  /* The title is the tile's link (first in the markup, for screen
     readers; shown below the picture). At most two lines. */
  .tile-title {
    grid-row: 2;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    font-family: var(--font-read);
    font-size: var(--text-read);
    font-weight: var(--weight-regular);
    line-height: 1.25;
    color: var(--color-text);
    text-decoration: none;
    overflow-wrap: anywhere;
  }

  .tile-title::after {
    content: "";
    position: absolute;
    inset: 0;
  }

  .tile-title:focus-visible {
    outline: none;
  }

  .tile:has(.tile-title:focus-visible) .tile-image img,
  .tile:has(.tile-title:focus-visible) .tile-image-empty {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  /* ---------- The card: the picture, and what unrolls below it ---------- */
  .tile-card {
    position: absolute;
    z-index: 1; /* over the title's link, so its own buttons can be reached */
    top: 0;
    inset-inline: 0;
    display: grid;
    grid-template-rows: auto 0fr; /* the facts are rolled up */
    padding: 0;
    border-radius: var(--radius-m);
    background: transparent;
    box-shadow: none;
    outline: 1px solid transparent; /* in the dark, a line replaces the shadow */
    /* The facts do not roll up as the card grows; they only fade in
       (.tile-more): rolling moved them against the card's growth (the
       initiator, 13.10.2026). */
    transition-property: top, left, right, padding, background-color, box-shadow, outline-color;
    transition-duration: var(--motion-quick);
    transition-timing-function: var(--ease-exit);
  }

  /* Open (keyboard focus here; hover below): 10 % larger, grown from the picture's upper edge (which stays
     where it was), with a surface; the facts unroll. */
  .tile:has(:focus-visible) .tile-card {
    top: calc(-1 * var(--tile-lift));
    inset-inline: calc((1 - var(--tile-grow)) * 50% - var(--tile-lift));
    grid-template-rows: auto 1fr;
    padding: var(--tile-lift) var(--tile-lift) var(--space-group);
    background: var(--color-surface);
    box-shadow: var(--shadow-floating);
    transition-timing-function: var(--ease-enter);
  }

  @media (hover: hover) {
    .tile:hover .tile-card {
      top: calc(-1 * var(--tile-lift));
      inset-inline: calc((1 - var(--tile-grow)) * 50% - var(--tile-lift));
      grid-template-rows: auto 1fr;
      padding: var(--tile-lift) var(--tile-lift) var(--space-group);
      background: var(--color-surface);
      box-shadow: var(--shadow-floating);
      transition-timing-function: var(--ease-enter);
      transition-delay: var(--tile-intent);
    }
  }

  /* The picture's way to the work page (the same as the title's). */
  .tile-card-link {
    display: block;
    color: inherit;
    text-decoration: none;
  }

  .tile-card-link::after {
    content: "";
    position: absolute;
    inset: 0;
  }

  /* What unrolls: hidden at rest, also from the keyboard and screen
     readers (the title link says what the tile is). */
  .tile-more {
    min-height: 0;
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    transition:
      opacity var(--motion-quick) var(--ease-exit),
      visibility 0s linear var(--motion-quick);
  }

  .tile:has(:focus-visible) .tile-more {
    opacity: 1;
    visibility: visible;
    transition:
      opacity var(--motion-quick) var(--ease-enter),
      visibility 0s;
  }

  @media (hover: hover) {
    .tile:hover .tile-more {
      opacity: 1;
      visibility: visible;
      transition:
        opacity var(--motion-quick) var(--ease-enter) var(--tile-intent),
        visibility 0s linear var(--tile-intent);
    }
  }

  .tile-card-body {
    display: flow-root; /* holds the floating "+ / −" */
    min-width: 0;
    margin-block-start: var(--space-related);
    font-size: var(--text-meta);
    line-height: var(--leading-ui);
    color: var(--color-muted);
  }

  .tile-card-body p {
    margin: 0;
  }

  .tile-card-body p + p {
    margin-block-start: var(--space-tight);
  }

  .tile-card-title {
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: 1.25;
    color: var(--color-text);
    margin-block-end: var(--space-tight) !important;
    text-wrap: balance;
  }

  .tile-card-meta {
    font-variant-numeric: tabular-nums;
  }

  .tile-card-people b,
  .tile-card-friends b,
  .tile-card-from b {
    font-weight: var(--weight-regular);
    color: var(--color-text);
  }

  .tile-card-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 4;
    line-clamp: 4;
    overflow: hidden;
    margin-block-start: var(--space-related) !important;
  }

  /* The bookmark hangs from the card's upper edge over the picture's
     corner, "+ / −" right below it in the same column; the title flows
     around it, so a long title keeps the card's full width. */
  .tile-card .save {
    position: static; /* the bookmark hangs from the card, not from here */
    float: inline-end;
    display: grid;
    justify-items: center;
    width: var(--target-min);
    margin-inline-start: var(--space-related);
  }

  .tile-card .save > .mark {
    position: absolute;
    z-index: 1;
    top: 0; /* of the card */
    left: auto;
    right: calc(var(--tile-lift) + (var(--target-min) - 1.375rem) / 2);
    translate: none;
  }

  .tile-card .save > .qa,
  .tile-card .save > .qa-form {
    position: relative;
    z-index: 1;
    top: auto;
    left: auto;
    translate: none;
  }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .tile:is(:hover, :has(:focus-visible)) .tile-card {
      outline-color: var(--color-line);
    }
  }

  :root[data-theme="dark"] .tile:is(:hover, :has(:focus-visible)) .tile-card {
    outline-color: var(--color-line);
  }
}

/* css/components/toast.css */
/* toast.css – the short note at the bottom of the screen (component
   Toast, card 2.5b): a sentence and at most one action. The sentence may
   wrap onto a second line; the action keeps its full width and never
   wraps, so it is never cut off. One place for the whole platform. */

@layer components {
  .toast-region {
    position: fixed;
    left: 50%;
    bottom: calc(var(--space-siblings) + env(safe-area-inset-bottom));
    translate: -50% 0;
    z-index: 50;
    width: max-content;
    max-width: min(32rem, 100% - 2 * var(--gutter));
  }

  .toast {
    display: flex;
    align-items: center;
    gap: var(--space-group);
    min-height: var(--target-min);
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
    background: var(--color-text);
    color: var(--color-bg);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-floating);
    font-size: var(--text-meta);
    animation: toast-in var(--motion-gentle) var(--ease-enter);
  }

  .toast-text {
    flex: 1 1 auto;
    min-width: 0;
  }

  .toast form {
    margin: 0;
  }

  .toast > :not(.toast-text) {
    flex: none;
  }

  .toast .btn {
    margin: 0;
    white-space: nowrap;
    color: var(--color-bg);
  }

  .toast .btn:hover {
    background: color-mix(in srgb, var(--color-bg) 14%, transparent);
  }

  @keyframes toast-in {
    from {
      opacity: 0;
      translate: 0 var(--space-2);
    }
  }
}

/* css/components/visibility.css */
/* visibility.css – "Sichtbar für: …" with the four symbols in one
   container (card 2.5d): at the bottom of the work page for one entry,
   at the top of the collection for the whole collection. A tap chooses and
   saves; the chosen surface glides (segment.js). On a phone text and
   symbols stand in two lines. */

@layer components {
  .visibility-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-related) var(--space-group);
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .seg-icons button {
    min-width: 2.25rem;
    padding-inline: var(--space-2);
  }

  .seg-icons button[disabled] {
    opacity: 0.35;
    cursor: not-allowed;
  }

  .visibility-row-hint {
    flex-basis: 100%;
    text-align: end;
  }

  @media (max-width: 47.99rem) {
    .visibility-row {
      flex-direction: column;
      align-items: flex-end;
    }
  }
}

/* css/components/welcome.css */
/* welcome.css – the onboarding (card 2.17; approved in the preview
   docs/05-design/vorschau/daizee-onboarding.html, 07.10.2026). Only what
   is its own: the steps and their rhythm, the daisy that unfolds, the
   friends around it, the footer with "Zurück" and "Weiter", the finale.
   Chips, tiles, list rows, the daisy's colours and its blooming, the
   sliders and the preview come from their own files. Without JavaScript
   the steps stand one below the other; welcome.js adds .is-live. */

@layer components {
  .welcome {
    display: grid;
    gap: var(--space-section);
    width: min(100%, var(--width-form));
    margin-inline: auto;
  }

  .welcome.is-live {
    gap: 0;
  }

  .welcome.is-live .welcome-step:not(.is-current),
  .welcome.is-live .welcome-end {
    display: none;
  }

  .welcome-step {
    display: grid;
    align-content: start;
    gap: var(--space-component);
    min-width: 0;
  }

  .welcome-intro {
    display: grid;
    gap: var(--space-related);
  }

  .welcome-title {
    margin: 0;
    font-family: var(--font-read);
    font-size: var(--text-page-title);
    font-weight: var(--weight-medium);
    line-height: 1.2;
    text-wrap: balance;
  }

  .welcome-lead {
    margin: 0;
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: var(--leading-read);
    color: var(--color-muted);
    text-wrap: pretty;
  }

  .welcome-group {
    display: grid;
    gap: var(--space-group);
    min-width: 0;
  }

  .welcome-subtitle {
    display: flex;
    align-items: baseline;
    margin: 0;
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-semibold);
  }

  .welcome-hint,
  .welcome-taste-label {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
    text-wrap: pretty;
  }

  .welcome-taste-label {
    font-size: var(--text-fine);
  }

  .welcome-end {
    display: flex;
    justify-content: center;
    padding-block-end: var(--space-page-end);
  }

  /* ---------- The progress and "Überspringen" in the header ---------- */
  .welcome-tools {
    flex: 1;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: var(--space-group);
  }

  .welcome-tools[hidden] {
    display: none;
  }

  .welcome-dots {
    grid-column: 2;
    display: flex;
    align-items: center;
    gap: var(--space-related);
    margin: 0;
    padding: 0;
    list-style: none;
    transition: opacity var(--motion-gentle) var(--ease-standard);
  }

  .welcome-dots li {
    display: flex;
  }

  .welcome-dots button {
    position: relative;
    width: 0.5rem;
    height: 0.5rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius-full);
    background: var(--color-line);
    cursor: default;
    transition:
      width var(--motion-gentle) var(--ease-standard),
      background-color var(--motion-gentle) var(--ease-standard);
  }

  /* A target of 44 px around the small point. */
  .welcome-dots button::after {
    content: "";
    position: absolute;
    inset: -1.125rem -0.25rem;
  }

  .welcome-dots button.is-done {
    background: var(--color-muted);
    cursor: pointer;
  }

  .welcome-dots button[aria-current="step"] {
    width: 1.5rem;
    background: var(--color-accent);
  }

  .welcome-skip {
    grid-column: 3;
    justify-self: end;
  }

  .welcome-dots button:disabled {
    cursor: default;
  }

  .welcome-skip form {
    margin: 0;
  }

  /* Buttons in the frame keep their words on one line ("Nicht jetzt"). */
  .welcome-frame .btn,
  .welcome-tools .btn {
    white-space: nowrap;
  }

  /* ---------- The daisy, from the logo: eight petals around a centre ---------- */
  .welcome-bloom {
    display: block;
    width: 6rem;
    height: 6rem;
    overflow: visible;
  }

  .welcome-petals,
  .welcome-petal,
  .welcome-center {
    transform-box: view-box;
    transform-origin: 24px 24px;
  }

  .welcome-petal {
    fill: var(--color-moment);
  }

  .welcome-center {
    fill: var(--color-accent);
  }

  /* It unfolds petal by petal, the flower turning a little into place
     (an "erstes Mal", bewegung-klang-haptik.md 4); welcome.js adds
     .is-playing when the step shows. */
  .is-playing .welcome-petals {
    animation: welcome-turn calc(var(--motion-slow) * 1.6) var(--ease-curtain) both;
  }

  .is-playing .welcome-petal {
    animation: welcome-petal var(--motion-slow) var(--ease-curtain) both;
  }

  .is-playing .welcome-center {
    animation: welcome-center var(--motion-gentle) var(--ease-enter) both;
  }

  .welcome-petals > :nth-child(2) .welcome-petal { animation-delay: calc(var(--motion-instant) * 0.55); }
  .welcome-petals > :nth-child(3) .welcome-petal { animation-delay: calc(var(--motion-instant) * 1.1); }
  .welcome-petals > :nth-child(4) .welcome-petal { animation-delay: calc(var(--motion-instant) * 1.65); }
  .welcome-petals > :nth-child(5) .welcome-petal { animation-delay: calc(var(--motion-instant) * 2.2); }
  .welcome-petals > :nth-child(6) .welcome-petal { animation-delay: calc(var(--motion-instant) * 2.75); }
  .welcome-petals > :nth-child(7) .welcome-petal { animation-delay: calc(var(--motion-instant) * 3.3); }
  .welcome-petals > :nth-child(8) .welcome-petal { animation-delay: calc(var(--motion-instant) * 3.85); }

  @keyframes welcome-turn {
    from {
      rotate: -40deg;
    }
  }

  @keyframes welcome-petal {
    from {
      scale: 0.12;
      opacity: 0;
    }
    55% {
      opacity: 1;
    }
    70% {
      scale: 1.07;
    }
  }

  @keyframes welcome-center {
    from {
      scale: 0;
    }
    70% {
      scale: 1.12;
    }
  }

  /* The daisy is one thing from "Du" over the passkey offer to "Deine
     Leute": the page changes around it (view transition, base.css). */
  .welcome-hero .welcome-bloom {
    view-transition-name: welcome-daisy;
  }

  /* Arrived from "Du": instead of unfolding again, it rejoices: turns a
     little, grows a moment, and settles (welcome.js, .is-joyful). */
  .is-joyful .welcome-petals,
  .is-joyful .welcome-petal,
  .is-joyful .welcome-center {
    animation: none;
  }

  .is-joyful .welcome-hero .welcome-bloom {
    animation: welcome-joy calc(var(--motion-slow) * 1.4) var(--ease-curtain) both;
  }

  @keyframes welcome-joy {
    40% {
      rotate: 28deg;
      scale: 1.2;
    }
    70% {
      rotate: -6deg;
      scale: 0.97;
    }
  }

  /* ---------- Du: name, short name, language, age ---------- */
  .welcome-hero-you {
    height: 7rem;
  }

  .welcome-form {
    margin: 0;
  }

  /* The short name with its "@" in front, inside the field. */
  .handle-field {
    position: relative;
    display: grid;
  }

  .handle-at {
    position: absolute;
    inset-block: 0;
    inset-inline-start: var(--space-3);
    display: grid;
    place-items: center;
    color: var(--color-muted);
    pointer-events: none;
  }

  .handle-field input {
    padding-inline-start: calc(var(--space-3) + 1.1em);
  }

  .welcome-language {
    display: grid;
    gap: var(--space-related);
    margin: 0;
    padding: 0;
    border: 0;
  }

  .welcome-language .seg {
    justify-self: start;
  }

  /* ---------- 1 · Deine Leute: the friends step out of the daisy ---------- */
  .welcome-hero {
    position: relative;
    display: grid;
    place-items: center;
    height: 9rem;
    margin-block-end: var(--space-group);
  }

  .welcome-hero .welcome-bloom {
    position: relative;
    z-index: 1;
  }

  .welcome-friend {
    --x: 0rem;
    --y: 0rem;
    position: absolute;
    left: 50%;
    top: 50%;
    display: grid;
    justify-items: center;
    gap: var(--space-tight);
    translate: calc(-50% + var(--x)) calc(-50% + var(--y));
  }

  .has-1 .welcome-friend-1 { --x: 7.5rem; --y: 0.5rem; }
  .has-2 .welcome-friend-1,
  .has-3 .welcome-friend-1,
  .has-4 .welcome-friend-1 { --x: -7.5rem; --y: 0.5rem; }
  .has-2 .welcome-friend-2,
  .has-3 .welcome-friend-2,
  .has-4 .welcome-friend-2 { --x: 7.5rem; --y: 0.5rem; }
  .has-3 .welcome-friend-3,
  .has-4 .welcome-friend-3 { --x: -4.75rem; --y: -2.75rem; }
  .has-4 .welcome-friend-4 { --x: 4.75rem; --y: -2.75rem; }

  .is-playing .welcome-friend {
    animation: welcome-friend var(--motion-slow) var(--ease-curtain) calc(var(--motion-slow) * 0.75) both;
  }

  .is-playing .welcome-friend-2 { animation-delay: calc(var(--motion-slow) * 0.75 + var(--motion-quick)); }
  .is-playing .welcome-friend-3 { animation-delay: calc(var(--motion-slow) * 0.75 + var(--motion-quick) * 2); }
  .is-playing .welcome-friend-4 { animation-delay: calc(var(--motion-slow) * 0.75 + var(--motion-quick) * 3); }

  @keyframes welcome-friend {
    from {
      translate: -50% -50%;
      scale: 0.3;
      opacity: 0;
    }
  }

  .welcome-friend .avatar {
    box-shadow: 0 0 0 3px var(--color-bg);
  }

  .welcome-friend-name {
    max-width: 7rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-meta);
    font-weight: var(--weight-semibold);
  }

  /* The sentence they came with, on a card pushed across the table: the
     work's picture on the left, the sentence beside it. */
  .welcome-came {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: var(--space-group);
    margin: 0;
    padding: var(--space-group);
    border-radius: var(--radius-l);
    background: var(--color-surface);
    box-shadow: var(--shadow-raised);
  }

  .welcome-came:not(:has(.welcome-came-image)) {
    grid-template-columns: minmax(0, 1fr);
  }

  .welcome-came-image {
    display: block;
    width: 4.25rem;
    aspect-ratio: 2 / 3;
    object-fit: cover;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
  }

  .welcome-came.is-square .welcome-came-image {
    width: 5rem;
    aspect-ratio: 1 / 1;
  }

  .welcome-came.is-wide .welcome-came-image {
    width: 7rem;
    aspect-ratio: 16 / 9;
  }

  .welcome-came-body {
    display: grid;
    gap: var(--space-related);
  }

  .welcome-sentence {
    margin: 0;
    font-family: var(--font-read);
    font-size: var(--text-heading);
    line-height: 1.35;
    text-wrap: pretty;
  }

  .welcome-came-from {
    display: flex;
    align-items: flex-start;
    gap: var(--space-related);
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .welcome-came-from i {
    font-family: var(--font-read);
  }

  /* Initials in warm tones around the daisy and on the card (a picture,
     where there is one, stays as it is). */
  .welcome-friend-1 .avatar-empty,
  .welcome-friend-3 .avatar-empty,
  .welcome-came-from .avatar-empty {
    background: color-mix(in srgb, var(--color-accent) 16%, var(--color-surface));
    color: var(--color-text);
  }

  .welcome-friend-2 .avatar-empty,
  .welcome-friend-4 .avatar-empty {
    background: color-mix(in srgb, var(--color-moment) 34%, var(--color-surface));
    color: var(--color-text);
  }

  .is-playing .welcome-came {
    animation: welcome-card var(--motion-slow) var(--ease-curtain) calc(var(--motion-slow) * 1.5) both;
  }

  @keyframes welcome-card {
    from {
      opacity: 0;
      translate: -1.5rem 1rem;
      rotate: -2deg;
    }
  }

  .welcome-invite {
    display: grid;
    justify-items: start;
    gap: var(--space-related);
  }

  /* ---------- 2 · Was dich begeistert ---------- */
  .welcome-media {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: var(--space-related);
  }

  .welcome-medium {
    flex-direction: column;
    gap: var(--space-2);
    min-height: 5rem;
    padding: var(--space-3) var(--space-1);
  }

  .welcome-medium .icon {
    width: 1.5rem;
    height: 1.5rem;
  }

  .welcome-medium.tapped {
    animation: status-nod var(--motion-quick) var(--ease-standard);
  }

  /* "Tipp oben ein Medium an …" only while no genres show. */
  [data-welcome-genres]:has(.taste-set:not([hidden])) [data-genres-empty] {
    display: none;
  }

  /* ---------- 3 · Was du liebst ---------- */
  .welcome-results:empty {
    display: none;
  }

  .welcome-results:not(:empty) ~ .welcome-suggest {
    display: none;
  }

  .welcome-suggest {
    display: grid;
    gap: var(--space-related);
  }

  /* ---------- 4 · Listen ---------- */
  .welcome-works:empty {
    display: none;
  }

  .welcome-works {
    display: grid;
    gap: var(--space-related);
  }

  .welcome-lists {
    display: grid;
    gap: var(--space-related);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  /* ---------- 5 · Wie dich deine Leute erreichen ---------- */
  .welcome-reach,
  .welcome-reach-options {
    display: grid;
    gap: var(--space-related);
    margin: 0;
    padding: 0;
    border: 0;
  }

  .welcome-reach-option {
    justify-content: flex-start;
    align-items: flex-start;
    gap: var(--space-group);
    padding: var(--space-4);
    text-align: start;
  }

  .welcome-reach-option .icon {
    width: 1.5rem;
    height: 1.5rem;
    margin-block-start: 0.1rem;
    color: var(--color-muted);
  }

  .welcome-reach-option:has(input:checked) .icon {
    color: var(--color-accent);
  }

  .welcome-reach-text {
    display: grid;
    gap: var(--space-tight);
  }

  .welcome-reach-name {
    font-size: var(--text-control);
    font-weight: var(--weight-semibold);
  }

  .welcome-reach-desc {
    color: var(--color-muted);
    text-wrap: pretty;
  }

  .welcome-reach-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-meta);
  }

  .welcome-reach-table th,
  .welcome-reach-table td {
    padding: var(--space-1) 0;
    font-weight: var(--weight-regular);
    text-align: start;
  }

  .welcome-reach-table thead th {
    font-size: var(--text-fine);
    color: var(--color-muted);
    text-align: center;
  }

  .welcome-reach-table thead th:first-child {
    text-align: start;
  }

  .welcome-cell {
    width: 3rem;
    text-align: center;
    color: var(--color-line);
    transition: color var(--motion-quick) var(--ease-standard);
  }

  .welcome-cell .icon {
    width: 1.125rem;
    height: 1.125rem;
  }

  /* Which mails each answer brings, without a round trip: the cells say
     it for all three answers, the chosen one decides what lights up. */
  .welcome-cell.is-on,
  .welcome:has([name="reach"][value="app"]:checked) .welcome-cell[data-app="true"],
  .welcome:has([name="reach"][value="both"]:checked) .welcome-cell[data-both="true"],
  .welcome:has([name="reach"][value="mail"]:checked) .welcome-cell[data-mail="true"] {
    color: var(--color-accent);
  }

  .welcome:not(:has([name="reach"][value="app"]:checked)) .welcome-reach-hint[data-for="app"],
  .welcome:has([name="reach"][value="app"]:checked) .welcome-reach-hint[data-for="other"] {
    display: none;
  }

  /* ---------- The footer: "Zurück" always left, "Weiter" always right ---------- */
  .welcome-foot {
    position: sticky;
    bottom: 0;
    z-index: 5;
    margin-block-start: var(--space-component);
    padding-block: var(--space-group);
    background: var(--color-bg);
  }

  .welcome-foot[hidden] {
    display: none;
  }

  /* The content fades out under it, instead of a line. */
  .welcome-foot::before {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 100%;
    height: var(--space-siblings);
    background: linear-gradient(to top, var(--color-bg), transparent);
    pointer-events: none;
  }

  .welcome-foot-inner {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "back slot next";
    align-items: center;
    gap: var(--space-group);
  }

  .welcome-back {
    grid-area: back;
  }

  .welcome-back.is-first {
    visibility: hidden;
  }

  .welcome-next {
    grid-area: next;
  }

  /* "Deine Sammlung": where the loved works fly to (step 3). */
  .welcome-tray {
    grid-area: slot;
    justify-self: center;
    display: flex;
    align-items: center;
    gap: var(--space-group);
    min-width: 0;
  }

  .welcome-tray[hidden] {
    display: none;
  }

  .welcome-tray-stack {
    position: relative;
    flex: none;
    width: 3.25rem;
    height: 2.75rem;
  }

  .welcome-tray-stack:empty {
    display: none;
  }

  .welcome-tray-stack img {
    position: absolute;
    top: 0;
    width: 1.875rem;
    height: 2.75rem;
    object-fit: cover;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
    transition:
      left var(--motion-gentle) var(--ease-standard),
      rotate var(--motion-gentle) var(--ease-standard);
  }

  .welcome-tray-stack img:nth-child(1) { left: 0; rotate: -7deg; }
  .welcome-tray-stack img:nth-child(2) { left: 0.7rem; rotate: 0deg; }
  .welcome-tray-stack img:nth-child(3) { left: 1.4rem; rotate: 7deg; }

  .welcome-tray-text {
    display: grid;
    min-width: 0;
    font-size: var(--text-meta);
  }

  .welcome-tray-text b {
    font-weight: var(--weight-semibold);
  }

  .welcome-tray-text span {
    color: var(--color-muted);
  }

  .welcome-tray-moment {
    font-family: var(--font-read);
    font-size: var(--text-read);
    font-style: italic;
  }

  /* "Erste Male": the words open like a curtain, from the middle. */
  .welcome-tray-moment:not([hidden]) {
    animation: welcome-curtain var(--motion-slow) var(--ease-curtain) both;
  }

  .welcome-tray:has(.welcome-tray-moment:not([hidden])) .welcome-tray-text {
    display: none;
  }

  @keyframes welcome-curtain {
    from {
      clip-path: inset(0 50% 0 50%);
      opacity: 0.4;
    }
  }

  /* A copy of a picture flying (into the tray, onto a list). */
  .welcome-flyer {
    position: fixed;
    z-index: 70;
    margin: 0;
    object-fit: cover;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-floating);
    pointer-events: none;
  }

  /* ---------- The finale: the daisy blooms and flies into the logo ---------- */
  .welcome-finale {
    --finale-top: 0px;
    position: fixed;
    inset: 0;
    z-index: 60;
    display: grid;
    place-content: center;
    justify-items: center;
    gap: var(--space-group);
    padding: var(--gutter);
    text-align: center;
    /* The header stays in sight above it: that is where the daisy goes. */
    background: linear-gradient(transparent var(--finale-top), var(--color-bg) var(--finale-top));
  }

  .welcome-finale[hidden] {
    display: none;
  }

  .welcome-finale .welcome-bloom {
    width: 9rem;
    height: 9rem;
  }

  .welcome-finale-text {
    margin: 0;
    font-family: var(--font-read);
    font-size: var(--text-page-title);
    font-weight: var(--weight-medium);
  }

  .welcome-finale-sub {
    margin: 0;
    font-family: var(--font-read);
    color: var(--color-muted);
  }

  .welcome-finale.is-playing .welcome-finale-text {
    animation: welcome-curtain var(--motion-slow) var(--ease-curtain) calc(var(--motion-slow) * 0.4) both;
  }

  .welcome-finale.is-playing .welcome-finale-sub {
    animation: welcome-fade var(--motion-gentle) var(--ease-enter) var(--motion-slow) both;
  }

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

  .logo.is-waiting :is(.logo-petals, .logo-center) {
    opacity: 0;
  }

  /* The daisy of the logo blooms when the big one has landed in it (the
     blooming of collection.css, daisy-bloom). */
  .logo-petals.is-blooming {
    transform-box: fill-box;
    transform-origin: center;
    animation: daisy-bloom var(--motion-slow) var(--ease-curtain);
  }

  .is-finale .welcome-tools {
    opacity: 0;
    pointer-events: none;
  }

  /* ---------- Phone: the same order, condensed ---------- */
  @media (max-width: 47.99rem) {
    .welcome-tools {
      grid-template-columns: minmax(0, 1fr) auto;
    }

    .welcome-dots {
      grid-column: 1;
      justify-self: center;
    }

    .welcome-skip {
      grid-column: 2;
    }

    .welcome-media {
      gap: var(--space-2);
    }

    .welcome-medium {
      min-height: 4.5rem;
      font-size: var(--text-fine);
    }

    .welcome-friend-name {
      max-width: 5.5rem;
    }

    .has-1 .welcome-friend-1 { --x: 6.5rem; }
    .has-2 .welcome-friend-1,
    .has-3 .welcome-friend-1,
    .has-4 .welcome-friend-1 { --x: -6.5rem; }
    .has-2 .welcome-friend-2,
    .has-3 .welcome-friend-2,
    .has-4 .welcome-friend-2 { --x: 6.5rem; }

    /* The tray above; "Zurück" and a wide "Weiter" below. */
    .welcome-foot-inner {
      grid-template-columns: auto minmax(0, 1fr);
      grid-template-areas:
        "slot slot"
        "back next";
      gap: var(--space-related);
    }

    .welcome-tray {
      justify-self: start;
    }

    .welcome-next {
      justify-self: stretch;
    }

    .welcome-back.is-first {
      display: none;
    }

    .welcome-back.is-first + .welcome-tray + .welcome-next,
    .welcome-back.is-first ~ .welcome-next {
      grid-column: 1 / -1;
    }
  }
}

/* css/components/wordmark.css */
/* wordmark.css – the logo in the header (test of "Blume über dem i",
   card 2.6, 04.10.2026). The letters take the text colour, the petals the
   daisy yellow, the centre the accent: in light and dark mode alike. */

@layer components {
  .wordmark {
    display: inline-flex;
    align-items: center;
    min-height: var(--target-min);
    color: var(--color-text);
    text-decoration: none;
  }

  .wordmark .logo {
    display: block;
    height: 2.5rem;
    width: auto;
    overflow: visible;
  }

  .logo .logo-letters {
    fill: currentColor;
  }

  .logo .logo-petals {
    fill: var(--color-moment);
  }

  .logo .logo-center {
    fill: var(--color-accent);
  }

  /* On hover the daisy turns a little, the letters stay calm. */
  .wordmark .logo-petals,
  .wordmark .logo-center {
    transform-box: fill-box;
    transform-origin: center;
    transition: rotate var(--motion-gentle) var(--ease-standard);
  }

  .wordmark:hover .logo-petals {
    rotate: 30deg;
  }

  @media (max-width: 47.99rem) {
    .wordmark .logo {
      height: 2.125rem;
    }
  }
}

/* css/components/work-card.css */
/* work-card.css – a work in a list, the same card in the collection and in
   the result list (card 1.7b, design card 2.5b).

   Poster · text · a narrow side column. The text has three type roles:
   the title (reading face), the facts (original title in italics, year ·
   kind, who made it), and the actions. Lists are dense
   (data-density="compact"), so the roles keep small, fixed values there. */

@layer components {
  .work-card {
    position: relative;
    display: grid;
    grid-template-columns: 4.625rem minmax(0, 1fr) 2.75rem;
    grid-template-rows: auto auto 1fr auto;
    column-gap: var(--space-group);
    padding-block: var(--space-group);
    border-bottom: 1px solid var(--color-line);
    isolation: isolate; /* the hover surface below stays behind all content */
  }

  .work-card-wide {
    grid-template-columns: 8rem minmax(0, 1fr) 2.75rem;
  }

  /* Hovering lights a surface with a little air around poster and text,
     always behind the text (z-index -1 inside the card's own layer). */
  .work-card::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 0 calc(-1 * var(--space-related));
    border-radius: var(--radius-m);
    background: var(--color-surface);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--motion-quick) var(--ease-standard);
  }

  .work-card:hover::before {
    opacity: 1;
  }

  .work-card > * {
    position: relative;
  }

  /* The image lies on the symbol of the medium (one grid cell for both):
     while it loads, or when the source has none after all (daizee then
     sends an empty image), the symbol shows. At the top of its column, so
     a square album cover sits where a poster begins (card 2.12c). */
  .work-card-image {
    grid-column: 1;
    grid-row: 1 / -1;
    align-self: start;
    display: grid;
  }

  .work-card-image > * {
    grid-area: 1 / 1;
  }

  .work-card-image img,
  .work-card-image-empty {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 2 / 3;
    object-fit: cover;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-rest);
  }

  .work-card-wide .work-card-image img,
  .work-card-wide .work-card-image-empty {
    aspect-ratio: 16 / 9;
  }

  /* An album cover: square, as wide as a poster (card 2.12c). */
  .work-card-square .work-card-image img,
  .work-card-square .work-card-image-empty {
    aspect-ratio: 1 / 1;
  }

  /* No picture: a calm surface with the symbol of the medium. */
  .work-card-image-empty {
    display: grid;
    place-items: center;
    color: var(--color-muted);
    background: color-mix(in srgb, var(--color-line) 75%, var(--color-bg));
    box-shadow: none;
  }

  .work-card-image-empty .icon {
    width: 1.5rem;
    height: 1.5rem;
    stroke-width: 1.5;
  }

  /* Title and original title: one close group. */
  .work-card-head {
    grid-column: 2;
    grid-row: 1;
    min-width: 0;
  }

  .work-card-title {
    display: block;
    font-family: var(--font-read);
    font-size: var(--text-card-title);
    font-weight: var(--weight-regular);
    line-height: 1.25;
    color: var(--color-text);
    text-decoration: none;
    text-wrap: balance;
  }

  /* The whole card is the link (stretched over it from the static head);
     the side column and the page's buttons lie on top and stay buttons. */
  .work-card-title::after {
    content: "";
    position: absolute;
    inset: 0;
  }

  .work-card > .work-card-head,
  .work-card > .work-card-facts {
    position: static;
  }

  .work-card:hover .work-card-title {
    color: var(--color-accent);
  }

  .work-card-title:focus-visible {
    outline: none;
  }

  .work-card:has(.work-card-title:focus-visible) {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
    border-radius: var(--radius-m);
  }

  .work-card p {
    margin: 0;
  }

  .work-card-original {
    margin-block-start: var(--space-tight) !important;
    font-family: var(--font-read);
    font-style: italic;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  /* The facts: year · kind on the left, who made it on the right, on one
     baseline; the names share one edge, however long "Regie" or "Idee" is. */
  .work-card-facts {
    grid-column: 2;
    grid-row: 2;
    min-width: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    column-gap: var(--space-siblings);
    margin-block-start: var(--space-related);
    font-size: var(--text-meta);
    line-height: var(--leading-ui);
  }

  /* "Freier Eintrag", "Vorschlag" after year · kind (card 2.12c). */
  .work-card-meta .badge {
    margin-inline-start: var(--space-tight);
    vertical-align: baseline;
  }

  .work-card-meta,
  .work-card-hint {
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  .card-people {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 0 var(--space-related);
    margin: 0;
  }

  .card-people > div {
    display: contents;
  }

  .card-people dt {
    color: var(--color-muted);
  }

  .card-people dd {
    margin: 0;
  }

  /* The page's buttons (status in the collection), below the facts. */
  .work-card-actions {
    grid-column: 2;
    grid-row: 4;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-related);
    margin-block-start: var(--space-group);
  }

  .work-card-actions:empty {
    display: none;
  }

  /* The side column: the bookmark on top, "+ / −" in the vertical middle. */
  .work-card-side {
    grid-column: 3;
    grid-row: 1 / -1;
    z-index: 1;
    position: relative;
  }

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

  .save > .mark {
    position: absolute;
    top: calc(-1 * var(--space-group));
    left: 50%;
    translate: -50% 0;
  }

  .save > .qa,
  .save > .qa-form {
    position: absolute;
    top: 50%;
    left: 50%;
    translate: -50% -50%;
    margin: 0;
  }

  /* Text styles a work's facts share, also on the recommendation page
     (until card 2.5g gives it the work card). */
  .result-original {
    font-family: var(--font-read);
    font-style: italic;
    color: var(--color-muted);
  }

  .result-meta,
  .result-people {
    font-size: var(--text-s);
    color: var(--color-muted);
    margin-block-start: var(--space-1) !important;
  }

  @media (max-width: 36rem) {
    .work-card {
      grid-template-columns: 3.625rem minmax(0, 1fr) 2.75rem;
    }

    .work-card-wide {
      grid-template-columns: 6rem minmax(0, 1fr) 2.75rem;
    }

    .work-card-facts {
      grid-template-columns: minmax(0, 1fr);
    }

    /* Status buttons get the full width below poster and text. */
    .work-card-actions {
      grid-column: 1 / -1;
    }
  }
}

/* css/components/work.css */
/* work.css – the work page (card 1.4b, design card 2.5d). The work stands
   in the light (design principle 1); the distances follow one ladder
   (tokens.md, 3a): title → original title "related", → facts "group",
   → status and → note "component", → Empfehlen and → credits "section",
   → visibility "frame" (it belongs to the platform), → sources "group". */

@layer components {
  /* Poster left, head and the rest beside it; on a phone the head stands
     beside a small poster and everything else runs below at full width. */
  .work {
    position: relative;
    display: grid;
    grid-template-columns: var(--width-side) minmax(0, 1fr);
    grid-template-areas:
      "image head"
      "image text";
    grid-template-rows: auto 1fr;
    column-gap: var(--space-section);
    align-items: start;
  }

  /* The poster's frame (it asks again while the picture of a new work is
     on its way, card 4.15) takes no part in the layout. */
  .work-poster {
    display: contents;
  }

  .work-image {
    grid-area: image;
  }

  .work-head {
    grid-area: head;
    min-width: 0;
  }

  .work-text {
    grid-area: text;
    min-width: 0;
  }

  /* Room for the bookmark at the top right. */
  .work-signed-in .work-head {
    padding-inline-end: 3.5rem;
  }

  .poster {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-raised);
  }

  /* An album cover: square, as wide as a poster, at the top (card 2.12c). */
  .work-image-square .poster {
    aspect-ratio: 1 / 1;
    object-fit: cover;
  }

  /* No picture: the symbol of the medium on a calm surface (card 2.12c). */
  .poster-empty {
    display: grid;
    place-items: center;
    aspect-ratio: 2 / 3;
    color: var(--color-muted);
    background: color-mix(in srgb, var(--color-line) 75%, var(--color-bg));
    box-shadow: none;
  }

  .poster-empty .icon {
    width: 2.5rem;
    height: 2.5rem;
    stroke-width: 1.25;
  }

  /* The video: the stored preview image with a play symbol on top (ADR 0031). */
  .work:has(.video, .video-frame) {
    grid-template-columns: minmax(0, 1fr);
  }

  .video,
  .video-frame {
    position: relative;
    display: block;
    width: 100%;
    max-width: 48rem;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius-poster);
    box-shadow: var(--shadow-raised);
    overflow: hidden;
    background: var(--color-video-bg);
  }

  .video-frame {
    border: 0;
  }

  .video img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: filter var(--motion-quick) var(--ease-standard);
  }

  .video:hover img {
    filter: brightness(0.85);
  }

  /* A calm, round play symbol in the middle. */
  .video-play {
    position: absolute;
    inset: 50% auto auto 50%;
    translate: -50% -50%;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: var(--radius-full);
    background: var(--color-scrim);
    backdrop-filter: blur(4px);
    transition: background-color var(--motion-quick) var(--ease-standard), scale var(--motion-quick) var(--ease-standard);
  }

  .video-play::after {
    content: "";
    position: absolute;
    inset: 50% auto auto 54%;
    translate: -50% -50%;
    border-style: solid;
    border-width: 0.8rem 0 0.8rem 1.35rem;
    border-color: transparent transparent transparent var(--color-on-image);
  }

  .video:hover .video-play,
  .video:focus-visible .video-play {
    background: var(--color-accent);
    scale: 1.06;
  }

  /* Small, quiet note where playing leads, before anyone clicks. */
  .video-note {
    position: absolute;
    inset: auto var(--space-3) var(--space-2) auto;
    font-size: var(--text-xs);
    color: var(--color-on-image);
    opacity: 0.75;
    text-shadow: 0 1px 2px var(--color-scrim);
  }

  /* A video: its picture on top in full width, title and the rest below. */
  .work:has(.video, .video-frame) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "image"
      "head"
      "text";
  }

  .work:has(.video, .video-frame) .work-head {
    margin-block-start: var(--space-component);
  }

  /* ---------- The head: title, original title, facts ---------- */
  .work-title {
    font-size: var(--text-work-title);
    margin: 0;
  }

  .work-original {
    font-family: var(--font-read);
    font-style: italic;
    font-size: var(--text-heading);
    line-height: 1.25;
    color: var(--color-muted);
    margin: var(--space-related) 0 0;
  }

  .work-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-tight) var(--space-related);
    margin: var(--space-group) 0 0;
    color: var(--color-muted);
    font-size: var(--text-meta);
    font-variant-numeric: tabular-nums;
  }

  /* The edition's year beside when an old work was made: quieter. */
  .work-edition {
    color: var(--color-muted);
  }

  .work-meta .separator {
    color: var(--color-muted);
  }

  .age-rating {
    border: 1px solid var(--color-line);
  }

  /* The familiar FSK colours; other rating systems stay neutral. */
  .fsk-0 { background: var(--fsk-0-bg); color: var(--fsk-0-text); border-color: var(--color-line); }
  .fsk-6 { background: var(--fsk-6-bg); color: var(--fsk-6-text); border-color: transparent; }
  .fsk-12 { background: var(--fsk-12-bg); color: var(--fsk-12-text); border-color: transparent; }
  .fsk-16 { background: var(--fsk-16-bg); color: var(--fsk-16-text); border-color: transparent; }
  .fsk-18 { background: var(--fsk-18-bg); color: var(--fsk-18-text); border-color: transparent; }

  /* ---------- My entry: status and note ---------- */
  .work-text > :first-child {
    margin-block-start: var(--space-component);
  }

  .work .entry {
    display: grid;
    gap: var(--space-component);
  }

  .work .entry .status-group {
    margin: 0;
  }

  /* The status row and the quiet facts of the entry (where it came from,
     the lists it lies on) form one group; the note follows as the next
     (card 2.23). The facts keep a little distance from the buttons
     (initiator, 08.10.2026) and stand close to each other. */
  .entry-head {
    display: grid;
    gap: var(--space-group);
  }

  .entry-facts {
    display: grid;
    gap: var(--space-tight);
  }

  /* What I think of it on the left, "Empfehlen" on the right. When the
     row is too narrow (phones), the button takes a line of its own over
     the whole width, as a form's main button does. */
  .entry-actions-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-related) var(--space-group);
  }

  @media (max-width: 36rem) {
    .entry-actions-row > .btn {
      flex: 1 1 100%;
    }
  }

  /* Where the entry came from (card 2.16b): quiet, right under the status. */
  .entry-origin {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .work-add {
    margin: 0;
  }

  /* ---------- What I can do: recommend (a work not in the collection) ---------- */
  .work-recommend {
    margin: var(--space-section) 0 0;
  }

  /* Lists of others with a work not in the collection: a group of its own. */
  .work-text > .work-in-lists {
    margin-block-start: var(--space-group);
  }

  /* The tags belong to the work and the own entry: the entry's distance
     (card 4.3b). */
  .work-text > .work-tags {
    margin-block-start: var(--space-component);
  }

  .work-notice {
    font-family: var(--font-read);
    font-style: italic;
    color: var(--color-muted);
    margin: var(--space-group) 0 0;
  }

  /* ---------- A free entry may have found its work (card 2.12c) ----------
     One quiet line: the badge, the question, and the way to decide. */
  .link-suggestion {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-related) var(--space-group);
    margin: var(--space-group) 0 0;
    font-size: var(--text-control);
  }

  .link-suggestion p {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-related);
    margin: 0;
  }

  /* The dialog: both works side by side, on a phone one below the other. */
  .dialog-link {
    width: min(40rem, 100% - 2 * var(--space-4));
  }

  .link-compare {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-group);
    margin-block: var(--space-group);
  }

  .compare-card {
    margin: 0;
  }

  .compare-label {
    margin: 0 0 var(--space-related);
    font-family: var(--font-ui);
    font-size: var(--text-fine);
    font-weight: var(--weight-semibold);
    color: var(--color-muted);
  }

  .compare-body {
    display: grid;
    grid-template-columns: 4rem minmax(0, 1fr);
    gap: var(--space-group);
    align-items: start;
  }

  .compare-card-wide .compare-body {
    grid-template-columns: 7rem minmax(0, 1fr);
  }

  .compare-card-wide .work-card-image img,
  .compare-card-wide .work-card-image-empty {
    aspect-ratio: 16 / 9;
  }

  .compare-card-square .work-card-image img,
  .compare-card-square .work-card-image-empty {
    aspect-ratio: 1 / 1;
  }

  .compare-body p {
    margin: 0;
  }

  .compare-title {
    font-family: var(--font-read);
    font-size: var(--text-card-title);
    line-height: var(--leading-ui);
    margin-block-end: var(--space-tight) !important;
  }

  @media (max-width: 36rem) {
    .link-compare {
      grid-template-columns: minmax(0, 1fr);
    }
  }

  /* ---------- What the work is about (card 2.21) ----------
     The reading face, a section above the credits; six lines, then
     "Weiterlesen" (fold.css). Where it comes from in the finest print,
     the quietest colour: it must be there, but not be noticed. */
  .work-description {
    margin-block-start: var(--space-section);
    max-width: var(--width-read);
  }

  /* While a book's description is looked for: three lines of the
     reading size pulse quietly where the text will stand (no spinner);
     still with reduced motion. */
  .description-skeleton {
    display: grid;
    gap: calc((var(--leading-read) - 1) * var(--text-read));
    padding-block: calc((var(--leading-read) - 1) * var(--text-read) / 2);
  }

  .description-skeleton > span {
    display: block;
    height: var(--text-read);
    border-radius: var(--radius-s);
    background: color-mix(in srgb, var(--color-text) 8%, transparent);
    animation: description-pulse 1.6s var(--ease-standard) infinite alternate;
  }

  .description-skeleton > span:last-child {
    width: 60%;
  }

  @keyframes description-pulse {
    to {
      opacity: 0.45;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .description-skeleton > span {
      animation: none;
    }
  }

  /* Nothing found: one calm line in the text's place. */
  .work-description-none {
    margin: 0;
    font-family: var(--font-read);
    font-size: var(--text-read);
    font-style: italic;
    color: var(--color-muted);
  }

  /* Body text (base.css, .body-text); here only the source's own line
     breaks (chapters of a video) and long addresses. */
  .work-description-text {
    white-space: pre-line;
    overflow-wrap: anywhere;
  }

  .work-description-source {
    margin: var(--space-related) 0 0;
    font-size: var(--text-fine);
    color: var(--color-muted);
  }

  /* "Weiterlesen" on the left, where the text comes from on the right, on
     one line (Alex, 15.10.2026): the fold's box and button join the
     section's grid (fold.js measures only the box). */
  .work-description:has(> .work-description-source) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: var(--space-related) var(--space-group);
  }

  .work-description:has(> .work-description-source) > .fold {
    display: contents;
  }

  .work-description:has(> .work-description-source) .fold-box {
    grid-column: 1 / -1;
  }

  .work-description:has(> .work-description-source) .fold-toggle {
    grid-column: 1;
    justify-self: start;
  }

  .work-description:has(> .work-description-source) > .work-description-source {
    grid-column: 2;
    justify-self: end;
    margin: 0;
    text-align: end;
  }

  .work-description-source a {
    color: inherit;
  }

  /* ---------- An album's tracks (card 2.22) ----------
     Above what it is about. Number and length in quiet tabular figures,
     the title in the reading face; one line per track, a calm line per
     disc. Twenty tracks stand open (fold.js cuts in the 21st). */
  .work-tracks {
    margin-block-start: var(--space-section);
    max-width: var(--width-read);
  }

  .work-tracks-title {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-related) var(--space-group);
    margin: 0 0 var(--space-group);
    font-size: var(--text-heading);
  }

  .work-tracks-summary {
    font-family: var(--font-ui);
    font-size: var(--text-meta);
    font-weight: var(--weight-regular);
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

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

  .track {
    display: grid;
    grid-template-columns: 2.5rem minmax(0, 1fr) auto;
    gap: var(--space-group);
    align-items: baseline;
    padding-block: var(--space-tight);
  }

  .track-number,
  .track-length {
    font-size: var(--text-meta);
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
  }

  .track-title {
    font-family: var(--font-read);
    font-size: var(--text-read);
    line-height: 1.4;
  }

  .track-disc {
    margin-block: var(--space-group) var(--space-related);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
  }

  .track-disc:first-child {
    margin-block-start: 0;
  }

  /* An album's people: one per line, the instruments quiet beside them. */
  .credits-fold {
    margin-block-start: var(--space-section);
  }

  .credits-fold .credits {
    margin-block-start: 0;
  }

  .credit-people {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-tight);
  }

  .credit-detail {
    margin-inline-start: var(--space-related);
    color: var(--color-muted);
  }

  /* ---------- Soundtracks (card 4.11): album tiles, a section apart,
     the heading as the tracks' ---------- */
  .work-soundtracks {
    margin-block-start: var(--space-section);
  }

  .work-soundtracks-title {
    margin: 0;
    font-size: var(--text-heading);
  }

  /* While they are looked for: a heading and album tiles, pulsing as a
     person's works (person.css). */
  .work-soundtracks-skeleton {
    display: block;
    inline-size: 8rem;
    block-size: var(--text-heading);
    border-radius: var(--radius-s);
    background: color-mix(in srgb, var(--color-text) 8%, transparent);
    animation: description-pulse 1.6s var(--ease-standard) infinite alternate;
  }

  .tile-skeleton-square .tile-skeleton-image {
    aspect-ratio: 1 / 1;
  }

  @media (prefers-reduced-motion: reduce) {
    .work-soundtracks-skeleton {
      animation: none;
    }
  }

  /* ---------- "Verwandte Werke" and "daizee empfiehlt" (cards 4.13, 4.8) ----------
     Below the work, a section apart, over the page's width: rows as in
     Discover (feed.css), as far apart as there. While one is asked for,
     its heading and tiles pulse where they will stand, so nothing jumps
     when they come. When neither has anything, the frame takes no room. */
  .work-rows {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-siblings); /* as between the rows of Discover (feed.css) */
    margin-block-start: var(--space-section);
  }

  .work-rows:empty {
    display: none;
  }

  /* The placeholder is the row itself: each pulsing tile spans the shelf
     and the line of titles, as a real tile does (tile.css), so it is one
     line of tiles, not a grid of two (feedback after stage 4). */
  .feed-row-skeleton .row-track {
    overflow: hidden;
  }

  /* The heading's place: the same heading with a space in it (the row is
     hidden from screen readers), so it is exactly as high as the real one
     and nothing moves when the row comes. */
  .feed-row-title-skeleton {
    inline-size: 9rem;
    border-radius: var(--radius-s);
    background: color-mix(in srgb, var(--color-text) 8%, transparent);
    animation: description-pulse 1.6s var(--ease-standard) infinite alternate;
  }

  @media (prefers-reduced-motion: reduce) {
    .feed-row-title-skeleton {
      animation: none;
    }
  }

  .row-track > .tile-skeleton {
    grid-row: span 2;
    grid-template-rows: subgrid;
    row-gap: var(--space-related);
  }

  .row-track > .tile-skeleton .tile-skeleton-image {
    align-self: end;
  }

  /* ---------- Where it runs (card 4.6) ----------
     A quiet button below what the work is about, a section apart, its
     chevron turning as the comments' do (contact.css). Opened, the
     providers stand as the credits do: the way to watch quiet on the
     left, the names on the right; where the data comes from in the
     finest print below, as under the description. */
  .work-watch {
    margin-block-start: var(--space-section);
    max-width: var(--width-read);
  }

  .work-watch-summary {
    width: fit-content;
  }

  .work-watch[open] .talk-chevron {
    rotate: 180deg;
  }

  .work-watch-body {
    display: grid;
    justify-items: start;
    gap: var(--space-group);
    margin-block-start: var(--space-group);
  }

  /* Groups clearly apart (group), providers within one closer (related,
     .work-watch-offers): nearness groups. */
  .work-watch-groups {
    margin: 0;
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: var(--space-group) var(--space-siblings);
    font-size: var(--text-meta);
  }

  .work-watch-group {
    display: contents;
  }

  .work-watch-group dt {
    color: var(--color-muted);
  }

  .work-watch-group dd {
    margin: 0;
  }

  /* One line per provider: the name, and below it in the finest print
     what is known about watching it there (card 4.6b). Name and details
     belong together (tight), providers apart (related). */
  .work-watch-offers {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-related);
  }

  .work-watch-offer {
    display: grid;
    justify-items: start;
    gap: var(--space-tight);
  }

  .work-watch-details {
    font-size: var(--text-fine);
    color: var(--color-muted);
  }

  .work-watch-details .separator,
  .work-watch-source .separator {
    margin-inline: var(--space-tight);
  }

  .work-watch-none {
    margin: 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  .work-watch-source {
    margin: 0;
    font-size: var(--text-fine);
    color: var(--color-muted);
  }

  .work-watch-source a {
    color: inherit;
  }

  /* While daizee asks, the description's quiet lines pulse in the
     answer's place (.description-skeleton). While an old answer is
     asked again, one of them below the list (htmx marks the details). */
  .work-watch-body > .description-skeleton {
    justify-self: stretch;
  }

  .work-watch-pending {
    display: none;
  }

  .work-watch.htmx-request .work-watch-pending {
    display: grid;
  }

  @media (max-width: 47.99rem) {
    .work-watch-groups {
      grid-template-columns: minmax(0, 1fr);
      row-gap: var(--space-tight);
    }

    .work-watch-group + .work-watch-group dt {
      margin-block-start: var(--space-group);
    }
  }

  /* An album's page: the work it is the soundtrack of, below the facts. */
  .work-soundtrack-of {
    margin: var(--space-related) 0 0;
    font-size: var(--text-meta);
    color: var(--color-muted);
  }

  /* ---------- Credits: reference, calm ---------- */
  .credits {
    margin: var(--space-section) 0 0;
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: var(--space-related) var(--space-siblings);
    font-size: var(--text-meta);
  }

  .credit {
    display: contents;
  }

  .credit dt {
    color: var(--color-muted);
  }

  .credit dd {
    margin: 0;
  }

  .work-origin {
    font-size: var(--text-meta);
    overflow-wrap: anywhere;
    margin: var(--space-group) 0 0;
  }

  /* ---------- Visibility: the platform's frame, far below the content ----------
     The row itself is in visibility.css. */
  .entry-visibility {
    margin-block-start: var(--space-frame);
  }

  /* ---------- Sources ----------
     Right-aligned above the line, which closes the work and sets it
     apart from "daizee empfiehlt" below (Alex, 15.10.2026). */
  .work-sources {
    margin-block-start: var(--space-section);
    padding-block-end: var(--space-group);
    border-bottom: 1px solid var(--color-line);
    font-size: var(--text-fine);
    color: var(--color-muted);
    text-align: end;
  }

  .work-sources-after-visibility {
    margin-block-start: var(--space-group);
  }

  .work-sources p {
    margin: 0 0 var(--space-1);
  }

  .work-sources p:last-child {
    margin-block-end: 0;
  }

  .work-sources a {
    color: var(--color-muted);
  }

  .work-sources .separator {
    margin-inline: var(--space-1);
  }

  /* ---------- The large bookmark at the top right ---------- */
  .work-save {
    position: absolute;
    top: calc(-1 * var(--space-page-start));
    right: 0;
    z-index: 1;
  }

  .rb-l {
    --rb-length: 2.625rem; /* 34 px + 8 px notch */
    --rb-grow: 1rem;
    width: 1.75rem;
  }

  .mark-l .mark-count {
    top: 0.55rem;
    font-size: 0.8125rem;
  }

  @media (max-width: 47.99rem) {
    .work:not(:has(.video, .video-frame)) {
      grid-template-columns: 6.5rem minmax(0, 1fr);
      grid-template-areas:
        "image head"
        "text text";
      column-gap: var(--space-group);
    }

    .work:not(:has(.video, .video-frame)) .work-title {
      font-size: var(--text-xl);
    }

    .work:not(:has(.video, .video-frame)) .work-original {
      font-size: var(--text-read);
    }

    .work:not(:has(.video, .video-frame)) .poster {
      box-shadow: var(--shadow-rest);
    }

    .work-signed-in .work-head {
      padding-inline-end: 2.25rem;
    }

  }
}

/* css/utilities.css */
/* utilities.css – a few helpers. Keep this file small. */

@layer utilities {
  /* "hidden" always hides, also where a component sets a display of its
     own (a .btn is inline-flex): the install button showed in every face,
     also in the installed app (found in round 3 of the feedback after
     stage 3). */
  [hidden] {
    display: none !important;
  }

  /* Hidden on screen, still read by screen readers. */
  .visually-hidden:not(:focus):not(:active) {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* "Skip to content": appears when reached with the keyboard. */
  .skip-link {
    position: absolute;
    inset-block-start: var(--space-2);
    inset-inline-start: var(--space-2);
    padding: var(--space-2) var(--space-4);
    background: var(--color-accent);
    color: var(--color-on-accent);
    border-radius: var(--radius-m);
    z-index: 10;
  }
}

