Files
pbakaus_impeccable/crates/context/assets/component-review.js
T
Paul Bakaus 6a93a35293 Add human component review and prepare Impeccable 4.4.0
Carry native comp capture and completion integrity fixes into the reviewed component workflow. Bump the skill to 4.4.0 and engine/platform pins to 0.1.6; keep publication separate from this release candidate.

AI assistance: implemented and validated with OpenAI Codex.
2026-09-13 20:41:09 -07:00

135 lines
55 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(()=>{function te(n,i){let a=i?.changes[n],o=i?.feedback?.[n],r=o?.decision??(i?.submitted?i.draft.decisions[n]:void 0),g=a?.kind==="unchanged"&&(a.carried??(i?.submitted&&r?.action==="approve"))===!0;return{change:a,prior:r,feedbackRound:o?.round??i?.packet.round,carried:g,label:a?.kind==="added"?"New component":a?.kind==="changed"?"Review again":g?"Approval kept":r?.action==="revise"?"Changes still requested":"Awaiting review"}}function I(n,i,a){let o=i.decisions[n.id],r=o?.revision===n.revision?o:void 0,g=te(n.id,a);if(r?.action==="approve")return{kind:"approved",label:g.carried?"Approval kept":"Approved",priority:3};if(r?.action==="revise")return{kind:"feedback",label:"Feedback ready",priority:2};return{kind:"pending",label:g.change?.kind==="changed"?"Review again":g.change?.kind==="added"?"New · review needed":"Not reviewed",priority:g.change?.kind==="changed"||g.change?.kind==="added"?0:1}}function Ye(n){return{packetRevision:n.revision,decisions:{},missing:[],inventoryConfirmed:!1}}function pi(n){return Object.values(n).every(Number.isFinite)&&n.x>=0&&n.y>=0&&n.w>0&&n.h>0&&n.x+n.w<=1.00001&&n.y+n.h<=1.00001}function pe(n,i){let a=n.components.map((h)=>i.decisions[h.id]?.revision===h.revision?i.decisions[h.id]:void 0),o=a.filter((h)=>h?.action==="approve").length,r=a.filter((h)=>h?.action==="revise").length,g=a.length-o-r,l=r>0||i.missing.length>0;return{approved:o,revisions:r,pending:g,hasFeedback:l,canSubmit:i.packetRevision===n.revision&&i.missing.every((h)=>h.name.trim()&&pi(h.box))&&(l||!g&&i.inventoryConfirmed)}}function De(n,i){let a={...i.decisions};for(let o of n.components)if(!a[o.id]||a[o.id].revision!==o.revision)a[o.id]={revision:o.revision,action:"approve",feedback:"",split:!1};return{...i,decisions:a}}function Ze(n,i){if(!pe(n,i).canSubmit)throw Error("Review is incomplete or stale");return{schemaVersion:1,requestId:n.id,...structuredClone(i)}}function ae(n){let i=n.preview.kind==="page"||n.preview.sourceKind==="page",a=n.preview.sourceKind==="page";return{code:i,captured:a,label:i?n.medium.match(/html|css|svg/i)?n.medium:"HTML / CSS / SVG":"Raster",caption:i?a?"Rendered component":"Live component":"Produced asset",fileLabel:a?"Open captured preview":"Open source image"}}function ce(n,i,a,o,r){let g=r==="fit"?Math.min(a/n,o/i):r;return{scale:g,width:n*g,height:i*g}}var Re=`
:host{height:var(--component-review-height,100dvh);min-height:0;overflow:hidden}
.review{height:100%;max-width:none;min-height:0;padding:0;display:flex;flex-direction:column;overflow:hidden;background:var(--color-bg,#fafafa)}
.review>header{flex-shrink:0;padding:16px 24px;margin:0;border-bottom:1px solid var(--line);gap:16px}.review>header>div{min-width:0}.review h1{font-size:30px;line-height:1}.review>header p{font-size:12px;margin-top:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.review .badge{font-size:11px}
.review>.preview-note{flex-shrink:0;margin:0;padding:8px 24px;font-size:11px}.review>.round-summary{flex-shrink:0;margin:0;padding:8px 24px;border-top:0;gap:6px 16px;max-height:120px;overflow:auto}.round-summary p{font-size:12px;gap:6px 14px}.round-summary button{font-size:12px;min-height:32px;padding:5px 10px}
.review>.workbench{flex:1;min-height:0;align-items:stretch;padding:20px 24px;gap:32px;overflow:hidden;grid-template-columns:minmax(0,1.12fr) minmax(0,1fr)}.reference{display:flex;flex-direction:column;min-height:0;width:100%;max-width:none;margin:0}.reference>.section-head{flex-shrink:0;margin-bottom:10px}.map-space{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;background:#eeefec;border:1px solid var(--line);overflow:hidden}.map{flex-shrink:0;max-width:100%;max-height:100%}.map-legend{flex-shrink:0;margin-top:9px;gap:6px 14px}.map-caption{flex-shrink:0;min-height:0;padding-top:6px;font-size:10px}.map-caption button{display:inline-block;margin:0 0 0 8px;min-height:28px;padding:3px 7px}
.workbench>.inspector{height:100%;min-height:0;padding:0;border:0}.inspector>.section-head{margin-bottom:10px;min-height:32px}.section-head h2{font-size:16px}.inspection-content{padding-bottom:8px}.review-form{max-height:55%;overflow-y:auto;scrollbar-width:thin;scrollbar-gutter:stable}.material{min-height:28px}.material strong{font-size:13px}.compare-toolbar{margin-bottom:12px}.previous-feedback{padding:10px 12px}.repair-context{margin-bottom:12px}.preview-round{margin-bottom:10px}.decision-title p{font-size:11px}.decisions>button{min-height:40px;font-size:13px}.decision-title strong{font-size:13px}.view-controls{margin-top:8px}.component-details{height:auto;max-height:80px}
.review>.inventory-section{height:236px;flex-shrink:0;display:flex;flex-direction:column;min-height:0;margin:0;padding:10px 24px 8px;border-top:1px solid var(--line);background:var(--color-panel,#f5f5f2);overflow:hidden}.inventory-section>.section-head{margin:0 0 8px;min-height:36px;flex-shrink:0;gap:10px}.inventory-section h2{font-size:14px}.inventory{flex:1;min-height:0;align-items:stretch;margin:0;padding:3px 3px 7px;overflow-x:auto;overflow-y:hidden}.inventory.all{overflow:auto;align-items:start;grid-auto-rows:160px}.inventory .item{flex:0 0 136px;padding:8px;gap:3px;grid-template-rows:auto auto minmax(24px,1fr) auto;min-height:0}.inventory .item-thumb{height:46px;margin-bottom:3px}.inventory .thumb-crop{max-height:46px}.inventory .item-number{font-size:11px;min-height:16px;padding:0}.inventory .item strong{font-size:12px;min-height:26px;line-height:1.2}.inventory .state{font-size:11px;padding-top:4px}.tray-actions{display:flex;gap:6px;align-items:center}.tray-actions button{white-space:nowrap;font-size:11px;min-height:32px}.tray-actions #toggle-tray{display:flex;align-items:center;gap:6px}.tray-actions svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.review>.inventory-section.tray-expanded{height:min(42dvh,390px)}.review>.inventory-section.tray-collapsed{height:56px;padding-block:10px}.tray-collapsed .inventory{display:none}.tray-collapsed>.section-head{margin-bottom:0}
.review>footer{flex-shrink:0;margin:0;padding:12px 24px;gap:16px;border-top:1px solid var(--line);background:var(--paper);align-items:center}.review>footer>div:first-child{display:flex;align-items:center;gap:14px;min-width:0}.review>footer .check{margin:0;max-width:240px;font-size:11px}.review>footer #approve-rest{min-height:36px;font-size:12px;max-width:210px;padding:7px 10px}.review>footer .submit-area{gap:12px}.review>footer .submit-area p{font-size:11px;max-width:24ch}.review>footer .primary{min-height:40px;font-size:13px}.mobile-panes{display:none}
/* Depth describes the shell: recessed work area, raised inspector, anchored docks. */
.review{--workspace:#e3e6e2;--canvas:#d5dad5;--dock:#f6f7f4;--surface:#fff;background:var(--workspace)}
.review>header{position:relative;z-index:8;background:var(--surface);border-bottom-color:#d5d9d3}
.review>.round-summary,.review>.preview-note{position:relative;z-index:7;background:var(--dock);box-shadow:0 3px 6px #202b2510;border-bottom-color:#c8cfc7}
.review>.workbench{background:var(--workspace)}
.map-space{background:var(--canvas);border-color:#bdc6bd;box-shadow:inset 0 2px 7px #263d2a12;border-radius:5px}
.map{box-shadow:0 3px 10px #182a242b}
.workbench>.inspector{background:var(--surface);border-radius:6px;box-shadow:0 2px 4px #24332812,0 8px 24px #24332818;scrollbar-gutter:auto;isolation:isolate}
.inspector>.section-head{position:relative;z-index:2;margin:0;padding:12px 16px;background:var(--surface);border-bottom:1px solid #dce0da}
.inspection-content{padding:14px 12px 14px 16px;background:#fafbf9}
.inspector>.review-form{position:relative;z-index:2;padding:12px 12px 12px 16px;background:var(--surface);border-top:1px solid #cdd4ca}
.inspector[data-scroll-above=true]>.section-head{box-shadow:0 6px 8px -4px #24332838}
.inspector[data-scroll-below=true]>.review-form{box-shadow:0 -6px 8px -4px #24332838}
.inspection-content,.review-form,.inventory{scrollbar-width:auto;scrollbar-color:#8d9c91 #e3e8e1;overscroll-behavior:contain}
.inspection-content::-webkit-scrollbar,.review-form::-webkit-scrollbar,.inventory::-webkit-scrollbar{width:10px;height:10px}
.inspection-content::-webkit-scrollbar-track,.review-form::-webkit-scrollbar-track,.inventory::-webkit-scrollbar-track{background:#e3e8e1;border-radius:6px}
.inspection-content::-webkit-scrollbar-thumb,.review-form::-webkit-scrollbar-thumb,.inventory::-webkit-scrollbar-thumb{background:#8d9c91;border:2px solid #e3e8e1;border-radius:6px}
.review>.inventory-section{position:relative;z-index:8;background:var(--dock);border-top-color:#b9c3b8;box-shadow:0 -3px 5px #2433280c,0 -10px 24px #24332814}
.inventory-section>.section-head{border-bottom:1px solid #d9dfd5;padding-bottom:8px;margin-bottom:8px}
.tray-collapsed>.section-head{border:0;padding-bottom:0;margin-bottom:0}
.inventory{background:#e9ece5;border-radius:4px;padding:7px 7px 9px}
.inventory .item{background:#fafbf8}
.inventory .item.active{background:#fff}
.review>footer{position:relative;z-index:9;background:var(--surface);border-top-color:#ccd4c7;box-shadow:0 -2px 5px #2433280b}
.mobile-panes{position:relative;z-index:7;box-shadow:0 3px 6px #202b2510}
/* Utility actions share a compact icon language; decisions retain explicit labels. */
.utility-icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}
.review .icon-button{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;min-height:36px;padding:7px;text-decoration:none;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--muted)}
.review .icon-button:hover{background:#e5ebe4;border-color:#b9c8bd;color:var(--teal)}
.review .icon-button[aria-pressed=true]{background:#e5eee8;color:var(--teal)}
.review .icon-button:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.review .label-icon{display:inline-flex;align-items:center;justify-content:center;gap:7px}
.review .tray-actions .utility-icon{width:18px;height:18px;stroke-width:1.6}
.material{align-items:center;margin-bottom:10px}.material .source-link{margin-left:auto;width:30px;height:30px;min-height:30px;padding:5px}
.preview-round{justify-content:flex-end;margin-bottom:10px}
.compare-toolbar{margin-bottom:10px}
.view-controls{min-height:0}.view-controls>.background-options{margin-left:auto;gap:2px}.view-controls .swatch-button{display:flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:5px}
.background-swatch{display:block;width:20px;height:20px;border:1px solid #9ba99d;border-radius:2px;pointer-events:none}.background-swatch.checker{background-size:8px 8px}.page-swatch{background:var(--comp-background,#eee)}
.review>footer .submit-area p{max-width:25ch}
@media(max-width:1100px) and (min-width:801px){.review>header{padding:12px 20px}.review>.workbench{padding:16px 20px;gap:24px}.review>.round-summary{padding-inline:20px}.round-summary p{max-width:calc(100% - 52px)}.round-summary p>span{font-size:11px}.inventory-section>.section-head h2{max-width:none;white-space:nowrap}.review>footer>div:first-child{gap:8px}.review>footer .submit-area p{max-width:19ch}.inventory-filters button{padding-inline:8px}}
@media(max-width:800px){
.review>header{padding:12px 14px;align-items:center}.review h1{font-size:25px}.review>header p{max-width:68vw;font-size:11px}.review .badge{display:none}.review>.preview-note{padding:6px 14px}.review>.round-summary{padding:6px 14px;max-height:76px;gap:4px 8px}.round-summary p{max-width:calc(100% - 44px);gap:4px 10px;font-size:11px}.round-summary p>span{font-size:10px}.round-summary button{font-size:11px}
.mobile-panes{display:flex;flex-shrink:0;gap:4px;padding:7px 14px;border-bottom:1px solid var(--line);background:var(--paper)}.mobile-panes button{flex:1;font-size:12px;min-height:32px;padding:5px;border-color:transparent;background:transparent}.mobile-panes button[aria-pressed=true]{background:#e8efec;color:var(--teal);box-shadow:none;border-color:#bdd0c8}
.review>.workbench{display:block;padding:12px 14px;min-height:0;overflow:hidden}.workbench[data-mobile-pane=component]>.reference,.workbench[data-mobile-pane=comp]>.inspector{display:none}.reference{height:100%;max-width:none}.connector{display:none}.inspector{height:100%;border:0}.map-legend{gap:6px 12px;font-size:10px}.map-caption{font-size:9px}.reference .section-head button{min-height:30px;padding:4px 8px}.reference .section-head h2{font-size:14px}.inspector>.section-head{min-height:26px;margin-bottom:0;padding:9px 12px}.inspection-content{padding:10px 8px 10px 12px}.inspector>.review-form{padding:8px 8px 8px 12px}.number{width:23px;height:23px;font-size:11px}.section-head h2{font-size:14px}.review-form{padding-top:8px}.decision-title strong{font-size:12px}.decision-title p{font-size:10px}.decisions>button{min-height:36px}.review-form .feedback{font-size:12px}.review-form .feedback textarea{min-height:60px}.compare{max-width:none}
.review>.inventory-section{height:160px;padding:6px 14px}.inventory-section>.section-head{flex-direction:row;flex-wrap:nowrap;align-items:center;gap:6px;min-height:34px;margin-bottom:4px}.inventory-section h2{display:none}.inventory-filters{flex:1;width:auto;min-width:0;padding:2px}.inventory-filters button{padding:4px 6px;font-size:10px;min-height:28px}.inventory-filters b{margin-left:3px}.tray-actions #show-all{display:none}.tray-actions #toggle-tray{padding:6px;min-width:32px;min-height:32px}.tray-actions #toggle-tray span{display:none}.tray-actions svg{width:16px;height:16px}.review>.inventory-section.tray-collapsed{height:46px;padding:6px 14px}.review>.inventory-section.tray-expanded{height:160px}.inventory .item{flex-basis:130px;grid-template-rows:auto 1fr auto;padding:6px}.inventory .item-thumb{display:none}.inventory.all{display:flex;overflow-x:auto;overflow-y:hidden}.inventory .item strong{font-size:11px;min-height:22px}.inventory .state{font-size:10px}.inventory .item-number{font-size:10px;min-height:14px}.inventory-empty{padding:8px 0;font-size:12px}
.review>footer{padding:8px 14px calc(8px + env(safe-area-inset-bottom));gap:8px;flex-direction:column;align-items:stretch}.review>footer>div:first-child{gap:10px;justify-content:space-between}.review>footer #approve-rest{font-size:10px;min-height:32px;max-width:47%;padding:5px 8px}.review>footer .check{font-size:10px;max-width:48%;gap:4px}.review>footer .check input{width:14px;height:14px}.review>footer .submit-area{justify-content:space-between;gap:10px}.review>footer .submit-area p{font-size:10px;max-width:22ch}.review>footer .primary{min-height:34px;font-size:12px;padding:6px 10px}
}
`;var Ae=`
:host{display:block;color:var(--color-text,#292929);font:14px/1.45 var(--font-sans,Arial,sans-serif);--line:var(--color-border,#ddd);--paper:var(--color-panel,#fff);--muted:var(--color-muted,#666);--teal:var(--color-patina,#28625e);--warn:var(--color-warn,#8a5b30);--selection:#43897f}
*{box-sizing:border-box}h1,h2,p,figure{margin:0}button,input,textarea{font:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:4px;background:var(--paper);color:inherit;padding:8px 12px;min-height:36px}button:hover{border-color:var(--teal);color:var(--teal)}button:disabled{cursor:default;opacity:.45}button:focus-visible,input:focus-visible,textarea:focus-visible{outline:2px solid var(--teal);outline-offset:3px}button[aria-pressed=true]{box-shadow:inset 0 0 0 1px var(--teal)}input[type=checkbox]{accent-color:var(--teal);width:16px;height:16px;flex-shrink:0}textarea,input:not([type=checkbox]){width:100%;background:var(--paper);color:inherit;border:1px solid #999;border-radius:4px;padding:9px 10px}textarea{resize:vertical;min-height:80px}::selection{background:#c7ddd8}a{color:var(--teal)}
.review{max-width:1600px;margin:auto;padding:24px 28px 0}header{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:16px}h1{font:400 40px/1.05 var(--font-display,Arial,sans-serif);letter-spacing:-.02em}header p{margin-top:8px;font-size:15px}header p span,.medium{color:var(--muted)}.badge{border:1px solid var(--line);padding:5px 10px;font-size:12px;white-space:nowrap}.preview-note{color:var(--muted);font-size:12px;border-bottom:1px solid var(--line);padding-bottom:16px;margin-bottom:24px}
.connector{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:5;overflow:visible}.connector path{fill:none;stroke:var(--selection);stroke-width:2}.workbench{align-items:start;display:grid;grid-template-columns:minmax(0,1.18fr) minmax(0,1fr);gap:40px;position:relative}.section-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px;min-height:36px}.section-head h2{font-size:17px;font-weight:500;line-height:1.2}.section-head>span,.section-head h2>span:not(.number){font-size:12px;color:var(--muted)}.section-head button{font-size:12px}.number{display:inline-flex;align-items:center;justify-content:center;width:27px;height:27px;border:1px solid var(--teal);color:var(--teal);margin-right:7px;font:12px var(--font-mono,monospace)}
.map{position:relative;background:#eaeaea;isolation:isolate}.comp{width:100%;height:100%;display:block;user-select:none}.map.marking{touch-action:none;cursor:crosshair}.map.marking .pin{pointer-events:none;opacity:.25}.pin{position:absolute;transform:translate(-50%,-50%);padding:0;min-height:25px;width:25px;height:25px;border-radius:50%;border:1px solid #fff;background:#fff;color:#292929;font:11px var(--font-mono,monospace);box-shadow:0 1px 4px #0006;z-index:2}.pin.selected{background:var(--teal);color:#fff;border-color:var(--teal);box-shadow:none;outline:none;z-index:3}.pin:focus-visible{outline:2px solid white;outline-offset:3px}.region,.draw-box{position:absolute;pointer-events:none;outline:2px solid var(--selection);z-index:1}.draw-box{background:#28625e33;z-index:4}.map-caption{font-size:12px;color:var(--muted);padding-top:12px;min-height:40px}.map-caption button{display:block;margin-top:10px}.compare{display:grid;grid-template-columns:1fr 1fr;gap:12px}.compare figure{min-width:0}.compare figcaption{height:24px;min-height:0;font-size:12px;margin-bottom:9px}.compare figcaption span{display:block;color:var(--muted);font-size:11px}.crop-stage{position:relative;overflow:hidden;background:var(--comp-background,#eee);min-width:0}.crop-image{position:absolute;max-width:none;height:auto}.asset{display:block;width:100%;height:100%;object-fit:cover}.crop-stage iframe{position:absolute;max-width:none;border:0;transform-origin:top left;pointer-events:none}.overlay-image{opacity:.5;pointer-events:none}.component-note{font-size:12px;line-height:1.5;color:var(--muted);margin:6px 0 0}.decisions{display:flex;gap:10px;padding:10px 0;background:var(--paper);position:sticky;bottom:0;z-index:6}.decisions>button{flex:1;min-height:48px;font-weight:600;font-size:15px;border-color:var(--teal)}.decision-approve{background:var(--teal);color:white}.decision-approve:hover{background:#224e4b;color:white}.decision-revise{color:var(--teal);background:var(--paper)}.decisions>.quiet{flex:0;border:0;background:transparent;font-size:12px}.decisions .approved{color:var(--teal);background:#e9f0ed;border-color:var(--teal)}.decisions .revise{color:var(--warn);background:#f7f0e6;border-color:var(--warn)}.feedback{display:block;margin-top:16px;font-size:13px}.feedback>span{float:right;color:var(--muted);font-size:12px}.feedback textarea,.feedback input{display:block;margin-top:7px}.check{display:flex;align-items:center;gap:7px;font-size:12px;line-height:1.5;margin-top:12px}.coordinates{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:16px 0}.coordinates label{font-size:12px}.coordinates input{margin-top:5px}
.inventory-section{margin-top:28px;border-top:1px solid var(--line);padding-top:16px}.inventory-section .section-head{margin-bottom:10px}.inventory{display:flex;gap:8px;overflow-x:auto;padding:3px 3px 14px;scrollbar-color:#a6bcb8 #eee;scrollbar-width:thin}.item{position:relative;flex:0 0 134px;display:grid;grid-template-columns:20px 1fr;column-gap:8px;row-gap:3px;padding:10px;text-align:left;background:transparent}.item strong{grid-column:2;font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item.active{background:var(--paper);border-color:var(--teal)}.thumb-crop{position:relative;display:block;overflow:hidden}.item-thumb{display:flex;align-items:center;justify-content:center;grid-column:1/-1;position:relative;overflow:hidden;width:100%;height:76px;background:var(--comp-background,#eee);margin-bottom:7px}.item-thumb img{width:100%;height:100%;object-fit:contain}.item-thumb img[style]{height:auto}.inventory.all{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));overflow:visible}.item-number{grid-row:2/4;font:12px var(--font-mono,monospace);color:var(--muted);padding-top:2px}.state{grid-column:2;font-size:11px;color:var(--muted)}.state.approve{color:var(--teal)}.state.revise{color:var(--warn)}footer{display:flex;justify-content:space-between;gap:24px;padding:20px 0 24px;border-top:1px solid var(--line);margin-top:8px}.submit-area{display:flex;align-items:center;gap:20px}.submit-area p{font-size:12px;color:var(--muted);max-width:28ch}.primary{min-height:46px;font-weight:600;background:var(--teal);border-color:var(--teal);color:white;white-space:nowrap}.primary:hover{color:white;background:#224e4b}.primary:disabled{opacity:.45}.inspector>p{margin:12px 0}
@media(min-width:1300px){.workbench{gap:56px}.review{padding-top:32px}.component-note{max-width:62ch}}
@media(max-width:800px){.connector{display:none}.review{padding:20px 16px 0}.workbench{grid-template-columns:1fr;gap:24px}.reference{max-width:640px;margin:auto;width:100%}.inspector{border-top:1px solid var(--line);padding-top:16px}.compare{max-width:640px}.inventory-section .section-head{align-items:flex-start;flex-direction:column;gap:4px}footer{flex-direction:column}.submit-area{justify-content:space-between}.badge{font-size:11px}.section-head{gap:8px}h1{font-size:34px}header{align-items:flex-start}.section-head h2{font-size:16px}}
.inspector{height:690px;overflow-y:auto;scrollbar-gutter:stable;scrollbar-width:thin;padding:0 5px 0 1px;overflow-anchor:none}
.material{display:flex;align-items:baseline;flex-wrap:wrap;gap:5px 12px;min-height:34px;margin-bottom:6px}.material strong{font-size:14px;font-weight:600}.material span{font:11px var(--font-mono,monospace);color:var(--muted)}
.compare-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:16px}.compare-toolbar label{font-size:12px;display:flex;align-items:center;gap:8px}select{font:inherit;color:inherit;background:var(--paper);border:1px solid #999;border-radius:4px;padding:7px 9px;min-height:36px}select:focus-visible,.pan-viewport:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.overlay-control{display:flex;align-items:center;gap:8px;border-color:var(--teal);color:var(--teal);font-weight:500}.overlay-control[aria-pressed=true]{background:#e9f0ed}.overlay-control svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.3}
.pan-viewport{height:248px;overflow:auto;display:flex;background:#efefef;scrollbar-width:thin;scrollbar-color:#829b98 #eee;overscroll-behavior:contain}.crop-stage{flex-shrink:0;margin:auto}.checker{background-color:#eee;background-image:conic-gradient(#c7c7c7 25%,transparent 0 50%,#c7c7c7 0 75%,transparent 0);background-size:16px 16px}
.view-controls{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;margin-top:12px;min-height:36px}.view-controls>div{display:flex;background:#eee;border:1px solid var(--line);padding:2px;border-radius:4px}.view-controls button{border:0;background:transparent;font-size:12px;min-height:30px;padding:5px 9px}.view-controls button[aria-pressed=true]{background:var(--paper);box-shadow:0 1px 2px #0002}.view-controls label{font-size:11px;display:flex;gap:6px;align-items:center}.view-controls select{font-size:11px;min-height:32px;padding:5px}.view-controls>span{font-size:12px;color:var(--muted)}.scale-note{font-size:11px;color:var(--muted);margin:10px 0 0;min-height:18px}.scale-note a{white-space:nowrap}
.component-details{height:78px;overflow:auto;margin-top:12px;padding-right:4px;scrollbar-width:thin}.layering{font-size:12px;line-height:1.5}.component-details .component-note{margin-top:6px}
#approve-rest{min-height:44px;font-weight:600;border-color:var(--teal);color:var(--teal)}
@media(max-width:800px){.inspector{height:720px}.pan-viewport{height:248px}.component-details{height:92px}.view-controls label{font-size:11px}.review header{align-items:flex-start}}
.round-summary{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 20px;padding:14px 0;margin-bottom:22px;border-block:1px solid var(--line)}.round-summary p{display:flex;flex-wrap:wrap;gap:5px 18px;font-size:13px}.round-summary p>span{color:var(--muted)}.round-summary details{flex-basis:100%;font-size:12px}.round-summary details p{font-size:12px;margin-top:8px;max-width:80ch}
.repair-context{margin:0 0 16px;font-size:12px}.previous-feedback{margin:0;padding:12px 14px;background:#f0f0ed;border-radius:4px;overflow-wrap:anywhere}.previous-feedback h3{margin:0;font-size:13px;font-weight:600;display:flex;align-items:baseline;justify-content:space-between;gap:12px}.previous-feedback h3 span{font-size:11px;color:var(--muted);font-weight:400}.previous-feedback .previous-verdict{color:var(--warn);font-size:11px;margin-top:6px}.previous-feedback blockquote{margin:4px 0 0;white-space:pre-wrap;font-size:13px;line-height:1.5}.previous-feedback>p:last-child:not(.previous-verdict){margin-top:8px}.kept-approval{color:var(--teal);font-size:12px}
.preview-round{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 14px;font-size:12px}.preview-round>span{font-weight:500}.round-switch{display:flex;gap:2px;border:1px solid var(--line);border-radius:4px;padding:2px;background:#eee}.round-switch button{min-height:30px;font-size:11px;padding:5px 8px;border:0;background:transparent}.round-switch button[aria-pressed=true]{background:var(--paper);box-shadow:0 1px 2px #0002}
.changed-files{margin-top:10px;color:var(--muted)}summary{cursor:pointer;padding:5px 0;min-height:30px}summary:focus-visible{outline:2px solid var(--teal);outline-offset:2px}.changed-files ul{padding-left:18px;margin:6px 0;overflow-wrap:anywhere;font:11px/1.6 var(--font-mono,monospace)}.description-diff{margin:6px 0 12px}.description-diff dt{font-weight:600;font-size:11px;margin-top:10px}.description-diff dd{margin:4px 0 0;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--color-text,#292929)}.previous-notice{font-size:12px;color:var(--muted);margin-top:8px}.inspector .previous-notice{display:none}
.decisions{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr) auto;gap:8px 10px;padding-top:12px}.decision-title{grid-column:1/-1}.decision-title strong{font-size:14px;font-weight:600;display:flex;justify-content:space-between;gap:12px}.decision-title strong span{font-size:11px;color:var(--muted);font-weight:400}.decision-title p{font-size:12px;font-weight:400;color:var(--muted);margin-top:4px}.decisions>.quiet{align-self:center;padding-inline:4px}.decisions:not(:has(.quiet)){grid-template-columns:1fr 1fr}
.inspector{display:flex;flex-direction:column;overflow:hidden;padding:0}.inspection-content{flex:1;min-height:0;overflow:auto;scrollbar-width:thin;scrollbar-gutter:stable;overscroll-behavior:contain;padding:0 5px 10px 1px}.review-form{flex-shrink:0;padding:12px 5px 0 1px;border-top:1px solid var(--line);background:var(--paper)}.review-form .decisions{position:static;padding:0 0 8px;background:transparent}.review-form .feedback{margin-top:8px}.review-form .feedback textarea{min-height:72px;max-height:120px}.review-form .check{margin:8px 0;font-size:11px}
.inspection-content:focus-visible{outline:2px solid var(--teal);outline-offset:-2px}
.inspector>.section-head{flex-shrink:0;padding:0 5px 0 1px}
.pin{display:flex;align-items:center;justify-content:center;gap:3px;font-size:12px;font-weight:600;width:30px;height:30px;min-height:30px}.pin svg,.map-legend svg,.item-number svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}.pin.pending,.pin.pending.selected{background:#f4cd68;border-color:#f4cd68;color:#3c300f}.pin.approved,.pin.approved.selected{width:38px;min-height:24px;height:24px;background:#edf3ef;border-color:#edf3ef;color:#326258;border-radius:4px}.pin.approved:not(.selected){opacity:.55;box-shadow:none}.pin.approved:hover,.pin.approved:focus-visible{opacity:1}.pin.feedback,.pin.feedback.selected{width:38px;background:#964b2f;border-color:#fff;color:#fff;border-radius:4px}.pin.selected{outline:none;border:3px solid #fff;box-shadow:none;z-index:3}.pin:focus-visible{outline:2px solid #fff;outline-offset:3px}
.map-legend{display:flex;flex-wrap:wrap;gap:9px 18px;margin-top:14px;font-size:11px;color:var(--muted)}.map-legend>span{display:flex;align-items:center;gap:6px}.map-legend i{display:inline-flex;align-items:center;justify-content:center;min-width:21px;height:21px;font-style:normal}.legend-pending{background:#f4cd68;color:#3c300f;border-radius:50%}.legend-feedback{background:#964b2f;color:#fff;border-radius:3px}.legend-approved{background:#edf3ef;color:#326258;border-radius:3px}.map-caption{font-size:11px}
.inventory-section .section-head{flex-wrap:wrap}.inventory-filters{display:flex;gap:3px;padding:3px;background:#eee;border-radius:5px}.inventory-filters button{border:0;background:transparent;min-height:34px;padding:7px 10px;font-size:12px;white-space:nowrap}.inventory-filters button[aria-pressed=true]{background:var(--paper);color:var(--teal);box-shadow:0 1px 2px #0002}.inventory-filters b{margin-left:5px;font-weight:600;font-variant-numeric:tabular-nums}.item.pending{border-color:#b18a2f;background:#fffbef}.item.feedback{border-color:#964b2f;background:#fcf1eb}.item.approved{background:#f1f4f2;border-color:#ccd8d1}.item.approved .item-thumb{opacity:.65}.item.active{outline:2px solid var(--teal);outline-offset:0;box-shadow:none}.item-number{display:flex;align-items:center;gap:3px;grid-column:1/-1;grid-row:auto;min-height:20px;font-weight:600}.item strong{grid-column:1/-1;font-size:13px;white-space:normal;min-height:36px;line-height:1.35}.state{grid-column:1/-1;font-size:12px;font-weight:600;padding-top:6px;border-top:1px solid #0002}.state.pending{color:#725819}.state.feedback{color:#914429}.state.approved{color:#326258}.inventory-empty{padding:20px 0;font-size:13px;color:var(--muted)}
@media(max-width:800px){.inventory-section .section-head{gap:10px}.inventory-filters{width:100%}.inventory-filters button{padding-inline:7px;font-size:11px;flex:1}.inventory-filters b{margin-left:3px}}
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}}
.item-medium{margin-left:auto;font-weight:400;display:flex;align-items:center;gap:4px;font-size:10px;color:var(--muted);min-height:16px}.item-medium .utility-icon{width:14px;height:14px;flex-shrink:0}.material>.utility-icon{width:18px;height:18px;align-self:center;color:var(--teal)}
`+Re;var ai={code:'<path d="m8 6-6 6 6 6m8-12 6 6-6 6m-3-15-2 18"/>',image:'<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8" cy="8" r="1.5"/><path d="m3 17 6-6 4 4 3-3 5 5"/>',expand:'<path d="M8 3H3v5m13-5h5v5M3 16v5h5m13-5v5h-5M3 3l6 6m12-6-6 6M3 21l6-6m12 6-6-6"/>',compact:'<path d="M3 9h6V3m6 0v6h6M9 21v-6H3m12 6v-6h6M3 3l6 6m12-6-6 6M3 21l6-6m12 6-6-6"/>',hideTray:'<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 14h18m-12 3 3 2 3-2"/>',showTray:'<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 14h18m-12-4 3-3 3 3"/>',next:'<path d="M5 12h14m-6-6 6 6-6 6"/>',mark:'<path d="M9 4H4v5m11-5h5v5M4 15v5h5m11-5v5h-5M8 12h8m-4-4v8"/>',close:'<path d="m6 6 12 12M6 18 18 6"/>',undo:'<path d="M4 10h9a6 6 0 0 1 0 12M4 10l5-5m-5 5 5 5" transform="translate(0 -2)"/>',external:'<path d="M14 3h7v7m0-7L10 14m0-10H5a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h13a2 2 0 0 0 2-2v-5"/>',zoom:'<circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 5 5"/>'};function E(n){return`<svg class="utility-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">${ai[n]}</svg>`}var x=(n)=>n.replace(/[&<>"']/g,(i)=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[i]),U=(n)=>`${n*100}%`,X=(n)=>{let i=new URL(n,location.href);if(!["http:","https:"].includes(i.protocol))throw Error("Unsupported preview URL");return x(i.href)};function Oe(n,i,a){let o=n.attachShadow({mode:"open"}),r=structuredClone(a.initialDraft??Ye(i)),g=()=>[...i.components].sort((m,k)=>I(m,r,a.history).priority-I(k,r,a.history).priority),l=g().find((m)=>I(m,r,a.history).kind!=="approved")?.id??i.components[0]?.id,h=a.completed?"all":"attention",H=!1,B=!1,ie=a.completed??!1,xe="",Y=!1,$=!1,T=!0,z="comp",be=z,G="fit",S="checker",R=!1,A="isolated",de,me="fit",V=null,J=null,C=null,le='<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m3 8 3 3 7-7"/></svg>',oe='<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m3 11 1 2 2-1 7-7-3-3-7 7v2Z"/></svg>',we=(m)=>`left:${U(m.x)};top:${U(m.y)};width:${U(m.w)};height:${U(m.h)}`;function ue(m){let k=i.components.find((K)=>K.id===l);if(!k)return;if(r.decisions[k.id]={revision:k.revision,action:m,feedback:r.decisions[k.id]?.feedback??"",split:m==="revise"&&(r.decisions[k.id]?.split??!1)},d(),m==="revise"){let K=o.querySelector("#feedback");K?.focus({preventScroll:!0});let F=o.querySelector(".review-form");if(F&&K){let D=K.getBoundingClientRect().bottom-F.getBoundingClientRect().bottom;if(D>0)F.scrollTop+=D+8}let P=o.querySelector(".inspection-content"),ne=o.querySelector(".compare");if(P&&ne)P.scrollTop+=ne.getBoundingClientRect().top-P.getBoundingClientRect().top}}function ve(m){let k=`missing-${crypto.randomUUID()}`;r.missing.push({id:k,name:"Missing component",feedback:"",box:m}),r.inventoryConfirmed=!1,l=k,z="component",H=!1,V=null,J=null,d(),o.querySelector("#missing-name")?.focus()}function d(){C?.disconnect();let m=o.activeElement,k=m?.id,K=m?.dataset.select,F=window.scrollX,P=window.scrollY,ne=o.querySelector(".inventory")?.scrollLeft??0,D=de===l,Ie=z==="component"&&(!D||be!==z);be=z;let Be=D?o.querySelector(".inspection-content")?.scrollTop??0:0,Se=D&&(o.querySelector(".changed-files")?.open??!1),ye=o.querySelector(".pan-viewport"),ze=de===l&&me===G,Ce=ze?ye?.scrollLeft??0:0,Fe=ze?ye?.scrollTop??0:0;de=l,me=G;let f=i.components.find((e)=>e.id===l),b=r.missing.find((e)=>e.id===l),ke=f?.box??b?.box,je=f?i.components.indexOf(f)+1:i.components.length+r.missing.findIndex((e)=>e.id===l)+1,Me=f?r.decisions[f.id]:void 0,L=Me?.revision===f?.revision?Me:void 0,u=pe(i,r),w=a.history,j=f?te(f.id,w):void 0,Z=w?.packet.components.find((e)=>e.id===f?.id),N=R&&!!Z,p=N?Z:f,M=N?w.packet:i,He=Object.values(w?.changes??{}),Te=He.filter((e)=>e.kind==="changed").length,qe=He.filter((e)=>e.kind==="added").length,se=i.components.filter((e)=>I(e,r,w).kind==="approved"&&te(e.id,w).carried).length,ee=(e)=>I(e,r,w),Pe=i.components.length-u.approved+r.missing.length,Ee=(h==="attention"?g():i.components).filter((e)=>h==="all"||ee(e).kind==="approved"===(h==="approved")),ei=[u.revisions+r.missing.length?`${u.revisions+r.missing.length} feedback ready`:"",se?`${se} ${se===1?"approval":"approvals"} kept`:"",Te?`${Te} changed`:"",qe?`${qe} added`:"",w?.removed.length?`${w.removed.length} removed`:""].filter(Boolean).join(" · "),ii=xe||(ie?a.preview?"Preview submitted. No run changed.":"Review submitted.":u.hasFeedback?"Ready to send for corrections.":u.pending?`${u.pending} left to review`:!r.inventoryConfirmed?"Confirm the map is complete.":"Ready to continue."),Q=p?ae(p):null,Ve=p?.preview.kind==="image"&&!Q?.code,W=!!(p?.context&&A==="context"),Je=p&&(W?p.context?.kind!=="image":p.preview.kind==="page"),Ne=W&&p?.context?p.context.url:p?.preview.url,oi=Q?.code?`${Q.label} · ${Q.captured?"captured from code":"live preview"}`:p?.material?`${p.material.alpha==="transparent"?"Transparent":p.material.alpha==="opaque"?"Opaque":"Transparency unverified"} ${p.material.format}`:"Raster · transparency unverified";if(o.innerHTML=`<style>${Ae}</style><section class="review" aria-label="Component review" style="--comp-background:${/^#[0-9a-f]{6}$/i.test(i.comp.background??"")?i.comp.background:"#eeeeee"}">
<header><div><h1>Review the components.</h1><p>${x(i.title)} <span>· Round ${i.round}</span></p></div>${a.preview?'<span class="badge">Interactive preview</span>':""}</header>
${a.preview?'<p class="preview-note">Historical hotel artwork for testing this interface. Decisions stay in this preview; no run is changed.</p>':""}
${w?`<section class="round-summary" aria-label="Changes since previous round"><p><strong>${u.pending} ${u.pending===1?"component":"components"} to review</strong><span>${ei}</span></p>${u.pending?`<button id="review-changes" class="icon-button" aria-label="Next to review" title="Next to review">${E("next")}</button>`:""}${w.removed.length?`<details><summary>Removed from the map</summary><p>${w.removed.map((e)=>x(e.name)).join(" · ")}. Confirm these omissions are intentional before accepting the map.</p></details>`:""}</section>`:""}
<div class="mobile-panes" role="group" aria-label="Inspection view"><button id="show-comp" aria-pressed="${z==="comp"}">Approved comp</button><button id="show-component" aria-pressed="${z==="component"}">Component ${je}</button></div>
<div class="workbench" data-mobile-pane="${z}"><svg class="connector" aria-hidden="true"><path /></svg>
<section class="reference" aria-label="Approved composition">
<div class="section-head"><h2>Approved comp</h2><button id="mark" class="label-icon" aria-pressed="${H}">${E(H?"close":"mark")}${H?"Cancel":"Mark missing"}</button></div>
<div class="map-space"><div class="map ${H?"marking":""}" style="aspect-ratio:${i.comp.width}/${i.comp.height}">
<img class="comp" src="${X(i.comp.url)}" alt="Approved composition for ${x(i.title)}" draggable="false">
${ke?`<div class="region" style="${we(ke)}"></div>`:""}
${i.components.map((e,t)=>{let s=ee(e);return`<button class="pin ${s.kind} ${l===e.id?"selected":""}" data-select="${x(e.id)}" style="left:${U(Math.min(0.96,e.box.x+e.box.w/2))};top:${U(Math.max(0.035,e.box.y))}" aria-label="Inspect ${x(e.name)}${x(s.label)}" title="${t+1}. ${x(e.name)} · ${x(s.label)}" aria-pressed="${l===e.id}">${s.kind==="approved"?le:s.kind==="feedback"?oe:""}<span>${t+1}</span></button>`}).join("")}
${r.missing.map((e,t)=>`<button class="pin feedback ${l===e.id?"selected":""}" data-select="${x(e.id)}" style="left:${U(e.box.x+e.box.w/2)};top:${U(e.box.y)}" aria-label="Inspect missing ${x(e.name)}" title="Missing: ${x(e.name)}">${oe}<span>${i.components.length+t+1}</span></button>`).join("")}
<div class="draw-box" hidden></div>
</div></div>
<div class="map-legend" aria-label="Map status legend"><span><i class="legend-pending">#</i> To review</span><span><i class="legend-feedback">${oe}</i> Feedback ready</span><span><i class="legend-approved">${le}</i> Approved</span></div>
${H?'<div class="map-caption">Draw around the missing piece.<button id="add-box">Add an adjustable box</button></div>':""}
</section>
<section class="inspector" aria-label="Selected component">
<div class="section-head"><h2><span class="number">${je}</span> ${x(f?.name??b?.name??"Component")}</h2></div><div class="inspection-content" role="region" aria-label="Component comparison" tabindex="0">
${f?`<div class="material">${E(Q.code?"code":"image")}<strong>${x(oi)}</strong><span>${p?.material?`${p.material.width} × ${p.material.height} px`:""}</span>${p?.preview.kind==="image"?`<a class="icon-button source-link" href="${X(p.preview.url)}" target="_blank" rel="noopener" aria-label="${Q.fileLabel}" title="${Q.fileLabel}">${E("external")}</a>`:""}</div>
${w?`<div class="repair-context">
${j?.prior?.action==="revise"?`<section class="previous-feedback" aria-label="Previous feedback"><h3>Previous feedback <span>Round ${j.feedbackRound}</span></h3><p class="previous-verdict">Needs work</p><blockquote>${x(j.prior.feedback||"No written feedback was supplied.")}</blockquote>${j.prior.split?"<p>Requested: split into separately reviewable components.</p>":""}</section>`:j?.carried?'<p class="kept-approval">Unchanged · approval kept</p>':""}
${j?.change?.kind==="changed"?`<details class="changed-files" ${Se?"open":""}><summary>${j.change.files.length?`${j.change.files.length} changed ${j.change.files.length===1?"file":"files"}`:j.change.reasons.includes("region")?"Region changed":Z?.note!==f.note?"Description changed · files unchanged":"Component definition changed · files unchanged"}</summary>${j.change.files.length?`<ul>${j.change.files.map((e)=>`<li>${x(e)}</li>`).join("")}</ul>`:""}${Z&&Z.note!==f.note?`<dl class="description-diff"><dt>Previous description</dt><dd>${x(Z.note)}</dd><dt>Current description</dt><dd>${x(f.note)}</dd></dl>`:""}</details>`:""}
</div>`:""}
${Z?`<div class="preview-round"><div class="round-switch" role="group" aria-label="Preview version"><button id="current-round" aria-pressed="${!N}">Current · round ${i.round}</button><button id="previous-round" aria-pressed="${N}">Previous · round ${w.packet.round}</button></div></div>`:""}
<div class="compare-toolbar"><label title="Comparison zoom · based on comp pixels">${E("zoom")}<select id="zoom" aria-label="Comparison zoom">${[["fit","Fit"],["1","100%"],["2","200%"],["4","400%"]].map(([e,t])=>`<option value="${e}" ${String(G)===e?"selected":""}>${t}</option>`).join("")}</select></label><button id="overlay" class="overlay-control" aria-pressed="${Y}"><svg viewBox="0 0 20 20" aria-hidden="true"><rect x="3" y="3" width="10" height="10"/><rect x="7" y="7" width="10" height="10"/></svg>Overlay comp</button></div>
<div class="compare">
<figure><figcaption>${N?`Comp · Round ${w.packet.round}`:"In the comp"}</figcaption><div class="pan-viewport" aria-label="Reference comparison canvas" tabindex="0"><div class="crop-stage"><img class="crop-image" src="${X(M.comp.url)}" alt="Reference region for ${x(p.name)}" style="width:${100/p.box.w}%;left:${-100*p.box.x/p.box.w}%;top:${-100*p.box.y/p.box.h}%"></div></div></figure>
<figure><figcaption>${N?`Previous · Round ${w.packet.round}`:w?`Current · Round ${i.round}`:W?"In the page":Q.caption}</figcaption><div class="pan-viewport" aria-label="Produced comparison canvas" tabindex="0"><div class="output crop-stage ${Ve&&!W&&!Je&&S==="checker"?"checker":""}">${!Je?`<img class="asset" src="${X(Ne)}" alt="Produced ${x(p.name)}" style="object-position:${x(p.preview.position??"center")}">`:`<iframe aria-hidden="true" title="Rendered ${x(p.name)}" src="${X(Ne)}" sandbox="" tabindex="-1" width="${M.comp.width}" height="${M.comp.height}"></iframe>`}${Y?`<img class="crop-image overlay-image" src="${X(M.comp.url)}" alt="Reference overlay" style="width:${100/p.box.w}%;left:${-100*p.box.x/p.box.w}%;top:${-100*p.box.y/p.box.h}%">`:""}</div></div></figure>
</div>
${Ve?`<div class="view-controls">${p.context?`<div role="group" aria-label="Asset view"><button id="isolated" aria-pressed="${!W}">Asset only</button><button id="context" aria-pressed="${W}">In page</button></div>`:""}<div class="background-options" role="group" aria-label="Asset preview background"><button id="background-checker" class="swatch-button" aria-label="Checkerboard background" title="Checkerboard background" aria-pressed="${S==="checker"}" ${W?"disabled":""}><span class="background-swatch checker"></span></button><button id="background-page" class="swatch-button" aria-label="${M.comp.background?"Page color":"Neutral"} background" title="${M.comp.background?"Page color":"Neutral"} background" aria-pressed="${S==="page"}" ${W?"disabled":""}><span class="background-swatch page-swatch"></span></button></div></div>`:""}
<div class="component-details"><p class="layering">${x(p?.context?.layering??"Layer placement not recorded.")}</p>
<p class="component-note">${x(p.note)}</p>
</div></div><div class="review-form">${N?'<p class="previous-notice">Viewing the previous round. Return to Current to make a decision.</p>':""}<div class="decisions" role="group" aria-label="Decision for ${x(f.name)}"><div class="decision-title"><strong>Your review <span>Round ${i.round}</span></strong>${N?"<p>Return to Current to review this round.</p>":""}</div><button id="approve" class="decision-approve ${L?.action==="approve"?"approved":""}" aria-pressed="${L?.action==="approve"}">Looks good</button><button id="revise" class="decision-revise ${L?.action==="revise"?"revise":""}" aria-pressed="${L?.action==="revise"}">Needs work</button>${L?`<button id="clear" class="quiet icon-button" aria-label="Clear decision" title="Clear decision">${E("undo")}</button>`:""}</div>
${L?.action==="revise"?`<label class="feedback">New feedback <span>Optional</span><textarea id="feedback" placeholder="What still needs to change in this version?">${x(L.feedback)}</textarea></label><label class="check"><input id="split" type="checkbox" ${L.split?"checked":""}> Split into separately reviewable components</label>`:""}
</div>`:b?`<p>This piece will be added to the unresolved inventory.</p><label class="feedback">Name<input id="missing-name" value="${x(b.name)}"></label><label class="feedback">What is missing?<textarea id="missing-feedback">${x(b.feedback)}</textarea></label><div class="coordinates">${["x","y","w","h"].map((e)=>`<label>${{x:"Left",y:"Top",w:"Width",h:"Height"}[e]} %<input type="number" data-coordinate="${e}" value="${Math.round(b.box[e]*1000)/10}" min="0" max="100" step="0.1"></label>`).join("")}</div><button id="remove-missing">Remove this mark</button></div>`:"<p>No components supplied.</p></div>"}
</section>
</div>
<section class="inventory-section ${T?"":"tray-collapsed"} ${$&&T?"tray-expanded":""}" aria-label="Component inventory"><div class="section-head"><h2>Components</h2><div class="inventory-filters" role="group" aria-label="Filter components"><button data-filter="attention" aria-pressed="${h==="attention"}">Needs attention <b>${Pe}</b></button><button data-filter="approved" aria-pressed="${h==="approved"}">Approved <b>${u.approved}</b></button><button data-filter="all" aria-pressed="${h==="all"}">All <b>${i.components.length+r.missing.length}</b></button></div><div class="tray-actions"><button id="show-all" class="icon-button" aria-pressed="${$}" aria-controls="component-tray" aria-label="${$?"Compact":"Expand"} tray" title="${$?"Compact":"Expand"} tray">${E($?"compact":"expand")}</button><button id="toggle-tray" class="icon-button" aria-expanded="${T}" aria-controls="component-tray" aria-label="${T?"Hide":"Show"} component tray" title="${T?"Hide":"Show"} component tray">${E(T?"hideTray":"showTray")}</button></div></div>
<div id="component-tray" class="inventory ${$?"all":""}">${Ee.map((e)=>{let t=i.components.indexOf(e),s=ee(e);return`<button class="item ${s.kind} ${l===e.id?"active":""}" data-select="${x(e.id)}" aria-pressed="${l===e.id}">${e.thumbnail?`<span class="item-thumb">${e.thumbnail.box?`<span class="thumb-crop" style="width:min(100%,${76*e.thumbnail.box.w*i.comp.width/(e.thumbnail.box.h*i.comp.height)}px);aspect-ratio:${e.thumbnail.box.w*i.comp.width}/${e.thumbnail.box.h*i.comp.height}"><img alt="" loading="lazy" src="${X(e.thumbnail.url)}" style="position:absolute;width:${100/e.thumbnail.box.w}%;max-width:none;left:${-100*e.thumbnail.box.x/e.thumbnail.box.w}%;top:${-100*e.thumbnail.box.y/e.thumbnail.box.h}%;"></span>`:`<img alt="" loading="lazy" src="${X(e.thumbnail.url)}">`}</span>`:""}<span class="item-number">${s.kind==="approved"?le:s.kind==="feedback"?oe:""}${t+1}<span class="item-medium">${E(ae(e).code?"code":"image")}${x(ae(e).label)}</span></span><strong>${x(e.name)}</strong><span class="state ${s.kind}">${x(s.label)}</span></button>`}).join("")}${(h==="approved"?[]:r.missing).map((e,t)=>`<button class="item feedback ${l===e.id?"active":""}" data-select="${x(e.id)}"><span class="item-number">${i.components.length+t+1}</span><strong>${x(e.name)}</strong><span class="state revise">Missing</span></button>`).join("")}${!Ee.length&&(h==="approved"||!r.missing.length)?`<p class="inventory-empty">${h==="attention"?"Every component is approved. Confirm the map is complete, then continue.":"No components approved yet."}</p>`:""}</div></section>
<footer><div><button id="approve-rest" ${!u.pending?"disabled":""}>Approve ${u.approved||u.revisions?"remaining":"all"}</button><label class="check"><input id="inventory-confirm" type="checkbox" ${r.inventoryConfirmed?"checked":""}> Nothing missing from the comp</label></div><div class="submit-area"><p role="status">${x(ii)}</p><button id="submit" class="primary" ${!u.canSubmit||B||ie?"disabled":""}>${B?"Sending…":u.hasFeedback?"Send feedback":"Approve & continue"}</button></div></footer>
</section>`,N)o.querySelectorAll(".decisions button,#feedback,#split,#approve-rest,#submit,#inventory-confirm").forEach((e)=>e.disabled=!0);if(o.querySelector(".inspection-content").scrollTop=Be,ie||B)o.querySelectorAll(".decisions button,#approve-rest,#mark,#inventory-confirm,#missing-name,#missing-feedback,#feedback,#split,#remove-missing,[data-coordinate]").forEach((e)=>e.disabled=!0);if(o.querySelector(".inventory").scrollLeft=ne,!D&&T)Array.from(o.querySelectorAll(".inventory [data-select]")).find((e)=>e.dataset.select===l)?.scrollIntoView({block:"nearest",inline:"nearest"});if(k)o.getElementById(k)?.focus({preventScroll:!0});else if(K)Array.from(o.querySelectorAll(".item[data-select]")).find((e)=>e.dataset.select===K)?.focus({preventScroll:!0});let c=(e,t)=>o.querySelector(`#${e}`)?.addEventListener("click",t);o.querySelectorAll("[data-select]").forEach((e)=>e.onclick=()=>{if(H)return;l=e.dataset.select,z="component",Y=!1,G="fit",A="isolated",R=!1,d()}),c("previous-round",()=>{R=!0,d()}),c("current-round",()=>{R=!1,d()}),c("review-changes",()=>{let e=g().filter((y)=>ee(y).kind==="pending"),t=e.findIndex((y)=>y.id===l),s=e[(t+1)%e.length];if(s)l=s.id,z="component",h="attention",R=!1,G="fit",Y=!1,A="isolated",d()}),o.querySelectorAll("[data-filter]").forEach((e)=>e.onclick=()=>{h=e.dataset.filter;let t=g().filter((y)=>h==="all"||ee(y).kind==="approved"===(h==="approved"));if(!(h!=="approved"&&r.missing.some((y)=>y.id===l))&&!t.some((y)=>y.id===l)&&t.length)l=t[0].id,z="component",R=!1,G="fit",Y=!1,A="isolated";d()}),c("approve",()=>ue("approve")),c("revise",()=>ue("revise")),c("clear",()=>{if(f)delete r.decisions[f.id];d()}),c("overlay",()=>{Y=!Y,d()}),c("isolated",()=>{A="isolated",d()}),c("context",()=>{A="context",d()}),o.querySelector("#zoom")?.addEventListener("change",(e)=>{let t=e.target.value;G=t==="fit"?"fit":Number(t),d()}),c("background-checker",()=>{S="checker",d()}),c("background-page",()=>{S="page",d()}),c("show-all",()=>{$=!$,T=!0,d()}),c("toggle-tray",()=>{T=!T,d()}),c("show-comp",()=>{z="comp",d()}),c("show-component",()=>{z="component",d()}),c("mark",()=>{H=!H,d()}),c("add-box",()=>ve({x:0.35,y:0.35,w:0.2,h:0.2})),c("remove-missing",()=>{r.missing=r.missing.filter((e)=>e.id!==l),l=i.components[0]?.id,d()}),c("approve-rest",()=>{r=De(i,r),d()}),o.querySelector("#inventory-confirm")?.addEventListener("change",(e)=>{r.inventoryConfirmed=e.target.checked,d()}),o.querySelector("#feedback")?.addEventListener("input",(e)=>{if(f)r.decisions[f.id].feedback=e.target.value}),o.querySelector("#split")?.addEventListener("change",(e)=>{if(f)r.decisions[f.id].split=e.target.checked}),o.querySelector("#missing-name")?.addEventListener("input",(e)=>{if(b)b.name=e.target.value;let t=o.querySelector("#submit");if(t)t.disabled=!pe(i,r).canSubmit}),o.querySelector("#missing-feedback")?.addEventListener("input",(e)=>{if(b)b.feedback=e.target.value}),o.querySelectorAll("[data-coordinate]").forEach((e)=>e.addEventListener("change",()=>{if(!b)return;let t=e.dataset.coordinate,s=Number(e.value)/100;if(Number.isFinite(s))b.box[t]=Math.max(t==="w"||t==="h"?0.001:0,Math.min(1,s));b.box.w=Math.min(b.box.w,1-b.box.x),b.box.h=Math.min(b.box.h,1-b.box.y),d()})),c("submit",async()=>{B=!0,xe="",d();try{await a.onSubmit(Ze(i,r)),ie=!0}catch(e){xe=e instanceof Error?e.message:"Could not save. Try again."}finally{B=!1,d()}});let _=o.querySelector(".map");function $e(e){let t=_.getBoundingClientRect();return{x:Math.max(0,Math.min(1,(e.clientX-t.left)/t.width)),y:Math.max(0,Math.min(1,(e.clientY-t.top)/t.height))}}_.addEventListener("pointerdown",(e)=>{if(!H)return;V=$e(e),_.setPointerCapture(e.pointerId),e.preventDefault()}),_.addEventListener("pointermove",(e)=>{if(!V)return;let t=$e(e);J={x:Math.min(V.x,t.x),y:Math.min(V.y,t.y),w:Math.abs(t.x-V.x),h:Math.abs(t.y-V.y)};let s=o.querySelector(".draw-box");s.hidden=!1,s.style.cssText=we(J)}),_.addEventListener("pointerup",()=>{if(J&&J.w>0.01&&J.h>0.01)ve(J);else V=null,J=null}),_.addEventListener("pointercancel",()=>{V=null,J=null,d()});let Ge=o.querySelector(".output"),re=o.querySelector("iframe"),Ke=o.querySelector(".workbench"),O=o.querySelector(".inspection-content"),he=o.querySelector(".inspector");function ge(){if(!O||!he)return;he.dataset.scrollAbove=String(O.scrollTop>1),he.dataset.scrollBelow=String(O.scrollHeight-O.clientHeight-O.scrollTop>1)}O?.addEventListener("scroll",ge,{passive:!0});function Le(){let e=o.querySelector(".map-space");if(e&&e.clientWidth&&e.clientHeight){let v=ce(i.comp.width,i.comp.height,Math.max(1,e.clientWidth-32),Math.max(1,e.clientHeight-32),"fit");_.style.width=`${v.width}px`,_.style.height=`${v.height}px`}let t=o.querySelector(".inspection-content"),s=Array.from(o.querySelectorAll(".pan-viewport"));if(t?.clientHeight)s.forEach((v)=>v.style.height=`${Math.min(248,Math.max(100,t.clientHeight-40))}px`);if(p&&s.length){let v=ce(p.box.w*M.comp.width,p.box.h*M.comp.height,Math.min(...s.map((q)=>q.clientWidth)),Math.min(...s.map((q)=>q.clientHeight)),G);o.querySelectorAll(".crop-stage").forEach((q)=>{q.style.width=`${v.width}px`,q.style.height=`${v.height}px`})}if(Ge&&re&&p){let v=Ge.clientWidth/(p.box.w*M.comp.width);re.style.transform=`scale(${v})`,re.style.left=`${-p.box.x*M.comp.width*v}px`,re.style.top=`${-p.box.y*M.comp.height*v}px`}let y=Ke.getBoundingClientRect(),We=o.querySelector(".region"),_e=o.querySelector(".number"),Ue=o.querySelector(".connector path");if(We&&_e&&Ue){let v=We.getBoundingClientRect(),q=_e.getBoundingClientRect(),ni=v.right-y.left,ri=v.top+v.height/2-y.top,Xe=q.left-y.left-8,ti=q.top+q.height/2-y.top;Ue.setAttribute("d",`M ${ni} ${ri} H ${Xe-14} V ${ti} H ${Xe}`)}}C=new ResizeObserver(()=>{Le(),ge()}),C.observe(Ke);let Qe=o.querySelector(".inspection-content");if(Qe)C.observe(Qe);Le();let fe=Array.from(o.querySelectorAll(".pan-viewport"));if(fe.forEach((e)=>{e.scrollLeft=Ce,e.scrollTop=Fe}),fe.forEach((e)=>e.addEventListener("scroll",()=>{for(let t of fe)if(t!==e){if(t.scrollLeft!==e.scrollLeft)t.scrollLeft=e.scrollLeft;if(t.scrollTop!==e.scrollTop)t.scrollTop=e.scrollTop}})),Ie&&window.matchMedia("(max-width:800px)").matches){let e=o.querySelector(".inspection-content"),t=o.querySelector(".compare");if(e&&t)e.scrollTop+=t.getBoundingClientRect().top-e.getBoundingClientRect().top}ge(),window.scrollTo(F,P)}return d(),{destroy(){C?.disconnect(),o.replaceChildren()},getDraft(){return structuredClone(r)}}}async function xi(){let n=document.getElementById("review"),i=await fetch("/packet",{cache:"no-store"});if(!i.ok)throw Error("The review packet could not be loaded. Reload to retry.");let a=await i.json();if(a.sourceStatus){let o=document.createElement("p");o.textContent=a.sourceStatus,n.before(o)}Oe(n,a.packet,{initialDraft:a.draft,history:a.history,completed:!!a.receipt,onSubmit:async(o)=>{let r=await fetch("/decision",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),g=await r.json();if(!r.ok)throw Error(g.error??"The review could not be saved. Try again.")}})}xi().catch((n)=>{let i=document.createElement("p");i.textContent=n instanceof Error?n.message:String(n),document.getElementById("review")?.replaceChildren(i)});})();