/* Projects & tasks (Kanban). Loaded automatically by app_stylesheets_tag. */
@layer components {
  :root {
    --kanban-column-width: 20rem;
    --kanban-card-gap: 0.55rem;
  }

  /* ---- Board ---- */

  /* Board mode: the page never scrolls, the columns do (frame rule in the
     utilities layer at the end of this file). The surface takes what the
     resource-header leaves and hands it down to the board. */
  .kanban-surface--board {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  .kanban-board {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    /* Columns run the full height: a big, steady drop target, and the
       "+ Aufgabe" button sits at the same spot however many cards there are. */
    align-items: stretch;
    /* Wider than the card gap: the columns breathe, and on a photo
       background more of the picture shows between them. */
    gap: 1.5rem;
    padding: 1rem 1.5rem 1rem;
    overflow-x: auto;
  }

  /* The column wears its state color as a soft wash — color-mix keeps any
     user-picked color easy on the eye instead of painting it as a dot. */
  .kanban-column {
    flex: 0 0 var(--kanban-column-width);
    display: flex;
    flex-direction: column;
    min-height: 0; /* lets the card list shrink and scroll instead of growing the column */
    position: relative; /* anchors the frost layer on a photo background (below) */
    gap: var(--kanban-card-gap);
    padding: 0.75rem;
    background: color-mix(in srgb, var(--state-accent, var(--muted-3)) var(--state-wash, 10%), var(--cream-soft));
    border: 1px solid color-mix(in srgb, var(--state-accent, var(--muted-3)) calc(var(--state-wash, 10%) + 12%), var(--surface-border));
    border-radius: var(--radius-card);
  }

  .kanban-column-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0 0.25rem 0.35rem;
    cursor: grab; /* columns are dragged by their header */
  }

  /* Drag & drop ghosts (SortableJS drops these classes on the placeholder). */
  .kanban-card-ghost,
  .kanban-column-ghost {
    opacity: 0.4;
  }

  /* Small print inside the move dialogs. */
  .dialog-hint {
    margin-top: 0.6rem;
    font-size: 0.8rem;
    color: var(--muted-2);
  }

  .kanban-column-dot {
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    background: var(--state-accent, var(--muted-3));
    flex-shrink: 0;
  }

  /* The name is a blitz inline_edit; the editable span carries the old
     .kanban-column-name look. The form is itself a centering flex box, so
     it hugs the name instead of adding line-height leading around it —
     with everything center-aligned in the header, name, count and menu
     share one vertical middle. Content-sized (not grow), so the count
     sits right beside the name. */
  .kanban-column-name-form {
    display: flex;
    align-items: center;
    flex: 0 1 auto;
    min-width: 0;
  }

  .kanban-column-name-form [data-editable-target="editable"] {
    display: inline-block;
    max-width: 100%;
    font-weight: 600;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Card count beside the column name — a quiet pill in the column's
     accent wash; the kanban Stimulus controller keeps the number live. */
  .kanban-column-count {
    flex-shrink: 0;
    min-width: 1.5em;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
    text-align: center;
    line-height: 1;
    font-size: 0.72rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
    background: color-mix(in srgb, var(--state-accent, var(--muted-3)) 18%, var(--surface));
  }

  .kanban-column-menu {
    margin-left: auto;
  }

  /* Palette grid inside the column ⋮ menu — the only color source. */
  .column-color-grid {
    display: grid;
    grid-template-columns: repeat(5, 1.6rem);
    gap: 0.35rem;
    padding: 0.3rem 1rem 0.5rem;
  }

  .kanban-column-menu .column-color-grid a {
    display: inline-flex;
    padding: 0;
    background: none;
  }

  /* Swatches preview the RESULTING column wash, not the raw color —
     the same mix the column background gets (soft 10%, bold 26%). The
     border carries more of the color so the grid stays tellable. */
  .column-color-swatch span {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    background: color-mix(in srgb, var(--swatch) var(--swatch-wash, 10%), var(--cream-soft));
    border: 1px solid color-mix(in srgb, var(--swatch) 55%, var(--surface-border));
  }

  .column-color-swatch:hover span {
    border-color: var(--wine);
  }

  /* Own selected look — blitz's a.selected paints a ✓ that has no room
     in a swatch circle. */
  .kanban-column-menu .column-color-grid a.selected {
    background: none;
  }

  .kanban-column-menu .column-color-grid a.selected::after {
    content: none;
  }

  .column-color-swatch.selected span {
    outline: 2px solid var(--wine);
    outline-offset: 2px;
  }

  /* Radio variant of the swatch (new-column dialog). */
  .column-color-swatch input[type="radio"] {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  .column-color-swatch:has(input:checked) span {
    outline: 2px solid var(--wine);
    outline-offset: 2px;
  }

  .column-color-swatch:has(input:focus-visible) span {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
  }

  .column-color-grid--form {
    border: none;
    margin: 0.5rem 0 0;
    padding: 0;
  }

  .column-color-grid--form .column-color-swatch {
    position: relative;
    display: inline-flex;
    cursor: pointer;
  }

  /* The scroll wrapper (cards plus the lazy frame that loads the rest) is
     the column's scroll container: it takes the height between header and
     "+ Aufgabe" and scrolls on its own, so dragging a card to another
     column never has to fight the page scroll. Padding and the matching
     negative margin keep the layout metrics unchanged while the scrollport
     reaches the column's edges — the hover lift and shadow of the
     first/last card and the sides are not clipped, and the scrollbar sits
     at the column's edge rather than beside the cards. */
  .kanban-column-scroll {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: var(--kanban-card-gap);
    min-height: 0;
    overflow-y: auto;
    scrollbar-width: thin;
    padding: 0.25rem 0.75rem 0.5rem;
    margin: -0.25rem -0.75rem -0.5rem;
  }

  /* The card list — Sortable's container and the broadcast target. It
     grows to the scroll wrapper's full height: Sortable only accepts a
     drop INSIDE this element, so an empty or short column must offer its
     whole area, not just the strip the loaded cards cover. Once the cards
     overflow, the list is content-sized anyway and the lazy frame follows
     the last card. */
  .kanban-column-cards {
    display: flex;
    flex-direction: column;
    gap: var(--kanban-card-gap);
    min-height: 2.5rem; /* drop target stays reachable when empty */
    flex: 1 0 auto;
  }

  /* The lazy frame below the loaded cards: a quiet spinner line that
     scrolls into view and is replaced by the next page. */
  .kanban-column-more {
    display: block;
    flex-shrink: 0;
    padding: 0.25rem 0;
    font-size: 0.85rem;
    color: var(--muted);
  }

  .kanban-column-more .spinner-wrapper {
    margin: 0;
    color: inherit;
  }

  /* ---- Project page background ---- */

  .kanban-surface {
    background-size: cover;
    background-position: center;
  }

  /* On a photo background the columns go translucent with a soft blur —
     the wash mixes into frosted white instead of cream, cards stay opaque.
     Light frost and a gentle blur: the picture stays recognizable through
     the column, the blur only takes the edge off it so the cards read.

     The blur lives on a pseudo-element, NOT on the column itself: a
     backdrop-filter turns its element into the containing block for
     position: fixed descendants, and that is what the card's ⋮ menu relies
     on where the Popover API is not used (blitz's dropdown skips it on
     iPhone/iPad and positions the menu itself). Fixed to a column, the
     menu would fall back to absolute placement inside the card and the
     column's scroll container would clip it (task 179). The frost layer
     sits behind the column's wash at z-index -1; the surface isolates its
     stacking so "behind the column" still means "above the photo". */
  .kanban-surface.has-background {
    isolation: isolate;
  }

  .kanban-surface.has-background .kanban-column {
    background: color-mix(in srgb, var(--state-accent, var(--muted-3)) var(--state-wash, 10%), rgba(255, 255, 255, 0.42));
  }

  .kanban-surface.has-background .kanban-column::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    backdrop-filter: blur(4px);
    pointer-events: none;
  }

  /* Muted greys drown over photos — column chrome switches to full ink. */
  .kanban-surface.has-background .kanban-column-name-form [data-editable-target="editable"],
  .kanban-surface.has-background .kanban-add-task,
  .kanban-surface.has-background .kanban-column-count,
  .kanban-surface.has-background .kanban-column-header .kanban-card-menu-btn {
    color: var(--ink);
  }

  .background-picker-button {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 5;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.6rem;
    height: 2.6rem;
    border-radius: 50%;
    background: color-mix(in srgb, var(--surface) 85%, transparent);
    border: 1px solid var(--surface-border);
    color: var(--muted);
    box-shadow: 0 2px 8px rgba(60, 30, 40, 0.15);
  }

  .background-picker-button:hover {
    color: var(--wine);
    background: var(--surface);
  }

  .background-dialog {
    min-width: min(34rem, 86vw);
  }

  .background-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.6rem;
  }

  .background-option {
    display: block;
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-control);
    overflow: hidden;
    border: 2px solid transparent;
  }

  .background-option img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  .background-option--none {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--cream-soft);
    border-color: var(--surface-border);
    color: var(--muted);
    font-size: 0.82rem;
    text-decoration: none;
    text-align: center;
  }

  .background-option:hover {
    border-color: var(--wine);
  }

  .background-option.selected {
    border-color: var(--wine);
    outline: 2px solid var(--wine);
    outline-offset: 1px;
  }

  .background-hint {
    margin: 0.6rem 0 0;
    font-size: 0.78rem;
    color: var(--muted-2);
  }

  /* ---- Card ---- */

  .kanban-card {
    position: relative;
    background: var(--surface);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius-control);
    padding: 0.6rem 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    box-shadow: 0 1px 3px rgba(60, 30, 40, 0.08);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
  }

  /* The title link stretches over the whole card (notes-card pattern) —
     everywhere opens the dialog except star and ⋮, lifted above it. */
  .kanban-card-title-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
  }

  .kanban-card-favorite,
  .kanban-card-top .kanban-card-menu {
    position: relative;
    z-index: 2;
  }

  /* Hover lifts the card slightly, deepening the resting shadow — only
     where hover exists. On touch the tapped card would keep the state
     (sticky :hover), and the transform would make the card the containing
     block for the ⋮ menu's fixed placement — clipped by the column's
     scroll container, like the frost layer above. */
  @media (hover: hover) {
    .kanban-card:hover {
      transform: translateY(-2px);
      box-shadow: 0 6px 16px rgba(60, 30, 40, 0.16);
    }
  }

  /* No lift while Sortable drags — the ghost should sit flat in place. */
  .kanban-card-ghost,
  .kanban-card-ghost:hover {
    transform: none;
    box-shadow: none;
  }

  /* The drag handles (kanban_controller): with a mouse the whole card and
     the whole column header drag, so the six dots stay hidden. Wherever
     touch is the primary pointer — and on small screens, whatever they
     report — they are the only place a drag starts; the finger anywhere
     else scrolls. touch-action: none keeps the page from scrolling under
     the finger on the handle itself (today.css has the same pattern). */
  .kanban-card-handle,
  .kanban-column-handle {
    display: none;
    color: var(--muted-2);
    --icon-size: 1.1rem;
    cursor: grab;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
  }

  @media (pointer: coarse), (max-width: 640px) {
    /* A strip down the card's full left edge — a finger needs more than
       the icon. Lifted above the stretched title link like star and ⋮. */
    .kanban-card {
      padding-left: 2.1rem;
    }

    .kanban-card-handle {
      display: flex;
      align-items: center;
      justify-content: center;
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      width: 1.9rem;
      z-index: 2;
      --icon-size: 1.25rem;
      border-radius: var(--radius-control) 0 0 var(--radius-control);
    }

    /* Six dots at the header's start, the row's full height. */
    .kanban-column-handle {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      align-self: stretch;
      flex-shrink: 0;
      width: 1.5rem;
      margin-left: -0.25rem;
    }

    .kanban-column-header {
      cursor: default;
    }
  }

  /* The lifted copy that follows the finger (Sortable's touch fallback):
     no transition — the card's transform transition would make it lag —
     and a deeper shadow than the resting card. */
  .kanban-card-drag {
    transition: none;
    box-shadow: 0 8px 22px rgba(60, 30, 40, 0.24);
  }

  /* Important tasks tint like favorite notes (item_list.css palette), and
     off the same attribute: favorite_controller flips it on click, so the
     star answers to the click alone — no round trip, no redraw. */
  .kanban-card[data-favorite-favorite="true"] {
    background: #faf0d4;
  }

  /* Star, id and the label pills; the row wraps, so a third or fourth
     label goes onto a line of its own beneath. The ⋮ is taken out of the
     flow into the corner — as a flex item it would wrap along. */
  .kanban-card-top {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.3rem 0.5rem;
    padding-right: 1.6rem;
    min-height: 1.3rem;
  }

  .kanban-card-top .kanban-card-menu {
    position: absolute;
    top: 0.6rem;
    right: 0.6rem;
  }

  /* Star and sun share one system: idle a light outline in the muted
     ramp, hover the outline one step darker, picked the gold fill. */
  .kanban-card-favorite {
    display: inline-flex;
    border: none;
    background: none;
    padding: 0.1rem;
    cursor: pointer;
    color: var(--muted-3);
    transition: color 0.3s ease;
  }

  .kanban-card-favorite:hover {
    color: var(--muted-2);
  }

  /* star-light.svg is a filled glyph despite its name; the idle star on
     the board is an outline like the sun. The picked swap to star-fill
     (below, same specificity, later in the file) wins over this. */
  .kanban-card-favorite .icon {
    --svg: url("/assets/star-outline-c6e664da.svg");
  }

  .kanban-card[data-favorite-favorite="true"] .kanban-card-favorite {
    color: #e0a800;
  }

  .kanban-card[data-favorite-favorite="true"] .kanban-card-favorite:hover {
    color: #f0c65a;
  }

  .kanban-card-id {
    font-size: 0.72rem;
    color: var(--muted-2);
    font-variant-numeric: tabular-nums;
  }

  .kanban-card-menu-btn {
    border: none;
    background: none;
    padding: 0.1rem;
    cursor: pointer;
    color: var(--muted-2);
    border-radius: 6px;
  }

  .kanban-card-menu-btn:hover {
    background: var(--rose-tint);
    color: var(--wine);
  }

  .kanban-card-title {
    color: var(--ink);
    line-height: 1.35;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }

  .kanban-card-footer {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 1.4rem;
  }

  /* ---- Labels (Task#labels) ---- */

  /* A pill: the color's ink on its wash. The palette is fixed
     (Task::LABEL_COLORS) — one modifier class per color, nothing inline. */
  .task-label {
    --label-ink: #5f5a5e;
    --label-bg: #ece7e8;
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    max-width: 100%;
    font-size: 0.68rem;
    font-weight: 600;
    line-height: 1.2;
    padding: 0.14rem 0.55rem;
    border-radius: 999px;
    color: var(--label-ink);
    background: var(--label-bg);
  }

  .task-label-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Gray is the base look; named too so the palette swatch gets it. */
  .task-label--gray   { --label-ink: #5f5a5e; --label-bg: #ece7e8; }
  .task-label--green  { --label-ink: #2f6b44; --label-bg: #e2f1e6; }
  .task-label--gold   { --label-ink: var(--gold-ink); --label-bg: var(--gold); }
  .task-label--wine   { --label-ink: var(--wine); --label-bg: #f7e0e3; }
  .task-label--blue   { --label-ink: #2d5a8a; --label-bg: #dfeaf6; }
  .task-label--purple { --label-ink: #6b3f9a; --label-bg: #ece2f6; }
  .task-label--teal   { --label-ink: #23707a; --label-bg: #dcf0f1; }
  .task-label--orange { --label-ink: #a0521f; --label-bg: #fbe6d6; }

  .kanban-list-title .task-label {
    margin-left: 0.4rem;
    vertical-align: middle;
  }

  .kanban-card-comments,
  .kanban-card-images,
  .kanban-card-files,
  .kanban-card-date {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-size: 0.78rem;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
  }

  .kanban-card-date {
    color: var(--muted-2);
    white-space: nowrap;
  }

  .kanban-card-assignee {
    margin-left: auto;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--wine-deep);
    background: var(--rose);
    flex-shrink: 0;
  }

  /* "+ Aufgabe" ghost button in the first column (direct child — the card
     star forms are button_to too and must keep their natural size). */
  .kanban-column > form.button_to {
    display: flex;
  }

  /* "+" next to the last column, at title-bar height. */
  .kanban-add-column {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.2rem;
    height: 2.2rem;
    margin-top: 0.55rem; /* lines up with the column titles */
    border: 1px dashed var(--surface-border);
    border-radius: var(--radius-control);
    background: none;
    cursor: pointer;
    color: var(--muted);
  }

  .kanban-add-column:hover {
    background: var(--rose-tint);
    color: var(--wine);
    border-color: var(--muted-3);
  }

  .kanban-add-task {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    border: none;
    background: none;
    cursor: pointer;
    padding: 0.45rem;
    border-radius: var(--radius-control);
    color: var(--muted);
    font-size: 0.88rem;
  }

  .kanban-add-task:hover {
    background: var(--rose-tint);
    color: var(--wine);
  }

  /* ---- List view ----
     The list renders as stacked cards on every screen size — the same
     recipe (radius, border, shadow, spacing) as the shared item lists.
     Sorting lives in the toolbar dropdown; there is no table header. */

  .kanban-list-wrap {
    padding: 0.75rem 0 2rem;
    /* Stretch to the bottom of the viewport: the cell dropdowns get room
       below instead of being clipped. */
    min-height: calc(100dvh - 10.5rem);
  }

  .kanban-list,
  .kanban-list tbody {
    display: block;
    width: 100%;
  }

  .kanban-list tbody {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
  }

  /* Star and ✕ sit out of flow, vertically centered like the notes
     cards — the side paddings reserve their gutters. Both text lines
     start at the same x, so the state dot lines up with the id. The
     title's flex basis leaves no room next to it, forcing line two. */
  .kanban-list tbody tr {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 0.35rem;
    row-gap: 0.15rem;
    padding: 0.55rem 2.1rem 0.55rem 2.3rem;
    border-radius: 12px;
    border: 1px solid var(--surface-border);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.08);
    /* Plain surface like the other resources' list rows — the column
       color stays on the state dot, important rows go gold. */
    background: var(--surface);
  }

  /* Same gold as the notes/bookmarks rows (item_list.css). Restated here:
     favorite.css's bare attribute selector loses to the tr rule above. */
  .kanban-list tbody tr[data-favorite-favorite="true"] {
    background: #faf0d4;
  }

  .kanban-list tbody tr td {
    display: block;
    padding: 0;
    border: none;
  }

  .kanban-list-star {
    position: absolute;
    left: 0.45rem;
    top: 50%;
    transform: translateY(-50%);
  }

  .kanban-list-star .kanban-card-favorite {
    --icon-size: 1.15rem;
    padding: 0.1rem;
  }

  .kanban-list-actions {
    position: absolute;
    right: 0.35rem;
    top: 50%;
    transform: translateY(-50%);
  }

  .kanban-list-id {
    order: 1;
    color: var(--muted-2);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    font-size: 0.75rem;
  }

  .kanban-list-title {
    order: 2;
    flex: 1 1 70%;
    min-width: 0;
    font-size: 1.02rem;
    font-weight: 500;
  }

  /* The toggle's own left padding would nudge the dot off the id's x. */
  .kanban-list-state-cell {
    order: 3;
    margin-left: -0.2rem;
  }

  .kanban-list-assignee { order: 4; }

  .kanban-list-changed {
    order: 5;
    font-size: 0.75rem;
    color: var(--muted-2);
    white-space: nowrap;
  }

  /* Quiet in-cell toggles for status and assignee — the second line
     stays smaller and lighter than the title. */
  .kanban-list-field-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    border: none;
    background: none;
    padding: 0.1rem 0.2rem;
    border-radius: var(--radius-control);
    cursor: pointer;
    color: var(--muted);
    font-size: 0.78rem;
    white-space: nowrap;
  }

  .kanban-list-field-toggle:hover {
    background: var(--rose-tint);
  }

  .kanban-list-field-toggle .icon--caret-down-light {
    --icon-size: 0.9em;
    color: var(--muted);
  }

  /* Star state in the list rows — same attribute, same palette. */
  [data-favorite-favorite="true"] .kanban-card-favorite {
    color: #e0a800;
  }

  [data-favorite-favorite="true"] .kanban-card-favorite:hover {
    color: #f0c65a;
  }

  /* The glyph follows too: one icon in the markup, the filled star is a
     swapped variable — icons.css draws it from --svg. */
  [data-favorite-favorite="true"] .kanban-card-favorite .icon {
    --svg: url("/assets/star-fill-e6434a03.svg");
  }

  /* "+ Aufgabe" below the rows — the board button's look, stretched to
     the list's full width (button_to wraps it in a form). */
  .kanban-list-add {
    margin-top: 0.85rem;
  }

  .kanban-list-add .kanban-add-task {
    width: 100%;
  }

  .kanban-list-state {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
  }

  .kanban-list-assignee {
    color: var(--muted);
    white-space: nowrap;
  }

  /* Tasks toolbar on phones: it carries the sort AND two filter
     dropdowns — the generic .view-and-sort grid has one slot and stacks
     them into extra lines. One flex line fits them all here. */
  @media (max-width: 760px) {
    .task-filters .view-and-sort {
      display: flex;
      flex-wrap: wrap;
      column-gap: 0.15rem;
      row-gap: 0.3rem;
    }

    .task-filters .list-filter {
      flex: 1 1 100%;
    }

    .task-filters .favorites-switch {
      flex: 0 1 auto;
      min-width: 0;
    }

    /* A long state or member name must not push the row apart. */
    .task-filters .sort-toggle-label {
      max-width: 5.5rem;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* Three dropdowns share the line — the sort toggle keeps only its
       icon (the channel-dropdown pattern). */
    .task-filters .task-sort-dropdown .sort-toggle-label {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip: rect(0 0 0 0);
      white-space: nowrap;
      border: 0;
    }
  }

  /* Without the Popover API (older iOS) the open menu stays inside its
     card's stacking context — later sibling cards would paint over it.
     Lift whatever holds an open menu above its siblings. */
  .kanban-card:has(.dropdown-menu.show),
  .kanban-column:has(.dropdown-menu.show),
  .kanban-list tbody tr:has(.dropdown-menu.show) {
    z-index: 30;
  }

  /* ---- Empty state ---- */

  .kanban-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    text-align: center;
    margin: 3rem auto;
    max-width: 34rem;
    padding: 2.5rem 2rem;
    background: var(--cream-soft);
    border: 1px dashed var(--surface-border);
    border-radius: var(--radius-card);
  }

  .kanban-empty-state h2 {
    margin: 0;
    color: var(--ink);
  }

  .kanban-empty-state p {
    margin: 0;
    color: var(--muted);
  }
}

/* ---- Board mode: page height ----
   In board mode the #resource_content frame takes exactly the viewport
   below the fixed header (minus .app-main's 1rem padding at both ends and,
   on phones, the footer bar) and lays out as a column: the resource-header
   keeps its natural height however the toolbar wraps, the surface gets the
   rest. No hard-coded chrome offset — the board always ends at the bottom
   of the window and the page needs no vertical scrollbar. Lives in the
   utilities layer because turbo_frames.css sets the frame's display there. */
@layer utilities {
  /* blitz's reset gives the body min-height: 100dvh, and .app-main's top
     margin (the header offset) collapses through the body — so every page
     is a header's height taller than the viewport and always scrolls a
     little. Harmless under long pages, but it would defeat the fixed
     board: drop the minimum here, the sized frame carries the height. */
  body:has(.kanban-surface--board) {
    min-height: 0;
  }

  turbo-frame#resource_content:has(> .kanban-surface--board) {
    display: flex;
    flex-direction: column;
    height: calc(100dvh - var(--app-header-height, 2.75rem) - var(--blitz-staging-banner-height, 0rem) - 2rem - var(--footer-nav-height, 0rem));
  }

  turbo-frame#resource_content:has(> .kanban-surface--board) > .resource-header {
    flex: 0 0 auto;
  }
}

/* ---- Task dialog ---- */
@layer components {
  .task-dialog {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    min-width: min(44rem, 86vw);
    max-width: 46rem;
  }

  /* Sticky like the footer: stays put while the dialog scrolls, bleeding
     into the dialog's 1rem padding. It carries its own ✕ — blitz's
     absolutely positioned close button would scroll away and is hidden
     for this dialog below. */
  .task-dialog-header {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    position: sticky;
    top: -1rem;
    z-index: 6;
    background: var(--surface, #fff);
    margin: -1rem -1rem 0;
    padding: 1rem 1rem 0.6rem;
    border-bottom: 1px solid var(--hairline);
  }

  dialog:has(.task-dialog) > .dialog-close-button {
    display: none;
  }

  .task-dialog-close {
    display: inline-flex;
    border: none;
    background: none;
    padding: 0.3rem;
    border-radius: var(--radius-control);
    cursor: pointer;
    color: var(--muted-2);
  }

  .task-dialog-close:hover {
    color: var(--wine);
    background: var(--rose-tint);
  }

  .task-title-form {
    flex: 1;
    min-width: 0;
  }

  .task-title-form [data-editable-target="editable"] {
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--ink);
  }

  .task-dialog-fields {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
  }

  .task-field-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    border: 1px solid var(--surface-border);
    background: var(--surface);
    border-radius: var(--radius-control);
    padding: 0.3rem 0.7rem;
    cursor: pointer;
    color: var(--ink);
  }

  .task-field-toggle:hover {
    background: var(--rose-tint);
  }

  /* A link since the dialog row travels in broadcasts (forms would
     carry a dead CSRF token there). */
  .task-important-toggle {
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    border: 1px solid var(--surface-border);
    background: var(--surface);
    border-radius: var(--radius-control);
    padding: 0.3rem 0.7rem;
    cursor: pointer;
    color: var(--muted);
  }

  .task-important-toggle.is-important {
    color: var(--gold-ink);
    background: var(--gold);
    border-color: var(--gold);
  }

  /* Labels in the dialog: the pills (with their ✕) and, on the same
     line, the small add form — name, palette, plus. */
  .task-dialog-labels-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
  }

  .task-dialog-labels {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem;
  }

  .task-dialog-labels:empty {
    display: none;
  }

  .task-dialog-labels .task-label {
    font-size: 0.78rem;
    padding-right: 0.3rem;
  }

  /* The ✕ on a pill — the pill's ink, dimmed until hovered. */
  .task-label-remove {
    display: inline-flex;
    align-items: center;
    color: var(--label-ink);
    opacity: 0.55;
    text-decoration: none;
  }

  .task-label-remove:hover {
    opacity: 1;
  }

  .task-label-form {
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
    flex: 0 0 auto;
    gap: 0.4rem;
    margin: 0;
  }

  .task-label-form input[type="text"] {
    width: 11rem;
    padding: 0.25rem 0.55rem;
    font-size: 0.85rem;
    border: 1px solid var(--surface-border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--ink);
  }

  .task-label-palette {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
  }

  /* A swatch is a plain disc of the color's wash; the chosen one gets an
     ink ring. The radio itself stays hidden but focusable. */
  .task-label-swatch {
    position: relative;
    display: inline-flex;
    width: 1.1rem;
    height: 1.1rem;
    border-radius: 50%;
    background: var(--label-bg);
    cursor: pointer;
  }

  .task-label-swatch input {
    position: absolute;
    inset: 0;
    opacity: 0;
    margin: 0;
    cursor: pointer;
  }

  .task-label-swatch:has(input:checked) {
    box-shadow: 0 0 0 2px var(--surface), 0 0 0 3.5px var(--label-ink);
  }

  .task-label-swatch:has(input:focus-visible) {
    outline: 2px solid var(--wine);
    outline-offset: 2px;
  }

  .task-label-add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border: 1px solid var(--surface-border);
    border-radius: 50%;
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
  }

  .task-label-add:hover {
    background: var(--rose-tint);
    color: var(--wine);
  }

  .task-dialog-plainfields {
    display: flex;
    align-items: end;
    flex-wrap: wrap;
    gap: 0.75rem;
  }

  .task-plainfield {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    flex: 1;
    min-width: 12rem;
  }

  .task-plainfield span {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted-2);
  }

  .task-test-url-link {
    padding-bottom: 0.4rem;
  }

  /* Dispatcher-managed fields: no input, just the value — the label span
     above stays in its uppercase style, the value must not inherit it. */
  .task-custom-field--readonly .task-custom-readonly-value {
    font-size: 0.9rem;
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
    color: var(--ink);
    padding: 0.35rem 0;
    overflow-wrap: anywhere;
  }

  /* "Agents only" checkbox in the fields dialog. */
  .task-custom-readonly-toggle {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.8rem;
    color: var(--muted);
    white-space: nowrap;
    cursor: pointer;
  }

  .task-dialog-description h4,
  .task-dialog-stream h4 {
    margin: 0 0 0.4rem;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
  }

  .task-description-toolbar {
    margin-bottom: 0.4rem;
    min-width: 0; /* let the responsive toolbar measure the dialog width */
  }

  /* Read-only view — same frame as the editor box, click to edit. */
  .task-description-view {
    min-height: 3.5rem;
    border: 1px solid var(--surface-border);
    border-radius: var(--radius-control);
    padding: 0.6rem 0.75rem;
    background: var(--surface);
    cursor: pointer;
  }

  .task-description-view:hover {
    border-color: var(--muted-3);
  }

  /* While this viewer edits, the read-only view steps aside. The state sits
     on the dialog, not on the view itself: the viewer's own autosave
     broadcast replaces that element, and a `hidden` set on it would be gone
     with the replacement — the text would stand twice, above the editor. */
  .task-dialog--editing-description .task-description-view {
    display: none;
  }

  .task-description-placeholder {
    color: var(--muted-2);
  }

  /* Notes pattern: the container is a flex column and the ProseMirror child
     fills it — a click anywhere inside the box focuses the editor. */
  .task-description-content {
    min-height: 7rem;
    border: 1px solid var(--surface-border);
    border-radius: var(--radius-control);
    padding: 0.6rem 0.75rem;
    background: var(--surface);
    display: flex;
    flex-direction: column;
    cursor: text;
  }

  .task-description-content > .tiptap,
  .task-description-content > .ProseMirror {
    flex: 1;
  }

  .task-description-content:focus,
  .task-description-content .ProseMirror:focus {
    outline: none;
  }

  .task-dialog-stream {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    border-top: 1px solid var(--hairline);
    padding-top: 0.75rem;
  }

  /* Attachments: the list (tasks/_attachments, a broadcast target) and
     the upload form beneath it. */
  .task-dialog-attachments {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .task-dialog-attachments .task-attachments:empty {
    display: none;
  }

  /* The ✕ next to a file — muted until hovered, like the card's menu. */
  .task-attachment-remove {
    display: inline-flex;
    align-items: center;
    color: var(--muted-2);
    text-decoration: none;
    border-radius: var(--radius-control);
    padding: 0.1rem;
  }

  .task-attachment-remove:hover {
    color: var(--danger, var(--ink));
    background: var(--rose-tint);
  }

  .task-attachment-upload {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-height: 1.8rem;
  }

  /* A label styled as the button: the file input inside stays hidden,
     the click opens the picker, the change submits the form. blitz's
     form label rule (bold, bottom margin) must not show through. */
  .task-add-attachment {
    cursor: pointer;
    font-weight: 400;
    margin-bottom: 0;
  }

  /* Turbo marks the form aria-busy for the round trip: the button gives
     way to the progress note, so a second click cannot start another
     upload of the same files. */
  .task-attachment-progress {
    display: none;
    font-size: 0.85rem;
    color: var(--muted);
  }

  .task-attachment-upload[aria-busy="true"] .task-add-attachment {
    display: none;
  }

  .task-attachment-upload[aria-busy="true"] .task-attachment-progress {
    display: inline-flex;
  }

  /* No scroll container of its own: the whole dialog scrolls, comments
     included — a nested scroll area would trap the wheel and hide the
     conversation behind a second scrollbar. */
  .task-stream {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .task-event {
    font-size: 0.82rem;
    color: var(--muted-2);
    padding-left: 0.5rem;
  }

  .task-stream-time {
    color: var(--muted-3);
    font-size: 0.78rem;
  }

  .task-comment {
    border: 1px solid var(--surface-border);
    border-radius: var(--radius-control);
    background: var(--cream-soft);
    padding: 0.5rem 0.75rem;
  }

  .task-comment-head {
    display: flex;
    justify-content: space-between;
    gap: 0.6rem;
    margin-bottom: 0.25rem;
  }

  .task-comment-content :first-child { margin-top: 0; }
  .task-comment-content :last-child { margin-bottom: 0; }

  /* Code blocks keep their lines (blitz's tiptap `pre` neither wraps nor
     scrolls): a long line would otherwise widen the whole dialog into a
     horizontal scroller. Scroll inside the block instead, GitHub-style —
     description view, description editor and comments alike. */
  .task-dialog pre {
    max-width: 100%;
    overflow-x: auto;
  }

  .task-comment-composer {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
  }

  .task-add-comment {
    align-self: flex-start;
  }

  /* blitz's .btn sets display: flex, which beats the UA's [hidden] rule —
     without this the button would stay visible next to the open composer. */
  .task-add-comment[hidden] {
    display: none;
  }

  .task-composer-toolbar {
    min-width: 0;
  }

  .task-composer-row {
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
  }

  .task-composer-row .task-composer-form {
    flex: 1;
    min-width: 0;
  }

  .task-composer-form .tiptap-content {
    min-height: 4rem;
    border: 1px solid var(--surface-border);
    border-radius: var(--radius-control);
    padding: 0.5rem 0.75rem;
    background: var(--surface);
    display: flex;
    flex-direction: column;
    cursor: text;
  }

  .task-composer-form .tiptap-content > .tiptap,
  .task-composer-form .tiptap-content > .ProseMirror {
    flex: 1;
  }

  .task-composer-form .tiptap-content .ProseMirror:focus {
    outline: none;
  }

  /* Icon-only send button beside the comment box — neutral, like the
     field toggles above. */
  .task-composer-submit {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border: 1px solid var(--surface-border);
    background: var(--surface);
    border-radius: var(--radius-control);
    cursor: pointer;
    color: var(--muted);
  }

  .task-composer-submit:hover {
    background: var(--rose-tint);
    color: var(--wine);
  }

  /* Layout and look come from the house .dialog-buttons; sticky keeps the
     bar visible while a long description scrolls. The -1rem offset matches
     .dialog-buttons' bleed into the dialog's 1rem padding. */
  .task-dialog-footer {
    position: sticky;
    bottom: -1rem;
    z-index: 5;
    border-top: 1px solid var(--hairline);
  }

  /* Mobile: the ✕ in the sticky header covers cancel; the remaining
     three buttons shrink to fit — and may wrap instead of clipping. */
  @media (max-width: 640px) {
    .task-cancel-button {
      display: none;
    }

    .task-dialog-footer {
      flex-wrap: wrap;
      gap: 0.4rem;
    }

    .task-dialog-footer .btn {
      padding: 0.45rem 0.7rem;
      font-size: 0.85rem;
    }
  }

  .task-stream-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
  }

  .task-events-toggle {
    border: none;
    background: none;
    cursor: pointer;
    font-size: 0.78rem;
    color: var(--muted-2);
    padding: 0.15rem 0.4rem;
    border-radius: var(--radius-control);
  }

  .task-events-toggle:hover {
    color: var(--wine);
    background: var(--rose-tint);
  }

  .task-dialog-stream:not(.task-stream--events-hidden) .when-events-hidden {
    display: none;
  }

  .task-dialog-stream.task-stream--events-hidden .when-events-shown {
    display: none;
  }

  .task-dialog-stream.task-stream--events-hidden .task-event {
    display: none;
  }

  /* ---- "Zusatzfelder" dialog ---- */

  /* One definition per row: name | type | options | agents-only | save | ×.
     The dialog owns the column tracks, every row is a subgrid on them, so
     the label row, the saved definitions and the new-field row line up
     however wide "Speichern" or the type select come out. The per-row form
     is display: contents and hands its controls straight to the grid. */
  .custom-fields-dialog {
    display: grid;
    grid-template-columns: minmax(8rem, 1.2fr) max-content minmax(9rem, 1.6fr) max-content max-content max-content;
    align-items: center;
    column-gap: 0.5rem;
    row-gap: 0.5rem;
    min-width: min(48rem, 88vw);
  }

  .custom-fields-dialog > h3,
  .custom-fields-hint {
    grid-column: 1 / -1;
  }

  .custom-fields-dialog > h3 {
    margin-bottom: 0;
  }

  .custom-fields-row {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    grid-template-areas: "name type options toggle save delete";
    align-items: center;
    row-gap: 0.4rem;
  }

  .custom-fields-form {
    display: contents;
  }

  .custom-fields-cell--name { grid-area: name; }
  .custom-fields-cell--type { grid-area: type; }
  .custom-fields-cell--options { grid-area: options; }
  .custom-fields-cell--toggle { grid-area: toggle; }
  .custom-fields-cell--delete { grid-area: delete; }

  .custom-fields-cell--save {
    grid-area: save;
    gap: 0.3rem;
    white-space: nowrap;
  }

  .custom-fields-row--head {
    margin-bottom: -0.2rem;
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted-2);
  }

  .custom-fields-row--head span {
    padding-left: 0.45rem; /* over the input text, not its border */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .custom-fields-row--new {
    margin-top: 0.25rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--hairline);
  }

  .custom-fields-delete {
    display: inline-flex;
    padding: 0.3rem;
    border-radius: var(--radius-control);
    color: var(--muted-2);
  }

  .custom-fields-delete:hover {
    color: #991b1b;
    background: var(--rose-tint);
  }

  .custom-fields-hint,
  .task-states-hint {
    margin: 0.25rem 0 0;
    font-size: 0.8rem;
    color: var(--muted-2);
  }

  /* Narrow screens: two lines per row — name + type (+ ×) above,
     options + agents-only + save below. */
  @media (max-width: 44rem) {
    .custom-fields-dialog {
      grid-template-columns: minmax(0, 1fr) max-content max-content;
      min-width: 0;
    }

    .custom-fields-row {
      grid-template-areas:
        "name    type   delete"
        "options toggle save";
    }

    .custom-fields-cell--delete {
      justify-self: end;
    }

    .custom-fields-row--head {
      display: none;
    }

    .custom-fields-row:not(.custom-fields-row--head) + .custom-fields-row:not(.custom-fields-row--new) {
      padding-top: 0.5rem;
      border-top: 1px solid var(--hairline);
    }
  }

  /* ---- Members & agents dialog ---- */

  .members-dialog {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: min(32rem, 86vw);
  }

  .members-section h4 {
    margin: 0 0 0.5rem;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
  }

  .members-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .members-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  .members-row-actions {
    margin-left: auto;
    display: flex;
    gap: 0.4rem;
  }

  .members-row--available {
    color: var(--muted);
    margin-top: 0.4rem;
  }

  .members-row--available form {
    margin-left: auto;
  }

  .members-owner-badge {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--gold-ink);
    background: var(--gold);
    border-radius: 5px;
    padding: 0.05rem 0.45rem;
  }

  .agent-new-form {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.6rem;
  }

  .agent-new-form input[type="text"] {
    flex: 1;
    min-width: 10rem;
  }

  .members-invited-badge {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--muted);
    background: var(--cream-soft);
    border: 1px solid var(--surface-border);
    border-radius: 5px;
    padding: 0.05rem 0.45rem;
  }

  .members-error {
    margin: 0 0 0.5rem;
    font-size: 0.85rem;
    color: #991b1b;
  }

  .agent-token-banner {
    background: #faf0d4;
    border: 1px solid var(--gold);
    border-radius: var(--radius-control);
    padding: 0.6rem 0.75rem;
  }

  .agent-token-hint {
    margin: 0 0 0.4rem;
    font-size: 0.85rem;
    color: var(--gold-ink);
  }

  .agent-token-row {
    display: flex;
    gap: 0.5rem;
  }

  .agent-token-row input {
    flex: 1;
    min-width: 0;
    font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
    font-size: 0.82rem;
  }
}

/* ---- The sun (personal day list, pick_controller) ----
   Sits next to the star on the card and in the list row, and in the
   dialog header. Same optimistic palette logic as the star: the element's
   data-pick-picked paints color and swaps the glyph to the filled sun.

   Same system as the star (see .kanban-card-favorite): idle a light
   outline in the muted ramp, hover one step darker, picked the gold fill.

   Drawn larger than the star: the sun's disk fills only half its box
   (the rays take the rest), so at the star's size it reads smaller. The
   padding goes instead, the row keeps its height. */
@layer components {
  .kanban-card-pick {
    --icon-size: 1.2rem;
    display: inline-flex;
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;
    color: var(--muted-3);
    transition: color 0.3s ease;
    position: relative;
    z-index: 2;
  }

  .kanban-card-pick:hover {
    color: var(--muted-2);
  }

  /* Picked: the star's gold, not --gold-ink — that dark gold is nearly
     the same tone as the muted hover gray at icon size. */
  [data-pick-picked="true"] .kanban-card-pick {
    color: #e0a800;
  }

  [data-pick-picked="true"] .kanban-card-pick:hover {
    color: var(--gold-bright);
  }

  [data-pick-picked="true"] .kanban-card-pick .icon {
    --svg: url("/assets/sun-fill-a6dd1fc5.svg");
  }

  /* List row: star and sun side by side in the left gutter, like on the
     card — the gutter grows by one icon. (td.… beats the generic
     display: block of the list's cells.) */
  .kanban-list tbody tr td.kanban-list-star {
    display: flex;
    align-items: center;
    gap: 0.1rem;
  }

  .kanban-list tbody tr {
    padding-left: 3.6rem;
  }

  .kanban-list-star .kanban-card-pick {
    --icon-size: 1.35rem;
    padding: 0;
  }

  /* ⋮ menu entry "Heute": the ✓ of a selected entry (blitz dropdown.css),
     painted from the card's viewer state instead of a class — a class
     would not survive a broadcast render. */
  [data-pick-picked="true"] .dropdown-menu a.kanban-pick-entry {
    position: relative;
    background-color: var(--dropdown-menu-selected-color);
  }

  [data-pick-picked="true"] .dropdown-menu a.kanban-pick-entry::after {
    position: absolute;
    top: 5px;
    right: 5px;
    bottom: 0;
    margin-top: auto;
    margin-bottom: auto;
    content: "\2713";
  }

  /* Dialog: the "Heute" button next to Wichtig, gold when picked — same
     look as .is-important, but read from the article's data attribute. */
  .task-dialog[data-pick-picked="true"] .task-today-toggle {
    color: var(--gold-ink);
    background: var(--gold);
    border-color: var(--gold);
  }

  .task-dialog[data-pick-picked="true"] .task-today-toggle .icon {
    --svg: url("/assets/sun-fill-a6dd1fc5.svg");
  }

  /* ---- Waiting area (doc/wartezustand-spezifikation.html) ----
     Every column has one below its cards: collapsed while empty, shown as
     soon as a card is in it — or while a card is being dragged, as a drop
     zone. Always in the DOM (broadcast target), so a card arriving live
     opens it by itself. :has(), not :empty — ERB leaves whitespace text
     nodes in the list. */
  .kanban-column-waiting {
    display: none;
    flex-direction: column;
    gap: 0.4rem;
    flex-shrink: 0;
    margin-top: 0.35rem;
    padding-top: 0.6rem;
    border-top: 1px dashed color-mix(in srgb, var(--state-accent, var(--muted-3)) 45%, var(--surface-border));
  }

  .kanban-column-waiting:has(.kanban-card),
  .kanban-board.is-dragging .kanban-column-waiting {
    display: flex;
  }

  .kanban-waiting-label {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0 0.25rem;
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted-2);
  }

  /* The area's list is content-sized (the active list takes the column's
     free height for its drop target). Empty while dragging: a dashed box
     with the hint, so there is something to drop onto. */
  .kanban-column-waiting-cards {
    flex: 0 0 auto;
    min-height: 0;
  }

  .kanban-board.is-dragging .kanban-column-waiting-cards:not(:has(.kanban-card)) {
    min-height: 3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px dashed var(--surface-border-strong, var(--muted-3));
    border-radius: var(--radius-control);
    color: var(--muted-2);
    font-size: 0.8rem;
  }

  .kanban-board.is-dragging .kanban-column-waiting-cards:not(:has(.kanban-card))::before {
    content: attr(data-drop-hint);
  }

  .kanban-surface.has-background .kanban-waiting-label {
    color: var(--ink);
  }

  /* Over a photo the dashed box drowns — frost it like the column. */
  .kanban-surface.has-background .kanban-board.is-dragging .kanban-column-waiting-cards:not(:has(.kanban-card)) {
    background: rgba(255, 255, 255, 0.55);
    border-color: var(--muted-2);
    color: var(--ink);
  }

  /* A waiting card: damped, the title muted, the reason line beneath it. */
  .kanban-card.is-waiting {
    opacity: 0.78;
  }

  .kanban-card.is-waiting .kanban-card-title {
    color: var(--muted);
  }

  .kanban-card-waiting {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    min-width: 0;
    font-size: 0.78rem;
    color: var(--muted-2);
  }

  .kanban-card-waiting .icon {
    flex-shrink: 0;
  }

  .kanban-card-waiting span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* The list row and the day-list row: a small pill behind the title. */
  .task-waiting-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    vertical-align: middle;
    margin-left: 0.4rem;
    font-size: 0.68rem;
    font-weight: 600;
    line-height: 1.2;
    padding: 0.14rem 0.55rem;
    border-radius: 999px;
    color: var(--wine-deep);
    background: var(--rose-tint);
  }

  tr.is-waiting .kanban-list-title > a {
    color: var(--muted);
  }

  /* The plan pill: gold, like the sun it leads to. */
  .task-scheduled-badge {
    color: var(--gold-ink);
    background: var(--gold);
  }

  /* Dialog: the waiting chip next to Wichtig (rosé when on) and the
     reason line beneath the control row. */
  .task-waiting-toggle.is-waiting {
    color: var(--wine-deep);
    background: var(--rose);
    border-color: var(--rose);
  }

  .task-dialog-waiting {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 0.88rem;
    color: var(--muted);
  }

  .task-dialog-waiting > span,
  .task-schedule-label {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    white-space: nowrap;
  }

  .task-dialog-waiting input {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 28rem;
    border: 1px solid var(--surface-border);
    border-radius: var(--radius-control);
    padding: 0.3rem 0.6rem;
    background: var(--surface);
    color: var(--ink);
    font: inherit;
  }

  /* The plan line: label + date field as one flex row (the label wraps
     the field, so it inherits the reason line's look), the ✕ beside it. */
  .task-dialog-scheduled > label {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
  }

  /* The plan field is blitz' date picker: it hides the real input and shows
     a field of its own, handing it the original's class — so one rule
     dresses whichever of the two is visible. Sizes and colours of the
     calendar itself come from the --date-picker-* tokens in _tokens.css. */
  .task-dialog-scheduled .task-schedule-date {
    flex: 0 1 auto;
    width: 10.5rem;
    max-width: 100%;
    cursor: pointer;
  }

  .task-schedule-clear {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem;
    border-radius: var(--radius-control);
    color: var(--muted);
  }

  .task-schedule-clear:hover {
    color: var(--wine-deep);
    background: var(--rose-tint);
  }
}
