Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>SENTINEL Trust Mission Control</title> | |
| <style> | |
| :root { | |
| --bg: #060706; | |
| --panel: #10130f; | |
| --panel-2: #151911; | |
| --panel-3: #1b2017; | |
| --ink: #f6f1df; | |
| --muted: #aaa48e; | |
| --faint: #6e705f; | |
| --line: #343a2d; | |
| --jade: #27e0a1; | |
| --jade-soft: rgba(39, 224, 161, 0.14); | |
| --amber: #f5ba41; | |
| --amber-soft: rgba(245, 186, 65, 0.14); | |
| --flame: #ff5f45; | |
| --flame-soft: rgba(255, 95, 69, 0.14); | |
| --blue: #73a7ff; | |
| --blue-soft: rgba(115, 167, 255, 0.14); | |
| --magenta: #e879f9; | |
| --magenta-soft: rgba(232, 121, 249, 0.12); | |
| --cream: #fff6d8; | |
| --shadow: 0 22px 70px rgba(0, 0, 0, 0.38); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| html { | |
| background: var(--bg); | |
| } | |
| body { | |
| margin: 0; | |
| min-height: 100vh; | |
| color: var(--ink); | |
| background: | |
| linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(255, 255, 255, 0.022) 1px, transparent 1px), | |
| linear-gradient(145deg, #060706 0%, #11130d 44%, #0b0e0a 100%); | |
| background-size: 32px 32px, 32px 32px, auto; | |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| letter-spacing: 0; | |
| } | |
| button, | |
| select, | |
| input { | |
| font: inherit; | |
| } | |
| button { | |
| min-height: 40px; | |
| border: 1px solid #4a5241; | |
| background: #171b13; | |
| color: var(--ink); | |
| border-radius: 8px; | |
| padding: 0 13px; | |
| cursor: pointer; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| white-space: nowrap; | |
| transition: transform 160ms ease, border-color 160ms ease, background 160ms ease; | |
| } | |
| button:hover { | |
| border-color: #80906f; | |
| background: #202719; | |
| transform: translateY(-1px); | |
| } | |
| button:disabled { | |
| cursor: not-allowed; | |
| opacity: 0.52; | |
| transform: none; | |
| } | |
| button.primary { | |
| border-color: rgba(39, 224, 161, 0.72); | |
| background: linear-gradient(180deg, #1e6b54, #134736); | |
| color: #f7fffb; | |
| } | |
| button.warn { | |
| border-color: rgba(245, 186, 65, 0.72); | |
| background: linear-gradient(180deg, #73551d, #403012); | |
| color: #fff4d0; | |
| } | |
| button.danger { | |
| border-color: rgba(255, 95, 69, 0.72); | |
| background: #2a1510; | |
| color: #ffd8d1; | |
| } | |
| select, | |
| input { | |
| min-height: 40px; | |
| border: 1px solid #4a5241; | |
| background: #0f120d; | |
| color: var(--ink); | |
| border-radius: 8px; | |
| padding: 0 11px; | |
| outline: none; | |
| } | |
| select:focus, | |
| input:focus, | |
| button:focus { | |
| border-color: var(--jade); | |
| box-shadow: 0 0 0 3px rgba(39, 224, 161, 0.16); | |
| } | |
| input { | |
| width: 90px; | |
| } | |
| .shell { | |
| min-height: 100vh; | |
| } | |
| header { | |
| position: sticky; | |
| top: 0; | |
| z-index: 20; | |
| display: grid; | |
| grid-template-columns: minmax(280px, 1fr) auto; | |
| gap: 18px; | |
| align-items: center; | |
| padding: 16px 22px; | |
| background: rgba(7, 8, 6, 0.94); | |
| border-bottom: 1px solid #2e3529; | |
| backdrop-filter: blur(14px); | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 14px; | |
| min-width: 0; | |
| } | |
| .mark { | |
| width: 42px; | |
| height: 42px; | |
| border: 1px solid rgba(39, 224, 161, 0.64); | |
| border-radius: 8px; | |
| display: grid; | |
| place-items: center; | |
| background: | |
| linear-gradient(135deg, rgba(39, 224, 161, 0.28), rgba(245, 186, 65, 0.1)), | |
| #10150f; | |
| color: #dfffee; | |
| font-weight: 860; | |
| box-shadow: inset 0 0 22px rgba(39, 224, 161, 0.16); | |
| } | |
| h1, | |
| h2, | |
| h3, | |
| p { | |
| margin: 0; | |
| } | |
| h1 { | |
| font-size: 20px; | |
| line-height: 1.05; | |
| font-weight: 840; | |
| } | |
| .subhead { | |
| margin-top: 5px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .header-actions { | |
| display: flex; | |
| align-items: center; | |
| justify-content: flex-end; | |
| gap: 9px; | |
| flex-wrap: wrap; | |
| } | |
| .modebar { | |
| padding: 0 22px 14px; | |
| border-bottom: 1px solid #232920; | |
| background: rgba(7, 8, 6, 0.9); | |
| backdrop-filter: blur(14px); | |
| } | |
| .modebar-inner { | |
| width: min(1540px, 100%); | |
| margin: 0 auto; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 14px; | |
| flex-wrap: wrap; | |
| } | |
| .view-tabs { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| } | |
| .view-tab { | |
| min-height: 38px; | |
| border-radius: 999px; | |
| padding: 0 14px; | |
| background: #10140f; | |
| } | |
| .view-tab.active { | |
| border-color: rgba(39, 224, 161, 0.72); | |
| background: linear-gradient(180deg, #1d6a53, #133c30); | |
| color: #effff7; | |
| } | |
| .view-copy { | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.4; | |
| max-width: 760px; | |
| } | |
| .console { | |
| width: min(1540px, 100%); | |
| margin: 0 auto; | |
| padding: 18px; | |
| display: grid; | |
| gap: 14px; | |
| grid-template-columns: minmax(420px, 1.35fr) minmax(340px, 0.85fr); | |
| grid-template-areas: | |
| "hero hero" | |
| "theater command" | |
| "mission playground" | |
| "trust playground" | |
| "story judge" | |
| "proof events" | |
| "flow themes" | |
| "readiness readiness"; | |
| align-items: start; | |
| } | |
| section { | |
| min-width: 0; | |
| background: linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent 38%), var(--panel); | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| box-shadow: var(--shadow); | |
| overflow: hidden; | |
| } | |
| .hero { grid-area: hero; } | |
| .theater { grid-area: theater; } | |
| .command { grid-area: command; } | |
| .mission { grid-area: mission; } | |
| .trust { grid-area: trust; } | |
| .playground { grid-area: playground; } | |
| .story { grid-area: story; } | |
| .judge { grid-area: judge; } | |
| .readiness { grid-area: readiness; } | |
| .proof { grid-area: proof; } | |
| .events { grid-area: events; } | |
| .flow { grid-area: flow; } | |
| .themes { grid-area: themes; } | |
| .section-hidden { | |
| display: none; | |
| } | |
| .section-head { | |
| min-height: 54px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| padding: 13px 15px; | |
| border-bottom: 1px solid var(--line); | |
| background: rgba(0, 0, 0, 0.14); | |
| } | |
| h2 { | |
| font-size: 12px; | |
| text-transform: uppercase; | |
| letter-spacing: 0; | |
| color: #d6d0b7; | |
| font-weight: 820; | |
| } | |
| .body { | |
| padding: 15px; | |
| } | |
| .hero-grid { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr); | |
| gap: 13px; | |
| } | |
| .hero-panel { | |
| min-height: 208px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 15px; | |
| background: var(--panel-2); | |
| } | |
| .hero-panel.primary { | |
| border-color: rgba(39, 224, 161, 0.42); | |
| background: | |
| linear-gradient(180deg, rgba(39, 224, 161, 0.12), transparent 45%), | |
| var(--panel-2); | |
| } | |
| .hero-panel h3, | |
| .judge-card h3 { | |
| font-size: 18px; | |
| color: var(--cream); | |
| margin: 0 0 10px 0; | |
| } | |
| .hero-panel p { | |
| color: #e8e1ca; | |
| font-size: 14px; | |
| line-height: 1.55; | |
| } | |
| .hero-callouts, | |
| .hero-steps, | |
| .judge-list { | |
| display: grid; | |
| gap: 9px; | |
| margin-top: 14px; | |
| } | |
| .hero-callout, | |
| .hero-step, | |
| .judge-step { | |
| min-height: 52px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 11px 12px; | |
| background: #0d100b; | |
| color: #ebe5cf; | |
| line-height: 1.42; | |
| } | |
| .hero-callout strong, | |
| .hero-step strong, | |
| .judge-step strong { | |
| display: block; | |
| margin-bottom: 4px; | |
| color: var(--cream); | |
| font-size: 13px; | |
| } | |
| .hero-stats { | |
| margin-top: 14px; | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 10px; | |
| } | |
| .hero-stat { | |
| min-height: 80px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 11px; | |
| background: #0d100b; | |
| } | |
| .hero-stat .label { | |
| margin-bottom: 7px; | |
| } | |
| .hero-stat .value { | |
| font-size: 22px; | |
| } | |
| .chips { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| align-items: center; | |
| justify-content: flex-end; | |
| } | |
| .chip { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 27px; | |
| border: 1px solid #46503d; | |
| border-radius: 999px; | |
| padding: 0 9px; | |
| font-size: 12px; | |
| font-weight: 760; | |
| color: #d8d1ba; | |
| background: #161a13; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .chip.live { | |
| border-color: rgba(39, 224, 161, 0.45); | |
| color: #a7f9d8; | |
| background: var(--jade-soft); | |
| } | |
| .chip.warn { | |
| border-color: rgba(245, 186, 65, 0.46); | |
| color: #ffe0a3; | |
| background: var(--amber-soft); | |
| } | |
| .chip.fail { | |
| border-color: rgba(255, 95, 69, 0.52); | |
| color: #ffc3b8; | |
| background: var(--flame-soft); | |
| } | |
| .muted { | |
| color: var(--muted); | |
| } | |
| .stage { | |
| position: relative; | |
| min-height: 464px; | |
| border: 1px solid #333c2b; | |
| border-radius: 8px; | |
| background: | |
| linear-gradient(90deg, transparent 0, transparent calc(50% - 1px), rgba(245, 186, 65, 0.28) 50%, transparent calc(50% + 1px), transparent 100%), | |
| linear-gradient(rgba(39, 224, 161, 0.055) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(39, 224, 161, 0.04) 1px, transparent 1px), | |
| #080a08; | |
| background-size: auto, 26px 26px, 26px 26px, auto; | |
| overflow: hidden; | |
| } | |
| .stage::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| border: 1px solid rgba(255, 255, 255, 0.045); | |
| border-radius: inherit; | |
| } | |
| .stage-topline { | |
| position: absolute; | |
| top: 13px; | |
| left: 14px; | |
| right: 14px; | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 10px; | |
| z-index: 2; | |
| } | |
| .stage-label { | |
| min-height: 46px; | |
| border: 1px solid #343c2e; | |
| border-radius: 8px; | |
| padding: 8px 10px; | |
| background: rgba(13, 16, 11, 0.86); | |
| } | |
| .stage-label span { | |
| display: block; | |
| color: var(--muted); | |
| font-size: 11px; | |
| line-height: 1.2; | |
| } | |
| .stage-label strong { | |
| display: block; | |
| margin-top: 4px; | |
| font-size: 15px; | |
| color: var(--cream); | |
| overflow-wrap: anywhere; | |
| } | |
| .brain-card { | |
| position: absolute; | |
| top: 95px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: min(360px, calc(100% - 32px)); | |
| min-height: 96px; | |
| border: 1px solid rgba(39, 224, 161, 0.56); | |
| border-radius: 8px; | |
| padding: 15px; | |
| background: | |
| linear-gradient(180deg, rgba(39, 224, 161, 0.18), rgba(39, 224, 161, 0.035)), | |
| #0f1711; | |
| box-shadow: 0 0 0 1px rgba(39, 224, 161, 0.06), 0 18px 42px rgba(0, 0, 0, 0.42); | |
| text-align: center; | |
| z-index: 3; | |
| } | |
| .node-title { | |
| font-size: 18px; | |
| font-weight: 820; | |
| color: #ecfff5; | |
| } | |
| .node-copy { | |
| margin-top: 6px; | |
| color: #b8d7c5; | |
| font-size: 13px; | |
| line-height: 1.35; | |
| } | |
| .signal-line { | |
| position: absolute; | |
| left: 50%; | |
| top: 190px; | |
| bottom: 178px; | |
| width: 1px; | |
| background: linear-gradient(180deg, rgba(39, 224, 161, 0.7), rgba(245, 186, 65, 0.6), rgba(255, 95, 69, 0.55)); | |
| opacity: 0.9; | |
| z-index: 1; | |
| } | |
| .specialist-grid { | |
| position: absolute; | |
| left: 15px; | |
| right: 15px; | |
| bottom: 96px; | |
| display: grid; | |
| grid-template-columns: repeat(5, minmax(92px, 1fr)); | |
| gap: 10px; | |
| z-index: 3; | |
| } | |
| .node { | |
| min-height: 116px; | |
| border: 1px solid #3a4433; | |
| border-radius: 8px; | |
| padding: 10px; | |
| background: rgba(16, 19, 15, 0.94); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .node::before { | |
| content: ""; | |
| position: absolute; | |
| left: 0; | |
| right: 0; | |
| top: 0; | |
| height: 3px; | |
| background: var(--node-color, var(--blue)); | |
| } | |
| .node.active { | |
| border-color: var(--jade); | |
| box-shadow: 0 0 0 2px rgba(39, 224, 161, 0.13); | |
| } | |
| .node.watch { | |
| border-color: rgba(245, 186, 65, 0.72); | |
| } | |
| .node.quarantine { | |
| border-color: rgba(255, 95, 69, 0.74); | |
| } | |
| .node-id { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 8px; | |
| color: #f9f3d9; | |
| font-weight: 820; | |
| font-size: 16px; | |
| } | |
| .node-trust { | |
| margin-top: 14px; | |
| display: grid; | |
| gap: 8px; | |
| } | |
| .trust-rail { | |
| height: 10px; | |
| border-radius: 999px; | |
| background: #2a3024; | |
| overflow: hidden; | |
| } | |
| .trust-rail span { | |
| display: block; | |
| height: 100%; | |
| width: calc(var(--trust, 0.5) * 100%); | |
| border-radius: inherit; | |
| background: var(--node-color, var(--blue)); | |
| transition: width 220ms ease, background 220ms ease; | |
| } | |
| .node-meta { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 8px; | |
| color: var(--muted); | |
| font-size: 12px; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .outcome-strip { | |
| position: absolute; | |
| left: 15px; | |
| right: 15px; | |
| bottom: 14px; | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 10px; | |
| z-index: 3; | |
| } | |
| .outcome { | |
| min-height: 62px; | |
| border: 1px solid #373f31; | |
| border-radius: 8px; | |
| padding: 10px; | |
| background: rgba(11, 13, 9, 0.91); | |
| } | |
| .outcome span { | |
| color: var(--muted); | |
| font-size: 11px; | |
| display: block; | |
| } | |
| .outcome strong { | |
| display: block; | |
| margin-top: 5px; | |
| font-size: 15px; | |
| color: var(--cream); | |
| overflow-wrap: anywhere; | |
| } | |
| .command-grid { | |
| display: grid; | |
| gap: 12px; | |
| } | |
| .field-row { | |
| display: grid; | |
| grid-template-columns: 1fr 96px; | |
| gap: 10px; | |
| } | |
| .button-row { | |
| display: grid; | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| gap: 10px; | |
| } | |
| .action-row { | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 9px; | |
| } | |
| .decision-card { | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 13px; | |
| background: var(--panel-2); | |
| } | |
| .decision-title { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .decision-title strong { | |
| color: var(--cream); | |
| } | |
| .recommendation { | |
| display: grid; | |
| grid-template-columns: 1fr auto; | |
| gap: 10px; | |
| align-items: center; | |
| margin-top: 10px; | |
| } | |
| .recommendation-output { | |
| min-height: 44px; | |
| border: 1px solid rgba(39, 224, 161, 0.32); | |
| border-radius: 8px; | |
| padding: 9px 10px; | |
| background: rgba(39, 224, 161, 0.08); | |
| color: #c9ffe8; | |
| font-weight: 820; | |
| overflow-wrap: anywhere; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 10px; | |
| } | |
| .stat { | |
| min-height: 82px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 11px; | |
| background: var(--panel-2); | |
| } | |
| .label { | |
| color: var(--muted); | |
| font-size: 12px; | |
| line-height: 1.2; | |
| margin-bottom: 8px; | |
| } | |
| .value { | |
| font-size: 24px; | |
| line-height: 1; | |
| color: var(--cream); | |
| font-weight: 840; | |
| font-variant-numeric: tabular-nums; | |
| overflow-wrap: anywhere; | |
| } | |
| .subtask { | |
| margin-top: 12px; | |
| min-height: 84px; | |
| border: 1px solid #3a4433; | |
| border-radius: 8px; | |
| padding: 13px; | |
| background: #0d100b; | |
| color: #e8e1c9; | |
| line-height: 1.45; | |
| } | |
| .progress { | |
| height: 12px; | |
| margin-top: 12px; | |
| border-radius: 999px; | |
| overflow: hidden; | |
| background: #2a3024; | |
| } | |
| .progress span { | |
| display: block; | |
| height: 100%; | |
| width: 0%; | |
| background: linear-gradient(90deg, var(--flame), var(--amber), var(--jade)); | |
| border-radius: inherit; | |
| transition: width 220ms ease; | |
| } | |
| .risk-meter { | |
| display: grid; | |
| gap: 8px; | |
| margin-top: 12px; | |
| } | |
| .risk-track { | |
| height: 12px; | |
| border-radius: 999px; | |
| background: #2a3024; | |
| overflow: hidden; | |
| } | |
| .risk-track span { | |
| display: block; | |
| height: 100%; | |
| width: 0%; | |
| background: linear-gradient(90deg, var(--jade), var(--amber), var(--flame)); | |
| border-radius: inherit; | |
| transition: width 220ms ease; | |
| } | |
| .trust-list { | |
| display: grid; | |
| gap: 9px; | |
| } | |
| .specialist { | |
| display: grid; | |
| grid-template-columns: 50px 1fr 58px; | |
| gap: 11px; | |
| align-items: center; | |
| min-height: 52px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 10px; | |
| background: var(--panel-2); | |
| } | |
| .sid { | |
| color: var(--cream); | |
| font-weight: 820; | |
| } | |
| .bar { | |
| height: 12px; | |
| border-radius: 999px; | |
| background: #2a3024; | |
| overflow: hidden; | |
| } | |
| .fill { | |
| height: 100%; | |
| width: 50%; | |
| border-radius: inherit; | |
| transition: width 220ms ease, background 220ms ease; | |
| } | |
| .score { | |
| text-align: right; | |
| color: #e8e1c9; | |
| font-weight: 820; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .proof-grid { | |
| display: grid; | |
| grid-template-columns: minmax(280px, 0.95fr) minmax(300px, 1.05fr); | |
| gap: 13px; | |
| align-items: stretch; | |
| } | |
| .baseline-table { | |
| display: grid; | |
| gap: 10px; | |
| } | |
| .baseline-row { | |
| display: grid; | |
| grid-template-columns: 98px 1fr 56px; | |
| align-items: center; | |
| gap: 10px; | |
| min-height: 39px; | |
| color: #e9e2ca; | |
| font-size: 13px; | |
| } | |
| .mini-bar { | |
| height: 12px; | |
| border-radius: 999px; | |
| background: #2a3024; | |
| overflow: hidden; | |
| } | |
| .mini-bar span { | |
| display: block; | |
| height: 100%; | |
| border-radius: inherit; | |
| } | |
| .chart-frame { | |
| min-height: 232px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| background: #f7f4ec; | |
| padding: 8px; | |
| display: grid; | |
| place-items: center; | |
| } | |
| .chart-frame img { | |
| display: block; | |
| width: 100%; | |
| max-height: 360px; | |
| object-fit: contain; | |
| border-radius: 4px; | |
| } | |
| .json-grid { | |
| display: grid; | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| gap: 12px; | |
| } | |
| .json-panel { | |
| min-height: 248px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| background: #0c100a; | |
| overflow: hidden; | |
| } | |
| .json-head { | |
| min-height: 44px; | |
| padding: 10px 12px; | |
| border-bottom: 1px solid #394132; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 10px; | |
| color: var(--cream); | |
| font-size: 13px; | |
| font-weight: 780; | |
| background: rgba(255, 255, 255, 0.02); | |
| } | |
| .json-head span { | |
| color: var(--muted); | |
| font-weight: 620; | |
| font-size: 12px; | |
| } | |
| .json-block { | |
| margin: 0; | |
| min-height: 204px; | |
| padding: 12px; | |
| overflow: auto; | |
| color: #d7fbe8; | |
| font-size: 12px; | |
| line-height: 1.48; | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| } | |
| .playground-meta { | |
| margin-top: 12px; | |
| display: grid; | |
| grid-template-columns: 168px 1fr; | |
| gap: 12px; | |
| } | |
| .playground-card { | |
| min-height: 96px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 12px; | |
| background: var(--panel-2); | |
| } | |
| .playground-card strong { | |
| display: block; | |
| color: var(--cream); | |
| margin-bottom: 7px; | |
| font-size: 14px; | |
| } | |
| .playground-card span { | |
| display: block; | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.4; | |
| } | |
| .story-grid { | |
| display: grid; | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| gap: 12px; | |
| } | |
| .story-lane { | |
| min-height: 250px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 14px; | |
| background: var(--panel-2); | |
| } | |
| .story-lane.before { | |
| border-color: rgba(255, 95, 69, 0.42); | |
| background: var(--flame-soft); | |
| } | |
| .story-lane.after { | |
| border-color: rgba(39, 224, 161, 0.42); | |
| background: var(--jade-soft); | |
| } | |
| .story-title { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 10px; | |
| margin-bottom: 12px; | |
| } | |
| .story-title strong { | |
| color: var(--cream); | |
| font-size: 16px; | |
| } | |
| .story-score { | |
| border-radius: 999px; | |
| padding: 4px 9px; | |
| font-size: 12px; | |
| font-weight: 820; | |
| color: #0b0d08; | |
| background: var(--cream); | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .story-flow { | |
| display: grid; | |
| gap: 9px; | |
| } | |
| .story-step { | |
| min-height: 48px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 10px; | |
| background: rgba(10, 12, 8, 0.68); | |
| color: #ece6cf; | |
| font-size: 13px; | |
| line-height: 1.35; | |
| } | |
| .story-note { | |
| margin-top: 12px; | |
| min-height: 58px; | |
| border: 1px dashed #4a5241; | |
| border-radius: 8px; | |
| padding: 11px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.4; | |
| background: rgba(10, 12, 8, 0.35); | |
| } | |
| .judge-grid { | |
| display: grid; | |
| gap: 12px; | |
| } | |
| .judge-stats { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 10px; | |
| } | |
| .judge-card { | |
| min-height: 132px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 13px; | |
| background: var(--panel-2); | |
| } | |
| .judge-card.good { | |
| border-color: rgba(39, 224, 161, 0.4); | |
| background: var(--jade-soft); | |
| } | |
| .judge-card.warn { | |
| border-color: rgba(245, 186, 65, 0.4); | |
| background: var(--amber-soft); | |
| } | |
| .judge-card.bad { | |
| border-color: rgba(255, 95, 69, 0.4); | |
| background: var(--flame-soft); | |
| } | |
| .judge-card .value { | |
| font-size: 28px; | |
| margin-top: 8px; | |
| } | |
| .judge-card .muted { | |
| display: block; | |
| margin-top: 6px; | |
| line-height: 1.4; | |
| } | |
| .judge-actions { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 10px; | |
| } | |
| .readiness-list { | |
| display: grid; | |
| gap: 10px; | |
| } | |
| .readiness-item { | |
| min-height: 64px; | |
| border: 1px solid rgba(39, 224, 161, 0.38); | |
| border-radius: 8px; | |
| padding: 11px 12px; | |
| background: rgba(39, 224, 161, 0.08); | |
| } | |
| .readiness-item.pending { | |
| border-color: rgba(245, 186, 65, 0.38); | |
| background: rgba(245, 186, 65, 0.09); | |
| } | |
| .readiness-item strong { | |
| display: block; | |
| color: var(--cream); | |
| margin-bottom: 5px; | |
| font-size: 14px; | |
| } | |
| .readiness-item span { | |
| display: block; | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.35; | |
| } | |
| .event-list { | |
| display: grid; | |
| gap: 8px; | |
| max-height: 432px; | |
| overflow: auto; | |
| padding-right: 2px; | |
| } | |
| .event { | |
| display: grid; | |
| grid-template-columns: 48px 1fr 58px; | |
| gap: 10px; | |
| align-items: start; | |
| min-height: 52px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 9px; | |
| background: var(--panel-2); | |
| font-size: 13px; | |
| } | |
| .event strong { | |
| color: var(--cream); | |
| } | |
| .event .reward { | |
| text-align: right; | |
| color: var(--jade); | |
| font-weight: 840; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .flow-line { | |
| display: grid; | |
| grid-template-columns: repeat(6, minmax(0, 1fr)); | |
| gap: 9px; | |
| } | |
| .flow-step { | |
| position: relative; | |
| min-height: 112px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 12px; | |
| background: var(--panel-2); | |
| } | |
| .flow-step::before { | |
| content: attr(data-num); | |
| display: grid; | |
| place-items: center; | |
| width: 28px; | |
| height: 28px; | |
| border-radius: 50%; | |
| border: 1px solid rgba(39, 224, 161, 0.45); | |
| color: #b7ffdf; | |
| background: #101a13; | |
| font-weight: 820; | |
| margin-bottom: 10px; | |
| } | |
| .flow-step strong { | |
| display: block; | |
| color: var(--cream); | |
| margin-bottom: 7px; | |
| } | |
| .flow-step span { | |
| display: block; | |
| color: var(--muted); | |
| font-size: 12px; | |
| line-height: 1.35; | |
| } | |
| .theme-grid { | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 10px; | |
| } | |
| .theme-card { | |
| min-height: 132px; | |
| border: 1px solid #394132; | |
| border-radius: 8px; | |
| padding: 12px; | |
| background: var(--panel-2); | |
| } | |
| .theme-card strong { | |
| display: block; | |
| color: var(--cream); | |
| margin-bottom: 8px; | |
| font-size: 15px; | |
| } | |
| .theme-card span { | |
| display: block; | |
| color: var(--muted); | |
| line-height: 1.38; | |
| font-size: 13px; | |
| } | |
| .theme-card.blue { | |
| border-color: rgba(115, 167, 255, 0.4); | |
| background: var(--blue-soft); | |
| } | |
| .theme-card.green { | |
| border-color: rgba(39, 224, 161, 0.4); | |
| background: var(--jade-soft); | |
| } | |
| .theme-card.amber { | |
| border-color: rgba(245, 186, 65, 0.4); | |
| background: var(--amber-soft); | |
| } | |
| .theme-card.magenta { | |
| border-color: rgba(232, 121, 249, 0.4); | |
| background: var(--magenta-soft); | |
| } | |
| /* Phase 4 visual system rebuild: calmer glass surfaces, cleaner hierarchy, | |
| and per-mode layouts so the page feels like a product instead of one giant board. */ | |
| :root { | |
| --bg: #060912; | |
| --panel: rgba(15, 19, 30, 0.72); | |
| --panel-2: rgba(20, 25, 39, 0.68); | |
| --panel-3: rgba(26, 31, 47, 0.66); | |
| --ink: #f4f7ff; | |
| --muted: #a4aec4; | |
| --faint: #71809f; | |
| --line: rgba(255, 255, 255, 0.1); | |
| --jade: #63dfc0; | |
| --jade-soft: rgba(99, 223, 192, 0.14); | |
| --amber: #ffbf7d; | |
| --amber-soft: rgba(255, 191, 125, 0.14); | |
| --flame: #ff8d93; | |
| --flame-soft: rgba(255, 141, 147, 0.14); | |
| --blue: #8eb5ff; | |
| --blue-soft: rgba(142, 181, 255, 0.14); | |
| --magenta: #c5a3ff; | |
| --magenta-soft: rgba(197, 163, 255, 0.14); | |
| --cream: #f7f9ff; | |
| --shadow: 0 24px 60px rgba(3, 8, 20, 0.36); | |
| } | |
| html { | |
| background: var(--bg); | |
| } | |
| body { | |
| background: | |
| radial-gradient(circle at 12% 0%, rgba(142, 181, 255, 0.18), transparent 24%), | |
| radial-gradient(circle at 88% 8%, rgba(197, 163, 255, 0.14), transparent 22%), | |
| radial-gradient(circle at 60% 100%, rgba(99, 223, 192, 0.08), transparent 22%), | |
| linear-gradient(180deg, #05070d 0%, #0a0d15 46%, #070a12 100%); | |
| background-attachment: fixed; | |
| color: var(--ink); | |
| } | |
| body::before { | |
| content: ""; | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| background: | |
| linear-gradient(rgba(255, 255, 255, 0.022) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px); | |
| background-size: 42px 42px; | |
| mask-image: radial-gradient(circle at center, black 35%, transparent 82%); | |
| opacity: 0.34; | |
| } | |
| header, | |
| .modebar { | |
| background: rgba(8, 11, 19, 0.72); | |
| border-color: rgba(255, 255, 255, 0.08); | |
| backdrop-filter: blur(24px) saturate(1.3); | |
| } | |
| .brand { | |
| gap: 16px; | |
| } | |
| .mark { | |
| border-color: rgba(255, 255, 255, 0.12); | |
| background: | |
| radial-gradient(circle at 30% 28%, rgba(142, 181, 255, 0.45), transparent 58%), | |
| linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)), | |
| rgba(17, 22, 34, 0.82); | |
| color: #eef4ff; | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.18), | |
| 0 12px 34px rgba(5, 10, 22, 0.34); | |
| } | |
| h1 { | |
| font-size: 22px; | |
| color: #f7faff; | |
| } | |
| .subhead, | |
| .view-copy, | |
| .muted, | |
| .label, | |
| .node-copy, | |
| .playground-card span, | |
| .theme-card span, | |
| .readiness-item span, | |
| .flow-step span { | |
| color: var(--muted); | |
| } | |
| button, | |
| select, | |
| input, | |
| .view-tab { | |
| border-color: rgba(255, 255, 255, 0.1); | |
| background: rgba(255, 255, 255, 0.045); | |
| color: var(--ink); | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); | |
| } | |
| button:hover, | |
| .view-tab:hover { | |
| border-color: rgba(255, 255, 255, 0.16); | |
| background: rgba(255, 255, 255, 0.08); | |
| } | |
| button.primary, | |
| .view-tab.active { | |
| border-color: rgba(142, 181, 255, 0.38); | |
| background: | |
| linear-gradient(180deg, rgba(142, 181, 255, 0.22), rgba(142, 181, 255, 0.1)), | |
| rgba(255, 255, 255, 0.05); | |
| color: #f3f8ff; | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12); | |
| } | |
| button.warn { | |
| border-color: rgba(255, 191, 125, 0.32); | |
| background: | |
| linear-gradient(180deg, rgba(255, 191, 125, 0.2), rgba(255, 191, 125, 0.08)), | |
| rgba(255, 255, 255, 0.04); | |
| color: #fff1e0; | |
| } | |
| button.danger { | |
| border-color: rgba(255, 141, 147, 0.3); | |
| background: | |
| linear-gradient(180deg, rgba(255, 141, 147, 0.2), rgba(255, 141, 147, 0.08)), | |
| rgba(255, 255, 255, 0.04); | |
| color: #ffe9eb; | |
| } | |
| .console { | |
| width: min(1480px, 100%); | |
| gap: 16px; | |
| } | |
| .console[data-view="overview"] { | |
| grid-template-columns: minmax(420px, 1.02fr) minmax(360px, 0.98fr); | |
| grid-template-areas: | |
| "hero hero" | |
| "story proof" | |
| "flow themes" | |
| "readiness readiness"; | |
| } | |
| .console[data-view="playground"] { | |
| grid-template-columns: minmax(520px, 1.24fr) minmax(360px, 0.76fr); | |
| grid-template-areas: | |
| "theater command" | |
| "mission playground" | |
| "trust playground" | |
| "events playground"; | |
| } | |
| .console[data-view="judge"] { | |
| grid-template-columns: minmax(500px, 1.05fr) minmax(360px, 0.95fr); | |
| grid-template-areas: | |
| "hero hero" | |
| "story proof" | |
| "theater judge" | |
| "mission judge"; | |
| } | |
| section, | |
| .hero-panel, | |
| .decision-card, | |
| .stat, | |
| .playground-card, | |
| .json-panel, | |
| .story-lane, | |
| .judge-card, | |
| .readiness-item, | |
| .event, | |
| .flow-step, | |
| .theme-card, | |
| .node, | |
| .outcome, | |
| .stage-label { | |
| background: | |
| linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)), | |
| var(--panel); | |
| border-color: rgba(255, 255, 255, 0.1); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.05), | |
| 0 18px 50px rgba(3, 8, 20, 0.22); | |
| backdrop-filter: blur(22px) saturate(1.3); | |
| } | |
| .section-head, | |
| .json-head { | |
| background: rgba(255, 255, 255, 0.025); | |
| border-bottom-color: rgba(255, 255, 255, 0.07); | |
| } | |
| h2 { | |
| color: rgba(247, 249, 255, 0.84); | |
| letter-spacing: 0.08em; | |
| } | |
| .chip { | |
| border-color: rgba(255, 255, 255, 0.1); | |
| background: rgba(255, 255, 255, 0.055); | |
| color: #dde5f6; | |
| } | |
| .chip.live { | |
| border-color: rgba(142, 181, 255, 0.24); | |
| color: #dbe8ff; | |
| background: rgba(142, 181, 255, 0.12); | |
| } | |
| .chip.warn { | |
| border-color: rgba(255, 191, 125, 0.22); | |
| color: #ffe7c8; | |
| background: rgba(255, 191, 125, 0.12); | |
| } | |
| .chip.fail { | |
| border-color: rgba(255, 141, 147, 0.22); | |
| color: #ffd7da; | |
| background: rgba(255, 141, 147, 0.12); | |
| } | |
| .stage { | |
| border-color: rgba(255, 255, 255, 0.11); | |
| background: | |
| radial-gradient(circle at 50% 12%, rgba(142, 181, 255, 0.22), transparent 18%), | |
| radial-gradient(circle at 82% 82%, rgba(197, 163, 255, 0.18), transparent 18%), | |
| linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(255, 255, 255, 0.016) 1px, transparent 1px), | |
| linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)), | |
| rgba(9, 13, 23, 0.72); | |
| background-size: auto, auto, 32px 32px, 32px 32px, auto, auto; | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.05), | |
| 0 24px 60px rgba(3, 8, 20, 0.28); | |
| } | |
| .stage::after { | |
| border-color: rgba(255, 255, 255, 0.06); | |
| } | |
| .brain-card { | |
| border-color: rgba(142, 181, 255, 0.28); | |
| background: | |
| radial-gradient(circle at 20% 20%, rgba(142, 181, 255, 0.22), transparent 42%), | |
| linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)), | |
| rgba(18, 23, 35, 0.72); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.08), | |
| 0 22px 48px rgba(3, 8, 20, 0.32); | |
| } | |
| .signal-line { | |
| background: linear-gradient(180deg, rgba(142, 181, 255, 0.72), rgba(197, 163, 255, 0.56), rgba(99, 223, 192, 0.46)); | |
| } | |
| .node.active { | |
| border-color: rgba(142, 181, 255, 0.34); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.06), | |
| 0 0 0 1px rgba(142, 181, 255, 0.16); | |
| } | |
| .node.watch { | |
| border-color: rgba(255, 191, 125, 0.34); | |
| } | |
| .node.quarantine { | |
| border-color: rgba(255, 141, 147, 0.38); | |
| } | |
| .node-id, | |
| .node-title, | |
| .outcome strong, | |
| .hero-panel h3, | |
| .judge-card h3, | |
| .story-title strong, | |
| .decision-title strong, | |
| .theme-card strong, | |
| .flow-step strong, | |
| .readiness-item strong, | |
| .playground-card strong { | |
| color: #f7f9ff; | |
| } | |
| .trust-rail, | |
| .bar, | |
| .progress, | |
| .risk-track, | |
| .mini-bar { | |
| background: rgba(255, 255, 255, 0.08); | |
| } | |
| .progress span { | |
| background: linear-gradient(90deg, #ff9e8c, #c5a3ff, #8eb5ff); | |
| } | |
| .risk-track span { | |
| background: linear-gradient(90deg, #63dfc0, #8eb5ff, #ffbf7d); | |
| } | |
| .recommendation-output { | |
| border-color: rgba(142, 181, 255, 0.2); | |
| background: rgba(142, 181, 255, 0.1); | |
| color: #e6efff; | |
| } | |
| .story-lane.before, | |
| .judge-card.bad { | |
| background: | |
| linear-gradient(180deg, rgba(255, 141, 147, 0.14), rgba(255, 141, 147, 0.05)), | |
| rgba(18, 22, 34, 0.7); | |
| border-color: rgba(255, 141, 147, 0.2); | |
| } | |
| .story-lane.after, | |
| .judge-card.good { | |
| background: | |
| linear-gradient(180deg, rgba(142, 181, 255, 0.14), rgba(99, 223, 192, 0.06)), | |
| rgba(18, 22, 34, 0.7); | |
| border-color: rgba(142, 181, 255, 0.2); | |
| } | |
| .judge-card.warn, | |
| .readiness-item.pending { | |
| background: | |
| linear-gradient(180deg, rgba(255, 191, 125, 0.14), rgba(255, 191, 125, 0.05)), | |
| rgba(18, 22, 34, 0.7); | |
| border-color: rgba(255, 191, 125, 0.2); | |
| } | |
| .story-score { | |
| color: #f4f7ff; | |
| background: rgba(255, 255, 255, 0.08); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .chart-frame { | |
| background: | |
| linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04)), | |
| rgba(246, 248, 253, 0.96); | |
| border-color: rgba(255, 255, 255, 0.2); | |
| } | |
| .hero-panel.primary { | |
| border-color: rgba(142, 181, 255, 0.18); | |
| background: | |
| radial-gradient(circle at 82% 12%, rgba(99, 223, 192, 0.12), transparent 22%), | |
| radial-gradient(circle at 18% 0%, rgba(142, 181, 255, 0.18), transparent 24%), | |
| linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)), | |
| rgba(18, 22, 34, 0.72); | |
| } | |
| .hero-callout, | |
| .hero-step, | |
| .judge-step, | |
| .story-step, | |
| .story-note, | |
| .hero-stat { | |
| background: | |
| linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.015)), | |
| rgba(11, 15, 24, 0.56); | |
| border-color: rgba(255, 255, 255, 0.08); | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); | |
| } | |
| .hero-stat .value, | |
| .judge-card .value, | |
| .value { | |
| color: #f7f9ff; | |
| } | |
| .json-block { | |
| color: #dce8ff; | |
| } | |
| .event .reward { | |
| color: #9bd0ff; | |
| } | |
| ::-webkit-scrollbar { | |
| width: 10px; | |
| height: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: rgba(255, 255, 255, 0.14); | |
| border-radius: 999px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(255, 255, 255, 0.04); | |
| } | |
| @media (max-width: 1180px) { | |
| .console { | |
| grid-template-columns: 1fr; | |
| grid-template-areas: | |
| "hero" | |
| "theater" | |
| "command" | |
| "mission" | |
| "trust" | |
| "playground" | |
| "story" | |
| "judge" | |
| "readiness" | |
| "proof" | |
| "events" | |
| "flow" | |
| "themes"; | |
| } | |
| .console[data-view="overview"] { | |
| grid-template-columns: 1fr; | |
| grid-template-areas: | |
| "hero" | |
| "story" | |
| "proof" | |
| "flow" | |
| "themes" | |
| "readiness"; | |
| } | |
| .console[data-view="playground"] { | |
| grid-template-columns: 1fr; | |
| grid-template-areas: | |
| "theater" | |
| "command" | |
| "mission" | |
| "trust" | |
| "playground" | |
| "events"; | |
| } | |
| .console[data-view="judge"] { | |
| grid-template-columns: 1fr; | |
| grid-template-areas: | |
| "hero" | |
| "story" | |
| "proof" | |
| "theater" | |
| "mission" | |
| "judge"; | |
| } | |
| header { | |
| grid-template-columns: 1fr; | |
| } | |
| .header-actions { | |
| justify-content: flex-start; | |
| } | |
| } | |
| @media (max-width: 860px) { | |
| .console { | |
| padding: 12px; | |
| } | |
| .stage { | |
| min-height: 620px; | |
| } | |
| .stage-topline, | |
| .outcome-strip, | |
| .hero-grid, | |
| .hero-stats, | |
| .proof-grid, | |
| .json-grid, | |
| .playground-meta, | |
| .story-grid, | |
| .judge-stats, | |
| .judge-actions, | |
| .flow-line, | |
| .theme-grid, | |
| .stats-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .brain-card { | |
| top: 190px; | |
| } | |
| .signal-line { | |
| display: none; | |
| } | |
| .specialist-grid { | |
| top: 315px; | |
| bottom: auto; | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| } | |
| .outcome-strip { | |
| bottom: 12px; | |
| } | |
| .action-row { | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| } | |
| } | |
| @media (max-width: 560px) { | |
| header { | |
| padding: 13px; | |
| } | |
| .field-row, | |
| .button-row, | |
| .recommendation, | |
| .specialist, | |
| .event, | |
| .baseline-row { | |
| grid-template-columns: 1fr; | |
| } | |
| input { | |
| width: 100%; | |
| } | |
| .modebar { | |
| padding: 0 13px 12px; | |
| } | |
| .view-tabs { | |
| width: 100%; | |
| flex-direction: column; | |
| align-items: stretch; | |
| } | |
| .view-tab { | |
| width: 100%; | |
| } | |
| .specialist-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .stage { | |
| min-height: 980px; | |
| } | |
| .event .reward, | |
| .score { | |
| text-align: left; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="shell"> | |
| <header> | |
| <div class="brand"> | |
| <div class="mark">S</div> | |
| <div> | |
| <h1>SENTINEL Trust Mission Control</h1> | |
| <div class="subhead">OpenEnv RL environment for adversarial multi-agent trust calibration</div> | |
| </div> | |
| </div> | |
| <div class="header-actions"> | |
| <select id="taskSelect" aria-label="Task"> | |
| <option value="task1">Task 1 - Easy</option> | |
| <option value="task2">Task 2 - Medium</option> | |
| <option value="task3" selected>Task 3 - Hard</option> | |
| </select> | |
| <input id="seedInput" aria-label="Seed" type="number" value="42"> | |
| <button id="resetBtn" class="primary" type="button">Reset Episode</button> | |
| <button id="swapBtn" class="warn" type="button">Swap Profiles</button> | |
| <button id="autoBtn" type="button">Heuristic Auto</button> | |
| </div> | |
| </header> | |
| <div class="modebar"> | |
| <div class="modebar-inner"> | |
| <div class="view-tabs"> | |
| <button id="viewOverviewBtn" class="view-tab active" type="button">Overview</button> | |
| <button id="viewPlaygroundBtn" class="view-tab" type="button">Playground</button> | |
| <button id="viewJudgeBtn" class="view-tab" type="button">Judge Demo</button> | |
| </div> | |
| <div id="viewCopy" class="view-copy">Overview turns the environment into a judge-readable system story: the problem, the learning signal, and the live failure mode it fixes.</div> | |
| </div> | |
| </div> | |
| <main id="console" class="console"> | |
| <section class="hero"> | |
| <div class="section-head"> | |
| <h2>System Overview</h2> | |
| <div class="chips"> | |
| <span class="chip live">reset → step → state</span> | |
| <span class="chip">OpenEnv compatible</span> | |
| <span class="chip warn">skill, not identity</span> | |
| </div> | |
| </div> | |
| <div class="body"> | |
| <div class="hero-grid"> | |
| <div class="hero-panel primary"> | |
| <h3>What SENTINEL actually teaches</h3> | |
| <p>SENTINEL is not training a specialist to solve one domain task. It trains the orchestrator to decide who to trust, when to verify, when to self-solve, and how to recover when one public slot turns unreliable or adversarial inside a long multi-agent task graph.</p> | |
| <div class="hero-callouts"> | |
| <div class="hero-callout"> | |
| <strong>Observation model</strong> | |
| The orchestrator only sees behavior: public slots, trust scores, stakes, step budget, and outcomes. | |
| </div> | |
| <div class="hero-callout"> | |
| <strong>Core novelty</strong> | |
| Hidden specialist profiles reshuffle every reset, so the agent cannot memorize that S2 or S3 is dangerous. | |
| </div> | |
| <div class="hero-callout"> | |
| <strong>Judge takeaway</strong> | |
| This environment turns blind agent-to-agent trust into a trainable oversight skill. | |
| </div> | |
| </div> | |
| <div class="hero-stats"> | |
| <div class="hero-stat"> | |
| <div class="label">Random overall</div> | |
| <div id="heroRandomScore" class="value">0.695</div> | |
| </div> | |
| <div class="hero-stat"> | |
| <div class="label">Heuristic overall</div> | |
| <div id="heroHeuristicScore" class="value">0.796</div> | |
| </div> | |
| <div class="hero-stat"> | |
| <div class="label">Task 3 detect</div> | |
| <div id="heroDetectionScore" class="value">0.735</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="hero-panel"> | |
| <h3>How to test this fast</h3> | |
| <div class="hero-steps"> | |
| <div class="hero-step"> | |
| <strong>1. Overview mode</strong> | |
| Read the before/after lanes and reward proof. This tells the story in judge language. | |
| </div> | |
| <div class="hero-step"> | |
| <strong>2. Playground mode</strong> | |
| Reset an episode, click Auto Policy, and watch the API payloads, trust bars, and reward stream update. | |
| </div> | |
| <div class="hero-step"> | |
| <strong>3. Judge Demo mode</strong> | |
| Run Random, then Heuristic, then Swap + Replay. That is the live finale sequence. | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="theater"> | |
| <div class="section-head"> | |
| <h2>Live Trust Theater</h2> | |
| <div class="chips"> | |
| <span id="statusChip" class="chip live">READY</span> | |
| <span id="scenarioChip" class="chip">SCENARIO</span> | |
| </div> | |
| </div> | |
| <div class="body"> | |
| <div class="stage"> | |
| <div class="stage-topline"> | |
| <div class="stage-label"> | |
| <span>Profile rule</span> | |
| <strong>reshuffle on reset</strong> | |
| </div> | |
| <div class="stage-label"> | |
| <span>Observation rule</span> | |
| <strong>behavior only</strong> | |
| </div> | |
| <div class="stage-label"> | |
| <span>Failure mode</span> | |
| <strong>high-stakes poison</strong> | |
| </div> | |
| </div> | |
| <div class="brain-card"> | |
| <div class="node-title">Orchestrator</div> | |
| <div id="leadMove" class="node-copy">Reset starts a fresh trust game.</div> | |
| </div> | |
| <div class="signal-line"></div> | |
| <div class="specialist-grid" id="networkGrid"> | |
| <div id="node-S0" class="node" style="--trust:0.5;--node-color:#73a7ff"> | |
| <div class="node-id"><span>S0</span><span>0.50</span></div> | |
| <div class="node-trust"><div class="trust-rail"><span></span></div></div> | |
| <div class="node-meta"><span>public slot</span><span>watch</span></div> | |
| </div> | |
| <div id="node-S1" class="node" style="--trust:0.5;--node-color:#73a7ff"> | |
| <div class="node-id"><span>S1</span><span>0.50</span></div> | |
| <div class="node-trust"><div class="trust-rail"><span></span></div></div> | |
| <div class="node-meta"><span>public slot</span><span>watch</span></div> | |
| </div> | |
| <div id="node-S2" class="node" style="--trust:0.5;--node-color:#73a7ff"> | |
| <div class="node-id"><span>S2</span><span>0.50</span></div> | |
| <div class="node-trust"><div class="trust-rail"><span></span></div></div> | |
| <div class="node-meta"><span>public slot</span><span>watch</span></div> | |
| </div> | |
| <div id="node-S3" class="node" style="--trust:0.5;--node-color:#73a7ff"> | |
| <div class="node-id"><span>S3</span><span>0.50</span></div> | |
| <div class="node-trust"><div class="trust-rail"><span></span></div></div> | |
| <div class="node-meta"><span>public slot</span><span>watch</span></div> | |
| </div> | |
| <div id="node-S4" class="node" style="--trust:0.5;--node-color:#73a7ff"> | |
| <div class="node-id"><span>S4</span><span>0.50</span></div> | |
| <div class="node-trust"><div class="trust-rail"><span></span></div></div> | |
| <div class="node-meta"><span>public slot</span><span>watch</span></div> | |
| </div> | |
| </div> | |
| <div class="outcome-strip"> | |
| <div class="outcome"> | |
| <span>Recommended move</span> | |
| <strong id="stageMove">delegate:S0</strong> | |
| </div> | |
| <div class="outcome"> | |
| <span>Adversarial signals</span> | |
| <strong id="stageSignals">0 detected / 0 poison</strong> | |
| </div> | |
| <div class="outcome"> | |
| <span>Trust objective</span> | |
| <strong>skill, not identity</strong> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="command"> | |
| <div class="section-head"> | |
| <h2>Command Deck</h2> | |
| <span id="sessionText" class="muted">No session</span> | |
| </div> | |
| <div class="body"> | |
| <div class="command-grid"> | |
| <div class="decision-card"> | |
| <div class="decision-title"> | |
| <strong>Route Decision</strong> | |
| <span id="recommendChip" class="chip live">delegate:S0</span> | |
| </div> | |
| <select id="specialistSelect" aria-label="Specialist"></select> | |
| <div class="recommendation"> | |
| <div id="recommendText" class="recommendation-output">Waiting for episode state.</div> | |
| <button id="applyRecommendBtn" class="primary" type="button">Apply</button> | |
| </div> | |
| </div> | |
| <div class="action-row"> | |
| <button id="delegateBtn" class="primary" type="button">Delegate</button> | |
| <button id="verifyBtn" class="warn" type="button">Verify</button> | |
| <button id="selfBtn" type="button">Self Solve</button> | |
| <button id="skipBtn" class="danger" type="button">Skip</button> | |
| </div> | |
| <div class="button-row"> | |
| <button id="resetPanelBtn" type="button">New Seed Run</button> | |
| <button id="swapPanelBtn" class="warn" type="button">Profile Swap</button> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="mission"> | |
| <div class="section-head"> | |
| <h2>Mission State</h2> | |
| <div class="chips"> | |
| <span id="detectChip" class="chip live">0 detected</span> | |
| <span id="poisonChip" class="chip warn">0 poison</span> | |
| </div> | |
| </div> | |
| <div class="body"> | |
| <div class="stats-grid"> | |
| <div class="stat"> | |
| <div class="label">Score</div> | |
| <div id="scoreValue" class="value">0.000</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="label">Step Budget</div> | |
| <div id="stepValue" class="value">0/45</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="label">Subtasks Done</div> | |
| <div id="completeValue" class="value">0/20</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="label">Stakes</div> | |
| <div id="stakesValue" class="value">0.00</div> | |
| </div> | |
| </div> | |
| <div class="progress"><span id="progressFill"></span></div> | |
| <div class="risk-meter"> | |
| <div class="label">Risk gate</div> | |
| <div class="risk-track"><span id="riskFill"></span></div> | |
| </div> | |
| <div id="subtaskText" class="subtask">Reset an episode to begin.</div> | |
| </div> | |
| </section> | |
| <section class="trust"> | |
| <div class="section-head"> | |
| <h2>Bayesian Trust Ledger</h2> | |
| <span id="trustMean" class="muted">mean 0.50</span> | |
| </div> | |
| <div class="body"> | |
| <div id="trustList" class="trust-list"></div> | |
| </div> | |
| </section> | |
| <section class="playground"> | |
| <div class="section-head"> | |
| <h2>API Playground</h2> | |
| <div class="chips"> | |
| <span id="endpointChip" class="chip">POST /reset</span> | |
| <span class="chip live">backend visible</span> | |
| </div> | |
| </div> | |
| <div class="body"> | |
| <div class="json-grid"> | |
| <div class="json-panel"> | |
| <div class="json-head"> | |
| <strong>Last Request</strong> | |
| <span>what UI sent</span> | |
| </div> | |
| <pre id="requestJson" class="json-block">{ | |
| "status": "waiting", | |
| "message": "Reset or step to inspect backend payloads." | |
| }</pre> | |
| </div> | |
| <div class="json-panel"> | |
| <div class="json-head"> | |
| <strong>Last Response</strong> | |
| <span>what backend returned</span> | |
| </div> | |
| <pre id="responseJson" class="json-block">{ | |
| "status": "waiting", | |
| "message": "Observation, reward, and info will appear here." | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="playground-meta"> | |
| <div class="playground-card"> | |
| <strong id="playgroundMode">reset()</strong> | |
| <span id="playgroundCopy">Start a new episode, get the first observation, then choose actions step by step.</span> | |
| </div> | |
| <div class="playground-card"> | |
| <strong>What To Watch</strong> | |
| <span id="playgroundSummary">Observation tells you the next subtask and public trust scores. Reward tells you whether routing was correct. Info tells you the normalized episode score and adversarial counters.</span> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="story"> | |
| <div class="section-head"> | |
| <h2>Before And After</h2> | |
| <div class="chips"> | |
| <span class="chip fail">blind trust</span> | |
| <span class="chip live">trained skepticism</span> | |
| </div> | |
| </div> | |
| <div class="body"> | |
| <div class="story-grid"> | |
| <div class="story-lane before"> | |
| <div class="story-title"> | |
| <strong>Without SENTINEL</strong> | |
| <span id="storyBeforeScore" class="story-score">task3 random 0.666</span> | |
| </div> | |
| <div class="story-flow"> | |
| <div class="story-step">All public slots start near the same trust. The orchestrator delegates with weak evidence.</div> | |
| <div class="story-step">A high-confidence specialist can slip poisoned output into a high-stakes node.</div> | |
| <div class="story-step">Downstream subtasks inherit bad state, so the mission drifts before anyone notices.</div> | |
| <div class="story-step">Detection stays weak and the agent cannot explain which public slot became dangerous.</div> | |
| </div> | |
| <div class="story-note">Judge takeaway: good-looking multi-agent systems still fail if trust is static or role-based.</div> | |
| </div> | |
| <div class="story-lane after"> | |
| <div class="story-title"> | |
| <strong>With SENTINEL</strong> | |
| <span id="storyAfterScore" class="story-score">task3 heuristic 0.784</span> | |
| </div> | |
| <div class="story-flow"> | |
| <div class="story-step">Behavior updates the TrustLedger after every step, so public slots diverge quickly.</div> | |
| <div class="story-step">When stakes rise and trust is shaky, the orchestrator switches from delegate to verify.</div> | |
| <div class="story-step">Adversarial attempts are detected before they cascade through the task graph.</div> | |
| <div class="story-step">Profile swap forces re-learning from evidence, proving skill instead of memorized identity.</div> | |
| </div> | |
| <div class="story-note">Judge takeaway: this environment teaches oversight, recovery, and calibrated delegation under uncertainty.</div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="judge"> | |
| <div class="section-head"> | |
| <h2>Judge Demo Rail</h2> | |
| <div class="chips"> | |
| <span class="chip live">3-minute flow</span> | |
| <span class="chip">one-click policies</span> | |
| </div> | |
| </div> | |
| <div class="body"> | |
| <div class="judge-grid"> | |
| <div class="judge-stats"> | |
| <div class="judge-card bad"> | |
| <div class="label">Random baseline</div> | |
| <div id="judgeRandomScore" class="value">0.695</div> | |
| <span class="muted">Blind delegation baseline. Good enough to move, weak at skepticism.</span> | |
| </div> | |
| <div class="judge-card warn"> | |
| <div class="label">Heuristic policy</div> | |
| <div id="judgeHeuristicScore" class="value">0.796</div> | |
| <span class="muted">Trust-weighted routing plus verification at risky gates.</span> | |
| </div> | |
| <div class="judge-card good"> | |
| <div class="label">Task 3 detection</div> | |
| <div id="judgeDetectionScore" class="value">0.735</div> | |
| <span class="muted">Adversarial detections before poison can cascade into later nodes.</span> | |
| </div> | |
| </div> | |
| <div class="judge-actions"> | |
| <button id="randomPolicyBtn" class="danger" type="button">Run Random</button> | |
| <button id="heuristicPolicyBtn" class="primary" type="button">Run Heuristic</button> | |
| <button id="judgeSwapBtn" class="warn" type="button">Swap + Replay</button> | |
| </div> | |
| <div class="judge-list"> | |
| <div class="judge-step"> | |
| <strong>Step 1 — show the failure</strong> | |
| Run Random to show how similar-looking trust scores lead to brittle routing and weak detection. | |
| </div> | |
| <div class="judge-step"> | |
| <strong>Step 2 — show the learned behavior</strong> | |
| Run Heuristic to show trust divergence, verification at risky gates, and cleaner recovery. | |
| </div> | |
| <div class="judge-step"> | |
| <strong>Step 3 — show generalization</strong> | |
| Hit Swap + Replay so hidden roles reshuffle and the orchestrator has to learn from fresh evidence again. | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="readiness"> | |
| <div class="section-head"> | |
| <h2>Hackathon Readiness</h2> | |
| <span class="muted">what is done vs what is left</span> | |
| </div> | |
| <div class="body"> | |
| <div class="readiness-list"> | |
| <div class="readiness-item"> | |
| <strong>Environment Core Ready</strong> | |
| <span>OpenEnv shape works: reset, step, state, normalized score, Docker, Space, and live dashboard.</span> | |
| </div> | |
| <div class="readiness-item"> | |
| <strong>Reward Proof Ready</strong> | |
| <span>Random, heuristic, and oracle-lite comparisons are committed and visible in the UI.</span> | |
| </div> | |
| <div class="readiness-item"> | |
| <strong>Training Harness Ready</strong> | |
| <span>TRL and Unsloth dry-run path exists; onsite job is to capture the real reward-improvement curve.</span> | |
| </div> | |
| <div class="readiness-item pending"> | |
| <strong>Still Needed For Finale</strong> | |
| <span>Mini-blog or video, onsite GRPO run, and one polished 3-minute story using this dashboard plus before/after evidence.</span> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="proof"> | |
| <div class="section-head"> | |
| <h2>Reward Signal Proof</h2> | |
| <span class="muted">random to heuristic to oracle-lite</span> | |
| </div> | |
| <div class="body"> | |
| <div class="proof-grid"> | |
| <div class="baseline-table"> | |
| <div class="baseline-row"> | |
| <span>Random</span> | |
| <div class="mini-bar"><span id="proofRandomBar" style="width:69.5%;background:#ff5f45"></span></div> | |
| <strong id="proofRandomScore">0.695</strong> | |
| </div> | |
| <div class="baseline-row"> | |
| <span>Heuristic</span> | |
| <div class="mini-bar"><span id="proofHeuristicBar" style="width:79.6%;background:#73a7ff"></span></div> | |
| <strong id="proofHeuristicScore">0.796</strong> | |
| </div> | |
| <div class="baseline-row"> | |
| <span>Oracle-lite</span> | |
| <div class="mini-bar"><span id="proofOracleBar" style="width:85.5%;background:#27e0a1"></span></div> | |
| <strong id="proofOracleScore">0.855</strong> | |
| </div> | |
| <div class="baseline-row"> | |
| <span>T3 detect</span> | |
| <div class="mini-bar"><span id="proofDetectBar" style="width:73.5%;background:#f5ba41"></span></div> | |
| <strong id="proofDetectScore">0.735</strong> | |
| </div> | |
| </div> | |
| <div class="chart-frame"> | |
| <img src="/assets/baseline_comparison.png" alt="SENTINEL baseline comparison chart"> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="events"> | |
| <div class="section-head"> | |
| <h2>Flight Recorder</h2> | |
| <span id="rewardText" class="muted">last reward 0.00</span> | |
| </div> | |
| <div class="body"> | |
| <div id="eventList" class="event-list"></div> | |
| </div> | |
| </section> | |
| <section class="flow"> | |
| <div class="section-head"> | |
| <h2>Code Flow</h2> | |
| <span class="muted">reset, step, state</span> | |
| </div> | |
| <div class="body"> | |
| <div class="flow-line"> | |
| <div class="flow-step" data-num="1"> | |
| <strong>Reset</strong> | |
| <span>environment.py samples scenario, resets graph, ledger, and specialist profile.</span> | |
| </div> | |
| <div class="flow-step" data-num="2"> | |
| <strong>Observe</strong> | |
| <span>agent sees subtask, stakes, trust snapshot, step budget, and public slots.</span> | |
| </div> | |
| <div class="flow-step" data-num="3"> | |
| <strong>Act</strong> | |
| <span>delegate, verify, self solve, or skip through the OpenEnv step API.</span> | |
| </div> | |
| <div class="flow-step" data-num="4"> | |
| <strong>Specialist</strong> | |
| <span>scripted FSM returns outcome, confidence, cost, and adversarial flag.</span> | |
| </div> | |
| <div class="flow-step" data-num="5"> | |
| <strong>Ledger</strong> | |
| <span>trust_ledger.py updates public slot reliability from observed behavior.</span> | |
| </div> | |
| <div class="flow-step" data-num="6"> | |
| <strong>Reward</strong> | |
| <span>graders.py scores completion, detection, calibration, and efficiency.</span> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="themes"> | |
| <div class="section-head"> | |
| <h2>Hackathon Fit</h2> | |
| <span class="muted">judge story map</span> | |
| </div> | |
| <div class="body"> | |
| <div class="theme-grid"> | |
| <div class="theme-card blue"> | |
| <strong>Theme 1</strong> | |
| <span>Orchestrator manages five partially observable actors under adversarial pressure.</span> | |
| </div> | |
| <div class="theme-card green"> | |
| <strong>Theme 2</strong> | |
| <span>Long-horizon task graph with budget pressure, retries, and delayed terminal reward.</span> | |
| </div> | |
| <div class="theme-card amber"> | |
| <strong>Theme 4</strong> | |
| <span>Profile shuffle creates an adaptive curriculum and blocks identity memorization.</span> | |
| </div> | |
| <div class="theme-card magenta"> | |
| <strong>Wild Card</strong> | |
| <span>Turns blind agent-to-agent trust into a trainable safety and oversight skill.</span> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| <script> | |
| const ids = ["S0", "S1", "S2", "S3", "S4"]; | |
| const state = { | |
| sessionId: null, | |
| taskType: "task3", | |
| observation: null, | |
| done: true, | |
| running: false, | |
| events: [], | |
| lastRequest: null, | |
| lastResult: null, | |
| lastMode: "reset()", | |
| view: "overview", | |
| evaluation: null, | |
| demoPolicy: "heuristic" | |
| }; | |
| const el = { | |
| console: document.getElementById("console"), | |
| taskSelect: document.getElementById("taskSelect"), | |
| seedInput: document.getElementById("seedInput"), | |
| resetBtn: document.getElementById("resetBtn"), | |
| resetPanelBtn: document.getElementById("resetPanelBtn"), | |
| swapBtn: document.getElementById("swapBtn"), | |
| swapPanelBtn: document.getElementById("swapPanelBtn"), | |
| autoBtn: document.getElementById("autoBtn"), | |
| viewOverviewBtn: document.getElementById("viewOverviewBtn"), | |
| viewPlaygroundBtn: document.getElementById("viewPlaygroundBtn"), | |
| viewJudgeBtn: document.getElementById("viewJudgeBtn"), | |
| viewCopy: document.getElementById("viewCopy"), | |
| randomPolicyBtn: document.getElementById("randomPolicyBtn"), | |
| heuristicPolicyBtn: document.getElementById("heuristicPolicyBtn"), | |
| judgeSwapBtn: document.getElementById("judgeSwapBtn"), | |
| specialistSelect: document.getElementById("specialistSelect"), | |
| recommendChip: document.getElementById("recommendChip"), | |
| recommendText: document.getElementById("recommendText"), | |
| applyRecommendBtn: document.getElementById("applyRecommendBtn"), | |
| delegateBtn: document.getElementById("delegateBtn"), | |
| verifyBtn: document.getElementById("verifyBtn"), | |
| selfBtn: document.getElementById("selfBtn"), | |
| skipBtn: document.getElementById("skipBtn"), | |
| statusChip: document.getElementById("statusChip"), | |
| scenarioChip: document.getElementById("scenarioChip"), | |
| scoreValue: document.getElementById("scoreValue"), | |
| stepValue: document.getElementById("stepValue"), | |
| completeValue: document.getElementById("completeValue"), | |
| stakesValue: document.getElementById("stakesValue"), | |
| progressFill: document.getElementById("progressFill"), | |
| riskFill: document.getElementById("riskFill"), | |
| subtaskText: document.getElementById("subtaskText"), | |
| trustList: document.getElementById("trustList"), | |
| trustMean: document.getElementById("trustMean"), | |
| endpointChip: document.getElementById("endpointChip"), | |
| requestJson: document.getElementById("requestJson"), | |
| responseJson: document.getElementById("responseJson"), | |
| playgroundMode: document.getElementById("playgroundMode"), | |
| playgroundCopy: document.getElementById("playgroundCopy"), | |
| playgroundSummary: document.getElementById("playgroundSummary"), | |
| detectChip: document.getElementById("detectChip"), | |
| poisonChip: document.getElementById("poisonChip"), | |
| sessionText: document.getElementById("sessionText"), | |
| eventList: document.getElementById("eventList"), | |
| leadMove: document.getElementById("leadMove"), | |
| stageMove: document.getElementById("stageMove"), | |
| stageSignals: document.getElementById("stageSignals"), | |
| rewardText: document.getElementById("rewardText"), | |
| heroRandomScore: document.getElementById("heroRandomScore"), | |
| heroHeuristicScore: document.getElementById("heroHeuristicScore"), | |
| heroDetectionScore: document.getElementById("heroDetectionScore"), | |
| storyBeforeScore: document.getElementById("storyBeforeScore"), | |
| storyAfterScore: document.getElementById("storyAfterScore"), | |
| judgeRandomScore: document.getElementById("judgeRandomScore"), | |
| judgeHeuristicScore: document.getElementById("judgeHeuristicScore"), | |
| judgeDetectionScore: document.getElementById("judgeDetectionScore"), | |
| proofRandomBar: document.getElementById("proofRandomBar"), | |
| proofRandomScore: document.getElementById("proofRandomScore"), | |
| proofHeuristicBar: document.getElementById("proofHeuristicBar"), | |
| proofHeuristicScore: document.getElementById("proofHeuristicScore"), | |
| proofOracleBar: document.getElementById("proofOracleBar"), | |
| proofOracleScore: document.getElementById("proofOracleScore"), | |
| proofDetectBar: document.getElementById("proofDetectBar"), | |
| proofDetectScore: document.getElementById("proofDetectScore") | |
| }; | |
| function trustColor(value) { | |
| if (value >= 0.72) return "#63dfc0"; | |
| if (value >= 0.48) return "#8eb5ff"; | |
| if (value >= 0.30) return "#ffbf7d"; | |
| return "#ff8d93"; | |
| } | |
| function trustStatus(value) { | |
| if (value >= 0.72) return "trusted"; | |
| if (value >= 0.48) return "watch"; | |
| if (value >= 0.30) return "risk"; | |
| return "quarantine"; | |
| } | |
| function prettyJson(value) { | |
| return JSON.stringify(value, null, 2); | |
| } | |
| function bestSpecialist() { | |
| const obs = state.observation; | |
| if (!obs) return "S0"; | |
| return obs.available_specialists | |
| .slice() | |
| .sort((a, b) => (obs.trust_snapshot[b] ?? 0.5) - (obs.trust_snapshot[a] ?? 0.5))[0]; | |
| } | |
| function recommendedMove() { | |
| const obs = state.observation; | |
| if (!obs) return {type: "delegate", specialist: "S0", trust: 0.5}; | |
| const best = bestSpecialist(); | |
| const trust = obs.trust_snapshot[best] ?? 0.5; | |
| const highStakes = obs.stakes_level >= 0.70; | |
| if (highStakes && trust < 0.65) return {type: "verify", specialist: best, trust}; | |
| return {type: "delegate", specialist: best, trust}; | |
| } | |
| function randomMove() { | |
| const obs = state.observation; | |
| if (!obs) return {type: "delegate", specialist: "S0", trust: 0.5}; | |
| const available = obs.available_specialists || ids; | |
| const specialist = available[Math.floor(Math.random() * available.length)] || "S0"; | |
| return {type: "delegate", specialist, trust: obs.trust_snapshot?.[specialist] ?? 0.5}; | |
| } | |
| function setView(view) { | |
| state.view = view; | |
| const sectionViews = { | |
| hero: ["overview", "judge"], | |
| theater: ["playground", "judge"], | |
| command: ["playground"], | |
| mission: ["playground", "judge"], | |
| trust: ["playground"], | |
| playground: ["playground"], | |
| story: ["overview", "judge"], | |
| judge: ["judge"], | |
| readiness: ["overview"], | |
| proof: ["overview", "judge"], | |
| events: ["playground"], | |
| flow: ["overview"], | |
| themes: ["overview"] | |
| }; | |
| Object.entries(sectionViews).forEach(([name, views]) => { | |
| const node = document.querySelector(`section.${name}`); | |
| if (!node) return; | |
| node.classList.toggle("section-hidden", !views.includes(view)); | |
| }); | |
| el.viewOverviewBtn.classList.toggle("active", view === "overview"); | |
| el.viewPlaygroundBtn.classList.toggle("active", view === "playground"); | |
| el.viewJudgeBtn.classList.toggle("active", view === "judge"); | |
| if (el.console) { | |
| el.console.dataset.view = view; | |
| } | |
| const copy = { | |
| overview: "Overview is the clean narrative surface: what SENTINEL solves, how reward proves learning, and why trust calibration matters for real multi-agent systems.", | |
| playground: "Playground is the engineering surface: live trust state, action routing, backend request and response payloads, and flight-recorder events.", | |
| judge: "Judge Demo is the pitch surface: baseline failure, heuristic recovery, then profile swap to show the orchestrator learned a transferable trust skill." | |
| }; | |
| if (el.viewCopy) { | |
| el.viewCopy.textContent = copy[view] || copy.overview; | |
| } | |
| } | |
| async function loadEvaluation() { | |
| try { | |
| const response = await fetch("/assets/evaluation_results.json"); | |
| if (!response.ok) throw new Error("evaluation asset missing"); | |
| state.evaluation = await response.json(); | |
| renderEvaluation(); | |
| } catch (error) { | |
| console.warn("Failed to load evaluation results", error); | |
| } | |
| } | |
| function setMetricText(node, value, digits = 3) { | |
| if (!node || value === undefined || value === null || Number.isNaN(Number(value))) return; | |
| node.textContent = Number(value).toFixed(digits); | |
| } | |
| function setMetricBar(node, value) { | |
| if (!node || value === undefined || value === null || Number.isNaN(Number(value))) return; | |
| node.style.width = `${Math.max(0, Math.min(100, Number(value) * 100))}%`; | |
| } | |
| function renderEvaluation() { | |
| const data = state.evaluation; | |
| if (!data) return; | |
| const overall = data.summary || {}; | |
| const task3 = data.by_task?.task3 || {}; | |
| const random = overall.random || {}; | |
| const heuristic = overall.heuristic || {}; | |
| const oracle = overall.oracle_lite || {}; | |
| const task3Random = task3.random || {}; | |
| const task3Heuristic = task3.heuristic || {}; | |
| setMetricText(el.heroRandomScore, random.avg_score); | |
| setMetricText(el.heroHeuristicScore, heuristic.avg_score); | |
| setMetricText(el.heroDetectionScore, task3Heuristic.avg_detection_rate); | |
| if (el.storyBeforeScore && task3Random.avg_score !== undefined) { | |
| el.storyBeforeScore.textContent = `task3 random ${Number(task3Random.avg_score).toFixed(3)}`; | |
| } | |
| if (el.storyAfterScore && task3Heuristic.avg_score !== undefined) { | |
| el.storyAfterScore.textContent = `task3 heuristic ${Number(task3Heuristic.avg_score).toFixed(3)}`; | |
| } | |
| setMetricText(el.judgeRandomScore, random.avg_score); | |
| setMetricText(el.judgeHeuristicScore, heuristic.avg_score); | |
| setMetricText(el.judgeDetectionScore, task3Heuristic.avg_detection_rate); | |
| setMetricBar(el.proofRandomBar, random.avg_score); | |
| setMetricBar(el.proofHeuristicBar, heuristic.avg_score); | |
| setMetricBar(el.proofOracleBar, oracle.avg_score); | |
| setMetricBar(el.proofDetectBar, task3Heuristic.avg_detection_rate); | |
| setMetricText(el.proofRandomScore, random.avg_score); | |
| setMetricText(el.proofHeuristicScore, heuristic.avg_score); | |
| setMetricText(el.proofOracleScore, oracle.avg_score); | |
| setMetricText(el.proofDetectScore, task3Heuristic.avg_detection_rate); | |
| } | |
| function renderTrust() { | |
| const trust = state.observation?.trust_snapshot || Object.fromEntries(ids.map(id => [id, 0.5])); | |
| const values = ids.map(id => Number(trust[id] ?? 0.5)); | |
| const mean = values.reduce((sum, value) => sum + value, 0) / values.length; | |
| el.trustMean.textContent = `mean ${mean.toFixed(2)}`; | |
| el.trustList.innerHTML = ids.map(id => { | |
| const value = Number(trust[id] ?? 0.5); | |
| const pct = Math.round(value * 100); | |
| const color = trustColor(value); | |
| return ` | |
| <div class="specialist"> | |
| <div class="sid">${id}</div> | |
| <div class="bar"><div class="fill" style="width:${pct}%;background:${color}"></div></div> | |
| <div class="score">${value.toFixed(2)}</div> | |
| </div> | |
| `; | |
| }).join(""); | |
| ids.forEach(id => { | |
| const node = document.getElementById(`node-${id}`); | |
| const value = Number(trust[id] ?? 0.5); | |
| const status = trustStatus(value); | |
| const active = id === bestSpecialist(); | |
| node.style.setProperty("--trust", value); | |
| node.style.setProperty("--node-color", trustColor(value)); | |
| node.className = `node ${active ? "active" : ""} ${status === "risk" ? "watch" : ""} ${status === "quarantine" ? "quarantine" : ""}`; | |
| node.querySelector(".node-id").innerHTML = `<span>${id}</span><span>${value.toFixed(2)}</span>`; | |
| node.querySelector(".node-meta").innerHTML = `<span>public slot</span><span>${status}</span>`; | |
| }); | |
| } | |
| function renderSpecialists() { | |
| const available = state.observation?.available_specialists || ids; | |
| const selected = el.specialistSelect.value || bestSpecialist(); | |
| el.specialistSelect.innerHTML = available.map(id => `<option value="${id}">${id}</option>`).join(""); | |
| el.specialistSelect.value = available.includes(selected) ? selected : bestSpecialist(); | |
| } | |
| function renderEvents() { | |
| if (!state.events.length) { | |
| el.eventList.innerHTML = `<div class="muted">No events yet.</div>`; | |
| return; | |
| } | |
| el.eventList.innerHTML = state.events.slice(-18).reverse().map(item => ` | |
| <div class="event"> | |
| <strong>#${item.step}</strong> | |
| <div>${item.action}<br><span class="muted">${item.summary}</span></div> | |
| <div class="reward">${item.reward}</div> | |
| </div> | |
| `).join(""); | |
| } | |
| function renderPlayground() { | |
| if (el.requestJson) { | |
| el.requestJson.textContent = prettyJson(state.lastRequest || { | |
| status: "waiting", | |
| message: "Reset or step to inspect backend payloads." | |
| }); | |
| } | |
| if (el.responseJson) { | |
| el.responseJson.textContent = prettyJson(state.lastResult || { | |
| status: "waiting", | |
| message: "Observation, reward, and info will appear here." | |
| }); | |
| } | |
| if (el.playgroundMode) { | |
| el.playgroundMode.textContent = state.lastMode; | |
| } | |
| if (el.endpointChip) { | |
| const path = state.lastRequest?.path || "/reset"; | |
| el.endpointChip.textContent = `POST ${path}`; | |
| } | |
| if (el.playgroundCopy) { | |
| el.playgroundCopy.textContent = state.lastMode === "step()" | |
| ? "A step sends one action into the environment and returns the next observation, reward, done flag, and info." | |
| : "Reset starts a new episode, samples a scenario, reshuffles hidden profiles, and returns the first observation."; | |
| } | |
| if (el.playgroundSummary) { | |
| const obs = state.lastResult?.observation; | |
| const reward = state.lastResult?.reward; | |
| const info = state.lastResult?.info; | |
| el.playgroundSummary.textContent = obs | |
| ? `Current subtask: ${obs.current_subtask} | Reward: ${Number(reward?.value ?? 0).toFixed(2)} | Score: ${Number(info?.score ?? 0).toFixed(3)} | Detections: ${info?.adversarial_detections ?? 0}` | |
| : "Observation tells you the next subtask and public trust scores. Reward tells you whether routing was correct. Info tells you the normalized episode score and adversarial counters."; | |
| } | |
| } | |
| function renderRecommendation() { | |
| const move = recommendedMove(); | |
| const obs = state.observation; | |
| const label = `${move.type}:${move.specialist}`; | |
| const highStakes = Number(obs?.stakes_level ?? 0) >= 0.70; | |
| el.recommendChip.textContent = label; | |
| el.recommendChip.className = `chip ${move.type === "verify" ? "warn" : "live"}`; | |
| el.stageMove.textContent = label; | |
| el.leadMove.textContent = obs | |
| ? `${move.type.toUpperCase()} ${move.specialist} at trust ${move.trust.toFixed(2)}` | |
| : "Reset starts a fresh trust game."; | |
| el.recommendText.textContent = highStakes | |
| ? `High-stakes gate active. ${move.type === "verify" ? "Verify before accepting output." : "Best specialist is trusted enough to delegate."}` | |
| : `Route to ${move.specialist}; keep budget for later high-stakes checks.`; | |
| } | |
| function setDisabled(disabled) { | |
| el.delegateBtn.disabled = disabled; | |
| el.verifyBtn.disabled = disabled; | |
| el.selfBtn.disabled = disabled; | |
| el.skipBtn.disabled = disabled; | |
| el.applyRecommendBtn.disabled = disabled; | |
| if (el.randomPolicyBtn) el.randomPolicyBtn.disabled = state.running; | |
| if (el.heuristicPolicyBtn) el.heuristicPolicyBtn.disabled = state.running; | |
| if (el.judgeSwapBtn) el.judgeSwapBtn.disabled = state.running; | |
| } | |
| function render(result) { | |
| if (result) { | |
| state.observation = result.observation; | |
| state.done = Boolean(result.done); | |
| } | |
| const obs = state.observation; | |
| if (!obs) { | |
| renderTrust(); | |
| renderSpecialists(); | |
| renderEvents(); | |
| renderRecommendation(); | |
| renderPlayground(); | |
| setDisabled(true); | |
| return; | |
| } | |
| const info = result?.info || {}; | |
| const completed = obs.subtasks_total - obs.subtasks_remaining; | |
| const progress = obs.subtasks_total ? (completed / obs.subtasks_total) * 100 : 0; | |
| const status = state.done ? "DONE" : obs.episode_status.toUpperCase(); | |
| const detections = info.adversarial_detections ?? 0; | |
| const poisonings = info.adversarial_poisonings ?? 0; | |
| const lastReward = Number(result?.reward?.value ?? obs.last_reward ?? 0); | |
| el.statusChip.textContent = status; | |
| el.statusChip.className = `chip ${state.done ? "live" : "live"}`; | |
| el.scenarioChip.textContent = obs.scenario_id; | |
| el.scoreValue.textContent = Number(info.score ?? 0).toFixed(3); | |
| el.stepValue.textContent = `${obs.step_count}/${obs.max_steps}`; | |
| el.completeValue.textContent = `${completed}/${obs.subtasks_total}`; | |
| el.stakesValue.textContent = Number(obs.stakes_level).toFixed(2); | |
| el.progressFill.style.width = `${Math.max(0, Math.min(100, progress))}%`; | |
| el.riskFill.style.width = `${Math.round(Number(obs.stakes_level || 0) * 100)}%`; | |
| el.subtaskText.textContent = state.done ? "Episode complete. Swap profiles for the generalization demo." : obs.current_subtask; | |
| el.sessionText.textContent = state.sessionId ? `session ${state.sessionId.slice(0, 8)}` : "No session"; | |
| el.detectChip.textContent = `${detections} detected`; | |
| el.poisonChip.textContent = `${poisonings} poison`; | |
| el.poisonChip.className = `chip ${poisonings > 0 ? "fail" : "warn"}`; | |
| el.stageSignals.textContent = `${detections} detected / ${poisonings} poison`; | |
| el.rewardText.textContent = `last reward ${lastReward.toFixed(2)}`; | |
| renderTrust(); | |
| renderSpecialists(); | |
| renderEvents(); | |
| renderRecommendation(); | |
| renderPlayground(); | |
| setDisabled(state.done || state.running); | |
| } | |
| function addEvent(step, action, summary, reward) { | |
| state.events.push({step, action, summary, reward}); | |
| } | |
| function actionPayload(type, specialist) { | |
| const obs = state.observation; | |
| return { | |
| session_id: state.sessionId, | |
| task_type: obs.task_type, | |
| action_type: type, | |
| specialist_id: specialist, | |
| subtask_response: type === "solve_independently" ? "SELF_SOLVED" : null, | |
| reasoning: `ui-${type}${specialist ? "-" + specialist : ""}` | |
| }; | |
| } | |
| async function resetEpisode() { | |
| state.running = true; | |
| el.resetBtn.disabled = true; | |
| el.resetPanelBtn.disabled = true; | |
| try { | |
| const seed = Number(el.seedInput.value || 0); | |
| state.lastMode = "reset()"; | |
| state.lastRequest = { | |
| method: "POST", | |
| path: "/reset", | |
| body: {task_type: el.taskSelect.value, seed} | |
| }; | |
| const response = await fetch("/reset", { | |
| method: "POST", | |
| headers: {"Content-Type": "application/json"}, | |
| body: JSON.stringify({task_type: el.taskSelect.value, seed}) | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.detail || "reset failed"); | |
| state.lastResult = result; | |
| state.taskType = result.observation.task_type; | |
| state.sessionId = result.info.session_id; | |
| state.events = []; | |
| state.done = false; | |
| addEvent(0, "reset", "Episode initialized with shuffled hidden profiles.", "0.00"); | |
| render(result); | |
| } catch (error) { | |
| state.lastResult = {error: error.message}; | |
| addEvent(0, "error", error.message, "0.00"); | |
| renderPlayground(); | |
| renderEvents(); | |
| } finally { | |
| state.running = false; | |
| el.resetBtn.disabled = false; | |
| el.resetPanelBtn.disabled = false; | |
| setDisabled(state.done); | |
| } | |
| } | |
| async function stepEpisode(type, specialist = null) { | |
| if (!state.sessionId || state.done || state.running) return; | |
| state.running = true; | |
| setDisabled(true); | |
| try { | |
| const chosen = specialist || el.specialistSelect.value || bestSpecialist(); | |
| const payload = actionPayload(type, type === "delegate" || type === "verify" ? chosen : null); | |
| state.lastMode = "step()"; | |
| state.lastRequest = { | |
| method: "POST", | |
| path: `/step?session_id=${state.sessionId}`, | |
| body: payload | |
| }; | |
| const response = await fetch(`/step?session_id=${encodeURIComponent(state.sessionId)}`, { | |
| method: "POST", | |
| headers: {"Content-Type": "application/json"}, | |
| body: JSON.stringify(payload) | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.detail || "step failed"); | |
| state.lastResult = result; | |
| const reward = Number(result.reward.value || 0).toFixed(2); | |
| const label = payload.specialist_id ? `${type}:${payload.specialist_id}` : type; | |
| addEvent(result.info.step_count, label, result.reward.reason, reward); | |
| render(result); | |
| } catch (error) { | |
| state.lastResult = {error: error.message}; | |
| addEvent(state.observation?.step_count || 0, "error", error.message, "0.00"); | |
| renderPlayground(); | |
| renderEvents(); | |
| } finally { | |
| state.running = false; | |
| setDisabled(state.done); | |
| } | |
| } | |
| async function autoRun(policy = state.demoPolicy) { | |
| if (!state.observation || state.done) await resetEpisode(); | |
| let guard = 0; | |
| while (!state.done && guard < 70) { | |
| const move = policy === "random" ? randomMove() : recommendedMove(); | |
| await stepEpisode(move.type, move.specialist); | |
| guard += 1; | |
| await new Promise(resolve => setTimeout(resolve, 150)); | |
| } | |
| } | |
| async function applyRecommendation() { | |
| const move = recommendedMove(); | |
| await stepEpisode(move.type, move.specialist); | |
| } | |
| async function runPolicy(policy) { | |
| state.demoPolicy = policy; | |
| await resetEpisode(); | |
| await autoRun(policy); | |
| } | |
| async function swapProfiles(policy = null) { | |
| const nextSeed = Number(el.seedInput.value || 0) + 1; | |
| el.seedInput.value = String(nextSeed); | |
| await resetEpisode(); | |
| if (policy) { | |
| await autoRun(policy); | |
| } | |
| } | |
| el.resetBtn.addEventListener("click", resetEpisode); | |
| el.resetPanelBtn.addEventListener("click", resetEpisode); | |
| el.swapBtn.addEventListener("click", () => swapProfiles()); | |
| el.swapPanelBtn.addEventListener("click", () => swapProfiles()); | |
| el.delegateBtn.addEventListener("click", () => stepEpisode("delegate")); | |
| el.verifyBtn.addEventListener("click", () => stepEpisode("verify")); | |
| el.selfBtn.addEventListener("click", () => stepEpisode("solve_independently")); | |
| el.skipBtn.addEventListener("click", () => stepEpisode("skip")); | |
| el.autoBtn.addEventListener("click", () => autoRun("heuristic")); | |
| el.applyRecommendBtn.addEventListener("click", applyRecommendation); | |
| el.viewOverviewBtn.addEventListener("click", () => setView("overview")); | |
| el.viewPlaygroundBtn.addEventListener("click", () => setView("playground")); | |
| el.viewJudgeBtn.addEventListener("click", () => setView("judge")); | |
| el.randomPolicyBtn.addEventListener("click", () => runPolicy("random")); | |
| el.heuristicPolicyBtn.addEventListener("click", () => runPolicy("heuristic")); | |
| el.judgeSwapBtn.addEventListener("click", () => swapProfiles(state.demoPolicy)); | |
| setView("overview"); | |
| loadEvaluation(); | |
| render(); | |
| resetEpisode(); | |
| </script> | |
| </body> | |
| </html> | |