/*
 * Zammad host-layout contract for markdown-rendered articles.
 *
 * Zammad measures an article BEFORE this plugin renders it, so it sizes the
 * raw markdown — including the whole fenced ```form block that we turn into a
 * compact widget. It then collapses by writing an inline pixel `height` onto
 * .textBubble-content and revealing .textBubble-overflowContainer, stashing
 * the expanded/folded measurements in data-height and data-height-origin for
 * its .js-toggleFold handler.
 * Both reported symptoms come from that one stale measurement: AI articles
 * arrive collapsed when the rendered result never needed collapsing, and
 * unfolding one leaves a large empty gap because the restored height belongs
 * to content that no longer exists.
 *
 * The renderer-owned data attribute is stable across Zammad's deferred passes,
 * and author !important rules beat the non-important inline styles Zammad
 * writes — so this holds without an observer fighting the host application on
 * every mutation.
 *
 * Class names verified against Zammad 7.1's compiled application.js. Earlier
 * versions of this file targeted .js-toggleSeeMore / .see-more /
 * .article-see-more, none of which exist in this Zammad; they were inert.
 */
.ticket-article-item[data-nmd-rendering="true"] .textBubble-content,
.ticket-article-item[data-md-rendered="true"] .textBubble-content {
  height: auto !important;
}

/*
 * The bubble uses the whole article column once the renderer owns it (#759).
 *
 * Zammad sizes .textBubble to its CONTENT — correct for a sentence of prose,
 * wrong for a widget. The third real-browser check measured the form at 114px
 * inside a 342px column and at 310px inside a 538px one, so the product
 * table's container query never saw room for a table and drew narrow cards at
 * every width.
 *
 * The block declaring `width: 100%` (markdown.css) is half of it; a percentage
 * resolves against a parent that is itself shrink-to-fit and nothing changes.
 * This is the other half, and it is `!important` for the same reason as the
 * height rule above: Zammad writes its own sizing later in its deferred passes.
 */
.ticket-article-item[data-nmd-rendering="true"] .textBubble,
.ticket-article-item[data-md-rendered="true"] .textBubble,
.ticket-article-item[data-nmd-rendering="true"] .textBubble-content,
.ticket-article-item[data-md-rendered="true"] .textBubble-content {
  width: 100% !important;
  max-width: 100% !important;
  box-sizing: border-box !important;
}

/*
 * Anderra's article reads at ZAMMAD's size (#899, owner 2026-09-29: "match
 * the font size of zammad. overall ours are smaller … ONLY the inline zammad
 * content"). Measured on staging: Zammad's article text is 14px / 20.3px
 * (1.45); markdown.css sets 13px / 1.6 for every surface. Here — and only here,
 * this file never reaches the sidebar — the body inherits the bubble's own
 * size. Headings, code and folds size in em, so they follow.
 *
 * Dense components keep their compact 13px so a wider glyph cannot wrap a
 * table cell or push a form row onto two lines (owner: "increasing may cause
 * alignment or wrapping issues"). Their own labels, buttons and chips are
 * already fixed px and are untouched.
 */
.ticket-article-item[data-md-rendered="true"] .nmd-content,
.ticket-article-item[data-nmd-rendering="true"] .nmd-content {
  /* 1em of the bubble = Zammad's own size (the same as inherit, for a size). */
  font-size: 1em;
  line-height: 1.45;
}

.ticket-article-item[data-md-rendered="true"] .nmd-content table,
.ticket-article-item[data-md-rendered="true"] .nmd-content .nmd-prows,
.ticket-article-item[data-md-rendered="true"] .nmd-content .nmd-form,
.ticket-article-item[data-md-rendered="true"] .nmd-content .nmd-totals,
.ticket-article-item[data-md-rendered="true"] .nmd-content .nmd-picker,
.ticket-article-item[data-md-rendered="true"] .nmd-content .nmd-search,
.ticket-article-item[data-nmd-rendering="true"] .nmd-content table,
.ticket-article-item[data-nmd-rendering="true"] .nmd-content .nmd-prows,
.ticket-article-item[data-nmd-rendering="true"] .nmd-content .nmd-form,
.ticket-article-item[data-nmd-rendering="true"] .nmd-content .nmd-totals,
.ticket-article-item[data-nmd-rendering="true"] .nmd-content .nmd-picker,
.ticket-article-item[data-nmd-rendering="true"] .nmd-content .nmd-search {
  font-size: 13px;
  line-height: 1.6;
}

/* Zammad owns creation of the control. Once the article is renderer-owned it
 * has no valid collapsed state to reveal, so the control must stay hidden even
 * if a later pass removes Zammad's own `hide` class. Hiding the container also
 * removes its ::before gradient, which would otherwise fade out the last lines
 * of an article that is no longer clipped. */
.ticket-article-item[data-nmd-rendering="true"] .textBubble-overflowContainer,
.ticket-article-item[data-md-rendered="true"] .textBubble-overflowContainer {
  display: none !important;
}

/*
 * Zammad styles EVERY <label> in the agent UI as a field caption
 * (zammad.scss:2036 — `label, .checkbox.form-group label, .label`), an
 * unscoped element selector that sets text-transform: uppercase along with a
 * muted colour, a fixed 13px size, normal weight and caption letter-spacing.
 *
 * Three of our form controls wrap their CONTENT in a <label> so the whole row
 * is clickable — the correct accessible markup, and the reason the host rule
 * reaches them. text-transform inherits, so a reply candidate's SMS body was
 * rendered to the agent IN FULL CAPS, greyed out and tracked like a caption.
 *
 * Reset the typography on those wrappers only. `.nmd-form__label` is a REAL
 * field caption ("Delivery method", "Customer PO") where Zammad's treatment is
 * correct, so it is deliberately absent from this list. Layout declarations
 * (display, margin, padding, text-align) are left to the host as before: this
 * fixes how the text reads, and changes nothing about where it sits.
 *
 * Class selectors (0,1,0) already beat the host's element selector (0,0,1), so
 * no !important is needed here.
 */
.nmd-form__radio-option,
.nmd-form__checkbox-wrap,
.nmd-product-picker__label {
  text-transform: none;
  color: inherit;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: normal;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Per-article processing status marker — host placement + state binding
 * docs/article-status/CONTRACT.md §2 / §9
 *
 * The marker is a direct child of .ticket-article-item and a SIBLING of
 * .article-content. It can never live inside .textBubble-content: the renderer
 * replaces that node wholesale on every re-render, so a marker in there would
 * disappear on each repaint and flicker on each push.
 *
 * .ticket-article-item is position: relative with a 55px gutter on both sides;
 * the avatar occupies one. This mirrors the avatar's own rule — absolute in the
 * OPPOSITE gutter, flipped for an agent-side article, and falling back to
 * static flow at Zammad's phone breakpoint, where there is no gutter to sit in.
 *
 * Everything visual is keyed off data-nmd-status on the article element and
 * carries author-!important, the same contract as the rules above: Zammad
 * writes non-important inline styles and late class passes, and the data
 * attribute is the only marker that survives its deferred re-renders.
 *
 * Colour tokens come from packages/neutralaiz-markdown-core/markdown.css (the
 * single source of truth; plugins/markdown_renderer.css is its generated copy).
 * ═══════════════════════════════════════════════════════════════════════════ */
/*
 * WHICH gutter is decided by the plugin, not by this file (#753).
 *
 * It used to be declared here — right gutter for a customer article, left for
 * an agent one, "because an agent-side article puts its avatar in the right
 * gutter". In this Zammad a CUSTOMER article's avatar is on the right, so the
 * marker was placed underneath it: measured on staging ticket 90, article 279,
 * marker x=729…769 against avatar x=737…777, 32 of its 40 pixels hidden. The
 * owner saw nothing until expanding the article moved the layout.
 *
 * `paintStatusMarkerChrome` measures the avatar against the bubble on every
 * status pass and writes the free side here. A stylesheet cannot ask where the
 * avatar is; it can only assume, and the assumption is what was wrong.
 */
.ticket-article-item > .nmd-article-status {
  /* Zammad 7.1 lays a 64px .scrollPageHeader (absolute, z-index 500) over the
     top of the ticket's scroll container (.main) once it scrolls — measured
     on staging, 2026-09-28. The sticky button rests 8px below it. */
  --nmd-status-sticky-top: 72px;
  position: absolute !important;
  /* The column is as tall as its article (#899): the button travels down it
     (sticky, below) and so stays in view while any part of the article — a
     long cart — is on screen, and never leaves the article it describes.
     pointer-events: none on the column (markdown.css) keeps the gutter's
     clicks going to Zammad. */
  top: 0 !important;
  bottom: 0 !important;
  left: auto !important;
  right: 8px !important;
  /* The button (glyph + 8px) plus the same again as breathing room: 40px at
     the default size, and it follows --nmd-status-size instead of clipping a
     larger glyph. Zammad's gutter is 55px. */
  width: calc(var(--nmd-status-size, 24px) + 16px) !important;
  /* 2, not 1 (#899). Zammad's .article-content is position: relative;
     z-index: 1 across the WHOLE article width and comes after the marker in
     the DOM, so at a tie it painted over the button: on staging
     elementFromPoint at the button's centre returned .article-content and no
     hover could reach the tooltip. Still inside the article's own stacking
     context (the item is z-index 1), so it never rises above Zammad's header. */
  z-index: 2 !important;
}

.ticket-article-item > .nmd-article-status > .nmd-article-status__btn {
  position: sticky !important;
  top: var(--nmd-status-sticky-top) !important;
}

.ticket-article-item > .nmd-article-status[data-nmd-status-side="left"] {
  right: auto !important;
  left: 8px !important;
}

.ticket-article-item > .nmd-article-status[data-nmd-status-side="right"] {
  left: auto !important;
  right: 8px !important;
}

/* Below Zammad's phone breakpoint there are no gutters to sit in, so the
   marker joins the flow — where it cannot be under anything. */
@media (max-width: 767px) {
  .ticket-article-item > .nmd-article-status,
  .ticket-article-item > .nmd-article-status[data-nmd-status-side="left"],
  .ticket-article-item > .nmd-article-status[data-nmd-status-side="right"] {
    position: static !important;
    left: auto !important;
    right: auto !important;
    width: auto !important;
    justify-content: flex-start !important;
  }

  .ticket-article-item > .nmd-article-status > .nmd-article-status__btn {
    position: static !important;
  }
}

/* ── State → colour. Five glyphs for the server states (CONTRACT §2). ────
 * Grouped explicitly rather than via a derived attribute so this file states
 * the collapse in full: reading it tells you which states share a glyph.
 * Shape carries the meaning too (see the SVGs in markdown_renderer.js) —
 * colour is never the only signal. */
.ticket-article-item[data-nmd-status="received"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="queued"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="processing"] .nmd-article-status__btn {
  color: var(--nmd-status-working) !important;
}

/* `answered` is not a server state: the renderer derives it when a person or
 * Anderra posted a customer-visible reply after this message (CONTRACT §2,
 * "Answered"). Same meaning as `replied`, so the same glyph and colour. */
/* `action_done` (#899) is derived too: a rep's click on this article's form
 * went through. Same check, same colour. */
.ticket-article-item[data-nmd-status="replied"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="answered"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="action_done"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="delivered"] .nmd-article-status__btn {
  color: var(--nmd-status-done) !important;
}

/* A reply Anderra STAGED but nobody has sent. Its own token, never a tint of
 * --nmd-status-done: "we answered the customer" and "somebody still has to
 * click Send" are the one pair this marker exists to keep apart. */
.ticket-article-item[data-nmd-status="awaiting_approval"] .nmd-article-status__btn {
  color: var(--nmd-status-pending) !important;
}

.ticket-article-item[data-nmd-status="no_reply_needed"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="folded"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="ignored"] .nmd-article-status__btn {
  color: var(--nmd-status-muted) !important;
}

.ticket-article-item[data-nmd-status="failed"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="lapsed"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="interrupted"] .nmd-article-status__btn,
.ticket-article-item[data-nmd-status="undelivered"] .nmd-article-status__btn {
  color: var(--nmd-status-warning) !important;
}

/* ── Motion. Only the three in-flight states move (CONTRACT §2). ──────────
 * The working glyph is a broken ring — a spinner's shape — and it now SPINS.
 * It used to only pulse its opacity, so the owner saw a spinner that "doesnt
 * spin": the animation was running exactly as written, and what was written
 * was the wrong motion for the shape. Rotation is on the <svg>'s own CSS box
 * (an HTML-context box, so its transform-origin is its centre); nothing inside
 * the SVG is transformed, which is where rotations usually go wrong.
 *
 * The keyframes are markdown.css's `nmd-spin` — the ONE spin (#899), which
 * the Zammad page loads first (markdown_renderer.css). A copy here would be
 * the second definition a rename silently breaks. */
.ticket-article-item[data-nmd-status="received"] .nmd-article-status__glyph,
.ticket-article-item[data-nmd-status="queued"] .nmd-article-status__glyph,
.ticket-article-item[data-nmd-status="processing"] .nmd-article-status__glyph {
  /* Turn about the glyph's own centre (#899, 1.18.1): fill-box makes the
     origin the drawing's box, so the ring never wobbles off-axis. */
  transform-box: fill-box !important;
  transform-origin: center !important;
  animation: nmd-spin 0.9s linear infinite !important;
}

/* Reduced motion SLOWS the spinner; it never freezes it (owner, 2026-09-27).
   A frozen broken ring reads as "stuck", which is the one thing a working
   marker must never say. One turn every 2.5 s is slow enough not to pull the eye
   and still unmistakably moving. Only the duration changes: the keyframes and
   the in-flight selectors above stay the one definition of what spins. The
   tooltip's loading spinner in markdown.css is a different control and keeps
   its own rule. */
@media (prefers-reduced-motion: reduce) {
  .ticket-article-item[data-nmd-status="received"] .nmd-article-status__glyph,
  .ticket-article-item[data-nmd-status="queued"] .nmd-article-status__glyph,
  .ticket-article-item[data-nmd-status="processing"] .nmd-article-status__glyph {
    animation-duration: 2.5s !important;
    opacity: 1 !important;
  }
}

/*
 * The control family is themed as ONE family (#759).
 *
 * Zammad's agent UI styles bare `input` elements with rules that outweigh a
 * single class, and it styles `select` and `textarea` differently again. The
 * third real-browser check found the result: in light Zammad the text inputs
 * stayed Zammad-white while the selects and textareas took the article's
 * tokens, inside the same form. A control must not be themed by which tag it
 * happens to be.
 *
 * So the host contract raises all three together, with the SAME declarations
 * markdown.css already makes — the tokens, never a literal, so whatever theme
 * the article declares is what every control in it wears. `!important` for the
 * same reason as every other rule in this file: it exists to outweigh the host.
 */
.ticket-article-item[data-md-rendered="true"] input.nmd-form__input,
.ticket-article-item[data-md-rendered="true"] select.nmd-form__input,
.ticket-article-item[data-md-rendered="true"] textarea.nmd-form__input {
  background: var(--nmd-bg, #0f0f23) !important;
  color: var(--nmd-text, #c8c8d0) !important;
  border-color: var(--nmd-border, #2a2a4a) !important;
}

/* ── A turn that did not finish (stuck spinners, 2026-09-29) ────────────────
 * The customer's latest message, when its turn failed / stalled / was cut by
 * a restart and nobody answered it, carries the kit's Get a fresh draft
 * button under the bubble — in the bubble's own column (55px gutters, the
 * avatar's and the marker's), never over the timeline. */
.ticket-article-item > .nmd-status-retry {
  display: flex !important;
  justify-content: flex-start !important;
  margin: 4px 55px 0 !important;
}

@media (max-width: 767px) {
  .ticket-article-item > .nmd-status-retry {
    margin: 4px 0 0 !important;
  }
}
