/*
 * Alert System — panel CSS
 * ========================
 *
 * Everything here is something Filament's own stylesheet cannot express and
 * that has a behavioural reason to exist. Colour and typography are
 * deliberately absent: those come from the panel's registered palette
 * (AdminPanelProvider) and from Filament's own faces, and the panel is
 * supposed to look like Filament.
 *
 * Why a plain stylesheet in public/ and not a Filament vite theme
 * --------------------------------------------------------------
 * `deploy/deploy.sh` runs no npm step at all - it says so, and it is right to:
 * Filament's compiled CSS and JS are committed under public/ and republished
 * by composer, so a production host needs no node. Registering
 * `->viteTheme()` would replace Filament's stylesheet with a build artefact,
 * and the first deploy that ran without `npm run build` would serve an
 * unstyled panel rather than a slightly stale one. This file is served
 * directly, cache-busted on its own mtime, and cannot fail that way.
 *
 * The consequence worth knowing: no Tailwind utility class invented in PHP
 * will ever have a rule behind it. That is not a theoretical limit - it is
 * what silently broke the incident row rails below.
 *
 * Every value resolves to one of Filament's own variables (--gray-*,
 * --danger-*, --warning-*), so the panel's colours stay the single source and
 * a change to ->colors() carries here.
 */

/* ==========================================================================
   1. The incident row rail

   IncidentsTable::recordClasses() has asked for this for a long time, in the
   form `border-l-4 border-l-danger-500`, and it drew nothing at all: those
   are Tailwind utilities, this panel's stylesheet is Filament's own build
   compiled with `source(none)`, and no rule for them was ever generated.
   Scanning a queue of fifty rows is a different job from reading one, and
   colour on the edge is the only thing that survives being scanned - so the
   intent was right and only the delivery was missing.

   Drawn as an inset bar rather than a real border so adding it shifts nothing
   in the row it marks.
   ========================================================================== */

.fi-ta-row[class*='con-state-'] > .fi-ta-cell:first-of-type {
    position: relative;
}

.fi-ta-row[class*='con-state-'] > .fi-ta-cell:first-of-type::before {
    content: '';
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    width: 3px;
    background-color: var(--con-rail, transparent);
    z-index: 1;
}

.fi-ta-row.con-state-breached {
    --con-rail: var(--danger-500);
}

.fi-ta-row.con-state-unacked {
    --con-rail: var(--warning-500);
}

/* Deliberately quiet, so it looks it - and the actions dim with the row,
   because the point of a snooze is that nobody should be reaching for them.
   
   The dimming is applied per cell rather than to the row, which looks
   identical and is not the same thing. `opacity` on an element composites its
   whole subtree, and that includes descendants taken out of flow: the row
   action dropdown renders inside the pinned cell and is only moved by
   `position: fixed`, so a row-level 0.6 came out as a menu you could read the
   table through. Cells cannot contain an open panel except the pinned one,
   which is excluded here and dimmed at its trigger instead - a sibling of the
   panel rather than its ancestor.
   
   The rail is unaffected: it is drawn on the first cell, which still dims. */
.fi-ta-row.con-state-snoozed {
    --con-rail: var(--gray-300);
}

.fi-ta-row.con-state-snoozed > .fi-ta-cell:not(:has(> .fi-ta-actions)) {
    opacity: 0.6;
}

.fi-ta-row.con-state-snoozed .fi-ta-actions > :not(.fi-dropdown),
.fi-ta-row.con-state-snoozed .fi-dropdown > .fi-dropdown-trigger {
    opacity: 0.6;
}

.dark .fi-ta-row.con-state-snoozed {
    --con-rail: var(--gray-700);
}

/* ==========================================================================
   2. Row actions, pinned to the right edge

   Some of these tables cannot be made to fit and should not be: an incident
   title is a namespaced exception class, the culprit under it is a route
   pattern with three path parameters, and on a narrow laptop there is no
   honest column set that shows those and everything else at once. The columns
   have already been trimmed to what a person triaging needs; what was left
   was that the last thing you reach for - acknowledge, snooze, assign - was
   the first thing to disappear off the right-hand edge, so acting on a row
   meant scrolling away from the row you had just read.

   Filament has no sticky-column API, so this targets its own stable class
   names: the header cell carries fi-ta-actions-header-cell, and the body cell
   is the one fi-ta-cell whose direct child is the fi-ta-actions wrapper.

   The background is explicit rather than inherited because fi-ta-row has no
   background of its own - only a hover state - so an inherited one would be
   transparent and the columns would scroll visibly underneath. The values are
   Filament's own table-surface tokens, so a theme change carries here too.
   ========================================================================== */

:where(.fi-ta-table) thead th.fi-ta-actions-header-cell,
:where(.fi-ta-table) tbody td.fi-ta-cell:has(> .fi-ta-actions) {
    position: sticky;
    right: 0;
    z-index: 5;
    background-color: var(--color-white);
}

/* The rule that makes it read as pinned rather than as a column that happens
   to be last: without an edge, text scrolling underneath looks like a
   rendering fault. */
:where(.fi-ta-table) tbody td.fi-ta-cell:has(> .fi-ta-actions)::before,
:where(.fi-ta-table) thead th.fi-ta-actions-header-cell::before {
    content: '';
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    width: 1px;
    background-color: color-mix(in oklab, var(--gray-950) 5%, transparent);
}

:is(.dark) :where(.fi-ta-table) thead th.fi-ta-actions-header-cell,
:is(.dark) :where(.fi-ta-table) tbody td.fi-ta-cell:has(> .fi-ta-actions) {
    background-color: var(--gray-900);
}

:is(.dark) :where(.fi-ta-table) tbody td.fi-ta-cell:has(> .fi-ta-actions)::before,
:is(.dark) :where(.fi-ta-table) thead th.fi-ta-actions-header-cell::before {
    background-color: color-mix(in oklab, var(--color-white) 10%, transparent);
}

/* An open row-action dropdown is rendered inside this cell - Filament's
   dropdown defaults to `teleport => false`, so the panel stays in the td - and
   `position: sticky` with a z-index makes the cell a stacking context. A child
   of a stacking context cannot paint above a sibling context, and every pinned
   cell below this one carries the same z-index 5 while coming later in the
   document. The menu therefore had the rows beneath it showing through: their
   opaque pinned cells, their own action triggers and their Ack badges painted
   straight over the open panel.
   
   Filament gives no open-state hook to hang this on - the trigger carries no
   aria-expanded, and x-float toggles an inline style no selector should be
   matching on - so the interaction is the signal instead. The panel is a
   descendant of the cell, so a pointer inside the menu still counts as
   hovering the cell, and focus stays within it while it is open.
   
   Both are listed because neither is sufficient: Safari does not focus a
   button on click, and a keyboard user never hovers. */
:where(.fi-ta-table) tbody td.fi-ta-cell:has(> .fi-ta-actions):hover,
:where(.fi-ta-table) tbody td.fi-ta-cell:has(> .fi-ta-actions):focus-within {
    z-index: 20;
}

/* Above the raised cell, because Filament's thead is sticky to the top as well:
   without this, hovering a row that has scrolled under the header would paint
   that row's pinned cell over it. */
:where(.fi-ta-table) thead th.fi-ta-actions-header-cell {
    z-index: 25;
}

/* Stacked on a phone the table is not a table at all - every cell is its own
   row - and a pinned one would sit on top of the card. */
.fi-ta-table-stacked-on-mobile td.fi-ta-cell:has(> .fi-ta-actions) {
    position: static;
}

@media (prefers-reduced-transparency: reduce) {
    :where(.fi-ta-table) tbody td.fi-ta-cell:has(> .fi-ta-actions) {
        background-color: var(--color-white);
    }
}

/* ==========================================================================
   3. Machine strings

   Columns marked `con-identifier` carry an identifier rather than a sentence:
   an exception class over a route pattern, a client over its domain. The
   difference between {order_id} and {orders_id} is a character somebody has
   to find, and a proportional face closes those gaps up and invites the eye
   to read the shape of the word instead.

   Opt-in per column, never blanket - the status column's description is prose
   ("escalated (SLA breached)"), and setting that in mono would say it is
   something to be read character by character when it is a sentence.
   ========================================================================== */

/* Mono is wider per character than the face the table is otherwise set in, so
   at the cell's own size a one-line incident title becomes a two-line one and
   every row in a list tuned to be scanned grows by a third. A step down in
   size buys that width back without truncating the title any harder than the
   column already does. */
.fi-ta-cell .fi-font-mono {
    font-size: 0.75rem;
    line-height: 1.4;
}

.con-identifier .fi-ta-text-description {
    font-family: var(--mono-font-family), ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.6875rem;
    letter-spacing: -0.01em;
    /* Held to one line, for the same reason as above. Clamped rather than
       `nowrap`, because a nowrap block inside an auto-laid-out table reports
       its full text as its minimum width and would widen the column. */
    display: -webkit-box;
    -webkit-line-clamp: 1;
    line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ==========================================================================
   4. The annunciator

   An annunciator panel in a control room is a grid of legends that stay dark
   until a condition exists, at which point one of them lights. This is that,
   in the topbar, for the three things about this platform that fail silently -
   and each of them fails looking exactly like a quiet week: a revoked Sentry
   token stops ingest, a deleted Teams channel swallows every card, an expired
   Claude login turns every analysis into an error payload with no exception
   and no failed job.

   The dashboard has said all three for a while. The reason to say them again
   here is that the dashboard is not the screen anybody is on: an operator
   spends the day in the incident list, and on that screen a dead pipeline is
   indistinguishable from a good week.

   Nothing lights while nothing is wrong, which is what makes a lit lamp worth
   looking at.
   ========================================================================== */

.con-annunciator {
    display: flex;
    align-items: stretch;
    gap: 1px;
    border-radius: var(--radius-md, 0.375rem);
    overflow: hidden;
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--gray-950) 8%, transparent);
    background-color: color-mix(in oklab, var(--gray-950) 8%, transparent);
}

.dark .con-annunciator {
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--color-white) 10%, transparent);
    background-color: color-mix(in oklab, var(--color-white) 10%, transparent);
}

.con-annunciator-legend {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.3125rem 0.5rem;
    background-color: var(--color-white);
    font-size: 0.6875rem;
    font-weight: 500;
    color: var(--gray-500);
    text-decoration: none;
    white-space: nowrap;
    transition: background-color 150ms;
}

.dark .con-annunciator-legend {
    background-color: var(--gray-900);
    color: var(--gray-400);
}

a.con-annunciator-legend:hover {
    background-color: var(--gray-50);
}

.dark a.con-annunciator-legend:hover {
    background-color: color-mix(in oklab, var(--color-white) 5%, var(--gray-900));
}

/* The lamp. Hollow while nominal - an unlit legend on a panel is not empty,
   it is the good news. */
.con-annunciator-lamp {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex: none;
    background-color: transparent;
    box-shadow: inset 0 0 0 1px var(--gray-400);
}

.con-annunciator-value {
    font-variant-numeric: tabular-nums;
    color: var(--gray-600);
}

.dark .con-annunciator-value {
    color: var(--gray-300);
}

.con-annunciator-legend[data-condition='warning'] {
    color: var(--warning-600);
}

.dark .con-annunciator-legend[data-condition='warning'] {
    color: var(--warning-400);
}

.con-annunciator-legend[data-condition='warning'] .con-annunciator-lamp {
    background-color: var(--warning-500);
    box-shadow: none;
}

.con-annunciator-legend[data-condition='danger'] {
    color: var(--danger-600);
    background-color: color-mix(in oklab, var(--danger-500) 8%, var(--color-white));
}

.dark .con-annunciator-legend[data-condition='danger'] {
    color: var(--danger-400);
    background-color: color-mix(in oklab, var(--danger-500) 15%, var(--gray-900));
}

.con-annunciator-legend[data-condition='danger'] .con-annunciator-lamp {
    background-color: var(--danger-500);
    box-shadow: none;
}

.con-annunciator-legend[data-condition='danger'] .con-annunciator-value,
.con-annunciator-legend[data-condition='warning'] .con-annunciator-value {
    color: inherit;
}

/* The one animation, and it means exactly one thing: a condition that is
   breached right now. */
@media (prefers-reduced-motion: no-preference) {
    .con-annunciator-legend[data-condition='danger'] .con-annunciator-lamp {
        animation: con-lamp 2.4s ease-in-out infinite;
    }
}

@keyframes con-lamp {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.35;
    }
}

/* Below the width where the topbar has room for the labels, the strip drops to
   the lamps alone: three dots still say how many things are wrong. */
@media (max-width: 63.9375rem) {
    .con-annunciator-legend-label,
    .con-annunciator-value {
        display: none;
    }

    .con-annunciator-legend {
        padding-inline: 0.4375rem;
    }
}

@media (forced-colors: active) {
    .con-annunciator-lamp {
        box-shadow: inset 0 0 0 1px CanvasText;
    }
}

/* ==========================================================================
   5. Chip row

   The example questions under the Ask AI box. This was `flex flex-wrap gap-2`
   and laid out as nothing at all, for the reason at the top of this file: a
   Tailwind utility written into a Blade template has no rule behind it here.
   ========================================================================== */

.con-chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/* ==========================================================================
   6. Rendered markdown

   Everything the analyser writes arrives here: a root cause, an impact, a
   suggested fix, the Ask AI answers, runbooks, write-ups. Filament renders it
   through Tailwind Typography, whose defaults are wrong for this content in
   three ways that compound into looking like unrendered markdown.

   1. Inline code gets a literal backtick before and after it - Typography's
      `code::before { content: "`" }`. Half the words in an analysis are
      symbols, so a paragraph came out peppered with backticks and read as raw
      source that had failed to parse. It had, in a sense: the backticks a
      reader saw were not the model's, they were the stylesheet's.
   2. Inline code has no background, only a different family and colour, so a
      class name and the prose around it sat on the same plane.
   3. There is no measure. In a full-width section the text ran to 1120px at
      14px - about 160 characters a line, roughly twice what an eye tracks
      without losing its place, on the longest and densest prose in the panel.

   These rules were partly present already, as a <style> block inside
   ask-console.blade.php, which fixed the background on that one page and left
   the backticks and every other page alone. Here they are once, for all of it.
   ========================================================================== */

/* Not the stylesheet's backticks. */
.fi-prose code::before,
.fi-prose code::after {
    content: none;
}

.fi-prose code {
    background-color: color-mix(in oklab, var(--gray-950) 7%, transparent);
    padding: 0.0625rem 0.25rem;
    border-radius: 4px;
    font-size: 0.875em;
    font-weight: 400;
}

.dark .fi-prose code {
    background-color: color-mix(in oklab, var(--color-white) 10%, transparent);
}

/*
 * A fenced block is already a surface of its own, so the chip treatment inside
 * one has to come back off. Worth more than a one-line comment, because the
 * obvious way to write this is wrong: `<code>` inside a `<pre>` is an inline
 * box, and an inline background is painted per line fragment, ending wherever
 * each line's text ends - so a diff came out as thirty ragged grey bands
 * rather than one block.
 *
 * The dark variant is listed explicitly and not left to inherit the reset.
 * `.dark .fi-prose code` is two classes and one element; `.fi-prose pre code`
 * is one class and two elements, and loses to it - so in dark mode the reset
 * silently did nothing at all. This is the whole failure mode of hand-written
 * overrides, and the only defence is to match the specificity of the rule
 * being undone.
 */
.fi-prose pre code,
.dark .fi-prose pre code {
    background-color: transparent;
    padding: 0;
    border-radius: 0;
    font-size: inherit;
}

/*
 * The measure.
 *
 * On the text blocks rather than on .fi-prose itself, because a fenced diff
 * and a table need every pixel the card can give them and are read by
 * scanning columns, not lines.
 *
 * break-word rather than anywhere: it lets
 * `PluginProductContract::getTotalQuantityByProductIdAndAutoSyncStockActive()`
 * break when it genuinely cannot fit a line of its own, and never otherwise -
 * a symbol chopped in half mid-line is harder to read than a ragged edge.
 */
.fi-prose p,
.fi-prose ul,
.fi-prose ol,
.fi-prose blockquote {
    max-width: 80ch;
    overflow-wrap: break-word;
    hyphens: none;
}

/* Rhythm. Typography's own margins assume an article on a page; these are
   paragraphs inside a card entry, and the first and last must not push the
   entry's own padding around. */
.fi-prose > :first-child {
    margin-top: 0;
}

.fi-prose > :last-child {
    margin-bottom: 0;
}

.fi-prose p {
    margin: 0 0 0.7rem;
    line-height: 1.65;
}

.fi-prose ul,
.fi-prose ol {
    margin: 0 0 0.7rem;
    padding-inline-start: 1.25rem;
}

.fi-prose li {
    margin: 0 0 0.25rem;
}

.fi-prose strong {
    font-weight: 600;
}

.fi-prose table {
    border-collapse: collapse;
    margin: 0 0 0.7rem;
    font-size: 0.875em;
    display: block;
    overflow-x: auto;
}

.fi-prose th,
.fi-prose td {
    border: 1px solid color-mix(in oklab, var(--gray-950) 15%, transparent);
    padding: 0.3125rem 0.625rem;
    text-align: start;
}

.dark .fi-prose th,
.dark .fi-prose td {
    border-color: color-mix(in oklab, var(--color-white) 15%, transparent);
}

.fi-prose th {
    font-weight: 600;
    background-color: color-mix(in oklab, var(--gray-950) 5%, transparent);
}

.dark .fi-prose th {
    background-color: color-mix(in oklab, var(--color-white) 6%, transparent);
}

/* ==========================================================================
   7. The analysis narrative

   Root cause, impact and suggested fix are three findings, not three fields
   with values. Given a label the same weight as "File" and "Confidence"
   beside them they read as one continuous wall, and the reader has to find
   the seams by reading. Marked in IncidentInfolist so this stays scoped to
   the narrative and does not restyle every label in the panel.
   ========================================================================== */

/*
 * Reached through :has() rather than directly.
 *
 * TextEntry::extraAttributes() puts the class on the rendered text itself -
 * the .fi-prose div - and the label is not inside that, it is a sibling of its
 * container. There is no extraEntryWrapperAttributes() to use instead, so the
 * entry is selected by the fact that it contains a marked text item.
 */
.fi-in-entry:has(.con-narrative) .fi-in-entry-label {
    font-size: 0.9375rem;
    font-weight: 600;
}

.fi-in-entry:has(.con-narrative) .fi-in-entry-content {
    margin-top: 0.25rem;
}

/* A rule under each finding rather than above it. Above, the first one would
   double up with the section header's own border; below, the third one lands
   exactly where it is wanted anyway - between the narrative and the fields
   that follow it. */
.fi-in-entry:has(.con-narrative) {
    padding-block-end: 1.125rem;
    border-block-end: 1px solid color-mix(in oklab, var(--gray-950) 8%, transparent);
}

.dark .fi-in-entry:has(.con-narrative) {
    border-block-end-color: color-mix(in oklab, var(--color-white) 8%, transparent);
}

/* ==========================================================================
   8. The suggested-fix diff

   Built line by line in IncidentInfolist::diff(), which explains why. Each
   line is block-level so its tint fills the row rather than stopping where
   the text stops, and the whole block scrolls sideways rather than wrapping:
   a wrapped diff line is a diff line you can no longer trust the shape of.
   ========================================================================== */

.con-diff {
    display: block;
    overflow-x: auto;
    padding: 0.625rem 0;
    border-radius: 8px;
    background-color: color-mix(in oklab, var(--gray-950) 4%, transparent);
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--gray-950) 8%, transparent);
    font-family: var(--mono-font-family), ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.75rem;
    line-height: 1.6;
    tab-size: 4;
}

.dark .con-diff {
    background-color: color-mix(in oklab, var(--color-white) 4%, transparent);
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--color-white) 8%, transparent);
}

.con-diff-line {
    display: block;
    white-space: pre;
    padding-inline: 0.75rem;
    /* The tint has to reach the right-hand edge of the scroller, not stop at
       the end of the longest line. */
    min-width: max-content;
}

.con-diff-add {
    background-color: color-mix(in oklab, var(--success-500) 14%, transparent);
    color: var(--success-700);
}

.dark .con-diff-add {
    background-color: color-mix(in oklab, var(--success-500) 18%, transparent);
    color: var(--success-300);
}

.con-diff-del {
    background-color: color-mix(in oklab, var(--danger-500) 12%, transparent);
    color: var(--danger-700);
}

.dark .con-diff-del {
    background-color: color-mix(in oklab, var(--danger-500) 16%, transparent);
    color: var(--danger-300);
}

/* Hunk headers and file names are scaffolding, not change. */
.con-diff-meta {
    color: var(--gray-500);
    background-color: color-mix(in oklab, var(--gray-500) 10%, transparent);
}

.con-diff-ctx {
    color: var(--gray-700);
}

.dark .con-diff-ctx {
    color: var(--gray-300);
}

/* ---------------------------------------------------------------------------
   Analysis in flight.

   An indeterminate bar, because the figure a determinate one would need does
   not exist: the analysis is a Claude subprocess reasoning over a checkout and
   it reports nothing at all until it returns. Inventing a percentage to fill
   the gap would be the one thing this panel is not allowed to do.

   Uses the panel's own custom properties rather than fixed colours, so it
   follows the theme it is dropped into. Keeping the stock Filament look is
   deliberate - see the panel provider.
   --------------------------------------------------------------------------- */
.incident-analysis-progress {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.875rem 1rem;
    border-radius: 0.75rem;
    border: 1px solid color-mix(in oklab, var(--info-500) 30%, transparent);
    background-color: color-mix(in oklab, var(--info-500) 8%, transparent);
}

.incident-analysis-progress--stalled {
    border-color: color-mix(in oklab, var(--warning-500) 35%, transparent);
    background-color: color-mix(in oklab, var(--warning-500) 10%, transparent);
}

.incident-analysis-progress__bar {
    position: relative;
    overflow: hidden;
    height: 0.25rem;
    border-radius: 9999px;
    background-color: color-mix(in oklab, var(--info-500) 20%, transparent);
}

.incident-analysis-progress--stalled .incident-analysis-progress__bar {
    background-color: color-mix(in oklab, var(--warning-500) 22%, transparent);
}

.incident-analysis-progress__bar > span {
    position: absolute;
    inset-block: 0;
    inline-size: 35%;
    border-radius: 9999px;
    background-color: var(--info-500);
    animation: incident-analysis-sweep 1.6s ease-in-out infinite;
}

.incident-analysis-progress--stalled .incident-analysis-progress__bar > span {
    background-color: var(--warning-500);
}

.incident-analysis-progress__text {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.375rem 0.5rem;
    font-size: 0.8125rem;
    line-height: 1.4;
    color: var(--gray-700);
}

.dark .incident-analysis-progress__text {
    color: var(--gray-300);
}

.incident-analysis-progress__text strong {
    font-weight: 600;
    color: var(--gray-950);
}

.dark .incident-analysis-progress__text strong {
    color: var(--gray-50);
}

.incident-analysis-progress__text code {
    padding: 0.0625rem 0.25rem;
    border-radius: 0.25rem;
    font-size: 0.75rem;
    background-color: color-mix(in oklab, var(--gray-500) 15%, transparent);
}

@keyframes incident-analysis-sweep {
    0% {
        inset-inline-start: -35%;
    }

    100% {
        inset-inline-start: 100%;
    }
}

/* A sweeping bar is motion for its own sake to anybody who has asked for less
   of it. The state stays legible without it: the wording and the elapsed time
   carry the whole message. */
@media (prefers-reduced-motion: reduce) {
    .incident-analysis-progress__bar > span {
        animation: none;
        inline-size: 100%;
        opacity: 0.55;
    }
}
