diff --git a/.gitignore b/.gitignore index c6fb17c..d2a3e01 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ node_modules/ .node_modules/ web/dist/ web/tsconfig.tsbuildinfo +build/ diff --git a/internal/webui/assets/assets/index-B0BkAbwp.css b/internal/webui/assets/assets/index-B0BkAbwp.css deleted file mode 100644 index 1c9cb25..0000000 --- a/internal/webui/assets/assets/index-B0BkAbwp.css +++ /dev/null @@ -1 +0,0 @@ -:root{color-scheme:dark;font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:#eef2f0;background:#0b0f12;font-synthesis:none;--bg: #0b0f12;--sidebar: #0d1215;--surface-1: #11171b;--surface-2: #151d21;--surface-3: #1a2429;--surface-hover: #1e2a2f;--line: rgba(221, 236, 231, .09);--line-strong: rgba(221, 236, 231, .15);--text: #eef2f0;--text-soft: #a9b4b1;--text-muted: #6f7c79;--accent: #8ee0b2;--accent-strong: #b4f0cf;--accent-wash: rgba(86, 200, 139, .12);--accent-line: rgba(86, 200, 139, .3);--blue: #88bdec;--blue-wash: rgba(96, 164, 224, .12);--amber: #efbd72;--amber-wash: rgba(229, 164, 76, .12);--red: #ec8f85;--red-wash: rgba(226, 105, 92, .12);--violet: #b5a2ef;--violet-wash: rgba(154, 126, 226, .12);--mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;--radius-sm: 8px;--radius: 12px;--radius-lg: 18px;--shadow: 0 20px 65px rgba(0, 0, 0, .42);--ease: cubic-bezier(.2, 0, 0, 1)}*{box-sizing:border-box}html{min-width:320px;min-height:100%;background:var(--bg)}body{min-width:320px;min-height:100vh;margin:0;background:radial-gradient(circle at 82% -10%,rgba(73,129,109,.09),transparent 31rem),var(--bg)}body.modal-open{overflow:hidden}button,input,textarea,select,summary{font:inherit}button,.button-link{display:inline-flex;min-height:38px;align-items:center;justify-content:center;gap:8px;padding:8px 13px;border:1px solid var(--accent-line);border-radius:var(--radius-sm);color:#092016;background:var(--accent);font-size:12px;font-weight:700;text-decoration:none;cursor:pointer;transition:transform .14s var(--ease),background .14s var(--ease),border-color .14s var(--ease),color .14s var(--ease)}button:hover:not(:disabled),.button-link:hover{border-color:var(--accent-strong);background:var(--accent-strong);transform:translateY(-1px)}button:active:not(:disabled),.button-link:active{transform:translateY(0)}button:disabled{opacity:.42;cursor:not-allowed}button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible,summary:focus-visible,a:focus-visible{outline:2px solid var(--accent);outline-offset:2px}a{color:inherit}input,textarea,select{width:100%;border:1px solid var(--line-strong);border-radius:var(--radius-sm);outline:0;color:var(--text);background:#0d1316;transition:border-color .14s var(--ease),box-shadow .14s var(--ease),background .14s var(--ease)}input,select{min-height:42px;padding:9px 11px}textarea{min-height:116px;padding:11px;line-height:1.55;resize:vertical}input:hover,textarea:hover,select:hover{border-color:#ddece73b}input:focus,textarea:focus,select:focus{border-color:var(--accent-line);background:#0f1619;box-shadow:0 0 0 3px #56c88b14}input::placeholder,textarea::placeholder{color:#53615e}select{color:var(--text-soft);cursor:pointer}kbd,.mono,.task-id,.task-short-id,pre,code,time{font-family:var(--mono);font-feature-settings:"tnum" 1,"zero" 1}.icon{display:block;flex:none}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.app-shell{min-height:100vh;padding-left:224px}.sidebar{position:fixed;inset:0 auto 0 0;z-index:20;display:flex;width:224px;flex-direction:column;padding:0 14px 15px;border-right:1px solid var(--line);background:#0d1215f7;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.brand{display:flex;height:64px;align-items:center;gap:11px;margin:0 -14px;padding:0 18px;border-bottom:1px solid var(--line);text-decoration:none}.logo-symbol{position:relative;display:block;width:25px;height:25px;flex:none}.logo-symbol i{position:absolute;display:block;width:7px;height:7px;border:2px solid var(--accent);border-radius:50%;box-shadow:0 0 13px #8ee0b233}.logo-symbol i:nth-child(1){top:0;left:0}.logo-symbol i:nth-child(2){right:0;top:9px}.logo-symbol i:nth-child(3){bottom:0;left:0}.logo-symbol:before,.logo-symbol:after{position:absolute;left:6px;width:14px;height:1px;transform-origin:left center;background:var(--accent);content:""}.logo-symbol:before{top:6px;transform:rotate(30deg)}.logo-symbol:after{bottom:6px;transform:rotate(-30deg)}.brand>span:last-child,.login-brand>span:last-child{display:grid;gap:1px}.brand b,.login-brand b{color:var(--text);font-size:14px;letter-spacing:-.02em}.brand small,.login-brand small{color:var(--text-muted);font:9px var(--mono);letter-spacing:.13em;text-transform:uppercase}.primary-nav{display:grid;gap:5px;margin-top:24px}.nav-heading,.menu-label{margin:0 10px 8px;color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.13em;text-transform:uppercase}.primary-nav a{position:relative;display:grid;min-height:42px;grid-template-columns:20px minmax(0,1fr) auto;align-items:center;gap:10px;padding:8px 10px;border:1px solid transparent;border-radius:var(--radius-sm);color:var(--text-muted);font-size:12px;font-weight:600;text-decoration:none;transition:color .14s var(--ease),background .14s var(--ease),border-color .14s var(--ease)}.primary-nav a:hover{color:var(--text-soft);background:var(--surface-2)}.primary-nav a.active{border-color:var(--accent-line);color:var(--accent-strong);background:var(--accent-wash)}.primary-nav a.active:before{position:absolute;left:-15px;width:3px;height:22px;border-radius:0 4px 4px 0;background:var(--accent);content:""}.nav-count{min-width:24px;padding:2px 5px;border-radius:999px;color:var(--text-muted);background:var(--surface-3);font:9px var(--mono);text-align:center}.nav-count.attention{color:var(--amber);background:var(--amber-wash)}.sidebar-status{display:flex;align-items:flex-start;gap:10px;margin-top:auto;padding:12px 10px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface-1)}.sidebar-status>span:last-child{display:grid;min-width:0;gap:3px}.sidebar-status b{color:var(--text-soft);font-size:10px}.sidebar-status small{color:var(--text-muted);font-size:9px}.signal{display:inline-block;width:7px;height:7px;flex:none;margin-top:3px;border-radius:50%;background:var(--red);box-shadow:0 0 0 4px var(--red-wash)}.signal.online{background:var(--accent);box-shadow:0 0 0 4px var(--accent-wash)}.sidebar-foot{padding:13px 9px 0;color:#4e5a57;font:8px var(--mono);letter-spacing:.1em}.topbar{position:fixed;inset:0 0 auto 224px;z-index:18;display:flex;height:64px;align-items:center;gap:14px;padding:0 28px;border-bottom:1px solid var(--line);background:#0b0f12e0;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.topbar-context{display:flex;min-width:0;align-items:center;gap:6px;color:var(--text-muted);font-size:11px}.topbar-context b{overflow:hidden;color:var(--text-soft);font-weight:600;text-overflow:ellipsis;white-space:nowrap}.command-trigger,.account-trigger,.button-secondary,.icon-button{border-color:var(--line-strong);color:var(--text-soft);background:var(--surface-1)}.command-trigger:hover:not(:disabled),.account-trigger:hover:not(:disabled),.button-secondary:hover:not(:disabled),.icon-button:hover:not(:disabled){border-color:#ddece73d;color:var(--text);background:var(--surface-3)}.command-trigger{min-height:34px;margin-left:auto;padding:6px 8px 6px 10px;color:var(--text-muted);font-weight:500}.command-trigger kbd,.page-actions kbd{padding:3px 5px;border:1px solid var(--line-strong);border-radius:5px;color:var(--text-muted);background:#00000021;font-size:9px;font-weight:400}.account{position:relative}.account-trigger{min-height:34px;padding:3px 8px 3px 4px;font-weight:600}.avatar{display:grid;width:26px;height:26px;place-items:center;border:1px solid var(--accent-line);border-radius:7px;color:var(--accent-strong);background:var(--accent-wash);font:9px var(--mono)}.account-menu{position:absolute;top:calc(100% + 9px);right:0;display:grid;width:196px;gap:9px;padding:11px;border:1px solid var(--line-strong);border-radius:var(--radius);background:var(--surface-2);box-shadow:var(--shadow)}.account-menu-user{display:flex;align-items:center;gap:9px;padding:3px 4px 10px;border-bottom:1px solid var(--line)}.account-menu-user>span:last-child{display:grid;min-width:0;gap:2px}.account-menu-user b{overflow:hidden;color:var(--text);font-size:11px;text-overflow:ellipsis}.account-menu-user small{color:var(--text-muted);font-size:9px}.account-menu>a{display:flex;min-height:33px;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--radius-sm);color:var(--text-soft);background:var(--surface-3);font-size:11px;font-weight:650;text-decoration:none}.account-menu>a:hover{color:var(--text);background:var(--surface-hover)}.account-menu button{width:100%;min-height:33px;justify-content:flex-start;color:var(--text-soft);background:var(--surface-3)}.content{min-height:100vh;padding-top:64px}.page{width:min(1500px,100%);margin:0 auto;padding:38px 32px 68px}.page-header{display:flex;align-items:flex-end;justify-content:space-between;gap:28px;margin-bottom:28px}.page-header h1,.task-header h1,.empty-state h1{margin:0;font-size:clamp(32px,4vw,48px);font-weight:650;line-height:1.02;letter-spacing:-.055em}.page-header>div>p{max-width:580px;margin:10px 0 0;color:var(--text-soft);font-size:14px;line-height:1.55}.eyebrow{display:block;margin-bottom:8px;color:var(--accent);font-size:9px;font-weight:800;letter-spacing:.15em;text-transform:uppercase}.page-actions{display:flex;align-items:center;gap:9px}.back-link{display:inline-flex;align-items:center;gap:5px;margin-bottom:24px;color:var(--text-muted);font-size:11px;font-weight:600;text-decoration:none}.back-link:hover{color:var(--accent-strong)}.section-header,.panel-header{display:flex;align-items:center;justify-content:space-between;gap:18px}.section-header{margin-bottom:13px}.section-header h2,.panel-header h2{margin:0;font-size:14px;letter-spacing:-.015em}.section-header p,.panel-header p{margin:4px 0 0;color:var(--text-muted);font-size:11px}.sync-label{display:inline-flex;align-items:center;gap:8px;color:var(--text-muted);font:9px var(--mono);text-transform:uppercase}.sync-label .signal{margin:0}.panel{padding:20px;border:1px solid var(--line);border-radius:var(--radius);background:linear-gradient(145deg,#151d21e6,#10171be6)}.panel-intro{margin:7px 0 0;color:var(--text-muted);font-size:11px;line-height:1.5}.empty-state{display:grid;min-height:300px;place-items:center;align-content:center;padding:38px;border:1px dashed var(--line-strong);border-radius:var(--radius-lg);text-align:center;background:#11171b73}.empty-state.compact{min-height:220px}.empty-state .empty-icon{display:grid;width:44px;height:44px;place-items:center;margin-bottom:14px;border:1px solid var(--accent-line);border-radius:13px;color:var(--accent);background:var(--accent-wash)}.empty-state h1,.empty-state h2{margin:0;font-size:20px;letter-spacing:-.03em}.empty-state p{max-width:390px;margin:8px 0 17px;color:var(--text-muted);font-size:12px;line-height:1.55}.error-state .empty-icon{border-color:#ec8f8540;color:var(--red);background:var(--red-wash)}.skeleton{min-height:180px;border-radius:var(--radius);background:linear-gradient(100deg,var(--surface-1) 30%,var(--surface-2) 50%,var(--surface-1) 70%);background-size:250% 100%;animation:shimmer 1.5s infinite}.skeleton-title{width:300px;min-height:44px;margin-top:10px}.skeleton-subtitle{width:420px;max-width:75%;min-height:18px;margin-top:13px}.skeleton-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:50px}@keyframes shimmer{to{background-position-x:-250%}}.attention-banner{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:13px;margin-bottom:15px;padding:13px 14px;border:1px solid rgba(239,189,114,.26);border-radius:var(--radius);background:var(--amber-wash)}.attention-icon{display:grid;width:35px;height:35px;place-items:center;border:1px solid rgba(239,189,114,.25);border-radius:10px;color:var(--amber);background:#efbd7214}.attention-banner b{font-size:12px}.attention-banner p{margin:3px 0 0;color:var(--text-soft);font-size:11px}.attention-banner>a{display:inline-flex;align-items:center;gap:3px;color:var(--amber);font-size:11px;font-weight:700;text-decoration:none}.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:32px}.metric{display:grid;min-width:0;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:14px;border:1px solid var(--line);border-radius:var(--radius);background:#11171bd1}.metric-icon{display:grid;width:34px;height:34px;grid-row:span 2;place-items:center;border:1px solid var(--line);border-radius:9px;color:var(--text-muted);background:var(--surface-2)}.metric-copy{display:flex;min-width:0;align-items:baseline;justify-content:space-between;gap:8px}.metric-copy small{overflow:hidden;color:var(--text-soft);font-size:10px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.metric-copy b{color:var(--text);font:600 19px var(--mono)}.metric-detail{overflow:hidden;color:var(--text-muted);font-size:10px;text-overflow:ellipsis;white-space:nowrap}.metric.tone-good .metric-icon{border-color:var(--accent-line);color:var(--accent);background:var(--accent-wash)}.metric.tone-warning .metric-icon{border-color:#efbd7242;color:var(--amber);background:var(--amber-wash)}.board-toolbar{display:flex;align-items:center;gap:9px;margin-bottom:13px}.segmented{display:flex;gap:3px;margin-right:auto;padding:3px;border:1px solid var(--line);border-radius:10px;background:var(--surface-1)}.segmented button{min-height:31px;padding:5px 10px;border-color:transparent;color:var(--text-muted);background:transparent;font-size:10px;font-weight:650}.segmented button:hover:not(:disabled){border-color:transparent;color:var(--text-soft);background:var(--surface-3);transform:none}.segmented button.selected{border-color:var(--line-strong);color:var(--text);background:var(--surface-3);box-shadow:0 2px 7px #0000002e}.segmented button span{margin-left:5px;color:var(--text-muted);font:9px var(--mono)}.search-field{position:relative;display:flex;width:min(280px,28vw);align-items:center}.search-field>.icon{position:absolute;left:11px;z-index:1;color:var(--text-muted)}.search-field input{min-height:38px;padding:7px 34px;color:var(--text-soft);background:var(--surface-1);font-size:11px}.search-field button{position:absolute;right:5px;min-width:27px;min-height:27px;padding:0;border-color:transparent;color:var(--text-muted);background:transparent}.select-field{width:155px}.select-field select{min-height:38px;padding:7px 29px 7px 10px;background-color:var(--surface-1);font-size:11px}.task-board{display:grid;gap:10px;overflow-x:auto;padding-bottom:6px;scrollbar-color:var(--surface-3) transparent}.task-board.lanes-2{grid-template-columns:repeat(2,minmax(270px,1fr))}.task-board.lanes-3{grid-template-columns:repeat(3,minmax(255px,1fr))}.task-board.lanes-5{grid-template-columns:repeat(5,minmax(235px,1fr))}.task-board.lanes-7{grid-template-columns:repeat(7,minmax(225px,1fr))}.task-lane{min-height:310px;padding:10px;border:1px solid var(--line);border-radius:var(--radius);background:#11171b9e}.task-lane>header{display:flex;height:29px;align-items:center;justify-content:space-between;padding:0 3px 9px}.task-lane>header>span{display:inline-flex;align-items:center;gap:7px;color:var(--text-soft);font-size:10px;font-weight:750;letter-spacing:.09em;text-transform:uppercase}.task-lane>header i,.status-pill i,.health-badge i{display:block;width:6px;height:6px;border-radius:50%;background:var(--text-muted)}.lane-queued>header i,.status-pill.state-queued i{background:var(--blue);box-shadow:0 0 0 3px var(--blue-wash)}.lane-leased>header i,.status-pill.state-leased i{background:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}.lane-blocked>header i,.lane-needs_attention>header i,.status-pill.state-blocked i{background:var(--amber);box-shadow:0 0 0 3px var(--amber-wash)}.status-pill.state-needs_attention i{background:var(--amber);box-shadow:0 0 0 3px var(--amber-wash)}.lane-in_review>header i,.status-pill.state-in_review i{background:var(--violet);box-shadow:0 0 0 3px var(--violet-wash)}.lane-completed>header i,.status-pill.state-completed i{background:#8fcfc5;box-shadow:0 0 0 3px #8fcfc51a}.lane-failed>header i,.status-pill.state-failed i{background:var(--red);box-shadow:0 0 0 3px var(--red-wash)}.task-lane>header b{display:grid;min-width:20px;height:20px;place-items:center;border:1px solid var(--line);border-radius:999px;color:var(--text-muted);background:var(--surface-2);font:9px var(--mono)}.lane-cards{display:grid;align-content:start;gap:8px}.empty-lane{margin:0;padding:30px 9px;color:#53615e;font-size:10px;text-align:center}.task-card{position:relative;display:block;min-width:0;padding:12px;border:1px solid var(--line);border-radius:10px;color:inherit;background:var(--surface-2);text-decoration:none;box-shadow:0 5px 18px #00000014;transition:border-color .15s var(--ease),background .15s var(--ease),transform .15s var(--ease),box-shadow .15s var(--ease)}.task-card:hover{z-index:1;border-color:#8ee0b24d;background:var(--surface-hover);box-shadow:0 9px 22px #0000002b;transform:translateY(-2px)}.task-card.state-blocked{border-color:#efbd722e}.task-card.state-needs_attention{border-color:#efbd7240;background:linear-gradient(145deg,rgba(62,46,27,.28),var(--surface-2))}.task-card.state-in_review{border-color:#b5a2ef33}.task-card.state-failed{border-color:#ec8f852e}.task-card-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:9px}.status-pill{display:inline-flex;min-height:22px;align-items:center;gap:6px;padding:3px 7px;border:1px solid var(--line);border-radius:999px;color:var(--text-muted);background:#0b0f1259;font-size:8px;font-weight:750;letter-spacing:.05em;text-transform:uppercase}.status-pill.state-leased,.status-pill.state-completed{color:var(--accent-strong)}.status-pill.state-blocked,.status-pill.state-needs_attention{color:var(--amber)}.status-pill.state-in_review{color:var(--violet)}.status-pill.state-failed{color:var(--red)}.task-short-id{color:#5c6865;font-size:8px}.task-card h3{display:-webkit-box;overflow:hidden;margin:0 0 6px;font-size:13px;font-weight:650;line-height:1.4;letter-spacing:-.012em;-webkit-box-orient:vertical;-webkit-line-clamp:2}.task-card>p{display:-webkit-box;overflow:hidden;min-height:30px;margin:0 0 10px;color:var(--text-muted);font-size:11px;line-height:1.5;-webkit-box-orient:vertical;-webkit-line-clamp:2}.approval-chip,.reason-chip{display:inline-flex;align-items:center;gap:5px;margin:0 0 9px;padding:4px 6px;border-radius:5px;color:var(--amber);background:var(--amber-wash);font-size:9px;font-weight:650}.reason-chip{color:var(--text-soft);background:var(--surface-3)}.capability-list,.fact-tags>div,.worker-projects>div{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:9px}.capability-list span,.fact-tags b,.worker-projects b{padding:3px 5px;border:1px solid var(--line);border-radius:5px;color:var(--text-muted);background:#0b0f1247;font:9px var(--mono);font-weight:500}.task-card footer{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:9px;padding-top:9px;border-top:1px solid var(--line);color:#66736f;font-size:9px}.task-card footer>span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-card footer time{color:var(--text-muted);white-space:nowrap}.overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:100;display:grid;padding:20px;background:#040708c7;-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px)}.modal-overlay{place-items:center}.modal{width:min(740px,100%);max-height:calc(100vh - 40px);overflow:auto;border:1px solid var(--line-strong);border-radius:var(--radius-lg);background:#11181c;box-shadow:var(--shadow)}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:22px 23px 18px;border-bottom:1px solid var(--line)}.modal-header h2{margin:0;font-size:22px;letter-spacing:-.035em}.modal-header p{margin:6px 0 0;color:var(--text-muted);font-size:11px}.icon-button{width:34px;min-height:34px;padding:0}.eligibility{display:flex;align-items:flex-start;gap:10px;margin:16px 23px 0;padding:10px 11px;border:1px solid var(--accent-line);border-radius:var(--radius-sm);background:var(--accent-wash)}.eligibility>span:last-child{display:grid;gap:2px}.eligibility b{color:var(--accent-strong);font-size:11px}.eligibility small{color:var(--text-muted);font-size:10px}.eligibility.warning{border-color:#efbd7247;background:var(--amber-wash)}.eligibility.warning .signal{background:var(--amber);box-shadow:0 0 0 4px var(--amber-wash)}.eligibility.warning b{color:var(--amber)}.create-modal form{display:grid;gap:14px;padding:18px 23px 23px}.create-modal label{display:grid;gap:6px}.create-modal label>span{display:flex;justify-content:space-between;color:var(--text-soft);font-size:11px;font-weight:650}.create-modal label>span b{color:var(--text-muted);font-size:8px;font-weight:600;letter-spacing:.05em;text-transform:uppercase}.create-modal label>small{color:var(--text-muted);font-size:10px}.form-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}.short-textarea{min-height:82px}.advanced-fields{border-top:1px solid var(--line);padding-top:4px}.advanced-fields summary{color:var(--text-soft);font-size:10px;font-weight:650;cursor:pointer}.advanced-fields p{margin:8px 0;color:var(--text-muted);font-size:10px}.advanced-fields textarea{min-height:80px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:3px}.form-error,.notice{display:flex;align-items:flex-start;gap:7px;margin:0;padding:9px 10px;border:1px solid rgba(236,143,133,.28);border-radius:var(--radius-sm);color:var(--red);background:var(--red-wash);font-size:11px;line-height:1.45}.notice{border-color:var(--accent-line);color:var(--accent-strong);background:var(--accent-wash)}.palette-overlay{place-items:start center;padding-top:12vh}.palette{width:min(590px,100%);overflow:hidden;border:1px solid var(--line-strong);border-radius:15px;background:#11181c;box-shadow:var(--shadow)}.palette-input{position:relative;display:flex;align-items:center;border-bottom:1px solid var(--line)}.palette-input .icon{position:absolute;left:17px;color:var(--text-muted)}.palette-input input{min-height:57px;padding:14px 17px 14px 48px;border:0;border-radius:0;background:transparent;font-size:14px}.palette-list{display:grid;gap:3px;padding:10px}.palette-list .menu-label{margin:3px 8px 5px}.palette-list button{width:100%;min-height:42px;justify-content:space-between;padding:8px 10px;border-color:transparent;color:var(--text-soft);background:transparent;font-weight:550}.palette-list button>span{display:flex;align-items:center;gap:9px}.palette-list button.selected,.palette-list button:hover:not(:disabled){border-color:var(--line);color:var(--text);background:var(--surface-3);transform:none}.palette-list kbd,.palette-footer kbd{color:var(--text-muted);font-size:9px}.palette-empty{padding:24px 8px;color:var(--text-muted);font-size:11px;text-align:center}.palette-footer{display:flex;gap:15px;padding:9px 17px;border-top:1px solid var(--line);color:var(--text-muted);font-size:8px}.task-header{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:20px}.task-heading-meta{display:flex;align-items:center;gap:8px;margin-bottom:11px;color:var(--text-muted);font-size:9px}.task-heading-meta>span:not(.status-pill)+span:before{margin-right:8px;color:#45514e;content:"/"}.task-header h1{max-width:850px;font-size:clamp(29px,4vw,44px)}.task-id{margin:9px 0 0;color:var(--text-muted);font-size:9px}.diagnosis{display:grid;grid-template-columns:minmax(0,1fr) minmax(420px,.8fr);align-items:center;gap:24px;margin-bottom:14px;padding:18px;border:1px solid var(--accent-line);border-radius:var(--radius);background:linear-gradient(110deg,var(--accent-wash),rgba(17,23,27,.65))}.diagnosis.state-blocked,.diagnosis.state-needs_attention,.diagnosis.state-failed{border-color:#efbd723b;background:linear-gradient(110deg,var(--amber-wash),rgba(17,23,27,.65))}.diagnosis.state-in_review{border-color:#b5a2ef42;background:linear-gradient(110deg,var(--violet-wash),rgba(17,23,27,.65))}.diagnosis-copy{display:flex;min-width:0;align-items:flex-start;gap:13px}.diagnosis-symbol{display:grid;width:38px;height:38px;flex:none;place-items:center;border:1px solid var(--accent-line);border-radius:10px;color:var(--accent);background:var(--accent-wash)}.diagnosis-symbol.state-blocked,.diagnosis-symbol.state-needs_attention,.diagnosis-symbol.state-failed{border-color:#efbd7240;color:var(--amber);background:var(--amber-wash)}.diagnosis-symbol.state-in_review{border-color:#b5a2ef4d;color:var(--violet);background:var(--violet-wash)}.diagnosis h2{margin:0;font-size:17px;letter-spacing:-.025em}.diagnosis-copy p{max-width:630px;margin:5px 0 0;color:var(--text-soft);font-size:10px;line-height:1.5}.diagnosis dl{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin:0;overflow:hidden;border:1px solid var(--line);border-radius:9px;background:#090e104d}.diagnosis dl>div{min-width:0;padding:9px;border-right:1px solid var(--line)}.diagnosis dl>div:last-child{border:0}.diagnosis dt,.facts dt,.worker-stats dt{margin-bottom:5px;color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.09em;text-transform:uppercase}.diagnosis dd{overflow:hidden;margin:0;color:var(--text-soft);font-size:10px;text-overflow:ellipsis;white-space:nowrap}.approval-panel{display:grid;gap:12px;margin-bottom:14px;padding:18px;border:1px solid rgba(239,189,114,.3);border-radius:var(--radius);background:linear-gradient(135deg,#44301461,#151d21eb)}.approval-heading{display:flex;align-items:flex-start;gap:13px}.approval-heading h2{margin:0;font-size:18px}.approval-heading p{margin:5px 0 0;color:var(--text-soft);font-size:10px}.approval-meta{display:flex;flex-wrap:wrap;gap:6px}.approval-meta span{padding:4px 6px;border:1px solid var(--line);border-radius:5px;color:var(--text-muted);background:#0a0e104d;font:9px var(--mono)}.approval-meta b{color:var(--text-soft);font-weight:500}.approval-code,.artifact-content{margin:0;padding:14px;overflow:auto;border:1px solid var(--line-strong);border-radius:var(--radius-sm);color:#d4dcd9;background:#080c0e;font-size:11px;line-height:1.6;white-space:pre-wrap}.approval-note{margin:0;color:var(--text-soft);font-size:11px;line-height:1.5}.approval-actions{display:flex;gap:8px}.detail-layout{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:14px;align-items:start}.detail-main,.detail-side{display:grid;gap:14px}.immutable-badge{padding:4px 6px;border:1px solid var(--line);border-radius:5px;color:var(--text-muted);font:9px var(--mono);letter-spacing:.05em;text-transform:uppercase}.task-description{margin:14px 0 0;color:#c1cac7;font-size:12px;line-height:1.7;white-space:pre-wrap}.acceptance-list,.quality-gate{margin-top:18px;padding-top:15px;border-top:1px solid var(--line)}.acceptance-list h3{margin:0 0 9px;color:var(--text-soft);font-size:11px}.acceptance-list ul{display:grid;gap:7px;margin:0;padding:0;list-style:none}.acceptance-list li{display:flex;align-items:flex-start;gap:8px;color:var(--text-soft);font-size:11px;line-height:1.45}.acceptance-list li>span{display:grid;width:18px;height:18px;flex:none;place-items:center;border:1px solid var(--accent-line);border-radius:5px;color:var(--accent);background:var(--accent-wash)}.quality-gate{display:grid;gap:6px}.quality-gate>span{color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.quality-gate code{display:block;padding:8px 10px;border:1px solid var(--line);border-radius:6px;color:var(--accent-strong);background:#0b1113;font-size:10px}.capture-panel{overflow:hidden;border:1px solid var(--line-strong);border-radius:var(--radius);background:#090d0f}.capture-panel>header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 12px;border-bottom:1px solid var(--line);background:var(--surface-1)}.capture-title,.capture-actions{display:flex;align-items:center;gap:8px}.capture-title>span{display:grid;width:30px;height:30px;place-items:center;border:1px solid var(--line);border-radius:7px;color:var(--accent);background:var(--accent-wash)}.capture-title h2{margin:0;font-size:11px}.capture-title p{margin:3px 0 0;color:var(--text-muted);font:9px var(--mono)}.small-button{min-height:32px;padding:5px 9px;font-size:10px}.capture-actions .icon-button{min-height:32px;width:32px}.terminal-filter{position:relative;display:flex;align-items:center;margin:10px 11px 0}.terminal-filter .icon{position:absolute;left:9px;color:#55625e}.terminal-filter input{min-height:31px;padding:5px 9px 5px 30px;border-color:var(--line);background:#0c1113;font-size:10px}.terminal-output{max-height:530px;margin:0;padding:15px;overflow:auto;color:#aabcb5;font-size:11px;line-height:1.65;white-space:pre;scrollbar-color:var(--surface-3) transparent}.capture-panel>footer{padding:7px 12px;border-top:1px solid var(--line);color:var(--text-muted);background:var(--surface-1);font-size:9px}.capture-empty{display:flex;align-items:flex-start;gap:11px;padding:17px;border:1px dashed var(--line-strong);border-radius:var(--radius);background:#11171b80}.capture-empty>span{color:var(--text-muted)}.capture-empty b{font-size:11px}.capture-empty p{margin:4px 0 0;color:var(--text-muted);font-size:10px;line-height:1.5}.action-panel .panel-header{margin-bottom:0}.action-list{display:grid;gap:8px;margin-top:13px}.action-card{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:11px;margin:0;padding:11px;border:1px solid var(--line);border-radius:9px;background:#0a0f1152}.action-icon{display:grid;width:31px;height:31px;place-items:center;border:1px solid var(--line);border-radius:8px;color:var(--text-muted);background:var(--surface-2)}.action-card h3{margin:0;font-size:11px}.action-card p{margin:3px 0 0;color:var(--text-muted);font-size:10px;line-height:1.5}.action-form{align-items:start}.action-copy{min-width:0}.action-copy input,.action-copy textarea{min-height:36px;margin-top:9px;padding:7px 9px;font-size:10px}.action-copy textarea{min-height:78px}.action-buttons{display:flex;align-self:end;gap:6px}.action-card button{min-height:32px;padding:6px 9px;font-size:10px}.action-card.confirming{border-color:#efbd7247;background:var(--amber-wash)}.button-danger,.button-danger:hover:not(:disabled){border-color:#ec8f8566;color:#27100e;background:var(--red)}.inline-error{color:var(--red)!important}.unavailable-panel{padding:0}.unavailable-panel summary{display:flex;align-items:center;justify-content:space-between;padding:13px 15px;color:var(--text-muted);font-size:10px;font-weight:650;cursor:pointer}.unavailable-panel summary span{display:grid;width:20px;height:20px;place-items:center;border-radius:50%;background:var(--surface-3);font:8px var(--mono)}.unavailable-list{display:grid;gap:8px;padding:0 15px 14px}.unavailable-list>div{display:grid;gap:3px;padding:9px;border:1px solid var(--line);border-radius:7px}.unavailable-list b{color:var(--text-soft);font-size:10px}.unavailable-list span{color:var(--text-muted);font-size:9px}.facts{display:grid;gap:0;margin:10px 0 0}.facts>div{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:10px;padding:9px 0;border-bottom:1px solid var(--line)}.facts>div:last-child{border:0}.facts dt{margin:0}.facts dd{overflow:hidden;margin:0;color:var(--text-soft);font-size:10px;text-align:right;text-overflow:ellipsis;white-space:nowrap}.fact-tags{display:grid;gap:7px;margin-top:9px;padding-top:12px;border-top:1px solid var(--line)}.fact-tags>span,.worker-projects>span,.worker-current>span{color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.09em;text-transform:uppercase}.fact-tags>div{margin:0}.artifacts-panel{display:grid;gap:7px}.artifacts-panel>a{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:9px;padding:9px;border:1px solid var(--line);border-radius:8px;color:var(--text-muted);text-decoration:none}.artifacts-panel>a:hover{border-color:var(--accent-line);color:var(--accent);background:var(--accent-wash)}.artifacts-panel a>span{display:grid;min-width:0;gap:2px}.artifacts-panel b{color:var(--text-soft);font-size:10px}.artifacts-panel small{color:var(--text-muted);font:9px var(--mono)}.timeline-panel{overflow:hidden}.timeline{margin:12px 0 0;padding:0;list-style:none}.timeline li{position:relative;display:grid;grid-template-columns:12px minmax(0,1fr);gap:6px;padding-bottom:13px}.timeline li:not(:last-child):before{position:absolute;top:11px;bottom:0;left:3px;width:1px;background:var(--line-strong);content:""}.event-dot{position:relative;z-index:1;display:block;width:7px;height:7px;margin-top:4px;border:2px solid var(--surface-2);border-radius:50%;background:var(--text-muted);box-shadow:0 0 0 1px var(--line-strong)}.timeline li.latest .event-dot{background:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}.event-copy{min-width:0}.event-copy>button{display:grid;width:100%;min-height:0;justify-content:stretch;gap:2px;padding:0;border:0;color:inherit;background:transparent;text-align:left}.event-copy>button:hover:not(:disabled){border:0;background:transparent;transform:none}.event-copy>button:disabled{opacity:1;cursor:default}.event-copy b{overflow:hidden;color:var(--text-soft);font-size:10px;text-overflow:ellipsis;white-space:nowrap}.event-copy span,.event-copy time{color:var(--text-muted);font-size:9px}.event-copy time{display:block;margin-top:3px}.event-copy pre{max-height:260px;margin:8px 0 0;padding:8px;overflow:auto;border:1px solid var(--line);border-radius:6px;color:var(--text-muted);background:#0b1012;font-size:9px;line-height:1.5;white-space:pre-wrap}.worker-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(350px,1fr));gap:11px}.worker-card{padding:17px;border:1px solid var(--line);border-radius:var(--radius);background:linear-gradient(145deg,var(--surface-2),var(--surface-1))}.worker-card.offline{border-color:#ec8f8533}.worker-card>header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.worker-identity{display:flex;min-width:0;align-items:center;gap:10px}.worker-glyph{display:grid;width:39px;height:39px;flex:none;place-items:center;border:1px solid var(--line);border-radius:10px;color:var(--accent);background:var(--accent-wash)}.worker-card.offline .worker-glyph{color:var(--red);background:var(--red-wash)}.worker-card h2{margin:0;font-size:13px}.worker-card header p{overflow:hidden;max-width:240px;margin:4px 0 0;color:var(--text-muted);font:9px var(--mono);text-overflow:ellipsis;white-space:nowrap}.health-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 7px;border:1px solid var(--accent-line);border-radius:999px;color:var(--accent-strong);background:var(--accent-wash);font-size:9px;font-weight:700;text-transform:uppercase}.health-badge i{background:var(--accent)}.health-badge.unhealthy{border-color:#ec8f8540;color:var(--red);background:var(--red-wash)}.health-badge.unhealthy i{background:var(--red)}.worker-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));margin:15px 0 0;overflow:hidden;border:1px solid var(--line);border-radius:8px;background:#090e103d}.worker-stats>div{min-width:0;padding:9px;border-right:1px solid var(--line)}.worker-stats>div:last-child{border:0}.worker-stats dd{overflow:hidden;margin:0;color:var(--text-soft);font-size:10px;text-overflow:ellipsis;text-transform:capitalize;white-space:nowrap}.worker-stats dd.health-reachable{color:var(--accent)}.worker-stats dd.health-unreachable{color:var(--red)}.worker-current,.worker-projects{display:grid;gap:7px;margin-top:14px}.worker-current>a{display:flex;align-items:center;justify-content:space-between;gap:9px;padding:9px;border:1px solid var(--line);border-radius:8px;color:var(--text-muted);background:#090e103d;text-decoration:none}.worker-current>a:hover{border-color:var(--accent-line);color:var(--accent)}.worker-current>a>span{display:grid;min-width:0;gap:3px}.worker-current a b{overflow:hidden;color:var(--text-soft);font:10px var(--mono);text-overflow:ellipsis;white-space:nowrap}.worker-current a small{color:var(--text-muted);font:9px var(--mono)}.worker-current>p{margin:0;padding:9px;border:1px dashed var(--line);border-radius:8px;color:var(--text-muted);font-size:10px}.worker-projects>div{margin:0}.worker-error{display:flex;align-items:flex-start;gap:8px;margin-top:13px;padding:9px;border:1px solid rgba(236,143,133,.2);border-radius:8px;color:var(--red);background:var(--red-wash)}.worker-error>span{display:grid;gap:3px}.worker-error b,.worker-error small{font-size:9px;font-weight:500}.worker-error small{color:var(--text-muted)}.worker-card>footer{display:flex;justify-content:space-between;gap:10px;margin-top:14px;padding-top:11px;border-top:1px solid var(--line);color:var(--text-muted);font:9px var(--mono)}.settings-page{max-width:1160px}.settings-layout{display:grid;grid-template-columns:300px minmax(0,1fr);align-items:start;gap:16px}.profile-card,.settings-card{border:1px solid var(--line);border-radius:var(--radius-lg);background:linear-gradient(145deg,#151d21f0,#0e1417f0);box-shadow:0 18px 50px #00000024}.profile-card{padding:24px}.profile-avatar{display:grid;width:58px;height:58px;place-items:center;margin-bottom:16px;border:1px solid var(--accent-line);border-radius:17px;color:var(--accent-strong);background:linear-gradient(145deg,rgba(86,200,139,.2),var(--accent-wash));box-shadow:inset 0 1px #ffffff0d,0 12px 30px #0000002e;font:600 16px var(--mono)}.profile-card h2{margin:0;font-size:20px;letter-spacing:-.035em;overflow-wrap:anywhere}.profile-card>p{margin:5px 0 22px;color:var(--text-muted);font-size:11px}.profile-card dl{display:grid;gap:12px;margin:0;padding:17px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.profile-card dl>div{display:grid;gap:4px}.profile-card dt{color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.profile-card dd{margin:0;color:var(--text-soft);font:9px var(--mono)}.database-badge{display:flex;align-items:flex-start;gap:10px;margin-top:18px;padding:12px;border:1px solid var(--accent-line);border-radius:10px;color:var(--accent);background:var(--accent-wash)}.database-badge>span{display:grid;gap:3px}.database-badge b{color:var(--accent-strong);font-size:10px}.database-badge small{color:var(--text-soft);font-size:9px;line-height:1.5}.settings-card{overflow:hidden}.settings-card>header{display:flex;align-items:flex-start;gap:13px;padding:22px 24px;border-bottom:1px solid var(--line);background:#11171b8c}.settings-icon{display:grid;width:38px;height:38px;flex:none;place-items:center;border:1px solid var(--accent-line);border-radius:11px;color:var(--accent);background:var(--accent-wash)}.settings-card h2{margin:1px 0 0;font-size:16px;letter-spacing:-.025em}.settings-card header p{margin:5px 0 0;color:var(--text-muted);font-size:10px}.settings-card form{display:grid;gap:9px;padding:24px}.settings-card label,.current-password-block>label{display:grid;gap:7px;color:var(--text-soft);font-size:10px;font-weight:650}.settings-field{display:grid;gap:7px}.settings-divider{display:flex;align-items:center;gap:12px;margin:14px 0 2px;color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase}.settings-divider:after{height:1px;flex:1;background:var(--line);content:""}.password-rules{display:flex;flex-wrap:wrap;gap:7px;margin-top:3px}.password-rules span{display:inline-flex;align-items:center;gap:4px;padding:5px 7px;border:1px solid var(--line);border-radius:999px;color:var(--text-muted);background:var(--surface-1);font-size:9px}.password-rules span.met{border-color:var(--accent-line);color:var(--accent);background:var(--accent-wash)}.current-password-block{display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,.65fr);align-items:end;gap:5px 18px;margin-top:14px;padding:15px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface-1)}.current-password-block label{grid-column:1}.current-password-block p{grid-column:1;margin:0;color:var(--text-muted);font-size:9px}.current-password-block input{grid-column:2;grid-row:1 / span 2}.settings-actions{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:10px -24px -24px;padding:16px 24px;border-top:1px solid var(--line);background:#0a0f1147}.settings-actions>span{color:var(--text-muted);font-size:9px}.artifact-header{align-items:flex-end}.artifact-content{min-height:400px;padding:20px;font-size:11px;line-height:1.7}.login-page{position:relative;display:grid;min-height:100vh;place-items:center;overflow:hidden;padding:24px;background:radial-gradient(circle at 24% 10%,rgba(86,200,139,.17),transparent 34rem),radial-gradient(circle at 88% 92%,rgba(117,96,186,.09),transparent 30rem),#090d0f}.login-grid{position:absolute;top:0;right:0;bottom:0;left:0;opacity:.24;background-image:linear-gradient(rgba(169,180,177,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(169,180,177,.07) 1px,transparent 1px);background-size:46px 46px;-webkit-mask-image:radial-gradient(circle at center,black,transparent 68%);mask-image:radial-gradient(circle at center,black,transparent 68%)}.login-layout{position:relative;display:grid;width:min(1040px,100%);grid-template-columns:minmax(0,1fr) 420px;gap:14px}.login-showcase{position:relative;display:flex;min-height:610px;flex-direction:column;overflow:hidden;padding:36px 40px;border:1px solid var(--line);border-radius:24px;background:linear-gradient(145deg,#192624e6,#0c1215f0),var(--surface-1);box-shadow:0 30px 90px #00000047}.login-showcase:before{position:absolute;width:430px;height:430px;right:-190px;top:-170px;border:1px solid rgba(142,224,178,.08);border-radius:50%;box-shadow:0 0 0 55px #8ee0b206,0 0 0 110px #8ee0b205;content:""}.showcase-brand{display:flex;align-items:center;gap:12px}.showcase-brand>span:last-child{display:grid;gap:2px}.showcase-brand b{font-size:14px;letter-spacing:-.02em}.showcase-brand small{color:var(--text-muted);font:8px var(--mono);letter-spacing:.1em;text-transform:uppercase}.showcase-copy{position:relative;max-width:500px;margin:auto 0 42px}.showcase-copy h2{margin:0;font-size:clamp(38px,4.2vw,58px);font-weight:620;line-height:.98;letter-spacing:-.064em}.showcase-copy p{max-width:460px;margin:20px 0 0;color:var(--text-soft);font-size:12px;line-height:1.7}.showcase-flow{display:grid;grid-template-columns:auto minmax(16px,1fr) auto minmax(16px,1fr) auto minmax(16px,1fr) auto;align-items:center;gap:8px;margin-bottom:36px;padding:14px;border:1px solid var(--line);border-radius:12px;background:#080d0f57}.showcase-flow>span{display:inline-flex;align-items:center;gap:6px;color:var(--text-soft);font:8px var(--mono);letter-spacing:.05em;text-transform:uppercase}.showcase-flow>b{height:1px;background:linear-gradient(90deg,var(--line-strong),var(--accent-line))}.flow-dot{display:block;width:6px;height:6px;border-radius:50%;background:var(--blue);box-shadow:0 0 0 3px var(--blue-wash)}.flow-dot.active,.flow-dot.done{background:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}.flow-dot.review{background:var(--violet);box-shadow:0 0 0 3px var(--violet-wash)}.login-showcase>footer{display:flex;align-items:center;gap:9px;padding-top:20px;border-top:1px solid var(--line);color:var(--text-muted);font:8px var(--mono);letter-spacing:.09em;text-transform:uppercase}.login-showcase>footer .signal{margin:0}.login-card{position:relative;width:100%;align-self:center;padding:30px;border:1px solid var(--line-strong);border-radius:22px;background:#11181cf2;box-shadow:0 30px 90px #00000080}.login-brand{display:flex;align-items:center;gap:11px;margin-bottom:38px}.login-heading h1{margin:0;font-size:36px;letter-spacing:-.055em}.login-heading>p{margin:8px 0 0;color:var(--text-soft);font-size:12px;line-height:1.55}.login-card form{display:grid;gap:9px;margin-top:24px}.login-card form>label{color:var(--text-soft);font-size:10px;font-weight:650}.password-field{position:relative}.password-field input{padding-right:62px}.password-field button{position:absolute;top:50%;right:5px;min-height:31px;padding:5px 8px;transform:translateY(-50%);border-color:transparent;color:var(--accent);background:transparent;font-size:10px}.password-field button:hover:not(:disabled){border-color:transparent;background:var(--accent-wash);transform:translateY(-50%)}.caps-lock{color:var(--amber);font-size:10px}.login-submit{min-height:42px;margin-top:7px}.login-security{display:flex;gap:9px;margin-top:23px;padding-top:16px;border-top:1px solid var(--line);color:var(--text-muted)}.login-security>span{display:grid;width:24px;height:24px;flex:none;place-items:center;border-radius:7px;color:var(--accent);background:var(--accent-wash)}.login-security p{margin:0;font-size:10px;line-height:1.55}.session-check{position:relative;display:flex;align-items:center;gap:11px;padding:15px 18px;border:1px solid var(--line-strong);border-radius:var(--radius);color:var(--text-soft);background:var(--surface-1);box-shadow:var(--shadow);font-size:11px}.spinner{display:inline-block;width:13px;height:13px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width:1180px){.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.diagnosis{grid-template-columns:1fr}.task-board.lanes-3{grid-template-columns:repeat(3,minmax(235px,1fr))}}@media(max-width:980px){.app-shell{padding-left:72px}.sidebar{width:72px;padding-right:10px;padding-left:10px}.brand{justify-content:center;margin-right:-10px;margin-left:-10px;padding:0}.brand>span:last-child,.nav-heading,.primary-nav a>span,.nav-count,.sidebar-status>span:last-child,.sidebar-foot{display:none}.primary-nav a{display:grid;width:50px;grid-template-columns:1fr;place-items:center;padding:0}.primary-nav a.active:before{left:-11px}.sidebar-status{justify-content:center;padding:11px}.topbar{left:72px}.detail-layout{grid-template-columns:minmax(0,1fr) 300px}.login-layout{grid-template-columns:minmax(0,1fr) 390px}.login-showcase{padding:30px}}@media(max-width:800px){.page{padding-right:20px;padding-left:20px}.detail-layout{grid-template-columns:1fr}.detail-side{grid-template-columns:1fr 1fr;align-items:start}.timeline-panel{grid-column:1 / -1}.board-toolbar{flex-wrap:wrap}.segmented{width:100%;margin-right:0}.segmented button{flex:1}.search-field{width:calc(100% - 164px)}.settings-layout{grid-template-columns:1fr}.profile-card{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:16px}.profile-avatar{grid-row:span 2;margin:0}.profile-card>p{margin:3px 0 16px}.profile-card dl,.database-badge{grid-column:1 / -1}.login-layout{width:min(430px,100%);grid-template-columns:1fr}.login-showcase{display:none}}@media(max-width:680px){.app-shell{padding:0 0 64px}.sidebar{inset:auto 0 0;width:100%;height:64px;padding:6px 10px;border-top:1px solid var(--line-strong);border-right:0}.brand,.sidebar-status{display:none}.primary-nav{display:flex;height:100%;justify-content:space-around;gap:8px;margin:0}.primary-nav a{width:auto;flex:1;grid-template-columns:auto auto;place-content:center;gap:7px}.primary-nav a>span{display:inline}.primary-nav a.active:before{inset:-7px auto auto 50%;width:26px;height:3px;transform:translate(-50%);border-radius:0 0 4px 4px}.topbar{left:0;height:56px;padding:0 15px}.content{padding-top:56px}.topbar-context>span,.topbar-context .icon,.command-trigger span,.command-trigger kbd,.account-label{display:none}.command-trigger{width:34px;margin-left:auto;padding:0}.account-trigger{padding:3px}.page{padding:27px 15px 45px}.page-header,.task-header{align-items:flex-start;flex-direction:column;gap:18px}.page-header h1{font-size:34px}.page-actions{width:100%}.page-actions>button:last-child{flex:1}.metrics{grid-template-columns:1fr 1fr}.metric{grid-template-columns:1fr}.metric-icon{display:none}.attention-banner{grid-template-columns:auto minmax(0,1fr)}.attention-banner>a{grid-column:2}.task-board.lanes-2,.task-board.lanes-3,.task-board.lanes-5,.task-board.lanes-7{grid-template-columns:repeat(var(--mobile-lanes, 5),minmax(255px,82vw))}.task-board.lanes-2{--mobile-lanes: 2}.task-board.lanes-3{--mobile-lanes: 3}.task-board.lanes-5{--mobile-lanes: 5}.task-board.lanes-7{--mobile-lanes: 7}.task-lane{min-height:270px}.form-row,.detail-side{grid-template-columns:1fr}.timeline-panel{grid-column:auto}.diagnosis dl{grid-template-columns:1fr 1fr}.diagnosis dl>div:nth-child(2){border-right:0}.diagnosis dl>div:nth-child(-n+2){border-bottom:1px solid var(--line)}.approval-panel{padding:14px}.approval-actions{align-items:stretch;flex-direction:column}.action-card,.action-form{grid-template-columns:auto minmax(0,1fr)}.action-card>button,.action-buttons{grid-column:2;justify-self:start}.worker-grid{grid-template-columns:1fr}.worker-card>header{align-items:flex-start}.overlay{padding:10px}.modal{max-height:calc(100vh - 20px)}.modal-header,.create-modal form{padding-right:16px;padding-left:16px}.eligibility{margin-right:16px;margin-left:16px}.artifact-header .task-id{overflow-wrap:anywhere}.login-card{padding:22px}.current-password-block{grid-template-columns:1fr}.current-password-block input{grid-column:1;grid-row:auto;margin-top:5px}.settings-actions{align-items:stretch;flex-direction:column}.settings-actions button{width:100%}.skeleton-grid{grid-template-columns:1fr}}@media(max-width:460px){.refresh-button span{display:none}.search-field,.select-field{width:100%}.metrics{grid-template-columns:1fr 1fr;gap:7px}.metric{padding:11px}.worker-stats{grid-template-columns:1fr}.worker-stats>div{border-right:0;border-bottom:1px solid var(--line)}.worker-stats>div:last-child{border-bottom:0}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}} diff --git a/internal/webui/assets/assets/index-BAiY1a7C.js b/internal/webui/assets/assets/index-BAiY1a7C.js new file mode 100644 index 0000000..c5f2827 --- /dev/null +++ b/internal/webui/assets/assets/index-BAiY1a7C.js @@ -0,0 +1,61 @@ +var _p=l=>{throw TypeError(l)};var No=(l,i,o)=>i.has(l)||_p("Cannot "+o);var b=(l,i,o)=>(No(l,i,"read from private field"),o?o.call(l):i.get(l)),ae=(l,i,o)=>i.has(l)?_p("Cannot add the same private member more than once"):i instanceof WeakSet?i.add(l):i.set(l,o),$=(l,i,o,c)=>(No(l,i,"write to private field"),c?c.call(l,o):i.set(l,o),o),me=(l,i,o)=>(No(l,i,"access private method"),o);var qu=(l,i,o,c)=>({set _(d){$(l,i,d,o)},get _(){return b(l,i,c)}});(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))c(d);new MutationObserver(d=>{for(const h of d)if(h.type==="childList")for(const v of h.addedNodes)v.tagName==="LINK"&&v.rel==="modulepreload"&&c(v)}).observe(document,{childList:!0,subtree:!0});function o(d){const h={};return d.integrity&&(h.integrity=d.integrity),d.referrerPolicy&&(h.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?h.credentials="include":d.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function c(d){if(d.ep)return;d.ep=!0;const h=o(d);fetch(d.href,h)}})();function A0(l){return l&&l.__esModule&&Object.prototype.hasOwnProperty.call(l,"default")?l.default:l}var Eo={exports:{}},ai={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var wp;function R0(){if(wp)return ai;wp=1;var l=Symbol.for("react.transitional.element"),i=Symbol.for("react.fragment");function o(c,d,h){var v=null;if(h!==void 0&&(v=""+h),d.key!==void 0&&(v=""+d.key),"key"in d){h={};for(var g in d)g!=="key"&&(h[g]=d[g])}else h=d;return d=h.ref,{$$typeof:l,type:c,key:v,ref:d!==void 0?d:null,props:h}}return ai.Fragment=i,ai.jsx=o,ai.jsxs=o,ai}var Cp;function M0(){return Cp||(Cp=1,Eo.exports=R0()),Eo.exports}var s=M0(),To={exports:{}},re={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Op;function z0(){if(Op)return re;Op=1;var l=Symbol.for("react.transitional.element"),i=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),c=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),v=Symbol.for("react.context"),g=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),E=Symbol.for("react.lazy"),x=Symbol.for("react.activity"),w=Symbol.iterator;function O(T){return T===null||typeof T!="object"?null:(T=w&&T[w]||T["@@iterator"],typeof T=="function"?T:null)}var U={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},z=Object.assign,q={};function H(T,Y,F){this.props=T,this.context=Y,this.refs=q,this.updater=F||U}H.prototype.isReactComponent={},H.prototype.setState=function(T,Y){if(typeof T!="object"&&typeof T!="function"&&T!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,T,Y,"setState")},H.prototype.forceUpdate=function(T){this.updater.enqueueForceUpdate(this,T,"forceUpdate")};function G(){}G.prototype=H.prototype;function K(T,Y,F){this.props=T,this.context=Y,this.refs=q,this.updater=F||U}var Z=K.prototype=new G;Z.constructor=K,z(Z,H.prototype),Z.isPureReactComponent=!0;var W=Array.isArray;function J(){}var te={H:null,A:null,T:null,S:null},ne=Object.prototype.hasOwnProperty;function ce(T,Y,F){var I=F.ref;return{$$typeof:l,type:T,key:Y,ref:I!==void 0?I:null,props:F}}function _e(T,Y){return ce(T.type,Y,T.props)}function He(T){return typeof T=="object"&&T!==null&&T.$$typeof===l}function X(T){var Y={"=":"=0",":":"=2"};return"$"+T.replace(/[=:]/g,function(F){return Y[F]})}var de=/\/+/g;function pe(T,Y){return typeof T=="object"&&T!==null&&T.key!=null?X(""+T.key):Y.toString(36)}function se(T){switch(T.status){case"fulfilled":return T.value;case"rejected":throw T.reason;default:switch(typeof T.status=="string"?T.then(J,J):(T.status="pending",T.then(function(Y){T.status==="pending"&&(T.status="fulfilled",T.value=Y)},function(Y){T.status==="pending"&&(T.status="rejected",T.reason=Y)})),T.status){case"fulfilled":return T.value;case"rejected":throw T.reason}}throw T}function D(T,Y,F,I,oe){var ve=typeof T;(ve==="undefined"||ve==="boolean")&&(T=null);var Ce=!1;if(T===null)Ce=!0;else switch(ve){case"bigint":case"string":case"number":Ce=!0;break;case"object":switch(T.$$typeof){case l:case i:Ce=!0;break;case E:return Ce=T._init,D(Ce(T._payload),Y,F,I,oe)}}if(Ce)return oe=oe(T),Ce=I===""?"."+pe(T,0):I,W(oe)?(F="",Ce!=null&&(F=Ce.replace(de,"$&/")+"/"),D(oe,Y,F,"",function(cs){return cs})):oe!=null&&(He(oe)&&(oe=_e(oe,F+(oe.key==null||T&&T.key===oe.key?"":(""+oe.key).replace(de,"$&/")+"/")+Ce)),Y.push(oe)),1;Ce=0;var pt=I===""?".":I+":";if(W(T))for(var Je=0;Je>>1,ke=D[Me];if(0>>1;Med(F,ue))Id(oe,F)?(D[Me]=oe,D[I]=ue,Me=I):(D[Me]=F,D[Y]=ue,Me=Y);else if(Id(oe,ue))D[Me]=oe,D[I]=ue,Me=I;else break e}}return V}function d(D,V){var ue=D.sortIndex-V.sortIndex;return ue!==0?ue:D.id-V.id}if(l.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;l.unstable_now=function(){return h.now()}}else{var v=Date,g=v.now();l.unstable_now=function(){return v.now()-g}}var p=[],y=[],E=1,x=null,w=3,O=!1,U=!1,z=!1,q=!1,H=typeof setTimeout=="function"?setTimeout:null,G=typeof clearTimeout=="function"?clearTimeout:null,K=typeof setImmediate<"u"?setImmediate:null;function Z(D){for(var V=o(y);V!==null;){if(V.callback===null)c(y);else if(V.startTime<=D)c(y),V.sortIndex=V.expirationTime,i(p,V);else break;V=o(y)}}function W(D){if(z=!1,Z(D),!U)if(o(p)!==null)U=!0,J||(J=!0,X());else{var V=o(y);V!==null&&se(W,V.startTime-D)}}var J=!1,te=-1,ne=5,ce=-1;function _e(){return q?!0:!(l.unstable_now()-ceD&&_e());){var Me=x.callback;if(typeof Me=="function"){x.callback=null,w=x.priorityLevel;var ke=Me(x.expirationTime<=D);if(D=l.unstable_now(),typeof ke=="function"){x.callback=ke,Z(D),V=!0;break t}x===o(p)&&c(p),Z(D)}else c(p);x=o(p)}if(x!==null)V=!0;else{var T=o(y);T!==null&&se(W,T.startTime-D),V=!1}}break e}finally{x=null,w=ue,O=!1}V=void 0}}finally{V?X():J=!1}}}var X;if(typeof K=="function")X=function(){K(He)};else if(typeof MessageChannel<"u"){var de=new MessageChannel,pe=de.port2;de.port1.onmessage=He,X=function(){pe.postMessage(null)}}else X=function(){H(He,0)};function se(D,V){te=H(function(){D(l.unstable_now())},V)}l.unstable_IdlePriority=5,l.unstable_ImmediatePriority=1,l.unstable_LowPriority=4,l.unstable_NormalPriority=3,l.unstable_Profiling=null,l.unstable_UserBlockingPriority=2,l.unstable_cancelCallback=function(D){D.callback=null},l.unstable_forceFrameRate=function(D){0>D||125Me?(D.sortIndex=ue,i(y,D),o(p)===null&&D===o(y)&&(z?(G(te),te=-1):z=!0,se(W,ue-Me))):(D.sortIndex=ke,i(p,D),U||O||(U=!0,J||(J=!0,X()))),D},l.unstable_shouldYield=_e,l.unstable_wrapCallback=function(D){var V=w;return function(){var ue=w;w=V;try{return D.apply(this,arguments)}finally{w=ue}}}})(Co)),Co}var Mp;function k0(){return Mp||(Mp=1,wo.exports=U0()),wo.exports}var Oo={exports:{}},ht={};/** + * @license React + * react-dom.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var zp;function q0(){if(zp)return ht;zp=1;var l=lf();function i(p){var y="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(l)}catch(i){console.error(i)}}return l(),Oo.exports=q0(),Oo.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Up;function B0(){if(Up)return ni;Up=1;var l=k0(),i=lf(),o=H0();function c(e){var t="https://react.dev/errors/"+e;if(1ke||(e.current=Me[ke],Me[ke]=null,ke--)}function F(e,t){ke++,Me[ke]=e.current,e.current=t}var I=T(null),oe=T(null),ve=T(null),Ce=T(null);function pt(e,t){switch(F(ve,t),F(oe,e),F(I,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?$m(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=$m(t),e=Fm(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}Y(I),F(I,e)}function Je(){Y(I),Y(oe),Y(ve)}function cs(e){e.memoizedState!==null&&F(Ce,e);var t=I.current,a=Fm(t,e.type);t!==a&&(F(oe,e),F(I,a))}function xi(e){oe.current===e&&(Y(I),Y(oe)),Ce.current===e&&(Y(Ce),Ps._currentValue=ue)}var lr,Tf;function Cn(e){if(lr===void 0)try{throw Error()}catch(a){var t=a.stack.trim().match(/\n( *(at )?)/);lr=t&&t[1]||"",Tf=-1)":-1u||j[n]!==A[u]){var k=` +`+j[n].replace(" at new "," at ");return e.displayName&&k.includes("")&&(k=k.replace("",e.displayName)),k}while(1<=n&&0<=u);break}}}finally{sr=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?Cn(a):""}function uy(e,t){switch(e.tag){case 26:case 27:case 5:return Cn(e.type);case 16:return Cn("Lazy");case 13:return e.child!==t&&t!==null?Cn("Suspense Fallback"):Cn("Suspense");case 19:return Cn("SuspenseList");case 0:case 15:return ir(e.type,!1);case 11:return ir(e.type.render,!1);case 1:return ir(e.type,!0);case 31:return Cn("Activity");default:return""}}function _f(e){try{var t="",a=null;do t+=uy(e,a),a=e,e=e.return;while(e);return t}catch(n){return` +Error generating stack: `+n.message+` +`+n.stack}}var ur=Object.prototype.hasOwnProperty,rr=l.unstable_scheduleCallback,cr=l.unstable_cancelCallback,ry=l.unstable_shouldYield,cy=l.unstable_requestPaint,Ot=l.unstable_now,oy=l.unstable_getCurrentPriorityLevel,wf=l.unstable_ImmediatePriority,Cf=l.unstable_UserBlockingPriority,ji=l.unstable_NormalPriority,fy=l.unstable_LowPriority,Of=l.unstable_IdlePriority,dy=l.log,hy=l.unstable_setDisableYieldValue,os=null,At=null;function Xa(e){if(typeof dy=="function"&&hy(e),At&&typeof At.setStrictMode=="function")try{At.setStrictMode(os,e)}catch{}}var Rt=Math.clz32?Math.clz32:vy,my=Math.log,py=Math.LN2;function vy(e){return e>>>=0,e===0?32:31-(my(e)/py|0)|0}var bi=256,Si=262144,Ni=4194304;function On(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ei(e,t,a){var n=e.pendingLanes;if(n===0)return 0;var u=0,r=e.suspendedLanes,f=e.pingedLanes;e=e.warmLanes;var m=n&134217727;return m!==0?(n=m&~r,n!==0?u=On(n):(f&=m,f!==0?u=On(f):a||(a=m&~e,a!==0&&(u=On(a))))):(m=n&~r,m!==0?u=On(m):f!==0?u=On(f):a||(a=n&~e,a!==0&&(u=On(a)))),u===0?0:t!==0&&t!==u&&(t&r)===0&&(r=u&-u,a=t&-t,r>=a||r===32&&(a&4194048)!==0)?t:u}function fs(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function yy(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Af(){var e=Ni;return Ni<<=1,(Ni&62914560)===0&&(Ni=4194304),e}function or(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function ds(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function gy(e,t,a,n,u,r){var f=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var m=e.entanglements,j=e.expirationTimes,A=e.hiddenUpdates;for(a=f&~a;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var Ey=/[\n"\\]/g;function Gt(e){return e.replace(Ey,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function vr(e,t,a,n,u,r,f,m){e.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?e.type=f:e.removeAttribute("type"),t!=null?f==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Yt(t)):e.value!==""+Yt(t)&&(e.value=""+Yt(t)):f!=="submit"&&f!=="reset"||e.removeAttribute("value"),t!=null?yr(e,f,Yt(t)):a!=null?yr(e,f,Yt(a)):n!=null&&e.removeAttribute("value"),u==null&&r!=null&&(e.defaultChecked=!!r),u!=null&&(e.checked=u&&typeof u!="function"&&typeof u!="symbol"),m!=null&&typeof m!="function"&&typeof m!="symbol"&&typeof m!="boolean"?e.name=""+Yt(m):e.removeAttribute("name")}function Gf(e,t,a,n,u,r,f,m){if(r!=null&&typeof r!="function"&&typeof r!="symbol"&&typeof r!="boolean"&&(e.type=r),t!=null||a!=null){if(!(r!=="submit"&&r!=="reset"||t!=null)){pr(e);return}a=a!=null?""+Yt(a):"",t=t!=null?""+Yt(t):a,m||t===e.value||(e.value=t),e.defaultValue=t}n=n??u,n=typeof n!="function"&&typeof n!="symbol"&&!!n,e.checked=m?e.checked:!!n,e.defaultChecked=!!n,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(e.name=f),pr(e)}function yr(e,t,a){t==="number"&&wi(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function ol(e,t,a,n){if(e=e.options,t){t={};for(var u=0;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Sr=!1;if(ja)try{var vs={};Object.defineProperty(vs,"passive",{get:function(){Sr=!0}}),window.addEventListener("test",vs,vs),window.removeEventListener("test",vs,vs)}catch{Sr=!1}var Za=null,Nr=null,Oi=null;function Ff(){if(Oi)return Oi;var e,t=Nr,a=t.length,n,u="value"in Za?Za.value:Za.textContent,r=u.length;for(e=0;e=xs),ad=" ",nd=!1;function ld(e,t){switch(e){case"keyup":return Py.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function sd(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ml=!1;function eg(e,t){switch(e){case"compositionend":return sd(t);case"keypress":return t.which!==32?null:(nd=!0,ad);case"textInput":return e=t.data,e===ad&&nd?null:e;default:return null}}function tg(e,t){if(ml)return e==="compositionend"||!Cr&&ld(e,t)?(e=Ff(),Oi=Nr=Za=null,ml=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:a,offset:t-e};e=n}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=hd(a)}}function pd(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?pd(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function vd(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=wi(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=wi(e.document)}return t}function Rr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var cg=ja&&"documentMode"in document&&11>=document.documentMode,pl=null,Mr=null,Ns=null,zr=!1;function yd(e,t,a){var n=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;zr||pl==null||pl!==wi(n)||(n=pl,"selectionStart"in n&&Rr(n)?n={start:n.selectionStart,end:n.selectionEnd}:(n=(n.ownerDocument&&n.ownerDocument.defaultView||window).getSelection(),n={anchorNode:n.anchorNode,anchorOffset:n.anchorOffset,focusNode:n.focusNode,focusOffset:n.focusOffset}),Ns&&Ss(Ns,n)||(Ns=n,n=Su(Mr,"onSelect"),0>=f,u-=f,oa=1<<32-Rt(t)+u|a<he?(je=ee,ee=null):je=ee.sibling;var Te=R(_,ee,C[he],L);if(Te===null){ee===null&&(ee=je);break}e&&ee&&Te.alternate===null&&t(_,ee),N=r(Te,N,he),Ee===null?le=Te:Ee.sibling=Te,Ee=Te,ee=je}if(he===C.length)return a(_,ee),be&&Sa(_,he),le;if(ee===null){for(;hehe?(je=ee,ee=null):je=ee.sibling;var pn=R(_,ee,Te.value,L);if(pn===null){ee===null&&(ee=je);break}e&&ee&&pn.alternate===null&&t(_,ee),N=r(pn,N,he),Ee===null?le=pn:Ee.sibling=pn,Ee=pn,ee=je}if(Te.done)return a(_,ee),be&&Sa(_,he),le;if(ee===null){for(;!Te.done;he++,Te=C.next())Te=Q(_,Te.value,L),Te!==null&&(N=r(Te,N,he),Ee===null?le=Te:Ee.sibling=Te,Ee=Te);return be&&Sa(_,he),le}for(ee=n(ee);!Te.done;he++,Te=C.next())Te=M(ee,_,he,Te.value,L),Te!==null&&(e&&Te.alternate!==null&&ee.delete(Te.key===null?he:Te.key),N=r(Te,N,he),Ee===null?le=Te:Ee.sibling=Te,Ee=Te);return e&&ee.forEach(function(O0){return t(_,O0)}),be&&Sa(_,he),le}function Ue(_,N,C,L){if(typeof C=="object"&&C!==null&&C.type===z&&C.key===null&&(C=C.props.children),typeof C=="object"&&C!==null){switch(C.$$typeof){case O:e:{for(var le=C.key;N!==null;){if(N.key===le){if(le=C.type,le===z){if(N.tag===7){a(_,N.sibling),L=u(N,C.props.children),L.return=_,_=L;break e}}else if(N.elementType===le||typeof le=="object"&&le!==null&&le.$$typeof===ne&&Ln(le)===N.type){a(_,N.sibling),L=u(N,C.props),Os(L,C),L.return=_,_=L;break e}a(_,N);break}else t(_,N);N=N.sibling}C.type===z?(L=Un(C.props.children,_.mode,L,C.key),L.return=_,_=L):(L=Bi(C.type,C.key,C.props,null,_.mode,L),Os(L,C),L.return=_,_=L)}return f(_);case U:e:{for(le=C.key;N!==null;){if(N.key===le)if(N.tag===4&&N.stateNode.containerInfo===C.containerInfo&&N.stateNode.implementation===C.implementation){a(_,N.sibling),L=u(N,C.children||[]),L.return=_,_=L;break e}else{a(_,N);break}else t(_,N);N=N.sibling}L=Lr(C,_.mode,L),L.return=_,_=L}return f(_);case ne:return C=Ln(C),Ue(_,N,C,L)}if(se(C))return P(_,N,C,L);if(X(C)){if(le=X(C),typeof le!="function")throw Error(c(150));return C=le.call(C),ie(_,N,C,L)}if(typeof C.then=="function")return Ue(_,N,Zi(C),L);if(C.$$typeof===K)return Ue(_,N,Yi(_,C),L);Vi(_,C)}return typeof C=="string"&&C!==""||typeof C=="number"||typeof C=="bigint"?(C=""+C,N!==null&&N.tag===6?(a(_,N.sibling),L=u(N,C),L.return=_,_=L):(a(_,N),L=Br(C,_.mode,L),L.return=_,_=L),f(_)):a(_,N)}return function(_,N,C,L){try{Cs=0;var le=Ue(_,N,C,L);return _l=null,le}catch(ee){if(ee===Tl||ee===Xi)throw ee;var Ee=zt(29,ee,null,_.mode);return Ee.lanes=L,Ee.return=_,Ee}finally{}}}var Yn=Ld(!0),Qd=Ld(!1),Wa=!1;function Pr(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ir(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Pa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ia(e,t,a){var n=e.updateQueue;if(n===null)return null;if(n=n.shared,(we&2)!==0){var u=n.pending;return u===null?t.next=t:(t.next=u.next,u.next=t),n.pending=t,t=Hi(e),Ed(e,null,a),t}return qi(e,n,t,a),Hi(e)}function As(e,t,a){if(t=t.updateQueue,t!==null&&(t=t.shared,(a&4194048)!==0)){var n=t.lanes;n&=e.pendingLanes,a|=n,t.lanes=a,Mf(e,a)}}function ec(e,t){var a=e.updateQueue,n=e.alternate;if(n!==null&&(n=n.updateQueue,a===n)){var u=null,r=null;if(a=a.firstBaseUpdate,a!==null){do{var f={lane:a.lane,tag:a.tag,payload:a.payload,callback:null,next:null};r===null?u=r=f:r=r.next=f,a=a.next}while(a!==null);r===null?u=r=t:r=r.next=t}else u=r=t;a={baseState:n.baseState,firstBaseUpdate:u,lastBaseUpdate:r,shared:n.shared,callbacks:n.callbacks},e.updateQueue=a;return}e=a.lastBaseUpdate,e===null?a.firstBaseUpdate=t:e.next=t,a.lastBaseUpdate=t}var tc=!1;function Rs(){if(tc){var e=El;if(e!==null)throw e}}function Ms(e,t,a,n){tc=!1;var u=e.updateQueue;Wa=!1;var r=u.firstBaseUpdate,f=u.lastBaseUpdate,m=u.shared.pending;if(m!==null){u.shared.pending=null;var j=m,A=j.next;j.next=null,f===null?r=A:f.next=A,f=j;var k=e.alternate;k!==null&&(k=k.updateQueue,m=k.lastBaseUpdate,m!==f&&(m===null?k.firstBaseUpdate=A:m.next=A,k.lastBaseUpdate=j))}if(r!==null){var Q=u.baseState;f=0,k=A=j=null,m=r;do{var R=m.lane&-536870913,M=R!==m.lane;if(M?(xe&R)===R:(n&R)===R){R!==0&&R===Nl&&(tc=!0),k!==null&&(k=k.next={lane:0,tag:m.tag,payload:m.payload,callback:null,next:null});e:{var P=e,ie=m;R=t;var Ue=a;switch(ie.tag){case 1:if(P=ie.payload,typeof P=="function"){Q=P.call(Ue,Q,R);break e}Q=P;break e;case 3:P.flags=P.flags&-65537|128;case 0:if(P=ie.payload,R=typeof P=="function"?P.call(Ue,Q,R):P,R==null)break e;Q=x({},Q,R);break e;case 2:Wa=!0}}R=m.callback,R!==null&&(e.flags|=64,M&&(e.flags|=8192),M=u.callbacks,M===null?u.callbacks=[R]:M.push(R))}else M={lane:R,tag:m.tag,payload:m.payload,callback:m.callback,next:null},k===null?(A=k=M,j=Q):k=k.next=M,f|=R;if(m=m.next,m===null){if(m=u.shared.pending,m===null)break;M=m,m=M.next,M.next=null,u.lastBaseUpdate=M,u.shared.pending=null}}while(!0);k===null&&(j=Q),u.baseState=j,u.firstBaseUpdate=A,u.lastBaseUpdate=k,r===null&&(u.shared.lanes=0),ln|=f,e.lanes=f,e.memoizedState=Q}}function Yd(e,t){if(typeof e!="function")throw Error(c(191,e));e.call(t)}function Gd(e,t){var a=e.callbacks;if(a!==null)for(e.callbacks=null,e=0;er?r:8;var f=D.T,m={};D.T=m,jc(e,!1,t,a);try{var j=u(),A=D.S;if(A!==null&&A(m,j),j!==null&&typeof j=="object"&&typeof j.then=="function"){var k=gg(j,n);Us(e,t,k,Ht(e))}else Us(e,t,n,Ht(e))}catch(Q){Us(e,t,{then:function(){},status:"rejected",reason:Q},Ht())}finally{V.p=r,f!==null&&m.types!==null&&(f.types=m.types),D.T=f}}function Eg(){}function gc(e,t,a,n){if(e.tag!==5)throw Error(c(476));var u=bh(e).queue;jh(e,u,t,ue,a===null?Eg:function(){return Sh(e),a(n)})}function bh(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:ue,baseState:ue,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:_a,lastRenderedState:ue},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:_a,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Sh(e){var t=bh(e);t.next===null&&(t=e.alternate.memoizedState),Us(e,t.next.queue,{},Ht())}function xc(){return ot(Ps)}function Nh(){return Fe().memoizedState}function Eh(){return Fe().memoizedState}function Tg(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=Ht();e=Pa(a);var n=Ia(t,e,a);n!==null&&(_t(n,t,a),As(n,t,a)),t={cache:Jr()},e.payload=t;return}t=t.return}}function _g(e,t,a){var n=Ht();a={lane:n,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},nu(e)?_h(t,a):(a=qr(e,t,a,n),a!==null&&(_t(a,e,n),wh(a,t,n)))}function Th(e,t,a){var n=Ht();Us(e,t,a,n)}function Us(e,t,a,n){var u={lane:n,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null};if(nu(e))_h(t,u);else{var r=e.alternate;if(e.lanes===0&&(r===null||r.lanes===0)&&(r=t.lastRenderedReducer,r!==null))try{var f=t.lastRenderedState,m=r(f,a);if(u.hasEagerState=!0,u.eagerState=m,Mt(m,f))return qi(e,t,u,0),qe===null&&ki(),!1}catch{}finally{}if(a=qr(e,t,u,n),a!==null)return _t(a,e,n),wh(a,t,n),!0}return!1}function jc(e,t,a,n){if(n={lane:2,revertLane:Pc(),gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},nu(e)){if(t)throw Error(c(479))}else t=qr(e,a,n,2),t!==null&&_t(t,e,2)}function nu(e){var t=e.alternate;return e===fe||t!==null&&t===fe}function _h(e,t){Cl=Fi=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function wh(e,t,a){if((a&4194048)!==0){var n=t.lanes;n&=e.pendingLanes,a|=n,t.lanes=a,Mf(e,a)}}var ks={readContext:ot,use:Ii,useCallback:Ke,useContext:Ke,useEffect:Ke,useImperativeHandle:Ke,useLayoutEffect:Ke,useInsertionEffect:Ke,useMemo:Ke,useReducer:Ke,useRef:Ke,useState:Ke,useDebugValue:Ke,useDeferredValue:Ke,useTransition:Ke,useSyncExternalStore:Ke,useId:Ke,useHostTransitionStatus:Ke,useFormState:Ke,useActionState:Ke,useOptimistic:Ke,useMemoCache:Ke,useCacheRefresh:Ke};ks.useEffectEvent=Ke;var Ch={readContext:ot,use:Ii,useCallback:function(e,t){return vt().memoizedState=[e,t===void 0?null:t],e},useContext:ot,useEffect:fh,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,tu(4194308,4,ph.bind(null,t,e),a)},useLayoutEffect:function(e,t){return tu(4194308,4,e,t)},useInsertionEffect:function(e,t){tu(4,2,e,t)},useMemo:function(e,t){var a=vt();t=t===void 0?null:t;var n=e();if(Gn){Xa(!0);try{e()}finally{Xa(!1)}}return a.memoizedState=[n,t],n},useReducer:function(e,t,a){var n=vt();if(a!==void 0){var u=a(t);if(Gn){Xa(!0);try{a(t)}finally{Xa(!1)}}}else u=t;return n.memoizedState=n.baseState=u,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:u},n.queue=e,e=e.dispatch=_g.bind(null,fe,e),[n.memoizedState,e]},useRef:function(e){var t=vt();return e={current:e},t.memoizedState=e},useState:function(e){e=hc(e);var t=e.queue,a=Th.bind(null,fe,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:vc,useDeferredValue:function(e,t){var a=vt();return yc(a,e,t)},useTransition:function(){var e=hc(!1);return e=jh.bind(null,fe,e.queue,!0,!1),vt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var n=fe,u=vt();if(be){if(a===void 0)throw Error(c(407));a=a()}else{if(a=t(),qe===null)throw Error(c(349));(xe&127)!==0||$d(n,t,a)}u.memoizedState=a;var r={value:a,getSnapshot:t};return u.queue=r,fh(Wd.bind(null,n,r,e),[e]),n.flags|=2048,Al(9,{destroy:void 0},Fd.bind(null,n,r,a,t),null),a},useId:function(){var e=vt(),t=qe.identifierPrefix;if(be){var a=fa,n=oa;a=(n&~(1<<32-Rt(n)-1)).toString(32)+a,t="_"+t+"R_"+a,a=Wi++,0<\/script>",r=r.removeChild(r.firstChild);break;case"select":r=typeof n.is=="string"?f.createElement("select",{is:n.is}):f.createElement("select"),n.multiple?r.multiple=!0:n.size&&(r.size=n.size);break;default:r=typeof n.is=="string"?f.createElement(u,{is:n.is}):f.createElement(u)}}r[rt]=t,r[jt]=n;e:for(f=t.child;f!==null;){if(f.tag===5||f.tag===6)r.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===t)break e;for(;f.sibling===null;){if(f.return===null||f.return===t)break e;f=f.return}f.sibling.return=f.return,f=f.sibling}t.stateNode=r;e:switch(dt(r,u,n),u){case"button":case"input":case"select":case"textarea":n=!!n.autoFocus;break e;case"img":n=!0;break e;default:n=!1}n&&Ca(t)}}return Qe(t),Dc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,a),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==n&&Ca(t);else{if(typeof n!="string"&&t.stateNode===null)throw Error(c(166));if(e=ve.current,bl(t)){if(e=t.stateNode,a=t.memoizedProps,n=null,u=ct,u!==null)switch(u.tag){case 27:case 5:n=u.memoizedProps}e[rt]=t,e=!!(e.nodeValue===a||n!==null&&n.suppressHydrationWarning===!0||Vm(e.nodeValue,a)),e||$a(t,!0)}else e=Nu(e).createTextNode(n),e[rt]=t,t.stateNode=e}return Qe(t),null;case 31:if(a=t.memoizedState,e===null||e.memoizedState!==null){if(n=bl(t),a!==null){if(e===null){if(!n)throw Error(c(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(c(557));e[rt]=t}else kn(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Qe(t),e=!1}else a=Xr(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),e=!0;if(!e)return t.flags&256?(Ut(t),t):(Ut(t),null);if((t.flags&128)!==0)throw Error(c(558))}return Qe(t),null;case 13:if(n=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(u=bl(t),n!==null&&n.dehydrated!==null){if(e===null){if(!u)throw Error(c(318));if(u=t.memoizedState,u=u!==null?u.dehydrated:null,!u)throw Error(c(317));u[rt]=t}else kn(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Qe(t),u=!1}else u=Xr(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=u),u=!0;if(!u)return t.flags&256?(Ut(t),t):(Ut(t),null)}return Ut(t),(t.flags&128)!==0?(t.lanes=a,t):(a=n!==null,e=e!==null&&e.memoizedState!==null,a&&(n=t.child,u=null,n.alternate!==null&&n.alternate.memoizedState!==null&&n.alternate.memoizedState.cachePool!==null&&(u=n.alternate.memoizedState.cachePool.pool),r=null,n.memoizedState!==null&&n.memoizedState.cachePool!==null&&(r=n.memoizedState.cachePool.pool),r!==u&&(n.flags|=2048)),a!==e&&a&&(t.child.flags|=8192),ru(t,t.updateQueue),Qe(t),null);case 4:return Je(),e===null&&ao(t.stateNode.containerInfo),Qe(t),null;case 10:return Ea(t.type),Qe(t),null;case 19:if(Y($e),n=t.memoizedState,n===null)return Qe(t),null;if(u=(t.flags&128)!==0,r=n.rendering,r===null)if(u)Hs(n,!1);else{if(Ze!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(r=$i(e),r!==null){for(t.flags|=128,Hs(n,!1),e=r.updateQueue,t.updateQueue=e,ru(t,e),t.subtreeFlags=0,e=a,a=t.child;a!==null;)Td(a,e),a=a.sibling;return F($e,$e.current&1|2),be&&Sa(t,n.treeForkCount),t.child}e=e.sibling}n.tail!==null&&Ot()>hu&&(t.flags|=128,u=!0,Hs(n,!1),t.lanes=4194304)}else{if(!u)if(e=$i(r),e!==null){if(t.flags|=128,u=!0,e=e.updateQueue,t.updateQueue=e,ru(t,e),Hs(n,!0),n.tail===null&&n.tailMode==="hidden"&&!r.alternate&&!be)return Qe(t),null}else 2*Ot()-n.renderingStartTime>hu&&a!==536870912&&(t.flags|=128,u=!0,Hs(n,!1),t.lanes=4194304);n.isBackwards?(r.sibling=t.child,t.child=r):(e=n.last,e!==null?e.sibling=r:t.child=r,n.last=r)}return n.tail!==null?(e=n.tail,n.rendering=e,n.tail=e.sibling,n.renderingStartTime=Ot(),e.sibling=null,a=$e.current,F($e,u?a&1|2:a&1),be&&Sa(t,n.treeForkCount),e):(Qe(t),null);case 22:case 23:return Ut(t),nc(),n=t.memoizedState!==null,e!==null?e.memoizedState!==null!==n&&(t.flags|=8192):n&&(t.flags|=8192),n?(a&536870912)!==0&&(t.flags&128)===0&&(Qe(t),t.subtreeFlags&6&&(t.flags|=8192)):Qe(t),a=t.updateQueue,a!==null&&ru(t,a.retryQueue),a=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),n=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(n=t.memoizedState.cachePool.pool),n!==a&&(t.flags|=2048),e!==null&&Y(Bn),null;case 24:return a=null,e!==null&&(a=e.memoizedState.cache),t.memoizedState.cache!==a&&(t.flags|=2048),Ea(Pe),Qe(t),null;case 25:return null;case 30:return null}throw Error(c(156,t.tag))}function Rg(e,t){switch(Yr(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Ea(Pe),Je(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return xi(t),null;case 31:if(t.memoizedState!==null){if(Ut(t),t.alternate===null)throw Error(c(340));kn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(Ut(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(c(340));kn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Y($e),null;case 4:return Je(),null;case 10:return Ea(t.type),null;case 22:case 23:return Ut(t),nc(),e!==null&&Y(Bn),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Ea(Pe),null;case 25:return null;default:return null}}function Ph(e,t){switch(Yr(t),t.tag){case 3:Ea(Pe),Je();break;case 26:case 27:case 5:xi(t);break;case 4:Je();break;case 31:t.memoizedState!==null&&Ut(t);break;case 13:Ut(t);break;case 19:Y($e);break;case 10:Ea(t.type);break;case 22:case 23:Ut(t),nc(),e!==null&&Y(Bn);break;case 24:Ea(Pe)}}function Bs(e,t){try{var a=t.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&e)===e){n=void 0;var r=a.create,f=a.inst;n=r(),f.destroy=n}a=a.next}while(a!==u)}}catch(m){Re(t,t.return,m)}}function an(e,t,a){try{var n=t.updateQueue,u=n!==null?n.lastEffect:null;if(u!==null){var r=u.next;n=r;do{if((n.tag&e)===e){var f=n.inst,m=f.destroy;if(m!==void 0){f.destroy=void 0,u=t;var j=a,A=m;try{A()}catch(k){Re(u,j,k)}}}n=n.next}while(n!==r)}}catch(k){Re(t,t.return,k)}}function Ih(e){var t=e.updateQueue;if(t!==null){var a=e.stateNode;try{Gd(t,a)}catch(n){Re(e,e.return,n)}}}function em(e,t,a){a.props=Xn(e.type,e.memoizedProps),a.state=e.memoizedState;try{a.componentWillUnmount()}catch(n){Re(e,t,n)}}function Ls(e,t){try{var a=e.ref;if(a!==null){switch(e.tag){case 26:case 27:case 5:var n=e.stateNode;break;case 30:n=e.stateNode;break;default:n=e.stateNode}typeof a=="function"?e.refCleanup=a(n):a.current=n}}catch(u){Re(e,t,u)}}function da(e,t){var a=e.ref,n=e.refCleanup;if(a!==null)if(typeof n=="function")try{n()}catch(u){Re(e,t,u)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof a=="function")try{a(null)}catch(u){Re(e,t,u)}else a.current=null}function tm(e){var t=e.type,a=e.memoizedProps,n=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":a.autoFocus&&n.focus();break e;case"img":a.src?n.src=a.src:a.srcSet&&(n.srcset=a.srcSet)}}catch(u){Re(e,e.return,u)}}function Uc(e,t,a){try{var n=e.stateNode;Ig(n,e.type,a,t),n[jt]=t}catch(u){Re(e,e.return,u)}}function am(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&on(e.type)||e.tag===4}function kc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||am(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&on(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function qc(e,t,a){var n=e.tag;if(n===5||n===6)e=e.stateNode,t?(a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a).insertBefore(e,t):(t=a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a,t.appendChild(e),a=a._reactRootContainer,a!=null||t.onclick!==null||(t.onclick=xa));else if(n!==4&&(n===27&&on(e.type)&&(a=e.stateNode,t=null),e=e.child,e!==null))for(qc(e,t,a),e=e.sibling;e!==null;)qc(e,t,a),e=e.sibling}function cu(e,t,a){var n=e.tag;if(n===5||n===6)e=e.stateNode,t?a.insertBefore(e,t):a.appendChild(e);else if(n!==4&&(n===27&&on(e.type)&&(a=e.stateNode),e=e.child,e!==null))for(cu(e,t,a),e=e.sibling;e!==null;)cu(e,t,a),e=e.sibling}function nm(e){var t=e.stateNode,a=e.memoizedProps;try{for(var n=e.type,u=t.attributes;u.length;)t.removeAttributeNode(u[0]);dt(t,n,a),t[rt]=e,t[jt]=a}catch(r){Re(e,e.return,r)}}var Oa=!1,tt=!1,Hc=!1,lm=typeof WeakSet=="function"?WeakSet:Set,it=null;function Mg(e,t){if(e=e.containerInfo,so=Au,e=vd(e),Rr(e)){if("selectionStart"in e)var a={start:e.selectionStart,end:e.selectionEnd};else e:{a=(a=e.ownerDocument)&&a.defaultView||window;var n=a.getSelection&&a.getSelection();if(n&&n.rangeCount!==0){a=n.anchorNode;var u=n.anchorOffset,r=n.focusNode;n=n.focusOffset;try{a.nodeType,r.nodeType}catch{a=null;break e}var f=0,m=-1,j=-1,A=0,k=0,Q=e,R=null;t:for(;;){for(var M;Q!==a||u!==0&&Q.nodeType!==3||(m=f+u),Q!==r||n!==0&&Q.nodeType!==3||(j=f+n),Q.nodeType===3&&(f+=Q.nodeValue.length),(M=Q.firstChild)!==null;)R=Q,Q=M;for(;;){if(Q===e)break t;if(R===a&&++A===u&&(m=f),R===r&&++k===n&&(j=f),(M=Q.nextSibling)!==null)break;Q=R,R=Q.parentNode}Q=M}a=m===-1||j===-1?null:{start:m,end:j}}else a=null}a=a||{start:0,end:0}}else a=null;for(io={focusedElem:e,selectionRange:a},Au=!1,it=t;it!==null;)if(t=it,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,it=e;else for(;it!==null;){switch(t=it,r=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(a=0;a title"))),dt(r,n,a),r[rt]=e,st(r),n=r;break e;case"link":var f=op("link","href",u).get(n+(a.href||""));if(f){for(var m=0;mUe&&(f=Ue,Ue=ie,ie=f);var _=md(m,ie),N=md(m,Ue);if(_&&N&&(M.rangeCount!==1||M.anchorNode!==_.node||M.anchorOffset!==_.offset||M.focusNode!==N.node||M.focusOffset!==N.offset)){var C=Q.createRange();C.setStart(_.node,_.offset),M.removeAllRanges(),ie>Ue?(M.addRange(C),M.extend(N.node,N.offset)):(C.setEnd(N.node,N.offset),M.addRange(C))}}}}for(Q=[],M=m;M=M.parentNode;)M.nodeType===1&&Q.push({element:M,left:M.scrollLeft,top:M.scrollTop});for(typeof m.focus=="function"&&m.focus(),m=0;ma?32:a,D.T=null,a=Kc,Kc=null;var r=un,f=Da;if(at=0,Ul=un=null,Da=0,(we&6)!==0)throw Error(c(331));var m=we;if(we|=4,pm(r.current),dm(r,r.current,f,a),we=m,Zs(0,!1),At&&typeof At.onPostCommitFiberRoot=="function")try{At.onPostCommitFiberRoot(os,r)}catch{}return!0}finally{V.p=u,D.T=n,zm(e,t)}}function Um(e,t,a){t=Kt(a,t),t=Ec(e.stateNode,t,2),e=Ia(e,t,2),e!==null&&(ds(e,2),ha(e))}function Re(e,t,a){if(e.tag===3)Um(e,e,a);else for(;t!==null;){if(t.tag===3){Um(t,e,a);break}else if(t.tag===1){var n=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof n.componentDidCatch=="function"&&(sn===null||!sn.has(n))){e=Kt(a,e),a=kh(2),n=Ia(t,a,2),n!==null&&(qh(a,n,t,e),ds(n,2),ha(n));break}}t=t.return}}function $c(e,t,a){var n=e.pingCache;if(n===null){n=e.pingCache=new Ug;var u=new Set;n.set(t,u)}else u=n.get(t),u===void 0&&(u=new Set,n.set(t,u));u.has(a)||(Qc=!0,u.add(a),e=Lg.bind(null,e,t,a),t.then(e,e))}function Lg(e,t,a){var n=e.pingCache;n!==null&&n.delete(t),e.pingedLanes|=e.suspendedLanes&a,e.warmLanes&=~a,qe===e&&(xe&a)===a&&(Ze===4||Ze===3&&(xe&62914560)===xe&&300>Ot()-du?(we&2)===0&&kl(e,0):Yc|=a,Dl===xe&&(Dl=0)),ha(e)}function km(e,t){t===0&&(t=Af()),e=Dn(e,t),e!==null&&(ds(e,t),ha(e))}function Qg(e){var t=e.memoizedState,a=0;t!==null&&(a=t.retryLane),km(e,a)}function Yg(e,t){var a=0;switch(e.tag){case 31:case 13:var n=e.stateNode,u=e.memoizedState;u!==null&&(a=u.retryLane);break;case 19:n=e.stateNode;break;case 22:n=e.stateNode._retryCache;break;default:throw Error(c(314))}n!==null&&n.delete(t),km(e,a)}function Gg(e,t){return rr(e,t)}var xu=null,Hl=null,Fc=!1,ju=!1,Wc=!1,cn=0;function ha(e){e!==Hl&&e.next===null&&(Hl===null?xu=Hl=e:Hl=Hl.next=e),ju=!0,Fc||(Fc=!0,Kg())}function Zs(e,t){if(!Wc&&ju){Wc=!0;do for(var a=!1,n=xu;n!==null;){if(e!==0){var u=n.pendingLanes;if(u===0)var r=0;else{var f=n.suspendedLanes,m=n.pingedLanes;r=(1<<31-Rt(42|e)+1)-1,r&=u&~(f&~m),r=r&201326741?r&201326741|1:r?r|2:0}r!==0&&(a=!0,Lm(n,r))}else r=xe,r=Ei(n,n===qe?r:0,n.cancelPendingCommit!==null||n.timeoutHandle!==-1),(r&3)===0||fs(n,r)||(a=!0,Lm(n,r));n=n.next}while(a);Wc=!1}}function Xg(){qm()}function qm(){ju=Fc=!1;var e=0;cn!==0&&t0()&&(e=cn);for(var t=Ot(),a=null,n=xu;n!==null;){var u=n.next,r=Hm(n,t);r===0?(n.next=null,a===null?xu=u:a.next=u,u===null&&(Hl=a)):(a=n,(e!==0||(r&3)!==0)&&(ju=!0)),n=u}at!==0&&at!==5||Zs(e),cn!==0&&(cn=0)}function Hm(e,t){for(var a=e.suspendedLanes,n=e.pingedLanes,u=e.expirationTimes,r=e.pendingLanes&-62914561;0m)break;var k=j.transferSize,Q=j.initiatorType;k&&Jm(Q)&&(j=j.responseEnd,f+=k*(j"u"?null:document;function ip(e,t,a){var n=Bl;if(n&&typeof t=="string"&&t){var u=Gt(t);u='link[rel="'+e+'"][href="'+u+'"]',typeof a=="string"&&(u+='[crossorigin="'+a+'"]'),sp.has(u)||(sp.add(u),e={rel:e,crossOrigin:a,href:t},n.querySelector(u)===null&&(t=n.createElement("link"),dt(t,"link",e),st(t),n.head.appendChild(t)))}}function o0(e){Ua.D(e),ip("dns-prefetch",e,null)}function f0(e,t){Ua.C(e,t),ip("preconnect",e,t)}function d0(e,t,a){Ua.L(e,t,a);var n=Bl;if(n&&e&&t){var u='link[rel="preload"][as="'+Gt(t)+'"]';t==="image"&&a&&a.imageSrcSet?(u+='[imagesrcset="'+Gt(a.imageSrcSet)+'"]',typeof a.imageSizes=="string"&&(u+='[imagesizes="'+Gt(a.imageSizes)+'"]')):u+='[href="'+Gt(e)+'"]';var r=u;switch(t){case"style":r=Ll(e);break;case"script":r=Ql(e)}Wt.has(r)||(e=x({rel:"preload",href:t==="image"&&a&&a.imageSrcSet?void 0:e,as:t},a),Wt.set(r,e),n.querySelector(u)!==null||t==="style"&&n.querySelector(Fs(r))||t==="script"&&n.querySelector(Ws(r))||(t=n.createElement("link"),dt(t,"link",e),st(t),n.head.appendChild(t)))}}function h0(e,t){Ua.m(e,t);var a=Bl;if(a&&e){var n=t&&typeof t.as=="string"?t.as:"script",u='link[rel="modulepreload"][as="'+Gt(n)+'"][href="'+Gt(e)+'"]',r=u;switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":r=Ql(e)}if(!Wt.has(r)&&(e=x({rel:"modulepreload",href:e},t),Wt.set(r,e),a.querySelector(u)===null)){switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(a.querySelector(Ws(r)))return}n=a.createElement("link"),dt(n,"link",e),st(n),a.head.appendChild(n)}}}function m0(e,t,a){Ua.S(e,t,a);var n=Bl;if(n&&e){var u=rl(n).hoistableStyles,r=Ll(e);t=t||"default";var f=u.get(r);if(!f){var m={loading:0,preload:null};if(f=n.querySelector(Fs(r)))m.loading=5;else{e=x({rel:"stylesheet",href:e,"data-precedence":t},a),(a=Wt.get(r))&&mo(e,a);var j=f=n.createElement("link");st(j),dt(j,"link",e),j._p=new Promise(function(A,k){j.onload=A,j.onerror=k}),j.addEventListener("load",function(){m.loading|=1}),j.addEventListener("error",function(){m.loading|=2}),m.loading|=4,Tu(f,t,n)}f={type:"stylesheet",instance:f,count:1,state:m},u.set(r,f)}}}function p0(e,t){Ua.X(e,t);var a=Bl;if(a&&e){var n=rl(a).hoistableScripts,u=Ql(e),r=n.get(u);r||(r=a.querySelector(Ws(u)),r||(e=x({src:e,async:!0},t),(t=Wt.get(u))&&po(e,t),r=a.createElement("script"),st(r),dt(r,"link",e),a.head.appendChild(r)),r={type:"script",instance:r,count:1,state:null},n.set(u,r))}}function v0(e,t){Ua.M(e,t);var a=Bl;if(a&&e){var n=rl(a).hoistableScripts,u=Ql(e),r=n.get(u);r||(r=a.querySelector(Ws(u)),r||(e=x({src:e,async:!0,type:"module"},t),(t=Wt.get(u))&&po(e,t),r=a.createElement("script"),st(r),dt(r,"link",e),a.head.appendChild(r)),r={type:"script",instance:r,count:1,state:null},n.set(u,r))}}function up(e,t,a,n){var u=(u=ve.current)?Eu(u):null;if(!u)throw Error(c(446));switch(e){case"meta":case"title":return null;case"style":return typeof a.precedence=="string"&&typeof a.href=="string"?(t=Ll(a.href),a=rl(u).hoistableStyles,n=a.get(t),n||(n={type:"style",instance:null,count:0,state:null},a.set(t,n)),n):{type:"void",instance:null,count:0,state:null};case"link":if(a.rel==="stylesheet"&&typeof a.href=="string"&&typeof a.precedence=="string"){e=Ll(a.href);var r=rl(u).hoistableStyles,f=r.get(e);if(f||(u=u.ownerDocument||u,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},r.set(e,f),(r=u.querySelector(Fs(e)))&&!r._p&&(f.instance=r,f.state.loading=5),Wt.has(e)||(a={rel:"preload",as:"style",href:a.href,crossOrigin:a.crossOrigin,integrity:a.integrity,media:a.media,hrefLang:a.hrefLang,referrerPolicy:a.referrerPolicy},Wt.set(e,a),r||y0(u,e,a,f.state))),t&&n===null)throw Error(c(528,""));return f}if(t&&n!==null)throw Error(c(529,""));return null;case"script":return t=a.async,a=a.src,typeof a=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Ql(a),a=rl(u).hoistableScripts,n=a.get(t),n||(n={type:"script",instance:null,count:0,state:null},a.set(t,n)),n):{type:"void",instance:null,count:0,state:null};default:throw Error(c(444,e))}}function Ll(e){return'href="'+Gt(e)+'"'}function Fs(e){return'link[rel="stylesheet"]['+e+"]"}function rp(e){return x({},e,{"data-precedence":e.precedence,precedence:null})}function y0(e,t,a,n){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?n.loading=1:(t=e.createElement("link"),n.preload=t,t.addEventListener("load",function(){return n.loading|=1}),t.addEventListener("error",function(){return n.loading|=2}),dt(t,"link",a),st(t),e.head.appendChild(t))}function Ql(e){return'[src="'+Gt(e)+'"]'}function Ws(e){return"script[async]"+e}function cp(e,t,a){if(t.count++,t.instance===null)switch(t.type){case"style":var n=e.querySelector('style[data-href~="'+Gt(a.href)+'"]');if(n)return t.instance=n,st(n),n;var u=x({},a,{"data-href":a.href,"data-precedence":a.precedence,href:null,precedence:null});return n=(e.ownerDocument||e).createElement("style"),st(n),dt(n,"style",u),Tu(n,a.precedence,e),t.instance=n;case"stylesheet":u=Ll(a.href);var r=e.querySelector(Fs(u));if(r)return t.state.loading|=4,t.instance=r,st(r),r;n=rp(a),(u=Wt.get(u))&&mo(n,u),r=(e.ownerDocument||e).createElement("link"),st(r);var f=r;return f._p=new Promise(function(m,j){f.onload=m,f.onerror=j}),dt(r,"link",n),t.state.loading|=4,Tu(r,a.precedence,e),t.instance=r;case"script":return r=Ql(a.src),(u=e.querySelector(Ws(r)))?(t.instance=u,st(u),u):(n=a,(u=Wt.get(r))&&(n=x({},a),po(n,u)),e=e.ownerDocument||e,u=e.createElement("script"),st(u),dt(u,"link",n),e.head.appendChild(u),t.instance=u);case"void":return null;default:throw Error(c(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(n=t.instance,t.state.loading|=4,Tu(n,a.precedence,e));return t.instance}function Tu(e,t,a){for(var n=a.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),u=n.length?n[n.length-1]:null,r=u,f=0;f title"):null)}function g0(e,t,a){if(a===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;switch(t.rel){case"stylesheet":return e=t.disabled,typeof t.precedence=="string"&&e==null;default:return!0}case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function dp(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function x0(e,t,a,n){if(a.type==="stylesheet"&&(typeof n.media!="string"||matchMedia(n.media).matches!==!1)&&(a.state.loading&4)===0){if(a.instance===null){var u=Ll(n.href),r=t.querySelector(Fs(u));if(r){t=r._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=wu.bind(e),t.then(e,e)),a.state.loading|=4,a.instance=r,st(r);return}r=t.ownerDocument||t,n=rp(n),(u=Wt.get(u))&&mo(n,u),r=r.createElement("link"),st(r);var f=r;f._p=new Promise(function(m,j){f.onload=m,f.onerror=j}),dt(r,"link",n),a.instance=r}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(a,t),(t=a.state.preload)&&(a.state.loading&3)===0&&(e.count++,a=wu.bind(e),t.addEventListener("load",a),t.addEventListener("error",a))}}var vo=0;function j0(e,t){return e.stylesheets&&e.count===0&&Ou(e,e.stylesheets),0vo?50:800)+t);return e.unsuspend=a,function(){e.unsuspend=null,clearTimeout(n),clearTimeout(u)}}:null}function wu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Ou(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Cu=null;function Ou(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Cu=new Map,t.forEach(b0,e),Cu=null,wu.call(e))}function b0(e,t){if(!(t.state.loading&4)){var a=Cu.get(e);if(a)var n=a.get(null);else{a=new Map,Cu.set(e,a);for(var u=e.querySelectorAll("link[data-precedence],style[data-precedence]"),r=0;r"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(l)}catch(i){console.error(i)}}return l(),_o.exports=B0(),_o.exports}var Q0=L0();/** + * react-router v7.18.1 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */var sf=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,bv=/^[\\/]{2}/;function Y0(l,i){return i+l.replace(/\\/g,"/")}var qp="popstate";function Hp(l){return typeof l=="object"&&l!=null&&"pathname"in l&&"search"in l&&"hash"in l&&"state"in l&&"key"in l}function G0(l={}){function i(c,d){var y;let h=(y=d.state)==null?void 0:y.masked,{pathname:v,search:g,hash:p}=h||c.location;return ko("",{pathname:v,search:g,hash:p},d.state&&d.state.usr||null,d.state&&d.state.key||"default",h?{pathname:c.location.pathname,search:c.location.search,hash:c.location.hash}:void 0)}function o(c,d){return typeof d=="string"?d:ci(d)}return K0(i,o,null,l)}function Xe(l,i){if(l===!1||l===null||typeof l>"u")throw new Error(i)}function ya(l,i){if(!l){typeof console<"u"&&console.warn(i);try{throw new Error(i)}catch{}}}function X0(){return Math.random().toString(36).substring(2,10)}function Bp(l,i){return{usr:l.state,key:l.key,idx:i,masked:l.mask?{pathname:l.pathname,search:l.search,hash:l.hash}:void 0}}function ko(l,i,o=null,c,d){return{pathname:typeof l=="string"?l:l.pathname,search:"",hash:"",...typeof i=="string"?ss(i):i,state:o,key:i&&i.key||c||X0(),mask:d}}function ci({pathname:l="/",search:i="",hash:o=""}){return i&&i!=="?"&&(l+=i.charAt(0)==="?"?i:"?"+i),o&&o!=="#"&&(l+=o.charAt(0)==="#"?o:"#"+o),l}function ss(l){let i={};if(l){let o=l.indexOf("#");o>=0&&(i.hash=l.substring(o),l=l.substring(0,o));let c=l.indexOf("?");c>=0&&(i.search=l.substring(c),l=l.substring(0,c)),l&&(i.pathname=l)}return i}function K0(l,i,o,c={}){let{window:d=document.defaultView,v5Compat:h=!1}=c,v=d.history,g="POP",p=null,y=E();y==null&&(y=0,v.replaceState({...v.state,idx:y},""));function E(){return(v.state||{idx:null}).idx}function x(){g="POP";let q=E(),H=q==null?null:q-y;y=q,p&&p({action:g,location:z.location,delta:H})}function w(q,H){g="PUSH";let G=Hp(q)?q:ko(z.location,q,H);y=E()+1;let K=Bp(G,y),Z=z.createHref(G.mask||G);try{v.pushState(K,"",Z)}catch(W){if(W instanceof DOMException&&W.name==="DataCloneError")throw W;d.location.assign(Z)}h&&p&&p({action:g,location:z.location,delta:1})}function O(q,H){g="REPLACE";let G=Hp(q)?q:ko(z.location,q,H);y=E();let K=Bp(G,y),Z=z.createHref(G.mask||G);v.replaceState(K,"",Z),h&&p&&p({action:g,location:z.location,delta:0})}function U(q){return Z0(d,q)}let z={get action(){return g},get location(){return l(d,v)},listen(q){if(p)throw new Error("A history only accepts one active listener");return d.addEventListener(qp,x),p=q,()=>{d.removeEventListener(qp,x),p=null}},createHref(q){return i(d,q)},createURL:U,encodeLocation(q){let H=U(q);return{pathname:H.pathname,search:H.search,hash:H.hash}},push:w,replace:O,go(q){return v.go(q)}};return z}function Z0(l,i,o=!1){let c="http://localhost";l&&(c=l.location.origin!=="null"?l.location.origin:l.location.href),Xe(c,"No window.location.(origin|href) available to create URL");let d=typeof i=="string"?i:ci(i);return d=d.replace(/ $/,"%20"),!o&&bv.test(d)&&(d=c+d),new URL(d,c)}function Sv(l,i,o="/"){return V0(l,i,o,!1)}function V0(l,i,o,c,d){let h=typeof i=="string"?ss(i):i,v=Ya(h.pathname||"/",o);if(v==null)return null;let g=J0(l),p=null,y=sx(v);for(let E=0;p==null&&E{let E={relativePath:y===void 0?v.path||"":y,caseSensitive:v.caseSensitive===!0,childrenIndex:g,route:v};if(E.relativePath.startsWith("/")){if(!E.relativePath.startsWith(c)&&p)return;Xe(E.relativePath.startsWith(c),`Absolute route path "${E.relativePath}" nested under path "${c}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),E.relativePath=E.relativePath.slice(c.length)}let x=ra([c,E.relativePath]),w=o.concat(E);v.children&&v.children.length>0&&(Xe(v.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${x}".`),Nv(v.children,i,w,x,p)),!(v.path==null&&!v.index)&&i.push({path:x,score:ax(x,v.index),routesMeta:w.map((O,U)=>{let[z,q]=_v(O.relativePath,O.caseSensitive,U===w.length-1);return{...O,matcher:z,compiledParams:q}})})};return l.forEach((v,g)=>{var p;if(v.path===""||!((p=v.path)!=null&&p.includes("?")))h(v,g);else for(let y of Ev(v.path))h(v,g,!0,y)}),i}function Ev(l){let i=l.split("/");if(i.length===0)return[];let[o,...c]=i,d=o.endsWith("?"),h=o.replace(/\?$/,"");if(c.length===0)return d?[h,""]:[h];let v=Ev(c.join("/")),g=[];return g.push(...v.map(p=>p===""?h:[h,p].join("/"))),d&&g.push(...v),g.map(p=>l.startsWith("/")&&p===""?"/":p)}function $0(l){l.sort((i,o)=>i.score!==o.score?o.score-i.score:nx(i.routesMeta.map(c=>c.childrenIndex),o.routesMeta.map(c=>c.childrenIndex)))}var F0=/^:[\w-]+$/,W0=3,P0=2,I0=1,ex=10,tx=-2,Lp=l=>l==="*";function ax(l,i){let o=l.split("/"),c=o.length;return o.some(Lp)&&(c+=tx),i&&(c+=P0),o.filter(d=>!Lp(d)).reduce((d,h)=>d+(F0.test(h)?W0:h===""?I0:ex),c)}function nx(l,i){return l.length===i.length&&l.slice(0,-1).every((c,d)=>c===i[d])?l[l.length-1]-i[i.length-1]:0}function lx(l,i,o=!1){let{routesMeta:c}=l,d={},h="/",v=[];for(let g=0;g{if(E==="*"){let U=g[w]||"";v=h.slice(0,h.length-U.length).replace(/(.)\/+$/,"$1")}const O=g[w];return x&&!O?y[E]=void 0:y[E]=(O||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:v,pattern:l}}function _v(l,i=!1,o=!0){ya(l==="*"||!l.endsWith("*")||l.endsWith("/*"),`Route path "${l}" will be treated as if it were "${l.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${l.replace(/\*$/,"/*")}".`);let c=[],d="^"+l.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(v,g,p,y,E)=>{if(c.push({paramName:g,isOptional:p!=null}),p){let x=E.charAt(y+v.length);return x&&x!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return l.endsWith("*")?(c.push({paramName:"*"}),d+=l==="*"||l==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):o?d+="\\/*$":l!==""&&l!=="/"&&(d+="(?:(?=\\/|$))"),[new RegExp(d,i?void 0:"i"),c]}function sx(l){try{return l.split("/").map(i=>decodeURIComponent(i).replace(/\//g,"%2F")).join("/")}catch(i){return ya(!1,`The URL path "${l}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${i}).`),l}}function Ya(l,i){if(i==="/")return l;if(!l.toLowerCase().startsWith(i.toLowerCase()))return null;let o=i.endsWith("/")?i.length-1:i.length,c=l.charAt(o);return c&&c!=="/"?null:l.slice(o)||"/"}function ix(l,i="/"){let{pathname:o,search:c="",hash:d=""}=typeof l=="string"?ss(l):l,h;return o?(o=Cv(o),o.startsWith("/")?h=Qp(o.substring(1),"/"):h=Qp(o,i)):h=i,{pathname:h,search:cx(c),hash:ox(d)}}function Qp(l,i){let o=Ju(i).split("/");return l.split("/").forEach(d=>{d===".."?o.length>1&&o.pop():d!=="."&&o.push(d)}),o.length>1?o.join("/"):"/"}function Ao(l,i,o,c){return`Cannot include a '${l}' character in a manually specified \`to.${i}\` field [${JSON.stringify(c)}]. Please separate it out to the \`to.${o}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function ux(l){return l.filter((i,o)=>o===0||i.route.path&&i.route.path.length>0)}function wv(l){let i=ux(l);return i.map((o,c)=>c===i.length-1?o.pathname:o.pathnameBase)}function uf(l,i,o,c=!1){let d;typeof l=="string"?d=ss(l):(d={...l},Xe(!d.pathname||!d.pathname.includes("?"),Ao("?","pathname","search",d)),Xe(!d.pathname||!d.pathname.includes("#"),Ao("#","pathname","hash",d)),Xe(!d.search||!d.search.includes("#"),Ao("#","search","hash",d)));let h=l===""||d.pathname==="",v=h?"/":d.pathname,g;if(v==null)g=o;else{let x=i.length-1;if(!c&&v.startsWith("..")){let w=v.split("/");for(;w[0]==="..";)w.shift(),x-=1;d.pathname=w.join("/")}g=x>=0?i[x]:"/"}let p=ix(d,g),y=v&&v!=="/"&&v.endsWith("/"),E=(h||v===".")&&o.endsWith("/");return!p.pathname.endsWith("/")&&(y||E)&&(p.pathname+="/"),p}var Cv=l=>l.replace(/[\\/]{2,}/g,"/"),ra=l=>Cv(l.join("/")),Ju=l=>l.replace(/\/+$/,""),rx=l=>Ju(l).replace(/^\/*/,"/"),cx=l=>!l||l==="?"?"":l.startsWith("?")?l:"?"+l,ox=l=>!l||l==="#"?"":l.startsWith("#")?l:"#"+l,fx=class{constructor(l,i,o,c=!1){this.status=l,this.statusText=i||"",this.internal=c,o instanceof Error?(this.data=o.toString(),this.error=o):this.data=o}};function dx(l){return l!=null&&typeof l.status=="number"&&typeof l.statusText=="string"&&typeof l.internal=="boolean"&&"data"in l}function hx(l){let i=l.map(o=>o.route.path).filter(Boolean);return ra(i)||"/"}var Ov=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Av(l,i){let o=l;if(typeof o!="string"||!sf.test(o))return{absoluteURL:void 0,isExternal:!1,to:o};let c=o,d=!1;if(Ov)try{let h=new URL(window.location.href),v=bv.test(o)?new URL(Y0(o,h.protocol)):new URL(o),g=Ya(v.pathname,i);v.origin===h.origin&&g!=null?o=g+v.search+v.hash:d=!0}catch{ya(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:c,isExternal:d,to:o}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Rv=["POST","PUT","PATCH","DELETE"];new Set(Rv);var mx=["GET",...Rv];new Set(mx);var px=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];function vx(l){try{return px.includes(new URL(l).protocol)}catch{return!1}}var is=S.createContext(null);is.displayName="DataRouter";var er=S.createContext(null);er.displayName="DataRouterState";var Mv=S.createContext(!1);function yx(){return S.useContext(Mv)}var zv=S.createContext({isTransitioning:!1});zv.displayName="ViewTransition";var gx=S.createContext(new Map);gx.displayName="Fetchers";var xx=S.createContext(null);xx.displayName="Await";var aa=S.createContext(null);aa.displayName="Navigation";var vi=S.createContext(null);vi.displayName="Location";var ca=S.createContext({outlet:null,matches:[],isDataRoute:!1});ca.displayName="Route";var rf=S.createContext(null);rf.displayName="RouteError";var Dv="REACT_ROUTER_ERROR",jx="REDIRECT",bx="ROUTE_ERROR_RESPONSE";function Sx(l){if(l.startsWith(`${Dv}:${jx}:{`))try{let i=JSON.parse(l.slice(28));if(typeof i=="object"&&i&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.location=="string"&&typeof i.reloadDocument=="boolean"&&typeof i.replace=="boolean")return i}catch{}}function Nx(l){if(l.startsWith(`${Dv}:${bx}:{`))try{let i=JSON.parse(l.slice(40));if(typeof i=="object"&&i&&typeof i.status=="number"&&typeof i.statusText=="string")return new fx(i.status,i.statusText,i.data)}catch{}}function Ex(l,{relative:i}={}){Xe(yi(),"useHref() may be used only in the context of a component.");let{basename:o,navigator:c}=S.useContext(aa),{hash:d,pathname:h,search:v}=gi(l,{relative:i}),g=h;return o!=="/"&&(g=h==="/"?o:ra([o,h])),c.createHref({pathname:g,search:v,hash:d})}function yi(){return S.useContext(vi)!=null}function Ga(){return Xe(yi(),"useLocation() may be used only in the context of a component."),S.useContext(vi).location}var Uv="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function kv(l){S.useContext(aa).static||S.useLayoutEffect(l)}function qv(){let{isDataRoute:l}=S.useContext(ca);return l?Bx():Tx()}function Tx(){Xe(yi(),"useNavigate() may be used only in the context of a component.");let l=S.useContext(is),{basename:i,navigator:o}=S.useContext(aa),{matches:c}=S.useContext(ca),{pathname:d}=Ga(),h=JSON.stringify(wv(c)),v=S.useRef(!1);return kv(()=>{v.current=!0}),S.useCallback((p,y={})=>{if(ya(v.current,Uv),!v.current)return;if(typeof p=="number"){o.go(p);return}let E=uf(p,JSON.parse(h),d,y.relative==="path");l==null&&i!=="/"&&(E.pathname=E.pathname==="/"?i:ra([i,E.pathname])),(y.replace?o.replace:o.push)(E,y.state,y)},[i,o,h,d,l])}var _x=S.createContext(null);function wx(l){let i=S.useContext(ca).outlet;return S.useMemo(()=>i&&S.createElement(_x.Provider,{value:l},i),[i,l])}function Hv(){let{matches:l}=S.useContext(ca),i=l[l.length-1];return(i==null?void 0:i.params)??{}}function gi(l,{relative:i}={}){let{matches:o}=S.useContext(ca),{pathname:c}=Ga(),d=JSON.stringify(wv(o));return S.useMemo(()=>uf(l,JSON.parse(d),c,i==="path"),[l,d,c,i])}function Cx(l,i){return Bv(l,i)}function Bv(l,i,o){var q;Xe(yi(),"useRoutes() may be used only in the context of a component.");let{navigator:c}=S.useContext(aa),{matches:d}=S.useContext(ca),h=d[d.length-1],v=h?h.params:{},g=h?h.pathname:"/",p=h?h.pathnameBase:"/",y=h&&h.route;{let H=y&&y.path||"";Qv(g,!y||H.endsWith("*")||H.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${g}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let E=Ga(),x;if(i){let H=typeof i=="string"?ss(i):i;Xe(p==="/"||((q=H.pathname)==null?void 0:q.startsWith(p)),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${p}" but pathname "${H.pathname}" was given in the \`location\` prop.`),x=H}else x=E;let w=x.pathname||"/",O=w;if(p!=="/"){let H=p.replace(/^\//,"").split("/");O="/"+w.replace(/^\//,"").split("/").slice(H.length).join("/")}let U=o&&o.state.matches.length?o.state.matches.map(H=>Object.assign(H,{route:o.manifest[H.route.id]||H.route})):Sv(l,{pathname:O});ya(y||U!=null,`No routes matched location "${x.pathname}${x.search}${x.hash}" `),ya(U==null||U[U.length-1].route.element!==void 0||U[U.length-1].route.Component!==void 0||U[U.length-1].route.lazy!==void 0,`Matched leaf route at location "${x.pathname}${x.search}${x.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let z=zx(U&&U.map(H=>Object.assign({},H,{params:Object.assign({},v,H.params),pathname:ra([p,c.encodeLocation?c.encodeLocation(H.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:H.pathname]),pathnameBase:H.pathnameBase==="/"?p:ra([p,c.encodeLocation?c.encodeLocation(H.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:H.pathnameBase])})),d,o);return i&&z?S.createElement(vi.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",mask:void 0,...x},navigationType:"POP"}},z):z}function Ox(){let l=Hx(),i=dx(l)?`${l.status} ${l.statusText}`:l instanceof Error?l.message:JSON.stringify(l),o=l instanceof Error?l.stack:null,c="rgba(200,200,200, 0.5)",d={padding:"0.5rem",backgroundColor:c},h={padding:"2px 4px",backgroundColor:c},v=null;return console.error("Error handled by React Router default ErrorBoundary:",l),v=S.createElement(S.Fragment,null,S.createElement("p",null,"💿 Hey developer 👋"),S.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",S.createElement("code",{style:h},"ErrorBoundary")," or"," ",S.createElement("code",{style:h},"errorElement")," prop on your route.")),S.createElement(S.Fragment,null,S.createElement("h2",null,"Unexpected Application Error!"),S.createElement("h3",{style:{fontStyle:"italic"}},i),o?S.createElement("pre",{style:d},o):null,v)}var Ax=S.createElement(Ox,null),Lv=class extends S.Component{constructor(l){super(l),this.state={location:l.location,revalidation:l.revalidation,error:l.error}}static getDerivedStateFromError(l){return{error:l}}static getDerivedStateFromProps(l,i){return i.location!==l.location||i.revalidation!=="idle"&&l.revalidation==="idle"?{error:l.error,location:l.location,revalidation:l.revalidation}:{error:l.error!==void 0?l.error:i.error,location:i.location,revalidation:l.revalidation||i.revalidation}}componentDidCatch(l,i){this.props.onError?this.props.onError(l,i):console.error("React Router caught the following error during render",l)}render(){let l=this.state.error;if(this.context&&typeof l=="object"&&l&&"digest"in l&&typeof l.digest=="string"){const o=Nx(l.digest);o&&(l=o)}let i=l!==void 0?S.createElement(ca.Provider,{value:this.props.routeContext},S.createElement(rf.Provider,{value:l,children:this.props.component})):this.props.children;return this.context?S.createElement(Rx,{error:l},i):i}};Lv.contextType=Mv;var Ro=new WeakMap;function Rx({children:l,error:i}){let{basename:o}=S.useContext(aa);if(typeof i=="object"&&i&&"digest"in i&&typeof i.digest=="string"){let c=Sx(i.digest);if(c){let d=Ro.get(i);if(d)throw d;let h=Av(c.location,o),v=h.absoluteURL||h.to;if(vx(v))throw new Error("Invalid redirect location");if(Ov&&!Ro.get(i))if(h.isExternal||c.reloadDocument)window.location.href=v;else{const g=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(h.to,{replace:c.replace}));throw Ro.set(i,g),g}return S.createElement("meta",{httpEquiv:"refresh",content:`0;url=${v}`})}}return l}function Mx({routeContext:l,match:i,children:o}){let c=S.useContext(is);return c&&c.static&&c.staticContext&&(i.route.errorElement||i.route.ErrorBoundary)&&(c.staticContext._deepestRenderedBoundaryId=i.route.id),S.createElement(ca.Provider,{value:l},o)}function zx(l,i=[],o){let c=o==null?void 0:o.state;if(l==null){if(!c)return null;if(c.errors)l=c.matches;else if(i.length===0&&!c.initialized&&c.matches.length>0)l=c.matches;else return null}let d=l,h=c==null?void 0:c.errors;if(h!=null){let E=d.findIndex(x=>x.route.id&&(h==null?void 0:h[x.route.id])!==void 0);Xe(E>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(h).join(",")}`),d=d.slice(0,Math.min(d.length,E+1))}let v=!1,g=-1;if(o&&c){v=c.renderFallback;for(let E=0;E=0?d=d.slice(0,g+1):d=[d[0]];break}}}}let p=o==null?void 0:o.onError,y=c&&p?(E,x)=>{var w,O;p(E,{location:c.location,params:((O=(w=c.matches)==null?void 0:w[0])==null?void 0:O.params)??{},pattern:hx(c.matches),errorInfo:x})}:void 0;return d.reduceRight((E,x,w)=>{let O,U=!1,z=null,q=null;c&&(O=h&&x.route.id?h[x.route.id]:void 0,z=x.route.errorElement||Ax,v&&(g<0&&w===0?(Qv("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),U=!0,q=null):g===w&&(U=!0,q=x.route.hydrateFallbackElement||null)));let H=i.concat(d.slice(0,w+1)),G=()=>{let K;return O?K=z:U?K=q:x.route.Component?K=S.createElement(x.route.Component,null):x.route.element?K=x.route.element:K=E,S.createElement(Mx,{match:x,routeContext:{outlet:E,matches:H,isDataRoute:c!=null},children:K})};return c&&(x.route.ErrorBoundary||x.route.errorElement||w===0)?S.createElement(Lv,{location:c.location,revalidation:c.revalidation,component:z,error:O,children:G(),routeContext:{outlet:null,matches:H,isDataRoute:!0},onError:y}):G()},null)}function cf(l){return`${l} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function Dx(l){let i=S.useContext(is);return Xe(i,cf(l)),i}function Ux(l){let i=S.useContext(er);return Xe(i,cf(l)),i}function kx(l){let i=S.useContext(ca);return Xe(i,cf(l)),i}function of(l){let i=kx(l),o=i.matches[i.matches.length-1];return Xe(o.route.id,`${l} can only be used on routes that contain a unique "id"`),o.route.id}function qx(){return of("useRouteId")}function Hx(){var c;let l=S.useContext(rf),i=Ux("useRouteError"),o=of("useRouteError");return l!==void 0?l:(c=i.errors)==null?void 0:c[o]}function Bx(){let{router:l}=Dx("useNavigate"),i=of("useNavigate"),o=S.useRef(!1);return kv(()=>{o.current=!0}),S.useCallback(async(d,h={})=>{ya(o.current,Uv),o.current&&(typeof d=="number"?await l.navigate(d):await l.navigate(d,{fromRouteId:i,...h}))},[l,i])}var Yp={};function Qv(l,i,o){!i&&!Yp[l]&&(Yp[l]=!0,ya(!1,o))}S.memo(Lx);function Lx({routes:l,manifest:i,future:o,state:c,isStatic:d,onError:h}){return Bv(l,void 0,{manifest:i,state:c,isStatic:d,onError:h})}function Qx(l){return wx(l.context)}function ia(l){Xe(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Yx({basename:l="/",children:i=null,location:o,navigationType:c="POP",navigator:d,static:h=!1,useTransitions:v}){Xe(!yi(),"You cannot render a inside another . You should never have more than one in your app.");let g=l.replace(/^\/*/,"/"),p=S.useMemo(()=>({basename:g,navigator:d,static:h,useTransitions:v,future:{}}),[g,d,h,v]);typeof o=="string"&&(o=ss(o));let{pathname:y="/",search:E="",hash:x="",state:w=null,key:O="default",mask:U}=o,z=S.useMemo(()=>{let q=Ya(y,g);return q==null?null:{location:{pathname:q,search:E,hash:x,state:w,key:O,mask:U},navigationType:c}},[g,y,E,x,w,O,c,U]);return ya(z!=null,` is not able to match the URL "${y}${E}${x}" because it does not start with the basename, so the won't render anything.`),z==null?null:S.createElement(aa.Provider,{value:p},S.createElement(vi.Provider,{children:i,value:z}))}function Gx({children:l,location:i}){return Cx(qo(l),i)}function qo(l,i=[]){let o=[];return S.Children.forEach(l,(c,d)=>{if(!S.isValidElement(c))return;let h=[...i,d];if(c.type===S.Fragment){o.push.apply(o,qo(c.props.children,h));return}Xe(c.type===ia,`[${typeof c.type=="string"?c.type:c.type.name}] is not a component. All component children of must be a or `),Xe(!c.props.index||!c.props.children,"An index route cannot have child routes.");let v={id:c.props.id||h.join("-"),caseSensitive:c.props.caseSensitive,element:c.props.element,Component:c.props.Component,index:c.props.index,path:c.props.path,middleware:c.props.middleware,loader:c.props.loader,action:c.props.action,hydrateFallbackElement:c.props.hydrateFallbackElement,HydrateFallback:c.props.HydrateFallback,errorElement:c.props.errorElement,ErrorBoundary:c.props.ErrorBoundary,hasErrorBoundary:c.props.hasErrorBoundary===!0||c.props.ErrorBoundary!=null||c.props.errorElement!=null,shouldRevalidate:c.props.shouldRevalidate,handle:c.props.handle,lazy:c.props.lazy};c.props.children&&(v.children=qo(c.props.children,h)),o.push(v)}),o}var Gu="get",Xu="application/x-www-form-urlencoded";function tr(l){return typeof HTMLElement<"u"&&l instanceof HTMLElement}function Xx(l){return tr(l)&&l.tagName.toLowerCase()==="button"}function Kx(l){return tr(l)&&l.tagName.toLowerCase()==="form"}function Zx(l){return tr(l)&&l.tagName.toLowerCase()==="input"}function Vx(l){return!!(l.metaKey||l.altKey||l.ctrlKey||l.shiftKey)}function Jx(l,i){return l.button===0&&(!i||i==="_self")&&!Vx(l)}var Hu=null;function $x(){if(Hu===null)try{new FormData(document.createElement("form"),0),Hu=!1}catch{Hu=!0}return Hu}var Fx=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Mo(l){return l!=null&&!Fx.has(l)?(ya(!1,`"${l}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Xu}"`),null):l}function Wx(l,i){let o,c,d,h,v;if(Kx(l)){let g=l.getAttribute("action");c=g?Ya(g,i):null,o=l.getAttribute("method")||Gu,d=Mo(l.getAttribute("enctype"))||Xu,h=new FormData(l)}else if(Xx(l)||Zx(l)&&(l.type==="submit"||l.type==="image")){let g=l.form;if(g==null)throw new Error('Cannot submit a + )} + + + {(item.kind === 'pr' || item.kind === 'findings') && ( +
+ {t.review && ( + + reviewed sha {shortSha(t.review.result_sha)} + + )} + {t.submission && ( + + submitted sha{' '} + {shortSha(t.submission.result_sha)} + + )} + {t.submission?.gate_ref && ( + + gate {t.submission.gate_ref} + + )} + {t.submission?.review_ref && ( + + review {t.submission.review_ref} + + )} +
+ )} + + {item.kind === 'trajectory' && t.decision_request?.options?.length ? ( +
+

+ + agent-supplied · proposed options +

+ {t.decision_request.options.map((o) => ( +

+ {o.id} {o.description} + {o.tradeoff && — {o.tradeoff}} +

+ ))} +
+ ) : null} + + {open && item.kind === 'findings' && t.review && ( + + )} + + ) +} + +export function Review() { + const overview = useQuery({ + queryKey: ['overview'], + queryFn: api.overview, + refetchInterval: 5000, + }) + const [kind, setKind] = useState('all') + + const items = (overview.data?.tasks ?? []).flatMap(itemsFor) + // Most urgent first: high inherent priority, then oldest wait. + const order = KINDS.map((k) => k.kind) + items.sort( + (a, b) => + (b.task.inherent_priority ?? 0) - (a.task.inherent_priority ?? 0) || + Date.parse(a.at ?? '') - Date.parse(b.at ?? '') || + order.indexOf(a.kind) - order.indexOf(b.kind), + ) + const shown = kind === 'all' ? items : items.filter((i) => i.kind === kind) + const count = (k: Kind) => items.filter((i) => i.kind === k).length + const blocking = items.reduce((n, i) => n + (i.kind === 'findings' ? (i.task.review?.blocking ?? 0) : 0), 0) + + return ( +
+
+

Review

+

Items that need your attention and judgement.

+ + {overview.data ? ( + <> + read {new Date(overview.data.updated_at).toISOString().slice(11, 19)} UTC + + ) : overview.isError ? ( + overview unreachable — {String(overview.error)} + ) : ( + 'reading the first snapshot' + )} + +
+ +
+
+ Needs you + {items.length} + + across {new Set(items.map((i) => i.task.id)).size} tasks + +
+
+ Blocking findings + {blocking} + claimed by sealed reviews, not yet accepted +
+
+ Pull requests open + {count('pr')} + recorded on a submission +
+
+ + +
+ + {KINDS.map((k) => ( + + ))} +
+ + {shown.length === 0 ? ( + +

+ Trajectory gates, human decisions, operator-required blocks, blocking review findings + and open pull requests land here. +

+
+ ) : ( +
+ {shown.map((i) => ( + + ))} +
+ )} +
+ + +
+ ) +} diff --git a/web/src/screens/Settings.css b/web/src/screens/Settings.css new file mode 100644 index 0000000..51779ba --- /dev/null +++ b/web/src/screens/Settings.css @@ -0,0 +1,164 @@ +/* Settings. Only what the shared shell does not carry: the tab strip, the + credential form geometry, and the fact list. */ + +.set-tabs { + display: flex; + gap: 2px; + flex-wrap: wrap; + border-bottom: 1px solid var(--line); +} + +.set-tab { + background: none; + border: 0; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + padding: 8px 12px; + color: var(--text-mid); + cursor: pointer; + transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease); +} + +.set-tab:hover { + color: var(--text-hi); +} + +/* Accent marks the section you are in, and the save button. Nothing else. */ +.set-tab[aria-selected='true'] { + color: var(--accent-hi); + border-bottom-color: var(--accent); +} + +.set-account { + display: grid; + grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); + gap: 20px; + align-items: start; +} + +.set-form { + display: flex; + flex-direction: column; + gap: 16px; +} + +.set-note { + margin: 0; + color: var(--text-mid); + font-size: 13px; +} + +.set-field { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.set-split { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 14px; +} + +.set-input { + height: 34px; + padding: 0 11px; + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + color: var(--text-hi); + font: inherit; + outline: none; + width: 100%; + transition: border-color var(--fast) var(--ease); +} + +.set-input::placeholder { + color: var(--text-lo); +} + +.set-input:focus { + border-color: var(--accent-line); +} + +.set-reveal { + position: relative; + display: flex; +} + +.set-reveal-btn { + position: absolute; + right: 4px; + top: 4px; + height: 26px; + padding: 0 9px; + border: 0; + border-radius: 6px; + background: var(--bg-3); + color: var(--text-mid); + font-size: 12px; + cursor: pointer; +} + +.set-rules { + display: flex; + flex-wrap: wrap; + gap: 14px; + font-size: 12px; + color: var(--text-lo); +} + +.set-rules span { + display: inline-flex; + align-items: center; + gap: 5px; +} + +.set-rules span[data-met='true'] { + color: #5fbf82; +} + +.set-error { + display: flex; + align-items: center; + gap: 8px; + margin: 0; + font-size: 13px; + color: #e06c60; +} + +.set-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + flex-wrap: wrap; + border-top: 1px solid var(--line); + padding-top: 14px; +} + +.set-facts { + display: flex; + flex-direction: column; + gap: 12px; +} + +.set-facts > div { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; + word-break: break-word; +} + +.set-signout { + align-self: flex-start; + margin-top: 4px; +} + +@media (max-width: 900px) { + .set-account { + grid-template-columns: minmax(0, 1fr); + } +} diff --git a/web/src/screens/Settings.tsx b/web/src/screens/Settings.tsx new file mode 100644 index 0000000..6973a30 --- /dev/null +++ b/web/src/screens/Settings.tsx @@ -0,0 +1,256 @@ +import { useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { api } from '../api/client' +import type { Account } from '../api/types' +import { Chip, EndpointGap, M, Panel } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './Settings.css' + +/** The spec asks for eight tabs. The coordinator has exactly one settings + * mutation — PUT /v1/ui/account — so exactly one tab holds controls. Every + * other tab names the endpoint that would supply it rather than showing a + * form that cannot save. Law 5. */ +type TabKey = + | 'account' + | 'general' + | 'appearance' + | 'notifications' + | 'access' + | 'integrations' + | 'agents' + | 'policies' + | 'advanced' + +const TABS: { key: TabKey; label: string }[] = [ + { key: 'account', label: 'Account' }, + { key: 'general', label: 'General' }, + { key: 'appearance', label: 'Appearance' }, + { key: 'notifications', label: 'Notifications' }, + { key: 'access', label: 'Access & security' }, + { key: 'integrations', label: 'Integrations' }, + { key: 'agents', label: 'Agents & tools' }, + { key: 'policies', label: 'Policies' }, + { key: 'advanced', label: 'Advanced' }, +] + +const GAPS: Record, { path: string; what: string }> = { + general: { + path: 'GET/PUT /v1/ui/settings/general', + what: 'Instance name and environment, time display, default task preferences, default workflow policy and event/artifact retention. The coordinator keeps these in config.jsonc and the container environment; nothing reads or writes them over the API.', + }, + appearance: { + path: 'GET/PUT /v1/ui/settings/appearance', + what: 'Theme and reduced motion are the only appearance choices ethos permits, and neither is stored server-side today. The console follows the operating system.', + }, + notifications: { + path: 'GET/PUT /v1/ui/settings/notifications', + what: 'Notification sinks and per-event routing. ntfy credentials live in the compose .env file and are never returned by the API.', + }, + access: { + path: 'GET /v1/ui/settings/access', + what: 'Web tokens and their status, trusted and ignored review actors, live operator sessions, agent-surface auth and the source each secret is loaded from. Only the operator credential below is exposed today.', + }, + integrations: { + path: 'GET /v1/ui/settings/integrations', + what: 'Gitea and Vikunja endpoints, notification sinks and source reconciliation state, each with its identity and last successful interaction. No endpoint reports integration health.', + }, + agents: { + path: 'GET /v1/ui/settings/harnesses', + what: 'Harness definitions, adapter runtime configuration, herdr and tmux ownership, default launch behaviour and context occupancy thresholds. The registry is read from config.jsonc at start-up and is not served.', + }, + policies: { + path: 'GET/PUT /v1/ui/settings/policies', + what: 'Work phase defaults, the trajectory gate, the bounded decision request budget, retry policy, reconcile failure handoff threshold and review/submission policy. These are compiled-in defaults, not stored settings.', + }, + advanced: { + path: 'GET /v1/ui/settings/diagnostics', + what: 'Event-store diagnostics, snapshots and replay, raw configuration, migration status and destructive operator actions. Destructive actions must not be built before the coordinator can report what they would touch.', + }, +} + +function stamp(at?: string) { + if (!at) return '—' + const ms = Date.parse(at) + return Number.isFinite(ms) ? new Date(ms).toISOString().replace('T', ' ').slice(0, 19) + ' UTC' : at +} + +function message(error: unknown) { + return error instanceof Error ? error.message : String(error) +} + +export function Settings({ account, onLogout, onCredentialsChanged }: { + account: Account + onLogout: () => void + onCredentialsChanged: (username: string) => void +}) { + const [tab, setTab] = useState('account') + return ( +
+
+

Settings

+

One operator credential is stored here. Everything else Orchestra runs on comes from config and environment.

+
+ +
+ {TABS.map((t) => ( + + ))} +
+ + {tab === 'account' ? ( + + ) : ( + t.key === tab)?.label}> + + + )} +
+ ) +} + +function AccountTab({ account, onLogout, onCredentialsChanged }: { + account: Account + onLogout: () => void + onCredentialsChanged: (username: string) => void +}) { + const [username, setUsername] = useState(account.username) + const [currentPassword, setCurrentPassword] = useState('') + const [newPassword, setNewPassword] = useState('') + const [confirmation, setConfirmation] = useState('') + const [visible, setVisible] = useState(false) + const [formError, setFormError] = useState('') + + const mutation = useMutation({ + mutationFn: () => + api.updateAccount({ + current_password: currentPassword, + username: username.trim(), + ...(newPassword ? { new_password: newPassword } : {}), + }), + onSuccess: (updated) => onCredentialsChanged(updated.username), + }) + + const changed = username.trim() !== account.username || !!newPassword + const longEnough = newPassword.length >= 10 + const withinLimit = new TextEncoder().encode(newPassword).length <= 72 + const matches = newPassword === confirmation + + const submit = (event: React.FormEvent) => { + event.preventDefault() + setFormError('') + if (!username.trim()) return setFormError('Username cannot be empty.') + if (!changed) return setFormError('Change the username or enter a new password first.') + if (!currentPassword) return setFormError('Enter your current password to authorize this change.') + if (newPassword && (!longEnough || !withinLimit || !matches)) { + return setFormError(matches ? 'Use a password between 10 and 72 bytes.' : 'The new passwords do not match.') + } + mutation.mutate() + } + + return ( +
+ + +

+ Changing either field signs out every browser using this account. Your current password authorizes the change and is never stored by the console. +

+ +
+ + { setUsername(e.target.value); setFormError('') }} + /> +
+ +
+
+ +
+ { setNewPassword(e.target.value); setFormError('') }} + /> + +
+
+
+ + { setConfirmation(e.target.value); setFormError('') }} + /> +
+
+ + {newPassword && ( +
+ 10 characters or more + 72 bytes or fewer + Passwords match +
+ )} + +
+ + { setCurrentPassword(e.target.value); setFormError('') }} + /> +
+ + {(formError || mutation.error) && ( +

+ {formError || message(mutation.error)} +

+ )} + +
+ You sign in again after saving. + +
+ +
+ + operator}> +
+
Username{account.username}
+
Created{stamp(account.created_at)}
+
Credentials updated{stamp(account.updated_at)}
+
Credential storebcrypt · orchestra data volume
+ +
+
+
+ ) +} diff --git a/web/src/screens/TaskDetail.css b/web/src/screens/TaskDetail.css new file mode 100644 index 0000000..900fe92 --- /dev/null +++ b/web/src/screens/TaskDetail.css @@ -0,0 +1,293 @@ +/* Task detail. Only what orchestra.css does not already carry: the two-column + split, the agent-claim treatment, and the muted superseded register. */ + +.td-top { + display: flex; + align-items: flex-end; + gap: 16px; + flex-wrap: wrap; +} + +.td-top .page-head { + flex: 1; + min-width: 0; +} + +.td-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.td-ids { + display: flex; + align-items: baseline; + gap: 8px 18px; + flex-wrap: wrap; + margin-top: -12px; +} + +.td-columns { + display: grid; + grid-template-columns: minmax(280px, 5fr) minmax(320px, 7fr); + gap: 20px; + align-items: start; +} + +.td-doing > .label { + display: block; + margin: 18px 0 6px; +} + +.td-doing > .label:first-child { + margin-top: 0; +} + +.td-prose { + margin: 0; + color: var(--text-hi); +} + +.td-muted { + color: var(--text-lo); + font-size: 13px; + margin: 6px 0 0; +} + +.td-list { + margin: 0; + padding-left: 18px; + display: flex; + flex-direction: column; + gap: 5px; + color: var(--text-mid); +} + +.td-more > summary { + cursor: pointer; + color: var(--text-lo); + font-size: 13px; + margin-top: 8px; + list-style: none; +} + +.td-more > summary::before { + content: '▸ '; + color: var(--text-lo); +} + +.td-more[open] > summary::before { + content: '▾ '; +} + +.td-more[open] > summary { + margin-bottom: 8px; +} + +/* Operator authority: the one class of content that carries the accent. */ +.td-decisions { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 6px; +} + +.td-decisions li { + display: flex; + align-items: baseline; + gap: 10px; + flex-wrap: wrap; + padding: 8px 10px; + border-radius: var(--r-sm); + background: var(--accent-dim); + border-left: 2px solid var(--accent); +} + +.td-kind { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--accent-hi); +} + +.td-subject { + color: var(--text-mid); +} + +.td-value { + color: var(--text-hi); + flex: 1; + min-width: 0; +} + +/* Superseded: still readable, never dominant, and stripped of the accent. */ +.td-superseded .td-decisions li { + background: var(--bg-2); + border-left-color: var(--line-hi); + opacity: 0.7; +} + +.td-superseded .td-kind { + color: var(--text-lo); +} + +.td-superseded .td-value { + color: var(--text-mid); + text-decoration: line-through; +} + +.td-exec { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; +} + +.td-exec .stat { + gap: 5px; +} + +.td-exec .stat-value { + font-size: 20px; +} + +/* Agent claims read as quotation, not as state: dashed rule, machine text, + and a tag that names the source. */ +.td-claim-head { + display: flex; + align-items: baseline; + gap: 10px; + margin: 18px 0 6px; +} + +.td-claim-tag { + font-family: var(--mono); + font-size: 10px; + letter-spacing: 0; + text-transform: none; + color: var(--text-lo); +} + +.td-claim { + display: flex; + align-items: baseline; + gap: 12px; + margin: 0; + padding: 10px 12px; + background: var(--bg-2); + border: 1px dashed var(--line-hi); + border-radius: var(--r-sm); +} + +.td-claim > .mono:first-child { + flex: 1; + min-width: 0; + overflow-wrap: anywhere; +} + +.td-claim-at { + flex: none; +} + +.td-approval { + margin-top: 16px; + padding: 12px; + border: 1px solid var(--accent-line); + background: var(--accent-dim); + border-radius: var(--r-sm); +} + +.td-approval .td-prose { + margin: 4px 0 8px; +} + +.td-approval-row { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + margin-top: 10px; +} + +.td-approval-row .mono { + flex: 1; + min-width: 0; + font-size: 12px; +} + +.td-links { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin: 16px 0 4px; +} + +.td-flow { + display: flex; + flex-direction: column; + gap: 14px; +} + +.td-flow .phases { + overflow-x: auto; + padding-bottom: 4px; +} + +.td-entries { + display: flex; + gap: 8px 20px; + flex-wrap: wrap; + color: var(--text-lo); + font-size: 13px; +} + +.td-entry-phase { + color: var(--text-mid); +} + +/* Actions menu: a native disclosure, not a popover library. */ +.td-menu { + position: relative; +} + +.td-menu > summary { + list-style: none; + cursor: pointer; +} + +.td-menu > summary::-webkit-details-marker { + display: none; +} + +.td-menu-body { + position: absolute; + right: 0; + top: calc(100% + 6px); + z-index: 10; + min-width: 250px; + background: var(--bg-2); + border: 1px solid var(--line-hi); + border-radius: var(--r-md); + box-shadow: var(--shadow-soft); + overflow: hidden; +} + +.td-menu-body .row { + gap: 8px; +} + +.td-menu-body .row:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.td-menu-body .row:disabled:hover { + background: none; +} + +@media (max-width: 900px) { + .td-columns { + grid-template-columns: 1fr; + } +} diff --git a/web/src/screens/TaskDetail.tsx b/web/src/screens/TaskDetail.tsx new file mode 100644 index 0000000..ea79f59 --- /dev/null +++ b/web/src/screens/TaskDetail.tsx @@ -0,0 +1,454 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { Link, useParams } from 'react-router-dom' +import { api } from '../api/client' +import type { Action, Detail, Event, Task } from '../api/types' +import { Chip, EndpointGap, M, Panel, PhasePath } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './TaskDetail.css' + +/** work_phase is on the wire (domain.Task) but not yet in api/types.ts, which + * another screen owns. Read it through a local widening rather than editing a + * shared file out from under someone. */ +type Phased = Task & { work_phase?: string; last_lease_epoch?: string } + +const time = (iso?: string) => (iso ? new Date(iso).toISOString().slice(11, 19) : '—') + +function ago(iso?: string) { + if (!iso) return '—' + const s = Math.max(0, Math.round((Date.now() - Date.parse(iso)) / 1000)) + if (s < 60) return `${s}s ago` + if (s < 3600) return `${Math.floor(s / 60)}m ago` + return `${Math.floor(s / 3600)}h ago` +} + +/** ponytail: recomputed on each 5s poll rather than ticking per second. The + * operator reads a lease in minutes; a second-accurate countdown would need a + * timer for no decision it changes. */ +function remaining(iso?: string) { + if (!iso) return undefined + const s = Math.round((Date.parse(iso) - Date.now()) / 1000) + if (s <= 0) return 'expired' + return `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}` +} + +type Decision = { + id: string + kind: string + subject: string + value: string + at: string + standing: boolean +} + +/** The standing authority for this task, reduced from its own log. Mirrors + * domain.ReduceIntent: a decision is retired only by being named, never by a + * newer decision on the same subject. Superseded records are kept so the + * screen can show them muted instead of pretending they never existed. */ +function decisionsOf(events: Event[]): Decision[] { + const out: Decision[] = [] + const retired = new Set() + for (const e of events) { + const p = (e.payload ?? {}) as { + decision_id?: string + kind?: string + subject?: string + value?: string + supersedes?: string[] + } + if (!p.decision_id) continue + if (e.type === 'HumanDecisionRecorded') { + out.push({ + id: p.decision_id, + kind: p.kind ?? 'decision', + subject: p.subject ?? '', + value: p.value ?? '', + at: e.at, + standing: true, + }) + p.supersedes?.forEach((id) => retired.add(id)) + } else if (e.type === 'HumanDecisionSuperseded') { + retired.add(p.decision_id) + } + } + return out.map((d) => ({ ...d, standing: !retired.has(d.id) })) +} + +/** When each phase was entered, from the events Orchestra itself appended. + * Verified state, not a claim. */ +function phaseEntries(events: Event[]) { + return events + .filter((e) => e.type === 'WorkPhaseChanged') + .map((e) => ({ phase: (e.payload as { phase?: string })?.phase ?? '', at: e.at })) + .filter((p) => p.phase) +} + +/** The last thing the pane actually showed. Agent-authored text: it is + * rendered as a claim, never as orchestra state. */ +function lastLine(text?: string) { + const lines = (text ?? '').split('\n').map((l) => l.trimEnd()).filter((l) => l.trim()) + return lines[lines.length - 1] +} + +function stateTone(task: Task) { + if (task.state === 'blocked' || task.state === 'failed' || task.state === 'needs_attention') { + return 'fault' as const + } + if (task.state === 'completed') return 'done' as const + if (task.state === 'leased') return 'accent' as const + return undefined +} + +export function TaskDetail() { + const { id = '' } = useParams() + const client = useQueryClient() + const query = useQuery({ + queryKey: ['task', id], + queryFn: () => api.detail(id), + refetchInterval: 5000, + }) + const act = useMutation({ + mutationFn: (action: string) => api.action(id, action), + onSuccess: () => client.invalidateQueries({ queryKey: ['task', id] }), + }) + + if (query.isError) { + return ( +
+
+

Task

+
+ +
+

{String((query.error as Error).message)}

+
+
+
+ ) + } + if (!query.data) { + return ( +
+

reading /v1/ui/tasks/{id}…

+
+ ) + } + + const detail: Detail = query.data + const task = detail.task as Phased + const session = detail.session + const capture = session?.capture + const approval = session?.pending_approval + const decisions = decisionsOf(detail.events) + const standing = decisions.filter((d) => d.standing) + const constraints = standing.filter((d) => d.kind === 'constraint') + const effective = standing.filter((d) => d.kind !== 'constraint') + const superseded = decisions.filter((d) => !d.standing) + const acceptance = task.acceptance ?? [] + const shown = acceptance.slice(0, 4) + const lease = session?.lease_until ?? task.lease?.until + const activity = lastLine(capture?.text) + const entries = phaseEntries(detail.events) + + return ( +
+ {/* ── top area ──────────────────────────────────────────────────── */} +
+
+

{task.title || task.external_id || 'untitled task'}

+ {task.work_phase || 'frame'} phase + {task.state} +
+
+ + +
+
+ +
+ task id + {task.id} + epoch + {task.lease?.epoch || task.last_lease_epoch || '—'} + project + {task.project} + version + {task.version} +
+ + {act.isError &&

{String((act.error as Error).message)}

} + +
+ {/* ── what we're doing ────────────────────────────────────────── */} + +
+

goal

+

{task.description || task.title || 'No goal recorded.'}

+ +

+ acceptance {acceptance.length} +

+ {acceptance.length === 0 ? ( +

No acceptance criteria on the contract.

+ ) : ( + <> +
    + {shown.map((c) => ( +
  • {c}
  • + ))} +
+ {acceptance.length > shown.length && ( +
+ + +{acceptance.length - shown.length} more criteria + +
    + {acceptance.slice(shown.length).map((c) => ( +
  • {c}
  • + ))} +
+
+ )} + + )} + {task.quality_gate && ( +

+ quality gate {task.quality_gate} +

+ )} + + {effective.length > 0 && ( + <> +

effective human decisions

+
    + {effective.map((d) => ( +
  • + {d.kind} + {d.subject} + {d.value} + {time(d.at)} +
  • + ))} +
+ + )} + + {constraints.length > 0 && ( + <> +

active constraints

+
    + {constraints.map((d) => ( +
  • + constraint + {d.subject} + {d.value} + {time(d.at)} +
  • + ))} +
+ + )} + + {superseded.length > 0 && ( +
+ + {superseded.length} superseded {superseded.length === 1 ? 'decision' : 'decisions'} + +
    + {superseded.map((d) => ( +
  • + {d.kind} + {d.subject} + {d.value} + {time(d.at)} +
  • + ))} +
+
+ )} +
+
+ + {/* ── live execution ──────────────────────────────────────────── */} + + {session.agent_status} + + ) : ( + no session + ) + } + > +
+
+
+ worker / harness + {session?.harness_id || task.lease?.harness_id || task.last_harness_id || '—'} +
+
+ pane + {session?.pane_id || task.last_pane_id || '—'} + + {task.pane_state || 'unknown'} + +
+
+ lease remaining + {remaining(lease) ?? '—'} + + until {time(lease)} + +
+
+ last progress + {ago(capture?.at)} + + capture rev {capture?.revision ?? '—'} + +
+
+ + {/* Context occupancy is the one number this panel is supposed to + carry and the read model does not produce it. Say so rather + than compute a plausible-looking figure. */} + + +

+ current activity + + agent-supplied · pane capture{capture?.source ? ` · ${capture.source}` : ''} + +

+ {activity ? ( +

+ {activity} + + {time(capture?.at)} + +

+ ) : ( +

No capture from the owning worker.

+ )} + + {approval && ( +
+ pending approval +

{approval.summary}

+ {approval.command && {approval.command}} +
+ + {approval.pane_id} · rev {approval.capture_revision} + + + +
+
+ )} + +
+ + + Open live pane + + {task.handoff_ref && ( + + + Handoff + + )} + {detail.report_ref && ( + + + Report + + )} +
+ +
+
+
+ + {/* ── workflow ──────────────────────────────────────────────────── */} + ace-fca}> +
+ + {entries.length > 0 ? ( +
+ {entries.map((e) => ( + + {e.phase} entered {time(e.at)} + + ))} +
+ ) : ( +

+ No WorkPhaseChanged event in this task's log — the path shows the current phase only. +

+ )} +
+
+
+ ) +} + +/** Operator actions, exactly as the server declares them. An action that + * needs input the console cannot collect stays disabled and says what it + * wants — offering a button that will 409 is the dishonest option. */ +function ActionsMenu({ + actions, + pending, + run, +}: { + actions: Action[] + pending: boolean + run: (action: string) => void +}) { + return ( +
+ + Actions + +
+ {actions.map((a) => { + const blocked = !a.enabled || (a.needs?.length ?? 0) > 0 + return ( + + ) + })} +
+
+ ) +} diff --git a/web/src/screens/Tasks.css b/web/src/screens/Tasks.css new file mode 100644 index 0000000..09b1b3f --- /dev/null +++ b/web/src/screens/Tasks.css @@ -0,0 +1,202 @@ +/* Tasks list. Only what the shared shell does not already carry: the row + column geometry, the group disclosure, and the filter bar. */ + +.tasks-controls { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; +} + +.tasks-tabs { + display: flex; + gap: 2px; + flex-wrap: wrap; +} + +.tasks-tab { + background: none; + border: 0; + border-bottom: 2px solid transparent; + padding: 7px 11px; + color: var(--text-mid); + cursor: pointer; + transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease); +} + +.tasks-tab span { + font-size: 12px; + margin-left: 5px; +} + +.tasks-tab:hover { + color: var(--text-hi); +} + +.tasks-tab[aria-selected='true'] { + color: var(--text-hi); + border-bottom-color: var(--accent); +} + +.tasks-filters { + display: flex; + gap: 10px; + margin-left: auto; + flex-wrap: wrap; +} + +.tasks-filter { + display: flex; + align-items: center; + gap: 7px; +} + +.tasks-filter select { + font-family: var(--mono); + font-size: 12px; + color: var(--text-machine); + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + padding: 5px 8px; + cursor: pointer; +} + +.tasks-filter select:hover { + border-color: var(--line-hi); +} + +.page-head .count { + font-size: 12px; + padding: 2px 8px; + border-radius: 999px; + background: var(--bg-2); + border: 1px solid var(--line); +} + +/* ── group disclosure ────────────────────────────────────────────────── */ + +.tasks-group > summary { + cursor: pointer; + list-style: none; +} + +.tasks-group > summary::-webkit-details-marker { + display: none; +} + +.tasks-group:not([open]) > summary { + border-bottom: 0; +} + +.tasks-group > summary > svg:first-child { + color: var(--text-lo); +} + +.tasks-group > summary > svg:last-child { + margin-left: auto; + color: var(--text-lo); + transform: rotate(90deg); + transition: transform var(--fast) var(--ease); +} + +.tasks-group[open] > summary > svg:last-child { + transform: rotate(-90deg); +} + +/* ── row geometry ────────────────────────────────────────────────────── */ + +.task-row { + display: flex; + align-items: center; + gap: 18px; + color: inherit; +} + +.task-row > svg:last-child { + color: var(--text-lo); + flex: none; +} + +.task-row-main { + flex: 1 1 220px; + min-width: 0; + display: flex; + flex-direction: column; +} + +.task-row-main .row-title, +.task-row-main .row-sub { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.task-row-phase { + flex: none; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.task-row-phase-name { + font-size: 10px; + letter-spacing: 0.04em; +} + +/* The compact path carries no labels, so the connector no longer needs to + sit above a name. */ +.tasks-phases .phase-step { + min-width: 0; +} + +.tasks-phases .phase-link { + width: 14px; + margin-bottom: 0; +} + +.task-row-worker { + flex: none; + width: 150px; + display: flex; + flex-direction: column; + font-size: 13px; +} + +.task-row-when { + flex: none; + width: 118px; + display: flex; + flex-direction: column; + font-size: 12px; +} + +.task-row-status { + flex: none; + display: flex; + gap: 6px; + justify-content: flex-end; + min-width: 130px; +} + +@media (max-width: 900px) { + .task-row-phase, + .task-row-when { + display: none; + } + + .task-row-worker { + width: 110px; + } +} + +@media (max-width: 640px) { + .task-row-worker { + display: none; + } + + .tasks-filters { + margin-left: 0; + } +} diff --git a/web/src/screens/Tasks.tsx b/web/src/screens/Tasks.tsx new file mode 100644 index 0000000..45fadef --- /dev/null +++ b/web/src/screens/Tasks.tsx @@ -0,0 +1,329 @@ +import { useMemo, useState } from 'react' +import { Link } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { api } from '../api/client' +import type { Overview, Task } from '../api/types' +import { Chip, Empty, EndpointGap, M, PHASES, PhasePath } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './Tasks.css' + +type GroupKey = 'attention' | 'running' | 'waiting' | 'completed' | 'failed' + +const GROUPS: { key: GroupKey; title: string; icon: string; open: boolean }[] = [ + { key: 'attention', title: 'Needs attention', icon: 'alert', open: true }, + { key: 'running', title: 'Running', icon: 'play', open: true }, + { key: 'waiting', title: 'Waiting / queued', icon: 'queue', open: true }, + { key: 'completed', title: 'Completed', icon: 'check', open: false }, + { key: 'failed', title: 'Failed', icon: 'x', open: false }, +] + +function groupOf(task: Task): GroupKey { + switch (task.state) { + case 'needs_attention': + case 'blocked': + return 'attention' + case 'leased': + return 'running' + case 'completed': + return 'completed' + case 'failed': + return 'failed' + default: + return 'waiting' + } +} + +/** The harness holding the lease, or the last one that did. Nothing is + * invented: a task that never ran reports no worker. */ +function harnessOf(task: Task) { + return task.lease?.harness_id || task.last_harness_id || '' +} + +function stateTone(task: Task) { + if (task.state === 'failed' || task.state === 'blocked') return 'fault' as const + if (task.state === 'needs_attention') return 'warn' as const + if (task.state === 'completed') return 'done' as const + if (task.state === 'leased') return 'accent' as const + return undefined +} + +function ago(at?: string) { + if (!at) return '' + const ms = Date.now() - Date.parse(at) + if (!Number.isFinite(ms)) return '' + return `${span(ms)} ago` +} + +function span(ms: number) { + const s = Math.max(0, Math.round(ms / 1000)) + if (s < 60) return `${s}s` + if (s < 3600) return `${Math.round(s / 60)}m` + if (s < 86400) return `${Math.round(s / 3600)}h` + return `${Math.round(s / 86400)}d` +} + +/** The one time value a task actually carries per state. There is no + * per-task updated_at on the overview, so this never claims one. */ +function timing(task: Task): { label: string; value: string } | undefined { + if (task.lease?.until) { + const left = Date.parse(task.lease.until) - Date.now() + return left > 0 + ? { label: 'lease', value: `${span(left)} left` } + : { label: 'lease', value: `expired ${span(-left)} ago` } + } + if (task.blocked_at) return { label: 'blocked', value: ago(task.blocked_at) } + if (task.next_retry_at) { + const left = Date.parse(task.next_retry_at) - Date.now() + return { label: 'retry', value: left > 0 ? `in ${span(left)}` : 'due' } + } + if (task.due) return { label: 'due', value: task.due.slice(0, 10) } + return undefined +} + +function unique(values: (string | undefined)[]) { + return [...new Set(values.filter((value): value is string => !!value))].sort() +} + +function Select({ + label, + value, + options, + onChange, +}: { + label: string + value: string + options: string[] + onChange: (next: string) => void +}) { + return ( + + ) +} + +function Row({ task, agentStatus }: { task: Task; agentStatus?: string }) { + const harness = harnessOf(task) + const when = timing(task) + return ( + + + {task.title || task.external_id || 'untitled task'} + + {task.project} · {task.external_id || task.id} + + + + + + {task.lifecycle_phase || '—'} + + + + {harness ? ( + <> + {harness} + {task.last_pane_id && {task.last_pane_id}} + + ) : ( + unassigned + )} + + + + {when && ( + <> + {when.label} + {when.value} + + )} + + + + {agentStatus && {agentStatus}} + {task.block_reason || task.state} + + + + + ) +} + +function Group({ + title, + icon, + open, + tasks, + statusFor, +}: { + title: string + icon: string + open: boolean + tasks: Task[] + statusFor: (task: Task) => string | undefined +}) { + return ( +
0}> + + +

{title}

+ {tasks.length} + +
+ {tasks.length ? ( +
+ {tasks.map((task) => ( + + ))} +
+ ) : ( + + )} +
+ ) +} + +export function Tasks() { + const { data, isLoading, error } = useQuery({ + queryKey: ['overview'], + queryFn: api.overview, + refetchInterval: 5000, + }) + + const [project, setProject] = useState('') + const [phase, setPhase] = useState('') + const [worker, setWorker] = useState('') + const [attention, setAttention] = useState('') + const [group, setGroup] = useState('') + + const tasks = useMemo(() => data?.tasks ?? [], [data]) + + /** Agent status comes from the live session, matched by pane. It is an + * agent claim, so it stays a separate chip from orchestra's own state. */ + const statusFor = useMemo(() => { + const byPane = new Map((data?.sessions ?? []).map((s) => [s.pane_id, s])) + return (task: Task) => + task.state === 'leased' ? byPane.get(task.last_pane_id)?.agent_status : undefined + }, [data]) + + const filtered = tasks.filter((task) => { + if (project && task.project !== project) return false + if (phase && task.lifecycle_phase !== phase) return false + if (worker && harnessOf(task) !== worker) return false + if (attention === 'needs' && groupOf(task) !== 'attention') return false + if (attention === 'clear' && groupOf(task) === 'attention') return false + if (attention && attention !== 'needs' && attention !== 'clear' && task.block_reason !== attention) + return false + return true + }) + + const counts = (key: GroupKey) => filtered.filter((task) => groupOf(task) === key).length + const shown = GROUPS.filter((g) => !group || g.key === group) + + return ( +
+
+

Tasks

+

All work under orchestration.

+ {tasks.length} +
+ +
+
+ + {GROUPS.map((g) => ( + + ))} +
+
+ + task.block_reason))]} + onChange={setAttention} + /> +
+
+ + {error && ( +
+

/v1/ui/overview failed: {(error as Error).message}

+
+ )} + + {isLoading && !data && ( +
+

loading /v1/ui/overview…

+
+ )} + + {data && !tasks.length && ( +
+ +

Orchestra has no work on record. Ingest a task to start.

+
+
+ )} + + {data && + tasks.length > 0 && + shown.map((g) => ( + groupOf(task) === g.key)} + statusFor={statusFor} + /> + ))} + + {data && tasks.length > 0 && ( +
+ +
+ )} +
+ ) +} diff --git a/web/src/screens/Terminal.css b/web/src/screens/Terminal.css new file mode 100644 index 0000000..1619076 --- /dev/null +++ b/web/src/screens/Terminal.css @@ -0,0 +1,192 @@ +/* The live pane. Tokens only — nothing here redefines the ethos ramp. */ + +.term { + min-height: 0; +} + +.term-head { + align-items: center; +} + +.term-identity { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 12px; +} + +.term-sep { + color: var(--text-lo); +} + +.term-controls { + margin-left: auto; + display: flex; + align-items: center; + gap: 8px; +} + +.term-mode { + gap: 6px; +} + +.term-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 320px; + gap: 16px; + align-items: start; +} + +/* The frame. Depth from a hairline and a surface step, never from glass. */ +.term-frame { + background: var(--bg-0); + border: 1px solid var(--line-hi); + border-radius: var(--r-md); + overflow: hidden; + min-width: 0; +} + +.term-frame[data-lost='true'] { + border-color: rgba(224, 108, 96, 0.42); +} + +.term-bar { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 12px; + background: var(--bg-2); + border-bottom: 1px solid var(--line); + font-size: 11px; +} + +.term-bar-right { + margin-left: auto; + display: flex; + gap: 14px; +} + +/* Exact pane characters: no wrapping, no reflow, no prettifying. */ +.term-text { + margin: 0; + padding: 14px 16px; + height: min(62vh, 720px); + overflow: auto; + white-space: pre; + tab-size: 8; + font-size: 13px; + line-height: 1.35; + color: #cfc6b4; + background: var(--bg-0); +} + +.term-text:focus-visible { + outline-offset: -2px; +} + +.term-foot { + display: flex; + align-items: center; + gap: 12px; + min-height: 0; + padding: 0 12px; +} + +.term-foot:not(:empty) { + padding: 8px 12px; + border-top: 1px solid var(--line); + background: var(--bg-1); +} + +.term-jump { + height: 28px; + font-size: 12px; +} + +.term-warn { + font-size: 12px; + color: #e06c60; +} + +.term-side { + display: flex; + flex-direction: column; + gap: 14px; + min-width: 0; +} + +.term-fields, +.term-lost { + display: flex; + flex-direction: column; + gap: 10px; +} + +.term-lost p { + margin: 0 0 2px; + color: var(--text-mid); +} + +.term-field { + display: flex; + flex-direction: column; + gap: 2px; +} + +.term-field-value { + font-size: 12px; + color: var(--text-mid); + overflow-wrap: anywhere; +} + +.term-health { + display: inline-flex; + align-items: center; + gap: 7px; +} + +.term-link:hover { + color: var(--accent-hi); +} + +.term-resubmit p { + margin: 8px 0 0; + font-size: 12px; + color: var(--text-mid); +} + +.term-events { + display: flex; + flex-direction: column; + gap: 7px; +} + +.term-event { + display: flex; + gap: 10px; + font-size: 12px; + color: var(--text-mid); +} + +.term-muted { + margin: 0; + color: var(--text-lo); +} + +@media (max-width: 1040px) { + .term-layout { + grid-template-columns: minmax(0, 1fr); + } +} + +@media (max-width: 640px) { + .term-controls { + margin-left: 0; + width: 100%; + } + + .term-text { + height: 54vh; + font-size: 12px; + } +} diff --git a/web/src/screens/Terminal.tsx b/web/src/screens/Terminal.tsx new file mode 100644 index 0000000..87c8864 --- /dev/null +++ b/web/src/screens/Terminal.tsx @@ -0,0 +1,320 @@ +import { useEffect, useRef, useState } from 'react' +import { Link, useParams } from 'react-router-dom' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { api } from '../api/client' +import type { Capture, Detail } from '../api/types' +import { Chip, Dot, Empty, EndpointGap, M, Panel } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './Terminal.css' + +/** This is the screen that must not lie. Everything inside the frame is the + * pane's own bytes; everything outside it is state Orchestra can prove. */ + +function ago(at?: string) { + if (!at) return '—' + const seconds = Math.max(0, Math.round((Date.now() - new Date(at).getTime()) / 1000)) + if (seconds < 60) return `${seconds}s ago` + if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago` + return `${Math.floor(seconds / 3600)}h ago` +} + +function clock(at?: string) { + return at ? new Date(at).toISOString().slice(11, 19) : '—' +} + +function until(at?: string) { + if (!at) return '—' + const seconds = Math.round((new Date(at).getTime() - Date.now()) / 1000) + if (seconds <= 0) return 'expired' + const m = Math.floor(seconds / 60) + return `${String(m).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}` +} + +function Field({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value} +
+ ) +} + +export function Terminal() { + const { id = '' } = useParams() + const queries = useQueryClient() + const detail = useQuery({ + queryKey: ['detail', id], + queryFn: () => api.detail(id), + refetchInterval: 3000, + }) + + // The last frame we actually received, kept across refreshes so a lost pane + // still shows what was true when it was lost instead of an empty box. + const [frame, setFrame] = useState() + const [lostAt, setLostAt] = useState() + const [copied, setCopied] = useState(false) + const [pinned, setPinned] = useState(true) + const view = useRef(null) + + const task = detail.data?.task + const session = detail.data?.session + const capture = session?.capture + + useEffect(() => { + if (capture) { + setFrame(capture) + setLostAt(undefined) + return + } + // A leased task with no capture is a lost pane, not an empty one. + if (detail.isSuccess && task?.state === 'leased') { + setLostAt((was) => was ?? new Date().toISOString()) + } + }, [capture, detail.isSuccess, task?.state]) + + // Scrollback inspection must not disturb the live view: we only follow the + // tail while the operator is already sitting at the tail. + useEffect(() => { + const el = view.current + if (el && pinned) el.scrollTop = el.scrollHeight + }, [frame?.revision, pinned]) + + const resubmit = useMutation({ + mutationFn: () => api.action(id, 'resubmit'), + onSuccess: (next) => queries.setQueryData(['detail', id], next), + }) + + const canResubmit = detail.data?.actions.find((a) => a.id === 'resubmit')?.enabled ?? false + const lost = Boolean(lostAt) && Boolean(frame) + const status = session?.agent_status || task?.pane_state || 'unknown' + const health = lost ? 'error' : status === 'blocked' ? 'degraded' : status ? 'healthy' : 'unknown' + + if (detail.isLoading) { + return ( +
+
+

Terminal

+
+ +
Reading the pane…
+
+
+ ) + } + + if (detail.isError || !task) { + return ( +
+
+

Terminal

+
+ + +

+ {id} is not in the store, so there is no pane to show. +

+
+
+
+ ) + } + + return ( +
+
+

Terminal — live pane

+ {lost ? Pane lost : capture ? Live : No session} + + {session?.harness_id || task.last_harness_id || 'no worker'} + / + {session?.pane_id || task.last_pane_id || 'no pane'} + +
+ + Read-only + + + +
+
+ +
+
+
+ {session?.pane_id || task.last_pane_id || '—'} + + rev {frame ? frame.revision : '—'} + {frame?.source || '—'} + {clock(frame?.at)} + +
+ {frame ? ( +
 {
+                const el = event.currentTarget
+                setPinned(el.scrollHeight - el.scrollTop - el.clientHeight < 24)
+              }}
+            >
+              {frame.text}
+            
+ ) : ( + +

+ This task holds no leased session, so no worker is publishing pane text for{' '} + {task.id}. +

+
+ )} +
+ {frame?.truncated && frame truncated by the worker} + {!pinned && ( + + )} +
+
+ +
+ {lost && ( + +
+

+ The frame above is the last one Orchestra received. It is history, not the + present. +

+ {clock(frame?.at)} UTC} /> + {clock(lostAt)} UTC} /> + + the lease still runs until {clock(task.lease.until)} UTC; nothing is + rerouted before it expires + + ) : ( + no lease is held, so this task can be routed again immediately + ) + } + /> +
+
+ )} + + +
+ {task.id}} /> + {session?.harness_id || task.last_harness_id || '—'}} /> + {session?.pane_id || task.last_pane_id || '—'}} /> + {task.lease?.epoch || '—'}} /> + + {until(task.lease.until)} left · until {clock(task.lease.until)} UTC + + ) : ( + none + ) + } + /> + {frame?.source || '—'}} /> + {frame ? frame.revision : '—'}} /> + {ago(frame?.at)}} /> + read-only} /> +
+
+ + +
+ + + {status} + + } + /> + {ago(task.last_session?.checked_at || frame?.at)}} /> + {(session?.blocker || task.blocker) && ( + {session?.blocker || task.blocker}} /> + )} +
+
+ + + +
+ +

+ Presses Enter once on input Orchestra placed in this pane. It sends no other key and + changes no lifecycle state. +

+ {resubmit.isError && ( +

{(resubmit.error as Error).message}

+ )} +
+
+ + +
+ {(detail.data?.events ?? []).slice(-5).reverse().map((event) => ( +
+ {clock(event.at)} + {event.type} +
+ ))} + {!detail.data?.events.length &&

No events recorded yet.

} +
+
+
+
+
+ ) +} diff --git a/web/src/screens/Workers.css b/web/src/screens/Workers.css new file mode 100644 index 0000000..9bec391 --- /dev/null +++ b/web/src/screens/Workers.css @@ -0,0 +1,198 @@ +/* Workers screen. Only what the shared sheet does not already carry: + the two-column split, the label/value field row, and the observations ring. */ + +.workers-split { + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); + gap: 14px; + align-items: start; +} + +.workers-detail { + min-width: 0; +} + +.workers .worker-row { + cursor: pointer; +} + +.workers .worker-row[aria-selected='true'] { + background: var(--bg-2); + box-shadow: inset 3px 0 0 var(--accent); +} + +.workers .status-cell { + display: inline-flex; + align-items: center; + gap: 8px; + white-space: nowrap; +} + +.workers .worker-id { + font-size: 15px; + color: var(--accent-hi); +} + +.workers .dim { + color: var(--text-lo); +} + +.workers .task-link { + display: inline-flex; + align-items: baseline; + gap: 8px; + min-width: 0; +} + +.workers .task-link .mono { + color: var(--accent-hi); +} + +.workers .task-link:hover .mono { + text-decoration: underline; +} + +.workers .task-title { + color: var(--text-mid); + font-size: 13px; +} + +/* A human label, a machine value, on one line. Law 1 drawn as geometry. */ +.workers .field { + display: flex; + gap: 16px; + align-items: baseline; + padding: 5px 0; +} + +.workers .field-name { + flex: none; + width: 150px; + color: var(--text-mid); + font-size: 13px; +} + +.workers .field-value { + min-width: 0; + display: flex; + align-items: baseline; + gap: 8px; + flex-wrap: wrap; +} + +.workers .chips { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 12px; +} + +.workers .steps { + margin: 0 0 12px; + padding-left: 20px; + color: var(--text-mid); + display: flex; + flex-direction: column; + gap: 4px; +} + +.workers .reg-lead { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 10px; + color: var(--text-hi); +} + +.workers .reg-lead svg { + color: var(--accent); +} + +/* ── the observations ring ─────────────────────────────────────────── + One distinct failure per row, with its repeat count kept whole. A count + of 41 means something looped 41 times; that number is the finding, so it + gets weight instead of a parenthesis. */ + +.workers .obs-latest { + margin: 0 0 12px; + border-style: solid; + border-color: var(--line-hi); + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.workers .obs { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 8px; +} + +.workers .obs-item { + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + padding: 10px 12px; +} + +.workers .obs-head { + display: flex; + gap: 10px; + align-items: baseline; +} + +.workers .obs-count { + flex: none; + font-size: 15px; + color: var(--text-hi); + min-width: 42px; + text-align: right; +} + +/* Repeats are the signal that something is stuck rather than merely broken. */ +.workers .obs-count[data-repeat='true'] { + color: var(--accent-hi); +} + +.workers .obs-msg { + min-width: 0; + color: var(--text-machine); + font-size: 12.5px; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.workers .obs-when { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 8px; + margin-top: 8px; + padding-left: 52px; + font-size: 12px; +} + +.workers .obs-when .mono { + color: var(--text-lo); +} + +@media (max-width: 1100px) { + .workers-split { + grid-template-columns: 1fr; + } + + .workers .obs-when { + padding-left: 0; + } + + .workers .field { + flex-direction: column; + gap: 2px; + } + + .workers .field-name { + width: auto; + } +} diff --git a/web/src/screens/Workers.tsx b/web/src/screens/Workers.tsx new file mode 100644 index 0000000..989bd56 --- /dev/null +++ b/web/src/screens/Workers.tsx @@ -0,0 +1,362 @@ +import { useState } from 'react' +import { Link } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { api } from '../api/client' +import type { Task, Worker, WorkerHealth } from '../api/types' +import { Chip, Dot, Empty, EndpointGap, M, Panel } from '../components/Primitives' +import type { Health } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './Workers.css' + +/** The coordinator's Go structs carry `observations` on WorkerHealth; the + * shared type file has not caught up and is owned elsewhere, so the shape is + * narrowed here rather than guessed at the call site. */ +interface Observation { + message: string + count: number + first: string + last: string +} +type Health2 = WorkerHealth & { observations?: Observation[] } + +/** Go marshals a zero time.Time as year 1 — `omitempty` does not omit a + * struct. Treating that as a real timestamp would print "2025y ago". */ +function absent(iso?: string) { + return !iso || iso.startsWith('0001-') +} + +function ago(iso?: string) { + if (absent(iso)) return '—' + const s = Math.max(0, (Date.now() - new Date(iso as string).getTime()) / 1000) + if (s < 60) return `${Math.floor(s)}s ago` + if (s < 3600) return `${Math.floor(s / 60)}m ago` + if (s < 86400) return `${Math.floor(s / 3600)}h ago` + return `${Math.floor(s / 86400)}d ago` +} + +function clock(iso?: string) { + if (absent(iso)) return '—' + return new Date(iso as string).toISOString().slice(11, 19) + ' UTC' +} + +function health(w: Worker): Health { + if (!w.online) return 'unknown' + if (w.health.herdr_status === 'unreachable') return 'error' + if (w.health.herdr_status === 'unknown') return 'unknown' + return w.health.last_error ? 'degraded' : 'healthy' +} + +function statusLabel(w: Worker) { + if (!w.online) return 'Offline' + if (w.health.herdr_status === 'unreachable') return 'Backend unreachable' + if (w.health.active_task_id) return 'Busy' + return w.health.last_error ? 'Degraded' : 'Idle' +} + +/** The ring is the evidence. A message with a count of 41 means a loop ran 41 + * times, and that number is the whole point — it is never collapsed away. */ +function Observations({ items }: { items: Observation[] }) { + return ( +
    + {items.map((o) => ( +
  • +
    + 1} title={`${o.count} occurrences`}> + ×{o.count} + + {o.message} +
    +
    + first + {clock(o.first)} + last + {clock(o.last)} + ({ago(o.last)}) +
    +
  • + ))} +
+ ) +} + +function Field({ name, children }: { name: string; children: React.ReactNode }) { + return ( +
+ {name} + {children} +
+ ) +} + +function Detail({ worker, tasks }: { worker: Worker; tasks: Task[] }) { + const h = worker.health as Health2 + const obs = h.observations ?? [] + const task = tasks.find((t) => t.id === h.active_task_id) + + return ( +
+ +
+

{worker.id}

+ + {statusLabel(worker)} + +
+
+ + {h.backend || 'unknown'} + + + {h.herdr_status} + + + {clock(h.checked_at)} ({ago(h.checked_at)}) + + + {clock(worker.last_seen)}{' '} + ({ago(worker.last_seen)}) + + + {worker.capacity} + + {worker.address && ( + + {worker.address} + + )} +
+
+ + +
+ {h.active_task_id ? ( + <> + + + {h.active_task_id} + {task?.title && {task.title}} + + + {task?.lifecycle_phase && ( + + {task.lifecycle_phase} + + )} + + {h.active_pane_id || '—'} + + + ) : ( +

No task leased to this worker.

+ )} +
+
+ + +
+ + {worker.build?.revision || 'unknown'} + + + {worker.build?.time || 'unknown'} + + {worker.build?.dirty === 'true' && ( + + dirty — built from uncommitted changes + + )} +
+
+ + reported by worker}> +
+ {worker.supported_projects?.length ? ( +
+ {worker.supported_projects.map((p) => ( + + {p} + + ))} +
+ ) : ( +

The worker declared no projects, so nothing routes to it.

+ )} + +
+
+ + bounded ring of distinct failures} + > + {obs.length ? ( +
+ {h.last_error && ( +

+ latest: {h.last_error} + {!absent(h.error_at) && ` — ${clock(h.error_at)}`} +

+ )} + +
+ ) : ( + +

The worker has reported nothing to explain. Repeats would collapse here with a count.

+
+ )} +
+
+ ) +} + +export function Workers() { + const workers = useQuery({ queryKey: ['workers'], queryFn: api.workers, refetchInterval: 5000 }) + const overview = useQuery({ queryKey: ['overview'], queryFn: api.overview, refetchInterval: 5000 }) + const [picked, setPicked] = useState() + + const list = workers.data ?? [] + const selected = list.find((w) => w.id === picked) ?? list[0] + const online = list.filter((w) => w.online) + const busy = online.filter((w) => w.health.active_task_id) + const faulted = list.filter((w) => health(w) === 'error' || health(w) === 'degraded') + const capacity = online.reduce((n, w) => n + (w.capacity || 0), 0) + + return ( +
+
+
+

Workers

+

Where work can run, and whether that machinery is usable.

+
+
+ + {workers.isError ? ( + + +

GET /v1/federation/workers failed. Nothing can be leased until it answers.

+
+
+ ) : ( + <> +
+
+ Online + {online.length} +
+
+ Busy + {busy.length} +
+
+ Offline + {list.length - online.length} +
+
+ Faulted + {faulted.length} +
+
+ Free capacity + {capacity - busy.length} +
+
+ +
+ + {list.length === 0 ? ( + +

+ A worker registers itself: it starts with coordinator credentials, announces its + id, revision, harnesses, projects and capacity, then keeps liveness with a + heartbeat. Nothing here can create one. +

+
+ ) : ( +
+ + + + + + + + + + + + + {list.map((w) => { + const h = w.health as Health2 + return ( + setPicked(w.id)} + > + + + + + + + + ) + })} + +
WorkerStatusBackendCurrent taskPaneLast seen
{w.id} + + {statusLabel(w)} + + + + {h.backend || 'unknown'} + + + {h.active_task_id ? ( + e.stopPropagation()} + > + {h.active_task_id} + + ) : ( + + )} + {h.active_pane_id || '—'}{ago(w.last_seen)}
+
+ )} +
+ +
+ {selected && } +
+
+ + +
+

+ Workers register themselves; the console only reads + what they announce. +

+
    +
  1. The worker starts with coordinator credentials.
  2. +
  3. It announces id, revision, harnesses, projects and capacity.
  4. +
  5. The coordinator records it; heartbeats keep liveness.
  6. +
+ +
+
+ + )} +
+ ) +} diff --git a/web/src/shell/CommandPalette.tsx b/web/src/shell/CommandPalette.tsx new file mode 100644 index 0000000..9eae9c2 --- /dev/null +++ b/web/src/shell/CommandPalette.tsx @@ -0,0 +1,62 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import { NAV } from './Shell' + +/** The shared command surface. It navigates and nothing else: an operator + * console should not hide state changes behind a search box. */ +export function CommandPalette({ + onClose, + onNavigate, +}: { + onClose: () => void + onNavigate: (to: string) => void +}) { + const [query, setQuery] = useState('') + const [active, setActive] = useState(0) + const input = useRef(null) + + useEffect(() => input.current?.focus(), []) + + const matches = useMemo(() => { + const needle = query.trim().toLowerCase() + return NAV.filter((item) => !needle || item.label.toLowerCase().includes(needle)) + }, [query]) + + useEffect(() => setActive(0), [query]) + + return ( +
+
event.stopPropagation()}> + setQuery(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'ArrowDown') setActive((i) => Math.min(i + 1, matches.length - 1)) + if (event.key === 'ArrowUp') setActive((i) => Math.max(i - 1, 0)) + if (event.key === 'Enter' && matches[active]) onNavigate(matches[active].to) + }} + /> +
+ {matches.map((item, i) => ( + + ))} + {matches.length === 0 && ( +

+ Nothing matches that. +

+ )} +
+
+
+ ) +} diff --git a/web/src/shell/Shell.tsx b/web/src/shell/Shell.tsx new file mode 100644 index 0000000..72ab172 --- /dev/null +++ b/web/src/shell/Shell.tsx @@ -0,0 +1,94 @@ +import { useEffect, useState } from 'react' +import { NavLink, Outlet, useNavigate } from 'react-router-dom' +import { Icon } from '../components/Icon' +import { CommandPalette } from './CommandPalette' + +export const NAV = [ + { to: '/', icon: 'home', label: 'Dashboard', end: true }, + { to: '/tasks', icon: 'task', label: 'Tasks' }, + { to: '/decisions', icon: 'decision', label: 'Decisions' }, + { to: '/workers', icon: 'worker', label: 'Workers' }, + { to: '/projects', icon: 'project', label: 'Projects' }, + { to: '/review', icon: 'review', label: 'Review' }, + { to: '/settings', icon: 'settings', label: 'Settings', secondary: true }, +] + +/** Machine readouts belong in the top bar on desktop. On mobile they relocate + * into the detail screens rather than disappearing: honesty moves, it is + * never deleted. */ +function Clock() { + const [now, setNow] = useState(() => new Date()) + useEffect(() => { + const id = setInterval(() => setNow(new Date()), 1000) + return () => clearInterval(id) + }, []) + return {now.toISOString().slice(11, 19)} UTC +} + +export function Shell({ revision, live }: { revision?: string; live?: boolean }) { + const [palette, setPalette] = useState(false) + const navigate = useNavigate() + + useEffect(() => { + const onShortcut = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { + event.preventDefault() + setPalette((open) => !open) + } + if (event.key === 'Escape') setPalette(false) + } + window.addEventListener('keydown', onShortcut) + return () => window.removeEventListener('keydown', onShortcut) + }, []) + + return ( +
+ +
+
+ orchestra + +
+ {revision && {revision.slice(0, 7)}} + + {' '} + {live ? 'Live' : 'Unknown'} + + +
+
+ +
+ {palette && ( + setPalette(false)} + onNavigate={(to) => { + setPalette(false) + navigate(to) + }} + /> + )} +
+ ) +} diff --git a/web/src/style.css b/web/src/style.css index 48e9eae..798176d 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -1,4 +1,4 @@ -:root { +.legacy { color-scheme: dark; font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #eef2f0; @@ -54,7 +54,7 @@ body { var(--bg); } -body.modal-open { +.legacy body.modal-open { overflow: hidden; } @@ -62,12 +62,12 @@ button, input, textarea, select, -summary { +.legacy summary { font: inherit; } button, -.button-link { +.legacy .button-link { display: inline-flex; min-height: 38px; align-items: center; @@ -86,18 +86,18 @@ button, } button:hover:not(:disabled), -.button-link:hover { +.legacy .button-link:hover { border-color: var(--accent-strong); background: var(--accent-strong); transform: translateY(-1px); } button:active:not(:disabled), -.button-link:active { +.legacy .button-link:active { transform: translateY(0); } -button:disabled { +.legacy button:disabled { opacity: 0.42; cursor: not-allowed; } @@ -107,18 +107,18 @@ input:focus-visible, textarea:focus-visible, select:focus-visible, summary:focus-visible, -a:focus-visible { +.legacy a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } -a { +.legacy a { color: inherit; } input, textarea, -select { +.legacy select { width: 100%; border: 1px solid var(--line-strong); border-radius: var(--radius-sm); @@ -129,12 +129,12 @@ select { } input, -select { +.legacy select { min-height: 42px; padding: 9px 11px; } -textarea { +.legacy textarea { min-height: 116px; padding: 11px; line-height: 1.55; @@ -143,24 +143,24 @@ textarea { input:hover, textarea:hover, -select:hover { +.legacy select:hover { border-color: rgba(221, 236, 231, 0.23); } input:focus, textarea:focus, -select:focus { +.legacy select:focus { border-color: var(--accent-line); background: #0f1619; box-shadow: 0 0 0 3px rgba(86, 200, 139, 0.08); } input::placeholder, -textarea::placeholder { +.legacy textarea::placeholder { color: #53615e; } -select { +.legacy select { color: var(--text-soft); cursor: pointer; } @@ -171,17 +171,17 @@ kbd, .task-short-id, pre, code, -time { +.legacy time { font-family: var(--mono); font-feature-settings: "tnum" 1, "zero" 1; } -.icon { +.legacy .icon { display: block; flex: none; } -.sr-only { +.legacy .sr-only { position: absolute; width: 1px; height: 1px; @@ -195,12 +195,12 @@ time { /* Shell */ -.app-shell { +.legacy .app-shell { min-height: 100vh; padding-left: 224px; } -.sidebar { +.legacy .sidebar { position: fixed; inset: 0 auto 0 0; z-index: 20; @@ -213,7 +213,7 @@ time { backdrop-filter: blur(18px); } -.brand { +.legacy .brand { display: flex; height: 64px; align-items: center; @@ -224,7 +224,7 @@ time { text-decoration: none; } -.logo-symbol { +.legacy .logo-symbol { position: relative; display: block; width: 25px; @@ -232,7 +232,7 @@ time { flex: none; } -.logo-symbol i { +.legacy .logo-symbol i { position: absolute; display: block; width: 7px; @@ -242,23 +242,23 @@ time { box-shadow: 0 0 13px rgba(142, 224, 178, 0.2); } -.logo-symbol i:nth-child(1) { +.legacy .logo-symbol i:nth-child(1) { top: 0; left: 0; } -.logo-symbol i:nth-child(2) { +.legacy .logo-symbol i:nth-child(2) { right: 0; top: 9px; } -.logo-symbol i:nth-child(3) { +.legacy .logo-symbol i:nth-child(3) { bottom: 0; left: 0; } .logo-symbol::before, -.logo-symbol::after { +.legacy .logo-symbol::after { position: absolute; left: 6px; width: 14px; @@ -268,45 +268,45 @@ time { content: ""; } -.logo-symbol::before { +.legacy .logo-symbol::before { top: 6px; transform: rotate(30deg); } -.logo-symbol::after { +.legacy .logo-symbol::after { bottom: 6px; transform: rotate(-30deg); } .brand > span:last-child, -.login-brand > span:last-child { +.legacy .login-brand > span:last-child { display: grid; gap: 1px; } .brand b, -.login-brand b { +.legacy .login-brand b { color: var(--text); font-size: 14px; letter-spacing: -0.02em; } .brand small, -.login-brand small { +.legacy .login-brand small { color: var(--text-muted); font: 9px var(--mono); letter-spacing: 0.13em; text-transform: uppercase; } -.primary-nav { +.legacy .primary-nav { display: grid; gap: 5px; margin-top: 24px; } .nav-heading, -.menu-label { +.legacy .menu-label { margin: 0 10px 8px; color: var(--text-muted); font-size: 9px; @@ -315,7 +315,7 @@ time { text-transform: uppercase; } -.primary-nav a { +.legacy .primary-nav a { position: relative; display: grid; min-height: 42px; @@ -332,18 +332,18 @@ time { transition: color 140ms var(--ease), background 140ms var(--ease), border-color 140ms var(--ease); } -.primary-nav a:hover { +.legacy .primary-nav a:hover { color: var(--text-soft); background: var(--surface-2); } -.primary-nav a.active { +.legacy .primary-nav a.active { border-color: var(--accent-line); color: var(--accent-strong); background: var(--accent-wash); } -.primary-nav a.active::before { +.legacy .primary-nav a.active::before { position: absolute; left: -15px; width: 3px; @@ -353,7 +353,7 @@ time { content: ""; } -.nav-count { +.legacy .nav-count { min-width: 24px; padding: 2px 5px; border-radius: 999px; @@ -363,12 +363,12 @@ time { text-align: center; } -.nav-count.attention { +.legacy .nav-count.attention { color: var(--amber); background: var(--amber-wash); } -.sidebar-status { +.legacy .sidebar-status { display: flex; align-items: flex-start; gap: 10px; @@ -379,23 +379,23 @@ time { background: var(--surface-1); } -.sidebar-status > span:last-child { +.legacy .sidebar-status > span:last-child { display: grid; min-width: 0; gap: 3px; } -.sidebar-status b { +.legacy .sidebar-status b { color: var(--text-soft); font-size: 10px; } -.sidebar-status small { +.legacy .sidebar-status small { color: var(--text-muted); font-size: 9px; } -.signal { +.legacy .signal { display: inline-block; width: 7px; height: 7px; @@ -406,19 +406,19 @@ time { box-shadow: 0 0 0 4px var(--red-wash); } -.signal.online { +.legacy .signal.online { background: var(--accent); box-shadow: 0 0 0 4px var(--accent-wash); } -.sidebar-foot { +.legacy .sidebar-foot { padding: 13px 9px 0; color: #4e5a57; font: 8px var(--mono); letter-spacing: 0.1em; } -.topbar { +.legacy .topbar { position: fixed; inset: 0 0 auto 224px; z-index: 18; @@ -432,7 +432,7 @@ time { backdrop-filter: blur(18px); } -.topbar-context { +.legacy .topbar-context { display: flex; min-width: 0; align-items: center; @@ -441,7 +441,7 @@ time { font-size: 11px; } -.topbar-context b { +.legacy .topbar-context b { overflow: hidden; color: var(--text-soft); font-weight: 600; @@ -452,7 +452,7 @@ time { .command-trigger, .account-trigger, .button-secondary, -.icon-button { +.legacy .icon-button { border-color: var(--line-strong); color: var(--text-soft); background: var(--surface-1); @@ -461,13 +461,13 @@ time { .command-trigger:hover:not(:disabled), .account-trigger:hover:not(:disabled), .button-secondary:hover:not(:disabled), -.icon-button:hover:not(:disabled) { +.legacy .icon-button:hover:not(:disabled) { border-color: rgba(221, 236, 231, 0.24); color: var(--text); background: var(--surface-3); } -.command-trigger { +.legacy .command-trigger { min-height: 34px; margin-left: auto; padding: 6px 8px 6px 10px; @@ -476,7 +476,7 @@ time { } .command-trigger kbd, -.page-actions kbd { +.legacy .page-actions kbd { padding: 3px 5px; border: 1px solid var(--line-strong); border-radius: 5px; @@ -486,17 +486,17 @@ time { font-weight: 400; } -.account { +.legacy .account { position: relative; } -.account-trigger { +.legacy .account-trigger { min-height: 34px; padding: 3px 8px 3px 4px; font-weight: 600; } -.avatar { +.legacy .avatar { display: grid; width: 26px; height: 26px; @@ -508,7 +508,7 @@ time { font: 9px var(--mono); } -.account-menu { +.legacy .account-menu { position: absolute; top: calc(100% + 9px); right: 0; @@ -522,7 +522,7 @@ time { box-shadow: var(--shadow); } -.account-menu-user { +.legacy .account-menu-user { display: flex; align-items: center; gap: 9px; @@ -530,25 +530,25 @@ time { border-bottom: 1px solid var(--line); } -.account-menu-user > span:last-child { +.legacy .account-menu-user > span:last-child { display: grid; min-width: 0; gap: 2px; } -.account-menu-user b { +.legacy .account-menu-user b { overflow: hidden; color: var(--text); font-size: 11px; text-overflow: ellipsis; } -.account-menu-user small { +.legacy .account-menu-user small { color: var(--text-muted); font-size: 9px; } -.account-menu > a { +.legacy .account-menu > a { display: flex; min-height: 33px; align-items: center; @@ -562,12 +562,12 @@ time { text-decoration: none; } -.account-menu > a:hover { +.legacy .account-menu > a:hover { color: var(--text); background: var(--surface-hover); } -.account-menu button { +.legacy .account-menu button { width: 100%; min-height: 33px; justify-content: flex-start; @@ -575,20 +575,20 @@ time { background: var(--surface-3); } -.content { +.legacy .content { min-height: 100vh; padding-top: 64px; } /* Shared page patterns */ -.page { +.legacy .page { width: min(1500px, 100%); margin: 0 auto; padding: 38px 32px 68px; } -.page-header { +.legacy .page-header { display: flex; align-items: flex-end; justify-content: space-between; @@ -598,7 +598,7 @@ time { .page-header h1, .task-header h1, -.empty-state h1 { +.legacy .empty-state h1 { margin: 0; font-size: clamp(32px, 4vw, 48px); font-weight: 650; @@ -606,7 +606,7 @@ time { letter-spacing: -0.055em; } -.page-header > div > p { +.legacy .page-header > div > p { max-width: 580px; margin: 10px 0 0; color: var(--text-soft); @@ -614,7 +614,7 @@ time { line-height: 1.55; } -.eyebrow { +.legacy .eyebrow { display: block; margin-bottom: 8px; color: var(--accent); @@ -624,13 +624,13 @@ time { text-transform: uppercase; } -.page-actions { +.legacy .page-actions { display: flex; align-items: center; gap: 9px; } -.back-link { +.legacy .back-link { display: inline-flex; align-items: center; gap: 5px; @@ -641,37 +641,37 @@ time { text-decoration: none; } -.back-link:hover { +.legacy .back-link:hover { color: var(--accent-strong); } .section-header, -.panel-header { +.legacy .panel-header { display: flex; align-items: center; justify-content: space-between; gap: 18px; } -.section-header { +.legacy .section-header { margin-bottom: 13px; } .section-header h2, -.panel-header h2 { +.legacy .panel-header h2 { margin: 0; font-size: 14px; letter-spacing: -0.015em; } .section-header p, -.panel-header p { +.legacy .panel-header p { margin: 4px 0 0; color: var(--text-muted); font-size: 11px; } -.sync-label { +.legacy .sync-label { display: inline-flex; align-items: center; gap: 8px; @@ -680,25 +680,25 @@ time { text-transform: uppercase; } -.sync-label .signal { +.legacy .sync-label .signal { margin: 0; } -.panel { +.legacy .panel { padding: 20px; border: 1px solid var(--line); border-radius: var(--radius); background: linear-gradient(145deg, rgba(21, 29, 33, 0.9), rgba(16, 23, 27, 0.9)); } -.panel-intro { +.legacy .panel-intro { margin: 7px 0 0; color: var(--text-muted); font-size: 11px; line-height: 1.5; } -.empty-state { +.legacy .empty-state { display: grid; min-height: 300px; place-items: center; @@ -710,11 +710,11 @@ time { background: rgba(17, 23, 27, 0.45); } -.empty-state.compact { +.legacy .empty-state.compact { min-height: 220px; } -.empty-state .empty-icon { +.legacy .empty-state .empty-icon { display: grid; width: 44px; height: 44px; @@ -727,13 +727,13 @@ time { } .empty-state h1, -.empty-state h2 { +.legacy .empty-state h2 { margin: 0; font-size: 20px; letter-spacing: -0.03em; } -.empty-state p { +.legacy .empty-state p { max-width: 390px; margin: 8px 0 17px; color: var(--text-muted); @@ -741,13 +741,13 @@ time { line-height: 1.55; } -.error-state .empty-icon { +.legacy .error-state .empty-icon { border-color: rgba(236, 143, 133, 0.25); color: var(--red); background: var(--red-wash); } -.skeleton { +.legacy .skeleton { min-height: 180px; border-radius: var(--radius); background: linear-gradient(100deg, var(--surface-1) 30%, var(--surface-2) 50%, var(--surface-1) 70%); @@ -755,20 +755,20 @@ time { animation: shimmer 1.5s infinite; } -.skeleton-title { +.legacy .skeleton-title { width: 300px; min-height: 44px; margin-top: 10px; } -.skeleton-subtitle { +.legacy .skeleton-subtitle { width: 420px; max-width: 75%; min-height: 18px; margin-top: 13px; } -.skeleton-grid { +.legacy .skeleton-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; @@ -781,7 +781,7 @@ time { /* Overview */ -.attention-banner { +.legacy .attention-banner { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; @@ -793,7 +793,7 @@ time { background: var(--amber-wash); } -.attention-icon { +.legacy .attention-icon { display: grid; width: 35px; height: 35px; @@ -804,17 +804,17 @@ time { background: rgba(239, 189, 114, 0.08); } -.attention-banner b { +.legacy .attention-banner b { font-size: 12px; } -.attention-banner p { +.legacy .attention-banner p { margin: 3px 0 0; color: var(--text-soft); font-size: 11px; } -.attention-banner > a { +.legacy .attention-banner > a { display: inline-flex; align-items: center; gap: 3px; @@ -824,14 +824,14 @@ time { text-decoration: none; } -.metrics { +.legacy .metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-bottom: 32px; } -.metric { +.legacy .metric { display: grid; min-width: 0; grid-template-columns: auto minmax(0, 1fr); @@ -842,7 +842,7 @@ time { background: rgba(17, 23, 27, 0.82); } -.metric-icon { +.legacy .metric-icon { display: grid; width: 34px; height: 34px; @@ -854,7 +854,7 @@ time { background: var(--surface-2); } -.metric-copy { +.legacy .metric-copy { display: flex; min-width: 0; align-items: baseline; @@ -862,7 +862,7 @@ time { gap: 8px; } -.metric-copy small { +.legacy .metric-copy small { overflow: hidden; color: var(--text-soft); font-size: 10px; @@ -871,12 +871,12 @@ time { white-space: nowrap; } -.metric-copy b { +.legacy .metric-copy b { color: var(--text); font: 600 19px var(--mono); } -.metric-detail { +.legacy .metric-detail { overflow: hidden; color: var(--text-muted); font-size: 10px; @@ -884,26 +884,26 @@ time { white-space: nowrap; } -.metric.tone-good .metric-icon { +.legacy .metric.tone-good .metric-icon { border-color: var(--accent-line); color: var(--accent); background: var(--accent-wash); } -.metric.tone-warning .metric-icon { +.legacy .metric.tone-warning .metric-icon { border-color: rgba(239, 189, 114, 0.26); color: var(--amber); background: var(--amber-wash); } -.board-toolbar { +.legacy .board-toolbar { display: flex; align-items: center; gap: 9px; margin-bottom: 13px; } -.segmented { +.legacy .segmented { display: flex; gap: 3px; margin-right: auto; @@ -913,7 +913,7 @@ time { background: var(--surface-1); } -.segmented button { +.legacy .segmented button { min-height: 31px; padding: 5px 10px; border-color: transparent; @@ -923,41 +923,41 @@ time { font-weight: 650; } -.segmented button:hover:not(:disabled) { +.legacy .segmented button:hover:not(:disabled) { border-color: transparent; color: var(--text-soft); background: var(--surface-3); transform: none; } -.segmented button.selected { +.legacy .segmented button.selected { border-color: var(--line-strong); color: var(--text); background: var(--surface-3); box-shadow: 0 2px 7px rgba(0, 0, 0, 0.18); } -.segmented button span { +.legacy .segmented button span { margin-left: 5px; color: var(--text-muted); font: 9px var(--mono); } -.search-field { +.legacy .search-field { position: relative; display: flex; width: min(280px, 28vw); align-items: center; } -.search-field > .icon { +.legacy .search-field > .icon { position: absolute; left: 11px; z-index: 1; color: var(--text-muted); } -.search-field input { +.legacy .search-field input { min-height: 38px; padding: 7px 34px; color: var(--text-soft); @@ -965,7 +965,7 @@ time { font-size: 11px; } -.search-field button { +.legacy .search-field button { position: absolute; right: 5px; min-width: 27px; @@ -976,18 +976,18 @@ time { background: transparent; } -.select-field { +.legacy .select-field { width: 155px; } -.select-field select { +.legacy .select-field select { min-height: 38px; padding: 7px 29px 7px 10px; background-color: var(--surface-1); font-size: 11px; } -.task-board { +.legacy .task-board { display: grid; gap: 10px; overflow-x: auto; @@ -995,23 +995,23 @@ time { scrollbar-color: var(--surface-3) transparent; } -.task-board.lanes-2 { +.legacy .task-board.lanes-2 { grid-template-columns: repeat(2, minmax(270px, 1fr)); } -.task-board.lanes-3 { +.legacy .task-board.lanes-3 { grid-template-columns: repeat(3, minmax(255px, 1fr)); } -.task-board.lanes-5 { +.legacy .task-board.lanes-5 { grid-template-columns: repeat(5, minmax(235px, 1fr)); } -.task-board.lanes-7 { +.legacy .task-board.lanes-7 { grid-template-columns: repeat(7, minmax(225px, 1fr)); } -.task-lane { +.legacy .task-lane { min-height: 310px; padding: 10px; border: 1px solid var(--line); @@ -1019,7 +1019,7 @@ time { background: rgba(17, 23, 27, 0.62); } -.task-lane > header { +.legacy .task-lane > header { display: flex; height: 29px; align-items: center; @@ -1027,7 +1027,7 @@ time { padding: 0 3px 9px; } -.task-lane > header > span { +.legacy .task-lane > header > span { display: inline-flex; align-items: center; gap: 7px; @@ -1040,7 +1040,7 @@ time { .task-lane > header i, .status-pill i, -.health-badge i { +.legacy .health-badge i { display: block; width: 6px; height: 6px; @@ -1049,48 +1049,48 @@ time { } .lane-queued > header i, -.status-pill.state-queued i { +.legacy .status-pill.state-queued i { background: var(--blue); box-shadow: 0 0 0 3px var(--blue-wash); } .lane-leased > header i, -.status-pill.state-leased i { +.legacy .status-pill.state-leased i { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); } .lane-blocked > header i, .lane-needs_attention > header i, -.status-pill.state-blocked i { +.legacy .status-pill.state-blocked i { background: var(--amber); box-shadow: 0 0 0 3px var(--amber-wash); } -.status-pill.state-needs_attention i { +.legacy .status-pill.state-needs_attention i { background: var(--amber); box-shadow: 0 0 0 3px var(--amber-wash); } .lane-in_review > header i, -.status-pill.state-in_review i { +.legacy .status-pill.state-in_review i { background: var(--violet); box-shadow: 0 0 0 3px var(--violet-wash); } .lane-completed > header i, -.status-pill.state-completed i { +.legacy .status-pill.state-completed i { background: #8fcfc5; box-shadow: 0 0 0 3px rgba(143, 207, 197, 0.1); } .lane-failed > header i, -.status-pill.state-failed i { +.legacy .status-pill.state-failed i { background: var(--red); box-shadow: 0 0 0 3px var(--red-wash); } -.task-lane > header b { +.legacy .task-lane > header b { display: grid; min-width: 20px; height: 20px; @@ -1102,13 +1102,13 @@ time { font: 9px var(--mono); } -.lane-cards { +.legacy .lane-cards { display: grid; align-content: start; gap: 8px; } -.empty-lane { +.legacy .empty-lane { margin: 0; padding: 30px 9px; color: #53615e; @@ -1116,7 +1116,7 @@ time { text-align: center; } -.task-card { +.legacy .task-card { position: relative; display: block; min-width: 0; @@ -1130,7 +1130,7 @@ time { transition: border-color 150ms var(--ease), background 150ms var(--ease), transform 150ms var(--ease), box-shadow 150ms var(--ease); } -.task-card:hover { +.legacy .task-card:hover { z-index: 1; border-color: rgba(142, 224, 178, 0.3); background: var(--surface-hover); @@ -1138,24 +1138,24 @@ time { transform: translateY(-2px); } -.task-card.state-blocked { +.legacy .task-card.state-blocked { border-color: rgba(239, 189, 114, 0.18); } -.task-card.state-needs_attention { +.legacy .task-card.state-needs_attention { border-color: rgba(239, 189, 114, 0.25); background: linear-gradient(145deg, rgba(62, 46, 27, 0.28), var(--surface-2)); } -.task-card.state-in_review { +.legacy .task-card.state-in_review { border-color: rgba(181, 162, 239, 0.2); } -.task-card.state-failed { +.legacy .task-card.state-failed { border-color: rgba(236, 143, 133, 0.18); } -.task-card-top { +.legacy .task-card-top { display: flex; align-items: center; justify-content: space-between; @@ -1163,7 +1163,7 @@ time { margin-bottom: 9px; } -.status-pill { +.legacy .status-pill { display: inline-flex; min-height: 22px; align-items: center; @@ -1180,32 +1180,32 @@ time { } .status-pill.state-leased, -.status-pill.state-completed { +.legacy .status-pill.state-completed { color: var(--accent-strong); } -.status-pill.state-blocked { +.legacy .status-pill.state-blocked { color: var(--amber); } -.status-pill.state-needs_attention { +.legacy .status-pill.state-needs_attention { color: var(--amber); } -.status-pill.state-in_review { +.legacy .status-pill.state-in_review { color: var(--violet); } -.status-pill.state-failed { +.legacy .status-pill.state-failed { color: var(--red); } -.task-short-id { +.legacy .task-short-id { color: #5c6865; font-size: 8px; } -.task-card h3 { +.legacy .task-card h3 { display: -webkit-box; overflow: hidden; margin: 0 0 6px; @@ -1217,7 +1217,7 @@ time { -webkit-line-clamp: 2; } -.task-card > p { +.legacy .task-card > p { display: -webkit-box; overflow: hidden; min-height: 30px; @@ -1230,7 +1230,7 @@ time { } .approval-chip, -.reason-chip { +.legacy .reason-chip { display: inline-flex; align-items: center; gap: 5px; @@ -1243,14 +1243,14 @@ time { font-weight: 650; } -.reason-chip { +.legacy .reason-chip { color: var(--text-soft); background: var(--surface-3); } .capability-list, .fact-tags > div, -.worker-projects > div { +.legacy .worker-projects > div { display: flex; flex-wrap: wrap; gap: 4px; @@ -1259,7 +1259,7 @@ time { .capability-list span, .fact-tags b, -.worker-projects b { +.legacy .worker-projects b { padding: 3px 5px; border: 1px solid var(--line); border-radius: 5px; @@ -1269,7 +1269,7 @@ time { font-weight: 500; } -.task-card footer { +.legacy .task-card footer { display: flex; min-width: 0; align-items: center; @@ -1281,20 +1281,20 @@ time { font-size: 9px; } -.task-card footer > span:first-child { +.legacy .task-card footer > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.task-card footer time { +.legacy .task-card footer time { color: var(--text-muted); white-space: nowrap; } /* Overlays and forms */ -.overlay { +.legacy .overlay { position: fixed; inset: 0; z-index: 100; @@ -1304,11 +1304,11 @@ time { backdrop-filter: blur(7px); } -.modal-overlay { +.legacy .modal-overlay { place-items: center; } -.modal { +.legacy .modal { width: min(740px, 100%); max-height: calc(100vh - 40px); overflow: auto; @@ -1318,7 +1318,7 @@ time { box-shadow: var(--shadow); } -.modal-header { +.legacy .modal-header { display: flex; align-items: flex-start; justify-content: space-between; @@ -1327,25 +1327,25 @@ time { border-bottom: 1px solid var(--line); } -.modal-header h2 { +.legacy .modal-header h2 { margin: 0; font-size: 22px; letter-spacing: -0.035em; } -.modal-header p { +.legacy .modal-header p { margin: 6px 0 0; color: var(--text-muted); font-size: 11px; } -.icon-button { +.legacy .icon-button { width: 34px; min-height: 34px; padding: 0; } -.eligibility { +.legacy .eligibility { display: flex; align-items: flex-start; gap: 10px; @@ -1356,47 +1356,47 @@ time { background: var(--accent-wash); } -.eligibility > span:last-child { +.legacy .eligibility > span:last-child { display: grid; gap: 2px; } -.eligibility b { +.legacy .eligibility b { color: var(--accent-strong); font-size: 11px; } -.eligibility small { +.legacy .eligibility small { color: var(--text-muted); font-size: 10px; } -.eligibility.warning { +.legacy .eligibility.warning { border-color: rgba(239, 189, 114, 0.28); background: var(--amber-wash); } -.eligibility.warning .signal { +.legacy .eligibility.warning .signal { background: var(--amber); box-shadow: 0 0 0 4px var(--amber-wash); } -.eligibility.warning b { +.legacy .eligibility.warning b { color: var(--amber); } -.create-modal form { +.legacy .create-modal form { display: grid; gap: 14px; padding: 18px 23px 23px; } -.create-modal label { +.legacy .create-modal label { display: grid; gap: 6px; } -.create-modal label > span { +.legacy .create-modal label > span { display: flex; justify-content: space-between; color: var(--text-soft); @@ -1404,7 +1404,7 @@ time { font-weight: 650; } -.create-modal label > span b { +.legacy .create-modal label > span b { color: var(--text-muted); font-size: 8px; font-weight: 600; @@ -1412,44 +1412,44 @@ time { text-transform: uppercase; } -.create-modal label > small { +.legacy .create-modal label > small { color: var(--text-muted); font-size: 10px; } -.form-row { +.legacy .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } -.short-textarea { +.legacy .short-textarea { min-height: 82px; } -.advanced-fields { +.legacy .advanced-fields { border-top: 1px solid var(--line); padding-top: 4px; } -.advanced-fields summary { +.legacy .advanced-fields summary { color: var(--text-soft); font-size: 10px; font-weight: 650; cursor: pointer; } -.advanced-fields p { +.legacy .advanced-fields p { margin: 8px 0; color: var(--text-muted); font-size: 10px; } -.advanced-fields textarea { +.legacy .advanced-fields textarea { min-height: 80px; } -.modal-actions { +.legacy .modal-actions { display: flex; justify-content: flex-end; gap: 8px; @@ -1457,7 +1457,7 @@ time { } .form-error, -.notice { +.legacy .notice { display: flex; align-items: flex-start; gap: 7px; @@ -1471,18 +1471,18 @@ time { line-height: 1.45; } -.notice { +.legacy .notice { border-color: var(--accent-line); color: var(--accent-strong); background: var(--accent-wash); } -.palette-overlay { +.legacy .palette-overlay { place-items: start center; padding-top: 12vh; } -.palette { +.legacy .palette { width: min(590px, 100%); overflow: hidden; border: 1px solid var(--line-strong); @@ -1491,20 +1491,20 @@ time { box-shadow: var(--shadow); } -.palette-input { +.legacy .palette-input { position: relative; display: flex; align-items: center; border-bottom: 1px solid var(--line); } -.palette-input .icon { +.legacy .palette-input .icon { position: absolute; left: 17px; color: var(--text-muted); } -.palette-input input { +.legacy .palette-input input { min-height: 57px; padding: 14px 17px 14px 48px; border: 0; @@ -1513,17 +1513,17 @@ time { font-size: 14px; } -.palette-list { +.legacy .palette-list { display: grid; gap: 3px; padding: 10px; } -.palette-list .menu-label { +.legacy .palette-list .menu-label { margin: 3px 8px 5px; } -.palette-list button { +.legacy .palette-list button { width: 100%; min-height: 42px; justify-content: space-between; @@ -1534,14 +1534,14 @@ time { font-weight: 550; } -.palette-list button > span { +.legacy .palette-list button > span { display: flex; align-items: center; gap: 9px; } .palette-list button.selected, -.palette-list button:hover:not(:disabled) { +.legacy .palette-list button:hover:not(:disabled) { border-color: var(--line); color: var(--text); background: var(--surface-3); @@ -1549,19 +1549,19 @@ time { } .palette-list kbd, -.palette-footer kbd { +.legacy .palette-footer kbd { color: var(--text-muted); font-size: 9px; } -.palette-empty { +.legacy .palette-empty { padding: 24px 8px; color: var(--text-muted); font-size: 11px; text-align: center; } -.palette-footer { +.legacy .palette-footer { display: flex; gap: 15px; padding: 9px 17px; @@ -1572,7 +1572,7 @@ time { /* Task detail */ -.task-header { +.legacy .task-header { display: flex; align-items: flex-end; justify-content: space-between; @@ -1580,7 +1580,7 @@ time { margin-bottom: 20px; } -.task-heading-meta { +.legacy .task-heading-meta { display: flex; align-items: center; gap: 8px; @@ -1589,24 +1589,24 @@ time { font-size: 9px; } -.task-heading-meta > span:not(.status-pill) + span::before { +.legacy .task-heading-meta > span:not(.status-pill) + span::before { margin-right: 8px; color: #45514e; content: "/"; } -.task-header h1 { +.legacy .task-header h1 { max-width: 850px; font-size: clamp(29px, 4vw, 44px); } -.task-id { +.legacy .task-id { margin: 9px 0 0; color: var(--text-muted); font-size: 9px; } -.diagnosis { +.legacy .diagnosis { display: grid; grid-template-columns: minmax(0, 1fr) minmax(420px, 0.8fr); align-items: center; @@ -1620,24 +1620,24 @@ time { .diagnosis.state-blocked, .diagnosis.state-needs_attention, -.diagnosis.state-failed { +.legacy .diagnosis.state-failed { border-color: rgba(239, 189, 114, 0.23); background: linear-gradient(110deg, var(--amber-wash), rgba(17, 23, 27, 0.65)); } -.diagnosis.state-in_review { +.legacy .diagnosis.state-in_review { border-color: rgba(181, 162, 239, 0.26); background: linear-gradient(110deg, var(--violet-wash), rgba(17, 23, 27, 0.65)); } -.diagnosis-copy { +.legacy .diagnosis-copy { display: flex; min-width: 0; align-items: flex-start; gap: 13px; } -.diagnosis-symbol { +.legacy .diagnosis-symbol { display: grid; width: 38px; height: 38px; @@ -1651,25 +1651,25 @@ time { .diagnosis-symbol.state-blocked, .diagnosis-symbol.state-needs_attention, -.diagnosis-symbol.state-failed { +.legacy .diagnosis-symbol.state-failed { border-color: rgba(239, 189, 114, 0.25); color: var(--amber); background: var(--amber-wash); } -.diagnosis-symbol.state-in_review { +.legacy .diagnosis-symbol.state-in_review { border-color: rgba(181, 162, 239, 0.3); color: var(--violet); background: var(--violet-wash); } -.diagnosis h2 { +.legacy .diagnosis h2 { margin: 0; font-size: 17px; letter-spacing: -0.025em; } -.diagnosis-copy p { +.legacy .diagnosis-copy p { max-width: 630px; margin: 5px 0 0; color: var(--text-soft); @@ -1677,7 +1677,7 @@ time { line-height: 1.5; } -.diagnosis dl { +.legacy .diagnosis dl { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; @@ -1687,19 +1687,19 @@ time { background: rgba(9, 14, 16, 0.3); } -.diagnosis dl > div { +.legacy .diagnosis dl > div { min-width: 0; padding: 9px; border-right: 1px solid var(--line); } -.diagnosis dl > div:last-child { +.legacy .diagnosis dl > div:last-child { border: 0; } .diagnosis dt, .facts dt, -.worker-stats dt { +.legacy .worker-stats dt { margin-bottom: 5px; color: var(--text-muted); font-size: 9px; @@ -1708,7 +1708,7 @@ time { text-transform: uppercase; } -.diagnosis dd { +.legacy .diagnosis dd { overflow: hidden; margin: 0; color: var(--text-soft); @@ -1717,7 +1717,7 @@ time { white-space: nowrap; } -.approval-panel { +.legacy .approval-panel { display: grid; gap: 12px; margin-bottom: 14px; @@ -1727,30 +1727,30 @@ time { background: linear-gradient(135deg, rgba(68, 48, 20, 0.38), rgba(21, 29, 33, 0.92)); } -.approval-heading { +.legacy .approval-heading { display: flex; align-items: flex-start; gap: 13px; } -.approval-heading h2 { +.legacy .approval-heading h2 { margin: 0; font-size: 18px; } -.approval-heading p { +.legacy .approval-heading p { margin: 5px 0 0; color: var(--text-soft); font-size: 10px; } -.approval-meta { +.legacy .approval-meta { display: flex; flex-wrap: wrap; gap: 6px; } -.approval-meta span { +.legacy .approval-meta span { padding: 4px 6px; border: 1px solid var(--line); border-radius: 5px; @@ -1759,13 +1759,13 @@ time { font: 9px var(--mono); } -.approval-meta b { +.legacy .approval-meta b { color: var(--text-soft); font-weight: 500; } .approval-code, -.artifact-content { +.legacy .artifact-content { margin: 0; padding: 14px; overflow: auto; @@ -1778,19 +1778,19 @@ time { white-space: pre-wrap; } -.approval-note { +.legacy .approval-note { margin: 0; color: var(--text-soft); font-size: 11px; line-height: 1.5; } -.approval-actions { +.legacy .approval-actions { display: flex; gap: 8px; } -.detail-layout { +.legacy .detail-layout { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 14px; @@ -1798,12 +1798,12 @@ time { } .detail-main, -.detail-side { +.legacy .detail-side { display: grid; gap: 14px; } -.immutable-badge { +.legacy .immutable-badge { padding: 4px 6px; border: 1px solid var(--line); border-radius: 5px; @@ -1813,7 +1813,7 @@ time { text-transform: uppercase; } -.task-description { +.legacy .task-description { margin: 14px 0 0; color: #c1cac7; font-size: 12px; @@ -1822,19 +1822,19 @@ time { } .acceptance-list, -.quality-gate { +.legacy .quality-gate { margin-top: 18px; padding-top: 15px; border-top: 1px solid var(--line); } -.acceptance-list h3 { +.legacy .acceptance-list h3 { margin: 0 0 9px; color: var(--text-soft); font-size: 11px; } -.acceptance-list ul { +.legacy .acceptance-list ul { display: grid; gap: 7px; margin: 0; @@ -1842,7 +1842,7 @@ time { list-style: none; } -.acceptance-list li { +.legacy .acceptance-list li { display: flex; align-items: flex-start; gap: 8px; @@ -1851,7 +1851,7 @@ time { line-height: 1.45; } -.acceptance-list li > span { +.legacy .acceptance-list li > span { display: grid; width: 18px; height: 18px; @@ -1863,12 +1863,12 @@ time { background: var(--accent-wash); } -.quality-gate { +.legacy .quality-gate { display: grid; gap: 6px; } -.quality-gate > span { +.legacy .quality-gate > span { color: var(--text-muted); font-size: 9px; font-weight: 700; @@ -1876,7 +1876,7 @@ time { text-transform: uppercase; } -.quality-gate code { +.legacy .quality-gate code { display: block; padding: 8px 10px; border: 1px solid var(--line); @@ -1886,14 +1886,14 @@ time { font-size: 10px; } -.capture-panel { +.legacy .capture-panel { overflow: hidden; border: 1px solid var(--line-strong); border-radius: var(--radius); background: #090d0f; } -.capture-panel > header { +.legacy .capture-panel > header { display: flex; align-items: center; justify-content: space-between; @@ -1904,13 +1904,13 @@ time { } .capture-title, -.capture-actions { +.legacy .capture-actions { display: flex; align-items: center; gap: 8px; } -.capture-title > span { +.legacy .capture-title > span { display: grid; width: 30px; height: 30px; @@ -1921,42 +1921,42 @@ time { background: var(--accent-wash); } -.capture-title h2 { +.legacy .capture-title h2 { margin: 0; font-size: 11px; } -.capture-title p { +.legacy .capture-title p { margin: 3px 0 0; color: var(--text-muted); font: 9px var(--mono); } -.small-button { +.legacy .small-button { min-height: 32px; padding: 5px 9px; font-size: 10px; } -.capture-actions .icon-button { +.legacy .capture-actions .icon-button { min-height: 32px; width: 32px; } -.terminal-filter { +.legacy .terminal-filter { position: relative; display: flex; align-items: center; margin: 10px 11px 0; } -.terminal-filter .icon { +.legacy .terminal-filter .icon { position: absolute; left: 9px; color: #55625e; } -.terminal-filter input { +.legacy .terminal-filter input { min-height: 31px; padding: 5px 9px 5px 30px; border-color: var(--line); @@ -1964,7 +1964,7 @@ time { font-size: 10px; } -.terminal-output { +.legacy .terminal-output { max-height: 530px; margin: 0; padding: 15px; @@ -1976,7 +1976,7 @@ time { scrollbar-color: var(--surface-3) transparent; } -.capture-panel > footer { +.legacy .capture-panel > footer { padding: 7px 12px; border-top: 1px solid var(--line); color: var(--text-muted); @@ -1984,7 +1984,7 @@ time { font-size: 9px; } -.capture-empty { +.legacy .capture-empty { display: flex; align-items: flex-start; gap: 11px; @@ -1994,32 +1994,32 @@ time { background: rgba(17, 23, 27, 0.5); } -.capture-empty > span { +.legacy .capture-empty > span { color: var(--text-muted); } -.capture-empty b { +.legacy .capture-empty b { font-size: 11px; } -.capture-empty p { +.legacy .capture-empty p { margin: 4px 0 0; color: var(--text-muted); font-size: 10px; line-height: 1.5; } -.action-panel .panel-header { +.legacy .action-panel .panel-header { margin-bottom: 0; } -.action-list { +.legacy .action-list { display: grid; gap: 8px; margin-top: 13px; } -.action-card { +.legacy .action-card { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; @@ -2031,7 +2031,7 @@ time { background: rgba(10, 15, 17, 0.32); } -.action-icon { +.legacy .action-icon { display: grid; width: 31px; height: 31px; @@ -2042,71 +2042,71 @@ time { background: var(--surface-2); } -.action-card h3 { +.legacy .action-card h3 { margin: 0; font-size: 11px; } -.action-card p { +.legacy .action-card p { margin: 3px 0 0; color: var(--text-muted); font-size: 10px; line-height: 1.5; } -.action-form { +.legacy .action-form { align-items: start; } -.action-copy { +.legacy .action-copy { min-width: 0; } .action-copy input, -.action-copy textarea { +.legacy .action-copy textarea { min-height: 36px; margin-top: 9px; padding: 7px 9px; font-size: 10px; } -.action-copy textarea { +.legacy .action-copy textarea { min-height: 78px; } -.action-buttons { +.legacy .action-buttons { display: flex; align-self: end; gap: 6px; } -.action-card button { +.legacy .action-card button { min-height: 32px; padding: 6px 9px; font-size: 10px; } -.action-card.confirming { +.legacy .action-card.confirming { border-color: rgba(239, 189, 114, 0.28); background: var(--amber-wash); } .button-danger, -.button-danger:hover:not(:disabled) { +.legacy .button-danger:hover:not(:disabled) { border-color: rgba(236, 143, 133, 0.4); color: #27100e; background: var(--red); } -.inline-error { +.legacy .inline-error { color: var(--red) !important; } -.unavailable-panel { +.legacy .unavailable-panel { padding: 0; } -.unavailable-panel summary { +.legacy .unavailable-panel summary { display: flex; align-items: center; justify-content: space-between; @@ -2117,7 +2117,7 @@ time { cursor: pointer; } -.unavailable-panel summary span { +.legacy .unavailable-panel summary span { display: grid; width: 20px; height: 20px; @@ -2127,13 +2127,13 @@ time { font: 8px var(--mono); } -.unavailable-list { +.legacy .unavailable-list { display: grid; gap: 8px; padding: 0 15px 14px; } -.unavailable-list > div { +.legacy .unavailable-list > div { display: grid; gap: 3px; padding: 9px; @@ -2141,23 +2141,23 @@ time { border-radius: 7px; } -.unavailable-list b { +.legacy .unavailable-list b { color: var(--text-soft); font-size: 10px; } -.unavailable-list span { +.legacy .unavailable-list span { color: var(--text-muted); font-size: 9px; } -.facts { +.legacy .facts { display: grid; gap: 0; margin: 10px 0 0; } -.facts > div { +.legacy .facts > div { display: flex; min-width: 0; align-items: center; @@ -2167,15 +2167,15 @@ time { border-bottom: 1px solid var(--line); } -.facts > div:last-child { +.legacy .facts > div:last-child { border: 0; } -.facts dt { +.legacy .facts dt { margin: 0; } -.facts dd { +.legacy .facts dd { overflow: hidden; margin: 0; color: var(--text-soft); @@ -2185,7 +2185,7 @@ time { white-space: nowrap; } -.fact-tags { +.legacy .fact-tags { display: grid; gap: 7px; margin-top: 9px; @@ -2195,7 +2195,7 @@ time { .fact-tags > span, .worker-projects > span, -.worker-current > span { +.legacy .worker-current > span { color: var(--text-muted); font-size: 9px; font-weight: 700; @@ -2203,16 +2203,16 @@ time { text-transform: uppercase; } -.fact-tags > div { +.legacy .fact-tags > div { margin: 0; } -.artifacts-panel { +.legacy .artifacts-panel { display: grid; gap: 7px; } -.artifacts-panel > a { +.legacy .artifacts-panel > a { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; @@ -2224,39 +2224,39 @@ time { text-decoration: none; } -.artifacts-panel > a:hover { +.legacy .artifacts-panel > a:hover { border-color: var(--accent-line); color: var(--accent); background: var(--accent-wash); } -.artifacts-panel a > span { +.legacy .artifacts-panel a > span { display: grid; min-width: 0; gap: 2px; } -.artifacts-panel b { +.legacy .artifacts-panel b { color: var(--text-soft); font-size: 10px; } -.artifacts-panel small { +.legacy .artifacts-panel small { color: var(--text-muted); font: 9px var(--mono); } -.timeline-panel { +.legacy .timeline-panel { overflow: hidden; } -.timeline { +.legacy .timeline { margin: 12px 0 0; padding: 0; list-style: none; } -.timeline li { +.legacy .timeline li { position: relative; display: grid; grid-template-columns: 12px minmax(0, 1fr); @@ -2264,7 +2264,7 @@ time { padding-bottom: 13px; } -.timeline li:not(:last-child)::before { +.legacy .timeline li:not(:last-child)::before { position: absolute; top: 11px; bottom: 0; @@ -2274,7 +2274,7 @@ time { content: ""; } -.event-dot { +.legacy .event-dot { position: relative; z-index: 1; display: block; @@ -2287,16 +2287,16 @@ time { box-shadow: 0 0 0 1px var(--line-strong); } -.timeline li.latest .event-dot { +.legacy .timeline li.latest .event-dot { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); } -.event-copy { +.legacy .event-copy { min-width: 0; } -.event-copy > button { +.legacy .event-copy > button { display: grid; width: 100%; min-height: 0; @@ -2309,18 +2309,18 @@ time { text-align: left; } -.event-copy > button:hover:not(:disabled) { +.legacy .event-copy > button:hover:not(:disabled) { border: 0; background: transparent; transform: none; } -.event-copy > button:disabled { +.legacy .event-copy > button:disabled { opacity: 1; cursor: default; } -.event-copy b { +.legacy .event-copy b { overflow: hidden; color: var(--text-soft); font-size: 10px; @@ -2329,17 +2329,17 @@ time { } .event-copy span, -.event-copy time { +.legacy .event-copy time { color: var(--text-muted); font-size: 9px; } -.event-copy time { +.legacy .event-copy time { display: block; margin-top: 3px; } -.event-copy pre { +.legacy .event-copy pre { max-height: 260px; margin: 8px 0 0; padding: 8px; @@ -2355,38 +2355,38 @@ time { /* Workers */ -.worker-grid { +.legacy .worker-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 11px; } -.worker-card { +.legacy .worker-card { padding: 17px; border: 1px solid var(--line); border-radius: var(--radius); background: linear-gradient(145deg, var(--surface-2), var(--surface-1)); } -.worker-card.offline { +.legacy .worker-card.offline { border-color: rgba(236, 143, 133, 0.2); } -.worker-card > header { +.legacy .worker-card > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } -.worker-identity { +.legacy .worker-identity { display: flex; min-width: 0; align-items: center; gap: 10px; } -.worker-glyph { +.legacy .worker-glyph { display: grid; width: 39px; height: 39px; @@ -2398,17 +2398,17 @@ time { background: var(--accent-wash); } -.worker-card.offline .worker-glyph { +.legacy .worker-card.offline .worker-glyph { color: var(--red); background: var(--red-wash); } -.worker-card h2 { +.legacy .worker-card h2 { margin: 0; font-size: 13px; } -.worker-card header p { +.legacy .worker-card header p { overflow: hidden; max-width: 240px; margin: 4px 0 0; @@ -2418,7 +2418,7 @@ time { white-space: nowrap; } -.health-badge { +.legacy .health-badge { display: inline-flex; align-items: center; gap: 6px; @@ -2432,21 +2432,21 @@ time { text-transform: uppercase; } -.health-badge i { +.legacy .health-badge i { background: var(--accent); } -.health-badge.unhealthy { +.legacy .health-badge.unhealthy { border-color: rgba(236, 143, 133, 0.25); color: var(--red); background: var(--red-wash); } -.health-badge.unhealthy i { +.legacy .health-badge.unhealthy i { background: var(--red); } -.worker-stats { +.legacy .worker-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 15px 0 0; @@ -2456,17 +2456,17 @@ time { background: rgba(9, 14, 16, 0.24); } -.worker-stats > div { +.legacy .worker-stats > div { min-width: 0; padding: 9px; border-right: 1px solid var(--line); } -.worker-stats > div:last-child { +.legacy .worker-stats > div:last-child { border: 0; } -.worker-stats dd { +.legacy .worker-stats dd { overflow: hidden; margin: 0; color: var(--text-soft); @@ -2476,22 +2476,22 @@ time { white-space: nowrap; } -.worker-stats dd.health-reachable { +.legacy .worker-stats dd.health-reachable { color: var(--accent); } -.worker-stats dd.health-unreachable { +.legacy .worker-stats dd.health-unreachable { color: var(--red); } .worker-current, -.worker-projects { +.legacy .worker-projects { display: grid; gap: 7px; margin-top: 14px; } -.worker-current > a { +.legacy .worker-current > a { display: flex; align-items: center; justify-content: space-between; @@ -2504,18 +2504,18 @@ time { text-decoration: none; } -.worker-current > a:hover { +.legacy .worker-current > a:hover { border-color: var(--accent-line); color: var(--accent); } -.worker-current > a > span { +.legacy .worker-current > a > span { display: grid; min-width: 0; gap: 3px; } -.worker-current a b { +.legacy .worker-current a b { overflow: hidden; color: var(--text-soft); font: 10px var(--mono); @@ -2523,12 +2523,12 @@ time { white-space: nowrap; } -.worker-current a small { +.legacy .worker-current a small { color: var(--text-muted); font: 9px var(--mono); } -.worker-current > p { +.legacy .worker-current > p { margin: 0; padding: 9px; border: 1px dashed var(--line); @@ -2537,11 +2537,11 @@ time { font-size: 10px; } -.worker-projects > div { +.legacy .worker-projects > div { margin: 0; } -.worker-error { +.legacy .worker-error { display: flex; align-items: flex-start; gap: 8px; @@ -2553,22 +2553,22 @@ time { background: var(--red-wash); } -.worker-error > span { +.legacy .worker-error > span { display: grid; gap: 3px; } .worker-error b, -.worker-error small { +.legacy .worker-error small { font-size: 9px; font-weight: 500; } -.worker-error small { +.legacy .worker-error small { color: var(--text-muted); } -.worker-card > footer { +.legacy .worker-card > footer { display: flex; justify-content: space-between; gap: 10px; @@ -2581,11 +2581,11 @@ time { /* Account settings */ -.settings-page { +.legacy .settings-page { max-width: 1160px; } -.settings-layout { +.legacy .settings-layout { display: grid; grid-template-columns: 300px minmax(0, 1fr); align-items: start; @@ -2593,18 +2593,18 @@ time { } .profile-card, -.settings-card { +.legacy .settings-card { border: 1px solid var(--line); border-radius: var(--radius-lg); background: linear-gradient(145deg, rgba(21, 29, 33, 0.94), rgba(14, 20, 23, 0.94)); box-shadow: 0 18px 50px rgba(0, 0, 0, 0.14); } -.profile-card { +.legacy .profile-card { padding: 24px; } -.profile-avatar { +.legacy .profile-avatar { display: grid; width: 58px; height: 58px; @@ -2618,20 +2618,20 @@ time { font: 600 16px var(--mono); } -.profile-card h2 { +.legacy .profile-card h2 { margin: 0; font-size: 20px; letter-spacing: -0.035em; overflow-wrap: anywhere; } -.profile-card > p { +.legacy .profile-card > p { margin: 5px 0 22px; color: var(--text-muted); font-size: 11px; } -.profile-card dl { +.legacy .profile-card dl { display: grid; gap: 12px; margin: 0; @@ -2640,12 +2640,12 @@ time { border-bottom: 1px solid var(--line); } -.profile-card dl > div { +.legacy .profile-card dl > div { display: grid; gap: 4px; } -.profile-card dt { +.legacy .profile-card dt { color: var(--text-muted); font-size: 9px; font-weight: 700; @@ -2653,13 +2653,13 @@ time { text-transform: uppercase; } -.profile-card dd { +.legacy .profile-card dd { margin: 0; color: var(--text-soft); font: 9px var(--mono); } -.database-badge { +.legacy .database-badge { display: flex; align-items: flex-start; gap: 10px; @@ -2671,27 +2671,27 @@ time { background: var(--accent-wash); } -.database-badge > span { +.legacy .database-badge > span { display: grid; gap: 3px; } -.database-badge b { +.legacy .database-badge b { color: var(--accent-strong); font-size: 10px; } -.database-badge small { +.legacy .database-badge small { color: var(--text-soft); font-size: 9px; line-height: 1.5; } -.settings-card { +.legacy .settings-card { overflow: hidden; } -.settings-card > header { +.legacy .settings-card > header { display: flex; align-items: flex-start; gap: 13px; @@ -2700,7 +2700,7 @@ time { background: rgba(17, 23, 27, 0.55); } -.settings-icon { +.legacy .settings-icon { display: grid; width: 38px; height: 38px; @@ -2712,26 +2712,26 @@ time { background: var(--accent-wash); } -.settings-card h2 { +.legacy .settings-card h2 { margin: 1px 0 0; font-size: 16px; letter-spacing: -0.025em; } -.settings-card header p { +.legacy .settings-card header p { margin: 5px 0 0; color: var(--text-muted); font-size: 10px; } -.settings-card form { +.legacy .settings-card form { display: grid; gap: 9px; padding: 24px; } .settings-card label, -.current-password-block > label { +.legacy .current-password-block > label { display: grid; gap: 7px; color: var(--text-soft); @@ -2739,12 +2739,12 @@ time { font-weight: 650; } -.settings-field { +.legacy .settings-field { display: grid; gap: 7px; } -.settings-divider { +.legacy .settings-divider { display: flex; align-items: center; gap: 12px; @@ -2756,21 +2756,21 @@ time { text-transform: uppercase; } -.settings-divider::after { +.legacy .settings-divider::after { height: 1px; flex: 1; background: var(--line); content: ""; } -.password-rules { +.legacy .password-rules { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 3px; } -.password-rules span { +.legacy .password-rules span { display: inline-flex; align-items: center; gap: 4px; @@ -2782,13 +2782,13 @@ time { font-size: 9px; } -.password-rules span.met { +.legacy .password-rules span.met { border-color: var(--accent-line); color: var(--accent); background: var(--accent-wash); } -.current-password-block { +.legacy .current-password-block { display: grid; grid-template-columns: minmax(0, 1fr) minmax(220px, 0.65fr); align-items: end; @@ -2800,23 +2800,23 @@ time { background: var(--surface-1); } -.current-password-block label { +.legacy .current-password-block label { grid-column: 1; } -.current-password-block p { +.legacy .current-password-block p { grid-column: 1; margin: 0; color: var(--text-muted); font-size: 9px; } -.current-password-block input { +.legacy .current-password-block input { grid-column: 2; grid-row: 1 / span 2; } -.settings-actions { +.legacy .settings-actions { display: flex; align-items: center; justify-content: space-between; @@ -2827,25 +2827,25 @@ time { background: rgba(10, 15, 17, 0.28); } -.settings-actions > span { +.legacy .settings-actions > span { color: var(--text-muted); font-size: 9px; } /* Artifact and login */ -.artifact-header { +.legacy .artifact-header { align-items: flex-end; } -.artifact-content { +.legacy .artifact-content { min-height: 400px; padding: 20px; font-size: 11px; line-height: 1.7; } -.login-page { +.legacy .login-page { position: relative; display: grid; min-height: 100vh; @@ -2858,7 +2858,7 @@ time { #090d0f; } -.login-grid { +.legacy .login-grid { position: absolute; inset: 0; opacity: 0.24; @@ -2869,7 +2869,7 @@ time { mask-image: radial-gradient(circle at center, black, transparent 68%); } -.login-layout { +.legacy .login-layout { position: relative; display: grid; width: min(1040px, 100%); @@ -2877,7 +2877,7 @@ time { gap: 14px; } -.login-showcase { +.legacy .login-showcase { position: relative; display: flex; min-height: 610px; @@ -2892,7 +2892,7 @@ time { box-shadow: 0 30px 90px rgba(0, 0, 0, 0.28); } -.login-showcase::before { +.legacy .login-showcase::before { position: absolute; width: 430px; height: 430px; @@ -2906,36 +2906,36 @@ time { content: ""; } -.showcase-brand { +.legacy .showcase-brand { display: flex; align-items: center; gap: 12px; } -.showcase-brand > span:last-child { +.legacy .showcase-brand > span:last-child { display: grid; gap: 2px; } -.showcase-brand b { +.legacy .showcase-brand b { font-size: 14px; letter-spacing: -0.02em; } -.showcase-brand small { +.legacy .showcase-brand small { color: var(--text-muted); font: 8px var(--mono); letter-spacing: 0.1em; text-transform: uppercase; } -.showcase-copy { +.legacy .showcase-copy { position: relative; max-width: 500px; margin: auto 0 42px; } -.showcase-copy h2 { +.legacy .showcase-copy h2 { margin: 0; font-size: clamp(38px, 4.2vw, 58px); font-weight: 620; @@ -2943,7 +2943,7 @@ time { letter-spacing: -0.064em; } -.showcase-copy p { +.legacy .showcase-copy p { max-width: 460px; margin: 20px 0 0; color: var(--text-soft); @@ -2951,7 +2951,7 @@ time { line-height: 1.7; } -.showcase-flow { +.legacy .showcase-flow { display: grid; grid-template-columns: auto minmax(16px, 1fr) auto minmax(16px, 1fr) auto minmax(16px, 1fr) auto; align-items: center; @@ -2963,7 +2963,7 @@ time { background: rgba(8, 13, 15, 0.34); } -.showcase-flow > span { +.legacy .showcase-flow > span { display: inline-flex; align-items: center; gap: 6px; @@ -2973,12 +2973,12 @@ time { text-transform: uppercase; } -.showcase-flow > b { +.legacy .showcase-flow > b { height: 1px; background: linear-gradient(90deg, var(--line-strong), var(--accent-line)); } -.flow-dot { +.legacy .flow-dot { display: block; width: 6px; height: 6px; @@ -2988,17 +2988,17 @@ time { } .flow-dot.active, -.flow-dot.done { +.legacy .flow-dot.done { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); } -.flow-dot.review { +.legacy .flow-dot.review { background: var(--violet); box-shadow: 0 0 0 3px var(--violet-wash); } -.login-showcase > footer { +.legacy .login-showcase > footer { display: flex; align-items: center; gap: 9px; @@ -3010,11 +3010,11 @@ time { text-transform: uppercase; } -.login-showcase > footer .signal { +.legacy .login-showcase > footer .signal { margin: 0; } -.login-card { +.legacy .login-card { position: relative; width: 100%; align-self: center; @@ -3025,47 +3025,47 @@ time { box-shadow: 0 30px 90px rgba(0, 0, 0, 0.5); } -.login-brand { +.legacy .login-brand { display: flex; align-items: center; gap: 11px; margin-bottom: 38px; } -.login-heading h1 { +.legacy .login-heading h1 { margin: 0; font-size: 36px; letter-spacing: -0.055em; } -.login-heading > p { +.legacy .login-heading > p { margin: 8px 0 0; color: var(--text-soft); font-size: 12px; line-height: 1.55; } -.login-card form { +.legacy .login-card form { display: grid; gap: 9px; margin-top: 24px; } -.login-card form > label { +.legacy .login-card form > label { color: var(--text-soft); font-size: 10px; font-weight: 650; } -.password-field { +.legacy .password-field { position: relative; } -.password-field input { +.legacy .password-field input { padding-right: 62px; } -.password-field button { +.legacy .password-field button { position: absolute; top: 50%; right: 5px; @@ -3078,23 +3078,23 @@ time { font-size: 10px; } -.password-field button:hover:not(:disabled) { +.legacy .password-field button:hover:not(:disabled) { border-color: transparent; background: var(--accent-wash); transform: translateY(-50%); } -.caps-lock { +.legacy .caps-lock { color: var(--amber); font-size: 10px; } -.login-submit { +.legacy .login-submit { min-height: 42px; margin-top: 7px; } -.login-security { +.legacy .login-security { display: flex; gap: 9px; margin-top: 23px; @@ -3103,7 +3103,7 @@ time { color: var(--text-muted); } -.login-security > span { +.legacy .login-security > span { display: grid; width: 24px; height: 24px; @@ -3114,13 +3114,13 @@ time { background: var(--accent-wash); } -.login-security p { +.legacy .login-security p { margin: 0; font-size: 10px; line-height: 1.55; } -.session-check { +.legacy .session-check { position: relative; display: flex; align-items: center; @@ -3134,7 +3134,7 @@ time { font-size: 11px; } -.spinner { +.legacy .spinner { display: inline-block; width: 13px; height: 13px; @@ -3151,31 +3151,31 @@ time { /* Responsive layout */ @media (max-width: 1180px) { - .metrics { + .legacy .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .diagnosis { + .legacy .diagnosis { grid-template-columns: 1fr; } - .task-board.lanes-3 { + .legacy .task-board.lanes-3 { grid-template-columns: repeat(3, minmax(235px, 1fr)); } } @media (max-width: 980px) { - .app-shell { + .legacy .app-shell { padding-left: 72px; } - .sidebar { + .legacy .sidebar { width: 72px; padding-right: 10px; padding-left: 10px; } - .brand { + .legacy .brand { justify-content: center; margin-right: -10px; margin-left: -10px; @@ -3187,11 +3187,11 @@ time { .primary-nav a > span, .nav-count, .sidebar-status > span:last-child, - .sidebar-foot { + .legacy .sidebar-foot { display: none; } - .primary-nav a { + .legacy .primary-nav a { display: grid; width: 50px; grid-template-columns: 1fr; @@ -3199,108 +3199,108 @@ time { padding: 0; } - .primary-nav a.active::before { + .legacy .primary-nav a.active::before { left: -11px; } - .sidebar-status { + .legacy .sidebar-status { justify-content: center; padding: 11px; } - .topbar { + .legacy .topbar { left: 72px; } - .detail-layout { + .legacy .detail-layout { grid-template-columns: minmax(0, 1fr) 300px; } - .login-layout { + .legacy .login-layout { grid-template-columns: minmax(0, 1fr) 390px; } - .login-showcase { + .legacy .login-showcase { padding: 30px; } } @media (max-width: 800px) { - .page { + .legacy .page { padding-right: 20px; padding-left: 20px; } - .detail-layout { + .legacy .detail-layout { grid-template-columns: 1fr; } - .detail-side { + .legacy .detail-side { grid-template-columns: 1fr 1fr; align-items: start; } - .timeline-panel { + .legacy .timeline-panel { grid-column: 1 / -1; } - .board-toolbar { + .legacy .board-toolbar { flex-wrap: wrap; } - .segmented { + .legacy .segmented { width: 100%; margin-right: 0; } - .segmented button { + .legacy .segmented button { flex: 1; } - .search-field { + .legacy .search-field { width: calc(100% - 164px); } - .settings-layout { + .legacy .settings-layout { grid-template-columns: 1fr; } - .profile-card { + .legacy .profile-card { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 16px; } - .profile-avatar { + .legacy .profile-avatar { grid-row: span 2; margin: 0; } - .profile-card > p { + .legacy .profile-card > p { margin: 3px 0 16px; } .profile-card dl, - .database-badge { + .legacy .database-badge { grid-column: 1 / -1; } - .login-layout { + .legacy .login-layout { width: min(430px, 100%); grid-template-columns: 1fr; } - .login-showcase { + .legacy .login-showcase { display: none; } } @media (max-width: 680px) { - .app-shell { + .legacy .app-shell { padding: 0 0 64px; } - .sidebar { + .legacy .sidebar { inset: auto 0 0; width: 100%; height: 64px; @@ -3310,11 +3310,11 @@ time { } .brand, - .sidebar-status { + .legacy .sidebar-status { display: none; } - .primary-nav { + .legacy .primary-nav { display: flex; height: 100%; justify-content: space-around; @@ -3322,7 +3322,7 @@ time { margin: 0; } - .primary-nav a { + .legacy .primary-nav a { width: auto; flex: 1; grid-template-columns: auto auto; @@ -3330,11 +3330,11 @@ time { gap: 7px; } - .primary-nav a > span { + .legacy .primary-nav a > span { display: inline; } - .primary-nav a.active::before { + .legacy .primary-nav a.active::before { inset: -7px auto auto 50%; width: 26px; height: 3px; @@ -3342,13 +3342,13 @@ time { border-radius: 0 0 4px 4px; } - .topbar { + .legacy .topbar { left: 0; height: 56px; padding: 0 15px; } - .content { + .legacy .content { padding-top: 56px; } @@ -3356,67 +3356,67 @@ time { .topbar-context .icon, .command-trigger span, .command-trigger kbd, - .account-label { + .legacy .account-label { display: none; } - .command-trigger { + .legacy .command-trigger { width: 34px; margin-left: auto; padding: 0; } - .account-trigger { + .legacy .account-trigger { padding: 3px; } - .page { + .legacy .page { padding: 27px 15px 45px; } .page-header, - .task-header { + .legacy .task-header { align-items: flex-start; flex-direction: column; gap: 18px; } - .page-header h1 { + .legacy .page-header h1 { font-size: 34px; } - .page-actions { + .legacy .page-actions { width: 100%; } - .page-actions > button:last-child { + .legacy .page-actions > button:last-child { flex: 1; } - .metrics { + .legacy .metrics { grid-template-columns: 1fr 1fr; } - .metric { + .legacy .metric { grid-template-columns: 1fr; } - .metric-icon { + .legacy .metric-icon { display: none; } - .attention-banner { + .legacy .attention-banner { grid-template-columns: auto minmax(0, 1fr); } - .attention-banner > a { + .legacy .attention-banner > a { grid-column: 2; } .task-board.lanes-2, .task-board.lanes-3, .task-board.lanes-5, - .task-board.lanes-7 { + .legacy .task-board.lanes-7 { grid-template-columns: repeat(var(--mobile-lanes, 5), minmax(255px, 82vw)); } @@ -3425,142 +3425,142 @@ time { .task-board.lanes-5 { --mobile-lanes: 5; } .task-board.lanes-7 { --mobile-lanes: 7; } - .task-lane { + .legacy .task-lane { min-height: 270px; } .form-row, - .detail-side { + .legacy .detail-side { grid-template-columns: 1fr; } - .timeline-panel { + .legacy .timeline-panel { grid-column: auto; } - .diagnosis dl { + .legacy .diagnosis dl { grid-template-columns: 1fr 1fr; } - .diagnosis dl > div:nth-child(2) { + .legacy .diagnosis dl > div:nth-child(2) { border-right: 0; } - .diagnosis dl > div:nth-child(-n + 2) { + .legacy .diagnosis dl > div:nth-child(-n + 2) { border-bottom: 1px solid var(--line); } - .approval-panel { + .legacy .approval-panel { padding: 14px; } - .approval-actions { + .legacy .approval-actions { align-items: stretch; flex-direction: column; } .action-card, - .action-form { + .legacy .action-form { grid-template-columns: auto minmax(0, 1fr); } .action-card > button, - .action-buttons { + .legacy .action-buttons { grid-column: 2; justify-self: start; } - .worker-grid { + .legacy .worker-grid { grid-template-columns: 1fr; } - .worker-card > header { + .legacy .worker-card > header { align-items: flex-start; } - .overlay { + .legacy .overlay { padding: 10px; } - .modal { + .legacy .modal { max-height: calc(100vh - 20px); } .modal-header, - .create-modal form { + .legacy .create-modal form { padding-right: 16px; padding-left: 16px; } - .eligibility { + .legacy .eligibility { margin-right: 16px; margin-left: 16px; } - .artifact-header .task-id { + .legacy .artifact-header .task-id { overflow-wrap: anywhere; } - .login-card { + .legacy .login-card { padding: 22px; } - .current-password-block { + .legacy .current-password-block { grid-template-columns: 1fr; } - .current-password-block input { + .legacy .current-password-block input { grid-column: 1; grid-row: auto; margin-top: 5px; } - .settings-actions { + .legacy .settings-actions { align-items: stretch; flex-direction: column; } - .settings-actions button { + .legacy .settings-actions button { width: 100%; } - .skeleton-grid { + .legacy .skeleton-grid { grid-template-columns: 1fr; } } @media (max-width: 460px) { - .refresh-button span { + .legacy .refresh-button span { display: none; } - .search-field { + .legacy .search-field { width: 100%; } - .select-field { + .legacy .select-field { width: 100%; } - .metrics { + .legacy .metrics { grid-template-columns: 1fr 1fr; gap: 7px; } - .metric { + .legacy .metric { padding: 11px; } - .worker-stats { + .legacy .worker-stats { grid-template-columns: 1fr; } - .worker-stats > div { + .legacy .worker-stats > div { border-right: 0; border-bottom: 1px solid var(--line); } - .worker-stats > div:last-child { + .legacy .worker-stats > div:last-child { border-bottom: 0; } } diff --git a/web/src/styles/ethos.tokens.css b/web/src/styles/ethos.tokens.css new file mode 100644 index 0000000..db40891 --- /dev/null +++ b/web/src/styles/ethos.tokens.css @@ -0,0 +1,136 @@ +/* ═══════════════════════════════════════════════════════════════ + ETHOS — tokens + Shared design language for the kvmx.ru apps. + + The neutral system (ramp, type, motion, radii, shadow) is shared by + every app and does not change. The only per-app difference is the + accent (one hue) and the motif (one geometric signature). + + NEW APP: do not invent the accent silently. Ask the operator for the + accent name + hex and the motif, then add one [data-app] block at the + bottom using the TEMPLATE. Fonts are self-hosted (subset + woff2, + immutable caching) — no font CDN. + ═══════════════════════════════════════════════════════════════ */ + +@font-face { + font-family: 'Geist'; + src: url('/fonts/Geist-Variable.woff2') format('woff2'); + font-weight: 100 900; font-display: swap; +} +@font-face { + font-family: 'Geist Mono'; + src: url('/fonts/GeistMono-Variable.woff2') format('woff2'); + font-weight: 100 900; font-display: swap; +} + +:root { + /* ── neutral ramp — warm, brown-tinted (dark, default) ── */ + --bg-0: #14110D; /* deepest room */ + --bg-1: #1B1712; /* surface */ + --bg-2: #221D17; /* raised */ + --bg-3: #2C261D; /* hover / raised */ + --bg-4: #372F24; /* pressed / high */ + + --line: rgba(244, 234, 220, 0.09); /* hairline */ + --line-hi: rgba(244, 234, 220, 0.16); /* hairline emphasized */ + + --text-hi: #F4EEE4; /* human primary (sans) */ + --text-mid: #B4AA98; /* human secondary (sans) */ + --text-lo: #756C5C; /* human tertiary / idle */ + --text-machine: #9C917D; /* machine default (mono) */ + + /* ── type ── */ + --sans: 'Geist', -apple-system, system-ui, sans-serif; + --mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace; + + /* mono numerics: apply on any element using --mono */ + /* font-feature-settings: "tnum" 1, "zero" 1; letter-spacing: -0.01em; */ + + /* ── motion — mechanical, no bounce ── */ + --ease: cubic-bezier(0.2, 0, 0, 1); + --fast: 130ms; + --med: 170ms; + + /* ── radii ── */ + --r-sm: 8px; + --r-md: 12px; + --r-lg: 18px; + --r-xl: 24px; + + /* ── elevation: depth from light, never blur ── */ + --shadow-soft: 0 2px 8px rgba(0,0,0,0.35), 0 12px 32px rgba(0,0,0,0.28); + --shadow-lift: 0 4px 14px rgba(0,0,0,0.40), 0 20px 48px rgba(0,0,0,0.34); + + /* ── accent slot ── + Neutral fallback so an app with no [data-app] is never unstyled. + Real values come from the per-app block below. Accent is a SIGNAL + (active state, focus ring, primary action, one lit detail) — never a + fill-everything wash. */ + --accent: var(--text-mid); + --accent-hi: var(--text-hi); + --accent-dim: rgba(244, 234, 220, 0.10); + --accent-line: rgba(244, 234, 220, 0.24); + --accent-glow: rgba(244, 234, 220, 0.14); +} + +/* ── light theme — warm, off-white (kept off cream to dodge the AI-cream tell) ── */ +[data-theme="light"] { + --bg-0: #F1ECE3; + --bg-1: #EAE4D8; + --bg-2: #E2DACB; + --bg-3: #D7CDBB; + --bg-4: #C9BDA7; + + --line: rgba(28, 22, 14, 0.10); + --line-hi: rgba(28, 22, 14, 0.18); + + --text-hi: #1B1712; + --text-mid: #544C3E; + --text-lo: #877E6C; + --text-machine: #6B6252; + + --shadow-soft: 0 2px 8px rgba(60,45,25,0.10), 0 12px 32px rgba(60,45,25,0.08); + --shadow-lift: 0 4px 14px rgba(60,45,25,0.12), 0 20px 48px rgba(60,45,25,0.10); +} + +/* ═══════════════════════════════════════════════════════════════ + PER-APP OVERRIDES — one small block each. Accent + optional motif + hooks only; never touch the neutral ramp. + ═══════════════════════════════════════════════════════════════ */ + +/* muzick — reference app · honey amber · waveform */ +[data-app="muzick"] { + --accent: #EDA24E; + --accent-hi: #F5B667; + --accent-dim: rgba(237, 162, 78, 0.14); + --accent-line: rgba(237, 162, 78, 0.32); + --accent-glow: rgba(237, 162, 78, 0.22); + + /* motif hook: cool content-art gradient so art carries color, amber stays signal */ + --np-art: radial-gradient(120% 120% at 22% 14%, #7FB3BC 0%, #2F6E7A 42%, #1C4552 78%, #14262E 100%); +} + +/* ── TEMPLATE — copy for a new app AFTER intake with the operator ── +[data-app="APPNAME"] { + --accent: #RRGGBB; // the operator's chosen hue + --accent-hi: #RRGGBB; // ~ +10–14% lightness + --accent-dim: rgba(R, G, B, 0.14); // active-state backgrounds + --accent-line: rgba(R, G, B, 0.32); // accent hairlines + --accent-glow: rgba(R, G, B, 0.22); // restrained ambient pool + // optional motif hooks (gradients / seeds) go here, app-specific +} +*/ + +/* orchestra — signal violet · routing fork + Accent and motif come from orchestra-ui-spec.md, chosen by the operator. */ +[data-app="orchestra"] { + --accent: #8F7AE5; + --accent-hi: #A291EC; + --accent-dim: rgba(143, 122, 229, 0.14); + --accent-line: rgba(143, 122, 229, 0.32); + --accent-glow: rgba(143, 122, 229, 0.22); + + /* motif hook: the routing fork, one path splitting and rejoining. Used by + the app mark, phase transitions and empty states. */ + --fork-line: rgba(143, 122, 229, 0.55); +} diff --git a/web/src/styles/orchestra.css b/web/src/styles/orchestra.css new file mode 100644 index 0000000..6327329 --- /dev/null +++ b/web/src/styles/orchestra.css @@ -0,0 +1,679 @@ +/* Orchestra operator console — the ethos shell, applied. + Tokens come from ethos.tokens.css and are never redefined here. This file + holds the shared shell geometry and the primitives every screen reuses, so + nine screens cannot drift into nine dialects. */ + +@import './ethos.tokens.css'; + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body, +#root { + height: 100%; +} + +body { + margin: 0; + background: var(--bg-0); + color: var(--text-hi); + font-family: var(--sans); + font-size: 14px; + line-height: 1.45; + -webkit-font-smoothing: antialiased; +} + +/* Law 1. Anything the machine owns is mono, with tabular figures so columns + of ids, durations and percentages line up instead of shimmering. */ +.mono, +code, +kbd, +pre { + font-family: var(--mono); + font-feature-settings: 'tnum' 1, 'zero' 1; + letter-spacing: -0.01em; + color: var(--text-machine); +} + +/* Machine ids are long and unbreakable by default, so a pane id ran out of + its card and under the next one. Ids wrap; tables keep their own scroll. */ +.console .mono { + overflow-wrap: anywhere; +} + +.console .table .mono, +.console .topbar-readouts .mono { + overflow-wrap: normal; +} + +.console .label { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-lo); + font-weight: 500; +} + +.console h1 { + font-size: 28px; + line-height: 1.15; + letter-spacing: -0.02em; + margin: 0; + font-weight: 600; +} + +.console h2 { + font-size: 17px; + margin: 0; + font-weight: 600; + letter-spacing: -0.01em; +} + +.console a { + color: inherit; + text-decoration: none; +} + +.console button { + font: inherit; + color: inherit; +} + +/* ── shell ───────────────────────────────────────────────────────────── */ + +.console.shell { + display: grid; + grid-template-columns: 64px 1fr; + height: 100%; +} + +.console .rail { + background: var(--bg-1); + border-right: 1px solid var(--line); + display: flex; + flex-direction: column; + align-items: center; + padding: 14px 0 10px; + gap: 4px; +} + +.console .rail-mark { + color: var(--accent); + margin-bottom: 14px; +} + +.console .rail-item { + position: relative; + width: 44px; + height: 44px; + display: grid; + place-items: center; + border-radius: var(--r-sm); + color: var(--text-lo); + background: none; + border: 0; + cursor: pointer; + transition: color var(--fast) var(--ease), background var(--fast) var(--ease); +} + +.console .rail-item:hover { + color: var(--text-mid); + background: var(--bg-2); +} + +/* Accent marks the one active thing. It never fills the rail. */ +.console .rail-item[aria-current='page'] { + color: var(--accent); + background: var(--accent-dim); +} + +.console .rail-item[aria-current='page']::before { + content: ''; + position: absolute; + left: -10px; + top: 10px; + bottom: 10px; + width: 3px; + border-radius: 0 2px 2px 0; + background: var(--accent); +} + +.console .rail-spacer { + flex: 1; +} + +.console .topbar { + height: 56px; + display: flex; + align-items: center; + gap: 16px; + padding: 0 20px; + border-bottom: 1px solid var(--line); + background: var(--bg-1); +} + +.console .topbar-title { + font-weight: 600; + letter-spacing: -0.01em; +} + +.console .topbar-readouts { + margin-left: auto; + display: flex; + align-items: center; + gap: 18px; + font-family: var(--mono); + font-size: 12px; + color: var(--text-machine); +} + +.console .main { + display: grid; + grid-template-rows: 56px 1fr; + min-width: 0; + height: 100%; +} + +.console .page { + overflow-y: auto; + padding: 22px 24px 40px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.console .page-head { + display: flex; + align-items: flex-end; + gap: 14px; + flex-wrap: wrap; +} + +.console .page-head p { + margin: 0; + color: var(--text-mid); +} + +/* ── command surface ─────────────────────────────────────────────────── */ + +.console .cmdk-trigger { + display: flex; + align-items: center; + gap: 10px; + height: 34px; + min-width: 260px; + max-width: 520px; + flex: 1; + padding: 0 12px; + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + color: var(--text-lo); + cursor: pointer; + transition: border-color var(--fast) var(--ease); +} + +.console .cmdk-trigger:hover { + border-color: var(--line-hi); +} + +kbd { + font-size: 11px; + padding: 2px 6px; + border: 1px solid var(--line); + border-radius: 5px; + background: var(--bg-1); +} + +.console .cmdk-scrim { + position: fixed; + inset: 0; + background: rgba(10, 8, 6, 0.62); + display: grid; + place-items: start center; + padding-top: 12vh; + z-index: 40; +} + +.console .cmdk-panel { + width: min(620px, 92vw); + background: var(--bg-1); + border: 1px solid var(--line-hi); + border-radius: var(--r-md); + box-shadow: var(--shadow-soft); + overflow: hidden; +} + +.console .cmdk-input { + width: 100%; + height: 50px; + padding: 0 16px; + background: none; + border: 0; + border-bottom: 1px solid var(--line); + color: var(--text-hi); + font: inherit; + outline: none; +} + +.console .cmdk-list { + max-height: 46vh; + overflow-y: auto; + padding: 6px; +} + +.console .cmdk-row { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 9px 10px; + border: 0; + border-radius: var(--r-sm); + background: none; + text-align: left; + cursor: pointer; +} + +.console .cmdk-row[data-active='true'] { + background: var(--bg-3); +} + +/* ── panels and tables ───────────────────────────────────────────────── */ + +.console .panel { + background: var(--bg-1); + border: 1px solid var(--line); + border-radius: var(--r-md); +} + +.console .panel-head { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 18px; + border-bottom: 1px solid var(--line); +} + +.console .panel-head .count { + font-family: var(--mono); + font-size: 11px; + padding: 1px 7px; + border-radius: 999px; + background: var(--bg-3); + color: var(--text-machine); +} + +.console .panel-head .trailing { + margin-left: auto; + color: var(--text-lo); + font-size: 13px; +} + +.console .panel-body { + padding: 14px 18px; +} + +.console .rows { + display: flex; + flex-direction: column; +} + +.console .row { + display: flex; + align-items: center; + gap: 16px; + padding: 12px 18px; + border-bottom: 1px solid var(--line); + text-align: left; + background: none; + border-left: 0; + border-right: 0; + border-top: 0; + width: 100%; + cursor: pointer; + transition: background var(--fast) var(--ease); +} + +.console .row:last-child { + border-bottom: 0; +} + +.console .row:hover { + background: var(--bg-2); +} + +.console .row-main { + min-width: 0; + flex: 1; +} + +.console .row-title { + font-weight: 500; +} + +.console .row-sub { + color: var(--text-mid); + font-size: 13px; +} + +.console .table { + width: 100%; + border-collapse: collapse; +} + +.console .table th { + text-align: left; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-lo); + font-weight: 500; + padding: 10px 14px; + border-bottom: 1px solid var(--line); + white-space: nowrap; +} + +.console .table td { + padding: 12px 14px; + border-bottom: 1px solid var(--line); + vertical-align: middle; +} + +.console .table tbody tr:last-child td { + border-bottom: 0; +} + +.console .table tbody tr:hover { + background: var(--bg-2); +} + +.console .table-scroll { + overflow-x: auto; +} + +/* ── status, phases, values ──────────────────────────────────────────── */ + +.console .dot { + width: 7px; + height: 7px; + border-radius: 50%; + display: inline-block; + flex: none; +} + +.console .dot[data-health='healthy'] { + background: #5fbf82; +} + +.console .dot[data-health='degraded'] { + background: #d8a657; +} + +.console .dot[data-health='error'] { + background: #e06c60; +} + +.console .dot[data-health='unknown'] { + background: var(--text-lo); +} + +.console .chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 9px; + border-radius: 6px; + font-size: 12px; + border: 1px solid var(--line); + background: var(--bg-2); + color: var(--text-mid); + white-space: nowrap; +} + +.console .chip[data-tone='accent'] { + border-color: var(--accent-line); + background: var(--accent-dim); + color: var(--accent-hi); +} + +.console .chip[data-tone='warn'] { + border-color: rgba(216, 166, 87, 0.32); + background: rgba(216, 166, 87, 0.12); + color: #d8a657; +} + +.console .chip[data-tone='fault'] { + border-color: rgba(224, 108, 96, 0.32); + background: rgba(224, 108, 96, 0.12); + color: #e06c60; +} + +.console .chip[data-tone='done'] { + border-color: rgba(95, 191, 130, 0.28); + background: rgba(95, 191, 130, 0.1); + color: #5fbf82; +} + +/* The routing fork, rendered as the phase path. Completed is quiet green, + the current phase carries the accent, the future stays neutral. */ +.console .phases { + display: flex; + align-items: center; + gap: 0; +} + +.console .phase-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 3px; + min-width: 46px; +} + +.console .phase-node { + width: 9px; + height: 9px; + border-radius: 50%; + border: 1.6px solid var(--text-lo); +} + +.console .phase-step[data-state='done'] .phase-node { + border-color: #5fbf82; + background: rgba(95, 191, 130, 0.35); +} + +.console .phase-step[data-state='current'] .phase-node { + border-color: var(--accent); + background: var(--accent); + box-shadow: 0 0 0 3px var(--accent-dim); +} + +.console .phase-step .phase-name { + font-size: 9px; + color: var(--text-lo); + letter-spacing: 0.04em; +} + +.console .phase-step[data-state='current'] .phase-name { + color: var(--accent-hi); +} + +.console .phase-link { + height: 1.6px; + width: 18px; + background: var(--line-hi); + margin-bottom: 12px; +} + +.console .phase-link[data-state='done'] { + background: rgba(95, 191, 130, 0.4); +} + +.console .meter { + height: 4px; + border-radius: 2px; + background: var(--bg-3); + overflow: hidden; + min-width: 64px; +} + +.console .meter > span { + display: block; + height: 100%; + background: var(--accent); +} + +/* ── actions ─────────────────────────────────────────────────────────── */ + +.console .btn { + display: inline-flex; + align-items: center; + gap: 8px; + height: 32px; + padding: 0 13px; + border-radius: var(--r-sm); + border: 1px solid var(--line); + background: var(--bg-2); + color: var(--text-hi); + cursor: pointer; + transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease); +} + +.console .btn:hover { + background: var(--bg-3); + border-color: var(--line-hi); +} + +.console .btn[data-variant='primary'] { + background: var(--accent-dim); + border-color: var(--accent-line); + color: var(--accent-hi); +} + +.console .btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: 4px; +} + +/* ── empty, error, gap ───────────────────────────────────────────────── */ + +.console .empty { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + padding: 44px 20px; + text-align: center; + color: var(--text-mid); +} + +.console .empty .motif { + color: var(--accent); + opacity: 0.55; +} + +.console .empty h3 { + margin: 0; + font-size: 15px; + color: var(--text-hi); + font-weight: 600; +} + +/* A screen with no endpoint says so, with the path it would call. Law 5: an + interface never implies data it does not have. */ +.console .gap-note { + font-family: var(--mono); + font-size: 12px; + color: var(--text-machine); + background: var(--bg-2); + border: 1px dashed var(--line-hi); + border-radius: var(--r-sm); + padding: 8px 12px; +} + +.console .grid { + display: grid; + gap: 14px; +} + +.console .stats { + grid-template-columns: repeat(auto-fit, minmax(184px, 1fr)); +} + +.console .stat { + background: var(--bg-1); + border: 1px solid var(--line); + border-radius: var(--r-md); + padding: 14px 16px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.console .stat-value { + font-family: var(--mono); + font-size: 26px; + color: var(--text-hi); + letter-spacing: -0.02em; +} + +/* ── mobile: rail becomes a bottom tab bar, readouts relocate ────────── */ + +@media (max-width: 640px) { + .console.shell { + grid-template-columns: 1fr; + grid-template-rows: 1fr auto; + } + + .console .rail { + order: 2; + flex-direction: row; + justify-content: space-around; + width: 100%; + padding: 4px 0; + border-right: 0; + border-top: 1px solid var(--line); + } + + .rail-mark, + .rail-spacer, + .console .rail-item[data-secondary='true'] { + display: none; + } + + .console .rail-item[aria-current='page']::before { + left: 10px; + right: 10px; + top: -4px; + bottom: auto; + width: auto; + height: 3px; + border-radius: 0 0 2px 2px; + } + + .console .topbar-readouts { + display: none; + } + + .console .page { + padding: 16px 14px 28px; + } +} + +@media (prefers-reduced-motion: reduce) { + * { + transition: none !important; + animation: none !important; + } +}