/* ONE ROW ANATOMY, and every gap inside it. Lead, title over its line, stage, figure, acts, and the body
   under the pair; every row-shaped surface draws this, and `npm run probe:rows` measures it. One gap
   across the row — the mark-to-label rung (`--lotics-lead-gap`), as a rail gives (`stepper.css`) — as a
   margin, so an undrawn part costs nothing. No gap down the pair: each tight line box brings its own air,
   reading at 6, and the boxes hug their shared edge so a taller lead centres the pair rather than opening
   it. Each part is placed by its own rule on the named lines, since the fold moves parts and a container
   query cannot style the row itself. */
@layer theme, base, lotics, components, utilities;
@layer lotics.tokens, lotics.reset, lotics.components;

@layer lotics.components {
  /* At rest on every row, so a nested row never reads its outer row's. */
  .lotics-row {
    --lotics-row-line: var(--lotics-leading-tight-xs);
    --lotics-row-line-inset: calc((var(--lotics-control-content-height) - var(--lotics-leading-md)) / 2);
  }

  /* The always-written density attribute lifts this over the pressable surface's flex row. */
  .lotics-row[data-density] {
    display: grid;
    grid-template-columns: [lead] auto [title] minmax(0, 1fr) [stage] auto [figure] auto [acts] auto [end];
    /* `stand` holds stage and figure once a narrow band moves them off the title's line, `folded` the
       controls moved under the pair; empty tracks take no height. The pair's tracks are their content's:
       a clamped title hides its overflow and so claims no minimum, and a part centred across the pair
       would open an empty line under it, sliding off the words it centres on. */
    grid-template-rows: [pair] min-content [line] min-content [more] min-content [stand] auto [folded] auto [body] auto;
    align-content: center;
    align-items: start;
    gap: 0;
    flex-shrink: 0;
    min-width: 0;
  }

  .lotics-row[data-density]:has(> .lotics-row__line[data-size="sm"]) {
    --lotics-row-line: var(--lotics-leading-tight-sm);
  }

  .lotics-row[data-density]:has(> .lotics-row__line[data-size="md"]) {
    --lotics-row-line: var(--lotics-leading-tight-md);
  }

  .lotics-row[data-density]:has(> .lotics-row__line[data-size="lg"]) {
    --lotics-row-line: var(--lotics-leading-tight-lg);
  }

  .lotics-row[data-density]:has(> .lotics-row__line[data-size="xl"]) {
    --lotics-row-line: var(--lotics-leading-tight-xl);
  }

  .lotics-row[data-density]:has(> .lotics-row__line[data-size="xxl"]) {
    --lotics-row-line: var(--lotics-leading-tight-xxl);
  }

  .lotics-row[data-density]:has(> .lotics-row__line[data-size="xxxl"]) {
    --lotics-row-line: var(--lotics-leading-tight-xxxl);
  }

  /* A line carrying a logo is the logo's height, so the mark sits in the clip and the centred words give
     the pair its air. */
  .lotics-row[data-density]:has(> .lotics-row__line[data-marked]) {
    --lotics-row-line: var(--lotics-mark-sm);
  }

  .lotics-row[data-align="right"] > .lotics-row__title,
  .lotics-row[data-align="right"] > .lotics-row__line {
    justify-self: end;
    justify-content: flex-end;
  }

  .lotics-row[data-align="center"] > .lotics-row__title,
  .lotics-row[data-align="center"] > .lotics-row__line {
    justify-self: center;
    justify-content: center;
  }

  /* No line: the lead shares the title's track, so they centre on each other. */
  .lotics-row[data-density]:not(:has(> .lotics-row__line)) {
    align-items: center;
  }

  /* Two empty tracks of `--lotics-row-inset` close every row, so no surface states a row's air. On the
     beat they are a floor — taller content keeps them, and inside the beat the pair centres, the inset
     being half the beat's addition. On air they are all the spacing. */
  .lotics-row[data-density="beat"],
  .lotics-row[data-density="air"] {
    grid-template-rows:
      [air-start] var(--lotics-row-inset) [pair] min-content [line] min-content [more] min-content [stand] auto [folded] auto [body] auto
      [air-end] var(--lotics-row-inset);
  }

  .lotics-row[data-density="beat"] {
    min-height: var(--lotics-row-beat);
  }

  .lotics-row[data-density="band"] {
    min-height: var(--lotics-register-row);
  }

  /* A line row's air is two inset tracks too, a floor that a wrapped title or a supporting line keeps: sized
     so one line lands on the content height, the lead's pull on the title given back. */
  .lotics-row[data-density="line"] {
    grid-template-rows:
      [air-start] var(--lotics-row-line-inset) [pair] min-content [line] min-content [more] min-content [stand] auto [folded] auto [body] auto
      [air-end] var(--lotics-row-line-inset);
    min-height: var(--lotics-control-content-height);
  }

  .lotics-row[data-density="line"]:has(> .lotics-row__lead:not([data-vacant])) {
    --lotics-row-line-inset: calc((var(--lotics-control-content-height) - var(--lotics-leading-md)) / 2 - var(--lotics-row-pull));
  }

  /* A line-density row with a supporting line grows by it, or its date sits as close to the next title as
     to its own. */
  .lotics-row[data-density="line"]:has(> .lotics-row__line) {
    min-height: calc(var(--lotics-control-content-height) + var(--lotics-row-line));
  }

  /* Top-set: a rail's node is placed off the lead's height, and a lead sliding with a wrapping title would
     leave it behind. A lead is its block's height (`identityRung`), so unwrapped the answers match. */
  /* An expected slot's mark stands where a held row's does, quiet as its name: a place, not yet a thing. */
  .lotics-row[data-expected] > .lotics-row__lead {
    opacity: 0.45;
  }

  .lotics-row__lead {
    grid-column: lead;
    grid-row: pair;
    display: flex;
    flex-direction: row;
    align-items: center;
    align-self: start;
    flex-shrink: 0;
    min-width: 0;
    gap: var(--lotics-lead-gap);
    margin-inline-end: var(--lotics-lead-gap);
  }

  /* A list's picture: its one slot, so every title of the list starts at one x and every row stands one height; a
     mark smaller than the slot — a face beside pictures — centres in it. */
  .lotics-row__lead[data-slot] {
    justify-content: center;
    min-width: var(--lotics-row-lead-slot);
    min-height: var(--lotics-row-lead-slot);
  }

  /* No lead of the list holds a picture: the names start at the row's edge. */
  .lotics-row__lead[data-vacant] {
    display: none;
  }

  .lotics-row[data-density]:has(> .lotics-row__line) > .lotics-row__lead {
    grid-row: pair / span 2;
  }

  .lotics-row[data-density]:has(> .lotics-row__line ~ .lotics-row__line) > .lotics-row__lead {
    grid-row: pair / span 3;
  }

  .lotics-row__title {
    grid-column: title / stage;
    grid-row: pair;
    align-self: end;
    min-width: 0;
    max-width: 100%;
  }

  .lotics-row[data-density]:not(:has(> .lotics-row__line)) > .lotics-row__title {
    align-self: center;
  }

  /* THE OPTICAL RULE, off the type tokens alone: the lead stands at the top; the title block (title
     hugging its track's foot, lines their heads) is centred on the lead's tracks and pulled in by the
     title's half-leading. A block no taller than the lead centres on it; a taller one puts the title's cap
     on the lead's top edge, sliding by the pixel between (`probe:rows` `align`). `:where` keeps this under
     THE FOLD. */
  :where(.lotics-row[data-density]:has(> .lotics-row__lead)) {
    --lotics-row-pull: calc((var(--lotics-text-sm) - var(--lotics-leading-tight-sm)) / 2);
  }

  :where(.lotics-row[data-density]:has(> .lotics-row__lead)) > .lotics-row__title {
    margin-block-start: var(--lotics-row-pull);
  }

  :where(.lotics-row[data-density]:has(> .lotics-row__lead)) > .lotics-row__line:not(:has(~ .lotics-row__line)),
  :where(.lotics-row[data-density]:has(> .lotics-row__lead):not(:has(> .lotics-row__line))) > .lotics-row__title {
    margin-block-end: var(--lotics-row-pull);
  }

  /* THE SUPPORTING LINE — one line at every width, every statement whole: the box clips, and an unseatable
     statement wraps to a hidden second line rather than cutting the identifying words. It runs under stage
     and figure to the edge (THE FOLD). */
  .lotics-row__line {
    grid-column: title / acts;
    grid-row: line;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-content: flex-start;
    /* Centring keeps each statement box inside the clip; baseline alignment pokes one a pixel out. */
    align-items: center;
    align-self: start;
    min-width: 0;
    max-width: 100%;
    height: var(--lotics-row-line);
    overflow: hidden;
    column-gap: var(--lotics-space-4);
  }

  /* A second line on its own track, whole or absent like the first; a third is `Row.Body`. */
  .lotics-row__line ~ .lotics-row__line {
    grid-row: more;
  }

  /* A statement never shrinks, so each is whole or absent — `CN…` names another row. A statement may be a
     pair at the pair's rung. */
  .lotics-row__said {
    display: flex;
    flex-direction: row;
    align-items: center;
    flex-shrink: 0;
    height: 100%;
    column-gap: var(--lotics-space-4);
  }

  /* A zero-width lead item sends an overwide first statement to the hidden line instead of an ellipsis. It
     is the box's height, so the first flex line is the shown one; its end margin repays the gap. */
  .lotics-row__line::before {
    content: "";
    flex: 0 0 0;
    height: 100%;
    margin-inline-end: calc(var(--lotics-space-4) * -1);
  }

  /* A line with an owed statement (`Row.Line kept`) clips nothing: an unseatable folded reading takes the
     next line whole, since a hidden reading reads as absent. The separator hangs in the gap before its
     statement, so a line never opens on a `·`. */
  .lotics-row__line:has(> .lotics-row__said[data-kept]:not(:empty)):not([data-one]) {
    height: auto;
    overflow: visible;
    column-gap: var(--lotics-space-12);
    clip-path: inset(-100% -100% -100% 0);
  }

  /* On such a line an overwide statement wraps inside it, since no other line can seat it — its value
     under its label, whole: a figure cut inside itself reads as another. */
  .lotics-row__line:has(> .lotics-row__said[data-kept]:not(:empty)):not([data-one]) > .lotics-row__said {
    position: relative;
    height: auto;
    min-height: var(--lotics-row-line);
    flex-shrink: 1;
    flex-wrap: wrap;
    min-width: 0;
    max-width: 100%;
  }

  .lotics-row__line:has(> .lotics-row__said[data-kept]:not(:empty)):not([data-one]) > .lotics-row__said::before {
    position: absolute;
    inset-inline-start: calc(var(--lotics-space-12) * -1);
    width: var(--lotics-space-12);
    text-align: center;
  }

  .lotics-row__line:has(> .lotics-row__said[data-kept]:not(:empty))::before {
    display: none;
  }

  /* A REGISTER'S ROW keeps to its name and one line: its statements run on as words, a mark inline among
     them, and the line ends in an ellipsis after the last word it shows — a statement that cannot show its
     first word whole waits behind it (`data-cut`), and never an ellipsis with nothing before it. */
  .lotics-row__line[data-one] {
    display: block;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: var(--lotics-text-xs);
    line-height: var(--lotics-row-line);
    color: var(--lotics-ink-muted);
  }

  .lotics-row__line[data-one]::before {
    display: none;
  }

  .lotics-row__line[data-one] > .lotics-row__said[data-cut] {
    display: none;
  }

  /* What waits behind the line still says so — unless a mark left the ellipsis no room (`data-full`), since a
     mark is never cut to make it. */
  .lotics-row__line[data-one][data-cut]:not([data-full])::after {
    content: "…";
    padding-inline-start: var(--lotics-space-4);
  }

  .lotics-row__line[data-one] > .lotics-row__said {
    display: inline;
    height: auto;
  }

  /* A node among the words (a face and its name, a pictured link, a thumbnail stack) is laid out as its
     own box and stands on the line as one word does; a block would fall to the clipped second line. */
  .lotics-row__line[data-one] > .lotics-row__said[data-node]:not([data-cut], [data-folded], :empty) {
    display: inline-flex;
    align-items: center;
    height: var(--lotics-row-line);
    vertical-align: top;
  }

  /* Words inside a statement keep the line's no-wrap, or a run would break inside them onto the clipped
     second line instead of reaching the ellipsis. */
  .lotics-row__line[data-one] > .lotics-row__said [class*="lotics-"] {
    white-space: inherit;
  }

  /* The run has no gap to hang the separator in, so it carries a rung either side. */
  .lotics-row__line[data-one] > .lotics-row__said:not(:empty, [data-folded], [data-cut]) ~ .lotics-row__said:not(:empty)::before {
    padding-inline: var(--lotics-space-4);
  }

  /* A reading's detail (a deadline's day) continues its reading, a word apart rather than a separator apart. */
  .lotics-row__said:not(:empty) ~ .lotics-row__said[data-detail]:not(:empty)::before {
    content: none;
  }

  .lotics-row__line[data-one] > .lotics-row__said:not(:empty) ~ .lotics-row__said[data-detail]:not(:empty) {
    margin-inline-start: var(--lotics-space-4);
  }

  /* In a register's phone row the one line and the body run across the name's cell (`useReach` measures it),
     under the reading kept beside the name, so the name's width never holds the readings or the documents
     under it to nothing. */
  .lotics-row__line[data-one][data-reach],
  .lotics-row__body[data-reach] {
    justify-self: start;
    width: var(--lotics-row-reach);
    max-width: none;
  }

  /* Words standing for a tooltip lift over the door and take the pointer, so hovering them shows it; a press on
     them still reaches the row. */
  .lotics-row__said[data-tip] {
    position: relative;
    z-index: var(--lotics-z-row-content);
    pointer-events: auto;
  }

  /* Focused, its words underline in their own ink, as a link's do, and its short form takes the ring inside its
     box: a ring round them is cut by the line's clip. */
  .lotics-row__said[data-tip]:focus-visible {
    outline: none;
  }

  .lotics-row__said[data-tip]:focus-visible > [class*="lotics-"]:not(.lotics-row__short) {
    text-decoration-line: underline;
  }

  .lotics-row__said[data-tip]:focus-visible > .lotics-row__short {
    box-shadow: inset 0 0 0 var(--lotics-ring-width) var(--ring);
  }

  /* Where the line cannot seat them whole (`useWholeRuns`), such words shorten to the tone's mark and the count
     behind them, never cut. */
  .lotics-row__short {
    display: none;
  }

  .lotics-row__said[data-short] > .lotics-row__short {
    display: inline-flex;
    align-items: center;
    column-gap: var(--lotics-space-2);
    height: var(--lotics-row-line);
    padding-inline: var(--lotics-space-2);
    border-radius: var(--lotics-radius-control);
    vertical-align: top;
  }

  .lotics-row__said[data-short] > :not(.lotics-row__short) {
    display: none;
  }

  /* A folded column's label keeps its words; the value is what gives way. */
  .lotics-row__fold-label {
    flex-shrink: 0;
  }

  /* A reference is one unbroken run (`PO-` over `0412` names neither), declared by its kind (`Reference`),
     never a pattern. */
  .lotics-row__reference {
    white-space: nowrap;
  }

  /* Kept in the DOM for one shape and hidden here, with its separator. */
  .lotics-row__said:empty {
    display: none;
  }

  /* The gaps either side are one rung, so the separator has no margin. */
  .lotics-row__said:not(:empty, [data-folded], [data-cut]) ~ .lotics-row__said:not(:empty)::before {
    content: "·";
    color: var(--lotics-ink-muted);
  }

  /* What the columns said beside the pair joins the line only once they fold (`Row.Line folded`), first,
     since a line sheds from its end. Hidden, it separates nothing; a line with nothing else is not drawn. */
  .lotics-row__said[data-folded] {
    display: none;
  }

  .lotics-row__line:has(> .lotics-row__said[data-folded]):not(:has(> .lotics-row__said:not([data-folded]))) {
    display: none;
  }

  /* WHAT STANDS BESIDE THE PAIR CENTRES ON THE ROW — its stage, its figure and its acts, across every line the pair
     holds, as the lead stands: a status riding the title's line read as floating over the row's middle. A stage is
     one run, as a figure is: wrapped, it stood over the line under the title. */
  .lotics-row__stage {
    grid-column: stage;
    grid-row: pair;
    align-self: center;
    display: flex;
    flex-direction: row;
    align-items: center;
    flex-shrink: 0;
    min-width: 0;
    gap: var(--lotics-space-4);
    margin-inline-start: var(--lotics-lead-gap);
    white-space: nowrap;
  }

  .lotics-row__figure {
    grid-column: figure;
    grid-row: pair;
    align-self: center;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
    flex-shrink: 0;
    min-width: 0;
    gap: var(--lotics-space-4);
    margin-inline-start: var(--lotics-lead-gap);
    font-variant-numeric: var(--lotics-font-numeric);
  }

  /* On a pair they stand across its lines; `:where` keeps this under THE FOLD. */
  :where(.lotics-row[data-density]:has(> .lotics-row__line)) > :is(.lotics-row__stage, .lotics-row__figure) {
    grid-row: pair / span 2;
  }

  :where(.lotics-row[data-density]:has(> .lotics-row__line ~ .lotics-row__line)) > :is(.lotics-row__stage, .lotics-row__figure) {
    grid-row: pair / span 3;
  }

  /* A figure is one token; two classes, because a worded figure is a `Text`, whose one-class rule lets it
     wrap and ties go by emission order. */
  .lotics-row > .lotics-row__figure {
    white-space: nowrap;
  }

  /* Controls centre on the pair: a verb is about the row. */
  .lotics-row__acts {
    grid-column: acts;
    grid-row: pair;
    display: flex;
    flex-direction: row;
    align-items: center;
    align-self: center;
    flex-shrink: 0;
    min-width: 0;
    gap: var(--lotics-space-4);
    margin-inline-start: var(--lotics-lead-gap);
  }

  .lotics-row[data-density]:has(> .lotics-row__line) > .lotics-row__acts {
    grid-row: pair / span 2;
  }

  /* Beside a lead the controls size nothing, or a tall verb would slide the title off its lead; centred,
     they stand out of the pair evenly. Folded, they take their height again. */
  :where(.lotics-row[data-density]:has(> .lotics-row__lead)) > .lotics-row__acts {
    height: 0;
  }

  .lotics-row[data-density]:has(> .lotics-row__line ~ .lotics-row__line) > .lotics-row__acts {
    grid-row: pair / span 3;
  }

  /* On the title's left edge, at the group rung. */
  .lotics-row__body {
    grid-column: title / end;
    grid-row: body;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-width: 0;
    gap: var(--lotics-space-8);
    margin-block-start: var(--lotics-space-8);
  }

  /* THE FOLD — read off the nearest box owning its width (a list, band or page column), never the window,
     so a drawer row folds on a wide screen. In one order so the subject keeps the width: (1) column-like
     controls (`Row.fold`) go under the pair, their beside-the-pair statements (`Row.Line folded`) joining
     the line; (2) the stage takes its own line under the supporting line (`stand`); (3) the figure joins
     it. Never onto the supporting line, which would then drop the party or cut the reference. */
  @container (width < 520px) {
    /* Outranks every seat of the acts beside the pair: across its lines, or on the title's. */
    .lotics-row[data-density][data-fold]:has(> .lotics-row__acts) > .lotics-row__acts {
      grid-column: title / end;
      grid-row: folded;
      justify-self: end;
      height: auto;
      margin-inline-start: 0;
      margin-block-start: var(--lotics-space-4);
    }

    .lotics-row__said[data-folded] {
      display: flex;
    }

    .lotics-row__said[data-folded]:not(:empty) ~ .lotics-row__said:not(:empty)::before {
      content: "·";
      color: var(--lotics-ink-muted);
    }

    .lotics-row__line:has(> .lotics-row__said[data-folded]):not(:has(> .lotics-row__said:not([data-folded]))) {
      display: flex;
    }
  }

  /* Two verbs beside the pair would starve its title to a letter a line: they stand under it, at its end. A
     lone verb or ⋯ keeps its place — the focus guards and portal owner its open menu sets beside it counting for
     no verb, so a press moves it not. */
  @container (width < 440px) {
    .lotics-row[data-density]:has(> .lotics-row__acts > :not([data-base-ui-focus-guard], [aria-owns]) ~ :not([data-base-ui-focus-guard], [aria-owns])) > .lotics-row__acts {
      grid-column: title / end;
      grid-row: folded;
      justify-self: end;
      height: auto;
      margin-inline-start: 0;
      margin-block-start: var(--lotics-space-4);
    }
  }

  @container (width < 440px) {
    .lotics-row[data-density]:has(> .lotics-row__stage) > .lotics-row__title {
      grid-column: title / figure;
    }

    .lotics-row[data-density]:has(> .lotics-row__stage):not(:has(> .lotics-row__figure)) > .lotics-row__title {
      grid-column: title / acts;
    }

    /* The two columns' edge is the row's. */
    .lotics-row[data-density] > .lotics-row__stage {
      grid-column: stage / acts;
      grid-row: stand;
      justify-self: end;
      margin-block-start: var(--lotics-space-4);
    }
  }

  /* The figure joins a stand only under an existing line; with none it stays on the title. */
  @container (width < 440px) {
    .lotics-row[data-density]:has(> .lotics-row__line):has(> .lotics-row__figure) > .lotics-row__title,
    .lotics-row[data-density]:has(> .lotics-row__line):has(> .lotics-row__stage) > .lotics-row__title {
      grid-column: title / acts;
    }

    /* The stage opens the stand under the line, from the title's edge, and the figure ends it at the row's,
       under the acts, which stand beside the pair alone: in columns of their own the two would split what the
       title leaves them and cut each other. */
    .lotics-row[data-density]:has(> .lotics-row__line) > .lotics-row__stage {
      grid-column: title / figure;
      justify-self: start;
      margin-inline-start: 0;
    }

    .lotics-row[data-density]:has(> .lotics-row__line) > .lotics-row__figure {
      grid-column: figure / end;
      grid-row: stand;
      justify-self: end;
      margin-block-start: var(--lotics-space-4);
    }
  }

  /* The door spans the band; control-holding parts lift over it and keep their own presses
     (`RowFocusEntry`). */
  .lotics-row[data-door] > .lotics-row__lead,
  .lotics-row[data-door] > .lotics-row__stage,
  .lotics-row[data-door] > .lotics-row__figure,
  .lotics-row[data-door] > .lotics-row__acts {
    position: relative;
    z-index: var(--lotics-z-row-content);
  }

  /* Lifted, they still let the pointer through to the door: only their own controls take it. */
  .lotics-row[data-door] > :is(.lotics-row__lead, .lotics-row__stage, .lotics-row__figure, .lotics-row__acts) {
    pointer-events: none;
  }

  .lotics-row[data-door] :is(a[href], button, input, select, textarea, label, summary, [role="button"], [role="checkbox"], [role="switch"], [role="radio"], [role="link"], [role="menuitem"], [role="combobox"], [role="slider"], [role="tab"], [contenteditable="true"]) {
    pointer-events: auto;
  }
}
