/* Lexxy rich text editor. Shadows the gem's own lexxy.css aggregator so the
   gem stylesheets land in our cascade layers, with its palette mapped onto
   the house tokens — monochrome ink, editor chrome stays quiet.

   The hazard is LAYER ORDER, not specificity: the gem's stylesheets are
   imported into `base`, so any bare element rule we write in `components`
   beats all of them however weak its selector — which is how the toolbar once
   rendered as black squares (de693f3). Keep the gem in `base`, and never paint
   a bare control in `components`. (The gem does use :where() in places, but it
   also ships unwrapped rules; specificity is not what saves us here.)

   Everything the gem can be told through a custom property is told that way
   rather than overridden with a selector — Basecamp, who wrote it, themes it
   the same way.

   One composer, wrapped in .composer and sized through the gem's own
   variables. There were two — Hanshi 06's boxed one for long forms and 11's
   lean one for comments, chat and notes — until every surface had given up
   the box one by one and the user ruled the family down to a single line
   (2026-09-30, HANSHI ## Deviations): no frame anywhere, the sheet is the
   field, and at rest a composer is its prompt and nothing else. */
@import url("/assets/lexxy-variables-a4c922d5.css") layer(base);
@import url("/assets/lexxy-content-b2bd89df.css") layer(base);
@import url("/assets/lexxy-editor-cc059d12.css") layer(base);

:root {
  --lexxy-color-ink: var(--color-ink);
  --lexxy-color-ink-medium: var(--color-ink-subtle);
  --lexxy-color-ink-light: var(--color-ink-faint);
  --lexxy-color-ink-lighter: var(--color-line);
  --lexxy-color-ink-lightest: var(--color-surface);
  --lexxy-color-ink-inverted: var(--color-canvas);

  --lexxy-color-canvas: var(--color-canvas);
  --lexxy-color-link: var(--color-link);

  --lexxy-color-accent-dark: var(--color-ink);
  --lexxy-color-accent-medium: var(--color-ink-subtle);
  --lexxy-color-accent-light: var(--color-ink-faint);
  --lexxy-color-accent-lightest: var(--color-surface);

  --lexxy-color-selected: var(--color-selected);
  --lexxy-color-selected-dark: var(--color-accent);

  /* The gem ships GitHub-light hexes for these and derives the code background
     from --lexxy-color-ink-lightest, which we map to --color-surface — 20% L in
     dark mode. Left unmapped, `punctuation` (#24292e, ~24% L) sits invisible on
     it. We have no syntax palette, so the eight roles collapse onto the inks
     plus the two hues the app already spends. */
  --lexxy-color-code-token-att: var(--color-accent-ink);
  --lexxy-color-code-token-comment: var(--color-ink-faint);
  --lexxy-color-code-token-function: var(--color-ink);
  --lexxy-color-code-token-operator: var(--color-ink-subtle);
  --lexxy-color-code-token-property: var(--color-selected-ink);
  --lexxy-color-code-token-punctuation: var(--color-ink-subtle);
  --lexxy-color-code-token-selector: var(--color-positive-ink);
  --lexxy-color-code-token-variable: var(--color-ink);

  /* Attachment icon fill and border. Unmapped, the gem leaks its own blue and
     purple into a palette that is ink plus one red. */
  --lexxy-color-blue: var(--color-ink-subtle);
  --lexxy-color-purple: var(--color-ink-subtle);
  /* The gem derives its "selected hover" from accent-light, which we map to a
     mid ink — far too heavy for a wash. Every wake state in a composer is the
     same surface fill, so say so once here. */
  --lexxy-color-selected-hover: var(--color-surface);
  --lexxy-color-red: var(--color-negative);
  --lexxy-color-green: var(--color-positive);

  --lexxy-font-base: var(--font-sans);
  --lexxy-font-mono: var(--font-mono);
  --lexxy-text-small: var(--text-small);
  --lexxy-radius: var(--border-radius);
  --lexxy-shadow: var(--shadow);

  /* Focus is shu inside the editor too — the toolbar controls and a selected
     node are the same mark, so they read as one language with the rest of the
     app. The offset goes to 0 because the gem insets its ring by -2px, which
     eats into small toolbar buttons; this is what Basecamp does, and they
     wrote the gem. Suppressing the ring and re-adding it elsewhere was the
     workaround this replaces. */
  --lexxy-focus-ring-color: var(--color-accent);
  --lexxy-focus-ring-size: var(--focus-ring-size);
  --lexxy-focus-ring-offset: 0;

  --lexxy-toolbar-button-size: 1.75rem;
}

@layer components {
  /* ——— The composer ———
     At rest: one line, the prompt in ink-faint, no toolbar, no rule, no
     frame. Click, and the caret is all that changes: the field grows with
     the words and with nothing else (the action row is the form's own
     business: comments.css). Focus draws nothing, on the wrapper or in the
     editor: the caret is the whole mark, the same rule every .input keeps
     (inputs.css). What the frame used to say — "this is a field" — the
     prompt says.

     Scoped to .composer: an element selector standing on its own in this
     layer is exactly what flattened the toolbar once. */
  .composer {
    /* The caret is the focus mark, in shu — inputs.css says why. */
    caret-color: var(--color-accent);
    /* One line of the words, for whatever centres on it: the T below, the
       toolbar beside a label (todo-form.css). Registered so it computes
       here, in the words' type; read raw, 1lh resolves in the reader's. */
    --composer-line: 1lh;
  }

  @property --composer-line {
    syntax: "<length>";
    inherits: true;
    initial-value: 0px;
  }

  /* A composer that carries its own T — one field among several, where a T
     beside the submit read as the whole form's (todos/_fields): the T stands
     beside the last line, centred on it and no taller, so it costs the field
     no height and the first click moves nothing. */
  .composer:has(> .formatting-toggle) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    column-gap: var(--inline-space);

    > :is(.formatting-toggle, .composer-attach) {
      margin-block: calc((var(--composer-line) - var(--btn-size, 1.75rem)) / 2);
    }
  }

  .composer lexxy-editor {
    background: transparent;
    border: none;
    /* No box, so the gem's 1ch of breathing room is only an indent: it set
       a message's body 1ch right of the title it belongs under (user,
       2026-08-29). On the element, not the wrapper — the gem declares it
       there, so an inherited value would lose. Same for the rows. */
    --lexxy-editor-padding: 0px;
    /* One line, focused or not. The gem spends this as a FLOOR
       (min-block-size), so the words grow the field and this only says how
       tall it is with none. It used to jump to six lines on focus, and every
       row under it jumped too (user, 2026-10-06). */
    --lexxy-editor-rows: 1lh;
    /* Lexxy 1.0 reserves a toolbar's height on the element before it mounts
       (its only use of this variable), so the page does not shift when it
       does. Ours shows no toolbar until the T is pressed, so the reservation
       is the rows alone — otherwise every composer shrank once, on load. */
    --lexxy-toolbar-height: 0px;
  }

  /* A sheet's own body — a message written or edited, an answer — is long
     writing, and one line of prompt over the next field read as a note
     (user, 2026-10-06). So it stands open six lines tall from the start, the
     T in the box's corner. Still a floor, and still the same focused or not:
     the click moves nothing, the words grow it past six. */
  .sheet > .composer lexxy-editor {
    --lexxy-editor-rows: 6lh;
  }

  /* No margin under the last line: the gem's content margin belongs between
     paragraphs. Left on, it made the settled editor half a line taller than
     the one line it opens as — Lexxy builds its content a frame after
     mounting, so everything under a composer dropped 12px one frame after
     the click (chat 2026-08-30, the to-do's Notes 2026-10-06; both measured
     frame by frame). Trimmed, rest and settled stand level. */
  .composer .lexxy-editor__content > :last-child {
    margin-block-end: 0;
  }

  /* The prompt is a voice, not a field: ink-faint at full size rather than the
     gem's dimmed copy of whatever colour it inherits. */
  .composer .lexxy-editor__content::before {
    color: var(--color-ink-faint);
    opacity: 1;
  }

  /* THE TOOLBAR IS NEVER AUTOMATIC (user, 2026-10-06): people seldom format,
     so no composer shows its tools until the T beside its submit is pressed —
     chalet's chat rule, now every composer's. The T is composers/_formatting_toggle
     and the state is `data-formatting` on whatever the T and the composer
     share (formatting_controller). Until then the composer is the prompt, or
     the words, and nothing else. Focus never wakes it. */
  .composer lexxy-toolbar {
    display: none;
  }

  [data-formatting] .composer lexxy-toolbar {
    display: flex;
  }

  /* A phone writes plainly — chalet's rule, on fizzy's signal: where nothing
     can hover, no composer shows a toolbar, and the T goes with it. Just a
     line, the words, and the paperclip in the T's place: with the toolbar
     went its image and file buttons, and a phone still has pictures to give
     (composers/_formatting_toggle). After the rule above, so a stale
     attribute cannot re-open what the device cannot use. */
  @media (any-hover: none) {
    [data-formatting] .composer lexxy-toolbar,
    .formatting-toggle {
      display: none;
    }

    /* A composer that carries its own T and no paperclip gives the T's
       column and gap back: hidden, it still held both, and the words stopped
       short of the fields beside them (measured on a phone, 2026-10-06). */
    .composer:has(> .formatting-toggle):not(:has(> .composer-attach)) {
      display: block;
    }

    /* The paperclip wakes with the field, like the comment row (comments.css). */
    .composer:has(lexxy-editor.lexxy-editor--empty):not(:focus-within) > .composer-attach {
      visibility: hidden;
    }
  }

  /* Where a pointer hovers, the toolbar has the image and file buttons, so
     the paperclip would only say it twice. */
  @media (any-hover: hover) {
    .composer-attach {
      display: none;
    }
  }

  /* Tools are monochrome ink glyphs standing bare above the text: with no
     frame, the gem's rule under the toolbar would be a hairline with nothing
     to close, and its canvas ground a white strip on any tint (the panel's,
     a comment's). No inline padding, so the first tool starts on the text's
     left edge and the last ends on its right — the toolbar is exactly as wide
     as the words, undo at their far edge, not under the buttons beside the
     line (user, 2026-10-06, on the chat). And a half line of air under it,
     because the gem seats the tools flush on the first line of text. Hover
     and the active tool — including the tools inside a dropdown — are the
     same surface fill, never a colour; left alone the gem reaches for its
     selection blue. */
  .composer lexxy-toolbar {
    color: var(--color-ink-subtle);
    border-block-end: none;
    padding-inline: 0;
    padding-block-end: 0.5lh;

    /* At rest a tool stands on whatever the composer stands on: the gem
       paints every bare button in canvas, which reads as a white chip on
       any tint (the panel's, a comment's). */
    .lexxy-editor__toolbar-button {
      background-color: transparent;
    }

    [aria-pressed="true"],
    .lexxy-editor__toolbar-button:is(:hover, :active):not([aria-disabled="true"]) {
      color: var(--color-ink);
      background-color: var(--color-surface);
    }
  }

  /* The tools stay in view while the words run long (user, 2026-10-10):
     pressed at the foot of a long body, the T opened them at its head, off
     the screen. Sticky, so they ride under the fixed header. Stuck, they
     stand over the words, so they take the canvas the sheet is, and the half
     line of air under them is padding, so no word shows through it. Not on a
     chat line: it is never long enough to need its tools held, and it stands
     on tints no paint matches — a bubble, the panel. */
  .composer lexxy-editor:not([permitted-attachment-types]) lexxy-toolbar {
    position: sticky;
    inset-block-start: var(--header-height);
    z-index: 1;
    background-color: var(--color-canvas);
  }

  /* A modal scrolls itself, with no header over it, and a sticky box stops
     at its scroller's padding: the tools pin to its top edge, past the
     padding (dialog.css), or the words show in the band above them. */
  .modal .composer lexxy-editor lexxy-toolbar {
    inset-block-start: calc(-1 * var(--block-space-double));
  }

  /* A chat line carries words, a person and a link, so its toolbar drops the
     tools for anything else — chalet's trim: pictures and files arrive by the
     paperclip as lines of their own, and a table, a divider, a colour or an
     undo history has no place in a line of talk. The chat's editors are the
     ones naming their attachment types (rich_text_helper). Hidden rather than
     removed, the way the gem hides its own upload buttons. */
  lexxy-editor[permitted-attachment-types] lexxy-toolbar :is(
    button:is([name="image"], [name="file"], [name="underline"], [name="table"], [name="divider"], [name="undo"], [name="redo"]),
    lexxy-highlight-dropdown
  ) {
    display: none;
  }

  /* Dropdowns (link, colour, overflow) are floats: hairline, the float radius,
     the one warm shadow. The gem draws a 2px accent frame instead. */
  .composer [data-dropdown-panel] {
    border: var(--border);
    border-radius: var(--border-radius-float);
  }

  /* The @-mention menu is a float too, and it is how mentions are reached —
     the composer has no toolbar button for them. */
  .lexxy-prompt-menu {
    border: var(--border);
    border-radius: var(--border-radius-float);
    /* The gem caps the menu at 20ch, which wrapped a project's name onto two
       lines once the # rows carried their kind beside it (user, 2026-10-10).
       Its own formula, wider; a title longer still ends in an ellipsis
       (prompts/references/_item). */
    max-inline-size: min(40ch, calc(100% - var(--lexxy-prompt-offset-x)));

    &[data-clipped-at-right] {
      max-inline-size: min(40ch, calc(100% - 1ch));
    }
  }

  /* A modal scrolls, so it clips whatever hangs out of it — the menu opened
     at the foot of the add-card dialog was cut at its edge (user,
     2026-10-10). Fixed, the menu leaves the modal's clip; anchored, it stays
     where the gem put it, the same offsets measured from the editor's box
     instead of from inside it. The gem flips it above the caret only when it
     would leave the window, which is now the right question.
     ponytail: one anchor name per modal — a modal with two composers anchors
     to the last; anchor-scope per editor when one has two. */
  .modal lexxy-editor {
    anchor-name: --modal-composer;
  }

  .modal .lexxy-prompt-menu {
    /* !important because the gem writes `position: absolute` inline on the
       menu when it builds it, and nothing else outranks an inline style. */
    position: fixed !important;
    position-anchor: --modal-composer;
    inset-block-start: calc(anchor(top) + var(--lexxy-prompt-offset-y));
    inset-inline-start: calc(anchor(left) + var(--lexxy-prompt-offset-x));

    &[data-clipped-at-bottom] {
      inset-block-start: auto;
      inset-block-end: calc(anchor(bottom) + var(--lexxy-prompt-offset-y));
    }

    &[data-clipped-at-right] {
      inset-inline-start: auto;
      inset-inline-end: calc(anchor(right) + 1ch);
    }
  }

  /* ——— The rhythm, both sides of the glass ——— */

  /* Enter moves one line, not one section. The gem hangs 1rem under every
     block, so a run of paragraphs reads as a stack of filed sections and
     typing feels like completing a form rather than writing. Basecamp's own
     answer for this editor (fizzy) is these two declarations: half a line of
     content margin, and none at all between consecutive paragraphs. A blank
     line then means what it says — an empty paragraph you typed on purpose.

     `p:has(+ p)` and not `p + p`: the gem sets bottom margins only, so the gap
     between two paragraphs belongs to the first of them.

     The class marks editor-authored markup on BOTH sides — the gem puts it on
     lexxy-editor, and layouts/action_text/contents/_content wraps every
     rendered body in it — so one rule governs composing and reading alike.
     A caller passing its own `class:` drops the gem's default, which is why
     rich_text_helper spells lexxy-content out. */
  .lexxy-content {
    --lexxy-content-margin: 0.5lh;

    /* Fizzy's paragraph rhythm, verbatim (its lexxy.css:161): a paragraph
       followed by another gives up its margin, so consecutive lines sit
       flush and only a heading, a list or a rule opens a gap. This is the
       behaviour we ported fizzy for, and it stands on every surface — I
       argued against it for long-form prose and was wrong to (user,
       2026-08-29). Chat goes one step further, chalet's way (messages.css). */
    p:has(+ p) {
      margin: 0;
    }
  }
}
