/* Design tokens live in tokens.css, which base.html loads before this file for
   every page in the app. The `:root` block that used to sit here has been
   removed rather than moved: three stylesheets each declaring their own copy of
   the palette is precisely how they drifted apart. Everything it defined is
   still available under the same names -- see the compatibility section at the
   bottom of tokens.css -- so no rule below had to change to keep working. */

/* =============================================================================
   landing.css — the public marketing site.

   This is the first surface a prospective user sees, so it is deliberately
   quieter than it used to be: the glow was doing the work that type and spacing
   should do. Sizes, colours, radii, shadows and easings all come from tokens
   now; there are no hardcoded hexes below except where a value is genuinely
   local (the traffic-light dots in the fake window chrome).

   Sections
   --------
     1. Base & layout
     2. Ambient background
     3. Flash messages
     4. Navigation
     5. Buttons
     6. Hero
     7. Product preview — four tabs, all of them query results
     8. Stats strip — every tile names its source
     9. Section scaffolding
    10. Feature cards
    11. Maintenance page
    12. Sources
    13. Workflow
    14. Ecosystem
   15b. Status — catalog and supply chain, in public
    16. CTA banner
    17. Footer
   17b. Legal pages — Impressum and Datenschutzerklärung
    18. Responsive
   ============================================================================= */

/* =============================================================================
   1. Base & layout
   ============================================================================= */

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* The hero's glow-orb-1/2/3 are large (400-500px) absolutely positioned
     circles that intentionally bleed past the edge of the viewport on
     desktop for the glow effect -- on a narrow mobile viewport they
     genuinely overflow the page width. body's overflow-x:hidden below
     doesn't reliably stop that from becoming a swipeable/scrollable
     horizontal area on mobile browsers (notably iOS Safari), which compute
     the page's scrollable bounds from the root <html> element, not <body>.
     Needs to be set on both. */
  overflow-x: hidden;
}

body {
  margin: 0;
  font-family: var(--mf-font-sans);
  background: var(--mf-bg);
  color: var(--mf-fg);
  font-size: var(--mf-text-base);
  line-height: var(--mf-leading-normal);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a {
  color: inherit;
  text-decoration: none;
}

img, svg {
  display: block;
  max-width: 100%;
}

/* One container width for the whole site. Sections that need a narrower measure
   cap their own prose rather than inventing a second container. */
.container {
  width: 100%;
  max-width: var(--mf-layout-max);
  margin: 0 auto;
  padding: 0 var(--mf-space-5);
}

/* =============================================================================
   2. Ambient background

   The orbs are decoration layered on top of --mf-gradient-glow, which is now
   much softer than it was. They stay because motion at this scale reads as
   "alive" rather than "busy", but their opacities were pulled down to match.
   ============================================================================= */

.glow-backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--mf-bg-sunken);
  background-image: var(--mf-gradient-glow);
  overflow: hidden;
}

.glow-orb-1, .glow-orb-2, .glow-orb-3 {
  position: absolute;
  border-radius: var(--mf-radius-full);
  filter: blur(130px);
  pointer-events: none;
  z-index: var(--mf-z-base);
}

.glow-orb-1 {
  width: 500px;
  height: 500px;
  top: 5%;
  left: 3%;
  background: var(--mf-orb-1);
  animation: floatOrb1 14s ease-in-out infinite alternate;
}

.glow-orb-2 {
  width: 450px;
  height: 450px;
  top: 35%;
  right: 3%;
  background: var(--mf-orb-2);
  animation: floatOrb2 18s ease-in-out infinite alternate;
}

.glow-orb-3 {
  width: 400px;
  height: 400px;
  bottom: 10%;
  left: 30%;
  background: var(--mf-orb-3);
  animation: floatOrb1 20s ease-in-out infinite alternate-reverse;
}

@keyframes floatOrb1 {
  0% { transform: translate(0, 0) scale(1); }
  100% { transform: translate(40px, 50px) scale(1.15); }
}

@keyframes floatOrb2 {
  0% { transform: translate(0, 0) scale(1); }
  100% { transform: translate(-40px, -30px) scale(1.12); }
}

/* Scroll reveal. A short travel distance: content that flies in from far away
   draws attention to the animation instead of to the content. */
.reveal-on-scroll {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--mf-dur-slow) var(--mf-ease-out),
              transform var(--mf-dur-slow) var(--mf-ease-out);
}

.reveal-on-scroll.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* =============================================================================
   3. Flash messages
   ============================================================================= */

.flash-container {
  position: fixed;
  top: var(--mf-space-4);
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--mf-z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--mf-space-2);
  width: min(92vw, 420px);
}

.flash {
  padding: var(--mf-space-3) var(--mf-space-4);
  border-radius: var(--mf-radius-md);
  font-size: var(--mf-text-md);
  line-height: var(--mf-leading-snug);
  background: var(--mf-surface-raised);
  border: 1px solid var(--mf-border-strong);
  box-shadow: var(--mf-shadow-md);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  animation: slideDown var(--mf-dur-normal) var(--mf-ease-out);
}

@keyframes slideDown {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Status colour on the left edge as well as the border: a 1px tinted outline is
   easy to miss, a bar is not. */
.flash-error { border-color: var(--mf-danger-wash); border-left: 3px solid var(--mf-danger-fg); color: var(--mf-danger-fg); }
.flash-success { border-color: var(--mf-ok-wash); border-left: 3px solid var(--mf-ok-fg); color: var(--mf-ok-fg); }
.flash-info { border-color: var(--mf-info-wash); border-left: 3px solid var(--mf-info-fg); color: var(--mf-info-fg); }
.flash-warning { border-color: var(--mf-warn-wash); border-left: 3px solid var(--mf-warn-fg); color: var(--mf-warn-fg); }

/* =============================================================================
   4. Navigation

   Sticky, translucent, one hairline at the bottom. The scrolled state only
   firms up the same two properties rather than changing the bar's shape, so
   nothing under it shifts as you scroll.
   ============================================================================= */

.site-nav {
  position: sticky;
  top: 0;
  z-index: var(--mf-z-sticky);
  background: color-mix(in srgb, var(--mf-bg-sunken) 78%, transparent);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  border-bottom: 1px solid var(--mf-border);
  transition: border-color var(--mf-dur-normal) var(--mf-ease),
              background-color var(--mf-dur-normal) var(--mf-ease);
}

.site-nav.scrolled {
  border-color: var(--mf-border-strong);
  background: color-mix(in srgb, var(--mf-bg-sunken) 94%, transparent);
}

.site-nav .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 68px;
  /* The three groups (brand · links · CTAs) sit in one row with no room to spare.
     Without a gap here, space-between lets them touch as soon as the row fills up —
     which is how "MediaForge" ended up glued to "Features". */
  gap: var(--mf-space-5);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--mf-space-3);
  font-weight: var(--mf-weight-bold);
  font-size: var(--mf-text-lg);
  letter-spacing: var(--mf-tracking-tight);
  color: var(--mf-fg);
  transition: opacity var(--mf-dur-fast) var(--mf-ease);
  /* The wordmark is not a place to save pixels — never let flex shrink it. */
  flex-shrink: 0;
  white-space: nowrap;
}

.brand:hover {
  opacity: 0.82;
}

.brand-mark {
  width: 34px;
  height: 34px;
  border-radius: var(--mf-radius-sm);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 8px rgba(124, 92, 255, 0.35));
  animation: pulseLogo 6s infinite alternate;
}

@keyframes pulseLogo {
  0% { filter: drop-shadow(0 2px 6px rgba(124, 92, 255, 0.3)); }
  100% { filter: drop-shadow(0 2px 12px rgba(34, 211, 238, 0.45)); }
}

.brand-mark svg { width: 28px; height: 28px; }

.nav-links {
  display: flex;
  align-items: center;
  /* Breathing room where there is room, less where there isn't — instead of a fixed
     32px that forces the row to break as soon as the viewport gets tight. */
  gap: clamp(12px, 1.4vw, 24px);
  flex-wrap: nowrap;
  min-width: 0;
}

.nav-links a {
  color: var(--mf-fg-muted);
  font-size: var(--mf-text-md);
  font-weight: var(--mf-weight-medium);
  border-radius: var(--mf-radius-xs);
  transition: color var(--mf-dur-fast) var(--mf-ease);
  /* A nav item is a label, not a paragraph. Wrapping is what turned "How It Works"
     and "Dev Info API" into two-line stacks and made the bar look squashed. */
  white-space: nowrap;
}

.nav-links a:hover {
  color: var(--mf-fg);
}

.nav-links a.active {
  color: var(--mf-fg);
  font-weight: var(--mf-weight-semibold);
}

/* ---- the "More" disclosure in the nav -------------------------------------
   A native <details>. Four reference links (source, API, imprint, privacy) live
   in here so the bar holds seven items instead of eleven — see _public_nav.html
   for why they are behind a menu rather than dropped. */
.nav-menu { position: relative; flex-shrink: 0; }

.nav-menu > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--mf-fg-muted);
  font-size: var(--mf-text-md);
  font-weight: var(--mf-weight-medium);
  white-space: nowrap;
  transition: color var(--mf-dur-fast) var(--mf-ease);
}

.nav-menu > summary::-webkit-details-marker { display: none; }
.nav-menu > summary:hover { color: var(--mf-fg); }
.nav-menu > summary.active { color: var(--mf-fg); font-weight: var(--mf-weight-semibold); }

.nav-menu-caret {
  display: inline-flex;
  transition: transform var(--mf-dur-normal) var(--mf-ease);
}

.nav-menu-caret svg { width: 14px; height: 14px; display: block; }
.nav-menu[open] .nav-menu-caret { transform: rotate(180deg); }

.nav-menu-pop {
  position: absolute;
  right: 0;
  top: calc(100% + var(--mf-space-3));
  z-index: var(--mf-z-dropdown);
  min-width: 190px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--mf-space-2);
  background: var(--mf-surface-raised);
  border: 1px solid var(--mf-border-strong);
  border-radius: var(--mf-radius-md);
  box-shadow: var(--mf-shadow-lg);
}

.nav-menu-pop a {
  padding: var(--mf-space-2) var(--mf-space-3);
  border-radius: var(--mf-radius-xs);
  font-size: var(--mf-text-sm);
}

.nav-menu-pop a:hover { background: var(--mf-surface); }

/* The drawer's flat copies of the four. The menu is the desktop form; below the
   collapse breakpoint the two swap. */
.nav-drawer-only { display: none; }

.nav-cta {
  display: flex;
  align-items: center;
  gap: var(--mf-space-3);
  flex-shrink: 0;
}

/* The nav's buttons are navigation, not calls to action in a hero — the generous
   .btn padding is too much here and is what pushed the row over the edge. */
.site-nav .btn {
  height: var(--mf-control-height);
  padding: 0 var(--mf-space-4);
  font-size: var(--mf-text-md);
}

.nav-cta-mobile {
  display: none;
}

/* =============================================================================
   5. Buttons

   Two weights only. Primary is the gradient and carries a shadow; ghost is a
   hairline surface. Anything else on the page that looks like a button is a
   chip or a card, not a third button style.
   ============================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--mf-space-2);
  padding: 0 var(--mf-space-5);
  height: var(--mf-control-height-lg);
  border-radius: var(--mf-radius-sm);
  font-weight: var(--mf-weight-semibold);
  font-size: var(--mf-text-md);
  letter-spacing: var(--mf-tracking-tight);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform var(--mf-dur-fast) var(--mf-ease),
              box-shadow var(--mf-dur-normal) var(--mf-ease),
              background-color var(--mf-dur-normal) var(--mf-ease),
              border-color var(--mf-dur-normal) var(--mf-ease),
              color var(--mf-dur-normal) var(--mf-ease);
  white-space: nowrap;
}

.btn-primary {
  background: var(--mf-gradient-brand);
  color: var(--mf-fg-on-accent);
  box-shadow: var(--mf-shadow-brand);
  font-weight: var(--mf-weight-bold);
}

.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: var(--mf-shadow-brand), var(--mf-shadow-md);
}

.btn-ghost {
  background: transparent;
  border-color: var(--mf-border-strong);
  color: var(--mf-fg);
}

.btn-ghost:hover {
  background: var(--mf-surface-hover);
  border-color: var(--mf-border-accent);
  transform: translateY(-1px);
}

.btn-lg {
  height: 52px;
  padding: 0 var(--mf-space-6);
  font-size: var(--mf-text-base);
  border-radius: var(--mf-radius-md);
}

/* =============================================================================
   6. Mobile toggle
   ============================================================================= */

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--mf-border-strong);
  border-radius: var(--mf-radius-sm);
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.nav-toggle span,
.nav-toggle span::before,
.nav-toggle span::after {
  content: "";
  display: block;
  width: 20px;
  height: 2px;
  border-radius: var(--mf-radius-full);
  background: var(--mf-fg);
  position: relative;
  transition: transform var(--mf-dur-normal) var(--mf-ease),
              opacity var(--mf-dur-normal) var(--mf-ease),
              background-color var(--mf-dur-fast) var(--mf-ease);
}

.nav-toggle span::before { position: absolute; top: -7px; }
.nav-toggle span::after { position: absolute; top: 7px; }

.nav-toggle.is-active span { background: transparent; }
.nav-toggle.is-active span::before { transform: translateY(7px) rotate(45deg); }
.nav-toggle.is-active span::after { transform: translateY(-7px) rotate(-45deg); }

/* =============================================================================
   7. Hero

   The headline carries the page. It runs to --mf-text-5xl on wide viewports and
   clamps down to --mf-text-3xl on a phone, tracked tight so a 60px line still
   reads as one shape rather than as spaced-out words.
   ============================================================================= */

.hero {
  padding: var(--mf-space-9) 0 var(--mf-space-8);
  text-align: center;
  position: relative;
  z-index: 1;
}

.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--mf-space-2);
  padding: var(--mf-space-1) var(--mf-space-3);
  border-radius: var(--mf-radius-full);
  border: 1px solid var(--mf-border-accent);
  background: var(--mf-accent-wash);
  font-size: var(--mf-text-xs);
  font-weight: var(--mf-weight-medium);
  letter-spacing: var(--mf-tracking-wide);
  text-transform: uppercase;
  color: var(--mf-accent-text-bright);
  margin-bottom: var(--mf-space-5);
}

.hero-badge .dot {
  width: 6px;
  height: 6px;
  border-radius: var(--mf-radius-full);
  background: var(--mf-ok-solid);
  box-shadow: 0 0 8px var(--mf-ok-solid);
  animation: pulseDot 2.4s infinite;
}

@keyframes pulseDot {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.35); opacity: 0.45; }
}

.hero h1 {
  font-size: clamp(var(--mf-text-3xl), 6vw, var(--mf-text-5xl));
  line-height: var(--mf-leading-tight);
  letter-spacing: var(--mf-tracking-tighter);
  margin: 0 auto var(--mf-space-5);
  font-weight: var(--mf-weight-bold);
  max-width: 18ch;
  text-wrap: balance;
}

.hero h1 .grad {
  background: var(--mf-gradient-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Prose measure, not viewport measure: a subhead that runs the full width of a
   27" monitor is unreadable however nicely it is set. */
.hero p.subhead {
  max-width: var(--mf-layout-max-prose);
  margin: 0 auto var(--mf-space-7);
  font-size: var(--mf-text-lg);
  color: var(--mf-fg-muted);
  line-height: var(--mf-leading-relaxed);
  text-wrap: pretty;
}

.hero-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--mf-space-3);
  flex-wrap: wrap;
  margin-bottom: var(--mf-space-7);
}

.hero-note {
  margin-top: var(--mf-space-4);
  font-size: var(--mf-text-sm);
  color: var(--mf-fg-subtle);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--mf-space-2);
  /* Two clauses and a link: it has to be allowed to wrap on a phone instead of
     forcing the hero wider than the viewport. */
  flex-wrap: wrap;
  text-align: center;
}

.hero-note-icon {
  width: 16px;
  height: 16px;
  stroke: var(--mf-accent2);
  flex-shrink: 0;
}

.hero-note a { color: var(--mf-accent-text); }
.hero-note a:hover { text-decoration: underline; }

/* =============================================================================
   8. Product preview — the panel under the hero

   No longer a mock: every tab renders a query result. The tabs are radio inputs
   with <label> triggers rather than buttons with a click handler, because the
   previous JS version showed a visitor with scripts blocked exactly one of four
   panes with no way to reach the other three. Radios also bring arrow-key
   navigation and a real accessible name for free.
   ============================================================================= */

.hero-preview {
  max-width: 980px;
  margin: 0 auto var(--mf-space-7);
  border-radius: var(--mf-radius-lg);
  background: var(--mf-surface);
  border: 1px solid var(--mf-border-strong);
  box-shadow: var(--mf-shadow-xl);
  overflow: hidden;
  text-align: left;
  position: relative;
}

/* Visually hidden, still focusable and still announced — clip rather than
   display:none, which would take the group out of the tab order entirely and
   make the panel unreachable by keyboard. */
.preview-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.preview-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--mf-space-3) var(--mf-space-4);
  background: var(--mf-surface-raised);
  border-bottom: 1px solid var(--mf-border);
  flex-wrap: wrap;
  gap: var(--mf-space-3);
}

.preview-window-controls {
  display: flex;
  gap: var(--mf-space-2);
  align-items: center;
}

.window-dot {
  width: 10px;
  height: 10px;
  border-radius: var(--mf-radius-full);
}

/* macOS traffic lights. Deliberately literal hexes — these are a quotation of
   another product's chrome, not part of our palette. */
.dot-red { background: var(--mf-dot-red); }
.dot-yellow { background: var(--mf-dot-yellow); }
.dot-green { background: var(--mf-dot-green); }

.tab-buttons {
  display: flex;
  gap: var(--mf-space-1);
  background: var(--mf-bg-sunken);
  padding: var(--mf-space-1);
  border-radius: var(--mf-radius-sm);
}

.preview-title {
  margin-left: var(--mf-space-2);
  font-size: var(--mf-text-sm);
  font-weight: var(--mf-weight-semibold);
  color: var(--mf-fg-muted);
}

/* A <label>, not a <button>. Same affordance, no script. */
.tab-btn {
  display: inline-flex;
  align-items: center;
  background: transparent;
  border: none;
  color: var(--mf-fg-muted);
  font-size: var(--mf-text-xs);
  font-weight: var(--mf-weight-semibold);
  padding: var(--mf-space-1) var(--mf-space-3);
  border-radius: var(--mf-radius-xs);
  cursor: pointer;
  transition: color var(--mf-dur-fast) var(--mf-ease),
              background-color var(--mf-dur-fast) var(--mf-ease);
  font-family: var(--mf-font-sans);
  white-space: nowrap;
  /* 32px tall on a phone, where a 12px label is otherwise an unhittable target. */
  min-height: var(--mf-control-height-sm);
}

.tab-btn:hover {
  color: var(--mf-fg);
  background: var(--mf-surface-hover);
}

.preview-body {
  padding: var(--mf-space-5);
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-sm);
  line-height: var(--mf-leading-relaxed);
  color: var(--mf-fg-muted);
}

.preview-tab-pane {
  display: none;
}

/* The whole tab mechanism: which radio is checked decides which pane is shown and
   which label reads as selected. Four pairs, no JavaScript, no state to get out of
   sync with the DOM. */
#mfTabActivity:checked ~ .preview-body > #paneActivity,
#mfTabCatalog:checked ~ .preview-body > #paneCatalog,
#mfTabReleases:checked ~ .preview-body > #paneReleases,
#mfTabHealth:checked ~ .preview-body > #paneHealth {
  display: block;
  animation: fadeIn var(--mf-dur-normal) var(--mf-ease-out);
}

#mfTabActivity:checked ~ .preview-header .tab-btn[for="mfTabActivity"],
#mfTabCatalog:checked ~ .preview-header .tab-btn[for="mfTabCatalog"],
#mfTabReleases:checked ~ .preview-header .tab-btn[for="mfTabReleases"],
#mfTabHealth:checked ~ .preview-header .tab-btn[for="mfTabHealth"] {
  background: var(--mf-accent);
  color: var(--mf-fg-on-brand);
}

/* The input itself is clipped, so the focus ring has to be drawn on its label or
   keyboard users get no indication of where they are in the group. */
#mfTabActivity:focus-visible ~ .preview-header .tab-btn[for="mfTabActivity"],
#mfTabCatalog:focus-visible ~ .preview-header .tab-btn[for="mfTabCatalog"],
#mfTabReleases:focus-visible ~ .preview-header .tab-btn[for="mfTabReleases"],
#mfTabHealth:focus-visible ~ .preview-header .tab-btn[for="mfTabHealth"] {
  box-shadow: var(--mf-focus-ring);
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* --- Activity feed ------------------------------------------------------- */

.feed-row {
  display: flex;
  gap: var(--mf-space-3);
  border-left: 2px solid transparent;
  padding: var(--mf-space-1) var(--mf-space-2);
  transition: background-color var(--mf-dur-fast) var(--mf-ease),
              border-color var(--mf-dur-fast) var(--mf-ease);
}

.feed-row:hover {
  background: var(--mf-surface-hover);
  border-left-color: var(--mf-accent2);
}

.feed-time {
  color: var(--mf-fg-subtle);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

.feed-kind {
  font-weight: var(--mf-weight-semibold);
  flex-shrink: 0;
  /* Every kind word is 5-7 characters; a fixed column stops the message column
     from stepping left and right down the list. */
  min-width: 7ch;
}

.feed-kind-ok { color: var(--mf-ok-fg); }
.feed-kind-info { color: var(--mf-info-fg); }
.feed-kind-accent { color: var(--mf-accent-text); }
.feed-kind-warn { color: var(--mf-warn-fg); }
.feed-kind-muted { color: var(--mf-fg-subtle); }

/* .feed-row is a flex row with .feed-time/.feed-kind pinned via flex-shrink:0.
   Flex items default to min-width:auto, so without min-width:0 here .feed-msg
   refuses to shrink below its own content width and forces the whole row (and
   the page) wider than a mobile viewport -- the original cause of the landing
   page's horizontal swipe, not the glow orbs. */
.feed-msg { color: var(--mf-fg); min-width: 0; overflow-wrap: anywhere; }

.preview-empty,
.preview-note {
  font-family: var(--mf-font-sans);
  font-size: var(--mf-text-sm);
  color: var(--mf-fg-subtle);
  line-height: var(--mf-leading-relaxed);
  margin: var(--mf-space-4) 0 0;
  text-wrap: pretty;
}

.preview-empty {
  margin-top: 0;
  color: var(--mf-fg-muted);
}

.preview-note a { color: var(--mf-accent-text); }
.preview-note a:hover { text-decoration: underline; }

/* --- Catalog tab --------------------------------------------------------- */

.preview-grid {
  display: grid;
  /* min() floor, never a bare 160px: a track that cannot shrink turns into a
     sideways scrollbar at 320px. See docs/DESIGN.md and tests/test_responsive.py. */
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: var(--mf-space-4);
  font-family: var(--mf-font-sans);
}

.preview-cell-label {
  font-size: var(--mf-text-xs);
  letter-spacing: var(--mf-tracking-wide);
  text-transform: uppercase;
  color: var(--mf-fg-subtle);
  margin-bottom: var(--mf-space-2);
}

.preview-cell-value {
  font-size: var(--mf-text-2xl);
  font-weight: var(--mf-weight-bold);
  letter-spacing: var(--mf-tracking-tight);
  color: var(--mf-fg);
  line-height: var(--mf-leading-tight);
  font-variant-numeric: tabular-nums;
}

/* "3 days ago" is a value, not a figure — it must not be set at hero size. */
.preview-cell-value-sm {
  font-size: var(--mf-text-lg);
}

.preview-cell-note {
  font-size: var(--mf-text-xs);
  color: var(--mf-fg-subtle);
  margin-top: var(--mf-space-1);
  overflow-wrap: anywhere;
}

/* --- Releases tab -------------------------------------------------------- */

.preview-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mf-space-3);
  padding: var(--mf-space-2) 0;
  border-bottom: 1px solid var(--mf-border);
  font-family: var(--mf-font-sans);
}

.preview-row:last-of-type {
  border-bottom: none;
}

.preview-row-note {
  font-size: var(--mf-text-xs);
  color: var(--mf-fg-subtle);
  font-family: var(--mf-font-mono);
}

/* --- Health tab ---------------------------------------------------------- */

.preview-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--mf-font-sans);
  font-size: var(--mf-text-sm);
}

.preview-table th,
.preview-table td {
  text-align: left;
  padding: var(--mf-space-2) var(--mf-space-3) var(--mf-space-2) 0;
  border-bottom: 1px solid var(--mf-border);
  vertical-align: top;
}

.preview-table th {
  font-size: var(--mf-text-xs);
  letter-spacing: var(--mf-tracking-wide);
  text-transform: uppercase;
  color: var(--mf-fg-subtle);
  font-weight: var(--mf-weight-semibold);
  white-space: nowrap;
}

.preview-table td {
  color: var(--mf-fg);
}

.preview-table-detail {
  color: var(--mf-fg-muted);
  font-variant-numeric: tabular-nums;
}

.preview-table tr:last-child th,
.preview-table tr:last-child td {
  border-bottom: none;
}

/* A table is the widest thing in the panel; it scrolls sideways inside its own
   wrapper instead of widening the page. Enforced by tests/test_responsive.py. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* --- Status pills, shared by the panel and the Status section ------------- */

.badge-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--mf-space-1);
  padding: 2px var(--mf-space-2);
  border-radius: var(--mf-radius-full);
  font-size: var(--mf-text-2xs);
  font-weight: var(--mf-weight-semibold);
  letter-spacing: var(--mf-tracking-wide);
  text-transform: uppercase;
  border: 1px solid transparent;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.badge-pill-ok { background: var(--mf-ok-wash); color: var(--mf-ok-fg); }
.badge-pill-warn { background: var(--mf-warn-wash); color: var(--mf-warn-fg); }
/* "stale" is deliberately the warning colour and not the neutral one. A scan that
   could not be refreshed is a finding, not a formatting detail — see supply_chain.py. */
.badge-pill-stale { background: var(--mf-warn-wash); color: var(--mf-warn-fg); }
.badge-pill-empty,
.badge-pill-muted { background: var(--badge-bg); color: var(--mf-fg-muted); }
/* Unknown and not-measured are grey, never green. The whole point of the Health
   tab is that a state nobody measured must not be paintable as healthy. */
.badge-pill-unknown,
.badge-pill-not-measured { background: var(--badge-bg); color: var(--mf-fg-subtle); }

/* =============================================================================
   9. Stats strip

   Every tile carries a .stat-src line naming the query behind the number. That
   line is the feature: the page used to print "6+ Source Plugins" from a
   hardcoded data-target with nothing behind it at all.
   ============================================================================= */

.stats-strip {
  border-top: 1px solid var(--mf-border);
  border-bottom: 1px solid var(--mf-border);
  background: var(--mf-surface);
}

.stats-grid {
  display: grid;
  /* auto-fit rather than a fixed count: tiles are omitted when their query could
     not answer, so the number of them is not known at authoring time. */
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: var(--mf-space-5);
  padding: var(--mf-space-7) 0;
  text-align: center;
}

.stat-card {
  padding: var(--mf-space-4);
  border-radius: var(--mf-radius-md);
  transition: background-color var(--mf-dur-normal) var(--mf-ease);
  min-width: 0;
}

.stat-card:hover {
  background: var(--mf-surface-hover);
}

.stat-value {
  font-size: var(--mf-text-3xl);
  font-weight: var(--mf-weight-bold);
  letter-spacing: var(--mf-tracking-tighter);
  line-height: var(--mf-leading-tight);
  background: var(--mf-gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin-bottom: var(--mf-space-1);
  font-variant-numeric: tabular-nums;
}

/* A word, not a figure: "GPL-3.0-or-later", "4 minutes ago", "stale". These wrap
   and must not be set at the size a two-digit count is. */
.stat-value-word {
  font-size: var(--mf-text-lg);
  letter-spacing: var(--mf-tracking-tight);
  overflow-wrap: anywhere;
}

/* A stale scan is painted as a warning, and the gradient fill is dropped so the
   word cannot be mistaken for one of the brand-coloured measured figures. */
.stat-value-warn {
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  color: var(--mf-warn-fg);
}

.stat-label {
  font-size: var(--mf-text-sm);
  color: var(--mf-fg-muted);
  font-weight: var(--mf-weight-medium);
}

.stat-src {
  margin-top: var(--mf-space-2);
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-2xs);
  line-height: var(--mf-leading-snug);
  color: var(--mf-fg-subtle);
  overflow-wrap: anywhere;
}

/* =============================================================================
   10. Section scaffolding

   One vertical rhythm for the whole page, taken from the spacing scale. Every
   section is --mf-space-9 tall on desktop and --mf-space-8 on mobile; nothing
   picks its own padding.
   ============================================================================= */

.section {
  padding: var(--mf-space-9) 0;
  position: relative;
}

/* Sections that step back a level, so the page reads as bands rather than one
   long scroll. Used by Sources and Status. */
.section-sunken {
  background: var(--mf-bg-sunken);
  border-top: 1px solid var(--mf-border);
  border-bottom: 1px solid var(--mf-border);
}

/* The CTA sits directly under the Ecosystem chips and does not need a second
   full band of air above it. */
.section-tight {
  padding-top: var(--mf-space-6);
}

.section-head {
  text-align: center;
  max-width: var(--mf-layout-max-prose);
  margin: 0 auto var(--mf-space-8);
}

.section-eyebrow {
  display: inline-block;
  color: var(--mf-accent2-text);
  font-size: var(--mf-text-xs);
  font-weight: var(--mf-weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--mf-tracking-wider);
  margin-bottom: var(--mf-space-3);
}

.section-head h2 {
  font-size: clamp(var(--mf-text-2xl), 3.5vw, var(--mf-text-3xl));
  line-height: var(--mf-leading-tight);
  letter-spacing: var(--mf-tracking-tighter);
  margin: 0 0 var(--mf-space-3);
  font-weight: var(--mf-weight-bold);
  text-wrap: balance;
}

.section-head p {
  color: var(--mf-fg-muted);
  font-size: var(--mf-text-lg);
  margin: 0;
  line-height: var(--mf-leading-relaxed);
  text-wrap: pretty;
}

/* =============================================================================
   11. Feature cards

   Calm surface, hairline border, 2px lift on hover. The old card grew a purple
   bloom and moved 8px, which read as a hover *event* — twelve of those on one
   screen is noise. The gradient hairline along the top edge is enough.
   ============================================================================= */

.features-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--mf-space-4);
}

.feature-card {
  background: var(--mf-surface);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-lg);
  padding: var(--mf-space-6);
  transition: transform var(--mf-dur-normal) var(--mf-ease),
              border-color var(--mf-dur-normal) var(--mf-ease),
              background-color var(--mf-dur-normal) var(--mf-ease),
              box-shadow var(--mf-dur-normal) var(--mf-ease);
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.feature-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--mf-gradient-brand);
  opacity: 0;
  transition: opacity var(--mf-dur-normal) var(--mf-ease);
}

.feature-card:hover {
  transform: translateY(-2px);
  border-color: var(--mf-border-strong);
  background: var(--mf-surface-raised);
  box-shadow: var(--mf-shadow-md);
}

.feature-card:hover::before {
  opacity: 1;
}

.feature-icon {
  width: 44px;
  height: 44px;
  border-radius: var(--mf-radius-md);
  background: var(--mf-accent-wash);
  border: 1px solid var(--mf-border-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--mf-space-5);
  transition: background-color var(--mf-dur-normal) var(--mf-ease),
              border-color var(--mf-dur-normal) var(--mf-ease);
}

/* The icon brightens; it no longer rotates and scales. A tilting icon on every
   card is a party trick that gets old on the second hover. */
.feature-card:hover .feature-icon {
  background: var(--mf-accent-wash-strong);
  border-color: var(--mf-accent);
}

.feature-icon svg {
  width: 22px;
  height: 22px;
  stroke: var(--mf-accent2);
  transition: stroke var(--mf-dur-normal) var(--mf-ease);
}

.feature-card:hover .feature-icon svg {
  stroke: var(--mf-accent-text-strong);
}

.feature-card h3 {
  font-size: var(--mf-text-xl);
  margin: 0 0 var(--mf-space-2);
  letter-spacing: var(--mf-tracking-tight);
  line-height: var(--mf-leading-snug);
  font-weight: var(--mf-weight-semibold);
}

.feature-card p {
  color: var(--mf-fg-muted);
  font-size: var(--mf-text-md);
  margin: 0;
  line-height: var(--mf-leading-relaxed);
}

/* The footnote on a card: either a measured figure with its source, or the honest
   admission that the thing the card describes is measured in the visitor's own
   install and not here. `margin-top: auto` pins it to the bottom edge so the line
   sits at the same height across a row of cards with different copy lengths. */
.feature-fact {
  margin-top: auto;
  padding-top: var(--mf-space-4);
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-2xs);
  line-height: var(--mf-leading-snug);
  color: var(--mf-fg-subtle);
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}

/* =============================================================================
   12. Maintenance page — a single centred card, same language as auth
   ============================================================================= */

.maintenance-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--mf-space-5);
  position: relative;
  z-index: 1;
}

.maintenance-card {
  max-width: 480px;
  width: 100%;
  text-align: center;
  background: var(--mf-surface);
  border: 1px solid var(--mf-border-strong);
  border-radius: var(--mf-radius-xl);
  padding: var(--mf-space-8) var(--mf-space-7);
  box-shadow: var(--mf-shadow-lg);
  animation: authCardIn var(--mf-dur-slow) var(--mf-ease-out);
}

@keyframes authCardIn {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: translateY(0); }
}

.maintenance-brand-mark {
  width: 48px;
  height: 48px;
  border-radius: var(--mf-radius-md);
  margin: 0 auto var(--mf-space-5);
  animation: pulseLogo 6s infinite alternate;
}

.maintenance-brand-mark svg {
  width: 28px;
  height: 28px;
}

.maintenance-badge {
  margin: 0 auto var(--mf-space-5);
}

.maintenance-card h1 {
  font-size: clamp(var(--mf-text-2xl), 5vw, var(--mf-text-3xl));
  line-height: var(--mf-leading-tight);
  letter-spacing: var(--mf-tracking-tighter);
  margin: 0 0 var(--mf-space-3);
  font-weight: var(--mf-weight-bold);
}

.maintenance-card .subhead {
  margin: 0 auto var(--mf-space-6);
  max-width: 42ch;
  color: var(--mf-fg-muted);
  line-height: var(--mf-leading-relaxed);
}

.maintenance-admin-link {
  display: inline-block;
  color: var(--mf-fg-subtle);
  font-size: var(--mf-text-sm);
  font-weight: var(--mf-weight-medium);
  transition: color var(--mf-dur-fast) var(--mf-ease);
}

.maintenance-admin-link:hover {
  color: var(--mf-accent2-text);
}

/* =============================================================================
   13. Feature categories & sources
   ============================================================================= */

.feature-category {
  margin-bottom: var(--mf-space-7);
}

.feature-category:last-child {
  margin-bottom: 0;
}

.feature-category-title {
  font-size: var(--mf-text-xs);
  font-weight: var(--mf-weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--mf-tracking-wider);
  color: var(--mf-accent2-text);
  margin: 0 0 var(--mf-space-4);
  padding-left: 2px;
}

.sources-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--mf-space-3);
  max-width: 960px;
  margin: 0 auto;
}

.source-card {
  background: var(--mf-surface);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-md);
  padding: var(--mf-space-4) var(--mf-space-5);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mf-space-3);
  transition: transform var(--mf-dur-normal) var(--mf-ease),
              border-color var(--mf-dur-normal) var(--mf-ease),
              background-color var(--mf-dur-normal) var(--mf-ease);
}

.source-card:hover {
  border-color: var(--mf-border-strong);
  background: var(--mf-surface-raised);
  transform: translateY(-2px);
}

.source-name {
  font-weight: var(--mf-weight-semibold);
  font-size: var(--mf-text-md);
  color: var(--mf-fg);
}

.source-alt {
  font-weight: var(--mf-weight-normal);
  font-size: var(--mf-text-xs);
  color: var(--mf-fg-subtle);
}

.source-tag {
  flex-shrink: 0;
  padding: 2px var(--mf-space-2);
  border-radius: var(--mf-radius-full);
  font-size: var(--mf-text-2xs);
  font-weight: var(--mf-weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--mf-tracking-wide);
  background: var(--mf-accent-wash);
  border: 1px solid transparent;
  color: var(--mf-accent-text-strong);
}

.source-card-muted {
  opacity: 0.7;
}

.source-tag-18 {
  background: var(--mf-pink-wash);
  border-color: transparent;
  color: var(--mf-pink-text);
}

.section-footnote {
  text-align: center;
  color: var(--mf-fg-subtle);
  font-size: var(--mf-text-sm);
  max-width: var(--mf-layout-max-prose);
  margin: var(--mf-space-6) auto 0;
  line-height: var(--mf-leading-relaxed);
  text-wrap: pretty;
}

/* =============================================================================
   14. Workflow

   Numbered steps. The number is a watermark, not a heading — it sits behind the
   content at low contrast so the step title is what you read first.
   ============================================================================= */

.workflow-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--mf-space-4);
  position: relative;
}

.workflow-step {
  background: var(--mf-surface);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-lg);
  padding: var(--mf-space-6);
  position: relative;
  transition: transform var(--mf-dur-normal) var(--mf-ease),
              border-color var(--mf-dur-normal) var(--mf-ease),
              background-color var(--mf-dur-normal) var(--mf-ease);
}

.workflow-step:hover {
  background: var(--mf-surface-raised);
  border-color: var(--mf-border-strong);
  transform: translateY(-2px);
}

.step-number {
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-3xl);
  font-weight: var(--mf-weight-bold);
  color: var(--mf-track);
  position: absolute;
  top: var(--mf-space-4);
  right: var(--mf-space-5);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.workflow-step h3 {
  font-size: var(--mf-text-xl);
  margin: 0 0 var(--mf-space-2);
  color: var(--mf-fg);
  font-weight: var(--mf-weight-semibold);
  letter-spacing: var(--mf-tracking-tight);
  line-height: var(--mf-leading-snug);
}

.workflow-step p {
  color: var(--mf-fg-muted);
  font-size: var(--mf-text-md);
  margin: 0;
  line-height: var(--mf-leading-relaxed);
}

/* =============================================================================
   15. Ecosystem chips
   ============================================================================= */

.ecosystem-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mf-space-2);
  justify-content: center;
  max-width: 960px;
  margin: 0 auto;
}

.ecosystem-chip {
  padding: var(--mf-space-2) var(--mf-space-4);
  border-radius: var(--mf-radius-full);
  border: 1px solid var(--mf-border);
  background: var(--mf-surface);
  font-size: var(--mf-text-md);
  font-weight: var(--mf-weight-medium);
  color: var(--mf-fg-muted);
  display: flex;
  align-items: center;
  gap: var(--mf-space-2);
  transition: border-color var(--mf-dur-normal) var(--mf-ease),
              background-color var(--mf-dur-normal) var(--mf-ease),
              color var(--mf-dur-normal) var(--mf-ease);
}

.ecosystem-chip:hover {
  border-color: var(--mf-border-accent);
  background: var(--mf-surface-raised);
  color: var(--mf-fg);
}

.ecosystem-chip::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--mf-radius-full);
  background: var(--mf-accent);
  flex-shrink: 0;
}

/* =============================================================================
   15b. Status section — catalog and supply chain, in public

   Two cards of key/value pairs. Nothing decorative: each <dd> is a query result,
   and the one that cannot be refreshed says so in words rather than reprinting a
   number measured at some other time.
   ============================================================================= */

.status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--mf-space-4);
}

.status-card {
  background: var(--mf-surface);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-lg);
  padding: var(--mf-space-6);
  min-width: 0;
}

.status-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mf-space-3);
  margin-bottom: var(--mf-space-4);
  padding-bottom: var(--mf-space-3);
  border-bottom: 1px solid var(--mf-border);
  font-size: var(--mf-text-lg);
}

/* A two-column definition list that collapses to one column on a phone. The label
   column is `auto` rather than a fixed width so a longer term never truncates. */
.status-kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--mf-space-2) var(--mf-space-4);
  margin: 0;
  font-size: var(--mf-text-md);
}

.status-kv dt {
  color: var(--mf-fg-subtle);
  font-size: var(--mf-text-sm);
  white-space: nowrap;
}

.status-kv dd {
  margin: 0;
  color: var(--mf-fg);
  min-width: 0;
  overflow-wrap: anywhere;
}

.status-kv code {
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-sm);
  color: var(--mf-accent-text);
}

.status-note {
  margin: 0;
  color: var(--mf-fg-muted);
  font-size: var(--mf-text-md);
  line-height: var(--mf-leading-relaxed);
}

.status-stale {
  color: var(--mf-warn-fg);
  font-weight: var(--mf-weight-medium);
}

/* =============================================================================
   16. CTA banner
   ============================================================================= */

.cta-banner {
  border-radius: var(--mf-radius-xl);
  padding: var(--mf-space-8) var(--mf-space-7);
  text-align: center;
  background: var(--mf-surface);
  border: 1px solid var(--mf-border-strong);
  position: relative;
  overflow: hidden;
  box-shadow: var(--mf-shadow-lg);
}

.cta-banner::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--mf-gradient-glow);
  opacity: 0.7;
  pointer-events: none;
}

.cta-banner > * {
  position: relative;
  z-index: 1;
}

.cta-banner h2 {
  font-size: clamp(var(--mf-text-2xl), 4vw, var(--mf-text-4xl));
  line-height: var(--mf-leading-tight);
  margin: 0 0 var(--mf-space-3);
  letter-spacing: var(--mf-tracking-tighter);
  font-weight: var(--mf-weight-bold);
  text-wrap: balance;
}

.cta-banner p {
  color: var(--mf-fg-muted);
  max-width: 52ch;
  margin: 0 auto var(--mf-space-6);
  font-size: var(--mf-text-lg);
  line-height: var(--mf-leading-relaxed);
}

.cta-actions {
  display: flex;
  gap: var(--mf-space-4);
  justify-content: center;
  flex-wrap: wrap;
}

/* =============================================================================
   17. Footer — quiet by design. Nothing here should compete with the CTA above it.
   ============================================================================= */

.site-footer {
  border-top: 1px solid var(--mf-border);
  padding: var(--mf-space-7) 0;
  background: var(--mf-bg-sunken);
}

.footer-grid {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--mf-space-5);
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: var(--mf-space-3);
  color: var(--mf-fg-subtle);
  font-size: var(--mf-text-sm);
}

/* The footer mark is smaller and quieter than the one in the nav. Previously set
   with an inline style attribute on the element, which no stylesheet could see. */
.footer-brand-mark {
  width: 28px;
  height: 28px;
  background: transparent;
}

.footer-brand-mark svg {
  width: 24px;
  height: 24px;
}

.footer-links {
  display: flex;
  gap: var(--mf-space-5);
  flex-wrap: wrap;
}

.footer-links a {
  color: var(--mf-fg-subtle);
  font-size: var(--mf-text-sm);
  border-radius: var(--mf-radius-xs);
  transition: color var(--mf-dur-fast) var(--mf-ease);
}

.footer-links a:hover {
  color: var(--mf-fg);
}

/* The two legal links are the ones § 5 DDG wants "leicht erkennbar und unmittelbar
   erreichbar", so they are not allowed to fade into the row of API links next to them.
   Same size, full foreground colour — the quietest thing in the footer should be the
   copyright line, not the imprint. */
.footer-links a.footer-legal {
  color: var(--mf-fg-muted);
  font-weight: var(--mf-weight-medium);
}

.footer-links a.footer-legal:hover {
  color: var(--mf-fg);
}

/* =============================================================================
   17b. Legal pages — Impressum (§ 5 DDG) and Datenschutzerklärung

   Long-form legal prose, which is the one thing this stylesheet had never had to
   set. Three decisions carry the section:

     * The measure is capped at --mf-layout-max-prose. The rest of the public
       site is cards and grids where a 1280px container is right; a 1280px
       paragraph is 160 characters wide and genuinely hard to read, and this is
       the page somebody has to read carefully.
     * Unset values are styled as a *warning*, not as absence. The whole design
       of these pages is that a missing address is visible; rendering it in the
       same muted grey as body text would undo that in one line of CSS.
     * Mobile first, like everything else here: the definition lists stack by
       default and only become two columns once there is room, so nothing has to
       be un-done at a breakpoint.
   ============================================================================= */

.legal-page {
  padding: var(--mf-space-8) 0 var(--mf-space-9);
}

.legal-container {
  max-width: var(--mf-layout-max-prose);
}

.legal-head {
  margin-bottom: var(--mf-space-6);
}

.legal-head h1 {
  font-size: var(--mf-text-3xl);
  line-height: var(--mf-leading-tight);
  margin: 0 0 var(--mf-space-3);
}

.legal-head-alt {
  color: var(--mf-fg-subtle);
  font-weight: var(--mf-weight-normal);
}

.legal-lang-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mf-space-2);
  align-items: center;
  margin: 0;
  color: var(--mf-fg-subtle);
  font-size: var(--mf-text-sm);
}

.legal-lang-nav a {
  color: var(--mf-accent-text);
  border-radius: var(--mf-radius-xs);
  transition: color var(--mf-dur-fast) var(--mf-ease);
}

.legal-lang-nav a:hover {
  color: var(--mf-fg);
}

/* The "this page is incomplete" panel. Warning colours rather than danger: the page is
   not broken and the operator is not being accused of anything — a value is missing and
   the reader deserves to know before they read a contact detail that isn't there. */
.legal-alert {
  border: 1px solid var(--mf-warn-fg);
  background: var(--mf-warn-wash);
  border-radius: var(--mf-radius-md);
  padding: var(--mf-space-5);
  margin-bottom: var(--mf-space-7);
  font-size: var(--mf-text-md);
  line-height: var(--mf-leading-relaxed);
}

.legal-alert p {
  margin: 0 0 var(--mf-space-3);
}

.legal-alert-title {
  color: var(--mf-warn-fg);
  font-weight: var(--mf-weight-semibold);
}

.legal-missing {
  list-style: none;
  margin: var(--mf-space-4) 0 0;
  padding: 0;
  display: grid;
  gap: var(--mf-space-4);
}

.legal-missing li {
  display: grid;
  gap: var(--mf-space-1);
  padding-top: var(--mf-space-4);
  border-top: 1px solid var(--mf-border);
}

.legal-missing code {
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-sm);
  color: var(--mf-warn-fg);
  font-weight: var(--mf-weight-semibold);
  /* An env var name is a thing to copy, not to reflow: breaking IMPRINT_OPERATOR_NAME
     across two lines makes it unusable as the thing the reader is meant to paste. */
  overflow-wrap: anywhere;
}

.legal-missing-cite {
  font-size: var(--mf-text-xs);
  color: var(--mf-fg-subtle);
}

.legal-missing-text {
  font-size: var(--mf-text-sm);
  color: var(--mf-fg-muted);
  line-height: var(--mf-leading-normal);
}

.legal-doc {
  color: var(--mf-fg-muted);
  font-size: var(--mf-text-base);
  line-height: var(--mf-leading-relaxed);
}

.legal-doc h2 {
  font-size: var(--mf-text-xl);
  line-height: var(--mf-leading-snug);
  color: var(--mf-fg);
  margin: var(--mf-space-7) 0 var(--mf-space-3);
}

.legal-doc h2:first-child {
  margin-top: 0;
}

.legal-doc h3 {
  font-size: var(--mf-text-lg);
  color: var(--mf-fg);
  margin: var(--mf-space-6) 0 var(--mf-space-2);
}

.legal-doc h4 {
  font-size: var(--mf-text-base);
  color: var(--mf-fg);
  margin: var(--mf-space-5) 0 var(--mf-space-2);
}

.legal-doc p {
  margin: 0 0 var(--mf-space-4);
}

.legal-doc ul,
.legal-doc ol {
  margin: 0 0 var(--mf-space-4);
  padding-left: var(--mf-space-5);
}

.legal-doc li {
  margin-bottom: var(--mf-space-2);
}

.legal-doc a {
  color: var(--mf-accent-text);
  text-decoration: underline;
  text-underline-offset: 2px;
  border-radius: var(--mf-radius-xs);
  transition: color var(--mf-dur-fast) var(--mf-ease);
}

.legal-doc a:hover {
  color: var(--mf-fg);
}

.legal-doc code {
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-sm);
  background: var(--mf-surface);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-xs);
  padding: 0 var(--mf-space-1);
  overflow-wrap: anywhere;
}

/* Term above value at every width. An imprint is a list of short labels with values that
   include a four-line postal address; side by side, the address column collapses first and
   the address is what people came for. */
.legal-dl {
  display: grid;
  gap: var(--mf-space-1);
  margin: 0 0 var(--mf-space-5);
}

.legal-dl dt {
  color: var(--mf-fg-subtle);
  font-size: var(--mf-text-xs);
  letter-spacing: var(--mf-tracking-wide);
  text-transform: uppercase;
  margin-top: var(--mf-space-4);
}

.legal-dl dt:first-child {
  margin-top: 0;
}

.legal-dl dd {
  margin: 0;
  color: var(--mf-fg);
}

/* A single configured (or conspicuously unconfigured) statement, set apart from the prose
   explaining it — the access-log retention and the supervisory authority. */
.legal-fact {
  border-left: 3px solid var(--mf-border-accent);
  background: var(--mf-bg-sunken);
  border-radius: 0 var(--mf-radius-sm) var(--mf-radius-sm) 0;
  padding: var(--mf-space-3) var(--mf-space-4);
  color: var(--mf-fg);
}

.legal-note {
  color: var(--mf-fg-subtle);
  font-size: var(--mf-text-sm);
}

/* The honest-limits paragraph: this is a scaffold, a lawyer has not read it. Given a box
   so it is not skimmed past as one more paragraph of legal prose. */
.legal-caveat {
  border: 1px solid var(--mf-border);
  background: var(--mf-surface);
  border-radius: var(--mf-radius-md);
  padding: var(--mf-space-4);
  font-size: var(--mf-text-md);
  color: var(--mf-fg-muted);
}

.legal-caveat strong {
  color: var(--mf-fg);
}

/* An unset value. Loud on purpose — see legal.py: an imprint that looks finished and is
   wrong is worse than one that visibly is not finished. */
.legal-unset {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--mf-space-2);
  color: var(--mf-warn-fg);
  font-size: var(--mf-text-sm);
  font-weight: var(--mf-weight-semibold);
  background: var(--mf-warn-wash);
  border: 1px dashed var(--mf-warn-fg);
  border-radius: var(--mf-radius-xs);
  padding: var(--mf-space-1) var(--mf-space-2);
}

.legal-unset-key {
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-xs);
  font-weight: var(--mf-weight-normal);
  color: var(--mf-fg-subtle);
  overflow-wrap: anywhere;
}

.legal-split {
  border: 0;
  border-top: 1px solid var(--mf-border);
  margin: var(--mf-space-8) 0;
}

/* PRIVACY.md, rendered. Scoped to .legal-md rather than reusing .markdown-body from
   store.css, so these pages need exactly one stylesheet and stay renderable even if the
   store's CSS is not loaded.

   legal.py demotes the document's headings by two so it nests correctly under the section
   that introduces it, which is why the sized rules start at h3: the notice's own title
   arrives as an h3 and its sections as h4. h1/h2 are styled anyway, so a future document
   that does not get demoted is not left unstyled. */
.legal-md h1,
.legal-md h2,
.legal-md h3 {
  font-size: var(--mf-text-lg);
  color: var(--mf-fg);
  margin: var(--mf-space-6) 0 var(--mf-space-3);
}

.legal-md h4,
.legal-md h5,
.legal-md h6 {
  font-size: var(--mf-text-base);
  color: var(--mf-fg);
  margin: var(--mf-space-5) 0 var(--mf-space-2);
}

.legal-md p,
.legal-md ul,
.legal-md ol {
  margin: 0 0 var(--mf-space-4);
}

.legal-md ul,
.legal-md ol {
  padding-left: var(--mf-space-5);
}

.legal-md li {
  margin-bottom: var(--mf-space-2);
}

.legal-md code {
  font-family: var(--mf-font-mono);
  font-size: var(--mf-text-sm);
  background: var(--mf-surface);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-xs);
  padding: 0 var(--mf-space-1);
  overflow-wrap: anywhere;
}

.legal-md a {
  color: var(--mf-accent-text);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.legal-md blockquote {
  border-left: 3px solid var(--mf-border-accent);
  margin: 0 0 var(--mf-space-4);
  padding-left: var(--mf-space-4);
  color: var(--mf-fg-subtle);
}

/* =============================================================================
   18. Responsive

   Four breakpoints, each with one job: 1200 collapses the nav, 992 drops the
   grids to two columns, 768 tightens the vertical rhythm, 560 goes single
   column. The hero headline needs no override at any of them — its clamp()
   already runs from --mf-text-3xl to --mf-text-5xl.
   ============================================================================= */

@media (max-width: 992px) {
  .features-grid, .workflow-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .stats-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--mf-space-6);
  }

  .sources-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* The navigation collapses at 1120px, not 768px.
   Eight links plus two buttons plus the wordmark simply did not fit in one row below
   roughly 1050px: the original breakpoint kept the desktop bar alive down to 768 and let
   it squash — items wrapped to two lines, the brand touched the first link. A menu
   that has run out of room should become the menu button, not get thinner.

   It went 1200 → 1320 when Impressum and Datenschutz joined the row, and back down to
   1120 when four reference links moved into the "More" menu: the bar now carries seven
   items instead of eleven, so it stays honest a good deal further down. Below this it is
   still the menu button — .nav-links is `flex-wrap: nowrap`, so a row that runs out of
   room overflows rather than wrapping, and that is what made the CTAs sit on top of the
   last link. */
@media (max-width: 1120px) {
  .nav-toggle {
    display: flex;
  }

  .nav-links {
    position: absolute;
    top: 68px;
    left: 0;
    right: 0;
    background: color-mix(in srgb, var(--mf-bg-sunken) 97%, transparent);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    border-bottom: 1px solid var(--mf-border);
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    padding: var(--mf-space-2) var(--mf-space-5) var(--mf-space-5);
    display: none;
    box-shadow: var(--mf-shadow-lg);
  }

  .nav-links.is-open {
    display: flex;
    animation: slideDown var(--mf-dur-normal) var(--mf-ease-out);
  }

  .nav-links a {
    width: 100%;
    padding: var(--mf-space-4) 0;
    font-size: var(--mf-text-base);
    border-bottom: 1px solid var(--mf-border);
  }

  /* In the drawer there is a whole column of room, so the four reference links are
     flat entries again and the menu that holds them on desktop steps aside. A
     disclosure inside a disclosure is two taps to reach a link that fits anyway. */
  .nav-menu { display: none; }
  .nav-drawer-only { display: block; }

  .nav-cta {
    display: none;
  }

  .nav-links.is-open .nav-cta-mobile {
    display: flex;
    flex-direction: column;
    width: 100%;
    gap: var(--mf-space-3);
    padding: var(--mf-space-5) 0 var(--mf-space-2);
  }

  .nav-links.is-open .nav-cta-mobile .btn {
    width: 100%;
    justify-content: center;
  }

  /* Inside the drawer they are the primary action again, so the compact nav padding
     set above should not apply. */
  .site-nav .btn {
    height: var(--mf-control-height-lg);
    padding: 0 var(--mf-space-5);
    font-size: var(--mf-text-md);
  }
}

@media (max-width: 768px) {
  .hero {
    padding: var(--mf-space-8) 0 var(--mf-space-7);
  }

  /* Legal prose on a phone: less vertical padding around it, and the headline drops a
     step. Everything else on these pages is single-column already, by construction. */
  .legal-page {
    padding: var(--mf-space-7) 0 var(--mf-space-8);
  }

  .legal-head h1 {
    font-size: var(--mf-text-2xl);
  }

  .section {
    padding: var(--mf-space-8) 0;
  }

  .section-head {
    margin-bottom: var(--mf-space-7);
  }

  .cta-banner {
    padding: var(--mf-space-7) var(--mf-space-5);
  }

  .preview-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .tab-buttons {
    width: 100%;
    overflow-x: auto;
    padding-bottom: var(--mf-space-1);
  }

  .tab-btn {
    flex-shrink: 0;
  }
}

/* The preview panel used to be `display: none` below 767px, on the reasoning that
   a terminal mock has nothing to say on a phone. That is no longer true: the four
   tabs now carry the only measured figures on the page — activity, catalog,
   releases, health — and hiding them would mean a phone visitor gets the marketing
   copy and none of the evidence. It reflows instead: the panel loses its horizontal
   padding, the tab row scrolls sideways, and the health table scrolls inside its
   own wrapper. */
@media (max-width: 767px) {
  .hero-preview {
    margin-bottom: var(--mf-space-6);
  }

  .preview-body {
    padding: var(--mf-space-4) var(--mf-space-3);
  }

  .preview-title {
    /* The window title is chrome. On a 320px row it is the first thing to go. */
    display: none;
  }

  .feed-row {
    flex-wrap: wrap;
    gap: var(--mf-space-2);
  }

  .feed-msg {
    flex-basis: 100%;
  }
}

@media (max-width: 560px) {
  .features-grid, .workflow-grid {
    grid-template-columns: 1fr;
  }

  .sources-grid {
    grid-template-columns: 1fr;
  }

  .stats-grid {
    grid-template-columns: 1fr;
    gap: var(--mf-space-5);
  }

  /* Term above value rather than beside it: two columns at 320px leaves the value
     column about 12 characters wide, which breaks "api.osv.dev · PyPI ecosystem"
     across four lines. */
  .status-kv {
    grid-template-columns: 1fr;
    gap: var(--mf-space-1);
  }

  .status-kv dd {
    margin-bottom: var(--mf-space-3);
  }

  .status-card {
    padding: var(--mf-space-5);
  }

  /* The clamp() floor already lands at --mf-text-3xl here; this only tightens
     the measure so the headline breaks into sensible lines on a phone. */
  .hero h1 {
    max-width: 14ch;
  }

  .hero-actions {
    flex-direction: column;
    width: 100%;
  }

  .hero-actions .btn {
    width: 100%;
  }

  .footer-grid {
    flex-direction: column;
    align-items: flex-start;
    text-align: left;
  }
}
