/* The board's columns. Ours — it began as a copy of fizzy's board and stopped
   being one on 2026-08-14, when the geometry became Basecamp's (user): one
   flex row the full width of the page, every OPEN column sharing that width
   equally over a quiet wash of its own hue, every folded one a 40px ink rail
   (count on top, the name down the spine, a hairline finishing the height),
   any number open at once, and the two ends frozen to the board's edges while
   the flow scrolls between them. Fizzy's `1fr auto 1fr` grid, pinned 450px
   Maybe?, one-open-at-a-time rule and thermometer rails are gone; what
   remains fizzy-shaped is the card retunes, the maybe-column voice and the
   board tools, kept because they work, not because they must match. */
@layer components {
  /* Column container
  /* ------------------------------------------------------------------------ */

  .card-columns {
    --cards-gap: min(1.2cqi, 1.7rem);
    --column-gap: 8px;
    --column-padding: calc(var(--column-gap) * 2);
    --column-transition-duration: var(--speed);
    --column-width-collapsed: 40px;
    --column-width-min: 19rem;      /* an open column shrinks to this, then the board scrolls */

    /* Its own container, and UNNAMED — so every query below must name `main`
       or it resolves against this box instead of the sheet's. The two read
       differently: a rule ON .card-columns asks #main, a rule on anything
       INSIDE it asks .card-columns, and the same query text would then mean
       two things in one file (2026-08-29). */
    container-type: inline-size;
    display: flex;
    gap: var(--column-gap);
    inline-size: 100%;
    outline: none;
    overflow-x: auto;
    overflow-y: hidden;
    position: relative;

    &:has(.cards) {
      block-size: 100%;
      min-block-size: 20lh;
    }

    /* The reel. 40rem is the board's OWN number, not the shell's: a column
       shrinks to --column-width-min (19rem) and two of them plus the gap need
       39.5rem, so below this the flow cannot show a second column and the
       snap earns its place. Asked of #main and not the viewport, because the
       notification panel takes ~450px off #main without touching the window —
       and this board kept its frozen rails and its desktop padding inside a
       568px box until it was asked properly (AGENTS.md; 2026-08-29). */
    @container main (width < 40rem) {
      padding-inline: var(--column-gap);
      scroll-snap-type: inline mandatory;
    }

    /* Which geometry the board is in, published for collapsible-columns to
       read. The controller has to know — one column at a time on the reel, any
       number open on the spread — and it used to answer the question a second
       time with matchMedia("(min-width: 640px)"). That asked the WINDOW while
       every rule above asks #main, so with the notification panel open the
       stylesheet drew a reel and the controller still ran spread behaviour: no
       mass fold, and a tap opening a second column on a board with room for
       one. Declared here so the breakpoint is written once, in the file that
       owns it. */
    --board-geometry: reel;

    @container main (width >= 40rem) {
      --board-geometry: spread;
    }
  }

  /* The board's frozen edges (Basecamp): while the flow scrolls, a railed end
     stays stuck to its side of the container, and the middle columns pass
     beneath it — the rail's own canvas is what hides them. The + rides the
     same seam, one rail-width in from Done, so the next column's way in never
     scrolls away. An OPEN end leaves the flow to itself: it is at its edge
     anyway, and a stuck-open column would need opaque paper of its own.
     No sticking below 640px, where the board is a scroll-snapped reel. */
  @container main (width >= 40rem) {
    .cards--on-deck.is-collapsed,
    .cards--closed.is-collapsed {
      background-color: var(--color-canvas);
      position: sticky;
      z-index: 2;
    }

    .cards--on-deck.is-collapsed {
      inset-inline-start: 0;
    }

    .cards--closed.is-collapsed {
      inset-inline-end: 0;
    }
  }

  /* Column
  /* ------------------------------------------------------------------------ */

  .cards {
    --column-color: color-mix(in srgb, var(--card-color) 15%, var(--color-canvas));

    flex: 1 1 0;
    min-inline-size: var(--column-width-min);
    outline: none;
    position: relative;
    scroll-snap-align: center;
    transition:
      flex-grow var(--column-transition-duration) var(--ease-out),
      flex-basis var(--column-transition-duration) var(--ease-out),
      min-inline-size var(--column-transition-duration) var(--ease-out);

    .no-transitions & {
      transition: none;
    }

    &.is-expanded {
      @container main (width < 40rem) {
        overflow: hidden;
      }
    }

    &.is-collapsed {
      flex: 0 0 var(--column-width-collapsed);
      min-inline-size: var(--column-width-collapsed);

      .card,
      .blank-slate {
        display: none;
      }
    }

    @container main (width < 40rem) {
      min-inline-size: 0;

      &.is-expanded {
        flex: 0 0 100%;
      }

      &.is-collapsed {
        min-inline-size: 0;
      }
    }

    &.drag-and-drop__hover-container {
      --dnd-bg-color: transparent;
      --dnd-border-color: transparent;

      &.is-off-screen {
        &:after {
          content: attr(data-column-name);
          font-size: var(--text-x-small);
          font-weight: 500;
          line-height: var(--column-width-collapsed);
          padding-inline: 1ch;
          position: fixed;
          text-transform: uppercase;
          top: 0;
          translate: -50%;
        }

        &.is-collapsed {
          &:after {
            writing-mode: vertical-rl;
          }
        }

        &:not(.is-collapsed) {
          &:after {
            background-color: var(--column-color);
            border-radius: var(--border-radius);
          }
        }
      }
    }

  }

  .cards__transition-container {
    block-size: 100%;
    border-radius: var(--border-radius); /* one radius, open or railed — the board speaks one shape */

    /* An open column's one piece of chrome: a wash of its own hue, quiet
       enough to group the cards without shouting — the colour is data
       (CardColumn#color), and this is the one place it reads (user,
       2026-08-14, over a colored top tick). */
    .is-expanded & {
      background-color: color-mix(in srgb, var(--card-color) 5%, var(--color-canvas));
    }

    /* The rail: canvas and a hairline border, the count and the spine inside
       it drawn by the expander — and fizzy's load gauge translated into the
       column's own voice: the rail fills with the 15% wash from the top, one
       fifteenth per card (fizzy's cap), so a heavy column reads at a glance
       exactly as it would open. `--card-count` is server-rendered on the
       column and morphs with every drop. The hover wash is ink darkening
       with no transition — nothing appears or disappears. */
    .is-collapsed & {
      background-color: var(--color-canvas);
      background-image: linear-gradient(to bottom,
        var(--column-color) calc(min(var(--card-count, 0), 15) / 15 * 100%),
        transparent 0);
      border: 1px solid var(--color-ink-lighter);
    }

    .is-collapsed:hover & {
      background-color: var(--color-ink-lightest);
    }

    .drag-and-drop__hover-container & {
      --dnd-bg-color: var(--column-color);
      --dnd-border-color: var(--card-color);

      background-color: var(--dnd-bg-color);
      outline: 2px dashed var(--dnd-border-color);
      outline-offset: -2px;
      transition: background-color 200ms;
      z-index: 1;
    }

    /* Use flex so the __list container can take up the remaining space for scrolling */
    @container main (width < 40rem) {
      .is-expanded & {
        display: flex;
        flex-direction: column;
      }
    }
  }

  /* The card in flight is its own placeholder — it relocates live under the
     pointer — so it wears the dashed edge that already means "droppable"
     here, dimmed enough to read as where the card WOULD land rather than
     where it is. */
  .drag-and-drop__dragged-item {
    opacity: 0.6;
    outline: 2px dashed var(--color-ink-light);
    outline-offset: -2px;
  }

  /* While the filter box holds a query, a column with nothing left to show
     leaves the board, rails and ends included — BC's behaviour: the survivors
     spread out and the filter reads as an answer rather than as gaps. Only
     cards in the LIST count; the intake's add-card tool card is a `.card`
     too and must not hold its column open. The + goes with them — there is
     nothing to add a column after while the board is an answer. */
  [data-controller~="filter"]:has(.filter-box:not(:placeholder-shown)) {
    .cards:not(:has(.cards__list .card:not([hidden]))) {
      display: none;
    }

    .cards__new-column {
      display: none;
    }
  }

  /* The wrapper around the cards used to clip overflow while transitioning.
   * Also, don't resize cards while transitioning to avoid reflow. */
  .cards__list {
    display: flex;
    flex-direction: column;
    gap: var(--cards-gap);
    overflow-x: hidden;
    overflow-y: auto;

    .is-expanded & {
      padding: var(--column-padding) var(--column-padding) calc(var(--column-padding) + var(--custom-safe-inset-bottom));

      /* Use the rest of the column height for scrolling */
      @container main (width < 40rem) {
        flex: 1;
        padding-inline: calc(var(--column-padding) / 4);
      }
    }

    /* No outline for the hotkey cursor (fizzy drew one): mouse hover also
       selects a card for the hotkeys, so the ring appeared under the pointer
       "at random" and fought the dashed drag edge (user, 2026-08-14). A card
       actually focused by keyboard still wears the app's focus ring. */

    &:has(.card) {
      .blank-slate {
        display: none;
      }
    }

    /* Use the default blank-slate on small viewports since drag-and-drop isn't
       available. The board mounts chalet's own `drag-and-drop` — with fizzy's
       token this never matched, so every empty open column showed BOTH blank
       slates.

       These two stay VIEWPORT queries while the rest of the file moved to
       `@container main`: the question is whether a pointer can drag, not how
       much room the board has. Asked of the container, a mouse user with the
       notification panel open would be told to use the touch slate. */
    [data-controller~="drag-and-drop"] & {
      @media (max-width: 639px) {
        .blank-slate--drag {
          display: none;
        }
      }

      @media (min-width: 640px) {
        .blank-slate--default {
          display: none;
        }
      }
    }
  }

  .cards__new-column {
    align-self: start; /* level with the rails' heads */
    position: relative;

    /* On the reel the + is a RAIL, standing in the flow where the next column
       will go — the same rule the desktop + follows, translated to the
       geometry. Fizzy floated it out past the board's edge here
       (`translate: 100%`), which on our reel left a half-clipped 28px glyph
       nobody could see or reach (user, 2026-08-30). The dashed edge is the
       app's invitation mark (cards.css's claim circle says why), and rail
       width by the board's own token means it reads as one of the rails
       beside it. The circle's fixed 28px box gives way to the rail's. */
    @container main (width < 40rem) {
      align-self: stretch;
      flex: 0 0 var(--column-width-collapsed);

      .btn {
        inline-size: 100%;
        block-size: 100%;
        border: 1px dashed var(--color-ink-lighter);
        border-radius: var(--border-radius);
      }
    }

    /* Stuck to the frozen right edge, one rail in from Done. Floating over
       whatever scrolls past, the circle wears full ink — a translucent glyph
       here was invisible (user, 2026-08-14) — and sits 1px down so its top
       lines up with the rails' bordered heads. */
    @container main (width >= 40rem) {
      inset-inline-end: calc(var(--column-width-collapsed) + var(--column-gap));
      margin-block-start: 1px;
      position: sticky;
      z-index: 2;

      .btn {
        --btn-background: var(--color-ink);
        --btn-color: var(--color-canvas);
        --btn-background-hover: var(--color-ink-subtle);
      }
    }
  }

  /* Column Elements
  /* ------------------------------------------------------------------------ */

  .cards__header {
    .cards.is-collapsed & {
      block-size: 100%;
    }

    .cards.is-expanded & {
      display: grid;
      grid-template-areas: "menu expander maximize";
      grid-template-columns: var(--column-width-collapsed) 1fr var(--column-width-collapsed);
      padding-inline: var(--column-padding);
    }
  }

  .cards__menu .btn--circle,
  .cards__maximize-button {
    --btn-background: transparent;

    block-size: var(--column-width-collapsed);
    inline-size: var(--column-width-collapsed);
    opacity: 0;
    outline-offset: -2px;

    .cards:hover &,
    &:focus-visible {
      opacity: 1;
    }

    .cards.is-collapsed & {
      display: none;
    }
  }

  .cards__menu {
    position: relative;
    z-index: var(--z-popup);
  }

  .cards__maximize-button {
    grid-area: maximize;
  }

  .cards__expander {
    /* A `.btn--plain` that must also give up its ink: the base class is shared
       with a prose verb in the settings pane (buttons.css), whose ink-subtle
       and hover-to-ink would grey the column's name out on the way past. */
    --btn-color: inherit;
    --btn-color-hover: inherit;

    align-items: center;
    border-radius: 99rem;
    cursor: pointer;
    display: flex;
    flex-direction: row-reverse;
    font-size: var(--text-x-small);
    font-weight: 600;
    gap: 0.5ch;
    grid-area: expander;
    justify-content: center;
    outline: none;
    outline-offset: -2px;
    position: relative;
    text-transform: uppercase;

    /* The whole rail is the way back in: count on top, the name down the
       spine, the remaining height finished by a hairline (HANSHI's rail).
       Fizzy filled this with a gradient sized by the card count; the count
       already says the number, so the number said it. */
    .cards.is-collapsed & {
      block-size: 100%;
      flex-direction: column;
      inline-size: var(--column-width-collapsed);
      justify-content: start;
      letter-spacing: 0.05em;

      &:after {
        align-self: center;
        background-color: var(--color-ink-lighter);
        content: "";
        flex: 1;
        inline-size: 1px;
        margin-block: 1ch calc(var(--column-width-collapsed) / 2);
      }
    }

    .cards.is-expanded & {
      inline-size: 100%;
      justify-content: center;
    }
  }

  /* A column's description: what the column is FOR, under its name.
     Basecamp's second field on the add-column form, which fizzy had no
     equivalent of.

     Hidden while the column is a rail rather than removed from the markup, so
     it stays in the accessibility tree — the name and the purpose are read
     together whether or not the column happens to be open. `display: none`
     and not `visibility` or a clip: a rail is 40px wide, and a paragraph laid
     out invisibly inside it would still push the column's height around.

     One measure, one ink, no box. The description is context, not content —
     it sits at the small size the rest of the app's secondary text uses and
     is never allowed to out-shout the cards under it. */
  .cards__description {
    color: var(--color-ink-dark);
    font-size: var(--text-x-small);
    line-height: 1.4;
    margin-block: 0 var(--column-padding);
    padding-inline: var(--column-padding);
    text-wrap: pretty;

    .cards.is-collapsed & {
      display: none;
    }

    /* The card list already pays the column's bottom padding, so the
       description only owes the gap between itself and the first card. */
    @container main (width < 40rem) {
      padding-inline: calc(var(--column-padding) / 4);
    }
  }

  .cards__expander-count {
    color: var(--color-ink-dark); /* the faint figure voice — the name outranks the number */
    line-height: var(--column-width-collapsed);
    inline-size: var(--column-width-collapsed);

    /* Beside the title when the column is open — Basecamp's "To do (4)".
       Fizzy hid it here, but fizzy's columns were rails by default and ours
       are open; the number is the column's load and stays readable either
       way. Same element in both states, so the drag controller's optimistic
       bump lands wherever the count is showing. */
    .cards.is-expanded & {
      inline-size: auto;

      &:before {
        content: "(";
      }

      &:after {
        content: ")";
      }
    }
  }

  .cards__expander-title {
    font-weight: inherit;
    font-size: inherit;
    line-height: var(--column-width-collapsed);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;

    .cards.is-collapsed & {
      max-inline-size: 50vh;
      writing-mode: vertical-rl;
    }

    .cards.is-expanded & {
      align-items: center;
      display: flex;
      gap: 0.25ch;
      max-inline-size: calc(100% - var(--column-width-collapsed) * 2);
    }

    .icon--collapse {
    --icon-size: 1.15em;

      opacity: 0.66;
      transition: 150ms ease-out;
      transition-property: opacity, scale;

      @media (hover: hover) {
        opacity: 0;
        scale: 1.5;
      }

      .cards.is-collapsed & {
        display: none;
      }

      .cards.is-expanded .cards__expander:hover & {
        opacity: 0.66;
        scale: 1;
      }
    }
  }

  /* Override card styles within columns
  /* Adding .board-tools here since it sits outside the cards container on mobile */
  /* ------------------------------------------------------------------------ */

  /* What a column still has to say about a card.
  /* ------------------------------------------------------------------------ */

  /* Considering
  /* ------------------------------------------------------------------------ */

  /* The intake's own hue and nothing else. Everything this block used to say
     about its cards — a bigger avatar, a bigger title, a paper fill, a tighter
     line — the card now says once for every column (cards.css), which is what
     it means for a variant to retune properties rather than restate rules. */
  .cards--maybe {
    --card-color: oklch(var(--lch-blue-medium));

    position: relative;

    .card {
      z-index: 2;
    }
  }

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

  .board-tools.card {
    /* The em scaling that used to be shared with `.cards .card` and is now the
       tool card's alone: the cards stopped shrinking with their column when
       they got their own face, but this one still wants to, because it sits
       inside a column and has to look like it belongs there. */
    --block-space: 1em;
    --block-space-half: 0.5em;

    --border-color: var(--color-line); /* a hairline, not fizzy's blue — the way in should not out-shout the cards */
    --border-size: 1px;
    --card-padding-block: var(--block-space);

    font-size: clamp(0.6rem, 0.85cqi, 100px);
    border: 1px solid var(--border-color);
    inline-size: auto;
    text-align: center;

    /* Also still a viewport query, and for a worse reason: this is the board's
       ONLY way in — card_tables/_intake carries the sole add-card button — and
       hiding it leaves a phone with no way to add a card at all. The desktop
       replacement it was written against (`#cards_container`) has never existed
       in any view. Asked of the container it would take the button away every
       time the notification panel opened too, so it stays keyed to the phone
       until the missing half is built (2026-08-29). */
    @media (max-width: 639px) {
      .cards & {
        display: none;
      }
    }

    @container main (width >= 50rem) {
      margin: var(--column-padding) var(--column-padding) 0;
    }

    &:has(dialog[open]) {
      z-index: 5;
    }

    /* Never the buttons of a dialog that happens to NEST here — the add-card
       modal lives inside this tool card so its trigger can reach it, and this
       rule once repainted its submit ink-on-ink. */
    .btn:not(.btn--link, .btn--circle, .modal *) {
      border: 0;
      color: var(--color-link);
    }

    footer {
      font-size: var(--text-x-small);
      margin-block: 1ch calc(var(--block-space-half) * -1);
    }
  }

  /* On Deck (Not Now)
  /* ------------------------------------------------------------------------ */

  .cards--closed,
  .cards--on-deck {
    --card-color: var(--color-ink-light) !important;

    .card,
    .blank-slate {
      --card-color: var(--color-card-complete) !important;
    }
  }
}
