@layer components {
  .tree {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.875rem;
  }

  .tree-group {
    list-style: none;
    margin: 0;
    padding: 0 0 0 1.25rem;
  }

  .tree-item {
    padding: 0;
  }

  .tree-branch {
    border: none;

    /* Two gotchas, both required for the branch to glide:
       1. `::details-content` must attach to the compound (`&::details-content`,
          NOT `& ::details-content` - the descendant form never matches, the
          pseudo's originating element is the subject itself).
       2. `block-size: auto` is a keyword - without interpolate-size the
          0→auto pair is non-interpolable and the transition silently snaps. */
    &::details-content {
      block-size: 0;
      overflow-y: clip;
      interpolate-size: allow-keywords;
      transition: block-size 200ms ease, content-visibility 200ms allow-discrete;
    }

    &[open]::details-content {
      block-size: auto;
    }
  }

  @starting-style {
    .tree-branch[open]::details-content {
      block-size: 0;
    }
  }

  .tree-branch-trigger {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.25rem 0.5rem;
    border-radius: var(--radius-md);
    cursor: pointer;
    color: var(--foreground);
    list-style: none;

    &:hover {
      background-color: var(--accent);
    }

    &:focus-visible {
      outline: 2px solid var(--ring);
      outline-offset: -2px;
    }

    &::-webkit-details-marker {
      display: none;
    }

    & svg {
      width: 1rem;
      height: 1rem;
      flex-shrink: 0;
    }

    & svg:first-child {
      width: 0.875rem;
      height: 0.875rem;
      color: var(--muted-foreground);
      transition: transform 200ms ease;
    }
  }

  details[open] > .tree-branch-trigger > svg:first-child {
    transform: rotate(90deg);
  }

  .tree-leaf {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.25rem 0.5rem;
    padding-left: calc(0.5rem + 0.875rem + 0.375rem);
    border-radius: var(--radius-md);
    color: var(--foreground);
    cursor: default;

    &:hover {
      background-color: var(--accent);
    }

    & svg {
      width: 1rem;
      height: 1rem;
      flex-shrink: 0;
      color: var(--muted-foreground);
    }
  }
  /* -- Checkboxes (data-checkable) ---------------------------------
     A .tree-check (the checkbox component) sits after the icons, before
     the label; tree-view.js cascades and rolls it up (indeterminate). */
  .tree-check {
    flex-shrink: 0;
    margin: 0;
  }
  .tree[data-checkable] .tree-leaf:has(.tree-check) { cursor: pointer; }

  /* -- Drag & drop (data-sortable) ----------------------------------
     A dragged item fades; the row under the pointer shows where it lands:
     a line above / below (before / after) or a tinted frame (into a
     folder). */
  .tree[data-sortable] :is(.tree-branch-trigger, .tree-leaf)[draggable="true"] { cursor: grab; }
  .tree[data-sortable] :is(.tree-branch-trigger, .tree-leaf):active { cursor: grabbing; }
  .tree [data-dragging] > :is(.tree-leaf, details > .tree-branch-trigger) { opacity: 0.4; }
  :is(.tree-branch-trigger, .tree-leaf) {
    &[data-drop="before"] { box-shadow: inset 0 2px 0 var(--primary); }
    &[data-drop="after"] { box-shadow: inset 0 -2px 0 var(--primary); }
    &[data-drop="inside"] {
      background-color: color-mix(in oklch, var(--primary) 12%, transparent);
      box-shadow: inset 0 0 0 1px var(--primary);
    }
  }
  /* -- Labels -----------------------------------------------------
     The label is the trigger's / leaf's last <span>. Deep nesting eats the
     inline space (1.25rem indent per level), so a long label truncates with
     an ellipsis instead of overflowing the tree - put the full text in
     title="…" when it matters. Leaves may be links (<a class="tree-leaf">,
     navigation trees): no underline, pointer cursor. */
  :is(.tree-branch-trigger, .tree-leaf) > span:last-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  a.tree-leaf {
    text-decoration: none;
    cursor: pointer;
  }

  /* -- Selection (opt-in: data-selectable on .tree) ---------------
     aria-selected on the treeitem is the state (tree-view.js writes it on
     click / Enter / Space; authored markup may preset it). The selected row
     gets a primary-tinted surface and medium weight - distinct from the
     neutral accent hover surface, in every theme. The row is the item's
     leaf OR its branch's <summary> (a grandchild: <li> > <details> > <summary>). */
  :is(.tree-item[aria-selected="true"] > .tree-leaf,
    .tree-item[aria-selected="true"] > .tree-branch > .tree-branch-trigger) {
    background-color: color-mix(in oklch, var(--primary) 12%, transparent);
    color: var(--foreground);
    font-weight: 500;
  }

  .tree[data-selectable] :is(.tree-leaf, .tree-branch-trigger) {
    cursor: pointer;
  }

  /* -- Disabled: aria-disabled="true" on the treeitem --------------
     Still focusable (APG: disabled items stay in the keyboard path) but not
     operable - tree-view.js refuses to open or select it. The dimming covers
     the row only; an open disabled branch's children keep their own state. */
  :is(.tree-item[aria-disabled="true"] > .tree-leaf,
    .tree-item[aria-disabled="true"] > .tree-branch > .tree-branch-trigger) {
    opacity: 0.5;
    cursor: not-allowed;

    &:hover {
      background-color: transparent;
    }
  }

  /* -- Empty branch -----------------------------------------------
     A folder with no children shows a muted placeholder row instead of
     opening onto nothing. Text: data-empty on the .tree-group ("Empty" by
     default). :has() rather than :empty - authored whitespace would defeat
     :empty. Generated content: screen readers announce the group's size. ::after, because
     the guides variant already draws its line with ::before. */
  .tree-group:not(:has(> .tree-item))::after {
    content: 'Empty';
    display: block;
    padding: 0.25rem 0.5rem;
    padding-left: calc(0.5rem + 0.875rem + 0.375rem);
    color: var(--muted-foreground);
    font-style: italic;
  }

  .tree-group[data-empty]:not(:has(> .tree-item))::after {
    content: attr(data-empty);
  }

  /* -- Variant: guides --------------------------------------------
     data-variant="guides" on the .tree root draws one vertical guideline
     per nested .tree-group, so the depth of nesting reads at a glance. The
     line sits under the parent branch's chevron centre (trigger padding
     0.5rem + half the 0.875rem chevron) and spans the whole group; it is a
     border, not a background, so forced-colors keeps it visible. The line
     of the innermost group that holds focus darkens - the keyboard user
     sees which level they are on. */
  .tree[data-variant="guides"] .tree-group {
    position: relative;

    &::before {
      content: '';
      position: absolute;
      inset-block: 0;
      inset-inline-start: calc(0.5rem + 0.4375rem - 0.5px);
      border-inline-start: 1px solid var(--border);
      pointer-events: none;
    }

    &:focus-within:not(:has(.tree-group:focus-within))::before {
      border-inline-start-color: var(--muted-foreground);
    }
  }

  /* -- Density ----------------------------------------------------
     data-density on the .tree root scales the row padding-block. Indent
     (padding-left) is structural - untouched. comfortable == the default. */
  .tree:where([data-density="compact"]) {
    & .tree-branch-trigger, & .tree-leaf { padding-block: 0.125rem; }
  }
  .tree:where([data-density="comfortable"]) {
    & .tree-branch-trigger, & .tree-leaf { padding-block: 0.25rem; }
  }
  .tree:where([data-density="spacious"]) {
    & .tree-branch-trigger, & .tree-leaf { padding-block: 0.375rem; }
  }

}

/* Accessibility: suppress motion for users who request it (REQUIRED for all
   components - AGENTS.md "Accessibility CSS"). Near-zero duration instead of
   `none` keeps transitionend/animationend (and discrete display flips)
   firing so JS state machines that await them keep working. */
@media (prefers-reduced-motion: reduce) {
  @layer components {
    .tree,
    .tree *,
    .tree::before,
    .tree::after,
    .tree *::before,
    .tree *::after,
    .tree::backdrop,
    .tree-branch,
    .tree-branch *,
    .tree-branch::before,
    .tree-branch::after,
    .tree-branch *::before,
    .tree-branch *::after,
    .tree-branch::backdrop,
    .tree-branch::details-content,
    .tree-branch-trigger,
    .tree-branch-trigger *,
    .tree-branch-trigger::before,
    .tree-branch-trigger::after,
    .tree-branch-trigger *::before,
    .tree-branch-trigger *::after,
    .tree-branch-trigger::backdrop,
    .tree-group,
    .tree-group *,
    .tree-group::before,
    .tree-group::after,
    .tree-group *::before,
    .tree-group *::after,
    .tree-group::backdrop,
    .tree-item,
    .tree-item *,
    .tree-item::before,
    .tree-item::after,
    .tree-item *::before,
    .tree-item *::after,
    .tree-item::backdrop,
    .tree-leaf,
    .tree-leaf *,
    .tree-leaf::before,
    .tree-leaf::after,
    .tree-leaf *::before,
    .tree-leaf *::after,
    .tree-leaf::backdrop {
      transition-duration: 0.01ms !important;
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
    }
  }
}

/* Forced colors (Windows High Contrast) drop the tinted surface - the
   selected row must still read as selected: system
   Highlight pair, the same signal native listboxes use. */
@media (forced-colors: active) {
  @layer components {
    :is(.tree-item[aria-selected="true"] > .tree-leaf,
      .tree-item[aria-selected="true"] > .tree-branch > .tree-branch-trigger) {
      forced-color-adjust: none;
      background-color: Highlight;
      color: HighlightText;
    }
  }
}
