/*
 * Cockpit design system
 *
 * Mobile-first, plain modern CSS served via Propshaft (no build step).
 * All design decisions live in the custom properties below — components
 * reference tokens, never raw values.
 */

:root {
  /* Color */
  --color-bg: #f5f6f8;
  --color-surface: #ffffff;
  --color-border: #e4e7ec;
  --color-text: #101828;
  --color-text-muted: #667085;
  --color-primary: #1d4ed8;
  --color-primary-contrast: #ffffff;
  --color-danger: #b42318;
  --color-success: #067647;
  --color-warning: #b54708;

  /* Typography */
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.375rem;
  --text-2xl: 1.75rem;

  /* Spacing & shape */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --radius: 0.75rem;
  --shadow-card: 0 1px 2px rgb(16 24 40 / 0.06), 0 1px 3px rgb(16 24 40 / 0.1);

  /* Layout */
  --content-max-width: 80rem;
}

/* --- Reset & base ------------------------------------------------------ */

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

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--color-text);
  background-color: var(--color-bg);
  -webkit-font-smoothing: antialiased;

  /* Long words (German compounds, URLs) hyphenate/break instead of
     overflowing narrow columns. Uses the page's lang attribute. */
  hyphens: auto;
  overflow-wrap: break-word;
}

/* Hyphenating UI controls looks broken — keep them intact. */
button,
input,
select,
textarea,
.btn {
  hyphens: manual;
}

h1,
h2,
h3 {
  line-height: 1.2;
  margin-block: 0 var(--space-3);
}

h1 { font-size: var(--text-2xl); }
h2 { font-size: var(--text-xl); }
h3 { font-size: var(--text-lg); }

a {
  color: var(--color-primary);
}

/* --- Accessibility ------------------------------------------------------ */

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--color-surface);
  padding: var(--space-2) var(--space-4);
  z-index: 100;
}

.skip-link:focus {
  left: var(--space-2);
}

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

.site-header {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  /* Anchor for the mobile dropdown panel. */
  position: relative;
}

/* Hamburger — only rendered useful on phones (see media query below). */
.site-header__menu-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-text);
  cursor: pointer;
}

/* The header collapses into the hamburger below 1024px — earlier than the
   rest of the page (768px): with all nav items, avatar, switchers and
   sign-out, the desktop bar only fits comfortably above ~1000px. */
@media (max-width: 1023.98px) {
  .site-header__menu-toggle {
    display: inline-flex;
  }
}

/* Full-width nav like the mockup shell. */
.site-header__inner {
  padding: var(--space-3) clamp(var(--space-4), 3vw, var(--space-8));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.site-header__brand {
  font-weight: 700;
  font-size: var(--text-lg);
  color: var(--color-text);
  text-decoration: none;
  /* Never hyphenate the wordmark — it breaks only at its <wbr> (after the dot). */
  hyphens: none;
}

/* --- Main --------------------------------------------------------------- */

.site-main {
  max-width: var(--content-max-width);
  margin-inline: auto;
  padding: var(--space-6) var(--space-4) var(--space-8);
}

/* The dashboard uses the full viewport width (mockup .cockpit-main). */
.site-main--wide {
  max-width: none;
  padding-inline: clamp(var(--space-4), 3vw, var(--space-8));
}

@media (max-width: 767.98px) {
  .site-main {
    padding-inline: var(--space-2);
  }
}

/* Pages ending in a floating save bar (bottom: var(--space-3)): the page
   must end exactly where the bar's floating line is. With more padding the
   bar rises above that line on the last pixels of scrolling and closes in
   on the pinned sticky column, which stays viewport-anchored — browsers
   don't push it out of the way. */
.site-main--savebar {
  padding-bottom: var(--space-3);
}

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

.btn {
  display: inline-block;
  border: 1px solid transparent;
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-2) var(--space-4);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}

.btn--primary {
  background: var(--color-primary);
  color: var(--color-primary-contrast);
}

.btn:disabled,
.btn.is-disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

.btn--ghost {
  background: transparent;
  color: var(--color-text-muted);
  border-color: var(--color-border);
}

.btn--wide {
  width: 100%;
}

/* Round icon-only action button (row actions in admin lists). The label
   lives in title/aria-label; the ligature icon is the visible content. */
.btn--icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border-radius: 50%;
}

.btn--icon .material-icons-outlined {
  font-size: 18px;
}

/* Text button with a leading icon. */
.btn--with-icon {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.btn--with-icon .material-icons-outlined {
  font-size: 18px;
}

/* State-transition toggle: current icon, arrow, resulting icon (e.g. open
   eye -> crossed eye). Double class so it also wins over theme overrides. */
.btn--icon.btn--icon-pair {
  width: auto;
  padding-inline: var(--space-3);
  gap: 2px;
  border-radius: 999px;
}

.btn--icon-pair__label {
  font-size: var(--text-sm);
}

.btn--icon-pair__arrow {
  opacity: 0.55;
}

.btn--icon.btn--icon-pair .btn--icon-pair__arrow {
  font-size: 14px;
}

.btn--loading {
  pointer-events: none;
  opacity: 0.75;
}

/* The spinning button stays prominent even while its group is disabled. */
.btn.btn--loading:disabled {
  opacity: 0.75;
}

.btn--loading::before {
  content: "";
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  margin-right: var(--space-2);
  vertical-align: -0.1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: btn-spin 0.7s linear infinite;
}

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

/* --- Flash messages ------------------------------------------------------ */

/* Toast layer: fixed top-center, outside the document flow, so showing and
   dismissing flashes never moves the page content. */
.flash-stack {
  position: fixed;
  top: var(--space-4);
  left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  display: grid;
  gap: var(--space-2);
  width: min(560px, calc(100vw - 2 * var(--space-4)));
  pointer-events: none;
}

.flash-stack .flash {
  margin: 0;
  pointer-events: auto;
}

.flash {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-4);
  font-size: var(--text-sm);
  box-shadow: var(--shadow-card);
  animation: flash-in 240ms ease-out;
}

@keyframes flash-in {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
}

.flash.is-dismissing {
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 200ms ease, transform 200ms ease;
}

.flash__icon {
  flex: none;
  font-size: 20px;
}

.flash__message {
  flex: 1;
  align-self: center;
  min-width: 0;
}

/* Validation errors inside a flash: plain lines, no browser list markers. */
.flash__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-1);
}

.flash__close {
  flex: none;
  display: inline-flex;
  border: 0;
  background: none;
  padding: 0;
  color: inherit;
  opacity: 0.55;
  cursor: pointer;
}

.flash__close:hover {
  opacity: 1;
}

.flash__close .material-icons-outlined {
  font-size: 18px;
}

.flash--notice {
  background: #ecfdf3;
  color: var(--color-success);
  border: 1px solid #abefc6;
}

.flash--alert {
  background: #fef3f2;
  color: var(--color-danger);
  border: 1px solid #fecdca;
}

/* --- Confirm modal ---------------------------------------------------------- */

.confirm-dialog {
  border: 0;
  border-radius: var(--radius);
  padding: 0;
  max-width: 26rem;
  width: calc(100vw - 2 * var(--space-4));
  box-shadow: 0 24px 64px rgb(16 24 40 / 0.25);
}

.confirm-dialog::backdrop {
  background: rgb(16 24 40 / 0.4);
}

.confirm-dialog__body {
  display: grid;
  gap: var(--space-4);
  margin: 0;
  padding: var(--space-6);
}

.confirm-dialog__body p {
  margin: 0;
}

.confirm-dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* --- Header user area ----------------------------------------------------- */

.site-header__nav {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Avatar + hamburger group in the bar; empty on desktop (both hidden). */
.site-header__mobile-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Row icons and the language label only exist in the drawer menu. */
.site-header__link-icon,
.locale-switcher__label {
  display: none;
}

/* Drawer-only bits don't exist on desktop, and the settings wrapper
   dissolves so its children sit inline in the bar. */
.site-header__identity {
  display: none;
}

.site-header__settings,
.site-header__settings-row {
  display: contents;
}

/* Nav labels never hyphenate (body sets hyphens: auto for content) — when
   space runs out the header switches layout instead. */
.site-header__nav .site-header__link {
  white-space: nowrap;
  hyphens: none;
}

/* Below the header breakpoint the nav collapses into a full-width dropdown
   under the header bar, opened by the hamburger (mobile_menu_controller.js):
   nav rows, grouped settings, sign-out below — dimmed backdrop behind. */
@media (max-width: 1023.98px) {
  .site-header__nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 90;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--space-2) var(--space-4) var(--space-4);
    /* Same light grey as the page canvas — bar and panel form one flat
       piece in the page's own color while the backdrop darkens the rest.
       No shadow: its blur would bleed upward and tint the bar. */
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-border);
  }

  .site-header__nav.is-open {
    display: flex;
    animation: menu-in 150ms ease-out;
  }

  .site-header__nav .site-header__link {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 44px;
    font-size: var(--text-base);
    border-bottom: 1px solid var(--color-border);
    transition: border-color 150ms ease;
  }

  /* Faint hover cue: the row's own hairline darkens a touch. */
  .site-header__nav .site-header__link:hover {
    border-color: color-mix(in srgb, var(--color-border), var(--color-text) 10%);
  }

  .site-header__nav .site-header__link.is-active {
    color: var(--color-primary);
  }

  .site-header__nav .site-header__link-icon {
    display: inline-flex;
    font-size: 20px;
    color: var(--color-text-muted);
  }

  /* The icon follows the label's color whenever the link is highlighted —
     tapped/hovered or the current section — not only after navigating. */
  .site-header__nav .site-header__link.is-active .site-header__link-icon,
  .site-header__nav .site-header__link:hover .site-header__link-icon,
  .site-header__nav .site-header__link:active .site-header__link-icon {
    color: inherit;
  }

  /* The whitespace alone separates settings from the nav rows. */
  .site-header__settings {
    display: block;
    margin-block-start: var(--space-6);
  }

  .site-header__settings-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
  }

  .site-header__nav .locale-switcher {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .site-header__nav .locale-switcher__label {
    display: inline;
    color: var(--color-text-muted);
  }

  .site-header__nav .theme-menu {
    display: flex;
    align-items: center;
  }

  .site-header__nav .button_to {
    margin-block-start: var(--space-4);
  }

  .site-header__nav .button_to .btn {
    width: 100%;
    justify-content: center;
    text-align: center;
  }

  /* While the menu is open, the bar takes the same canvas grey as the
     panel — constant from the first frame, no fade: only the page behind
     the backdrop darkens. */
  .site-header:has(.site-header__nav.is-open) {
    background: var(--color-bg);
  }

  /* The dim starts below the header bar: brand, avatar, and hamburger stay
     bright and clickable, reading as part of the open menu. */
  .site-header__backdrop:not([hidden]) {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    height: 100vh;
    z-index: 80;
    background: rgb(0 0 0 / 0.32);
  }
}

/* Desktop never shows the backdrop, even if the menu was open before a
   resize across the breakpoint. */
@media (min-width: 1024px) {
  .site-header__backdrop {
    display: none;
  }
}

/* Movement only, no opacity fade: the panel is fully opaque in the page's
   canvas grey from the first frame — otherwise the darkening backdrop
   shines through and the color visibly "settles". */
@keyframes menu-in {
  from {
    transform: translateY(-6px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-header__nav.is-open {
    animation: none;
  }
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: var(--color-border);
  font-size: 0.8125rem;
  font-weight: 600;
  flex: none;
}

.avatar--photo {
  color: transparent;
  background-size: cover;
  background-position: center;
}

/* The avatar renders twice in the header: inside the nav for desktop, in
   the bar next to the hamburger for phones. */
.avatar.site-header__avatar--mobile {
  display: none;
}

@media (max-width: 1023.98px) {
  .avatar.site-header__avatar--mobile {
    display: inline-flex;
    width: 2rem;
    height: 2rem;
  }

  .avatar.site-header__avatar--desktop {
    display: none;
  }
}

/* --- Login ---------------------------------------------------------------- */

.login-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: var(--space-8) var(--space-6);
  max-width: 24rem;
  margin: 10vh auto 0;
  text-align: center;
}

.login-card__note {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin-block: 0 var(--space-6);
}

/* --- Utilities ------------------------------------------------------------ */

.text-muted {
  color: var(--color-text-muted);
}

/* Screen-reader-only text (e.g. the loading label under a skeleton). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

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

.btn--danger {
  color: var(--color-danger);
}

/* --- Page head ------------------------------------------------------------ */

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-end: var(--space-6);
}

.page-head h1 {
  margin: 0;
}

.page-head__actions {
  display: flex;
  gap: var(--space-2);
}

.site-header__link {
  color: var(--color-text);
  text-decoration: none;
  font-weight: 600;
  font-size: var(--text-sm);
}

/* --- Card list (admin) ------------------------------------------------------ */

.card-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}

.card-list__item {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-4);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
}

.card-list__item p {
  margin: var(--space-1) 0 0;
}

.card-list__actions {
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* Phones: content and actions stack — side by side, the non-shrinking
   actions would push off screen and squeeze the content to a sliver.
   Items with a single short action (--row, e.g. admin widgets) opt out
   and keep the button on the right. */
@media (max-width: 767.98px) {
  .card-list__item:not(.card-list__item--row) {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }

  .card-list__actions {
    flex-wrap: wrap;
  }
}

/* --- Forms ------------------------------------------------------------------ */

.form {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-6);
  max-width: 40rem;
  display: grid;
  gap: var(--space-4);
}

.form__field {
  display: grid;
  gap: var(--space-1);
  border: none;
  margin: 0;
  padding: 0;
}

/* Grid items must be allowed to shrink below their intrinsic width,
   otherwise wide content (e.g. the Trix editor) stretches the column. */
.form > *,
.form__field > * {
  min-width: 0;
}

trix-toolbar,
trix-editor {
  max-width: 100%;
}

/* We don't render captions, so hide the caption UI (filename/size,
   "Add a caption") inside the editor as well. */
trix-editor figcaption {
  display: none;
}

/* --- Language tabs (post form) ---------------------------------------------- */

.locale-tabs {
  display: flex;
  gap: var(--space-1);
  border-bottom: 1px solid var(--color-border);
  margin-block-end: var(--space-3);
  /* Five language tabs outgrow a phone: the strip scrolls internally
     instead of widening the page. */
  overflow-x: auto;
}

.locale-tabs__tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  border: none;
  background: none;
  font: inherit;
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.locale-tabs__tab.is-active {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

/* Tab status: empty outline, half = amber, title + body filled = green. */
.locale-tabs__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--color-text-muted);
  flex: none;
}

.locale-tabs__dot[data-state="partial"] {
  background: var(--color-warning);
  border-color: var(--color-warning);
}

.locale-tabs__dot[data-state="full"] {
  background: var(--color-success);
  border-color: var(--color-success);
}

.locale-tabs__panel {
  display: grid;
  gap: var(--space-4);
}

.locale-tabs__panel[hidden] {
  display: none;
}

.locale-tabs__translate {
  align-items: center;
  flex-wrap: wrap;
}

.locale-switcher select {
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-1) var(--space-2);
  font: inherit;
  font-size: var(--text-sm);
  background: var(--color-surface);
}

.form__field label,
.form__field legend {
  font-weight: 600;
  font-size: var(--text-sm);
}

.form__field input[type="text"],
.form__field input[type="number"] {
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-2) var(--space-3);
  font: inherit;
}

.form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.form__hint {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* --- Post editor form --------------------------------------------------------- */

/* Editorial layout: writing area left, publication settings in a sidebar,
   sticky save bar spanning both. Collapses to one column below 1000px. */
.post-form {
  max-width: none;
}

.post-form__main,
.post-form__aside {
  display: grid;
  gap: var(--space-4);
  align-content: start;
  min-width: 0;
}

@media (min-width: 1000px) {
  .post-form {
    grid-template-columns: minmax(0, 1fr) minmax(20rem, 24rem);
    align-items: start;
    column-gap: var(--space-8);
  }

  .post-form > .flash,
  .post-form__savebar {
    grid-column: 1 / -1;
  }

  /* Checkout-style: the sticky-column controller marks the shorter column
     and sets its top offset — positive (stick at top) when it fits into the
     viewport, negative (stick at bottom) when it's taller. */
  .post-form__main.is-sticky,
  .post-form__aside.is-sticky {
    position: sticky;
  }
}

.post-form__card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-4);
  display: grid;
  gap: var(--space-3);
}

/* Floating sticky bar: the primary action stays reachable in this long form. */
.post-form__savebar {
  position: sticky;
  bottom: var(--space-3);
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  box-shadow: 0 8px 24px rgba(16, 24, 40, 0.08);
}

.post-form__status {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.post-form__status::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-border);
  flex: none;
}

.post-form__status[data-state="live"]::before { background: var(--color-success); }
.post-form__status[data-state="scheduled"]::before { background: var(--color-warning); }
.post-form__status[data-state="expired"]::before { background: var(--color-danger); }

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

.post-form__unsaved {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-warning);
}

.post-form__versions {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  max-height: 16rem;
  overflow-y: auto;
}

.post-form__versions li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.post-form__version-meta {
  display: grid;
  min-width: 0;
}

/* Checkmark on the version matching the saved state. */
.post-form__version-meta strong {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.post-form__version-saved {
  display: inline-flex;
  color: var(--color-success);
}

/* Versions whose state lies in the dumpster — restoring one deletes the
   record, so the whole entry (including the muted meta line) turns red. */
.post-form__version--dumpster,
.post-form__version--dumpster .text-muted,
.version-log__entry--dumpster,
.version-log__entry--dumpster .text-muted {
  color: var(--color-danger);
}

/* Changelog toggle + popover per version — same look as the audience
   member popover. */
.popover-toggle {
  flex: none;
  display: inline-flex;
  border: 0;
  background: none;
  padding: 0;
  color: var(--color-text-muted);
  cursor: pointer;
}

.popover-toggle:hover {
  color: var(--color-primary);
}

.popover-toggle .material-icons-outlined {
  font-size: 16px;
}

.popover-panel {
  position: absolute;
  z-index: 80;
  width: 16rem;
  margin: 0;
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  box-shadow: 0 12px 32px rgb(16 24 40 / 0.16);
  font-size: var(--text-sm);
  line-height: 1.5;
}

/* Scrolling away fades popovers out (see popover/audience controllers);
   the controllers remove them after the transition ends. */
.popover-panel,
.audience-fields__members {
  transition: opacity 140ms ease;
}

.popover-panel.is-fading,
.audience-fields__members.is-fading {
  opacity: 0;
}

.popover-panel__title {
  margin: 0 0 var(--space-2);
  font-weight: 600;
}

/* The list — not the panel — scrolls: an elastic bounce then only moves the
   names and exposes the panel's own background, not the page behind it (and
   the title stays visible). Direct child only: nested lists (the members
   diff inside the version log) must not become scrollers of their own, or
   they would swallow the scroll instead of moving the outer list. */
.popover-panel > ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
  max-height: 13rem;
  overflow-y: auto;
}

/* Scroll chaining stops at the popover boundary: content inside a popover
   chains up TO the popover's scroller, but never FROM the popover into the
   page. Scrollers embedded directly in the page (e.g. the version log on
   the widget edit page) chain to the page normally. */
.popover-panel > ul,
.popover-panel .version-log,
.audience-fields__members ul {
  overscroll-behavior: contain;
}

/* Version log (groups popover + widget definition edit page). The entries
   carry restore buttons, so the popover variant needs more room. */
.popover-panel--log {
  width: 22rem;
}

.version-log {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  max-height: 18rem;
  overflow-y: auto;
}

.version-log__entry {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.version-log__meta {
  display: grid;
  min-width: 0;
}

/* Same custom chevron as the audience sections in the post form
   (.audience-fields__section summary) — no default marker. */
.version-log__members summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  color: var(--color-text-muted);
  list-style: none;
}

.version-log__members summary:hover {
  color: var(--color-text);
}

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

.version-log__members summary::before {
  content: "";
  width: 0.45em;
  height: 0.45em;
  margin-inline-start: var(--space-1);
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 120ms ease;
  flex: none;
}

.version-log__members[open] > summary::before {
  transform: rotate(45deg);
}

/* Capped like the member popovers, but no overscroll containment: hitting
   the end of this list hands the scroll to the version log around it. */
.version-log__members ul {
  margin-top: var(--space-1);
  padding-left: var(--space-3);
  max-height: 13rem;
  overflow-y: auto;
  /* The +/− prefixes are the markers; no bullets on top of them. */
  list-style: none;
}

.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--space-1) var(--space-3);
  max-height: 20rem;
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-3);
}

.checkbox-grid__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.checkbox-grid__cell {
  min-width: 0;
}

.checkbox-grid__row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.checkbox-grid__row .checkbox-grid__item {
  flex: 1;
  min-width: 0;
}

.audience-fields__members-toggle {
  flex: none;
  display: inline-flex;
  border: 0;
  background: none;
  padding: 0;
  color: var(--color-text-muted);
  cursor: pointer;
}

.audience-fields__members-toggle:hover {
  color: var(--color-primary);
}

.audience-fields__members-toggle .material-icons-outlined {
  font-size: 16px;
}

/* Member popover: opens near the cursor, anchored to the page (it scrolls
   along with its row), scrolls internally for large groups. */
.audience-fields__members {
  position: absolute;
  z-index: 80;
  width: 16rem;
  margin: 0;
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  box-shadow: 0 12px 32px rgb(16 24 40 / 0.16);
  font-size: var(--text-sm);
  line-height: 1.5;
}

.audience-fields__members-title {
  margin: 0 0 var(--space-2);
  font-weight: 600;
}

/* See .popover-panel ul: the list scrolls, the panel background stays put. */
.audience-fields__members ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
  max-height: 13rem;
  overflow-y: auto;
}

.audience-fields__members p {
  margin: 0;
}

/* --- Audience fields -------------------------------------------------------- */

.audience-fields__section {
  margin-block-start: var(--space-2);
}

/* Custom chevron instead of the default marker, which hugs the far left. */
.audience-fields__section summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: 600;
  padding-block: var(--space-1);
  list-style: none;
}

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

.audience-fields__section summary::before {
  content: "";
  width: 0.45em;
  height: 0.45em;
  margin-inline-start: var(--space-1);
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 120ms ease;
  flex: none;
}

.audience-fields__section[open] > summary::before {
  transform: rotate(45deg);
}

.audience-fields__section .checkbox-grid {
  margin-block-start: var(--space-2);
}

.audience-fields__filter {
  width: 100%;
  margin-block: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  background: var(--color-surface);
}

.audience-fields__count {
  color: var(--color-text-muted);
  font-weight: 400;
}

.audience-fields__summary {
  margin-block-start: var(--space-3);
}

/* --- Dashboard ------------------------------------------------------------- */

/* The canvas is the user-resizable wrapper around the grid: its width is a
   percentage of the page (users.dashboard_width_percent, dragged at the
   right-edge handle in edit mode), centered. */
.dashboard-canvas {
  position: relative;
  margin-inline: auto;
}

/* Two layouts per user: the 768px breakpoint decides which one a device
   sees. The hidden canvas' lazy frames never load. */
.dashboard-canvas--mobile {
  display: none;
}

@media (max-width: 767.98px) {
  .dashboard-canvas--desktop {
    display: none;
  }

  .dashboard-canvas--mobile {
    display: block;
  }
}

.dashboard-canvas__resize {
  display: none;
  position: absolute;
  top: 0;
  bottom: 0;
  right: calc(-1 * var(--space-3));
  width: 10px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--color-border);
  cursor: ew-resize;
  touch-action: none;
}

body.cockpit-edit .dashboard-canvas__resize {
  display: block;
}

.dashboard-canvas__resize:hover {
  background: var(--color-primary);
}

/* Fine-grained grid for free resizing: every visual column is 10 sub-tracks,
   so widget sizes have 0.1-column resolution (1.2 x 1.4 works). The column
   count (2-6) is computed from the canvas width by the dashboard-grid
   Stimulus controller, which sets grid-template-columns and the widgets'
   spans (--w-span/--h-span, in sub-tracks) from data-w/data-h. One sub-row
   plus gap is 1/10 of the 22rem base unit: 10 x 1.3rem + 9 x 1rem = 22rem.
   Without JS everything falls back to 2 columns and 1x1 widgets. */
.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(20, minmax(0, 1fr));
  grid-auto-rows: 1.3rem;
  grid-auto-flow: dense;
  gap: var(--space-4);
}

.dashboard-grid > turbo-frame {
  display: block;
  min-width: 0;
  grid-column: span var(--w-span, 10);
  grid-row: span var(--h-span, 10);
}

.widget-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: var(--space-4);

  /* Fills its grid cell; long content scrolls inside the body. */
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.widget-card__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Loading skeleton: the placeholder borrows the real card anatomy
   (header/icon/heading/body classes), so sizes, gaps and margins come from
   the same rules as the loaded widget — on phones included. Only the grey
   shimmer shapes are added here. */
.skeleton {
  display: grid;
  gap: var(--space-4);
  align-content: start;
}

.widget-card--placeholder .widget-card__heading {
  flex: 1;
  display: grid;
  gap: 0.4rem;
}

.skeleton__bar {
  height: 0.875rem;
  border-radius: 999px;
}

.skeleton__bar--title {
  height: var(--text-h4, 1rem);
  max-width: 45%;
}

.skeleton__bar--source {
  height: var(--text-meta, 0.75rem);
  max-width: 30%;
}

.skeleton__bar--short {
  max-width: 60%;
}

/* The placeholder-scoped selector outweighs the theme's icon fill. */
.widget-card--placeholder .widget-card__icon,
.skeleton__bar {
  background: linear-gradient(100deg,
    var(--color-border) 40%,
    color-mix(in srgb, var(--color-border) 45%, var(--color-surface)) 50%,
    var(--color-border) 60%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
}

@keyframes skeleton-shimmer {
  from {
    background-position: 200% 0;
  }

  to {
    background-position: -200% 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .widget-card--placeholder .widget-card__icon,
  .skeleton__bar {
    animation: none;
  }
}

/* Header per the Cockpit mockup: icon circle + title with a source label. */
.widget-card__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-block-end: var(--space-3);
}

.widget-card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: var(--color-bg);
  flex: none;
}

.widget-card__icon .material-icons-outlined {
  font-size: 22px;
  color: var(--color-primary);
}

.widget-card__heading {
  min-width: 0;
}

.widget-card__heading h2 {
  font-size: var(--text-base);
  font-weight: 600;
  margin: 0;
  line-height: 1.1;
  /* Long titles wrap (hyphenated via the body's hyphens: auto) instead of
     being cut off with an ellipsis. */
  overflow-wrap: break-word;
}

.widget-card__source {
  display: block;
  font-size: 0.75rem;
  color: var(--color-text-muted);
  letter-spacing: 0.02em;
}

/* --- Dashboard edit mode (size presets + free resize) ------------------------- */

.dashboard-edit-toggle__done {
  display: none;
}

/* Reset button only appears while editing, next to the edit toggle. */
.dashboard-reset {
  display: none;
}

body.cockpit-edit .dashboard-reset {
  display: block;
}

body.cockpit-edit .dashboard-edit-toggle__edit {
  display: none;
}

body.cockpit-edit .dashboard-edit-toggle__done {
  display: inline;
}

.dashboard-hint {
  display: none;
  margin-block: calc(-1 * var(--space-2)) var(--space-4);
  font-size: var(--text-sm);
}

body.cockpit-edit .dashboard-hint {
  display: block;
}

/* Same look as the dashboard's edit hint, but always visible — the one-line
   "what can I do here" text under admin page heads. */
.page-hint {
  margin-block: calc(-1 * var(--space-2)) var(--space-4);
  font-size: var(--text-sm);
}

/* Secondary links below a list, e.g. the dumpster on the posts page. */
.page-foot {
  margin-block-start: var(--space-6);
}

/* Quiet text pager below lists: no pill buttons, just links around the
   page indicator, with clear air between it and the list above. */
.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  margin-block-start: var(--space-8);
  font-size: var(--text-sm);
}

.pagination__link {
  font-weight: 600;
  color: inherit;
  text-decoration: none;
}

/* Explicit none: the Planet theme underlines every a:hover. */
.pagination__link:hover {
  color: var(--color-primary);
  text-decoration: none;
}

.pagination__link--disabled {
  opacity: 0.35;
}

.pagination__info {
  opacity: 0.65;
}

/* Phones show the mobile canvas, which has no overall-width grip. */
@media (max-width: 767.98px) {
  .dashboard-hint__desktop {
    display: none;
  }
}

/* Edit tools float above the card content on a frosted pill, so they don't
   push the header around and stay usable at any widget size. */
.widget-card__tools {
  display: none;
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  z-index: 5;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
  max-width: calc(100% - 2 * var(--space-2));
  padding: 6px;
  border-radius: 26px;
  background: rgba(255, 255, 255, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);
}

body.cockpit-edit .widget-card__tools {
  display: flex;
}

.widget-card__tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  padding: 0 0.6rem;
  border-radius: 999px;
  cursor: pointer;
}

.widget-card__tools .material-icons-outlined {
  font-size: 24px;
}

.widget-card__tool.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-contrast);
}

/* Drag handle for reordering (SortableJS grabs the whole frame via this). */
.widget-card__drag {
  padding: 0;
  cursor: grab;
  touch-action: none;
}

/* The frame currently hovering over its drop slot while dragging. */
.dashboard-grid > turbo-frame.is-drop-target {
  opacity: 0.35;
}

.dashboard-grid > turbo-frame.sortable-drag {
  opacity: 0.9;
}

.widget-card__resize {
  display: none;
  position: absolute;
  right: var(--space-2);
  bottom: var(--space-2);
  z-index: 5;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);
  color: var(--color-text);
  cursor: nwse-resize;
  touch-action: none;
}

body.cockpit-edit .widget-card__resize {
  display: inline-flex;
}

.widget-card__resize .material-icons-outlined {
  font-size: 24px;
}

/* Hide tool: the button_to form must not become its own flex item. */
.widget-card__hide-form {
  display: contents;
}

.widget-card__hide {
  padding: 0;
}

/* --- Floating button + widget tray (the group of invisible widgets) ------------ */

/* Only visible while editing the layout ("Anpassen"), like the other
   edit tools. */
.widget-fab {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: 60;
  width: 56px;
  height: 56px;
  display: none;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
  cursor: grab;
  touch-action: none;
}

body.cockpit-edit .widget-fab {
  display: inline-flex;
}

.widget-fab .material-icons-outlined {
  font-size: 26px;
}

.widget-fab__badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--color-text);
  color: var(--color-surface);
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.widget-tray {
  position: fixed;
  z-index: 60;
  width: min(340px, calc(100vw - 24px));
  max-height: min(70vh, 560px);
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.18);
  padding: var(--space-3);
}

.widget-tray__title,
.widget-tray__section {
  margin: 0 0 var(--space-2);
  font-size: 1rem;
  font-weight: 600;
}

.widget-tray__section {
  margin-block-start: var(--space-3);
}

/* Drop zone for hidden widgets: dashed, keeps a minimum height so it can
   receive drops even when empty. */
.widget-tray__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-height: 64px;
  padding: var(--space-2);
  border: 1px dashed var(--color-border);
  border-radius: 12px;
}

.widget-tray__empty {
  display: none;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1.4;
  margin-block: var(--space-1) 0;
}

.widget-tray__list:empty + .widget-tray__empty {
  display: block;
}

/* Chips: the same turbo-frames as on the grid, collapsed to icon + title. */
.widget-tray__list turbo-frame {
  display: block;
  cursor: grab;
}

.widget-tray__list .widget-card {
  padding: var(--space-2);
  height: auto;
}

.widget-tray__list .widget-card__header {
  margin: 0 !important;
  padding: 0 !important;
  border: 0 !important;
}

.widget-tray__list .widget-card__body,
.widget-tray__list .widget-card__tools,
.widget-tray__list .widget-card__resize,
.widget-tray__list .widget-card__source {
  display: none !important;
}

.widget-tray__list .widget-card__icon {
  width: 2rem;
  height: 2rem;
}

.widget-tray__list .widget-card__icon .material-icons-outlined {
  font-size: 18px;
}

.widget-tray__list .widget-card__heading h2 {
  font-size: 1rem;
}

/* The tray serves the layout you're looking at: same 768px switch as the
   canvases. */
@media (max-width: 767.98px) {
  .widget-tray__group--desktop {
    display: none;
  }
}

@media (min-width: 768px) {
  .widget-tray__group--mobile {
    display: none;
  }
}

/* The picker is a single column inside the narrow tray. */
/* Inside the header's palette popover the cards stack. */
.theme-picker--menu {
  grid-template-columns: 1fr;
}

/* The palette button in the header: anchor for its popover panel, icon a
   notch larger than the tiny inline info toggles. */
.theme-menu {
  position: relative;
}

.theme-menu .popover-toggle .material-icons-outlined {
  font-size: 20px;
  /* Optical alignment with the neighboring header items. */
  padding-top: 4px;
}

.popover-panel--theme {
  width: 17rem;
}

/* While dragging, keep the resize cursor and don't select text. */
body.is-resizing,
body.is-resizing * {
  cursor: nwse-resize !important;
  user-select: none;
}

body.is-resizing--x,
body.is-resizing--x * {
  cursor: ew-resize !important;
}

.widget-welcome__greeting {
  font-size: var(--text-xl);
  font-weight: 700;
  margin-block: 0 var(--space-2);
}

.card-list__item--muted {
  opacity: 0.6;
}

/* The Personio master group ("Alle Mitarbeitenden") leads the groups page;
   a small primary star next to the name is its only marker. */
.card-list__master-star {
  font-size: 18px;
  color: var(--color-primary);
  vertical-align: text-bottom;
  margin-inline-end: var(--space-1);
}

.popover-toggle--warning {
  color: var(--color-danger);
}

/* The popover toggles sit inside a text line here (next to the member
   count), unlike the audience form's flex rows. */
.card-list__item .popover-toggle {
  vertical-align: middle;
  margin-inline-start: var(--space-1);
}

.card-list__item .popover-toggle .material-icons-outlined {
  font-size: 18px;
}

/* --- Post widgets ------------------------------------------------------------ */

.widget-posts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Via custom property so themes resizing the gap (Planet: space-8 desktop,
     space-3 phones) keep the divider centered between posts. */
  --post-gap: var(--space-4);
  gap: var(--post-gap);
}

/* A visible boundary between posts — the gap alone doesn't read as one. */
.widget-posts__item + .widget-posts__item {
  border-top: 1px solid var(--color-border);
  padding-top: var(--post-gap);
}

/* Grid items default to min-width auto and would refuse to shrink below
   the intrinsic width of images/code blocks inside them. */
.widget-posts__item {
  min-width: 0;
}

.widget-posts__title {
  font-size: var(--text-base);
  margin-block-end: var(--space-1);
}

.widget-posts__meta {
  margin-block: 0 var(--space-1);
}

.widget-posts__body p {
  margin-block: 0 var(--space-2);
  font-size: var(--text-sm);
}

/* Embedded images never grow wider than their container. */
figure.attachment {
  margin: 0 0 var(--space-2);
}

figure.attachment img {
  max-width: 100%;
  height: auto;
  border-radius: 6px;
}

/* Code blocks scroll horizontally instead of overflowing their card. */
pre {
  overflow-x: auto;
  max-width: 100%;
  padding: var(--space-2);
  border-radius: 6px;
  background: var(--color-bg);
  font-size: var(--text-sm);
}

/* --- Badges -------------------------------------------------------------------- */

.badge {
  display: inline-block;
  border-radius: 999px;
  padding: 0 var(--space-2);
  font-size: var(--text-sm);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
}

.badge--published {
  color: var(--color-success);
  border-color: #abefc6;
  background: #ecfdf3;
}

.badge--scheduled {
  color: var(--color-primary);
  border-color: #b2ccff;
  background: #eff4ff;
}

/* Tab strip inside widgets (Asana: overdue / today / comments). Tabs that
   don't fit wrap to a second row instead of overflowing the card. */
.w-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-1);
  border-bottom: 1px solid var(--color-border);
  margin-block-end: var(--space-3);
}

.w-tabs__tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  background: none;
  font: inherit;
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  /* Right padding stretches the active underline past the count. */
  padding: var(--space-2) var(--space-3) var(--space-2) 0;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  /* Long labels ("Bestellchancen") hyphenate on narrow cards instead of
     overflowing. Words of 8 chars or less stay whole: hyphenate-limit-chars
     sets the minimum word length (Chromium/Firefox); the -webkit- pair
     approximates it for WebKit (5 before + 4 after the hyphen = 9-char
     minimum). No min-width: 0 here — a tab shrunk below its content would
     paint over its neighbor; tabs that don't fit wrap via the strip. */
  hyphens: auto;
  hyphenate-limit-chars: 9;
  -webkit-hyphenate-limit-before: 5;
  -webkit-hyphenate-limit-after: 4;
  overflow-wrap: break-word;
}

.w-tabs__tab.is-active {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

.w-tabs__count {
  font-weight: 400;
  font-size: 0.75rem;
}

/* Shared row pattern for data widgets (calendar, tasks, records, birthdays):
   time/date column, main content, optional action link — baseline-aligned so
   multi-line titles keep the date on the first line. */
.w-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.w-list__row {
  display: grid;
  /* Fixed time column so every row's content starts at the same x position
     (each row is its own grid — auto would size per row). */
  grid-template-columns: 5.5rem 1fr auto;
  gap: var(--space-3);
  align-items: baseline;
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
  line-height: 1.35;
}

.w-list__row:last-child {
  border-bottom: 0;
}

/* Long ranges ("07:00 Uhr–09:00 Uhr") wrap inside the column instead of
   pushing the content around. */
.w-list__time {
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  overflow-wrap: break-word;
}

/* Calendar ranges: stacked "09:00 Uhr– / 09:45 Uhr" on desktop, the compact
   "09:00–09:45" (no "Uhr") on phones. */
.w-list__time-compact {
  display: none;
}

@media (max-width: 767.98px) {
  .w-list__time-full {
    display: none;
  }

  .w-list__time-compact {
    display: inline;
  }
}

.w-list__time--alert,
.w-list__time--highlight {
  justify-self: start;
}

/* The birthday pill's emoji takes too much room in the compact layout. */
@media (max-width: 767.98px) {
  .w-list__time-emoji {
    display: none;
  }
}

.w-list__time--alert {
  color: var(--color-danger);
  font-weight: 600;
}

.w-list__time--highlight {
  font-weight: 600;
  color: var(--color-text);
}

.w-list__main {
  min-width: 0;
}

.w-list__main strong {
  display: block;
  font-weight: 600;
}

.w-list__meta {
  display: block;
  color: var(--color-text-muted);
  font-size: 0.8125rem;
}

.w-list__action {
  color: var(--color-primary);
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}

.w-list__action:hover {
  opacity: 0.7;
  text-decoration: none;
}

/* --- Personal Gains widget -------------------------------------------------- */

.gains {
  display: grid;
  gap: var(--space-3);
  font-size: var(--text-sm);
}

.gains__error {
  margin: 0;
  color: var(--color-danger);
}

.gains__composer textarea {
  width: 100%;
  resize: none;
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--text-sm);
  background: var(--color-surface);
}

.gains__composer-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.gains__composer-actions {
  display: inline-flex;
  gap: var(--space-2);
}

/* Widget-sized buttons — the global .btn padding is built for full pages. */
.gains__composer .btn {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
}

.gains__counter {
  color: var(--color-text-muted);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

.gains__locked {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.gains__locked .material-icons-outlined {
  font-size: 16px;
}

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

.gains__item {
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}

/* Avatar + name/time row; on phones body and reactions below span the full
   card width. */
.gains__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Desktop keeps the classic indented layout: body, edit form, and reactions
   align with the text column right of the avatar (2rem + 0.5rem gap). */
@media (min-width: 768px) {
  .gains__item > :not(.gains__head) {
    margin-inline-start: 2.5rem;
  }
}

.gains__item:last-child {
  border-bottom: 0;
}

.gains__item .avatar {
  width: 2rem;
  height: 2rem;
  font-size: 0.6875rem;
}


/* Type hierarchy per item: the message is the content (base size), the name
   identifies it (sm, semibold), time/edited are the small print. */
.gains__meta {
  margin: 0;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  /* Horizontal only — wrapped lines (mobile: time below the name) sit tight. */
  column-gap: var(--space-2);
  flex-wrap: wrap;
}

.gains__meta strong {
  font-size: var(--text-sm);
}

.gains__time {
  color: var(--color-text-muted);
  font-size: 0.8125rem;
}

/* The auto margin pushes the icons to the end of the meta row. */
.gains__actions {
  margin-inline-start: auto;
  display: inline-flex;
  gap: var(--space-1);
}

.gains__action-form {
  display: inline-flex;
}

.gains__action {
  display: inline-flex;
  border: 0;
  background: none;
  padding: 0;
  color: var(--color-text-muted);
  cursor: pointer;
  text-decoration: none;
}

.gains__action:hover {
  color: var(--color-primary);
  text-decoration: none;
}

.gains__action .material-icons-outlined {
  font-size: 16px;
}

.gains__body {
  margin: var(--space-1) 0 0;
  font-size: var(--text-base);
  line-height: 1.4;
  overflow-wrap: break-word;
}

/* Phones: smaller message text (desktop keeps the base size), and the
   full-width name pushes the timestamp and action icons onto the line
   below it. */
@media (max-width: 767.98px) {
  .gains__body {
    /* Same size as rendered post bodies (themes re-scope --text-meta inside
       widget cards, e.g. Planet: 0.75rem on phones). */
    font-size: var(--text-meta, 0.8125rem);
  }

  .gains__composer textarea {
    /* On touch devices the iOS zoom guard below overrides this to 16px. */
    font-size: 0.875rem;
  }

  .gains__meta strong {
    width: 100%;
  }
}

.gains__reactions {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
  margin-top: var(--space-2);
}

.gains__reaction-form {
  display: inline-flex;
}

.gains__reaction {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  padding: 2px 10px;
  font: inherit;
  font-size: var(--text-sm);
  line-height: 1.4;
  cursor: pointer;
}

.gains__reaction:hover {
  border-color: var(--color-primary);
}

.gains__reaction--active {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}

.gains__reaction-count {
  font-size: 0.8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.badge--channel {
  color: var(--color-text);
  font-weight: 600;
}

.badge--hidden {
  color: var(--color-text-muted);
  border-color: var(--color-border);
  background: var(--color-surface-muted, #f2f4f7);
}

.badge--expired {
  color: var(--color-danger);
  border-color: #fecdca;
  background: #fef3f2;
}

/* Editor post rows: title, then a dedicated badge row, then the byline. */
.post-row__title {
  display: block;
}

.post-row__badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin-block: var(--space-2);
}

/* Language completeness chips in the editor's post list. */
.lang-chips {
  display: inline-flex;
  gap: var(--space-1);
  flex-wrap: wrap;
  vertical-align: middle;
}

.badge--lang-done {
  color: #15803d;
  border-color: #86efac;
  background: #dcfce7;
}

.badge--lang-missing {
  color: #98a2b3;
  border-color: var(--color-border);
  background: transparent;
}

/* Audience: visible to everyone (default, unobtrusive) vs. a targeted
   subset (sky blue, stands out). */
.badge--audience-all {
  color: var(--color-text-muted);
  border-color: var(--color-border);
  background: transparent;
}

.badge--audience-limited {
  color: #0369a1;
  border-color: #7dd3fc;
  background: #e0f2fe;
}

/* --- AI panel -------------------------------------------------------------------- */

.ai-panel {
  border: 1px dashed var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-4);
  gap: var(--space-3);
}

/* Status lines are empty until the AI writes; don't reserve a blank row. */
.form [data-ai-assist-target="status"]:empty,
.form [data-ai-role="status"]:empty {
  display: none;
}

.ai-panel textarea {
  width: 100%;
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-2) var(--space-3);
  font: inherit;
}

.form__field textarea,
.form__field select,
.form__field input[type="datetime-local"] {
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) / 2);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  background: var(--color-surface);
}

/* Empty datetime fields: the native format mask ("tt.mm.jjjj --:--") is
   cryptic, so a label saying what "empty" means sits on top instead. The
   controller hides the mask (transparent text) and reveals it on focus. */
.datetime-field {
  position: relative;
  display: grid;
  /* One full-width track: an implicit auto track would size to the input's
     intrinsic width and the field would shrink. */
  grid-template-columns: minmax(0, 1fr);
}

.datetime-field input {
  width: 100%;
}

.datetime-field input.is-masked:not(:focus) {
  color: transparent;
}

/* Chrome renders the segments in a shadow pseudo-element; make sure the
   transparency reaches it. (Separate rule: grouping it with a standard
   selector would invalidate both in browsers without -webkit pseudos.) */
.datetime-field input.is-masked:not(:focus)::-webkit-datetime-edit {
  color: transparent;
}

.datetime-field__placeholder {
  position: absolute;
  inset-block: 0;
  inset-inline-start: var(--space-3);
  display: flex;
  align-items: center;
  pointer-events: none;
  color: var(--color-text-muted);
}

/* display: flex above beats the UA's [hidden] { display: none } — without
   this rule the hidden attribute set by the controller has no effect and
   the label stacks on top of the value. */
.datetime-field__placeholder[hidden] {
  display: none;
}

/* While editing, the native picker text must never be covered — independent
   of the controller's state. (Unmasking on focus is handled by the
   :not(:focus) guard on the mask rules above.) */
.datetime-field:focus-within .datetime-field__placeholder {
  display: none;
}

/* --- Theme picker (customize page) ------------------------------------------- */

.section-title {
  margin-block: var(--space-8) var(--space-4);
  font-size: var(--text-xl);
}

.theme-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--space-4);
}

.theme-picker__card {
  border: 1px solid var(--color-border);
  padding: var(--space-4);
  display: grid;
  gap: var(--space-3);
}

.theme-picker__preview {
  display: flex;
  gap: var(--space-2);
}

.theme-picker__preview span {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: 1px solid var(--color-border);
}

/* Swatches and card colors live in the base stylesheet: every theme's
   preview must render correctly no matter which theme is currently active.
   Each card is painted in its design's own page background (the bg swatch
   circle would be invisible on it, so the strips carry three swatches);
   the theme stylesheets counter-filter the whole card, so these literal
   colors survive any active theme's page filter. */
.theme-picker__card--planet,
.theme-picker__card--venus {
  background: #F5F5F5;
}

.theme-picker__card--jupiter,
.theme-picker__card--pluto {
  background: #0A0A0A;
  color: #FFFFFF;
}

/* Ghost buttons are black-on-light by default — flip them on the dark
   cards. (The name and checkmark simply inherit the card's text color.) */
.theme-picker__card--jupiter .btn--ghost,
.theme-picker__card--pluto .btn--ghost {
  color: #FFFFFF;
  border-color: #FFFFFF;
  background: transparent;
}

.theme-picker__card--jupiter .btn--ghost:hover,
.theme-picker__card--pluto .btn--ghost:hover {
  background: #FFFFFF;
  color: #0A0A0A;
}

.theme-picker__preview--planet span:nth-child(1) { background: #0000FF; }
.theme-picker__preview--planet span:nth-child(2) { background: #D4FF00; }
.theme-picker__preview--planet span:nth-child(3) { background: #000000; }

/* Venus: Planet rotated one channel forward (r→g, g→b, b→r). Greys stay. */
.theme-picker__preview--venus span:nth-child(1) { background: #FF0000; }
.theme-picker__preview--venus span:nth-child(2) { background: #00D4FF; }
.theme-picker__preview--venus span:nth-child(3) { background: #000000; }

/* Jupiter: the RGB inverses of the Planet palette. */
.theme-picker__preview--jupiter span:nth-child(1) { background: #FFFF00; }
.theme-picker__preview--jupiter span:nth-child(2) { background: #2B00FF; }
.theme-picker__preview--jupiter span:nth-child(3) { background: #FFFFFF; }

/* Pluto: Planet with the luminance flipped and the hue rotated one step —
   see themes/pluto.css for the math. */
.theme-picker__preview--pluto span:nth-child(1) { background: #55FF55; }
.theme-picker__preview--pluto span:nth-child(2) { background: #C7009C; }
.theme-picker__preview--pluto span:nth-child(3) { background: #FFFFFF; }

.theme-picker__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* Marker for the active design, next to its name. */
.theme-picker__check {
  font-size: 16px;
  vertical-align: -3px;
}

/* --- Admin nav --------------------------------------------------------------- */

.admin-nav {
  display: flex;
  gap: var(--space-1);
  margin-block-end: var(--space-4);
  border-bottom: 1px solid var(--color-border);
  /* Same as .locale-tabs: scroll internally on narrow screens. */
  overflow-x: auto;
}

/* Underlined tabs, same pattern as .locale-tabs__tab: the active link's 2px
   underline overlaps the strip's 1px border via the negative margin. */
.admin-nav__link {
  text-decoration: none;
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
  padding: var(--space-2) var(--space-3);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.admin-nav__link.is-active {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

/* Loader buttons: application.js marks the submitter with .is-loading while
   its form is in flight — the spinner bridges slow saves such as the inline
   AI label translation. Keyed off the class (not :disabled): some buttons
   are disabled while idle, e.g. the post form's save until the form is dirty. */
.btn.is-loading::after {
  content: "";
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  margin-left: 0.5em;
  vertical-align: -0.1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: btn-loader-spin 0.8s linear infinite;
}

@keyframes btn-loader-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Drag-to-sort rows (admin widgets index, list_reorder_controller.js). */
.drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border: 0;
  background: none;
  padding: var(--space-1);
  color: var(--color-text-muted);
  cursor: grab;
  touch-action: none;
}

.drag-handle:active {
  cursor: grabbing;
}

/* The row's content column, next to the handle. */
.card-list__main {
  flex: 1;
  min-width: 0;
}

/* The row's original slot while its clone is being dragged. */
.card-list .sortable-ghost {
  opacity: 0.4;
}

/* --- iOS zoom guard ----------------------------------------------------------- */

/* iOS zooms the page when a focused control renders under 16px, and the
   zoom knocks position: fixed overlays (confirm dialog, FAB) out of the
   visible viewport. Keep every focusable field at 16px on phones — listed
   explicitly to out-cascade the component rules above that set --text-sm.
   pointer: coarse limits this to real touch devices; a narrow desktop
   browser window doesn't auto-zoom and keeps the smaller field text. */
@media (max-width: 767.98px) and (pointer: coarse) {
  .form__field input,
  .form__field select,
  .form__field textarea,
  .locale-switcher select,
  .gains__composer textarea,
  .ai-panel textarea,
  trix-editor {
    font-size: 1rem;
  }
}
