Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>ProofFrame</title> | |
| <link | |
| rel="icon" | |
| href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23171714'/%3E%3Cpath d='M8 8h16v5H13v4h9v5h-9v6H8z' fill='%23fbfaf6'/%3E%3C/svg%3E" | |
| /> | |
| <style> | |
| :root { | |
| --paper: #fbfaf6; | |
| --ink: #171714; | |
| --muted: #6b6a61; | |
| --rule: #d9d4c8; | |
| --panel: #f1eee5; | |
| --line: #c9c1b1; | |
| --blue: #346c9c; | |
| --coral: #c85c42; | |
| --green: #2f815f; | |
| --gold: #b88d37; | |
| --mono: "IBM Plex Mono", "SFMono-Regular", Consolas, monospace; | |
| --body: "Aptos", "Helvetica Neue", Helvetica, Arial, sans-serif; | |
| --display: "Fraunces", Georgia, serif; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| margin: 0; | |
| min-height: 100vh; | |
| color: var(--ink); | |
| background: | |
| linear-gradient(90deg, rgba(23, 23, 20, 0.035) 1px, transparent 1px) 0 0 / 48px 48px, | |
| linear-gradient(0deg, rgba(23, 23, 20, 0.03) 1px, transparent 1px) 0 0 / 48px 48px, | |
| var(--paper); | |
| font-family: var(--body); | |
| letter-spacing: 0; | |
| } | |
| button, | |
| input, | |
| textarea { | |
| font: inherit; | |
| } | |
| button { | |
| border: 1px solid var(--ink); | |
| background: var(--ink); | |
| color: var(--paper); | |
| min-height: 38px; | |
| padding: 0 14px; | |
| cursor: pointer; | |
| } | |
| button.secondary { | |
| background: transparent; | |
| color: var(--ink); | |
| } | |
| button.approve { | |
| border-color: var(--green); | |
| background: var(--green); | |
| } | |
| button.reject { | |
| border-color: var(--coral); | |
| background: var(--coral); | |
| } | |
| button:disabled { | |
| cursor: not-allowed; | |
| opacity: 0.48; | |
| } | |
| .shell { | |
| display: grid; | |
| grid-template-columns: 260px 1fr 360px; | |
| min-height: 100vh; | |
| } | |
| .sidebar { | |
| border-right: 1px solid var(--rule); | |
| padding: 26px 22px; | |
| background: rgba(241, 238, 229, 0.68); | |
| } | |
| .brand { | |
| font-family: var(--display); | |
| font-size: 31px; | |
| font-weight: 700; | |
| margin-bottom: 6px; | |
| } | |
| .tagline { | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.4; | |
| margin-bottom: 26px; | |
| } | |
| .status-stack { | |
| border-top: 1px solid var(--line); | |
| border-bottom: 1px solid var(--line); | |
| padding: 14px 0; | |
| display: grid; | |
| gap: 12px; | |
| } | |
| .gate-panel { | |
| border-bottom: 1px solid var(--line); | |
| display: grid; | |
| gap: 10px; | |
| padding: 14px 0; | |
| } | |
| .gate-meter { | |
| display: grid; | |
| gap: 6px; | |
| } | |
| .gate-track { | |
| background: rgba(23, 23, 20, 0.12); | |
| height: 8px; | |
| overflow: hidden; | |
| } | |
| .gate-fill { | |
| background: var(--green); | |
| height: 100%; | |
| width: 0%; | |
| } | |
| .gate-list { | |
| display: grid; | |
| gap: 6px; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| .gate-item { | |
| align-items: start; | |
| display: grid; | |
| gap: 7px; | |
| grid-template-columns: 56px 1fr; | |
| } | |
| .gate-pill { | |
| border: 1px solid var(--line); | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| font-weight: 800; | |
| min-height: 22px; | |
| padding: 4px 6px; | |
| text-align: center; | |
| text-transform: uppercase; | |
| } | |
| .gate-pill.done { | |
| border-color: var(--green); | |
| color: var(--green); | |
| } | |
| .gate-pill.blocked { | |
| border-color: var(--coral); | |
| color: var(--coral); | |
| } | |
| .gate-pill.doing { | |
| border-color: var(--gold); | |
| color: var(--gold); | |
| } | |
| .gate-action { | |
| color: var(--muted); | |
| font-size: 12px; | |
| line-height: 1.35; | |
| } | |
| .stat label, | |
| .field label { | |
| display: block; | |
| color: var(--muted); | |
| font-size: 11px; | |
| font-weight: 700; | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| margin-bottom: 5px; | |
| } | |
| .stat strong { | |
| display: block; | |
| font-size: 14px; | |
| line-height: 1.25; | |
| } | |
| .campaign-list { | |
| margin-top: 24px; | |
| } | |
| .campaign-item { | |
| border-top: 1px solid var(--rule); | |
| padding: 11px 0; | |
| display: grid; | |
| gap: 3px; | |
| } | |
| .campaign-item span { | |
| color: var(--muted); | |
| font-size: 12px; | |
| } | |
| main { | |
| padding: 26px 30px 86px; | |
| } | |
| .topbar { | |
| display: flex; | |
| align-items: start; | |
| justify-content: space-between; | |
| gap: 24px; | |
| border-bottom: 2px solid var(--ink); | |
| padding-bottom: 20px; | |
| } | |
| .judge-slate { | |
| background: rgba(255, 255, 255, 0.42); | |
| border-bottom: 1px solid var(--rule); | |
| border-top: 1px solid var(--ink); | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| margin: 18px 0 24px; | |
| } | |
| .slate-item { | |
| min-height: 86px; | |
| padding: 14px 16px; | |
| border-right: 1px solid var(--rule); | |
| } | |
| .slate-item:last-child { | |
| border-right: 0; | |
| } | |
| .slate-item:nth-child(3) { | |
| border-right: 0; | |
| } | |
| .sponsor-map { | |
| border-top: 1px solid var(--ink); | |
| grid-column: 1 / -1; | |
| padding: 14px 16px 16px; | |
| } | |
| .sponsor-map header { | |
| align-items: baseline; | |
| display: flex; | |
| gap: 12px; | |
| justify-content: space-between; | |
| margin-bottom: 12px; | |
| } | |
| .sponsor-map h2 { | |
| font-family: var(--display); | |
| font-size: 21px; | |
| line-height: 1; | |
| margin: 0; | |
| } | |
| .sponsor-map header span { | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| } | |
| .sponsor-rail { | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 0; | |
| border-top: 1px solid var(--rule); | |
| } | |
| .sponsor-node { | |
| min-height: 82px; | |
| border-right: 1px solid var(--rule); | |
| padding: 12px 12px 0 0; | |
| } | |
| .sponsor-node:last-child { | |
| border-right: 0; | |
| } | |
| .sponsor-node label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 8px; | |
| text-transform: uppercase; | |
| } | |
| .sponsor-node strong { | |
| display: block; | |
| font-size: 14px; | |
| line-height: 1.25; | |
| margin-bottom: 6px; | |
| } | |
| .sponsor-node span { | |
| color: var(--muted); | |
| display: block; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.42; | |
| overflow-wrap: anywhere; | |
| } | |
| .judge-brief-strip { | |
| border-top: 1px solid var(--ink); | |
| display: grid; | |
| gap: 13px; | |
| grid-column: 1 / -1; | |
| padding: 14px 16px 16px; | |
| } | |
| .judge-brief-strip header { | |
| align-items: baseline; | |
| display: flex; | |
| gap: 12px; | |
| justify-content: space-between; | |
| } | |
| .judge-brief-strip h2 { | |
| font-family: var(--display); | |
| font-size: 21px; | |
| line-height: 1; | |
| margin: 0; | |
| } | |
| .judge-brief-strip header span { | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| } | |
| .brief-grid { | |
| border-top: 1px solid var(--rule); | |
| display: grid; | |
| grid-template-columns: 1.25fr 0.7fr 1fr; | |
| } | |
| .brief-node { | |
| border-right: 1px solid var(--rule); | |
| min-height: 84px; | |
| padding: 12px 14px 2px 0; | |
| } | |
| .brief-node:last-child { | |
| border-right: 0; | |
| } | |
| .brief-node label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 8px; | |
| text-transform: uppercase; | |
| } | |
| .brief-node strong { | |
| display: block; | |
| font-size: 16px; | |
| line-height: 1.22; | |
| margin-bottom: 7px; | |
| } | |
| .brief-node span { | |
| color: var(--muted); | |
| display: block; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.42; | |
| overflow-wrap: anywhere; | |
| } | |
| .brief-links { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| } | |
| .brief-links a { | |
| border: 1px solid var(--line); | |
| color: var(--ink); | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| font-weight: 800; | |
| padding: 7px 9px; | |
| text-decoration: none; | |
| } | |
| .brief-links a:hover { | |
| border-color: var(--ink); | |
| background: rgba(23, 23, 20, 0.06); | |
| } | |
| .criteria-crosswalk-strip { | |
| border-top: 1px solid var(--ink); | |
| display: grid; | |
| gap: 12px; | |
| grid-column: 1 / -1; | |
| padding: 14px 16px 16px; | |
| } | |
| .criteria-crosswalk-strip header { | |
| align-items: baseline; | |
| display: flex; | |
| gap: 12px; | |
| justify-content: space-between; | |
| } | |
| .criteria-crosswalk-strip h2 { | |
| font-family: var(--display); | |
| font-size: 21px; | |
| line-height: 1; | |
| margin: 0; | |
| } | |
| .criteria-crosswalk-strip header span { | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| } | |
| .crosswalk-grid { | |
| border-top: 1px solid var(--rule); | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| } | |
| .crosswalk-row { | |
| border-right: 1px solid var(--rule); | |
| min-height: 118px; | |
| padding: 12px 12px 4px 0; | |
| } | |
| .crosswalk-row:last-child { | |
| border-right: 0; | |
| } | |
| .crosswalk-row label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 8px; | |
| text-transform: uppercase; | |
| } | |
| .crosswalk-row strong { | |
| display: block; | |
| font-size: 15px; | |
| line-height: 1.22; | |
| margin-bottom: 7px; | |
| } | |
| .crosswalk-row span { | |
| color: var(--muted); | |
| display: block; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.42; | |
| overflow-wrap: anywhere; | |
| } | |
| .recording-runbook-strip { | |
| border-top: 1px solid var(--ink); | |
| display: grid; | |
| gap: 12px; | |
| grid-column: 1 / -1; | |
| padding: 14px 16px 16px; | |
| } | |
| .recording-runbook-strip header { | |
| align-items: baseline; | |
| display: flex; | |
| gap: 12px; | |
| justify-content: space-between; | |
| } | |
| .recording-runbook-strip h2 { | |
| font-family: var(--display); | |
| font-size: 21px; | |
| line-height: 1; | |
| margin: 0; | |
| } | |
| .recording-runbook-strip header span { | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| } | |
| .recording-grid { | |
| border-top: 1px solid var(--rule); | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| } | |
| .recording-row { | |
| border-right: 1px solid var(--rule); | |
| min-height: 116px; | |
| padding: 12px 12px 4px 0; | |
| } | |
| .recording-row:last-child { | |
| border-right: 0; | |
| } | |
| .recording-row label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 8px; | |
| text-transform: uppercase; | |
| } | |
| .recording-row strong { | |
| display: block; | |
| font-size: 15px; | |
| line-height: 1.22; | |
| margin-bottom: 7px; | |
| } | |
| .recording-row span { | |
| color: var(--muted); | |
| display: block; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.42; | |
| overflow-wrap: anywhere; | |
| } | |
| .devpost-kit-strip { | |
| border-top: 1px solid var(--ink); | |
| display: grid; | |
| gap: 12px; | |
| grid-column: 1 / -1; | |
| padding: 14px 16px 16px; | |
| } | |
| .devpost-kit-strip header { | |
| align-items: baseline; | |
| display: flex; | |
| gap: 12px; | |
| justify-content: space-between; | |
| } | |
| .devpost-kit-strip h2 { | |
| font-family: var(--display); | |
| font-size: 21px; | |
| line-height: 1; | |
| margin: 0; | |
| } | |
| .devpost-kit-strip header span { | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| } | |
| .devpost-grid { | |
| border-top: 1px solid var(--rule); | |
| display: grid; | |
| grid-template-columns: 0.8fr 1fr 1fr; | |
| } | |
| .devpost-row { | |
| border-right: 1px solid var(--rule); | |
| min-height: 116px; | |
| padding: 12px 12px 4px 0; | |
| } | |
| .devpost-row:last-child { | |
| border-right: 0; | |
| } | |
| .devpost-row label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 8px; | |
| text-transform: uppercase; | |
| } | |
| .devpost-row strong { | |
| display: block; | |
| font-size: 15px; | |
| line-height: 1.22; | |
| margin-bottom: 7px; | |
| } | |
| .devpost-row span { | |
| color: var(--muted); | |
| display: block; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.42; | |
| overflow-wrap: anywhere; | |
| } | |
| .submit-checklist-strip, | |
| .closeout-strip { | |
| border-top: 1px solid var(--ink); | |
| display: grid; | |
| gap: 12px; | |
| grid-column: 1 / -1; | |
| padding: 14px 16px 16px; | |
| } | |
| .submit-checklist-strip header, | |
| .closeout-strip header { | |
| align-items: baseline; | |
| display: flex; | |
| gap: 12px; | |
| justify-content: space-between; | |
| } | |
| .submit-checklist-strip h2, | |
| .closeout-strip h2 { | |
| font-family: var(--display); | |
| font-size: 21px; | |
| line-height: 1; | |
| margin: 0; | |
| } | |
| .submit-checklist-strip header span, | |
| .closeout-strip header span { | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| } | |
| .submit-grid, | |
| .closeout-grid { | |
| border-top: 1px solid var(--rule); | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| } | |
| .submit-row, | |
| .closeout-row { | |
| border-right: 1px solid var(--rule); | |
| min-height: 116px; | |
| padding: 12px 12px 4px 0; | |
| } | |
| .submit-row:last-child, | |
| .closeout-row:last-child { | |
| border-right: 0; | |
| } | |
| .submit-row label, | |
| .closeout-row label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0; | |
| margin-bottom: 8px; | |
| text-transform: uppercase; | |
| } | |
| .submit-row strong, | |
| .closeout-row strong { | |
| display: block; | |
| font-size: 15px; | |
| line-height: 1.22; | |
| margin-bottom: 7px; | |
| } | |
| .submit-row span, | |
| .closeout-row span { | |
| color: var(--muted); | |
| display: block; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.42; | |
| overflow-wrap: anywhere; | |
| } | |
| .slate-item label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 9px; | |
| text-transform: uppercase; | |
| } | |
| .slate-item strong { | |
| display: block; | |
| font-size: 18px; | |
| line-height: 1.15; | |
| margin-bottom: 8px; | |
| } | |
| .slate-item span { | |
| color: var(--muted); | |
| display: block; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.45; | |
| overflow-wrap: anywhere; | |
| } | |
| h1 { | |
| font-family: var(--display); | |
| font-size: 44px; | |
| line-height: 1; | |
| margin: 0 0 8px; | |
| } | |
| .subtle { | |
| color: var(--muted); | |
| line-height: 1.45; | |
| margin: 0; | |
| max-width: 680px; | |
| } | |
| .brief-panel { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 16px; | |
| margin: 24px 0; | |
| padding-bottom: 22px; | |
| border-bottom: 1px solid var(--rule); | |
| } | |
| .field.wide { | |
| grid-column: 1 / -1; | |
| } | |
| input, | |
| textarea { | |
| width: 100%; | |
| border: 1px solid var(--line); | |
| background: rgba(255, 255, 255, 0.58); | |
| color: var(--ink); | |
| padding: 11px 12px; | |
| outline: none; | |
| } | |
| textarea { | |
| min-height: 92px; | |
| resize: vertical; | |
| } | |
| input:focus, | |
| textarea:focus { | |
| border-color: var(--blue); | |
| box-shadow: inset 0 0 0 1px var(--blue); | |
| } | |
| .action-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| flex-wrap: wrap; | |
| } | |
| .review-console { | |
| display: grid; | |
| grid-template-columns: minmax(220px, 1fr) 180px 180px; | |
| gap: 12px; | |
| align-items: end; | |
| margin: 0 0 18px; | |
| } | |
| .review-console select { | |
| width: 100%; | |
| min-height: 42px; | |
| border: 1px solid var(--line); | |
| background: rgba(255, 255, 255, 0.58); | |
| color: var(--ink); | |
| padding: 0 10px; | |
| } | |
| .scorecard { | |
| display: grid; | |
| grid-template-columns: repeat(5, minmax(0, 1fr)); | |
| border-top: 1px solid var(--ink); | |
| border-bottom: 1px solid var(--rule); | |
| margin: 0 0 18px; | |
| } | |
| .scorecard-item { | |
| min-height: 74px; | |
| padding: 11px 13px; | |
| border-right: 1px solid var(--rule); | |
| } | |
| .scorecard-item:last-child { | |
| border-right: 0; | |
| } | |
| .scorecard-item label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 8px; | |
| text-transform: uppercase; | |
| } | |
| .scorecard-item strong { | |
| display: block; | |
| font-family: var(--display); | |
| font-size: 26px; | |
| line-height: 1; | |
| } | |
| .scorecard-item span { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 12px; | |
| margin-top: 6px; | |
| } | |
| .ledger-head, | |
| .asset-row { | |
| display: grid; | |
| grid-template-columns: 92px 1.25fr 0.8fr 0.9fr 112px; | |
| gap: 14px; | |
| align-items: center; | |
| } | |
| .ledger-head { | |
| color: var(--muted); | |
| font-size: 11px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| border-bottom: 1px solid var(--ink); | |
| padding: 0 0 9px; | |
| } | |
| .asset-row { | |
| min-height: 116px; | |
| border-bottom: 1px solid var(--rule); | |
| padding: 13px 0; | |
| cursor: pointer; | |
| } | |
| .asset-row.selected { | |
| background: linear-gradient(90deg, rgba(52, 108, 156, 0.09), transparent 75%); | |
| } | |
| .thumb { | |
| width: 92px; | |
| height: 62px; | |
| object-fit: cover; | |
| border: 1px solid var(--line); | |
| background: var(--panel); | |
| } | |
| .asset-title { | |
| display: block; | |
| font-weight: 800; | |
| margin-bottom: 5px; | |
| } | |
| .mono { | |
| font-family: var(--mono); | |
| font-size: 12px; | |
| color: #34342f; | |
| overflow-wrap: anywhere; | |
| } | |
| .status { | |
| display: inline-flex; | |
| width: fit-content; | |
| align-items: center; | |
| min-height: 24px; | |
| padding: 0 9px; | |
| border: 1px solid var(--line); | |
| font-size: 12px; | |
| font-weight: 800; | |
| } | |
| .status.approved { | |
| border-color: var(--green); | |
| color: var(--green); | |
| } | |
| .status.rejected { | |
| border-color: var(--coral); | |
| color: var(--coral); | |
| } | |
| .inspector { | |
| border-left: 1px solid var(--rule); | |
| padding: 26px 22px 82px; | |
| background: rgba(255, 255, 255, 0.44); | |
| } | |
| .inspector h2 { | |
| font-family: var(--display); | |
| font-size: 28px; | |
| margin: 0 0 14px; | |
| } | |
| .chain { | |
| display: grid; | |
| gap: 14px; | |
| margin: 20px 0; | |
| } | |
| .chain-step { | |
| border-top: 1px solid var(--rule); | |
| padding-top: 12px; | |
| } | |
| .chain-step label { | |
| color: var(--muted); | |
| display: block; | |
| font-size: 11px; | |
| font-weight: 800; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 5px; | |
| text-transform: uppercase; | |
| } | |
| .manifest-box { | |
| background: #181815; | |
| color: #f6f2e8; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.55; | |
| padding: 14px; | |
| max-height: 240px; | |
| overflow: auto; | |
| white-space: pre-wrap; | |
| } | |
| .packet-bar { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 18px; | |
| border-top: 1px solid var(--ink); | |
| border-bottom: 1px solid var(--rule); | |
| margin: 0 0 16px; | |
| padding: 13px 0 14px; | |
| } | |
| .footer-note { | |
| color: var(--muted); | |
| font-size: 12px; | |
| text-decoration: none; | |
| } | |
| .deerflow-signature { | |
| border-top: 1px solid var(--rule); | |
| color: var(--muted); | |
| display: inline-block; | |
| font-size: 11px; | |
| margin-top: 20px; | |
| padding-top: 12px; | |
| text-decoration: none; | |
| } | |
| .deerflow-signature:hover { | |
| color: var(--ink); | |
| } | |
| @media (max-width: 1100px) { | |
| .shell { | |
| grid-template-columns: 1fr; | |
| } | |
| .shell > * { | |
| min-width: 0; | |
| } | |
| main { | |
| padding: 22px 16px 72px; | |
| } | |
| h1 { | |
| font-size: 36px; | |
| } | |
| .topbar { | |
| flex-direction: column; | |
| } | |
| .sidebar, | |
| .inspector { | |
| border: 0; | |
| } | |
| .brief-panel { | |
| grid-template-columns: 1fr; | |
| } | |
| .review-console, | |
| .scorecard, | |
| .judge-slate { | |
| grid-template-columns: 1fr; | |
| } | |
| .sponsor-rail { | |
| grid-template-columns: 1fr; | |
| } | |
| .brief-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .crosswalk-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .recording-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .devpost-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .submit-grid, | |
| .closeout-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .slate-item { | |
| border-right: 0; | |
| border-bottom: 1px solid var(--rule); | |
| } | |
| .sponsor-node { | |
| border-right: 0; | |
| border-bottom: 1px solid var(--rule); | |
| padding-right: 0; | |
| } | |
| .sponsor-node:last-child { | |
| border-bottom: 0; | |
| } | |
| .brief-node { | |
| border-right: 0; | |
| border-bottom: 1px solid var(--rule); | |
| padding-right: 0; | |
| } | |
| .brief-node:last-child { | |
| border-bottom: 0; | |
| } | |
| .crosswalk-row { | |
| border-right: 0; | |
| border-bottom: 1px solid var(--rule); | |
| padding-right: 0; | |
| } | |
| .crosswalk-row:last-child { | |
| border-bottom: 0; | |
| } | |
| .recording-row { | |
| border-right: 0; | |
| border-bottom: 1px solid var(--rule); | |
| padding-right: 0; | |
| } | |
| .recording-row:last-child { | |
| border-bottom: 0; | |
| } | |
| .devpost-row { | |
| border-right: 0; | |
| border-bottom: 1px solid var(--rule); | |
| padding-right: 0; | |
| } | |
| .devpost-row:last-child { | |
| border-bottom: 0; | |
| } | |
| .submit-row, | |
| .closeout-row { | |
| border-right: 0; | |
| border-bottom: 1px solid var(--rule); | |
| padding-right: 0; | |
| } | |
| .submit-row:last-child, | |
| .closeout-row:last-child { | |
| border-bottom: 0; | |
| } | |
| .packet-bar { | |
| align-items: stretch; | |
| flex-direction: column; | |
| } | |
| section[aria-label="Asset ledger"] { | |
| overflow-x: auto; | |
| } | |
| .ledger-head, | |
| .asset-row { | |
| min-width: 640px; | |
| } | |
| .scorecard-item { | |
| border-right: 0; | |
| border-bottom: 1px solid var(--rule); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="shell"> | |
| <aside class="sidebar"> | |
| <div class="brand">ProofFrame</div> | |
| <p class="tagline">A storage-backed ledger for generated media approvals.</p> | |
| <div class="status-stack"> | |
| <div class="stat"> | |
| <label>Storage</label> | |
| <strong id="storageStatus">Checking...</strong> | |
| </div> | |
| <div class="stat"> | |
| <label>Generation</label> | |
| <strong id="generationStatus">Checking...</strong> | |
| </div> | |
| <div class="stat"> | |
| <label>Submission Gate</label> | |
| <strong id="submissionGateStatus">Checking...</strong> | |
| </div> | |
| </div> | |
| <section class="gate-panel" aria-label="Submission readiness gate"> | |
| <div class="gate-meter"> | |
| <div class="stat"> | |
| <label>Final Gate Progress</label> | |
| <strong id="gateProgress">0 / 6 complete</strong> | |
| </div> | |
| <div class="gate-track" aria-hidden="true"> | |
| <div class="gate-fill" id="gateFill"></div> | |
| </div> | |
| </div> | |
| <div id="gateList" class="gate-list"></div> | |
| <div class="gate-action" id="gateNextAction">Loading final submission blockers...</div> | |
| </section> | |
| <section class="campaign-list"> | |
| <div class="stat"> | |
| <label>Campaign Queue</label> | |
| </div> | |
| <div id="campaignList"></div> | |
| </section> | |
| <a class="deerflow-signature" href="https://deerflow.tech" target="_blank" rel="noreferrer"> | |
| Created By Deerflow | |
| </a> | |
| </aside> | |
| <main> | |
| <div class="topbar"> | |
| <div> | |
| <h1>Generated Media Ledger</h1> | |
| <p class="subtle"> | |
| Create a campaign packet, generate variants, approve usable assets, then export a | |
| downloadable evidence packet with hashes, storage keys, provider metadata, and review | |
| notes. | |
| </p> | |
| </div> | |
| <div class="action-row"> | |
| <button id="judgeDemo">Judge Demo</button> | |
| <button class="secondary" id="loadCampaigns">Refresh</button> | |
| <button class="secondary" id="sampleBrief">Load Sample</button> | |
| </div> | |
| </div> | |
| <section class="judge-slate" aria-label="Judge recording slate"> | |
| <div class="slate-item"> | |
| <label>Demo Mode</label> | |
| <strong id="slateMode">Checking...</strong> | |
| <span id="slateRuntime">Waiting for runtime.</span> | |
| </div> | |
| <div class="slate-item"> | |
| <label>Evidence Packet</label> | |
| <strong id="slatePacket">No packet yet</strong> | |
| <span id="slatePacketDetail">Awaiting manifest evidence.</span> | |
| </div> | |
| <div class="slate-item"> | |
| <label>Claim Boundary</label> | |
| <strong id="slateClaim">Pre-live safe</strong> | |
| <span id="slateClaimDetail">Live B2 and Genblaze proof remain gated.</span> | |
| </div> | |
| <div class="sponsor-map" aria-label="Sponsor evidence model"> | |
| <header> | |
| <h2>Sponsor Evidence Model</h2> | |
| <span id="sponsorModelStatus">B2/Genblaze final proof gated</span> | |
| </header> | |
| <div class="sponsor-rail"> | |
| <div class="sponsor-node"> | |
| <label>Genblaze Step</label> | |
| <strong id="sponsorGenblaze">Provider path ready</strong> | |
| <span id="sponsorGenblazeDetail">mock now / Genblaze final gate</span> | |
| </div> | |
| <div class="sponsor-node"> | |
| <label>B2 Object Route</label> | |
| <strong id="sponsorB2">B2 adapter ready</strong> | |
| <span id="sponsorB2Detail">private bucket prepared / live upload gated</span> | |
| </div> | |
| <div class="sponsor-node"> | |
| <label>Manifest Proof</label> | |
| <strong id="sponsorManifest">Prompt + model + hash</strong> | |
| <span id="sponsorManifestDetail">storage key and checksum travel together</span> | |
| </div> | |
| <div class="sponsor-node"> | |
| <label>Claim Mode</label> | |
| <strong id="sponsorClaim">Pre-live safe</strong> | |
| <span id="sponsorClaimDetail">no completed B2/Genblaze claim yet</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="judge-brief-strip" aria-label="30-second judge brief"> | |
| <header> | |
| <h2>30-Second Judge Brief</h2> | |
| <span id="briefStatus">Loading safe boundary</span> | |
| </header> | |
| <div class="brief-grid"> | |
| <div class="brief-node"> | |
| <label>Outcome</label> | |
| <strong id="briefOutcome">B2-ready evidence packets</strong> | |
| <span id="briefOutcomeDetail">Generated media becomes manifest-backed approval evidence.</span> | |
| </div> | |
| <div class="brief-node"> | |
| <label>Award Posture</label> | |
| <strong id="briefScore">Checking</strong> | |
| <span id="briefScoreDetail">Final closure remains gated.</span> | |
| </div> | |
| <div class="brief-node"> | |
| <label>Still Gated</label> | |
| <strong id="briefBlockers">Live proof pending</strong> | |
| <span id="briefBlockersDetail">B2, Genblaze, video, audit, and Devpost receipt.</span> | |
| </div> | |
| </div> | |
| <nav class="brief-links" aria-label="Judge evidence links"> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/judge-brief.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Judge brief</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/judge-crosswalk.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Criteria crosswalk</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/judge-decision-brief.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Decision brief</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/judge-evidence-index.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Evidence index</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/final-video-publish-kit.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Video publish kit</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/recording-assets.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Recording runbook</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/devpost-form-kit.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Devpost kit</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/devpost-submission-checklist.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Submit checklist</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/final-launch-plan.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Launch plan</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/final-submission-control.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Final gate</a> | |
| <a | |
| href="https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/docs/assets/final-closeout-status.md" | |
| target="_blank" | |
| rel="noreferrer" | |
| >Final closeout</a> | |
| </nav> | |
| </div> | |
| <div class="criteria-crosswalk-strip" aria-label="Criteria crosswalk"> | |
| <header> | |
| <h2>Criteria Crosswalk</h2> | |
| <span id="crosswalkStatus">Loading official criteria</span> | |
| </header> | |
| <div class="crosswalk-grid" id="crosswalkRows"> | |
| <div class="crosswalk-row"> | |
| <label>Official Angle</label> | |
| <strong>Real-world Utility</strong> | |
| <span>Waiting for public-safe evidence map.</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="recording-runbook-strip" aria-label="Recording runbook"> | |
| <header> | |
| <h2>Recording Runbook</h2> | |
| <span id="recordingStatus">Loading recording gate</span> | |
| </header> | |
| <div class="recording-grid" id="recordingRows"> | |
| <div class="recording-row"> | |
| <label>First Shot</label> | |
| <strong>Judge slate</strong> | |
| <span>Waiting for public-safe recording assets.</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="devpost-kit-strip" aria-label="Devpost kit"> | |
| <header> | |
| <h2>Devpost Kit</h2> | |
| <span id="devpostStatus">Loading form gate</span> | |
| </header> | |
| <div class="devpost-grid" id="devpostRows"> | |
| <div class="devpost-row"> | |
| <label>Project Name</label> | |
| <strong>ProofFrame</strong> | |
| <span>Waiting for public-safe form kit.</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="submit-checklist-strip" aria-label="Submit checklist"> | |
| <header> | |
| <h2>Submit Checklist</h2> | |
| <span id="submitStatus">Loading preflight</span> | |
| </header> | |
| <div class="submit-grid" id="submitRows"> | |
| <div class="submit-row"> | |
| <label>Preflight</label> | |
| <strong>Final gate pending</strong> | |
| <span>Waiting for public-safe submission checklist.</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="closeout-strip" aria-label="Final closeout"> | |
| <header> | |
| <h2>Final Closeout</h2> | |
| <span id="closeoutStatus">Loading closeout gate</span> | |
| </header> | |
| <div class="closeout-grid" id="closeoutRows"> | |
| <div class="closeout-row"> | |
| <label>Next Command</label> | |
| <strong>Credential entry</strong> | |
| <span>Waiting for no-secret final closeout status.</span> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="brief-panel" aria-label="Campaign brief"> | |
| <div class="field"> | |
| <label for="title">Campaign</label> | |
| <input id="title" value="Neighborhood Museum Launch" /> | |
| </div> | |
| <div class="field"> | |
| <label for="audience">Audience</label> | |
| <input id="audience" value="local families and arts donors" /> | |
| </div> | |
| <div class="field"> | |
| <label for="tone">Tone</label> | |
| <input id="tone" value="warm editorial, precise, public-service" /> | |
| </div> | |
| <div class="field"> | |
| <label for="variantCount">Variants</label> | |
| <input id="variantCount" type="number" min="1" max="6" value="3" /> | |
| </div> | |
| <div class="field wide"> | |
| <label for="brief">Brief</label> | |
| <textarea id="brief">Announce a new neighborhood museum opening with generated visuals that feel archival, welcoming, and trustworthy.</textarea> | |
| </div> | |
| <div class="field wide action-row"> | |
| <button id="createCampaign">Create Campaign</button> | |
| <button id="generateAssets" class="secondary" disabled>Generate Variants</button> | |
| </div> | |
| </section> | |
| <section class="scorecard" aria-label="Evidence scorecard"> | |
| <div class="scorecard-item"> | |
| <label>Total Assets</label> | |
| <strong id="scoreTotal">0</strong> | |
| <span id="scoreBackend">No backend yet</span> | |
| </div> | |
| <div class="scorecard-item"> | |
| <label>Approved</label> | |
| <strong id="scoreApproved">0</strong> | |
| <span>ready for packet</span> | |
| </div> | |
| <div class="scorecard-item"> | |
| <label>Draft</label> | |
| <strong id="scoreDraft">0</strong> | |
| <span>needs decision</span> | |
| </div> | |
| <div class="scorecard-item"> | |
| <label>Rejected</label> | |
| <strong id="scoreRejected">0</strong> | |
| <span>kept for audit trail</span> | |
| </div> | |
| <div class="scorecard-item"> | |
| <label>Coverage</label> | |
| <strong id="scoreCoverage">0%</strong> | |
| <span>reviewed assets</span> | |
| </div> | |
| </section> | |
| <section class="review-console" aria-label="Review console"> | |
| <div class="field"> | |
| <label for="assetSearch">Search Evidence</label> | |
| <input id="assetSearch" placeholder="prompt, model, storage key, checksum" /> | |
| </div> | |
| <div class="field"> | |
| <label for="statusFilter">Status</label> | |
| <select id="statusFilter"> | |
| <option value="all">All assets</option> | |
| <option value="draft">Draft</option> | |
| <option value="approved">Approved</option> | |
| <option value="rejected">Rejected</option> | |
| </select> | |
| </div> | |
| <button class="secondary" id="copySummary" disabled>Copy Summary</button> | |
| </section> | |
| <section class="packet-bar" aria-label="Packet actions"> | |
| <div> | |
| <strong id="packetStatus">No packet exported</strong> | |
| <div class="mono" id="packetKey">Awaiting campaign manifest.</div> | |
| </div> | |
| <div class="action-row"> | |
| <button id="exportPacket" disabled>Export Manifest</button> | |
| <button id="downloadPacket" class="secondary" disabled>Download ZIP</button> | |
| <span class="footer-note">ProofFrame public mock demo</span> | |
| </div> | |
| </section> | |
| <section aria-label="Asset ledger"> | |
| <div class="ledger-head"> | |
| <div>Preview</div> | |
| <div>Asset</div> | |
| <div>Storage Key</div> | |
| <div>Checksum</div> | |
| <div>Status</div> | |
| </div> | |
| <div id="assetRows"></div> | |
| </section> | |
| </main> | |
| <aside class="inspector"> | |
| <h2>Provenance Chain</h2> | |
| <p class="subtle" id="inspectorIntro"> | |
| Select an asset to inspect its brief, provider, B2-ready storage route, hash, and review | |
| state. | |
| </p> | |
| <div class="chain"> | |
| <div class="chain-step"> | |
| <label>Brief</label> | |
| <div id="chainBrief">No campaign yet.</div> | |
| </div> | |
| <div class="chain-step"> | |
| <label>Genblaze Step</label> | |
| <div id="chainProvider" class="mono">mock provider until Genblaze credentials are set</div> | |
| </div> | |
| <div class="chain-step"> | |
| <label>B2 Object Route</label> | |
| <div id="chainStorage" class="mono">Waiting for generated asset</div> | |
| </div> | |
| <div class="chain-step"> | |
| <label>Decision</label> | |
| <div class="action-row"> | |
| <button id="approveAsset" class="approve" disabled>Approve</button> | |
| <button id="rejectAsset" class="reject" disabled>Reject</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chain-step"> | |
| <label>Manifest Preview</label> | |
| <pre class="manifest-box" id="manifestPreview">{}</pre> | |
| </div> | |
| </aside> | |
| </div> | |
| <script> | |
| const state = { | |
| campaign: null, | |
| campaigns: [], | |
| assets: [], | |
| selectedAssetId: null, | |
| manifest: {}, | |
| submissionGate: null, | |
| judgeBrief: null, | |
| judgeCrosswalk: null, | |
| judgeRecording: null, | |
| judgeDevpost: null, | |
| judgeSubmitChecklist: null, | |
| judgeCloseout: null, | |
| health: null, | |
| packetStatus: "No packet exported", | |
| packetDetail: "Awaiting campaign manifest.", | |
| filters: { | |
| query: "", | |
| status: "all", | |
| }, | |
| }; | |
| const el = (id) => document.getElementById(id); | |
| function escapeHtml(value) { | |
| return String(value || "") | |
| .replaceAll("&", "&") | |
| .replaceAll("<", "<") | |
| .replaceAll(">", ">") | |
| .replaceAll('"', """) | |
| .replaceAll("'", "'"); | |
| } | |
| function short(value, length = 16) { | |
| if (!value) return ""; | |
| return value.length > length ? `${value.slice(0, length)}...` : value; | |
| } | |
| function humanizeStatus(value) { | |
| return String(value || "") | |
| .replaceAll("_", " ") | |
| .replace(/\b\w/g, (match) => match.toUpperCase()) | |
| .replaceAll("Pre Live", "Pre-Live") | |
| .replaceAll("Post Live", "Post-Live"); | |
| } | |
| function shouldAutoLoadJudgeDemo() { | |
| const params = new URLSearchParams(window.location.search); | |
| const value = String(params.get("judge") || params.get("demo") || "").toLowerCase(); | |
| return ["1", "true", "yes"].includes(value); | |
| } | |
| function filteredAssets() { | |
| const query = state.filters.query.trim().toLowerCase(); | |
| return state.assets.filter((asset) => { | |
| const statusMatch = state.filters.status === "all" || asset.status === state.filters.status; | |
| const searchable = [ | |
| asset.prompt, | |
| asset.provider, | |
| asset.model, | |
| asset.storage_backend, | |
| asset.storage_key, | |
| asset.sha256, | |
| asset.risk_note, | |
| ] | |
| .join(" ") | |
| .toLowerCase(); | |
| return statusMatch && (!query || searchable.includes(query)); | |
| }); | |
| } | |
| async function api(path, options = {}) { | |
| const response = await fetch(path, { | |
| headers: { "Content-Type": "application/json" }, | |
| ...options, | |
| }); | |
| if (!response.ok) { | |
| const body = await response.text(); | |
| throw new Error(`${response.status} ${body}`); | |
| } | |
| return response.json(); | |
| } | |
| async function loadHealth() { | |
| const health = await api("/api/health"); | |
| state.health = health; | |
| el("storageStatus").textContent = `${health.storage_backend}${ | |
| health.b2_configured ? " / B2 configured" : " / B2 pending" | |
| }`; | |
| el("generationStatus").textContent = `${health.generation_backend}${ | |
| health.genblaze_configured ? " / Genblaze configured" : " / Genblaze pending" | |
| }`; | |
| renderJudgeSlate(); | |
| } | |
| async function loadSubmissionGate() { | |
| state.submissionGate = await api("/api/submission/gate"); | |
| renderSubmissionGate(); | |
| } | |
| async function loadJudgeBrief() { | |
| try { | |
| state.judgeBrief = await api("/api/judge/brief"); | |
| } catch (error) { | |
| state.judgeBrief = { error: error.message }; | |
| } | |
| renderJudgeBrief(); | |
| } | |
| async function loadJudgeCrosswalk() { | |
| try { | |
| state.judgeCrosswalk = await api("/api/judge/crosswalk"); | |
| } catch (error) { | |
| state.judgeCrosswalk = { error: error.message }; | |
| } | |
| renderJudgeCrosswalk(); | |
| } | |
| async function loadJudgeRecording() { | |
| try { | |
| state.judgeRecording = await api("/api/judge/recording"); | |
| } catch (error) { | |
| state.judgeRecording = { error: error.message }; | |
| } | |
| renderJudgeRecording(); | |
| } | |
| async function loadJudgeDevpost() { | |
| try { | |
| state.judgeDevpost = await api("/api/judge/devpost"); | |
| } catch (error) { | |
| state.judgeDevpost = { error: error.message }; | |
| } | |
| renderJudgeDevpost(); | |
| } | |
| async function loadJudgeSubmitChecklist() { | |
| try { | |
| state.judgeSubmitChecklist = await api("/api/judge/submission-checklist"); | |
| } catch (error) { | |
| state.judgeSubmitChecklist = { error: error.message }; | |
| } | |
| renderJudgeSubmitChecklist(); | |
| } | |
| async function loadJudgeCloseout() { | |
| try { | |
| state.judgeCloseout = await api("/api/judge/final-closeout"); | |
| } catch (error) { | |
| state.judgeCloseout = { error: error.message }; | |
| } | |
| renderJudgeCloseout(); | |
| } | |
| async function loadCampaigns() { | |
| state.campaigns = await api("/api/campaigns"); | |
| renderCampaigns(); | |
| } | |
| function renderSubmissionGate() { | |
| const gate = state.submissionGate; | |
| if (!gate) return; | |
| const summary = gate.summary || { required: 6, done: 0 }; | |
| const percent = summary.required ? Math.round((summary.done / summary.required) * 100) : 0; | |
| el("submissionGateStatus").textContent = gate.ok | |
| ? "Ready for Devpost submission" | |
| : `${summary.done} / ${summary.required} final gates ready`; | |
| el("gateProgress").textContent = `${summary.done} / ${summary.required} complete`; | |
| el("gateFill").style.width = `${percent}%`; | |
| el("gateList").innerHTML = (gate.task_gates || []) | |
| .map( | |
| (item) => ` | |
| <div class="gate-item"> | |
| <span class="gate-pill ${escapeHtml(item.status)}">${escapeHtml(item.status)}</span> | |
| <div> | |
| <strong>${escapeHtml(item.id)}</strong> | |
| <div class="gate-action">${escapeHtml(short(item.title, 72))}</div> | |
| </div> | |
| </div> | |
| `, | |
| ) | |
| .join(""); | |
| const evidence = gate.evidence_gate?.ok ? "Live B2 + Genblaze proof verified." : "Live proof missing."; | |
| const reports = gate.report_gate?.ok ? "Final reports verified." : "Final reports pending."; | |
| const firstAction = gate.next_actions?.[0] || evidence; | |
| el("gateNextAction").textContent = `${evidence} ${reports} Next: ${firstAction}`; | |
| renderJudgeSlate(); | |
| } | |
| function setPacketCopy(status, detail) { | |
| state.packetStatus = status; | |
| state.packetDetail = detail; | |
| el("packetStatus").textContent = status; | |
| el("packetKey").textContent = detail; | |
| renderJudgeSlate(); | |
| } | |
| function renderJudgeSlate() { | |
| const health = state.health || {}; | |
| const gate = state.submissionGate || {}; | |
| const total = state.assets.length; | |
| const approved = state.assets.filter((asset) => asset.status === "approved").length; | |
| const firstAsset = state.assets[0]; | |
| const runtime = `${health.storage_backend || "checking"} storage / ${ | |
| health.generation_backend || "checking" | |
| } generation`; | |
| const finalReady = Boolean(gate.ok); | |
| const claimMode = gate.mode === "final_ready" ? "Post-live verified" : "Pre-live safe"; | |
| const nextAction = | |
| gate.next_actions?.[0] || | |
| (finalReady ? "Ready for final Devpost submission." : "Live B2 and Genblaze proof remain gated."); | |
| el("slateMode").textContent = finalReady ? "Final-ready proof" : "Public mock demo"; | |
| el("slateRuntime").textContent = runtime; | |
| el("slatePacket").textContent = total | |
| ? `${total} assets / ${approved} approved` | |
| : state.packetStatus; | |
| el("slatePacketDetail").textContent = state.packetDetail; | |
| el("slateClaim").textContent = claimMode; | |
| el("slateClaimDetail").textContent = nextAction; | |
| el("sponsorModelStatus").textContent = finalReady | |
| ? "B2/Genblaze proof verified" | |
| : "B2/Genblaze final proof gated"; | |
| el("sponsorGenblaze").textContent = health.genblaze_configured | |
| ? "Genblaze configured" | |
| : "Provider path ready"; | |
| el("sponsorGenblazeDetail").textContent = firstAsset | |
| ? `${firstAsset.provider} / ${firstAsset.model}` | |
| : "mock now / Genblaze final gate"; | |
| el("sponsorB2").textContent = health.b2_configured ? "B2 configured" : "B2 adapter ready"; | |
| el("sponsorB2Detail").textContent = firstAsset | |
| ? `${firstAsset.storage_backend} / ${short(firstAsset.storage_key, 42)}` | |
| : "private bucket prepared / live upload gated"; | |
| el("sponsorManifest").textContent = total | |
| ? `${total} assets tracked` | |
| : "Prompt + model + hash"; | |
| el("sponsorManifestDetail").textContent = firstAsset | |
| ? `sha256 ${short(firstAsset.sha256, 24)}` | |
| : "storage key and checksum travel together"; | |
| el("sponsorClaim").textContent = claimMode; | |
| el("sponsorClaimDetail").textContent = finalReady | |
| ? "safe to claim live sponsor proof" | |
| : "no completed B2/Genblaze claim yet"; | |
| renderJudgeBrief(); | |
| } | |
| function renderJudgeBrief() { | |
| const brief = state.judgeBrief || {}; | |
| const status = brief.status || {}; | |
| const blockers = Array.isArray(brief.not_yet_claimed) ? brief.not_yet_claimed : []; | |
| const safeToSubmit = Boolean(status.safe_to_submit); | |
| const awardScore = | |
| status.award_score == null || status.award_max_score == null | |
| ? "Checking" | |
| : `${status.award_score}/${status.award_max_score}`; | |
| const closureScore = | |
| status.final_closure_score == null || status.final_closure_max_score == null | |
| ? "Final closure gated" | |
| : `Final closure ${status.final_closure_score}/${status.final_closure_max_score}`; | |
| el("briefStatus").textContent = brief.error | |
| ? "Brief unavailable" | |
| : safeToSubmit | |
| ? "Final-ready" | |
| : "Pre-live safe"; | |
| el("briefOutcome").textContent = brief.tagline || "B2-ready evidence packets"; | |
| el("briefOutcomeDetail").textContent = | |
| brief.one_liner || "Generated media becomes manifest-backed approval evidence."; | |
| el("briefScore").textContent = awardScore; | |
| el("briefScoreDetail").textContent = `${humanizeStatus( | |
| status.award_mode || "pre_live_competitive", | |
| )} / ${closureScore}`; | |
| el("briefBlockers").textContent = safeToSubmit | |
| ? "Ready for Devpost" | |
| : `${blockers.length || 3} final claims gated`; | |
| el("briefBlockersDetail").textContent = | |
| blockers.slice(0, 2).join(" / ") || "B2, Genblaze, video, audit, and Devpost receipt."; | |
| } | |
| function renderJudgeCrosswalk() { | |
| const crosswalk = state.judgeCrosswalk || {}; | |
| const rows = Array.isArray(crosswalk.rows) ? crosswalk.rows : []; | |
| const safeToSubmit = Boolean(crosswalk.safe_to_submit); | |
| el("crosswalkStatus").textContent = crosswalk.error | |
| ? "Crosswalk unavailable" | |
| : safeToSubmit | |
| ? "Final-ready criteria map" | |
| : humanizeStatus(crosswalk.mode || "pre_live_crosswalk_ready"); | |
| el("crosswalkRows").innerHTML = rows.length | |
| ? rows | |
| .slice(0, 4) | |
| .map( | |
| (row) => ` | |
| <div class="crosswalk-row"> | |
| <label>${escapeHtml(humanizeStatus(row.readiness || "tracked"))}</label> | |
| <strong>${escapeHtml(row.criterion || "Official criterion")}</strong> | |
| <span>${escapeHtml(row.final_gate || row.safe_claim || "Evidence gate tracked.")}</span> | |
| </div> | |
| `, | |
| ) | |
| .join("") | |
| : ` | |
| <div class="crosswalk-row"> | |
| <label>Unavailable</label> | |
| <strong>Criteria map pending</strong> | |
| <span>${escapeHtml(crosswalk.error || "Waiting for generated judge crosswalk artifact.")}</span> | |
| </div> | |
| `; | |
| } | |
| function renderJudgeRecording() { | |
| const recording = state.judgeRecording || {}; | |
| const shots = Array.isArray(recording.shot_plan) ? recording.shot_plan : []; | |
| const nextActions = Array.isArray(recording.next_actions) ? recording.next_actions : []; | |
| const finalVideoReady = Boolean(recording.final_video_ready); | |
| el("recordingStatus").textContent = recording.error | |
| ? "Runbook unavailable" | |
| : finalVideoReady | |
| ? "Final video ready" | |
| : humanizeStatus(recording.mode || "mock_recording_ready"); | |
| el("recordingRows").innerHTML = shots.length | |
| ? shots | |
| .slice(0, 3) | |
| .map( | |
| (shot) => ` | |
| <div class="recording-row"> | |
| <label>${escapeHtml(humanizeStatus(shot.id || "shot"))}</label> | |
| <strong>${escapeHtml(shot.source || "Recording source")}</strong> | |
| <span>${escapeHtml(shot.screen || "Screen detail tracked.")}</span> | |
| </div> | |
| `, | |
| ) | |
| .join("") | |
| : ` | |
| <div class="recording-row"> | |
| <label>Next Action</label> | |
| <strong>Recording gate pending</strong> | |
| <span>${escapeHtml(recording.error || nextActions[0] || "Waiting for generated recording assets.")}</span> | |
| </div> | |
| `; | |
| } | |
| function renderJudgeDevpost() { | |
| const devpost = state.judgeDevpost || {}; | |
| const fields = Array.isArray(devpost.fields) ? devpost.fields : []; | |
| const fieldById = Object.fromEntries(fields.map((field) => [field.id, field])); | |
| const displayFields = ["project_name", "tagline", "video_url"] | |
| .map((fieldId) => fieldById[fieldId]) | |
| .filter(Boolean); | |
| const finalFormReady = Boolean(devpost.final_form_ready); | |
| el("devpostStatus").textContent = devpost.error | |
| ? "Form kit unavailable" | |
| : finalFormReady | |
| ? "Final form ready" | |
| : humanizeStatus(devpost.mode || "pre_live_form_ready"); | |
| el("devpostRows").innerHTML = displayFields.length | |
| ? displayFields | |
| .map( | |
| (field) => ` | |
| <div class="devpost-row"> | |
| <label>${escapeHtml(field.ok_for_final ? "Final Ready" : "Final Pending")}</label> | |
| <strong>${escapeHtml(field.label || "Devpost field")}</strong> | |
| <span>${escapeHtml(field.value || "Awaiting final value.")}</span> | |
| </div> | |
| `, | |
| ) | |
| .join("") | |
| : ` | |
| <div class="devpost-row"> | |
| <label>Unavailable</label> | |
| <strong>Form kit pending</strong> | |
| <span>${escapeHtml(devpost.error || "Waiting for generated Devpost form kit.")}</span> | |
| </div> | |
| `; | |
| } | |
| function renderJudgeSubmitChecklist() { | |
| const checklist = state.judgeSubmitChecklist || {}; | |
| const preflight = Array.isArray(checklist.preflight) ? checklist.preflight : []; | |
| const stopRules = Array.isArray(checklist.stop_rules) ? checklist.stop_rules : []; | |
| const safeToSubmit = Boolean(checklist.safe_to_submit); | |
| el("submitStatus").textContent = checklist.error | |
| ? "Checklist unavailable" | |
| : safeToSubmit | |
| ? "Ready to submit" | |
| : humanizeStatus(checklist.mode || "pre_submit_blocked"); | |
| el("submitRows").innerHTML = preflight.length | |
| ? preflight | |
| .slice(0, 3) | |
| .map( | |
| (item) => ` | |
| <div class="submit-row"> | |
| <label>${escapeHtml(item.ok ? "OK" : "Blocked")}</label> | |
| <strong>${escapeHtml(humanizeStatus(item.id || "preflight"))}</strong> | |
| <span>${escapeHtml(item.detail || item.evidence || "Preflight tracked.")}</span> | |
| </div> | |
| `, | |
| ) | |
| .join("") | |
| : ` | |
| <div class="submit-row"> | |
| <label>Stop Rule</label> | |
| <strong>Submission gate pending</strong> | |
| <span>${escapeHtml(checklist.error || stopRules[0] || "Waiting for generated Devpost submission checklist.")}</span> | |
| </div> | |
| `; | |
| } | |
| function renderJudgeCloseout() { | |
| const closeout = state.judgeCloseout || {}; | |
| const gates = Array.isArray(closeout.gates) ? closeout.gates : []; | |
| const blocked = gates.filter((gate) => gate && gate.ok === false); | |
| const displayGates = blocked.length ? blocked.slice(0, 3) : gates.slice(0, 3); | |
| const safeToSubmit = Boolean(closeout.safe_to_submit); | |
| const nextCommand = closeout.error || closeout.next_command || closeout.next_detail; | |
| const nextRow = ` | |
| <div class="closeout-row"> | |
| <label>${escapeHtml(closeout.error ? "Status" : "Next Command")}</label> | |
| <strong>${escapeHtml(humanizeStatus(closeout.phase || "credential_entry"))}</strong> | |
| <span>${escapeHtml(nextCommand || "Waiting for generated final closeout status.")}</span> | |
| </div> | |
| `; | |
| el("closeoutStatus").textContent = closeout.error | |
| ? "Closeout unavailable" | |
| : safeToSubmit | |
| ? "Final closeout ready" | |
| : humanizeStatus(closeout.mode || "waiting_for_credentials"); | |
| el("closeoutRows").innerHTML = displayGates.length | |
| ? displayGates | |
| .map( | |
| (gate) => ` | |
| <div class="closeout-row"> | |
| <label>${escapeHtml(gate.ok ? "OK" : "Blocked")}</label> | |
| <strong>${escapeHtml(humanizeStatus(gate.id || "closeout"))}</strong> | |
| <span>${escapeHtml(gate.detail || gate.evidence || "Closeout gate tracked.")}</span> | |
| </div> | |
| `, | |
| ) | |
| .join("") + nextRow | |
| : nextRow; | |
| } | |
| function renderCampaigns() { | |
| el("campaignList").innerHTML = | |
| state.campaigns | |
| .map( | |
| (campaign) => ` | |
| <div class="campaign-item"> | |
| <strong>${escapeHtml(campaign.title)}</strong> | |
| <span>${escapeHtml(new Date(campaign.created_at).toLocaleString())}</span> | |
| </div> | |
| `, | |
| ) | |
| .join("") || '<p class="subtle">No campaigns yet.</p>'; | |
| } | |
| function selectedAsset() { | |
| return state.assets.find((asset) => asset.id === state.selectedAssetId) || state.assets[0]; | |
| } | |
| function renderAssets() { | |
| const visibleAssets = filteredAssets(); | |
| const rows = visibleAssets | |
| .map( | |
| (asset, index) => ` | |
| <div class="asset-row ${asset.id === state.selectedAssetId ? "selected" : ""}" data-id="${asset.id}"> | |
| <img class="thumb" src="${escapeHtml(asset.public_url || "")}" alt="Generated variant ${index + 1}" /> | |
| <div> | |
| <span class="asset-title">Variant ${index + 1}</span> | |
| <div class="mono">${escapeHtml(short(asset.prompt, 84))}</div> | |
| </div> | |
| <div class="mono">${escapeHtml(asset.storage_key)}</div> | |
| <div class="mono">${escapeHtml(short(asset.sha256, 24))}</div> | |
| <div><span class="status ${escapeHtml(asset.status)}">${escapeHtml(asset.status)}</span></div> | |
| </div> | |
| `, | |
| ) | |
| .join(""); | |
| const emptyCopy = state.assets.length | |
| ? "No assets match the current review filter." | |
| : "No generated assets yet."; | |
| el("assetRows").innerHTML = rows || `<p class="subtle">${emptyCopy}</p>`; | |
| document.querySelectorAll(".asset-row").forEach((row) => { | |
| row.addEventListener("click", () => { | |
| state.selectedAssetId = row.dataset.id; | |
| renderAssets(); | |
| renderInspector(); | |
| }); | |
| }); | |
| renderScorecard(); | |
| } | |
| function renderScorecard() { | |
| const total = state.assets.length; | |
| const approved = state.assets.filter((asset) => asset.status === "approved").length; | |
| const rejected = state.assets.filter((asset) => asset.status === "rejected").length; | |
| const draft = state.assets.filter((asset) => asset.status === "draft").length; | |
| const reviewed = approved + rejected; | |
| const coverage = total ? Math.round((reviewed / total) * 100) : 0; | |
| const firstAsset = state.assets[0]; | |
| el("scoreTotal").textContent = total; | |
| el("scoreApproved").textContent = approved; | |
| el("scoreDraft").textContent = draft; | |
| el("scoreRejected").textContent = rejected; | |
| el("scoreCoverage").textContent = `${coverage}%`; | |
| el("scoreBackend").textContent = firstAsset | |
| ? `${firstAsset.storage_backend} / ${firstAsset.provider}` | |
| : "No backend yet"; | |
| el("copySummary").disabled = !state.campaign || total === 0; | |
| renderJudgeSlate(); | |
| } | |
| function renderInspector() { | |
| const asset = selectedAsset(); | |
| el("chainBrief").textContent = state.campaign | |
| ? `${state.campaign.title}: ${state.campaign.brief}` | |
| : "No campaign yet."; | |
| el("chainProvider").textContent = asset | |
| ? `${asset.provider} / ${asset.model}` | |
| : "mock provider until Genblaze credentials are set"; | |
| el("chainStorage").textContent = asset ? asset.storage_key : "Waiting for generated asset"; | |
| el("approveAsset").disabled = !asset; | |
| el("rejectAsset").disabled = !asset; | |
| el("exportPacket").disabled = !state.campaign || state.assets.length === 0; | |
| el("downloadPacket").disabled = !state.campaign || state.assets.length === 0; | |
| el("manifestPreview").textContent = JSON.stringify(state.manifest, null, 2); | |
| } | |
| async function refreshManifest() { | |
| if (!state.campaign) return; | |
| state.manifest = await api(`/api/campaigns/${state.campaign.id}/manifest`); | |
| renderInspector(); | |
| } | |
| async function createCampaign() { | |
| state.campaign = await api("/api/campaigns", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| title: el("title").value, | |
| brief: el("brief").value, | |
| audience: el("audience").value, | |
| tone: el("tone").value, | |
| }), | |
| }); | |
| state.assets = []; | |
| state.selectedAssetId = null; | |
| setPacketCopy("No packet exported", "Campaign manifest pending export."); | |
| el("generateAssets").disabled = false; | |
| await loadCampaigns(); | |
| await refreshManifest(); | |
| renderAssets(); | |
| } | |
| async function generateAssets() { | |
| const count = Number(el("variantCount").value || 3); | |
| state.assets = await api(`/api/campaigns/${state.campaign.id}/generate?count=${count}`, { | |
| method: "POST", | |
| }); | |
| state.selectedAssetId = state.assets[0]?.id || null; | |
| await refreshManifest(); | |
| renderAssets(); | |
| } | |
| async function updateStatus(status) { | |
| const asset = selectedAsset(); | |
| if (!asset) return; | |
| const updated = await api(`/api/assets/${asset.id}/status`, { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| status, | |
| risk_note: | |
| status === "approved" | |
| ? "Approved for demo use after provenance review." | |
| : "Rejected pending provenance or brand review.", | |
| }), | |
| }); | |
| state.assets = state.assets.map((item) => (item.id === updated.id ? updated : item)); | |
| await refreshManifest(); | |
| renderAssets(); | |
| } | |
| async function exportPacket() { | |
| const packet = await api(`/api/campaigns/${state.campaign.id}/export`, { method: "POST" }); | |
| state.manifest = packet.manifest; | |
| setPacketCopy("Manifest exported", packet.stored_manifest.storage_key); | |
| renderInspector(); | |
| } | |
| async function loadJudgeDemo(source = "manual") { | |
| const demo = await api("/api/demo/judge-packet", { method: "POST" }); | |
| state.campaign = demo.campaign; | |
| state.assets = demo.assets; | |
| state.manifest = demo.manifest; | |
| state.selectedAssetId = state.assets[0]?.id || null; | |
| el("generateAssets").disabled = false; | |
| await loadCampaigns(); | |
| renderAssets(); | |
| renderInspector(); | |
| setPacketCopy( | |
| source === "auto" ? "Judge demo auto-loaded" : "Judge demo packet ready", | |
| "Public-safe evidence ZIP staged.", | |
| ); | |
| } | |
| async function copySummary() { | |
| if (!state.campaign || state.assets.length === 0) return; | |
| const total = state.assets.length; | |
| const approved = state.assets.filter((asset) => asset.status === "approved").length; | |
| const rejected = state.assets.filter((asset) => asset.status === "rejected").length; | |
| const draft = state.assets.filter((asset) => asset.status === "draft").length; | |
| const firstAsset = state.assets[0]; | |
| const lines = [ | |
| `ProofFrame packet: ${state.campaign.title}`, | |
| `Campaign ID: ${state.campaign.id}`, | |
| `Assets: ${total} total, ${approved} approved, ${rejected} rejected, ${draft} draft`, | |
| `Runtime: ${firstAsset.storage_backend} storage / ${firstAsset.provider} generation`, | |
| `First asset checksum: ${firstAsset.sha256}`, | |
| `First storage key: ${firstAsset.storage_key}`, | |
| "No secrets, credentials, cookies, or signed URLs are included in this summary.", | |
| ]; | |
| await navigator.clipboard.writeText(lines.join("\n")); | |
| setPacketCopy("Evidence summary copied", `${state.campaign.id} summary copied without secrets.`); | |
| } | |
| function downloadPacket() { | |
| if (!state.campaign) return; | |
| const link = document.createElement("a"); | |
| link.href = `/api/campaigns/${state.campaign.id}/packet.zip`; | |
| link.download = `${state.campaign.id}-proofframe-packet.zip`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| link.remove(); | |
| setPacketCopy("Evidence packet download started", `${state.campaign.id}-proofframe-packet.zip`); | |
| } | |
| el("createCampaign").addEventListener("click", createCampaign); | |
| el("generateAssets").addEventListener("click", generateAssets); | |
| el("approveAsset").addEventListener("click", () => updateStatus("approved")); | |
| el("rejectAsset").addEventListener("click", () => updateStatus("rejected")); | |
| el("exportPacket").addEventListener("click", exportPacket); | |
| el("downloadPacket").addEventListener("click", downloadPacket); | |
| el("copySummary").addEventListener("click", copySummary); | |
| el("judgeDemo").addEventListener("click", () => loadJudgeDemo()); | |
| el("loadCampaigns").addEventListener("click", () => { | |
| loadCampaigns(); | |
| loadSubmissionGate(); | |
| }); | |
| el("assetSearch").addEventListener("input", (event) => { | |
| state.filters.query = event.target.value; | |
| renderAssets(); | |
| }); | |
| el("statusFilter").addEventListener("change", (event) => { | |
| state.filters.status = event.target.value; | |
| renderAssets(); | |
| }); | |
| el("sampleBrief").addEventListener("click", () => { | |
| el("title").value = "Indie Film Festival Packet"; | |
| el("audience").value = "festival programmers and local press"; | |
| el("tone").value = "cinematic, credible, storage-first"; | |
| el("brief").value = | |
| "Generate a public-safe visual packet for a neighborhood film festival, with manifest evidence ready for client approval."; | |
| }); | |
| async function boot() { | |
| await Promise.all([ | |
| loadHealth(), | |
| loadCampaigns(), | |
| loadSubmissionGate(), | |
| loadJudgeBrief(), | |
| loadJudgeCrosswalk(), | |
| loadJudgeRecording(), | |
| loadJudgeDevpost(), | |
| loadJudgeSubmitChecklist(), | |
| loadJudgeCloseout(), | |
| ]); | |
| renderInspector(); | |
| if (shouldAutoLoadJudgeDemo()) { | |
| await loadJudgeDemo("auto"); | |
| } | |
| } | |
| boot().catch((error) => { | |
| setPacketCopy("Runtime error", error.message); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |