@rig-c/review 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/styles.css CHANGED
@@ -1,17 +1,20 @@
1
- :root{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#e3eceb;background:#10181c;font-size:13px;color-scheme:dark;font-synthesis:none}*{box-sizing:border-box}body{margin:0}button,input,select,textarea{font:inherit}button{color:inherit;background:#26383f;border:1px solid #3a535f;border-radius:6px;padding:7px 10px;cursor:pointer}button:hover{background:#344d58}button:disabled{opacity:.45;cursor:default}button[aria-pressed=true]{background:#355445;border-color:#729985;color:#dcf4e5}button.primary{background:#b1dec6;border-color:#b1dec6;color:#163728;font-weight:600}button.outline{background:transparent}button.block{width:100%;margin-top:10px}a{color:#b2dccb}input,select,textarea{color:#e3eceb;background:#12242d;border:1px solid #486472;border-radius:6px;padding:8px;max-width:100%}select,input[type=text],textarea{width:100%}textarea{resize:vertical;line-height:1.6}input[type=range]{accent-color:#b2dec7;padding:0;min-width:0}input[type=checkbox],input[type=radio]{accent-color:#b2dec7}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid #c6e7d7;outline-offset:3px}[hidden]{display:none!important}.topbar{min-height:72px;padding:15px 22px;display:flex;align-items:center;gap:25px;border-bottom:1px solid #30464f;flex-wrap:wrap;background:#15232a}.brand{font-size:19px;letter-spacing:-.6px;white-space:nowrap}.brand-mark{display:inline-grid;place-items:center;width:24px;height:24px;border:1px solid #afdcc5;color:#afdcc5;border-radius:50%;margin-right:9px;font-size:14px}.topbar strong{font-weight:500;font-size:13px}.topbar #revision{margin-left:9px}.top-actions{display:flex;align-items:center;gap:8px;margin-left:auto;flex-wrap:wrap}.subtle,.hint{color:#a2bac4;font-size:11px}.hint{line-height:1.6}.workspaces{display:flex;gap:10px;padding:0 18px;border-bottom:1px solid #30464f;background:#142129}.workspaces button{border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;padding:15px 13px;font-size:12px;letter-spacing:.06em;color:#a6bec8}.workspaces button[aria-pressed=true]{border-bottom-color:#b1dec6;color:#c8ead8;background:transparent}.workspaces button span{font-size:11px;margin-left:8px;letter-spacing:0;color:#8faab6}.layout{display:grid;grid-template-columns:190px minmax(250px,1fr) 286px;min-height:calc(100vh - 160px)}aside{background:#182a33;padding:20px 15px}.left-panel{border-right:1px solid #30464f}.right-panel{border-left:1px solid #30464f}.eyebrow{font-size:11px;letter-spacing:.08em;color:#aac0c8;margin:0 0 11px}.label{display:block;color:#acbec8;font-size:11px;margin:0 0 7px}.space-top{margin-top:15px}.left-panel section{margin-top:26px}.tool{width:100%;display:flex;justify-content:space-between;align-items:center;text-align:left;gap:5px;border-color:transparent;background:transparent;padding:9px 6px;margin:3px 0}.tool small{font-size:11px;color:#9bb4bf}.checkpoint{display:block;width:100%;background:transparent;border:1px solid transparent;text-align:left;margin:4px 0;padding:10px 9px;white-space:normal;line-height:1.5}.checkpoint[aria-current=true]{background:#294638;border-color:#658d76}.checkpoint small{display:block;font-size:11px;color:#9eb8aa;margin-top:3px}.source-link{display:block;font-size:11px;overflow-wrap:anywhere;margin:8px 0}main{min-width:0;display:flex;flex-direction:column;background:#12232c}.viewbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:12px;border-bottom:1px solid #2e4855}.segmented{display:flex;gap:3px;padding:3px;border:1px solid #3a5663;border-radius:8px;background:#11202a}.segmented button{border:0;background:transparent;font-size:11px;padding:5px 9px}.segmented button[aria-pressed=true]{background:#395560;color:#e3eceb}.zoom-controls{display:flex;gap:4px;align-items:center}.zoom-controls button{padding:4px 8px;font-size:12px}.zoom-controls output{font:11px ui-monospace,SFMono-Regular,monospace;min-width:39px;text-align:center;color:#abc1cc}.stage-meta{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px 16px 0;font-size:11px;flex-wrap:wrap}.stages{display:flex;gap:10px;padding:12px 12px 0;flex:1;min-height:460px}.stage-pane{position:relative;min-width:0;flex:1;border:1px solid #35535f;border-radius:8px;overflow:hidden;background:radial-gradient(ellipse at 50% 40%,#233d48,#10222c 74%)}.pane-label{position:absolute;left:10px;top:10px;z-index:2;background:#152a35d9;border-radius:4px;padding:4px 7px;color:#c0d4db;font-size:11px;pointer-events:none}.viewport{position:relative;overflow:hidden;width:100%;height:100%;min-height:450px;touch-action:none;background-image:radial-gradient(#8eb2c333 .7px,transparent .7px);background-size:22px 22px}.empty{position:absolute;inset:65px 22px 30px;display:grid;place-items:center;text-align:center;color:#9dbac7;font-size:12px;line-height:1.8}.reference-content,#rig-root{position:absolute;transform-origin:0 0}.reference-content img,.reference-content video{display:block;max-width:none}.reference-content svg{display:block;max-width:none}.reference-content video{object-fit:contain}.stage-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:11px 16px;color:#90afbd;font-size:11px}.transport{display:flex;align-items:center;gap:11px;padding:13px;border-top:1px solid #35515e;background:#162e3b}.transport select{width:auto;max-width:150px;font-size:11px}.transport input{flex:1}.transport output{font:11px ui-monospace,SFMono-Regular,monospace;white-space:nowrap;color:#c1d7df}.sound-panel,.angle-panel{padding:15px;border-top:1px solid #35515e;background:#172f3b}.row{display:flex;align-items:center;gap:11px;flex-wrap:wrap}.row .eyebrow{margin:0}.sound-panel .row{font-size:11px;margin-bottom:9px}.sound-panel input[type=range]{flex:1;max-width:160px}.cue{display:flex;gap:9px;align-items:center;justify-content:space-between;border-top:1px solid #35505c;padding:9px 0;font-size:11px;flex-wrap:wrap}.cue button{padding:4px 7px;font-size:11px}.angle-panel{display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:11px}.angle-panel select{width:auto;max-width:150px}.angle-panel .hint{width:100%;margin:0}.angle-panel input{flex:1}.divider{height:1px;background:#38515e;margin:22px 0}h1{font-size:15px;font-weight:500;line-height:1.6;margin:0 0 7px}.outcome{border:0;padding:0;margin:18px 0 0;display:flex;gap:11px;flex-wrap:wrap}.outcome legend{font-size:11px;color:#aec6cf;margin-bottom:8px;padding:0}.outcome label{display:flex;align-items:center;gap:3px;font-size:11px}.proposal-item{border-top:1px solid #3b5664;padding:11px 0;margin-top:12px;font-size:11px;overflow-wrap:anywhere}.proposal-item strong{display:block;font-weight:500;margin-bottom:5px}.proposal-item button{font-size:11px;padding:3px 6px;margin-top:7px}footer{display:flex;justify-content:space-between;gap:15px;flex-wrap:wrap;padding:11px 18px;border-top:1px solid #344e5a;background:#14242c;font-size:11px;color:#b4cad3}.error{color:#f0baac!important}.dirty{color:#e4c99a!important}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
1
+ /* Theme tokens: site values copied from rig-c.org styles.css (dark); light and --rv-* are review-local derivations. No literal colour outside these blocks. */
2
+ :root{--rv-region-a:#ff9f43;--rv-region-a-fill:rgba(255,159,67,.18);--rv-region-b:#4dd6ff;--rv-region-b-fill:rgba(77,214,255,.18);--font-sans:Inter,system-ui,sans-serif;--font-display:Inter,system-ui,sans-serif;--font-mono:"JetBrains Mono",ui-monospace,monospace;--radius:10px;--bg:#070b18;--fg:#c9d4e8;--fg-strong:#f3f6fc;--fg-soft:#aebbd4;--fg-muted:#7f8ba6;--border:#18213a;--border-strong:#2a3657;--hover-bg:#0f1730;--active-bg:#142042;--link:#7cc8ff;--link-hover:#b3e0ff;--btn-fg:#08101f;--code-bg:#050814;--code-fg:#d6e2f5;--code-border:#18213a;--code-inline-bg:#121a30;--footer-bg:#050813;--pass:#30d158;--warn:#ffcc00;--fail:#ff453a;--rv-bar:#0b1124;--rv-panel:#0d152b;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:var(--active-bg);--rv-field:var(--code-bg);--rv-segmented:var(--code-bg);--rv-pressed-bg:#133733;--rv-pressed-border:#207c44;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link-hover);--rv-backdrop:rgba(7,11,24,.82);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020;--rv-scrollbar-thumb:#2a3657;--rv-scrollbar-thumb-hover:#3d4c74;--rv-scrollbar-track:transparent;--rv-separator:#1a2747;--rv-separator-hover:#3f7fb8}
3
+ :root[data-theme=light]{--rv-region-a:#ff9f43;--rv-region-a-fill:rgba(255,159,67,.18);--rv-region-b:#4dd6ff;--rv-region-b-fill:rgba(77,214,255,.18);--bg:#f5f7fb;--fg:#26324a;--fg-strong:#0c1324;--fg-soft:#3b4864;--fg-muted:#5d6982;--border:#d5dcea;--border-strong:#b3bed3;--hover-bg:#e8edf6;--active-bg:#dbe3f3;--link:#1a6fc0;--link-hover:#0f528f;--btn-fg:#ffffff;--code-bg:#eef1f7;--code-fg:#1d2940;--code-border:#d5dcea;--code-inline-bg:#e3e8f2;--footer-bg:#eceff6;--pass:#1e8a3c;--warn:#8a6400;--fail:#b3261e;--rv-bar:#edf1f7;--rv-panel:#e9eef6;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:#d5ddec;--rv-field:#ffffff;--rv-segmented:var(--code-inline-bg);--rv-pressed-bg:#c9ded8;--rv-pressed-border:#6fb286;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link);--rv-backdrop:rgba(12,19,36,.45);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020;--rv-scrollbar-thumb:#b3bed3;--rv-scrollbar-thumb-hover:#8e9bb5;--rv-scrollbar-track:transparent;--rv-separator:#c9d6ec;--rv-separator-hover:#5b93cf}
4
+ :root{font-family:var(--font-sans);color:var(--fg);background:var(--bg);font-size:13px;color-scheme:dark;font-synthesis:none}*{box-sizing:border-box}body{margin:0}button,input,select,textarea{font:inherit}button{color:inherit;background:var(--rv-control);border:1px solid var(--border-strong);border-radius:6px;padding:7px 10px;cursor:pointer}button:hover{background:var(--rv-control-hover)}button:disabled{opacity:.45;cursor:default}button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}button.primary{background:var(--link);border-color:var(--link);color:var(--btn-fg);font-weight:600}button.outline{background:transparent}button.block{width:100%;margin-top:10px}a{color:var(--link)}input,select,textarea{color:var(--fg);background:var(--rv-field);border:1px solid var(--border-strong);border-radius:6px;padding:8px;max-width:100%}select,input[type=text],textarea{width:100%}textarea{resize:vertical;line-height:1.6}input[type=range]{accent-color:var(--link);padding:0;min-width:0}input[type=checkbox],input[type=radio]{accent-color:var(--link)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--rv-focus);outline-offset:3px}[hidden]{display:none!important}.topbar{min-height:72px;padding:15px 22px;display:flex;align-items:center;gap:25px;border-bottom:1px solid var(--border);flex-wrap:wrap;background:var(--rv-bar)}.brand{font-size:19px;letter-spacing:-.6px;white-space:nowrap}.brand-mark{display:inline-grid;place-items:center;width:24px;height:24px;border:1px solid var(--link);color:var(--link);border-radius:50%;margin-right:9px;font-size:14px}.topbar strong{font-weight:500;font-size:13px}.topbar #revision{margin-left:9px}.top-actions{display:flex;align-items:center;gap:8px;margin-left:auto;flex-wrap:wrap}.subtle,.hint{color:var(--fg-muted);font-size:11px}.hint{line-height:1.6}.workspaces{display:flex;gap:10px;padding:0 18px;border-bottom:1px solid var(--border);background:var(--rv-bar)}.workspaces button{border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;padding:15px 13px;font-size:12px;letter-spacing:.06em;color:var(--fg-soft)}.workspaces button[aria-pressed=true]{border-bottom-color:var(--link);color:var(--fg-strong);background:transparent}.workspaces button span{font-size:11px;margin-left:8px;letter-spacing:0;color:var(--fg-muted)}.layout{display:grid;grid-template-columns:190px minmax(250px,1fr) 286px;min-height:calc(100vh - 160px)}aside{background:var(--rv-panel);padding:20px 15px}.left-panel{border-right:1px solid var(--border)}.right-panel{border-left:1px solid var(--border)}.eyebrow{font-size:11px;letter-spacing:.08em;color:var(--fg-muted);margin:0 0 11px}.label{display:block;color:var(--fg-muted);font-size:11px;margin:0 0 7px}.space-top{margin-top:15px}.left-panel section{margin-top:26px}.tool{width:100%;display:flex;justify-content:space-between;align-items:center;text-align:left;gap:5px;border-color:transparent;background:transparent;padding:9px 6px;margin:3px 0}.tool small{font-size:11px;color:var(--fg-muted)}.checkpoint{display:block;width:100%;background:transparent;border:1px solid transparent;text-align:left;margin:4px 0;padding:10px 9px;white-space:normal;line-height:1.5}.checkpoint[aria-current=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border)}.checkpoint small{display:block;font-size:11px;color:var(--fg-muted);margin-top:3px}.source-link{display:block;font-size:11px;overflow-wrap:anywhere;margin:8px 0}main{min-width:0;display:flex;flex-direction:column;background:var(--bg)}.viewbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:12px;border-bottom:1px solid var(--border)}.zoom-controls{display:flex;gap:4px;align-items:center}.zoom-controls button{padding:4px 8px;font-size:12px}.zoom-controls output{font:11px var(--font-mono);min-width:39px;text-align:center;color:var(--fg-muted)}.stages{display:flex;gap:10px;padding:12px 12px 0;flex:1;min-height:460px}.stage-pane{position:relative;min-width:0;flex:1;border:1px solid var(--border-strong);border-radius:8px;overflow:hidden;background:radial-gradient(ellipse at 50% 40%,var(--rv-pane-hi),var(--rv-pane-lo) 74%)}.pane-label{position:absolute;left:10px;top:10px;z-index:2;background:var(--rv-pane-label-bg);border-radius:4px;padding:4px 7px;color:var(--rv-stage-fg);font-size:11px;pointer-events:none}.viewport{position:relative;overflow:hidden;width:100%;height:100%;min-height:450px;touch-action:none;background-image:radial-gradient(var(--rv-grid-dot) .7px,transparent .7px);background-size:22px 22px}.empty{position:absolute;inset:65px 22px 30px;display:grid;place-items:center;text-align:center;color:var(--rv-stage-fg);font-size:12px;line-height:1.8}.reference-content,#rig-root{position:absolute;transform-origin:0 0}.reference-content img,.reference-content video{display:block;max-width:none}.reference-content svg{display:block;max-width:none}.reference-content video{object-fit:contain}.stage-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:11px 16px;color:var(--fg-muted);font-size:11px}.transport{display:flex;align-items:center;gap:11px;padding:13px;border-top:1px solid var(--border);background:var(--rv-panel)}.transport select{width:auto;max-width:150px;font-size:11px}.transport input{flex:1}.transport output{font:11px var(--font-mono);white-space:nowrap;color:var(--fg-muted)}.sound-panel,.angle-panel{padding:15px;border-top:1px solid var(--border);background:var(--rv-panel)}.row{display:flex;align-items:center;gap:11px;flex-wrap:wrap}.row .eyebrow{margin:0}.sound-panel .row{font-size:11px;margin-bottom:9px}.sound-panel input[type=range]{flex:1;max-width:160px}.cue{display:flex;gap:9px;align-items:center;justify-content:space-between;border-top:1px solid var(--border);padding:9px 0;font-size:11px;flex-wrap:wrap}.cue button{padding:4px 7px;font-size:11px}.angle-panel{display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:11px}.angle-panel select{width:auto;max-width:150px}.angle-panel .hint{width:100%;margin:0}.angle-panel input{flex:1}.divider{height:1px;background:var(--border-strong);margin:22px 0}h1{font-size:15px;font-weight:500;line-height:1.6;margin:0 0 7px}.outcome{border:0;padding:0;margin:18px 0 0;display:flex;gap:11px;flex-wrap:wrap}.outcome legend{font-size:11px;color:var(--fg-muted);margin-bottom:8px;padding:0}.outcome label{display:flex;align-items:center;gap:3px;font-size:11px}.proposal-item{border-top:1px solid var(--border);padding:11px 0;margin-top:12px;font-size:11px;overflow-wrap:anywhere}.proposal-item strong{display:block;font-weight:500;margin-bottom:5px}.proposal-item button{font-size:11px;padding:3px 6px;margin-top:7px}footer{display:flex;justify-content:space-between;align-items:center;gap:6px 15px;flex-wrap:nowrap;height:40px;box-sizing:border-box;overflow:hidden;padding:0 18px;border-top:1px solid var(--border);background:var(--footer-bg);font-size:11px;color:var(--fg-soft)}.error{color:var(--fail)!important}.dirty{color:var(--warn)!important}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
2
5
  @media(min-width:1500px){.layout{grid-template-columns:218px minmax(250px,1fr) 310px}.stages{min-height:600px}}
3
6
  @media(max-width:1000px){.layout{grid-template-columns:158px minmax(250px,1fr) 250px}aside{padding:17px 12px}.topbar{gap:14px}.topbar #revision{display:block;margin-left:0}.stages{min-height:400px}.viewport{min-height:400px}.workspaces span{display:none}}
4
- @media(max-width:780px){.layout{grid-template-columns:150px minmax(0,1fr)}.right-panel{grid-column:1/-1;border-left:0;border-top:1px solid #30464f;display:grid;grid-template-columns:1fr 1fr;gap:12px 24px}.right-panel>.eyebrow,.right-panel>.label,.right-panel>#review-status{grid-column:1/-1}.right-panel>.divider{display:none}#checkpoint-detail,#proposal-panel{min-width:0}.top-actions{margin-left:0}.stages{min-height:420px}.viewport{min-height:420px}.transport{flex-wrap:wrap}.transport input{min-width:130px}.topbar{padding:14px 16px}}
5
- @media(max-width:540px){.layout{grid-template-columns:1fr}.left-panel{border-right:0;border-bottom:1px solid #30464f;display:grid;grid-template-columns:1fr 1fr;gap:12px}.left-panel>.eyebrow,.left-panel>#checkpoints{grid-column:1/-1}.left-panel section{margin-top:8px}#checkpoints{display:flex;gap:5px;flex-wrap:wrap}.checkpoint{width:auto;flex:1;min-width:120px}.right-panel{display:block}#proposal-panel{margin-top:25px}.stages{gap:6px;padding:8px}.stage-pane{border-radius:5px}.viewport{min-height:365px}.stages{min-height:365px}.pane-label{left:5px;top:6px;font-size:11px;padding:3px}.viewbar{gap:8px}.workspaces{padding:0 8px}.workspaces button{padding:13px 9px}.stage-footer{padding:9px 12px}.source-link{font-size:11px}footer{padding:11px 12px}}
7
+ @media(max-width:780px){.layout{grid-template-columns:150px minmax(0,1fr)}.right-panel{grid-column:1/-1;border-left:0;border-top:1px solid var(--border);display:grid;grid-template-columns:1fr 1fr;gap:12px 24px}.right-panel>.eyebrow,.right-panel>.label,.right-panel>#review-status{grid-column:1/-1}.right-panel>.divider{display:none}#checkpoint-detail,#proposal-panel{min-width:0}.top-actions{margin-left:0}.stages{min-height:420px}.viewport{min-height:420px}.transport{flex-wrap:wrap}.transport input{min-width:130px}.topbar{padding:14px 16px}}
8
+ @media(max-width:540px){.layout{grid-template-columns:1fr}.left-panel{border-right:0;border-bottom:1px solid var(--border);display:grid;grid-template-columns:1fr 1fr;gap:12px}.left-panel>.eyebrow,.left-panel>#checkpoints{grid-column:1/-1}.left-panel section{margin-top:8px}#checkpoints{display:flex;gap:5px;flex-wrap:wrap}.checkpoint{width:auto;flex:1;min-width:120px}.right-panel{display:block}#proposal-panel{margin-top:25px}.stages{gap:6px;padding:8px}.stage-pane{border-radius:5px}.viewport{min-height:365px}.stages{min-height:365px}.pane-label{left:5px;top:6px;font-size:11px;padding:3px}.viewbar{gap:8px}.workspaces{padding:0 8px}.workspaces button{padding:13px 9px}.stage-footer{padding:9px 12px}.source-link{font-size:11px}footer{padding:0 12px}}
6
9
  @media(pointer:coarse){button{min-height:42px}input,select,textarea{font-size:16px}.outcome label{min-height:40px}.zoom-controls button{min-width:38px}}
7
10
 
8
- .frame-range{display:grid;grid-template-columns:1fr 1fr;gap:9px}.frame-range label{font-size:11px;color:#aec6cf}.frame-range input{width:100%;margin-top:6px}
11
+ .frame-range{display:grid;grid-template-columns:1fr 1fr;gap:9px}.frame-range label{font-size:11px;color:var(--fg-muted)}.frame-range input{width:100%;margin-top:6px}
9
12
 
10
- .provenance{margin-top:20px;padding:12px;background:#1d3540;border:1px solid #45616c;border-radius:7px}.provenance-row{display:flex;gap:9px;justify-content:space-between;font-size:11px;margin:6px 0}.provenance-row span{color:#a9c0ca}.provenance-row strong{font-weight:500;overflow-wrap:anywhere}.provenance code{font:11px ui-monospace,SFMono-Regular,monospace}.provenance details{margin-top:10px;font-size:11px;color:#b9d0d9}.provenance summary{cursor:pointer}.provenance pre{white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.6 ui-monospace,SFMono-Regular,monospace}.right-panel>.provenance{grid-column:1/-1}
13
+ .provenance{margin-top:20px;padding:12px;background:var(--rv-raised);border:1px solid var(--border-strong);border-radius:7px}.provenance-row{display:flex;gap:9px;justify-content:space-between;font-size:11px;margin:6px 0}.provenance-row span{color:var(--fg-muted)}.provenance-row strong{font-weight:500;overflow-wrap:anywhere}.provenance code{font:11px var(--font-mono)}.provenance details{margin-top:10px;font-size:11px;color:var(--fg-muted)}.provenance summary{cursor:pointer}.provenance pre{white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.6 var(--font-mono)}.right-panel>.provenance{grid-column:1/-1}
11
14
 
12
- .report-panel{padding:15px;border-top:1px solid #35515e;background:#142a35}.report-header{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.report-header h2{font-size:13px;font-weight:500;margin:0}.report-header a{font-size:11px;margin-left:auto}.report-controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:12px}.report-controls select{width:auto;max-width:250px;flex:1;font-size:11px}.report-controls button{font-size:11px;padding:5px 9px}.report-controls output{font:11px ui-monospace,SFMono-Regular,monospace;color:#b3cbd5}.report-empty-categories{display:flex;gap:6px;flex-wrap:wrap;margin-top:11px}.report-empty-categories span{font-size:11px;color:#a8c2ce;background:#213943;padding:2px 6px;border-radius:4px}.report-event-list{list-style:none;padding:0;margin:10px 0 0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px}.report-event-list li{min-width:0}.report-event-list button{display:flex;flex-direction:column;gap:2px;text-align:left;width:100%;height:100%;padding:8px 9px;border-color:#304b58;background:#192f3b;white-space:normal;overflow-wrap:anywhere;line-height:1.5}.report-event-list button[aria-current=true]{border-color:#88b39a;background:#294537}.report-event-list strong{font-size:11px;font-weight:500}.report-event-list small{font-size:11px;color:#a4bdc8}.report-event-list .no-time{color:#dfc49c}.report-selection{border-top:1px solid #3e5b68;margin-top:12px;padding-top:12px}.report-selection h3{font-size:12px;font-weight:500;margin:0}.report-selection details{font-size:11px;color:#aac3ce;margin-top:10px}.report-selection summary{cursor:pointer}.report-selection pre{white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.6 ui-monospace,SFMono-Regular,monospace;background:#11232d;padding:10px;border-radius:5px}.report-selection #report-time{max-width:100%}@media(max-width:1000px){.report-event-list{grid-template-columns:1fr}}@media(max-width:540px){.report-controls select{min-width:140px}.report-header a{margin-left:0}}
15
+ .report-panel{padding:15px;border-top:1px solid var(--border);background:var(--rv-panel)}.report-header{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.report-header h2{font-size:13px;font-weight:500;margin:0}.report-header a{font-size:11px;margin-left:auto}.report-controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:12px}.report-controls select{width:auto;max-width:250px;flex:1;font-size:11px}.report-controls button{font-size:11px;padding:5px 9px}.report-controls output{font:11px var(--font-mono);color:var(--fg-muted)}.report-empty-categories{display:flex;gap:6px;flex-wrap:wrap;margin-top:11px}.report-empty-categories span{font-size:11px;color:var(--fg-muted);background:var(--rv-raised);padding:2px 6px;border-radius:4px}.report-event-list{list-style:none;padding:0;margin:10px 0 0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px}.report-event-list li{min-width:0}.report-event-list button{display:flex;flex-direction:column;gap:2px;text-align:left;width:100%;height:100%;padding:8px 9px;border-color:var(--border);background:var(--rv-raised);white-space:normal;overflow-wrap:anywhere;line-height:1.5}.report-event-list button[aria-current=true]{border-color:var(--rv-pressed-border);background:var(--rv-pressed-bg)}.report-event-list strong{font-size:11px;font-weight:500}.report-event-list small{font-size:11px;color:var(--fg-muted)}.report-event-list .no-time{color:var(--warn)}.report-selection{border-top:1px solid var(--border);margin-top:12px;padding-top:12px}.report-selection h3{font-size:12px;font-weight:500;margin:0}.report-selection details{font-size:11px;color:var(--fg-muted);margin-top:10px}.report-selection summary{cursor:pointer}.report-selection pre{white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.6 var(--font-mono);background:var(--code-bg);padding:10px;border-radius:5px}.report-selection #report-time{max-width:100%}@media(max-width:1000px){.report-event-list{grid-template-columns:1fr}}@media(max-width:540px){.report-controls select{min-width:140px}.report-header a{margin-left:0}}
13
16
 
14
- .transport #frame-label{color:#c6e6d4;font-size:11px}.frame-controls{display:flex;align-items:center;gap:9px;flex-wrap:wrap;padding:10px 14px;background:#17303b;border-top:1px solid #35515e;font-size:11px}.frame-controls label{color:#b1c9d2}.frame-controls input[type=number]{width:86px;font-variant-numeric:tabular-nums}.frame-controls input[type=range]{flex:1;min-width:100px}.frame-controls button{font-size:11px;padding:5px 9px}.frame-controls .hint{margin:0}.frame-controls #frame-outside{width:100%}
17
+ .transport #frame-label{color:var(--fg-strong);font-size:11px}.frame-controls{display:flex;align-items:center;gap:9px;flex-wrap:wrap;padding:10px 14px;background:var(--rv-panel);border-top:1px solid var(--border);font-size:11px}.frame-controls label{color:var(--fg-muted)}.frame-controls input[type=number]{width:86px;font-variant-numeric:tabular-nums}.frame-controls input[type=range]{flex:1;min-width:100px}.frame-controls button{font-size:11px;padding:5px 9px}.frame-controls .hint{margin:0}.frame-controls #frame-outside{width:100%}
15
18
 
16
19
  /* Inspection layout is bounded independently of pane content and review forms. */
17
20
  #stage-layout{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:minmax(0,1fr);height:clamp(480px,calc(100dvh - 300px),900px);min-height:0;flex:none;align-items:stretch}
@@ -21,37 +24,20 @@
21
24
  #stage-layout [data-panning=true]{cursor:grabbing!important}
22
25
  #stage-layout img{-webkit-user-drag:none;user-select:none}
23
26
  .rig-overlay{position:absolute;inset:0;pointer-events:none;width:100%;height:100%;z-index:1}
24
- .inspection-controls{display:flex;align-items:center;gap:8px 14px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid #35515e;font-size:11px;background:#172f3b}
27
+ .inspection-controls{display:flex;align-items:center;gap:8px 14px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--border);font-size:11px;background:var(--rv-panel)}
25
28
  .inspection-controls label{display:inline-flex;align-items:center;gap:5px;margin:0;white-space:nowrap}
26
29
  .inspection-controls select{width:auto;padding:4px;font-size:11px}
27
30
  .inspection-controls input[type=checkbox]{width:auto;margin:0}
28
31
  .inspection-controls fieldset{display:flex;gap:10px;align-items:center;border:0;padding:0;margin:0;min-width:0;flex-wrap:wrap}
29
32
  .inspection-controls legend{float:left;margin-right:9px;font-size:11px}
30
33
  #control-state:empty{display:none}
31
- :root[data-theme=light]{color-scheme:light;color:#233c47;background:#f4f7f9}
34
+ :root[data-theme=light]{color-scheme:light}
32
35
  :root[data-theme=dark]{color-scheme:dark}
33
- [data-theme=light] body,[data-theme=light] main{background:#f4f7f9;color:#233c47}
34
- [data-theme=light] header,[data-theme=light] .topbar,[data-theme=light] .workspaces,[data-theme=light] footer{background:#edf3f5;border-color:#bacbd3;color:#294552}
35
- [data-theme=light] aside,[data-theme=light] .transport,[data-theme=light] .frame-controls,[data-theme=light] .inspection-controls,[data-theme=light] .sound-panel,[data-theme=light] .angle-panel,[data-theme=light] .report-panel{background:#e9f0f3;border-color:#b7c9d1;color:#294552}
36
- [data-theme=light] button{background:#e2ebef;border-color:#a6bdc8;color:#264652}
37
- [data-theme=light] button:hover{background:#d0e2e8}
38
- [data-theme=light] button.primary,[data-theme=light] .primary{background:#24694d;border-color:#24694d;color:#fff}
39
- [data-theme=light] input,[data-theme=light] select,[data-theme=light] textarea{background:#fff;border-color:#8faab7;color:#243e4a;accent-color:#276c50}
40
- [data-theme=light] input::placeholder,[data-theme=light] textarea::placeholder{color:#607986}
41
- [data-theme=light] .hint,[data-theme=light] .subtle,[data-theme=light] .label,[data-theme=light] .eyebrow,[data-theme=light] aside label,[data-theme=light] .tool small,[data-theme=light] .stage-footer,[data-theme=light] .compare-info,[data-theme=light] .boundary,[data-theme=light] .trace,[data-theme=light] .transport output,[data-theme=light] .zoom-controls output,[data-theme=light] .frame-range label,[data-theme=light] .frame-controls label{color:#496774}
42
- [data-theme=light] .workspaces button,[data-theme=light] .segmented button{color:#3e5c69}
43
- [data-theme=light] .workspaces button[aria-pressed=true]{color:#185738;border-bottom-color:#276c50}
44
- [data-theme=light] .workspaces button span{color:#58727e}
45
- [data-theme=light] .segmented,[data-theme=light] .provenance,[data-theme=light] .report-selection pre{background:#e1ebef;border-color:#9cb5c0;color:#294552}
46
- [data-theme=light] .segmented button[aria-pressed=true],[data-theme=light] .checkpoint[aria-current=true],[data-theme=light] .report-event-list button[aria-current=true]{background:#cde4d8;border-color:#56866e;color:#1f4937}
47
- [data-theme=light] .checkpoint small{color:#4b6c59}
48
- [data-theme=light] .provenance p,[data-theme=light] .provenance-row span,[data-theme=light] .provenance details,[data-theme=light] .report-selection details,[data-theme=light] .report-event-list small{color:#486470}
49
- [data-theme=light] .report-event-list button,[data-theme=light] .report-empty-categories span{background:#e5eef2;border-color:#acbfca;color:#294552}
50
- [data-theme=light] .feedback-item strong{color:#1e4e3b}
51
- [data-theme=light] a{color:#175f48}
52
- [data-theme=light] .error{color:#a03225!important}
53
- [data-theme=light] .dirty,[data-theme=light] .report-event-list .no-time{color:#875800!important}
54
- [data-theme=light] :focus-visible{outline-color:#24694d}
36
+
37
+ input::placeholder,textarea::placeholder{color:var(--fg-muted)}
38
+
39
+ :focus-visible{outline-color:var(--rv-focus)}
40
+ .chest-regions{margin-top:22px;padding-top:15px;border-top:1px solid var(--border)}.chest-regions code{font:11px var(--font-mono)}.chest-grid,.chest-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 10px}.chest-row{border:1px solid var(--border);border-radius:6px;padding:9px 10px;margin:10px 0 0;min-width:0}.chest-row legend{font-size:11px;color:var(--fg-muted);padding:0 4px}.chest-grid label,.chest-row label{display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--fg-muted);min-width:0}.chest-row input,.chest-row select,.chest-grid input{width:100%;padding:5px 6px;font-size:11px}.chest-row .chest-check{flex-direction:row;align-items:center;gap:6px}.chest-row .chest-check input{width:auto}.chest-more{margin-top:10px;font-size:11px;color:var(--fg-muted)}.chest-more summary{cursor:pointer}#chest-status.error{color:var(--fail)}
55
41
  /* Source pixels retain their original color; the neutral stage stays dark in both themes. */
56
42
 
57
43
  /* The application shell never scrolls; each Inspector tab and the source list owns overflow. */
@@ -60,14 +46,14 @@ html,body{height:100%;overflow:hidden}body{height:100dvh}#app{height:100dvh;disp
60
46
  main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
61
47
  .left-panel{min-height:0;overflow:auto;padding:13px 10px}
62
48
  #stage-layout{height:auto;min-height:0;flex:1;overflow:hidden;padding:8px;gap:7px}
63
- .inspector{height:clamp(205px,32dvh,340px);min-height:180px;flex:none;display:flex;flex-direction:column;border-top:1px solid #49616d;background:#182d38;overflow:hidden}
64
- .inspector-heading{display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding:7px 12px;border-bottom:1px solid #3e5c69;flex:none;font-size:11px}
49
+ .inspector{height:clamp(205px,32dvh,340px);min-height:180px;flex:none;display:flex;flex-direction:column;border-top:1px solid var(--border);background:var(--rv-panel);overflow:hidden}
50
+ .inspector-heading{display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding:7px 12px;border-bottom:1px solid var(--border);flex:none;font-size:11px}
65
51
  .inspector-heading strong{font-size:10px;letter-spacing:.08em;font-weight:500}
66
- .inspector-heading [role=tablist]{display:flex;gap:4px}
67
- .inspector-heading button{padding:4px 9px;font-size:11px}
68
- .inspector-heading button[aria-selected=true]{background:#3d5d4d;color:#e0f3e7;border-color:#7fa98e}
52
+
53
+
54
+
69
55
  .inspector-body{flex:1;min-height:0;overflow:hidden}
70
- .inspector-body>[role=tabpanel]{height:100%;overflow:auto;padding:12px 15px;min-height:0}
56
+ .inspector-body>[data-inspector-panel]{height:100%;overflow:auto;padding:12px 15px;min-height:0}
71
57
  .inspector .inspection-controls{padding:0;border:0;background:transparent}
72
58
  .inspector .provenance{margin-top:10px}
73
59
  .inspector .transport{padding:0 0 10px;border:0;background:transparent;flex-wrap:wrap}
@@ -82,35 +68,31 @@ main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
82
68
  #inspector-rig #part-set{max-width:190px}
83
69
  #inspector-playback #motion-tools{display:flex;gap:8px;flex-wrap:wrap}
84
70
  #inspector-playback .tool{width:auto}
85
- [data-theme=light] .inspector{background:#edf3f5;border-color:#a6bec8;color:#294552}
86
- [data-theme=light] .inspector-heading{border-color:#b3c6d0}
87
- [data-theme=light] .inspector-heading button[aria-selected=true]{background:#cde4d8;color:#1e4d36;border-color:#59836b}
88
- @media(max-width:780px){.layout{grid-template-columns:130px minmax(0,1fr)!important}.inspector{height:36dvh}.stage-meta{padding:6px 10px 0}.stage-footer{padding:5px 10px}.inspector-heading>.hint{display:none}}
89
- @media(max-width:540px){.layout{grid-template-columns:minmax(0,1fr)!important}.left-panel{display:block;padding:8px;font-size:11px}.layout{grid-template-columns:105px minmax(0,1fr)!important}.topbar,header{padding:7px 10px;gap:8px}.topbar .brand,header .brand{font-size:14px}.top-actions,.actions{gap:4px}.top-actions button,.actions button{padding:4px 6px;font-size:10px}.workspaces button{padding:6px}.inspector{height:40dvh;min-height:150px}.inspector-heading{gap:8px;padding:5px 8px}.inspector-heading button{padding:4px 7px}.stage-footer{display:none}}
90
71
 
91
- .inspector .feedback-form>label,.inspector .review-summary>label{display:block;font-size:11px;margin:10px 0 5px}.inspector .frame-range input{width:100%}.inspector [role=tabpanel]{overscroll-behavior:contain}.left-panel{overscroll-behavior:contain}#stage-layout .pane-label{max-width:calc(100% - 15px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
72
+ @media(max-width:780px){.layout{grid-template-columns:130px minmax(0,1fr)!important}.inspector{height:36dvh}.stage-footer{padding:5px 10px}.inspector-heading>.hint{display:none}}
73
+ @media(max-width:540px){.layout{grid-template-columns:minmax(0,1fr)!important}.left-panel{display:block;padding:8px;font-size:11px}.layout{grid-template-columns:105px minmax(0,1fr)!important}.topbar,header{padding:7px 10px;gap:8px}.topbar .brand,header .brand{font-size:14px}.top-actions,.actions{gap:4px}.top-actions button,.actions button{padding:4px 6px;font-size:10px}.workspaces button{padding:6px}.inspector{height:40dvh;min-height:150px}.inspector-heading{gap:8px;padding:5px 8px}.inspector-heading button{padding:4px 7px}.stage-footer{display:none}}
92
74
 
93
- .rig-layer-controls{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-bottom:12px;font-size:11px}.rig-layer-controls fieldset{display:flex;gap:12px;align-items:center;flex-wrap:wrap;border:0;margin:0;padding:0}.rig-layer-controls legend{float:left;margin-right:10px}.rig-layer-controls label{display:inline-flex;gap:5px;align-items:center}.rig-layer-controls input[type=checkbox]{width:auto}
75
+ .inspector .feedback-form>label,.inspector .review-summary>label{display:block;font-size:11px;margin:10px 0 5px}.inspector .frame-range input{width:100%}.inspector [data-inspector-panel]{overscroll-behavior:contain}.left-panel{overscroll-behavior:contain}#stage-layout .pane-label{max-width:calc(100% - 15px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
94
76
 
95
- .numeric-view{font:11px/1.5 ui-monospace,SFMono-Regular,monospace;white-space:pre-wrap;overflow-wrap:anywhere;max-width:100%;padding:8px;background:#14232c;color:#c9dfe7;border-radius:5px}.view-link-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}.draft-notice{flex:none;font-size:11px;padding:0 12px;color:#e0c58e}.draft-notice:has(#draft-state:empty){display:none}.draft-notice #draft-recovery{display:inline-flex;gap:8px;margin-left:8px;flex-wrap:wrap}.draft-notice button{font-size:11px;padding:2px 6px}[data-theme=light] .numeric-view{background:#e1eaee;color:#264653}[data-theme=light] .draft-notice{color:#80521a}[data-theme=light] .outcome legend,[data-theme=light] .brand span,[data-theme=light] .toolbar{color:#496774}
77
+ .numeric-view{font:11px/1.5 var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;max-width:100%;padding:8px;background:var(--code-bg);color:var(--code-fg);border-radius:5px}.view-link-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}.draft-notice{flex:none;font-size:11px;padding:0 12px;color:var(--warn)}.draft-notice:has(#draft-state:empty){display:none}.draft-notice #draft-recovery{display:inline-flex;gap:8px;margin-left:8px;flex-wrap:wrap}.draft-notice button{font-size:11px;padding:2px 6px}
96
78
 
97
79
  .camera-sync{display:inline-flex;align-items:center;gap:5px;font-size:11px;white-space:nowrap}.camera-sync input[type=checkbox]{width:auto;flex:none;margin:0}
98
80
  #stage-layout:has(>.stage-pane[hidden]){grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}
99
81
 
100
82
  /* Slot explorer shares the stage row; bottom Inspector remains in the main column. */
101
- .layout{grid-template-columns:150px minmax(0,1fr) 235px!important}.parts-explorer{display:block;grid-template-columns:none;gap:normal;min-width:0;min-height:0;overflow:auto;border-left:1px solid #49616d;background:#182d38;padding:12px;overscroll-behavior:contain}.parts-explorer h2{font-size:12px;margin:0 0 10px}.parts-explorer>label{display:block;font-size:11px;margin-top:12px}.parts-explorer input[type=search]{width:100%;min-width:0}.parts-explorer .part-solo{display:flex;gap:6px;align-items:center}.parts-explorer ul{list-style:none;margin:8px 0;padding:0;display:grid;gap:4px}.parts-explorer li{min-width:0}.parts-explorer li button{display:block;width:100%;text-align:left;overflow-wrap:anywhere;font-size:11px}.parts-explorer button[aria-pressed=true]{background:#355445;border-color:#9cd3b4}.parts-explorer h2,#selected-part h3{font-weight:600}#selected-part{margin-bottom:12px}#selected-part h3{margin:0 0 8px;overflow-wrap:anywhere}#selected-part p{overflow-wrap:anywhere}[data-theme=light] .parts-explorer{background:#edf3f5;border-color:#a6bec8;color:#294552}[data-theme=light] .parts-explorer button[aria-pressed=true]{background:#c5ddcd;color:#183c2c}
102
- @media(max-width:1050px){.layout{grid-template-columns:110px minmax(0,1fr) 185px!important}.parts-explorer{padding:8px}}
103
- @media(max-width:700px){.layout{grid-template-columns:minmax(0,1fr) 165px!important}.left-panel{display:none!important}}
104
- @media(max-width:460px){.layout{grid-template-columns:minmax(0,1fr) 132px!important}.parts-explorer{padding:6px}.parts-explorer li button{padding:6px}.parts-explorer>.hint{font-size:10px}}
83
+ .layout{grid-template-columns:260px minmax(0,1fr)!important}.parts-explorer{display:block;grid-template-columns:none;gap:normal;min-width:0;min-height:0;overflow:auto;border-left:1px solid var(--border);background:var(--rv-panel);padding:12px;overscroll-behavior:contain}.parts-explorer h2{font-size:12px;margin:0 0 10px}.parts-explorer>label{display:block;font-size:11px;margin-top:12px}.parts-explorer input[type=search]{width:100%;min-width:0}.parts-explorer .part-solo{display:flex;gap:6px;align-items:center}.parts-explorer ul{list-style:none;margin:8px 0;padding:0;display:grid;gap:4px}.parts-explorer li{min-width:0}.parts-explorer li button{display:block;width:100%;text-align:left;overflow-wrap:anywhere;font-size:11px}.parts-explorer button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border)}.parts-explorer h2,#selected-part h3{font-weight:600}#selected-part{margin-bottom:12px}#selected-part h3{margin:0 0 8px;overflow-wrap:anywhere}#selected-part p{overflow-wrap:anywhere}
84
+ @media(max-width:1050px){.layout{grid-template-columns:200px minmax(0,1fr)!important}.parts-explorer{padding:8px}}
85
+ @media(max-width:700px){.layout{grid-template-columns:minmax(0,1fr)!important}.left-panel{display:none!important}}
86
+ @media(max-width:460px){.layout{grid-template-columns:minmax(0,1fr)!important}.parts-explorer{padding:6px}.parts-explorer li button{padding:6px}.parts-explorer>.hint{font-size:10px}}
105
87
 
106
88
  /* Resizable workspace: one measured shell; workspace-ui clamps the desired browser sizes. */
107
89
  .layout.workspace-layout{display:grid;grid-template-columns:var(--workspace-columns)!important;min-height:0!important;overflow:hidden}
108
90
  .workspace-layout>main{display:grid;min-width:0;min-height:0;overflow:hidden;grid-column:3;grid-row:1}
109
91
  .workspace-stage-stack{grid-row:1;display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}
110
92
  .workspace-stage-stack>#stage-layout{flex:1;min-height:0;height:auto;overflow:hidden}
111
- .workspace-layout>.left-panel{grid-column:1;grid-row:1;display:flex!important;flex-direction:column;overflow:hidden;min-width:0;padding:0;border-right:1px solid #49616d}
93
+ .workspace-layout>.left-panel{grid-column:1;grid-row:1;display:flex!important;flex-direction:column;overflow:hidden;min-width:0;padding:0;border-right:1px solid var(--border)}
112
94
  .workspace-sources{flex:1;min-height:0;overflow:auto;padding:10px;overscroll-behavior:contain}
113
- .workspace-settings{flex:none;max-height:65%;overflow:auto;padding:10px;border-top:1px solid #49616d}
95
+ .workspace-settings{padding:10px;margin:12px -10px 0;border-top:1px solid var(--border)}
114
96
  .workspace-settings h2{font-size:12px;margin:0 0 8px}
115
97
  .workspace-settings label{display:flex;align-items:center;gap:6px;font-size:11px;margin:7px 0}
116
98
  .workspace-settings select{width:auto;min-width:0;flex:1}
@@ -118,22 +100,116 @@ main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
118
100
  .workspace-settings input[type=checkbox]{width:auto;flex:none}
119
101
  .workspace-layout>.parts-explorer{display:block;grid-template-columns:none;grid-column:5;grid-row:1;width:auto;min-width:0;min-height:0;height:auto;overflow:auto}
120
102
  .workspace-layout .inspector{grid-row:3;height:auto!important;min-height:0!important;max-height:none;overflow:hidden}
121
- .workspace-separator{position:relative;z-index:3;background:#34515e;touch-action:none;cursor:col-resize;min-width:0;min-height:0}
122
- .workspace-separator::after{content:'';position:absolute;inset:0 -4px}
123
- .workspace-separator:focus-visible{outline:2px solid #c8e8d6;outline-offset:-2px;background:#719581}
124
- .workspace-separator[data-resizing=true]{background:#8ac9a3}
125
- .workspace-leftWidth{grid-column:2;grid-row:1}.workspace-rightWidth{grid-column:4;grid-row:1}
126
- .workspace-inspectorHeight{grid-row:2;cursor:row-resize}.workspace-inspectorHeight::after{inset:-4px 0}
103
+ .workspace-separator{position:relative;z-index:3;background:var(--rv-separator);touch-action:none;cursor:col-resize;min-width:0;min-height:0}
104
+ .workspace-separator:hover{background:var(--rv-separator-hover)}
105
+ /* Drawn 4px (workspaceMinimums.separator); the transparent hit area stays 16px, 22px on coarse pointers. */
106
+ .workspace-separator::after{content:'';position:absolute;inset:0 -6px}
107
+ .workspace-separator:focus-visible{outline:2px solid var(--rv-focus);outline-offset:-2px;background:var(--link)}
108
+ .workspace-separator[data-resizing=true]{background:var(--link-hover)}
109
+ .workspace-side-left{grid-column:2;grid-row:1}.workspace-side-right{grid-column:4;grid-row:1}
110
+ .workspace-inspectorHeight{grid-row:2;cursor:row-resize}.workspace-inspectorHeight::after{inset:-6px 0}
127
111
  .workspace-drawer-triggers{display:flex;gap:6px;flex-wrap:wrap;flex:none;padding:4px 8px;min-height:0}
128
112
  .workspace-drawer-triggers:not(:has(button:not([hidden]))){display:none}
129
113
  .workspace-drawer-triggers button{padding:4px 7px;font-size:11px;min-height:28px}
130
- .workspace-drawer{position:fixed;inset:0;width:min(94vw,950px);height:min(92dvh,850px);max-width:94vw;max-height:92dvh;margin:auto;padding:8px;border:1px solid #739383;border-radius:8px;background:#182d38;color:inherit;overflow:hidden;z-index:20}
114
+ .workspace-drawer{position:fixed;inset:0;width:min(94vw,950px);height:min(92dvh,850px);max-width:94vw;max-height:92dvh;margin:auto;padding:8px;border:1px solid var(--border-strong);border-radius:8px;background:var(--rv-panel);color:inherit;overflow:hidden;z-index:20}
131
115
  .workspace-drawer:not([open]){display:none}.workspace-drawer[open]{display:flex;flex-direction:column;gap:8px}
132
- .workspace-drawer::backdrop{background:#07141ed0}
116
+ .workspace-drawer::backdrop{background:var(--rv-backdrop)}
133
117
  .workspace-drawer-close{align-self:flex-end;flex:none}
134
118
  .workspace-drawer>.left-panel{display:flex!important;flex-direction:column;flex:1;overflow:hidden;min-height:0;padding:0}
135
119
  .workspace-drawer>.parts-explorer{display:block;grid-template-columns:none;flex:1;width:auto;min-width:0;min-height:0;height:auto;overflow:auto}
136
120
  .workspace-drawer>.inspector{flex:1;height:auto!important;min-height:0!important;max-height:none;overflow:hidden}
137
- [data-theme=light] .workspace-settings,[data-theme=light] .workspace-drawer{background:#edf3f5;color:#294552;border-color:#a6bec8}
138
- [data-theme=light] .workspace-separator{background:#b2c7cf}[data-theme=light] .workspace-separator:focus-visible{outline-color:#24694d;background:#70a58c}
139
- @media(pointer:coarse){.workspace-separator::after{inset:0 -7px}.workspace-inspectorHeight::after{inset:-7px 0}.workspace-drawer-triggers button{min-height:38px}}
121
+
122
+ @media(pointer:coarse){.workspace-separator::after{inset:0 -9px}.workspace-inspectorHeight::after{inset:-9px 0}.workspace-drawer-triggers button{min-height:38px}}
123
+
124
+ /* Workspace rework: parts explorer is the left column; checkpoints and reference fold above it. */
125
+ .package-context{border-bottom:1px solid var(--border);padding-bottom:8px;margin-bottom:10px}
126
+ .package-context>summary{cursor:pointer;display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px;font-size:11px}
127
+ .package-context>summary .eyebrow{margin:0}
128
+ .package-context #checkpoint-title{color:var(--fg-soft);overflow-wrap:anywhere}
129
+ .package-context #checkpoints{margin-top:6px}
130
+ .package-context .checkpoint{padding:6px 8px;margin:2px 0}
131
+ .package-context section{margin-top:10px}
132
+ .left-panel .parts-explorer{border:0;padding:0;background:transparent;overflow:visible}
133
+ .source-files{margin-top:12px}
134
+ .source-files .eyebrow{margin-bottom:4px}
135
+
136
+ /* View bar: layer toggles keep native checkboxes; the label carries the pressed look. */
137
+ .layer-toggles{display:flex;gap:3px;padding:3px;margin:0;min-width:0;border:1px solid var(--border-strong);border-radius:8px;background:var(--rv-segmented)}
138
+ .layer-toggle{position:relative;display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border:1px solid transparent;border-radius:6px;font-size:11px;color:var(--fg-soft);cursor:pointer;white-space:nowrap}
139
+ .layer-toggle input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
140
+ .layer-toggle:has(input:checked){background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}
141
+ .layer-toggle:has(input:disabled){opacity:.45;cursor:default}
142
+ .layer-toggle:has(input:focus-visible){outline:2px solid var(--rv-focus);outline-offset:2px}
143
+ .layer-toggle kbd{font:10px var(--font-mono);padding:0 4px;border:1px solid var(--border-strong);border-radius:3px;color:var(--fg-muted)}
144
+ /* View bar is one row: layers, camera sync and zoom, then the View menu at the right end. */
145
+ .viewbar{flex-wrap:nowrap;justify-content:flex-start;gap:8px;padding:8px 10px;min-width:0}
146
+ .viewbar>.layer-toggles{flex:0 1 auto;overflow:hidden}
147
+ .viewbar .zoom-controls{flex:none}
148
+ .view-menu{position:relative;margin-left:auto;flex:none;font-size:11px}
149
+ .view-menu>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:5px;padding:5px 9px;border:1px solid var(--border-strong);border-radius:6px;background:var(--rv-control);white-space:nowrap}
150
+ .view-menu>summary::-webkit-details-marker{display:none}
151
+ .view-menu>summary::after{content:'\25BE';color:var(--fg-muted)}
152
+ .view-menu>summary:hover{background:var(--rv-control-hover)}
153
+ .view-menu-list{position:absolute;right:0;top:calc(100% + 4px);z-index:12;display:flex;flex-direction:column;gap:3px;min-width:150px;padding:4px;background:var(--rv-panel);border:1px solid var(--border-strong);border-radius:8px}
154
+ .view-menu-list button{text-align:left;font-size:11px;padding:5px 9px;border-color:transparent;background:transparent}
155
+ .view-menu-list button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}
156
+ .transport .idle-repeat{display:inline-flex;align-items:center;gap:5px;font-size:11px;white-space:nowrap;flex:none}
157
+ .transport .idle-repeat input{flex:none;width:auto;margin:0}
158
+
159
+ /* Footer is the one status area: message | readouts | view region and source hash. */
160
+ footer.status-bar{flex-wrap:nowrap}
161
+ .status-message{flex:1 1 180px;min-width:120px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
162
+ .status-readouts{flex:0 1 auto;max-width:60%;display:flex;align-items:center;justify-content:flex-end;gap:2px 12px;flex-wrap:nowrap;min-width:0;overflow:hidden}.status-readouts>*{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
163
+ .status-readouts .draft-notice{padding:0}
164
+ .status-readouts .draft-notice #draft-recovery{margin-left:6px}
165
+ .status-source{display:flex;align-items:center;gap:12px;margin-left:auto;flex:none;white-space:nowrap}
166
+ .view-region{position:relative}
167
+ .view-region>summary{cursor:pointer}
168
+ .view-region-body{position:absolute;right:0;bottom:calc(100% + 10px);width:min(520px,90vw);max-height:60dvh;overflow:auto;padding:10px;z-index:10;background:var(--rv-panel);border:1px solid var(--border);border-radius:7px}
169
+ .view-region-body .numeric-view{margin:8px 0}
170
+
171
+
172
+ /* Header mode switch: Setup and Animate reflect the Inspector's selected section. */
173
+ .mode-switch{display:flex;gap:3px;padding:3px;border:1px solid var(--border-strong);border-radius:8px;background:var(--rv-segmented)}
174
+ .mode-switch button{border:0;background:transparent;font-size:12px;padding:5px 12px}
175
+ .mode-switch button[aria-pressed=true]{background:var(--rv-pressed-bg);color:var(--rv-pressed-fg)}
176
+
177
+ /* Panel headers stay put while the panel body scrolls beneath them. */
178
+ .sticky-header{position:sticky;top:0;z-index:2;background:var(--rv-panel)}
179
+ /* The left column's fixed header row: title and search never scroll and nothing sits above them. */
180
+ .left-header{flex:none;padding:8px 10px;border-bottom:1px solid var(--border);background:var(--rv-panel)}
181
+ .left-header h2{font-size:12px;font-weight:600;margin:0 0 6px}
182
+ .left-header label{display:block;font-size:11px;margin:0 0 4px}
183
+ .left-header input[type=search]{width:100%;min-width:0}
184
+ .workspace-sources{padding-top:0}
185
+ .workspace-sources>.package-context{padding-top:10px}
186
+ .workspace-settings>.sticky-header{margin:-10px -10px 8px;padding:10px 10px 6px}
187
+ .workspace-drawer-header{display:flex;align-items:center;justify-content:space-between;gap:8px;flex:none;font-size:12px;font-weight:600}
188
+
189
+ /* One thin scrollbar for every scrolling region, from the theme tokens: the standard properties for
190
+ Firefox, a 6px ::-webkit-scrollbar for Chromium/WebKit (Chromium ignores it once scrollbar-width is set). */
191
+ @supports not selector(::-webkit-scrollbar){*{scrollbar-width:thin;scrollbar-color:var(--rv-scrollbar-thumb) var(--rv-scrollbar-track)}}
192
+ *::-webkit-scrollbar{width:6px;height:6px}
193
+ *::-webkit-scrollbar-track{background:var(--rv-scrollbar-track)}
194
+ *::-webkit-scrollbar-thumb{background:var(--rv-scrollbar-thumb);border-radius:3px}
195
+ *::-webkit-scrollbar-thumb:hover{background:var(--rv-scrollbar-thumb-hover)}
196
+ *::-webkit-scrollbar-corner{background:transparent}
197
+
198
+ /* Review dialog: today's Feedback section in a native modal opened from the top bar's Write review. */
199
+ .review-dialog{position:fixed;inset:0;width:min(94vw,1100px);max-width:94vw;max-height:90dvh;margin:auto;padding:0;border:1px solid var(--border-strong);border-radius:8px;background:var(--rv-panel);color:inherit;overflow:hidden}
200
+ .review-dialog:not([open]){display:none}
201
+ .review-dialog[open]{display:flex;flex-direction:column}
202
+ .review-dialog::backdrop{background:var(--rv-backdrop)}
203
+ .review-dialog-header{flex:none;display:flex;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid var(--border)}
204
+ .review-dialog-header h2{margin:0;font-size:14px;font-weight:600;color:var(--fg-strong)}
205
+ .review-dialog-header .hint{margin:0;flex:1;min-width:0}
206
+ .review-dialog-body{flex:1;min-height:0;overflow:auto;padding:14px 16px;overscroll-behavior:contain}
207
+ .review-dialog-actions{flex:none;display:flex;align-items:center;gap:12px;padding:10px 14px;border-top:1px solid var(--border)}
208
+ .review-message{flex:1;min-width:0;font-size:11px;color:var(--fg-soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
209
+ .review-message.error{color:var(--fail)}
210
+ .review-draft-state{font-size:11px;color:var(--warn)}
211
+ .review-draft-state:empty{display:none}
212
+ .review-dialog .provenance{margin-top:10px}
213
+ .review-dialog .review-summary>label{display:block;font-size:11px;margin:10px 0 5px}
214
+ .review-dialog .report-panel{padding:10px 0}
215
+ @media(max-width:540px){.review-dialog-header .hint{display:none}}
@@ -28,6 +28,38 @@ export function bindStageLayout(area:HTMLElement,changed:()=>void):()=>void {
28
28
  const render=()=>{if(syncStageLayout(area))changed();};
29
29
  const observer=new ResizeObserver(render);observer.observe(area);render();return ()=>observer.disconnect();
30
30
  }
31
+ /** Single-key layer toggles. Free of Space (pan), Enter (frame entry), arrows/Home/End (tabs, separators, part list). */
32
+ export const layerShortcuts=Object.freeze([
33
+ Object.freeze({key:'i',id:'layer-images',label:'Images'}),
34
+ Object.freeze({key:'m',id:'layer-mesh',label:'Mesh edges'}),
35
+ Object.freeze({key:'b',id:'layer-bones',label:'Bone points'}),
36
+ ] as const);
37
+ const nonTextInputs=new Set(['checkbox','radio','button','submit','reset','range','color','file','image']);
38
+ /** A focused text field, textarea, select or contenteditable keeps its keys. */
39
+ export function textEntryFocused(target:unknown):boolean {
40
+ const node=target as {closest?:(selector:string)=>unknown;isContentEditable?:boolean}|null;if(!node||typeof node.closest!=='function')return false;
41
+ if(node.isContentEditable||node.closest('textarea,select,[contenteditable]:not([contenteditable="false"])'))return true;
42
+ const input=node.closest('input') as {type?:string}|null;return !!input&&!nonTextInputs.has(String(input.type??'text').toLowerCase());
43
+ }
44
+ interface ShortcutInput {disabled:boolean;click():void}
45
+ /** `I`/`M`/`B` click the actual checkbox, so its change handler, checked state and draft input stay the only state. */
46
+ export function bindLayerShortcuts(root:Pick<Document,'addEventListener'|'removeEventListener'>&{getElementById(id:string):unknown}):()=>void {
47
+ const keyDown=(event:Event)=>{
48
+ const e=event as KeyboardEvent;if(e.defaultPrevented||e.repeat||e.ctrlKey||e.metaKey||e.altKey||typeof e.key!=='string'||textEntryFocused(e.target))return;
49
+ const shortcut=layerShortcuts.find(item=>item.key===e.key.toLowerCase());if(!shortcut)return;
50
+ const input=root.getElementById(shortcut.id) as ShortcutInput|null;if(!input||input.disabled)return;
51
+ e.preventDefault();input.click();
52
+ };
53
+ root.addEventListener('keydown',keyDown);return ()=>root.removeEventListener('keydown',keyDown);
54
+ }
55
+ /** Disabled toggles name their reason as a tooltip; enabled ones name their key. */
56
+ export function describeLayerToggles(root:{getElementById(id:string):unknown},reason:string):void {
57
+ for(const item of layerShortcuts){
58
+ const input=root.getElementById(item.id) as (ShortcutInput&{closest(selector:string):{title:string}|null})|null;const label=input?.closest('label');
59
+ if(input&&label)label.title=input.disabled?reason:`${item.label} · key ${item.key.toUpperCase()}`;
60
+ }
61
+ }
62
+
31
63
  // Nested or separately disposed pan binders must not restore another binder's override.
32
64
  const panVideoOverrides=new WeakMap<HTMLVideoElement,{owners:number;value:string;priority:string}>();
33
65
  export function bindPan(viewport:HTMLElement,move:(dx:number,dy:number)=>void,enabled:()=>boolean=()=>true):()=>void {
@@ -61,9 +93,16 @@ export function bindPan(viewport:HTMLElement,move:(dx:number,dy:number)=>void,en
61
93
  return ()=>{blur();window.removeEventListener('blur',blur);document.removeEventListener('keydown',keyDown,true);window.removeEventListener('keyup',keyUp,true);viewport.removeEventListener('pointerdown',start);viewport.removeEventListener('pointermove',drag);viewport.removeEventListener('pointerup',end);viewport.removeEventListener('pointercancel',end);viewport.removeEventListener('lostpointercapture',end);viewport.removeEventListener('dragstart',native);};
62
94
  }
63
95
 
64
- /** beforeChange runs while the old panel is still visible; changed runs after the new one is shown. */
96
+ /** beforeChange runs while the old panel is still visible; changed runs after the new one is shown.
97
+ * The selected section is the one source of the mode. A `[data-inspector]` element is a section selector:
98
+ * a `role=tab` in an Inspector tab row (aria-selected, roving tabindex, arrow keys) or a header `[data-mode]`
99
+ * toggle that is itself the selector (aria-pressed). Mode buttons without `data-inspector` select the matching
100
+ * tab and only reflect it; a tab with no mode button (compare's Feedback) leaves every mode button unpressed. */
65
101
  export function bindInspectorTabs(changed:()=>void,beforeChange:()=>void=()=>{}):void {
66
- const tabs=[...document.querySelectorAll<HTMLButtonElement>('[data-inspector]')];
67
- const activate=(tab:HTMLButtonElement)=>{beforeChange();for(const item of tabs){const active=item===tab;item.setAttribute('aria-selected',String(active));item.tabIndex=active?0:-1;}for(const panel of document.querySelectorAll<HTMLElement>('[data-inspector-panel]'))panel.hidden=panel.dataset.inspectorPanel!==tab.dataset.inspector;changed();};
68
- for(const [index,tab] of tabs.entries()){tab.onclick=()=>activate(tab);tab.onkeydown=event=>{const next=event.key==='ArrowRight'?(index+1)%tabs.length:event.key==='ArrowLeft'?(index+tabs.length-1)%tabs.length:event.key==='Home'?0:event.key==='End'?tabs.length-1:null;if(next===null)return;event.preventDefault();const target=tabs[next];if(target){activate(target);target.focus();}};}
102
+ const tabs=[...document.querySelectorAll<HTMLButtonElement>('[data-inspector]')],modes=[...document.querySelectorAll<HTMLButtonElement>('[data-mode]')].filter(mode=>!tabs.includes(mode));
103
+ const toggle=(item:HTMLButtonElement)=>item.dataset.mode!==undefined,state=(item:HTMLButtonElement)=>toggle(item)?'aria-pressed':'aria-selected';
104
+ const reflect=()=>{if(!modes.length)return;const selected=tabs.find(item=>item.getAttribute(state(item))==='true')?.dataset.inspector;for(const mode of modes)mode.setAttribute('aria-pressed',String(mode.dataset.mode===selected));};
105
+ const activate=(tab:HTMLButtonElement)=>{beforeChange();for(const item of tabs){const active=item===tab;item.setAttribute(state(item),String(active));if(!toggle(item))item.tabIndex=active?0:-1;}for(const panel of document.querySelectorAll<HTMLElement>('[data-inspector-panel]'))panel.hidden=panel.dataset.inspectorPanel!==tab.dataset.inspector;reflect();changed();};
106
+ for(const mode of modes)mode.onclick=()=>{const tab=tabs.find(item=>item.dataset.inspector===mode.dataset.mode);if(tab)activate(tab);};reflect();
107
+ for(const [index,tab] of tabs.entries()){tab.onclick=()=>activate(tab);if(toggle(tab))continue;tab.onkeydown=event=>{const next=event.key==='ArrowRight'?(index+1)%tabs.length:event.key==='ArrowLeft'?(index+tabs.length-1)%tabs.length:event.key==='Home'?0:event.key==='End'?tabs.length-1:null;if(next===null)return;event.preventDefault();const target=tabs[next];if(target){activate(target);target.focus();}};}
69
108
  }
@@ -1,31 +1,52 @@
1
1
  /** Effective workspace geometry and local DOM bindings. Never stores source or global preferences. */
2
2
  import {parseWorkspacePanels,panelBounds,type WorkspacePanels} from './workspace-preferences.ts';
3
3
  export type WorkspacePanel=keyof WorkspacePanels;
4
- export const workspaceMinimums=Object.freeze({stageWidth:240,stageHeight:180,separator:8});
4
+ /** `separator` is the drawn thickness of every resize handle in CSS pixels; its hit area is wider (styles.css `::after`). */
5
+ export const workspaceMinimums=Object.freeze({stageWidth:240,stageHeight:180,separator:4});
5
6
  export interface SeparatorRange {min:number;max:number;value:number;enabled:boolean}
6
- export interface EffectiveWorkspaceLayout {leftWidth:number;rightWidth:number;inspectorHeight:number;stageWidth:number;stageHeight:number;leftMode:'panel'|'drawer';rightMode:'panel'|'drawer';inspectorMode:'panel'|'drawer';stageMinimumSatisfied:boolean;separators:Record<WorkspacePanel,SeparatorRange>}
7
+ export type WorkspaceColumnKey=Exclude<WorkspacePanel,'inspectorHeight'>;
8
+ /** Which saved width each side column uses; null means the page has no column on that side.
9
+ * The compare page keeps sources left and parts right; the single review page shows the parts
10
+ * explorer (its saved `rightWidth`) on the left and has no right column. */
11
+ export interface WorkspaceColumns {left:WorkspaceColumnKey|null;right:WorkspaceColumnKey|null}
12
+ export const defaultWorkspaceColumns:Readonly<WorkspaceColumns>=Object.freeze({left:'leftWidth',right:'rightWidth'});
13
+ export type WorkspacePanelMode='panel'|'drawer'|'absent';
14
+ /** Sizes and modes are keyed by saved panel, not by side; `absent` panels have width 0 and no separator. */
15
+ export interface EffectiveWorkspaceLayout {leftWidth:number;rightWidth:number;inspectorHeight:number;stageWidth:number;stageHeight:number;leftMode:WorkspacePanelMode;rightMode:WorkspacePanelMode;inspectorMode:'panel'|'drawer';stageMinimumSatisfied:boolean;separators:Record<WorkspacePanel,SeparatorRange>}
7
16
  const clamp=(value:number,min:number,max:number)=>Math.max(min,Math.min(max,value));
17
+ export function parseWorkspaceColumns(value:WorkspaceColumns):WorkspaceColumns {
18
+ const ok=(v:unknown)=>v===null||v==='leftWidth'||v==='rightWidth';
19
+ if(!value||!ok(value.left)||!ok(value.right)||value.left!==null&&value.left===value.right)throw new Error(`WORKSPACE_COLUMNS: left ${String(value?.left)} / right ${String(value?.right)}; required distinct leftWidth, rightWidth or null`);
20
+ return {left:value.left,right:value.right};
21
+ }
8
22
  /** Input rectangle excludes app chrome. Drawer triggers must remain outside collapsed panel content.
9
- * Collapse left, then right when minima cannot coexist; Inspector becomes a drawer below its minimum.
23
+ * Collapse the left column, then the right one when minima cannot coexist; Inspector becomes a drawer below its minimum.
10
24
  * Returned clamps never replace the user's desired desktop sizes. */
11
- export function allocateWorkspaceLayout(available:{width:number;height:number},desired:WorkspacePanels):EffectiveWorkspaceLayout {
12
- const panels=parseWorkspacePanels(desired),{width,height}=available,{stageWidth:minWidth,stageHeight:minHeight,separator}=workspaceMinimums;
25
+ export function allocateWorkspaceLayout(available:{width:number;height:number},desired:WorkspacePanels,columns:WorkspaceColumns=defaultWorkspaceColumns):EffectiveWorkspaceLayout {
26
+ const panels=parseWorkspacePanels(desired),{left:leftKey,right:rightKey}=parseWorkspaceColumns(columns),{width,height}=available,{stageWidth:minWidth,stageHeight:minHeight,separator}=workspaceMinimums;
13
27
  if(![width,height].every(n=>Number.isFinite(n)&&n>=0&&n<=65536))throw new Error('WORKSPACE_RECTANGLE: finite 0..65536 CSS pixels required');
14
- let left=true,right=true;if(width<panelBounds.leftWidth.min+panelBounds.rightWidth.min+minWidth+2*separator)left=false;if(width<(left?panelBounds.leftWidth.min+separator:0)+panelBounds.rightWidth.min+minWidth+separator)right=false;
15
- const count=Number(left)+Number(right),budget=Math.max(0,width-minWidth-count*separator),leftMin=left?panelBounds.leftWidth.min:0,rightMin=right?panelBounds.rightWidth.min:0,wantLeft=left?panels.leftWidth:0,wantRight=right?panels.rightWidth:0;
16
- const slack=wantLeft+wantRight-leftMin-rightMin,ratio=slack>0?Math.min(1,Math.max(0,(budget-leftMin-rightMin)/slack)):1,leftWidth=leftMin+(wantLeft-leftMin)*ratio,rightWidth=rightMin+(wantRight-rightMin)*ratio;
28
+ const boundLeft=leftKey?panelBounds[leftKey].min:0,boundRight=rightKey?panelBounds[rightKey].min:0;
29
+ let left=leftKey!==null,right=rightKey!==null;
30
+ if(left&&right&&width<boundLeft+boundRight+minWidth+2*separator)left=false;
31
+ if(right&&width<(left?boundLeft+separator:0)+boundRight+minWidth+separator)right=false;
32
+ if(left&&!right&&width<boundLeft+minWidth+separator)left=false;
33
+ const count=Number(left)+Number(right),budget=Math.max(0,width-minWidth-count*separator),leftMin=left?boundLeft:0,rightMin=right?boundRight:0,wantLeft=left?panels[leftKey!]:0,wantRight=right?panels[rightKey!]:0;
34
+ const slack=wantLeft+wantRight-leftMin-rightMin,ratio=slack>0?Math.min(1,Math.max(0,(budget-leftMin-rightMin)/slack)):1,leftSize=leftMin+(wantLeft-leftMin)*ratio,rightSize=rightMin+(wantRight-rightMin)*ratio;
17
35
  const inspector=height>=minHeight+panelBounds.inspectorHeight.min+separator,inspectorHeight=inspector?Math.min(panels.inspectorHeight,height-minHeight-separator):0;
18
- const stageWidth=Math.max(0,width-leftWidth-rightWidth-count*separator),stageHeight=Math.max(0,height-inspectorHeight-(inspector?separator:0));
36
+ const stageWidth=Math.max(0,width-leftSize-rightSize-count*separator),stageHeight=Math.max(0,height-inspectorHeight-(inspector?separator:0));
19
37
  const range=(visible:boolean,key:WorkspacePanel,value:number,max:number):SeparatorRange=>visible?{min:panelBounds[key].min,max:Math.max(panelBounds[key].min,Math.min(panelBounds[key].max,max)),value,enabled:true}:{min:0,max:0,value:0,enabled:false};
20
- return {leftWidth,rightWidth,inspectorHeight,stageWidth,stageHeight,leftMode:left?'panel':'drawer',rightMode:right?'panel':'drawer',inspectorMode:inspector?'panel':'drawer',stageMinimumSatisfied:width>=minWidth&&height>=minHeight,separators:{leftWidth:range(left,'leftWidth',leftWidth,width-rightWidth-count*separator-minWidth),rightWidth:range(right,'rightWidth',rightWidth,width-leftWidth-count*separator-minWidth),inspectorHeight:range(inspector,'inspectorHeight',inspectorHeight,height-minHeight-separator)}};
38
+ const size:Record<WorkspaceColumnKey,number>={leftWidth:0,rightWidth:0},mode:Record<WorkspaceColumnKey,WorkspacePanelMode>={leftWidth:'absent',rightWidth:'absent'},separators:Record<WorkspacePanel,SeparatorRange>={leftWidth:range(false,'leftWidth',0,0),rightWidth:range(false,'rightWidth',0,0),inspectorHeight:range(inspector,'inspectorHeight',inspectorHeight,height-minHeight-separator)};
39
+ if(leftKey){size[leftKey]=leftSize;mode[leftKey]=left?'panel':'drawer';separators[leftKey]=range(left,leftKey,leftSize,width-rightSize-count*separator-minWidth);}
40
+ if(rightKey){size[rightKey]=rightSize;mode[rightKey]=right?'panel':'drawer';separators[rightKey]=range(right,rightKey,rightSize,width-leftSize-count*separator-minWidth);}
41
+ return {leftWidth:size.leftWidth,rightWidth:size.rightWidth,inspectorHeight,stageWidth,stageHeight,leftMode:mode.leftWidth,rightMode:mode.rightWidth,inspectorMode:inspector?'panel':'drawer',stageMinimumSatisfied:width>=minWidth&&height>=minHeight,separators};
21
42
  }
22
- export interface WorkspaceSeparatorOptions {element:HTMLElement;panel:WorkspacePanel;controls:string;label:string;range:()=>SeparatorRange;resize:(desired:number)=>void;desired?:()=>number;resolveDesired?:(effective:number,context:{range:SeparatorRange;desired:number})=>number;commit?:(desired:number)=>void;events?:Pick<EventTarget,'addEventListener'|'removeEventListener'>|null;step?:number;coarseStep?:number}
43
+ export interface WorkspaceSeparatorOptions {element:HTMLElement;panel:WorkspacePanel;controls:string;label:string;range:()=>SeparatorRange;resize:(desired:number)=>void;desired?:()=>number;resolveDesired?:(effective:number,context:{range:SeparatorRange;desired:number})=>number;commit?:(desired:number)=>void;side?:'left'|'right';events?:Pick<EventTarget,'addEventListener'|'removeEventListener'>|null;step?:number;coarseStep?:number}
23
44
  /** Arrow keys act only on the separator itself. Right/bottom boundaries grow inward.
24
45
  * Pointer cancellation, capture loss, Escape and blur restore the prior desired size.
25
46
  * Coupled allocators supply their reachable effective range and resolveDesired inverse;
26
47
  * resize/commit receive desired sizes, including when effective geometry is clamped. */
27
48
  export function bindWorkspaceSeparator(options:WorkspaceSeparatorOptions):{refresh():void;dispose():void} {
28
- const {element,panel}=options,vertical=panel!=='inspectorHeight',direction=panel==='leftWidth'?1:-1,events=options.events===undefined?(typeof window==='undefined'?null:window):options.events;
49
+ const {element,panel}=options,vertical=panel!=='inspectorHeight',direction=(options.side??(panel==='leftWidth'?'left':'right'))==='left'?1:-1,events=options.events===undefined?(typeof window==='undefined'?null:window):options.events;
29
50
  if(!options.controls||!options.label||/[\x00-\x1f]/.test(options.controls+options.label))throw new Error('WORKSPACE_SEPARATOR: controls and accessible label required');
30
51
  const step=options.step??8,coarse=options.coarseStep??40;if(![step,coarse].every(n=>Number.isFinite(n)&&n>0))throw new Error('WORKSPACE_SEPARATOR: positive finite keyboard steps required');
31
52
  const attrs=['role','aria-orientation','aria-controls','aria-label','aria-valuemin','aria-valuemax','aria-valuenow','aria-valuetext','aria-disabled'],saved=new Map(attrs.map(key=>[key,element.getAttribute(key)])),tabIndex=element.tabIndex,touchAction=element.style.touchAction;
@@ -2,6 +2,9 @@
2
2
  export const workspacePreferenceKey='rig-review-preferences/1';
3
3
  export const legacyAppearanceKey='rig-review-appearance/1';
4
4
  export type WorkspaceTheme='system'|'dark'|'light';
5
+ /** Saved field names are part of the stored record and do not move with the panels.
6
+ * `rightWidth` is the parts explorer's width wherever the page shows it (the single review page shows
7
+ * it on the left); `leftWidth` is the compare page's sources column. */
5
8
  export interface WorkspacePanels {leftWidth:number;rightWidth:number;inspectorHeight:number}
6
9
  export const panelBounds=Object.freeze({leftWidth:Object.freeze({min:120,max:720}),rightWidth:Object.freeze({min:180,max:900}),inspectorHeight:Object.freeze({min:160,max:900})});
7
10
  export const defaultWorkspacePanels:Readonly<WorkspacePanels>=Object.freeze({leftWidth:170,rightWidth:260,inspectorHeight:280});