Skip to main content

UI_CSS

Constant UI_CSS 

Source
pub const UI_CSS: &str = "      /* DESIGN.md tokens \u{2014} dark is the primary/default theme (DD5); light follows the OS via\n         prefers-color-scheme (#31), no manual toggle. Fonts are the metrics-compatible\n         fallbacks; the latin-subset Monaspace/General Sans woff2 land in a later slice with\n         no layout shift. */\n      :root {\n        color-scheme: dark;\n        --bg: #0a0a0b;\n        --surface: #121214;\n        --raised: #17171b;\n        --hair: #26262b;\n        --text: #e6e6e9;\n        --muted: #8a8a93;\n        --faint: #55555c;\n        --amber: #ff7a1a;\n        --warning: #f5a623;\n        --error: #ff5c5c;\n        --error-bg: #ff5c5c14;\n        --success: #46d39a;\n        --success-bg: #46d39a14;\n        --mono: ui-monospace, \"SF Mono\", SFMono-Regular, Menlo, monospace;\n        --sans: ui-sans-serif, system-ui, -apple-system, sans-serif;\n      }\n\n      /* Light palette (DESIGN.md \"Light mode\"). --raised coincides with --surface (both read\n         as the elevated/white layer against the --bg canvas \u{2014} the dark scale\'s bg<surface<raised\n         progression has no headroom above white). --faint has no explicit light spec; it\'s\n         decorative-only (DD4), so it\'s picked for a low, ~faint-in-dark contrast against --bg\n         rather than lifted from the doc. */\n      @media (prefers-color-scheme: light) {\n        :root {\n          color-scheme: light;\n          --bg: #f7f7f5;\n          --surface: #ffffff;\n          --raised: #ffffff;\n          --hair: #e2e2dd;\n          --text: #16161a;\n          --muted: #6b6b72;\n          --faint: #a3a39c;\n          --amber: #e0570b;\n          --error: #c7382f;\n          --error-bg: #c7382f14;\n          --success: #1e9e6a;\n          --success-bg: #1e9e6a14;\n        }\n      }\n\n      * {\n        box-sizing: border-box;\n        margin: 0;\n        padding: 0;\n      }\n\n      body {\n        height: 100vh; /* exact viewport height so the CANVAS scrolls, never the whole page */\n        display: flex;\n        flex-direction: column;\n        overflow: hidden;\n        background: var(--bg);\n        color: var(--text);\n        font-family: var(--sans);\n        font-size: 0.875rem;\n        line-height: 1.5;\n        -webkit-font-smoothing: antialiased;\n      }\n\n      /* The two-pane body: canvas flexes, attribution holds a fixed 320px on the right. Fills\n         the height below the header so the pane\'s border-left runs the full column. */\n      .body {\n        display: flex;\n        flex: 1;\n        min-height: 0;\n      }\n\n      /* --- The app shell: never a blank page before wasm mounts (D20). The boot node is\n             removed by the csr entry once the app is alive; if it survives, boot failed and\n             the error state below is revealed in its place. --- */\n      .boot {\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n        gap: 0.5rem;\n        min-height: 100vh;\n        padding: 2rem;\n        text-align: center;\n        color: var(--muted);\n      }\n\n      .boot-glyph {\n        font-family: var(--mono);\n        font-size: 1.5rem;\n        color: var(--faint);\n      }\n\n      .boot p {\n        max-width: 34rem;\n      }\n\n      .boot-hint {\n        color: var(--muted);\n        font-size: 0.8125rem;\n      }\n\n      .boot code {\n        font-family: var(--mono);\n        color: var(--text);\n      }\n\n      .boot--error .boot-glyph {\n        color: var(--warning);\n      }\n\n      [hidden] {\n        display: none !important;\n      }\n\n      @media (prefers-reduced-motion: no-preference) {\n        .boot-glyph {\n          animation: boot-pulse 1.6s ease-in-out infinite;\n        }\n      }\n\n      @keyframes boot-pulse {\n        0%,\n        100% {\n          opacity: 0.55;\n        }\n        50% {\n          opacity: 1;\n        }\n      }\n\n      /* --- Header bar. Mono, compact, quiet: the verdict is the protagonist through the\n             `text` token and its position, not color. Zero amber in the header frame. --- */\n      .hdr {\n        display: flex;\n        align-items: baseline;\n        gap: 1rem;\n        padding: 0.5rem 0.875rem;\n        border-bottom: 1px solid var(--hair);\n        background: var(--surface);\n        font-family: var(--mono);\n        font-size: 0.75rem;\n      }\n\n      .logo {\n        color: var(--text);\n        font-weight: 700;\n        letter-spacing: -0.01em;\n      }\n\n      .logo-glyph {\n        color: var(--muted); /* never amber \u{2014} amber is spent only in the canvas */\n      }\n\n      .pair {\n        color: var(--text);\n      }\n\n      .pair b {\n        font-weight: 600;\n      }\n\n      .role {\n        color: var(--muted);\n      }\n\n      .vs {\n        color: var(--faint);\n      }\n\n      .verdict {\n        color: var(--text);\n        font-variant-numeric: tabular-nums;\n        text-decoration: none;\n      }\n\n      .verdict--fork:hover {\n        text-decoration: underline;\n        text-underline-offset: 3px;\n      }\n\n      .verdict:focus-visible {\n        outline: 1px solid var(--hair);\n        outline-offset: 2px;\n        border-radius: 2px;\n      }\n\n      .meta {\n        margin-left: auto;\n        color: var(--muted);\n        font-variant-numeric: tabular-nums;\n      }\n\n      /* --- The alignment canvas: side-by-side runs on the shared spine. Sameness recedes\n             (muted), divergence glows (amber on the fork + every downstream row). Amber is\n             spent here and nowhere else. DOM rows carry selectable text; the SVG overlay\n             draws the timeline rail, the amber divergent-path segment, and the fork node,\n             both keyed to the 30px row pitch so they line up. --- */\n      .canvas {\n        flex: 1;\n        min-width: 0; /* let the flex child shrink so its own overflow engages */\n        min-height: 12rem;\n        overflow: auto; /* the canvas is the scroll container \u{2014} header + pane stay fixed */\n      }\n\n      /* Fills the canvas viewport (dotted field), with the bounded rows left-anchored on top.\n         When the rows are wider than the canvas the canvas alone scrolls (DD4 canvas-only\n         horizontal scroll) \u{2014} the header and attribution pane never move. */\n      .track {\n        position: relative;\n        min-height: 100%;\n        background-image: radial-gradient(circle, var(--hair) 1px, transparent 1px);\n        background-size: 22px 22px;\n      }\n\n      .spine {\n        position: absolute;\n        top: 0;\n        left: 0;\n        pointer-events: none;\n      }\n      .spine-rail {\n        stroke: var(--faint); /* the quiet shared timeline */\n        stroke-width: 1;\n      }\n      .spine-path {\n        stroke: var(--amber); /* the divergent path made literal; the ignition beat animates it */\n        stroke-width: 2;\n      }\n      .fork-node {\n        fill: var(--amber);\n      }\n\n      .rows {\n        list-style: none;\n        width: fit-content; /* hug the two bounded runs; the fork band never spans dead space */\n        margin-left: 28px; /* clear the 28px SVG spine strip */\n        padding: 18px 0; /* 18px top pad matches the geometry\'s TOP_PAD */\n      }\n\n      /* Fixed side columns, not 1fr: the two runs sit adjacent as one comparison instead of\n         stretching to opposite edges, and the layout anchors left \u{2014} leaving the right open for\n         the attribution pane (slice 2). Same template on every row keeps the columns aligned. */\n      .row {\n        display: grid;\n        grid-template-columns: 104px 300px 300px auto;\n        align-items: center;\n        column-gap: 18px;\n        height: 30px; /* == ROW_H; the SVG overlay is computed from the same pitch */\n        padding: 0 14px;\n        font-family: var(--mono);\n        font-size: 0.75rem;\n        color: var(--muted);\n        font-variant-numeric: tabular-nums;\n        cursor: pointer;\n        scroll-margin-top: 96px; /* auto-scroll lands the fork in the upper third, not the edge */\n      }\n      /* Hover tint (never on the selected row) and a clear focus-visible ring. The ring is a\n         box-shadow, not an outline, so it never overrides the fork\'s dashed amber cue. */\n      .row:hover:not(.row--selected) {\n        background: var(--surface);\n      }\n      .row:focus-visible {\n        outline: none;\n        box-shadow: inset 0 0 0 2px var(--muted);\n      }\n\n      .gutter {\n        display: flex;\n        align-items: baseline;\n        gap: 8px;\n      }\n      .cue {\n        width: 0.9em;\n        display: inline-block;\n        text-align: center;\n        color: var(--faint);\n      }\n      .idx {\n        color: var(--muted);\n      }\n\n      .cell {\n        display: flex;\n        align-items: baseline;\n        gap: 8px;\n        min-width: 0;\n      }\n      .kind {\n        width: 40px;\n        color: var(--faint);\n      }\n      .name {\n        width: 104px;\n        color: var(--muted);\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n      }\n      .sum {\n        flex: 1;\n        min-width: 0;\n        color: var(--muted);\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n      }\n      /* The honest cut mark (issue #17) and, when the slot carries a resolvable address, a real\n         click-to-expand affordance (issue #30) \u{2014} same element, same class, so idle appearance\n         never changes: only interaction reveals whether there\'s anything behind it to fetch.\n         Never amber (DESIGN.md) \u{2014} this is not divergence, and must not compete with the fork\'s\n         one scarce accent. */\n      .slot-trunc {\n        border: none;\n        background: none;\n        padding: 0;\n        margin: 0;\n        font: inherit;\n        color: var(--muted);\n      }\n      button.slot-trunc {\n        cursor: pointer;\n        transition: color 150ms ease-out;\n      }\n      button.slot-trunc:hover {\n        color: var(--text);\n        text-decoration: underline dotted;\n      }\n      button.slot-trunc:focus-visible {\n        color: var(--text);\n        text-decoration: underline dotted;\n        outline: 2px solid var(--muted);\n        outline-offset: 1px;\n      }\n      button.slot-trunc:disabled {\n        cursor: default;\n        opacity: 0.6;\n        text-decoration: none;\n      }\n      @media (prefers-reduced-motion: reduce) {\n        button.slot-trunc {\n          transition: none;\n        }\n      }\n\n      /* Divergence glows: the fork and every downstream row in one uniform amber (no intensity\n         grading \u{2014} one scarce accent). The dashed outline is the fork\'s redundant non-color cue;\n         `outline` never shifts layout, so the row stays 30px and the SVG stays aligned. */\n      .row--fork {\n        color: var(--amber);\n        background: rgba(255, 122, 26, 0.05);\n        border-radius: 4px;\n        outline: 1px dashed var(--amber);\n        outline-offset: -1px;\n      }\n      .row--down {\n        color: var(--amber);\n      }\n      .row--fork .idx,\n      .row--fork .kind,\n      .row--fork .name,\n      .row--fork .sum,\n      .row--fork .cue,\n      .row--down .idx,\n      .row--down .kind,\n      .row--down .name,\n      .row--down .sum,\n      .row--down .cue {\n        color: var(--amber);\n      }\n      .tag {\n        justify-self: end;\n        white-space: nowrap; /* the [FORK \u{b7} conf] tag stays on the fork\'s single 30px row */\n        color: var(--amber);\n        font-variant-numeric: tabular-nums;\n      }\n\n      /* Selection is the neutral frame: raised surface + a hairline ring (inset box-shadow, so\n         no layout shift). Never amber (DD2) \u{2014} the default-selected fork keeps its amber role via\n         `row--fork` and only gains the raised surface + hairline on top. Placed after the role\n         rules so its raised background wins over the fork\'s amber tint. */\n      .row--selected {\n        background: var(--raised);\n        box-shadow: inset 0 0 0 1px var(--hair);\n      }\n\n      /* --- Motion: the ONE expressive beat (DESIGN.md \u{a7}Motion). On load the amber ignites at\n             the fork node and flows down the divergent path \u{2014} the fork node pops, the amber\n             spine segment draws downward from the fork, and the divergent rows kindle to full\n             amber. Medium 250\u{2013}400ms, ease-out (enter easing); the beat spans 0\u{2013}380ms. Each\n             animation\'s `to` state IS the static markup, so with `prefers-reduced-motion:\n             reduce` this whole block is off and the lit end state simply shows with no motion.\n             The glow is the saturated stroke \u{2014} no blur halo. Fires on load; slice 3a already\n             scrolls the fork into view first, so the beat plays where the eye is. --- */\n      @media (prefers-reduced-motion: no-preference) {\n        .fork-node {\n          transform-box: fill-box;\n          transform-origin: center;\n          animation: fork-ignite 200ms ease-out both;\n        }\n        .spine-path {\n          transform-box: fill-box;\n          transform-origin: top; /* the fork end: the segment runs fork_y \u{2192} y_last */\n          animation: path-flow 300ms ease-out 80ms both;\n        }\n        .row--fork,\n        .row--down {\n          animation: row-kindle 300ms ease-out 80ms both;\n        }\n      }\n\n      @keyframes fork-ignite {\n        from {\n          opacity: 0;\n          transform: scale(0.2);\n        }\n        to {\n          opacity: 1;\n          transform: scale(1);\n        }\n      }\n      @keyframes path-flow {\n        from {\n          transform: scaleY(0);\n        }\n        to {\n          transform: scaleY(1);\n        }\n      }\n      @keyframes row-kindle {\n        from {\n          opacity: 0.4;\n        }\n        to {\n          opacity: 1;\n        }\n      }\n\n      /* --- Attribution pane: the diff\'s one answer in DR5 order. No amber, no red/green \u{2014} a\n             statement ABOUT the divergence, not the divergence itself. --- */\n      .attr {\n        flex: 0 0 320px;\n        border-left: 1px solid var(--hair);\n        background: var(--surface);\n        padding: 16px 18px;\n        font-family: var(--mono);\n        font-size: 0.75rem;\n        overflow-y: auto;\n      }\n      .attr-title {\n        margin-bottom: 14px;\n        font-size: 0.625rem;\n        font-weight: 600;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        color: var(--muted);\n      }\n      .attr-list {\n        display: flex;\n        flex-direction: column;\n        gap: 10px;\n      }\n      .attr-row {\n        display: flex;\n        align-items: baseline;\n        justify-content: space-between;\n        gap: 14px;\n      }\n      .attr-row dt {\n        color: var(--muted);\n      }\n      .attr-row dd {\n        color: var(--text);\n        text-align: right;\n        font-variant-numeric: tabular-nums;\n      }\n      .attr-empty {\n        color: var(--muted);\n        line-height: 1.6;\n      }\n\n      /* A division of the SAME pane (issue #40), not a new one \u{2014} a hairline divider plus a\n             heading-level step-down (h3, still styled as .attr-title) carry the hierarchy, so\n             this reads as \"more of the same voice,\" never a competing header. */\n      .attr-section {\n        margin-top: 20px;\n        padding-top: 16px;\n        border-top: 1px solid var(--hair);\n      }\n      .attr-section .attr-title {\n        margin-bottom: 10px;\n      }\n\n      /* --- Content-diff pane: the selected pair\'s field-level -/+ evidence (issue #27). The\n             ONLY red/green in any surface (DESIGN.md containment) \u{2014} removed red, added green,\n             kept deliberately distinct from the amber fork. Lives inside the attribution aside,\n             below the answer; a card per field, monospace, values wrapping so long payloads stay\n             readable and copyable (never a canvas draw). --- */\n      .content-diff,\n      .content-diff-empty {\n        margin-top: 18px;\n        padding-top: 14px;\n        border-top: 1px solid var(--hair);\n      }\n      .content-diff {\n        display: flex;\n        flex-direction: column;\n        gap: 12px;\n      }\n      .content-diff-empty {\n        color: var(--muted);\n        line-height: 1.6;\n      }\n      .content-diff-field {\n        display: flex;\n        flex-direction: column;\n        gap: 3px;\n      }\n      .content-diff-path {\n        color: var(--muted);\n        font-size: 0.6875rem;\n      }\n      .content-diff-del,\n      .content-diff-add {\n        display: flex;\n        gap: 6px;\n        padding: 2px 6px;\n        border-radius: 3px;\n        white-space: pre-wrap;\n        overflow-wrap: anywhere;\n      }\n      .content-diff-del {\n        color: var(--error);\n        background: var(--error-bg);\n      }\n      .content-diff-add {\n        color: var(--success);\n        background: var(--success-bg);\n      }\n      .content-diff-sign {\n        flex: 0 0 auto;\n        font-variant-numeric: tabular-nums;\n        opacity: 0.85;\n      }\n      .content-diff-val {\n        flex: 1;\n        min-width: 0;\n      }\n      /* The copy affordance (issue #27 evidence-out). Quiet neutral at rest \u{2014} buttons are never\n         amber (DESIGN.md), and it must not compete with the card\'s scarce red/green, so the\n         confirmed state stays neutral too. Sits top-right of the card; hover = surface tint;\n         focus-visible = hairline ring, matching the canvas rows. */\n      .content-diff-copy {\n        align-self: flex-end;\n        margin-bottom: -4px;\n        padding: 2px 8px;\n        border: 1px solid var(--hair);\n        border-radius: 4px;\n        background: var(--surface);\n        color: var(--muted);\n        font-family: var(--mono);\n        font-size: 0.6875rem;\n        cursor: pointer;\n        transition:\n          color 150ms ease-out,\n          background 150ms ease-out,\n          border-color 150ms ease-out;\n      }\n      .content-diff-copy:hover {\n        background: var(--raised);\n        color: var(--text);\n        border-color: var(--muted);\n      }\n      .content-diff-copy:focus-visible {\n        outline: 2px solid var(--muted);\n        outline-offset: 1px;\n      }\n      @media (prefers-reduced-motion: reduce) {\n        .content-diff-copy {\n          transition: none;\n        }\n      }\n\n      /* --- Disconnect banner: the server that fed this view stopped (D-decision 2026-07-12).\n             A slim strip docked at the bottom edge, `warning` \u{2014} never amber: a system-status\n             message is not a divergence. Fixed, so it annunciates without reflowing the canvas;\n             no spinner \u{2014} the state is terminal until the user restarts + reloads. --- */\n      .banner {\n        position: fixed;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        padding: 0.5rem 0.875rem;\n        border-top: 1px solid var(--warning);\n        background: rgba(245, 166, 35, 0.08); /* faint warning wash over the surface */\n        font-family: var(--mono);\n        font-size: 0.75rem;\n        color: var(--warning);\n      }\n\n      .banner code {\n        color: var(--text); /* the restart command reads as copy-ready, brighter than the label */\n      }\n";
Expand description

The design system’s stylesheet — the single source both painters dress from.

It lives beside the view-model rather than in ui/ for a packaging reason: ui/ is a separate workspace, so crates/amberfork/src/html_export.rs could only reach it with an include_str! that escapes its own crate root, which builds in the repo and then fails cargo package (the tarball carries no path outside the crate). Owning it here makes it a normal dependency for the static export, while ui/index.html inlines the same bytes at build time via trunk — one stylesheet, no hand-copied second copy to drift.