@layer components {
  /* ——— The dock ———
     A project's tools as a grid of cards that show what is inside them, not an
     icon (docs/planning/basecamp-pm-study.md). BC's grammar for the card
     itself (user, 2026-08-09): the title stands above the frame and is the
     rename affordance, the framed preview is the door, and every other verb —
     the ⋯, an empty tool's invitation — waits for the pointer. Where nothing
     hovers, nothing waits (HANSHI Deviations, 2026-10-07). */

  /* The activity digest follows the tools; this is the room between them. */
  .dock {
    margin-block-end: var(--block-space-double);
  }

  .dock__tools {
    /* One head height both sides agree on — the tallest thing in a title row
       is the compact quick button, and the add square's top margin has to
       equal the row the real tiles carry or their frames misalign. */
    --dock-head-size: 1.75rem;

    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
    gap: var(--block-space-double) var(--block-space);
    align-items: start;
  }

  .dock-tool {
    display: flex;
    flex-direction: column;
    gap: var(--block-space-half);
  }

  .dock-tool__head {
    display: flex;
    align-items: center;
    gap: var(--inline-space);
    block-size: var(--dock-head-size);
  }

  /* An inline-rename (inline-rename.css) in the dock's voice: title metrics
     rather than an h1's, and shu, not ink — BC paints its dock titles in the
     accent, and the user ruled the same here (2026-08-09; recorded in HANSHI's
     Deviations). One line always: a long name ellipsizes rather than wrapping
     under its neighbour. */
  .dock-tool__title {
    --inline-rename-size: var(--text-medium);

    flex: 1;
    min-inline-size: 0;
    color: var(--color-accent);
  }

  /* The framed part is the door: a .bucket-card retuned, resting on paper
     and square, so the grid reads as one set of windows. `position:
     relative` keeps the door's overlay on this box. */
  .dock-tool__card {
    --bucket-card-background: var(--color-canvas);
    --card-min-height: 0;

    position: relative;
    aspect-ratio: 1;
  }

  /* The pointer brings the wash, with no transition: only a colour changes.
     The chat's bubbles wear that wash at rest, so under it they turn to
     paper rather than vanish. */
  @media (any-hover: hover) {
    .dock-tool__card:hover {
      --bucket-card-background: var(--color-surface);
      --bubble-background: var(--color-canvas);
    }
  }

  /* The tile in flight shows as the empty slot it lands in. */
  .dock-tool.is-dragging {
    opacity: 1;

    .dock-tool__head,
    .dock-tool__card > * {
      visibility: hidden;
    }

    .dock-tool__card {
      --bucket-card-background: var(--color-surface);
      border-style: dashed;
    }
  }

  /* The door has no text of its own, so the anchor can *be* the overlay
     rather than stretching a ::after over one — which also gives it a real
     box for the focus ring, and for anything that asks whether it is
     visible. */
  .dock-tool__open {
    position: absolute;
    inset: 0;
    border-radius: var(--border-radius);
  }

  /* The ⋯ (tools/_menu) waits for the pointer like every hover-reveal;
     hover-reveals.css shows it where nothing hovers. */
  .dock-tool__head .btn--circle {
    opacity: 0;
    transition: opacity var(--speed-quick) var(--ease-out);
  }

  .dock-tool:is(:hover, :focus-within) .dock-tool__head .btn--circle,
  .dock-tool__head:has(:popover-open) .btn--circle {
    opacity: 1;
  }

  /* Where nothing hovers, the title joins the door — the overlay moves from
     the card to the whole tile, so a thumb aimed at a tool never lands in
     its rename form. The ⋯, and the rename form it can open, stand above
     the overlay. */
  @media (any-hover: none) {
    .dock-tool {
      position: relative;
    }

    .dock-tool__card {
      position: static;
    }

    .dock-tool__head .btn--circle,
    .dock-tool__title:has(form) {
      position: relative;
      z-index: var(--z-raised);
    }

    .dock-tool:active .dock-tool__card {
      --bucket-card-background: var(--color-surface);
      --bubble-background: var(--color-canvas);
    }
  }

  /* ——— The preview ———
     The tool's own page, small. It is a picture with the door under it, so
     it takes no clicks: its fade, or an empty tool's invitation, would
     otherwise paint over the door and swallow every one. */
  .dock-tool__preview {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--block-space-half);
    min-block-size: 0;
    overflow: hidden;
    font-size: var(--text-small);
    pointer-events: none;
  }

  /* What runs past the square fades at its foot. Not an empty tile: the
     mask would cut its invitation at the preview's edge. */
  .dock-tool__preview:not(:has(.dock-tool__blank)) {
    mask-image: linear-gradient(black calc(100% - var(--block-space)), transparent);
  }

  /* Faces at a row's height, not a byline's. */
  .dock-tool__lines {
    --avatar-size: 1.25rem;
  }

  /* A list (to-dos) or a question (check-ins) is one group; the space falls
     between groups. The to-dos' pie and circles share one size, so a list's
     name and its rows share a text edge. */
  :is(.dock-tool--todoset, .dock-tool--questionnaire) :is(.dock-tool__preview, .ghost) {
    gap: var(--block-space);
  }

  .dock-tool--todoset {
    --check-size: 1em;
  }

  /* The room fits at the top, like every tile; a longer one keeps its newest
     line at the foot. The auto margin takes free space while there is some;
     with none left, `flex-end` sends the overflow out at the top, where the
     fade sits in the card's top padding (the preview is pulled up into it
     and the lines put it back), so a room that fits is never faded. Bubbles
     are messages.css's, at the tile's padding. */
  .dock-tool--chat .dock-tool__preview {
    justify-content: flex-end;
    margin-block-start: calc(var(--block-space) * -1);

    &:not(:has(.dock-tool__blank)) {
      mask-image: linear-gradient(transparent, black var(--block-space));
    }
  }

  .dock-tool--chat .dock-tool__lines {
    --bubble-padding-inline: var(--inline-space);

    margin-block-end: auto;
    padding-block-start: var(--block-space);
  }

  .dock-tool__bubble--mine {
    align-self: end;
  }

  /* A run's next line sits under the first one's bubble. */
  .dock-tool__bubble--threaded {
    margin-inline-start: calc(var(--avatar-size) + var(--inline-space-half));
  }

  /* The board small: its columns' wash and its cards' face are the board's
     own rules (card-columns.css, cards.css); here only the miniature's size. */
  .dock-tool__columns > * {
    flex: 1;
    min-inline-size: 0;
    padding: var(--block-space-half) var(--inline-space-half);
  }

  /* A face carries its card's title on one line, at the board's own label
     size — a name you can almost read, not a blank to count. The ghost's
     faces are empty (card_tables/_ghost), so the height is the line's, not
     the words'. */
  .dock-tool__face {
    flex-shrink: 0;
    min-block-size: calc(1lh + 0.5em);
    padding: 0.25em var(--inline-space-half);
    font-size: var(--text-x-small);
  }

  /* A picture in a tile — a chat line's, a post's first — small and cropped
     to one shape, so a tall screenshot is not a sliver and a wide one does
     not take the square. The size it is drawn from was made at write time. */
  .dock-tool__thumb {
    align-self: start;
    block-size: 4.5rem;
    aspect-ratio: 4 / 3;
    object-fit: cover;
  }

  /* Docs & Files full of pictures is a mosaic of them, three rows of four
     (mosaics.css), whole to the card's foot: it does not run long, so it
     does not fade. */
  .dock-tool--vault .mosaic {
    --mosaic-columns: 4;
    --mosaic-rows: 3;
  }

  .dock-tool--vault .dock-tool__preview:has(.mosaic) {
    mask-image: none;
  }

  /* A link has one thing to say — where it goes — so it says it centred. */
  .dock-tool--external-link .dock-tool__preview {
    --icon-size: 1.5rem;

    align-items: center;
    justify-content: center;
    text-align: center;

    > * {
      max-inline-size: 100%;
    }
  }

  /* ——— An empty tool ———
     The ghost fills the card, and the invitation — the purpose and the first
     verb — stands over it. */
  .dock-tool__blank {
    flex: 1;
    min-block-size: 0;

    > .ghost {
      flex: 1;
      min-block-size: 0;
      transition: opacity var(--speed-quick) var(--ease-out);
    }
  }

  /* The invitation waits for the pointer — by opacity, so focus can still
     reveal it — and rises a little as it arrives. It covers the card but
     inherits the preview's `pointer-events: none`; only its button joins in,
     once it shows. */
  .dock-tool__invitation {
    position: absolute;
    inset: 0;
    text-align: center;
    color: var(--color-ink-subtle);
    opacity: 0;
    translate: 0 2px;
    transition:
      opacity var(--speed-quick) var(--ease-out),
      translate var(--speed-quick) var(--ease-out);

    p {
      max-inline-size: 24ch;
      text-wrap: balance;
    }

    &:focus-within {
      opacity: 1;
      translate: 0 0;
    }

    .btn:focus-visible {
      pointer-events: auto;
    }
  }

  @media (any-hover: hover) {
    .dock-tool:hover {
      .dock-tool__invitation {
        opacity: 1;
        translate: 0 0;
      }

      .dock-tool__invitation .btn {
        pointer-events: auto;
      }

      /* The ghost steps back so the words in front of it read. */
      .dock-tool__blank > .ghost {
        opacity: 0.25;
      }
    }
  }

  /* On touch the purpose stands at the foot for good, without its button —
     the card is the way in — and the ghost fades into it. */
  @media (any-hover: none) {
    .dock-tool__invitation {
      inset-block-start: auto;
      opacity: 1;
      translate: none;
    }

    .dock-tool__invitation .btn {
      display: none;
    }

    .dock-tool__blank > .ghost {
      mask-image: linear-gradient(black 30%, transparent 75%);
    }
  }

  /* Adding a tool is a small dashed square where the next tile would start —
     BC's own control (user, 2026-08-09; the full-size flipping tile it
     replaces read as a broken card once the real tiles grew title rows). The
     top margin is the head row the real tiles carry — --dock-head-size plus
     the tile's own gap — so the button top-aligns with the cards. */
  .dock-add {
    margin-block-start: calc(var(--dock-head-size) + var(--block-space-half));
  }

  .dock-add__button {
    display: grid;
    place-items: center;
    inline-size: 3rem;
    block-size: 3rem;
    font-size: var(--text-large);
    color: var(--color-ink-faint);
    background-color: transparent;
    border: var(--border-size) dashed var(--color-line);
    border-radius: var(--border-radius);
    cursor: pointer;

    &:hover {
      color: var(--color-ink-subtle);
      background-color: var(--color-surface);
    }
  }

  /* The catalog floats where the tile it would add will land — over the tile's
     own corner rather than under the button, so it covers the space it is
     about to fill. Everything else a float wears is .popup's; this is the
     card-sized box and the corner, said as the two properties popups.css
     takes. Scoped to .dock-add because dock.css sorts before popups.css, so
     an unscoped rule would lose max-block-size to .popup's 80dvh. */
  .dock-add .dock-add__panel {
    --popup-area: span-block-end span-inline-end;

    margin-block-start: 0;
    inline-size: min(18rem, 80vw);
    max-block-size: 22rem;
  }

  /* On a phone the add square stands mid-screen and no side of it fits the
     card-sized panel, so the panel takes the full row instead — spanned
     across its anchor, centred. Same clamp, same reason as the boost picker
     (boosts.css, user 2026-08-24): a top-layer float hangs over the
     viewport, not the sheet, so the width query is the right question. */
  @media (width < 35rem) {
    .dock-add .dock-add__panel {
      --popup-area: span-block-end span-all;

      justify-self: center;
    }
  }

  /* A phone-wide sheet: one tool per row, at a fixed height rather than a
     square, which would be taller than a phone is useful. */
  @container main (max-width: 70ch) {
    .dock__tools {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--block-space-double);
    }

    .dock-tool__card {
      aspect-ratio: auto;
      block-size: 12rem;
    }

    .dock-add {
      margin-block-start: 0;
    }
  }
}
