@property --_card-loading-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@scope (card-ui) {
  :where(:scope) {
    /* ── Spacing ── */
    --card-inset:             var(--a-inset);
    --card-gap:               var(--a-gap-self, var(--a-gap));
    --card-header-gap:        var(--a-space-2);
    --card-footer-gap:        var(--a-space-2);

    /* ── Radius ── */
    --card-radius:            var(--a-radius-lg);

    /* ── Typography ── */
    --card-font-size:         var(--a-body-size);
    --card-description-size:  var(--a-caption-size);

    /* ── Colors ── */
    --card-bg:                var(--a-canvas-1);
    --card-bg-padded:         var(--a-canvas-0-scrim);
    --card-fg:                var(--a-fg);
    --card-heading-fg:        var(--a-fg-strong);
    --card-description-fg:    var(--a-fg-subtle);
    --card-border:            1px solid var(--a-border-subtle);
    --card-divider:           var(--a-border-subtle);
    --card-shadow:            var(--a-shadow-sm);

    /* ── Loading ring ──
       The mask composition below uses an opaque source (masks are intensity-only,
       not theme-colored). `--a-chrome-light` is our theme-stable opaque white token.
       The visible ring color comes from the accent token. */
    --card-loading-ring:      var(--a-accent-bg);
    --card-loading-mask-color: var(--a-chrome-light);

    /* ── Elevation ── */
    --card-shadow-0:          none;
    --card-shadow-1:          var(--a-shadow-sm);
    --card-shadow-2:          var(--a-shadow-md);
    --card-shadow-3:          var(--a-shadow-lg);

    /* ── Loading motion ── */
    --card-loading-duration:  1.5s;
    --card-loading-easing:    linear;

    /* ── Draggable ── */
    --card-drag-bg-active:    var(--a-accent-muted);
  }

  :scope {
    box-sizing: border-box;
    display: block;
    border-radius: var(--card-radius);
    background: var(--card-bg);
    color: var(--card-fg);
    font-size: var(--card-font-size);
    border: var(--card-border);
    box-shadow: var(--card-shadow);
    overflow: hidden;
    corner-shape: superellipse(1.1);
    width: stretch;
  }

  /* ═══════ Variants — token-only overrides ═══════ */

  /* `outline` is a documented alias for `outlined` (yaml). */
  :scope[variant="outlined"],
  :scope[variant="outline"] {
    --card-bg: transparent;
    --card-shadow: none;
    --card-border: 1px solid var(--a-border);
  }

  :scope[variant="filled"] {
    --card-shadow: none;
    --card-border: none;
  }

  :scope[variant="ghost"] {
    --card-bg: transparent;
    --card-shadow: none;
    --card-border: none;
  }

  /* ═══════ Elevation — token override ═══════ */

  :scope[elevation="0"] { --card-shadow: var(--card-shadow-0); }
  :scope[elevation="1"] { --card-shadow: var(--card-shadow-1); }
  :scope[elevation="2"] { --card-shadow: var(--card-shadow-2); }
  :scope[elevation="3"] { --card-shadow: var(--card-shadow-3); }

  /* ═══════ Loading ═══════ */

  :scope[loading] {
    --card-border: 1px solid transparent;
    position: relative;
  }

  :scope[loading]::before {
    content: '';
    position: absolute;
    inset: -1px;
    border-radius: inherit;
    padding: 1px;
    background: conic-gradient(
      from var(--_card-loading-angle, 0deg),
      transparent 0deg,
      var(--card-loading-ring) 90deg,
      transparent 180deg
    );
    -webkit-mask: linear-gradient(var(--card-loading-mask-color) 0 0) content-box, linear-gradient(var(--card-loading-mask-color) 0 0);
            mask: linear-gradient(var(--card-loading-mask-color) 0 0) content-box, linear-gradient(var(--card-loading-mask-color) 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    animation: card-loading-spin var(--card-loading-duration) var(--card-loading-easing) infinite;
    pointer-events: none;
    z-index: 1;
  }

  @keyframes card-loading-spin {
    to { --_card-loading-angle: 360deg; }
  }

  /* ═══════ Raw (no chrome) — token overrides ═══════ */

  :scope[raw] {
    --card-bg: none;
    --card-border: none;
    --card-radius: 0;
    --card-shadow: none;
  }

  /* ═══════ Draggable ═══════ */

  :scope[draggable] {
    cursor: grab;
    user-select: none;
  }

  :scope[draggable]:active,
  :scope[draggable][data-draggable-dragging] {
    cursor: grabbing;
    background: var(--card-drag-bg-active);
  }

  /* ═══════ Header ═══════
     Uses margin (not padding) for inset.
     [padding] switches to padding mode with background.
     Grid layout: optional icon | heading+description | optional action */

  & > header {
    display: block;
    width: stretch;
    margin: var(--card-inset);
  }

  /* Activate grid layout only when a DIRECT slotted child is present.
     Constraining to `:has(> [slot])` prevents deeply nested slots (e.g. an
     [slot="icon"] inside an <avatar-ui>) from falsely activating the grid. */
  & > header:has(> [slot]) {
    display: grid;
    gap: var(--card-header-gap);
    align-items: center;
  }

  & > header[padding] {
    margin: 0;
    padding: var(--card-inset);
    background: var(--card-bg-padded);
  }

  & > header[divider] {
    padding-bottom: var(--card-inset);
    border-bottom: 1px solid var(--card-divider);
  }

  & > header[center] {
    text-align: center;
    justify-items: center;
  }

  /* Column templates — match gen-ui-kit pattern. Direct-child `:has(> …)` so
     nested [slot="icon"] inside a composite (e.g. <avatar-ui>) can't trigger
     the icon column by accident. */
  /* `auto` on the action column = minmax(min-content, max-content): takes
     max-content size when space allows, shrinks to min-content when the card
     is narrow. This lets segmented-ui / button groups in the action slot
     compress and show per-segment ellipsis rather than overflowing. */
  & > header:has(> [slot="icon"]):has(> [slot="action"])         { grid-template-columns: max-content 1fr auto; }
  & > header:has(> [slot="icon"]):not(:has(> [slot="action"]))   { grid-template-columns: max-content 1fr; }
  & > header:not(:has(> [slot="icon"])):has(> [slot="action"])   { grid-template-columns: 1fr auto; }
  & > header:not(:has(> [slot="icon"])):not(:has(> [slot="action"])) { grid-template-columns: 1fr; }

  /* Unslotted children (zettel fragment injection, etc.) — each on its own row,
     spanning the full width, stacked ABOVE the heading/description/action grid.
     This lets compositions inject logos, banners, etc. into a header without
     having to know the slot vocabulary.

     Guarded by `:has(> [slot])` so it ONLY fires when the header is in grid
     mode. Modern browsers honor `justify-self` on block-level in-flow elements,
     so without this guard, an unslotted `<row-ui>` direct child of header
     would be centered even when no slot grammar is in use — causing the
     role-management center-align bug seen in cycle-18 follow-up.

     A2UI transpilation note: the corpus harvester transpiles <h1>..<h6>/<p>/<small>
     to <text-ui variant="display|title|heading|subsection|body|caption">. The
     heading/description rules below match those variants so transpiled chunks
     get the same grid placement as hand-authored native heading/paragraph
     elements. text-ui is excluded here to avoid the centered-fallback. */
  & > header:has(> [slot]) > *:not([slot]):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(p):not(small):not(text-ui) {
    grid-column: 1 / -1;
    justify-self: center;
  }

  /* Icon — first column.
     Default: single row, center-aligned with heading/action.
     When a description row exists, spans both rows and anchors to the
     start so the icon visually aligns with the heading. */
  & > header > [slot="icon"] {
    grid-column: 1;
    grid-row: 1;
    align-self: center;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  & > header:has(> :is([slot="description"], p, small, text-ui[variant="body"], text-ui[variant="caption"])) > [slot="icon"] {
    grid-row: 1 / span 2;
    align-self: start;
  }

  /* Heading — row 1.
     Matches native h1-h6 AND the text-ui variants the A2UI transpiler emits
     for them (display|title|heading|subsection — see HTML_TAG_MAP in
     packages/a2ui/compose/transpiler/transpiler-maps.js).
     The text-ui variants are matched only when UNSLOTTED so an explicit
     `slot="heading"` / `slot="description"` / `slot="action"` always wins. */
  & > header > :is([slot="heading"], h1, h2, h3, h4, h5, h6),
  & > header > :is(text-ui[variant="display"], text-ui[variant="title"], text-ui[variant="heading"], text-ui[variant="subsection"]):not([slot]),
  & > header > [slot="heading"] :is(h1, h2, h3, h4, h5, h6) {
    grid-row: 1;
    line-height: 1.3;
    margin: 0;
  }
  /* Heading slot is a flex container — can hold title + inline badges/metadata */
  & > header > [slot="heading"] {
    display: flex;
    align-items: center;
    gap: var(--card-header-gap);
  }
  & > header:has(> [slot="icon"]) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 2; }
  & > header:has(> [slot="icon"]) > :is(text-ui[variant="display"], text-ui[variant="title"], text-ui[variant="heading"], text-ui[variant="subsection"]):not([slot]) { grid-column: 2; }
  & > header:not(:has(> [slot="icon"])) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 1; }
  & > header:not(:has(> [slot="icon"])) > :is(text-ui[variant="display"], text-ui[variant="title"], text-ui[variant="heading"], text-ui[variant="subsection"]):not([slot]) { grid-column: 1; }

  /* Description — row 2.
     Matches native p/small (with or without slot=description) AND the
     text-ui variants the transpiler emits for them (body|caption) ONLY
     when unslotted — so a `slot="heading"` text-ui doesn't get treated
     as description. */
  & > header > :is([slot="description"], p, small),
  & > header > :is(text-ui[variant="body"], text-ui[variant="caption"]):not([slot]) {
    grid-row: 2;
    grid-column: 1 / -1;
    line-height: 1.4;
    margin: 0;
  }
  & > header:has(> [slot="icon"]) > :is([slot="description"], p, small) { grid-column: 2 / -1; }
  & > header:has(> [slot="icon"]) > :is(text-ui[variant="body"], text-ui[variant="caption"]):not([slot]) { grid-column: 2 / -1; }

  /* Action — row 1, last column.
     Flex container so it can hold badge + button + anything inline. */
  & > header [slot="action"] {
    justify-self: end;
    align-self: center;
    grid-row: 1;
    grid-column: -2 / -1;
    display: flex;
    align-items: center;
    gap: var(--card-header-gap);
    min-width: 0; /* allow flex children (segmented-ui, button) to shrink */
  }

  /* ═══════ Section (body) ═══════
     Margin-based inset by default.
     [padding] switches to padding with background.
     [bleed] removes all spacing for edge-to-edge content. */

  & > section {
    width: stretch;
    margin: var(--card-inset);
  }

  & > section[padding] {
    margin: 0;
    padding: var(--card-inset);
    background: var(--card-bg-padded);
  }

  & > section[bleed] {
    margin: 0;
    padding: 0;
  }

  /* [grow] — section fills remaining card height. Switches the card to a
     column flex container so the section flex-grows into the leftover
     space after the header/footer. Matches the col-ui[grow] / row-ui[grow]
     semantics (flex:1) — same mental model, applied to a card body section.
     Requires the card to have a definite height (inline height, a grid row
     track, or a flex parent); a content-sized card has no leftover to fill.
     Combine with [bleed] for edge-to-edge fill (tables, charts, maps):
     <section bleed grow>. */
  :scope:has(> section[grow]) {
    display: flex;
    flex-direction: column;
  }

  & > section[grow] {
    flex: 1;
    min-height: 0;
  }

  /* Auto-grow for bleed chart cards (sparklines, area, line) — a chart-ui
     directly inside a bleed section fills height without an explicit [grow]
     (the chart itself uses height:100%). Equivalent to the [grow] rule
     above; kept as a zero-config convenience for the common chart case. */
  :scope:has(> section[bleed] > chart-ui) {
    display: flex;
    flex-direction: column;
  }

  :scope:has(> section[bleed] > chart-ui) > section[bleed] {
    flex: 1;
    min-height: 0;
  }

  /* ═══════ Footer ═══════
     Margin-based inset. Flex row for actions.
     [divider] adds top border.
     [padding] switches to padded mode. */

  & > footer {
    display: block;
    width: stretch;
    margin: var(--card-inset);
  }

  /* Activate flex layout when a direct slotted child or multiple children are
     present. `:has(> [slot])` so nested [slot="…"] inside composites can't
     trigger the flex row. */
  & > footer:has(> [slot]),
  & > footer:has(> :nth-child(2)) {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--card-footer-gap);
  }

  /* col-ui in footer takes full width (common for stacked footer: button + divider + social) */
  & > footer > col-ui { width: 100%; }

  & > footer[justify="end"] {
    justify-content: flex-end;
  }

  & > footer[divider] {
    margin-top: var(--card-inset);
    padding-top: var(--card-inset);
    border-top: 1px solid var(--card-divider);
  }

  & > footer[padding] {
    margin: 0;
    padding: var(--card-inset);
    background: var(--card-bg-padded);
  }

  /* Footer with direct-child description + action = space-between */
  & > footer:has(> :is([slot="description"], p, small)):has(> [slot="action"]) {
    justify-content: space-between;
  }

  & > footer :is([slot="description"], p, small) {
    font-size: var(--card-description-size);
    color: var(--card-description-fg);
    flex: 1;
    min-width: 0;
    margin: 0;
  }

  /* Footer heading — trend-stat line (symmetric with header's [slot="heading"]).
     Used for chart trend-footer patterns like "Trending up by 5.2% this month".
     Paired with [slot="description"] for a "Jan – Mar 2024" period caption. */
  & > footer > [slot="heading"] {
    font-size: var(--card-font-size);
    font-weight: var(--a-weight-medium);
    color: var(--card-heading-fg);
    line-height: 1.3;
    margin: 0;
    flex: 0 0 auto;
  }

  /* Footer with heading + description = column stack, heading on top. The
     existing flex-row layout becomes a flex-column when a heading is present,
     so the trend line and period caption stack naturally. */
  & > footer:has(> [slot="heading"]) {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--a-space-0-5);
  }

  & > footer:has(> [slot="heading"])[justify="end"] {
    align-items: flex-end;
  }

  & > footer:has(> [slot="heading"])[justify="center"] {
    align-items: center;
  }

  & > footer [slot="action"] {
    margin-inline-start: auto;
    display: flex;
    gap: var(--card-footer-gap);
  }

  /* Multiple direct action-slotted children (A2UI pattern):
     only the first pushes the group right; subsequent ones flow with gap */
  & > footer > [slot="action"] ~ [slot="action"] {
    margin-inline-start: 0;
  }

  /* Dual-cluster footer: leading action (e.g. Delete) on the inline-start
     edge, trailing action cluster on the inline-end. margin-inline-end:
     auto fills the gap between the two groups. */
  & > footer > [slot="action-leading"] {
    margin-inline-end: auto;
    display: flex;
    gap: var(--card-footer-gap);
  }

  /* ═══════ Images ═══════ */

  & > img,
  & > [slot="media"] {
    display: block;
    width: 100%;
    object-fit: cover;
  }

  & > img:first-child,
  & > [slot="media"]:first-child {
    border-radius: var(--card-radius) var(--card-radius) 0 0;
  }

  /* ═══════ Nested surface layering ═══════
     Step background up one canvas level per nesting depth.
     :scope card-ui has specificity (0,1,1) which beats the
     :where(:scope) initializer (0,0,0) in each inner scope. */
  :scope card-ui                                     { --card-bg: var(--a-canvas-1); --card-border: 1px solid var(--a-border); --a-radius: var(--a-radius-md); }
  :scope card-ui card-ui                              { --card-bg: var(--a-canvas-2); --a-radius: var(--a-radius-sm); }
  :scope card-ui card-ui card-ui                       { --card-bg: var(--a-canvas-3); }
  :scope card-ui card-ui card-ui card-ui                { --card-bg: var(--a-canvas-4); }
  :scope card-ui card-ui card-ui card-ui card-ui         { --card-bg: var(--a-canvas-5); }
  :scope card-ui card-ui card-ui card-ui card-ui card-ui  { --card-bg: var(--a-canvas-6); }
}
