/* ASR Mobile Demo portal styles. Hand-rolled, no framework, no web fonts, no external assets. The
   sandbox echoes the Eleos App Manager layout grammar (dark navbar, thin accent strip, one centered
   white content card with a panel-header title) rebranded to ASR: an ASR-navy navbar and ASR Navy
   primary with Eleos Orange kept as the accent, over a warm paper canvas. */

:root {
    /* Brand. */
    --eleos-orange: #F0741E; /* Accent placeholder pending a live App Manager sample; the named craft knob. */
    --navbar-bg: #101a3a; /* Deep ASR navy. */
    --navbar-ink: #FFFFFF;
    --navbar-ink-muted: #9AA4C4;
    --navbar-hover: #1b2a5c;
    --navbar-surface-hover: rgba(255, 255, 255, 0.16); /* Studio's --nav-surface-hover, hand-copied.
        This sheet's nav tokens carry the --navbar- prefix where Studio's carry --nav-, so the name
        diverges by that prefix alone: the -hover suffix is kept because Studio declares a --nav-surface
        at 0.08 beside this one, and a name that dropped the suffix would point at that other token. */

    /* Canvas and surfaces: warm paper, matching Studio's --paper and --line. */
    --bg: #F6F5F0;
    --surface: #ffffff;
    --panel-header-bg: #F6F5F0;
    --border: #E5E5DE;
    --card-shadow: 0 1px 2px rgba(22, 24, 31, 0.06), 0 10px 26px rgba(22, 24, 31, 0.09); /* Studio's
        own --card-shadow, carried here as a token rather than at the one rule that reads it: the value
        is copied by hand across two sheets that share no cascade, so a single declaration is the only
        place a future reconciliation has to look. StaticAssetTests pins it against studio.css. */

    /* The funnel's field edge, which is not one of the pairs above and is not borrowed: Studio
       paints its own resting field border with --line, the same warm tone as --border, which reads
       as no edge at all against the funnel's white card. A field's boundary is a UI component
       boundary, which WCAG 1.4.11 asks to reach 3:1 against the surface behind it; Studio's nearest
       tone, --accent-wash-line, reaches 1.58:1 because it edges a washed panel rather than a
       control. This value is 3.61:1 against --surface, and StaticAssetTests pins that floor rather
       than a literal, so a later tone change cannot quietly drop back under the bar. */
    --field-line: #7C86A6;

    /* Ink, matching Studio's --ink and --muted. */
    --ink: #16181F;
    --muted: #62667A;

    /* Interactive primary maps to ASR navy. */
    --accent: #101a3a;
    --accent-ink: #ffffff;

    /* Shape and rhythm: the control radius matches Studio's own control value (studio.css's
       .topnav > nav a, .account-menu-link, and others, all 8px). The three card-level surfaces
       (.content-card, .card, .modal) carry Studio's larger --r (12px) directly, not through this
       token, because Studio itself splits the two rather than using one radius everywhere. */
    --radius: 8px;
    --gap: 1rem;
    --shell-width: 1100px;

    /* Type scale. */
    --fs-sm: 0.85rem;
    --fs-base: 1rem;
    --fs-lg: 1.25rem;
    --fs-xl: 1.6rem;
    --serif: "Charter", "Georgia", "Times New Roman", serif; /* Studio's own --serif, hand-copied
        under the same name, matching this sheet's convention for hand-copied semantic Studio values
        (--accent, --card-shadow, --ink, --muted). */
}

* {
    box-sizing: border-box;
}

html {
    font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    font-size: 16px;
    line-height: 1.5;
    color: var(--ink);
    background: var(--bg);
}

/* Full-viewport column so .content can grow: the signed-out funnel centers its card in the grown
   space (see .content:has(.funnel)); every other surface keeps its top-anchored flow and simply
   gains canvas below. 100dvh rides after 100vh as the mobile-correct value where supported. */
body {
    margin: 0;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
}

a {
    color: var(--accent);
}

/* Navbar: dark ASR navy, full width. */
.navbar {
    background: var(--navbar-bg);
    color: var(--navbar-ink);
}

.navbar-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.5rem;
    max-width: var(--shell-width);
    margin: 0 auto;
    padding: 0.5rem 1rem;
}

/* The brand slot carries two renderings. Pages under _Layout render the star-and-wordmark block
   below; Error and NotFound render the black-on-white ASR wordmark image through .navbar-logo. The
   white chip that lets that image read against the navy bar is declared on the image rather than on
   this slot, so the block sits flush while the image keeps its chip. The colour and the decoration
   reset are what the block needs and the image never did: the bare a rule above paints link text
   --accent, which is the navbar's own navy, and an anchor whose children are text paints the user
   agent's underline across all of them. The radius is here rather than on the image so the focus ring
   follows the same corner for both renderings. */
.navbar-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    color: var(--navbar-ink);
    text-decoration: none;
    border-radius: var(--radius);
}

.navbar-logo {
    height: 26px;
    width: auto;
    display: block;
    box-sizing: content-box; /* The chip's padding is declared on this element, and the global
        box-sizing: border-box would absorb it into the 26px, leaving an 18px image. */
    background: #ffffff;
    padding: 4px 10px;
    border-radius: var(--radius);
}

/* The Studio brand block (star tile, serif name, small caps "by ASR"), ported from studio.css's
   .brand, .brand .star, .brand b and .brand small. The scope is .navbar-brand rather than a bare
   .brand because the two rules for b and small are element selectors, which loose in this sheet would
   reach every bold and every small in the portal. Studio also hides its .brand-text on a narrow rung;
   that rule is not ported, because this navbar wraps rather than collapsing and has no such rung. */
.navbar-brand .star {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background: var(--navbar-surface-hover);
    color: var(--navbar-ink);
    display: grid;
    place-items: center;
    font-size: 14px;
}

.navbar-brand b {
    font-family: var(--serif);
    font-size: 16px;
    letter-spacing: 0.1px;
}

.navbar-brand small {
    color: var(--navbar-ink-muted);
    font-size: 10.5px;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    display: block;
    margin-top: -3px;
}

/* Nav groups: a small uppercase label over its cluster of items. */
.navbar-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    flex: 1 1 auto;
}

.nav-group {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.nav-group-label {
    font-size: 0.7rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--navbar-ink-muted);
}

.nav-group-items {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
}

.nav-item {
    padding: 0.25rem 0.55rem;
    border-radius: var(--radius);
    color: var(--navbar-ink);
    text-decoration: none;
    font-size: var(--fs-sm);
    white-space: nowrap;
}

a.nav-item:hover {
    background: var(--navbar-hover);
}

.nav-item.active {
    background: var(--navbar-hover);
}

/* Account and logout, pinned right. */
.navbar-account {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-left: auto;
}

.account-name {
    color: var(--navbar-ink);
    font-size: var(--fs-sm);
}

.logout-form {
    margin: 0;
}

.logout-btn {
    font: inherit;
    font-size: var(--fs-sm);
    cursor: pointer;
    padding: 0.25rem 0.6rem;
    border-radius: var(--radius);
    border: 1px solid var(--navbar-ink-muted);
    background: transparent;
    color: var(--navbar-ink);
}

.logout-btn:hover {
    background: var(--navbar-hover);
}

/* Thin Eleos-orange accent strip under the navbar. */
.accent-strip {
    height: 3px;
    background: var(--eleos-orange);
}

/* Environment badge: orange, floats top-right just below the navbar. */
.env-bar {
    max-width: var(--shell-width);
    margin: 0 auto;
    padding: 0.5rem 1rem 0;
    display: flex;
    justify-content: flex-end;
}

.env-badge {
    background: var(--eleos-orange);
    color: #ffffff;
    font-size: var(--fs-sm);
    font-weight: 600;
    padding: 0.2rem 0.7rem;
    border-radius: 999px;
}

/* Content shell: light-gray canvas, one centered white content card per surface. */
.content {
    max-width: var(--shell-width);
    margin: 0 auto;
    padding: 1rem 1rem 3rem;
    flex: 1 0 auto;
    width: 100%;
}

/* The signed-out funnel pages (Index, Signup) sit on a full-bleed navy field, the same navy the navbar
   carries, so the chrome above and the field below belong to one navy family rather than the page
   reading as a white sheet with a navy strip on top. The accent strip still rules between them, so
   this is a shared colour, not one continuous band. That family is what gives the funnel the Studio's
   own proportion, navy chrome framing a contained light canvas, rather than the flat white page the
   funnel used to be. Breaking the shell's own max-width/margin-auto here, and reapplying centering to
   just the card below, is what lets the navy reach the viewport edges instead of stopping at the
   1100px shell width every other page keeps. The bottom padding drops to match the top: the base
   .content rule ends 3rem, and auto-margin centring resolves inside the content box, so an uneven pair
   would seat the card that much above true centre. */
.content:has(.funnel) {
    display: flex;
    flex-direction: column;
    max-width: none;
    margin: 0;
    padding-bottom: 1rem;
    background: var(--navbar-bg);
}

/* The card no longer grows to fill: it is sized and centered instead, echoing the Studio's own
   contained canvas over its navy chrome. Auto margins on all four sides rather than flex centering:
   .content is flex: 1 0 auto inside a viewport-height body, so it never shrinks under its content and
   the card is centred in grown space; auto margins are the centring mechanism, and they degrade to a
   top-anchored scroll rather than a clip should a later change ever cap that height. Padding is
   Studio's own canvas breathing room (studio.css's .canvas, 34px) rounded to this sheet's rem scale,
   trimmed on the sides because the funnel holds one column rather than Studio's task directory. */
.content:has(.funnel) > .content-card {
    flex: 0 0 auto;
    margin: auto;
    max-width: 32rem;
    width: 100%;
    padding: 2.25rem 2rem;
    box-shadow: var(--card-shadow);
}

.content-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px; /* Studio's --r, direct: card-level surfaces sit above the control token. */
    overflow: hidden;
    padding: 0 1.25rem 1.25rem;
}

/* Panel header bar: the card's leading h1 bleeds to the card edges as the surface title strip. */
.content-card > h1:first-child {
    margin: 0 -1.25rem 1.25rem;
    padding: 0.9rem 1.25rem;
    font-size: var(--fs-lg);
    background: var(--panel-header-bg);
    border-bottom: 1px solid var(--border);
}

h1 {
    font-size: var(--fs-xl);
    margin: 0 0 1rem;
}

h2 {
    font-size: var(--fs-lg);
    margin: 0 0 0.75rem;
}

.subtle {
    color: var(--muted);
}

/* The signed-out funnel (Index, Signup): a centered welcome column rather than the classic surface's
   left-aligned panel header. The wrapping div keeps its own h1 from ever being .content-card's first
   child, so the panel-header-strip rule above stops matching it; that no longer means .funnel needs to
   supply its own top padding, though, since the funnel-scoped .content-card override above already
   gives the card generous padding on every side instead of the zero-top convention plain content cards
   use. The centered heading, the muted lede, and the card's shadow weight (carried by that same
   funnel-scoped .content-card override) echo the weight of Studio's own orientation canvas
   (studio.css's .orient-headline, .card), narrower here because the funnel holds one field rather than
   Studio's task directory. The column width is not declared here either: the funnel-scoped
   .content-card caps it once, and a second cap on this rule would only ever be the looser of the two,
   reading as though it governed a width it could not reach. Colour and type come from this sheet's
   own tokens, the heading's serif face included (--serif, in :root above). */
.funnel {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
}

.funnel-title {
    margin: 0;
    font-family: var(--serif); /* Studio's own welcome type; the token is defined in :root above,
        hand-copied from studio.css's --serif. */
    font-size: var(--fs-xl);
    font-weight: 600;
    letter-spacing: -0.01em;
    text-align: center;
}

.funnel-lede {
    margin: 0;
    color: var(--muted);
    text-align: center;
}

.funnel-actions {
    display: flex;
    justify-content: center;
}

/* Buttons. */
button,
.btn {
    font: inherit;
    cursor: pointer;
    padding: 0.5rem 0.9rem;
    border-radius: var(--radius);
    border: 1px solid var(--accent);
    background: var(--accent);
    color: var(--accent-ink);
    text-decoration: none;
    display: inline-block;
}

button.secondary,
.btn.secondary {
    background: var(--surface);
    color: var(--accent);
}

:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 1px;
}

/* Layout primitives shared across the portal's forms. */
.stacked {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px; /* Studio's --r, direct: see .content-card. */
    padding: 1rem 1.25rem;
    margin: 0 0 1.5rem;
}

/* Inside the funnel, .card stops being a card. Both funnel pages wrap their sign-in content in one
   (Index's prompt-and-button block, Signup's form), and the funnel's own .content-card now carries the
   surface, the edge and the lift for the whole column, so leaving .card's white fill and border in
   place would paint a bordered white box on a white card: two lifts stacked with no canvas tone
   between them, which is the one composition studio.css's own :root notes say does not read. The
   mockup this section was built to shows a single card on the navy field, so the inner box flattens to
   a plain content group and the funnel's gap supplies the separation it used to get from padding. The
   bottom margin goes for the same reason it always did: flex items never collapse their margins, so
   .card's shared margin-bottom would otherwise add onto .content-card's own bottom padding. Scoped
   here rather than on bare .card, which also renders as kernel/diff-modal.js's outcome-success panel
   and news-editor.js's news-history block; neither is a funnel. */
.funnel .card {
    background: transparent;
    border: 0;
    box-shadow: none;
    padding: 0;
    margin-bottom: 0;
}

.field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.field label {
    font-weight: 600;
}

.field input,
.field select,
.field textarea {
    font: inherit;
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
}

/* Inside the funnel a field's edges carry more weight than the rest of the sheet gives them: the
   funnel is a single control on one card, where a --border edge against the card's white reads as
   no edge at all. The resting border takes --field-line, and focus takes the geometry of Studio's
   own .insp-input:focus rule, a 2px ring flush against the border with the border going navy under
   it, rather than the sheet-wide :focus-visible above, whose wider offset ring reads as a block on
   a control this size. Both are scoped to the funnel, because every other .field in this sheet
   wraps a sandbox inspector control: those sit beside .insp-input siblings that keep --border, and
   an unscoped focus rule of this specificity outranks .insp-input.invalid and would paint an
   invalid field's red edge navy for exactly as long as someone holds focus to fix it.
   StaticAssetTests pins the focus geometry against studio.css. */
.funnel .field input,
.funnel .field select,
.funnel .field textarea {
    border-color: var(--field-line);
}

.funnel .field input:focus-visible,
.funnel .field select:focus-visible,
.funnel .field textarea:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 0;
    border-color: var(--accent);
}

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
}

.validation {
    color: #b3261e;
}

/* Sandbox: the form list. */
.component-list {
    list-style: none;
    padding: 0;
    margin: 0 0 1.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden; /* Clips its square-cornered row fills to the rounded container, as .content-card and .modal already do. */
}

.component-list li {
    border-bottom: 1px solid var(--border);
}

.component-list li:last-child {
    border-bottom: 0;
}

.component-list a {
    display: block;
    padding: 0.6rem 0.9rem;
    text-decoration: none;
}

.component-list a:hover {
    background: var(--bg);
}

/* Sandbox: a friendly server notice, not a page error. */
.notice {
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 4px solid var(--eleos-orange);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    margin: 0 0 1.5rem;
}

/* Sandbox: the JSON editor. */
.json-editor {
    width: 100%;
    font-family: ui-monospace, "Cascadia Code", "Consolas", monospace;
    font-size: 0.9rem;
    white-space: pre;
    overflow: auto;
}

.template-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
}

/* Sandbox: the read-only key shown when editing an existing form, whose key is fixed. */
.key-static {
    font-family: ui-monospace, "Cascadia Code", "Consolas", monospace;
    color: var(--muted);
}

.key-static strong {
    color: var(--ink);
}

/* Sandbox: the two-phase diff. */
.diff-counts {
    display: flex;
    gap: 1rem;
    font-size: 0.9rem;
}

.diff-add {
    color: #1a7f37;
}

.diff-remove {
    color: #b3261e;
}

.diff-change {
    color: #9a6700;
}

.diff {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

.diff th,
.diff td {
    text-align: left;
    vertical-align: top;
    padding: 0.4rem 0.6rem;
    border-bottom: 1px solid var(--border);
}

.diff code {
    word-break: break-word;
}

.diff-row.diff-added {
    background: rgba(26, 127, 55, 0.08);
}

.diff-row.diff-removed {
    background: rgba(179, 38, 30, 0.08);
}

.diff-row.diff-changed {
    background: rgba(154, 103, 0, 0.08);
}

.outcome-success {
    border-left: 4px solid #1a7f37;
}

/* Chat pane: the transcript and its message bubbles. */
.chat-log {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin: 0 0 1rem;
    max-height: 60vh;
    overflow-y: auto;
}

.chat-bubble {
    padding: 0.55rem 0.8rem;
    border-radius: var(--radius);
    max-width: 80%;
    white-space: pre-wrap;
    word-wrap: break-word;
}

.chat-user {
    align-self: flex-end;
    background: var(--accent);
    color: var(--accent-ink);
}

.chat-assistant {
    align-self: flex-start;
    background: var(--panel-header-bg);
    border: 1px solid var(--border);
    color: var(--ink);
}

.chat-error {
    background: rgba(179, 38, 30, 0.1);
    color: #b3261e;
}

/* The retry control a retryable notice appends below itself: a small action attached to that one
   notice, not a full-width block. align-self holds it to its own width against the log's column flow,
   which otherwise stretches an unconstrained child to fill it. */
.chat-retry {
    align-self: flex-start;
    font: inherit;
    font-size: var(--fs-sm);
    color: #b3261e;
    background: none;
    border: 1px solid #b3261e;
    border-radius: 999px;
    padding: 0.4rem 0.9rem;
    cursor: pointer;
}

.chat-retry:hover {
    background: #b3261e;
    color: #ffffff;
}

/* The attention pulse, on the one surface that has no Studio stylesheet behind it. kernel/attention-marks.js
   owns the class name and the duration, studio.css carries the Studio host's copy in Studio's tokens, and
   this sheet is the only one the classic chat page loads: without this rule a pane refusing a click there
   would add a class that draws nothing, which is the silence the pulse exists to break. The ring is the
   parity sheet's own accent, since Studio's selection token is not defined here.
   tests/js/attention.test.mjs pins both copies against the module's values. */
@keyframes attn-pulse {
    0% {
        box-shadow: 0 0 0 2px var(--accent);
    }

    70% {
        box-shadow: 0 0 0 9px color-mix(in srgb, var(--accent) 0%, transparent);
    }

    100% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent);
    }
}

.attn-pulse {
    animation: attn-pulse 1.5s ease-out 1;
}

/* Reduced motion keeps the ring and loses the swell: it appears at full strength, holds, and fades where
   the default treatment expands outward. Still one run, ending on its own. */
@keyframes attn-fade {
    0%, 55% {
        box-shadow: 0 0 0 3px var(--accent);
    }

    100% {
        box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 0%, transparent);
    }
}

/* A bubble with nothing in it says nothing. The assistant's is created the moment its first text delta
   arrives and holds nothing for the instant between, and the typing indicator is what stands in for the
   reply until then. */
.chat-bubble:empty {
    display: none;
}

/* The typing indicator: three dots at the foot of the log while a turn is in flight and the assistant has
   said nothing yet. */
.chat-dots {
    align-self: flex-start;
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.2rem 0.35rem;
}

.chat-dots i {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--muted);
    animation: chat-dot-bump 1.2s infinite;
}

.chat-dots i:nth-child(2) {
    animation-delay: 0.15s;
}

.chat-dots i:nth-child(3) {
    animation-delay: 0.3s;
}

@keyframes chat-dot-bump {
    0%, 60%, 100% {
        transform: none;
        opacity: 0.5;
    }

    30% {
        transform: translateY(-3px);
        opacity: 1;
    }
}

/* The movement is decoration: the dots read as a waiting state standing still, so a visitor who asks for
   reduced motion gets the indicator without it. The pulse ring keeps the same trade under this query:
   .attn-pulse swaps its swelling animation for attn-fade, the still ring defined above. */
@media (prefers-reduced-motion: reduce) {
    .chat-dots i {
        animation: none;
    }

    .attn-pulse {
        animation: attn-fade 1.5s linear 1;
    }
}

/* The activity narrative: one quiet line per server-side tool call, sitting in the log where the stream
   produced it. The state attribute carries the mark's colour; the mark glyph itself is text content, and
   a running line shows a ring instead. */
.chat-activity {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.8rem;
    color: var(--muted);
}

.chat-activity-mark {
    flex: none;
    width: 0.7rem;
    text-align: center;
    font-weight: 700;
}

.chat-activity[data-state="running"] .chat-activity-mark {
    width: 0.55rem;
    height: 0.55rem;
    border: 2px solid var(--accent);
    border-top-color: transparent;
    border-radius: 50%;
}

.chat-activity[data-state="done"] .chat-activity-mark {
    color: #1a7f37;
}

.chat-activity[data-state="failed"] .chat-activity-mark {
    color: #b3261e;
}

.chat-activity-text {
    word-wrap: break-word;
}

.chat-form {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.chat-form textarea {
    font: inherit;
    padding: 0.55rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    resize: vertical;
}

.chat-form button {
    align-self: flex-start;
}

/* Driver-device pane: the start control, the warming screen, the friendly notice, and the web-player
   frame. The pane's module toggles device-hidden as the stream progresses, and a settled pane shows two of the
   four together: the notice carries what happened and the start card under it carries the one press that
   answers it. The stop control sits outside the stage, in the row under it, because a held device
   outlives whichever of the four is showing.
   The hide switch itself sits at the foot of this block; the comment there says why it must. */

.device-stage {
    margin: 0;
}

/* The pane's bottom row, under the stage: the way on at the near edge and the stop control at the far one.
   Neither control is worth a line of its own and both are absent from most of the pane's life, so they share
   one row. Nothing here sets `display` on either control, which leaves the hide switch at the foot of this
   block free to reach them. */
.device-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin: 1rem 0 0;
}

.device-onward {
    margin: 0;
}

/* The far edge of that row, held there by the auto margin so a row carrying the stop control alone still
   reads as the way out rather than the way on. The button's quiet treatment is the shared .btn.secondary
   outline. */
.device-stop-card {
    margin: 0 0 0 auto;
}

.device-warming {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--panel-header-bg);
    padding: 1.25rem;
    text-align: center;
}

.device-warming-title {
    font-size: var(--fs-lg);
    margin: 0 0 0.4rem;
}

.device-warming p {
    margin: 0.3rem 0;
}

/* The boot's progress, as far as an honest clock can carry it. The track is the panel's own hairline tone
   filled in, so the empty part reads as part of the card rather than as a gap in it, and the fill takes the
   page's primary over that paper ground. The fill's resting zero lives here rather than in the markup because
   the page's Content-Security-Policy drops an inline style attribute, and the width is a CSSOM assignment from
   the pane module (device-pane.js). Overflow keeps the fill's own corners inside the track's radius, and the
   transition eases each of the stream's steps rather than jumping it. The steps are five seconds apart
   (DemoPortal:DeviceProgressSeconds, defaulting to five in Pages/Sandbox/Device.cshtml.cs), so the bar
   advances about five and a half percent at a time and rests between them: the easing softens the step, it
   does not make the motion continuous, and a duration picked as though frames arrived every second would be
   tuning against a cadence the server does not use. */
.device-progress {
    height: 0.35rem;
    margin: 0.6rem 0 0.5rem;
    border-radius: var(--radius);
    background: var(--border);
    overflow: hidden;
}

.device-progress-fill {
    display: block;
    width: 0;
    height: 100%;
    border-radius: inherit;
    background: var(--accent);
    transition: width 0.4s ease-out;
}

/* The easing is decoration: the bar reports the same elapsed clock with or without it, so a visitor who asks
   for reduced motion gets the position and not the slide. Same trade the waiting indicator above takes. */
@media (prefers-reduced-motion: reduce) {
    .device-progress-fill {
        transition: none;
    }
}

/* A portrait phone viewport: the aspect ratio holds the frame's shape at any width, and the cap keeps a
   wide desktop from stretching it past a plausible handset. */
.device-player {
    display: flex;
    justify-content: center;
}

.device-frame {
    position: relative;
    width: 100%;
    max-width: 420px;
    aspect-ratio: 9 / 19.5;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--navbar-bg);
}

/* The live chip in the frame's top corner, over the stream: the pane's one statement that this is a real
   device rather than a picture of one. It exists only while a renderer is mounted, so its presence is the
   streaming state rather than a decoration the other pane states could carry. */
.device-live {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    background: rgba(22, 24, 31, 0.72);
    color: #fff;
    font-size: 0.65rem;
    letter-spacing: 0.05em;
    line-height: 1.5;
}

.device-live::before {
    content: "";
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #1a7f37;
}

/* The back control in the frame's bottom corner, over the stream: the device's own back key, which the
   streamed phone has no hardware key for and the driver app draws no affordance for on some of its
   screens. It exists only while a renderer is mounted, as the chip does. The bottom corner is where the
   streamed screen draws that affordance in both of Android's navigation styles, so what it covers there
   it replaces, and it takes the chip's own ground and scale rather than the page's action fill, because
   what is behind it is a video. */
.device-back {
    position: absolute;
    bottom: 10px;
    left: 10px;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.25rem 0.6rem;
    border: 1px solid transparent;
    background: rgba(22, 24, 31, 0.72);
    color: #fff;
    font-size: 0.75rem;
}

/* The pane's hide switch, and the cascade constraint that decides where it may sit. Several elements the pane
   toggles this class onto are matched above by a single-class `.device-*` rule setting `display`, and against
   any such rule this declaration carries equal specificity, so source order alone decides the winner: placed
   before them it would lose to every one of them and hide nothing, which is the whole reason it sits at the
   foot of this block rather than beside the pane it belongs to. Any rule setting `display` on an element that
   can carry this class goes ABOVE this line, never below it. An element with no `display` of its own is hidden
   from anywhere, so the constraint binds on the rules rather than on the element list. */
.device-hidden {
    display: none;
}

/* Editor kernel: the master-detail grid, one column for the item list and one for the inspector. */
.editor-grid {
    display: grid;
    grid-template-columns: minmax(240px, 1fr) minmax(280px, 1.4fr);
    gap: 1.25rem;
    align-items: start;
}

@media (max-width: 720px) {
    .editor-grid {
        grid-template-columns: 1fr;
    }
}

.editor-master {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.add-item {
    align-self: flex-start;
}

/* The reorderable item list: grip handle, selectable body button, and Move up/down controls. Rows
   render as spaced cards (own border, own background) rather than a single bordered box with hairline
   dividers, matching the App Manager list rhythm. */
.reorder-list {
    list-style: none;
    margin: 0;
    padding: 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
}

.reorder-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.7rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

/* Selection is a light orange tint plus an orange left border, never a saturated fill: a fill would
   force every child label to recolor for contrast, and the live editors render label/code/type chrome
   this rule never reaches (.wy-field-label, .code-badge, .wy-type from forms-editor.js and siblings).
   The tint keeps default text ink readable with no per-child overrides. */
.reorder-row.selected {
    background: color-mix(in srgb, var(--eleos-orange) 12%, var(--surface));
    border-left: 3px solid var(--eleos-orange);
}

.reorder-grip {
    cursor: grab;
    color: var(--muted);
    user-select: none;
}

/* The row body reads as text, not a button chrome, but stays a real button for keyboard selection.
   min-width: 0 lets it shrink below its content's natural width; without it a long field label forces
   the whole row wider than the card, pushing the trailing code chip into a cramped sliver where it
   wraps at its own hyphens instead of staying on one line. */
.reorder-body {
    flex: 1 1 auto;
    min-width: 0;
    text-align: left;
    background: transparent;
    border: 0;
    padding: 0.15rem 0.25rem;
    color: inherit;
    cursor: pointer;
}

.field-row {
    display: flex;
    flex-direction: column;
}

.field-code {
    font-size: var(--fs-sm);
    color: var(--muted);
}

.reorder-moves {
    display: flex;
    gap: 0.2rem;
}

.reorder-move {
    padding: 0.1rem 0.45rem;
    background: var(--surface);
    color: var(--accent);
    border: 1px solid var(--border);
}

.reorder-move:disabled {
    color: var(--muted);
    cursor: default;
    opacity: 0.5;
}

.reorder-empty {
    padding: 0.6rem 0.5rem;
}

/* The inspector: the selected item's property sheet in the right rail. */
.editor-detail {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.detail-header {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.code-badge {
    align-self: flex-start;
    font-family: ui-monospace, "Cascadia Code", "Consolas", monospace;
    background: var(--panel-header-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.15rem 0.5rem;
}

.inspector {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.insp-input {
    font: inherit;
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
}

/* Inline validation marks an invalid field with a red border, never an inline style. */
.insp-input.invalid {
    border-color: #b3261e;
}

.insp-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 600;
}

.insp-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

.insp-empty {
    padding: 0.6rem 0;
}

button.danger,
.btn.danger {
    background: var(--surface);
    color: #b3261e;
    border-color: #b3261e;
}

/* The action rail: Save and Discard, disabled until dirty, plus a visible dirty flag. */
.action-rail {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}

.secondary-rail {
    border-top: 0;
    padding-top: 0;
    margin-top: 0.5rem;
}

button:disabled {
    cursor: default;
    opacity: 0.5;
}

.dirty-flag {
    font-size: var(--fs-sm);
    color: var(--muted);
}

.dirty-flag.on {
    color: var(--eleos-orange);
    font-weight: 600;
}

/* The vector runner status the walk reads. */
.vector-run {
    font-size: var(--fs-sm);
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    margin: 0 0 1.25rem;
}

.vector-run.pass {
    border-left: 4px solid #1a7f37;
}

.vector-run.fail {
    border-left: 4px solid #b3261e;
    color: #b3261e;
}

/* The Save -> preview -> confirm modal: the centerpiece surface. */
.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(16, 26, 58, 0.55);
    display: flex;
    align-items: center;
    align-items: safe center;
    justify-content: center;
    padding: 3rem 1rem;
    overflow-y: auto;
    z-index: 50;
}

.modal {
    background: var(--surface);
    border-radius: 12px; /* Studio's --r, direct: see .content-card. */
    border: 1px solid var(--border);
    width: min(760px, 100%);
    box-shadow: 0 12px 40px rgba(16, 26, 58, 0.3);
    overflow: hidden;
}

.modal-header {
    background: var(--panel-header-bg);
    border-bottom: 1px solid var(--border);
    padding: 0.9rem 1.25rem;
}

.modal-header h2 {
    margin: 0;
    font-size: var(--fs-lg);
}

.modal-body {
    padding: 1.25rem;
}

.modal-actions {
    display: flex;
    gap: 0.6rem;
    padding: 0 1.25rem 1.25rem;
}

.diff-view {
    margin-top: 0.5rem;
}

.diff-none {
    color: var(--muted);
    font-style: italic;
}

/* The humanized lead the modal reads first: a plain, readable sentence, not the muted helper line. */
.diff-lead {
    margin: 0 0 0.75rem;
    font-size: var(--fs-base);
    color: var(--ink);
}

/* The technical detail disclosure: the summary reads as a small muted affordance like the sitewide
   links; the disclosed content sits just below. */
.diff-tech > summary {
    cursor: pointer;
    color: var(--muted);
    font-size: 0.85rem;
}

.diff-tech > summary:hover {
    color: var(--accent);
}

.diff-tech[open] > summary {
    margin-bottom: 0.5rem;
}

/* Forms editor: the outer forms master list (search + add + selectable rows, no reorder). */
.forms-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden; /* Clips its square-cornered row fills to the rounded container, as .content-card and .modal already do. */
}

.forms-list li {
    border-bottom: 1px solid var(--border);
}

.forms-list li:last-child {
    border-bottom: 0;
}

.form-list-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: 0;
    color: inherit;
    padding: 0.55rem 0.75rem;
}

.form-list-item.selected {
    background: color-mix(in srgb, var(--eleos-orange) 14%, var(--surface));
    color: var(--ink);
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--eleos-orange);
}

.form-list-tag {
    font-size: var(--fs-sm);
    color: var(--muted);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0 0.4rem;
}

.form-list-item.selected .form-list-tag {
    color: var(--eleos-orange);
    border-color: var(--eleos-orange);
}

.forms-empty {
    padding: 0.6rem 0.25rem;
}

/* Forms editor: the Visual / Advanced tab strip inside the detail pane. */
.tab-strip {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid var(--border);
    margin: 0.5rem 0 0.25rem;
}

.tab-btn {
    background: transparent;
    color: var(--muted);
    border: 1px solid transparent;
    border-bottom: 0;
    border-radius: var(--radius) var(--radius) 0 0;
    padding: 0.4rem 0.9rem;
}

.tab-btn.active {
    background: var(--surface);
    color: var(--accent);
    border-color: var(--border);
    font-weight: 600;
    margin-bottom: -1px;
}

/* Forms editor: the Visual tab body, the WYSIWYG field list beside the field inspector. */
.form-body {
    display: grid;
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    gap: 1.25rem;
    align-items: start;
    margin-top: 0.75rem;
}

@media (max-width: 900px) {
    .form-body {
        grid-template-columns: 1fr;
    }
}

.form-fields {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.form-editor {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

/* Each WYSIWYG field row: the hidden mark, the driver-preview affordance, and the summary card. */
.wy-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
}

.wy-hidden-mark {
    flex: 0 0 auto;
    width: 1.1rem;
    height: 1.1rem;
    border: 1.5px solid var(--muted);
    border-radius: 50%;
    position: relative;
}

.wy-hidden-mark::after {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 1.3rem;
    height: 1.5px;
    background: var(--muted);
    transform: rotate(45deg);
    transform-origin: center;
}

.wy-field {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}

/* Truncates a long label to one line with an ellipsis rather than wrapping into the code chip's
   column; the field's full label is still available via the row's hover title. */
.wy-field-label {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
    font-size: var(--fs-sm);
}

/* The required marker: a driver-facing required field carries a leading asterisk on its label. */
.wy-required {
    color: #b3261e;
    font-weight: 700;
}

.wy-input {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--muted);
    padding: 0.4rem 0.55rem;
    min-height: 2rem;
}

.wy-textarea {
    min-height: 3.5rem;
}

.wy-select::after {
    content: " ▾";
    color: var(--muted);
}

.wy-static {
    display: block;
    color: var(--ink);
    font-style: italic;
}

.wy-placeholder {
    display: block;
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--muted);
    padding: 0.5rem 0.55rem;
    text-align: center;
    font-size: var(--fs-sm);
}

.wy-choices {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.wy-choice {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: var(--fs-sm);
}

.wy-choice-mark {
    display: inline-block;
    width: 0.85rem;
    height: 0.85rem;
    border: 1.5px solid var(--muted);
    flex: 0 0 auto;
}

.wy-choice-mark.radio {
    border-radius: 50%;
}

.wy-choice-mark.checkbox {
    border-radius: 2px;
}

.wy-rating {
    color: var(--eleos-orange);
    letter-spacing: 0.15rem;
    font-size: 1.1rem;
}

.wy-summary {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.2rem;
    text-align: right;
}

.wy-type {
    font-size: var(--fs-sm);
    color: var(--muted);
}

/* Forms editor: the field inspector. An empty actions row (no rail control declared) collapses. */
.field-inspector {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

/* Forms editor: the Scripts tab holding the form-level power-script editors. */
.scripts-tab {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.insp-help {
    margin: -0.4rem 0 0;
    font-size: var(--fs-sm);
}

.insp-actions:empty {
    display: none;
}

/* Forms editor: the PICK-LIST options pair editor. */
.opt-editor {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    border-top: 1px solid var(--border);
    padding-top: 0.6rem;
}

.opt-title {
    font-weight: 600;
    margin: 0;
}

.opt-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.opt-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.opt-grip {
    cursor: grab;
    color: var(--muted);
    user-select: none;
    letter-spacing: -1px;
}

.opt-fields {
    flex: 1 1 auto;
    display: flex;
    gap: 0.4rem;
    min-width: 0;
}

.opt-fields .insp-input {
    flex: 1 1 0;
    min-width: 0;
}

.opt-moves {
    display: flex;
    gap: 0.2rem;
}

.opt-remove {
    background: var(--surface);
    color: #b3261e;
    border: 1px solid var(--border);
    padding: 0.1rem 0.5rem;
    line-height: 1;
}

.opt-add {
    align-self: flex-start;
}

/* Menus editor: a submenu item's nested children, indented under its own inspector. */
.menu-nested {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    border-left: 3px solid var(--border);
    padding: 0.6rem 0 0.6rem 0.9rem;
    margin-left: 0.2rem;
}

.menu-nested-inspector {
    border-left: 3px solid var(--border);
    padding-left: 0.9rem;
}

/* Actions editor: the live Light/Dark button preview. Each surface renders the action button as the
   driver sees it. The button's background is set at runtime via the CSSOM object-style prop (a
   user-chosen hex), never a CSS declaration here. */
.button-preview {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.button-preview-surface {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.25rem 1rem;
}

/* These two surfaces and .button-preview-btn's own text colour model the driver app's light and dark
   themes, not this portal's chrome: they stay literal white and black on purpose, paired against each
   other rather than against any token here, the same way studio-device.css keeps the vendor's own
   blues. Do not fold them into a portal-chrome sweep. */
.button-preview-surface.light {
    background: #ffffff;
}

.button-preview-surface.dark {
    background: #000000;
}

.button-preview-caption {
    font-size: var(--fs-sm);
    color: var(--muted);
}

.button-preview-surface.dark .button-preview-caption {
    color: #b0b0b8;
}

.button-preview-btn {
    display: inline-block;
    min-width: 8rem;
    text-align: center;
    padding: 0.6rem 1.2rem;
    border-radius: var(--radius);
    color: #ffffff;
    font-weight: 600;
    font-size: var(--fs-sm);
}

/* Actions editor: the hex text field's static "#" prefix addon and the native color-picker swatch beside
   it, lined up in one row so the addon, the bare-hex input, and the color input read as one control. */
.hex-input-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.hex-input-row .hex-input {
    flex: 1 1 auto;
    min-width: 0;
}

.hex-addon {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.8rem;
    height: 1.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--muted);
}

.color-input-native {
    flex: none;
    width: 1.9rem;
    height: 1.9rem;
    padding: 0.1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
}

/* Actions editor: the button-color preset swatch row. Each swatch's color is a fixed preset expressed
   as a class, so it needs no inline style; clicking a swatch sets both the light and dark hex. Wraps to a
   grid (App Manager shows its 12 presets 6x2). */
.color-swatch-row {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.color-swatch {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    border: 1px solid var(--border);
    cursor: pointer;
    padding: 0;
}

.swatch-red {
    background: #FF3830;
}

.swatch-orange {
    background: #FF9500;
}

.swatch-yellow {
    background: #FFCC00;
}

.swatch-green {
    background: #34C759;
}

.swatch-blue {
    background: #007AFF;
}

.swatch-purple {
    background: #AF52DE;
}

.swatch-pink {
    background: #FF2D55;
}

.swatch-indigo {
    background: #5856D6;
}

.swatch-cyan {
    background: #32ADE6;
}

.swatch-teal {
    background: #30B0C7;
}

.swatch-brown {
    background: #A2845E;
}

.swatch-gray {
    background: #8E8E93;
}

/* Actions editor: the pre-filled-field-values list, one field code beside its template-value input. */
.prefill-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.prefill-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.prefill-row .code-badge {
    flex: 0 0 auto;
}

.prefill-row .insp-input {
    flex: 1 1 auto;
    min-width: 0;
}

/* News editor: the plain-textarea message body (the sanctioned fallback for the deferred rich toolbar)
   and the published-items History list below the compose grid, each row pairing the item's title,
   summary, and creation time with its Delete control. */
.news-message {
    resize: vertical;
    min-height: 8rem;
}

.news-history {
    margin-top: 1.25rem;
}

.news-history .opt-title {
    margin-bottom: 0.6rem;
}

.news-item-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.5rem 0;
}

.news-item-main {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}

.news-item-title {
    font-weight: 600;
}

/* A page-level note that a surface is honestly read-only in this demo (Workflows), distinct from the
   orange-accented .notice reserved for a server refusal or error. */
.parity-note {
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 4px solid var(--accent);
    border-radius: var(--radius);
    padding: 0.6rem 1rem;
    margin: 0 0 1.5rem;
    color: var(--muted);
}

/* Workflows viewer: the Load/Stops transition graphs, read-only. Each group is a titled, horizontally
   scrollable SVG diagram: a status is a node, an action that changes status is a labeled directed edge
   to its destination. Node/edge geometry rides SVG attributes and colours ride these fixed classes, so
   nothing is set via inline style (the CSP posture). Clicking a node expands its full action list
   beneath the diagrams. */
.workflow-viz {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    margin-top: 0.75rem;
}

.workflow-group {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.workflow-group-title {
    font-size: var(--fs-base);
    margin: 0;
}

/* The diagram scrolls horizontally inside its own container so a wide state machine never forces the
   page body to scroll sideways. */
.workflow-graph-scroll {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.5rem;
    background: var(--panel-header-bg);
}

.workflow-graph {
    display: block;
}

.workflow-node-g {
    cursor: pointer;
}

/* Nodes reuse the Load/Stops palette the chips used; a selected node takes the accent outline. */
.workflow-node {
    fill: #ffffff;
    stroke: var(--border);
    stroke-width: 1;
}

.workflow-node-load {
    fill: #dce8fd;
    stroke: #a9c6f7;
}

.workflow-node-stop {
    fill: #fdf1cf;
    stroke: #f0d98c;
}

.workflow-node.selected {
    stroke: var(--accent);
    stroke-width: 2;
}

.workflow-node-text {
    font-family: ui-monospace, "Cascadia Code", "Consolas", monospace;
    font-size: 12px;
    fill: var(--ink);
    pointer-events: none;
}

.workflow-edge {
    fill: none;
    stroke: #9aa4b2;
    stroke-width: 1.5;
}

.workflow-arrow {
    fill: #9aa4b2;
}

/* paint-order:stroke draws the stroke first, so the container-coloured stroke reads as a knockout halo
   behind the fill, keeping the label legible where it crosses an edge or sits over the panel background. */
.workflow-edge-label {
    font-size: 10px;
    fill: var(--muted);
    pointer-events: none;
    paint-order: stroke;
    stroke: var(--panel-header-bg);
    stroke-width: 3px;
    stroke-linejoin: round;
}

/* The click-to-expand detail panel for the currently expanded status: its wired actions rendered as
   plain sentences. */
.workflow-detail-panel {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    background: var(--panel-header-bg);
}

.workflow-detail-title {
    font-weight: 600;
    margin: 0 0 0.4rem;
}

.workflow-binding-list {
    margin: 0;
    padding-left: 1.1rem;
}

.workflow-binding-list li {
    margin: 0.2rem 0;
}
