What Changed Added scene-owned occupancy data:Scenes.FloorPlanID Scenes.InitialFloorPlanFloorID new SceneFloorPlanOccupancy table Added Occupancy section to the Scene Inspector with AJAX save/refresh. Added Floor Plan editor Structure / Occupancy mode. Occupancy mode shows linked scenes, previous/next scene controls, and character/asset tokens inside assigned locations. Character tokens use image thumbnail, then initials fallback. Asset tokens do the same, smaller. No Features, no new floor-plan architecture, no transition changes. Files Changed [CoreModels.cs](C:/Source/PlotLine/PlotLine/Models/CoreModels.cs) [CoreViewModels.cs](C:/Source/PlotLine/PlotLine/ViewModels/CoreViewModels.cs) [Repositories.cs](C:/Source/PlotLine/PlotLine/Data/Repositories.cs) [CoreServices.cs](C:/Source/PlotLine/PlotLine/Services/CoreServices.cs) [ScenesController.cs](C:/Source/PlotLine/PlotLine/Controllers/ScenesController.cs) [Program.cs](C:/Source/PlotLine/PlotLine/Program.cs) [Edit.cshtml](C:/Source/PlotLine/PlotLine/Views/FloorPlans/Edit.cshtml) [_SceneInspector.cshtml](C:/Source/PlotLine/PlotLine/Views/Scenes/_SceneInspector.cshtml) [_SceneOccupancy.cshtml](C:/Source/PlotLine/PlotLine/Views/Scenes/_SceneOccupancy.cshtml) [site.js](C:/Source/PlotLine/PlotLine/wwwroot/js/site.js) [site.css](C:/Source/PlotLine/PlotLine/wwwroot/css/site.css) minified CSS/JS updated. Database Added new migration only: [080_SceneFloorPlanOccupancy.sql](C:/Source/PlotLine/PlotLine/Sql/080_SceneFloorPlanOccupancy.sql) Applied locally. Added constraints/FKs and filtered unique indexes for one character/asset occupancy row per scene. Stored Procedures Added/Updated Scene_Save Scene_Get Scene_FloorPlanLink_Save Scene_ListByFloorPlan FloorPlanFloor_ListByPlan FloorPlanBlock_ListByPlan SceneFloorPlanOccupancy_ListByScene SceneFloorPlanOccupancy_ListByFloorPlan SceneFloorPlanOccupancy_Save Test Data Permanent Calendar House seed data added: Book: The Calendar House Chapters 1-3 14 scenes Characters: Ellie, Grant, Stephen, Housekeeper, Gardener Assets: Brass Key, Journal, Candle, Map, Letter 45 occupancy assignments Verification dotnet build PlotLine.slnx passes with 0 warnings/errors. Migration applied locally. Local app boots and returns HTTP 200. DB checks confirm 14 linked scenes and 45 occupancy rows. Manual browser click-through of the authenticated UI still needs a human pass for the AJAX controls and visual token placement.
1 line
47 KiB
JavaScript
1 line
47 KiB
JavaScript
(()=>{const n="plotline.theme",t=document.querySelectorAll("[data-theme-choice]"),r=()=>{try{return localStorage.getItem(n)}catch{return null}},u=t=>{try{localStorage.setItem(n,t)}catch{}},i=n=>{const i=n==="dark"?"dark":"light";document.documentElement.dataset.theme=i;document.documentElement.dataset.bsTheme=i;u(i);t.forEach(n=>{const t=n.dataset.themeChoice===i;n.classList.toggle("active",t);n.setAttribute("aria-pressed",String(t))})};t.forEach(n=>{n.addEventListener("click",()=>i(n.dataset.themeChoice))});i(r()||"light")})();(()=>{const n=document.querySelector("[data-trial-banner]");if(n){const t=`plotline.trialBanner.dismissed.${n.dataset.trialBannerKey||"current"}`;try{if(localStorage.getItem(t)==="true"){n.hidden=!0;return}}catch{}n.querySelector("[data-trial-banner-dismiss]")?.addEventListener("click",()=>{n.hidden=!0;try{localStorage.setItem(t,"true")}catch{}})}})();(()=>{const n=document.querySelectorAll(".scene-inspector-root");if(n.length){const t=()=>`${window.location.pathname}${window.location.search}${window.location.hash}`;n.forEach(n=>{n.addEventListener("submit",n=>{const r=n.target;if(r instanceof HTMLFormElement){let i=r.querySelector("input[name='ReturnUrl']");i||(i=document.createElement("input"),i.type="hidden",i.name="ReturnUrl",r.appendChild(i));i.value=t()}},!0)})}})();(()=>{const t=document.querySelectorAll(".scene-inspector-root");if(t.length){const n=(n,t,i)=>{const r=n.querySelector("[data-scene-occupancy-status]");r&&(r.textContent=t,r.dataset.saveState=i||"")},i=(n,t)=>{const r=document.createElement("div");r.innerHTML=t;const i=r.querySelector("[data-scene-occupancy-form]");if(i){n.replaceWith(i);const t=i.closest(".scene-inspector-root")?.querySelector(".scene-inspector-form"),r=t?.querySelector("[name='FloorPlanID']"),u=t?.querySelector("[name='InitialFloorPlanFloorID']"),f=i.querySelector("[name='FloorPlanID']"),e=i.querySelector("[name='InitialFloorPlanFloorID']");r&&f&&(r.value=f.value);u&&e&&(u.value=e.value)}};t.forEach(t=>{t.addEventListener("change",async t=>{const u=t.target.closest?.("[data-occupancy-plan-select]");if(u){const r=u.closest("[data-scene-occupancy-form]"),e=r?.querySelector("[name='SceneID']")?.value,o=r?.dataset.editorUrl;if(r&&e&&o){n(r,"Loading...","saving");const f=new URL(o,window.location.origin);f.searchParams.set("sceneId",e);u.value&&f.searchParams.set("floorPlanId",u.value);try{const n=await fetch(f,{headers:{"X-Requested-With":"XMLHttpRequest"}});if(!n.ok)throw new Error("The occupancy editor could not be refreshed.");i(r,await n.text())}catch(s){n(r,s.message||"Load failed","failed")}}}}),t.addEventListener("submit",async t=>{const r=t.target.closest?.("[data-scene-occupancy-form]");if(r){t.preventDefault();n(r,"Saving...","saving");try{const n=await fetch(r.action,{method:"POST",body:new FormData(r),headers:{"X-Requested-With":"XMLHttpRequest"}});if(!n.ok){let t="Save failed";const i=n.headers.get("content-type")||"";if(i.includes("application/json")){const i=await n.json();t=i.message||t}throw new Error(t);}i(r,await n.text())}catch(u){n(r,u.message||"Save failed","failed")}}})})}})();(()=>{const t=document.getElementById("plotlineConfirmModal"),f=t&&window.bootstrap?new bootstrap.Modal(t):null,e=t?.querySelector("#plotlineConfirmModalTitle"),o=t?.querySelector("#plotlineConfirmModalMessage"),i=t?.querySelector("[data-confirm-submit]"),s=t?.querySelector("[data-delete-confirm-field]"),r=t?.querySelector("[data-delete-confirm-input]");let n=null,u=null;const h=n=>{const t=n.split(/\n\s*\n/);return{title:t.shift()?.trim()||"Confirm action",body:t.join("\n\n").trim()}},c=()=>{n=null,u=null,r&&(r.value=""),s?.classList.add("d-none"),i?.removeAttribute("disabled")};t?.addEventListener("hidden.bs.modal",c);r?.addEventListener("input",()=>{n?.dataset.requireDeleteText==="true"&&i&&(i.disabled=r.value.trim()!=="DELETE")});i?.addEventListener("click",()=>{if(n){if(n.dataset.requireDeleteText==="true"){const t=n.querySelector("[data-delete-confirm-target], [name='confirmationText']");t&&(t.value=r?.value.trim()||"")}n.dataset.confirmed="true";f?.hide();n.requestSubmit&&u?n.requestSubmit(u):n.requestSubmit?n.requestSubmit():n.submit()}});document.addEventListener("submit",c=>{const l=c.target;if(l instanceof HTMLFormElement&&l.dataset.confirmed!=="true"&&l.dataset.confirmSkip!=="true"&&l.dataset.noDeleteConfirm!=="true"){const b=l.dataset.confirmMessage,k=c.submitter instanceof HTMLElement?c.submitter:l.querySelector("button[type='submit']"),a=k?.textContent||"",p=l.getAttribute("action")||"",y=/restore/i.test(a)||/\/restore\b/i.test(p),v=/delete forever/i.test(a)||/\/deleteforever\b/i.test(p),d=!y&&!v&&(/archive/i.test(a)||/\/archive\b/i.test(p)),nt=!v&&/(delete|remove)/i.test(a);if(b||d||y||nt||v){const tt=b||(d?"Archive this item?\n\nThis will hide it from active lists and timelines, but the data will be kept and can be restored later.":y?"Restore this item?\n\nThis will return it to active lists and timelines where applicable.":v?"Permanently delete this item?\n\nThis cannot be undone. Any linked planning data may also be removed or become unavailable. Only continue if you are certain.":"Remove this item?\n\nThis removes the link or supporting record. Major story items are archived instead of permanently deleted.");if(f&&t&&e&&o&&i){c.preventDefault();const g=h(tt);n=l;u=k;e.textContent=l.dataset.confirmTitle||g.title;o.textContent=l.dataset.confirmBody||g.body;i.textContent=l.dataset.confirmButtonText||a.trim()||"Confirm";i.className=`btn ${l.dataset.confirmButtonClass||(y?"btn-primary":"btn-danger")}`;const w=l.dataset.requireDeleteText==="true"||v;s?.classList.toggle("d-none",!w);w&&i&&(i.disabled=!0,r?.focus());f.show();w&&window.setTimeout(()=>r?.focus(),200)}}}})})();(()=>{const r=document.querySelectorAll("[data-help-icon]");if(r.length&&window.bootstrap){const u=new Map;let n=null;const f=n=>n.querySelector("[data-help-tooltip]")||n,i=n=>bootstrap.Tooltip.getInstance(f(n))?.hide(),t=(t=null)=>{n&&n!==t&&bootstrap.Popover.getInstance(n)?.hide()},e=()=>r.forEach(n=>i(n));r.forEach(r=>{const o=f(r),s=o.dataset.helpMicro||"Help article not found.",h=r.dataset.helpTitle||"Help unavailable",c=r.dataset.helpSummary||"Help content has not yet been written for this feature.";bootstrap.Tooltip.getOrCreateInstance(o,{title:s,trigger:"hover focus",placement:"top",container:"body"});const l=bootstrap.Popover.getOrCreateInstance(r,{title:h,trigger:"click",placement:"auto",container:"body",html:!0,customClass:"plotline-help-popover",content(){const u=document.createElement("div"),i=document.createElement("p");i.className="mb-2";i.textContent=c;const n=document.createElement("a");return n.href="#",n.textContent="More...",n.className="plotline-help-more",n.addEventListener("click",n=>{n.preventDefault(),t(),e(),r.dispatchEvent(new CustomEvent("plotline:help-more",{bubbles:!0,detail:{key:r.dataset.helpKey||""}}))}),u.append(i,n),u}});u.set(r,l);r.addEventListener("click",n=>{window.PlotLineHelpDrawer?.isActive?.()&&(n.preventDefault(),n.stopPropagation(),t(),i(r),window.PlotLineHelpPopovers?.hideAll(),window.PlotLineHelpDrawer.open(r.dataset.helpKey||""))});r.addEventListener("show.bs.popover",n=>{if(window.PlotLineHelpDrawer?.isActive?.()){n.preventDefault();return}t(r);i(r)});r.addEventListener("shown.bs.popover",()=>{n=r,i(r)});r.addEventListener("hidden.bs.popover",()=>{n===r&&(n=null)})});document.addEventListener("click",i=>{const r=i.target instanceof Element?i.target:null;if(r&&n&&!r.closest("[data-help-icon]")){const u=document.querySelector(`#${n.getAttribute("aria-describedby")}`);u?.contains(r)||t()}});document.addEventListener("keydown",n=>{n.key==="Escape"&&t()});window.PlotLineHelpPopovers={hideAll(){u.forEach(n=>n.hide());e();n=null}}}})();(()=>{const n=document.querySelector("[data-help-layout]"),p=document.querySelector("[data-help-drawer]"),i=document.querySelector("[data-help-content]"),l=document.querySelector("[data-help-splitter]"),w=document.querySelector("[data-help-close]"),h=document.querySelector("[data-help-collapse]"),a=document.querySelector("[data-help-expand]"),u=document.querySelector("[data-help-pin]"),f=document.querySelector("[data-help-drawer-heading]");if(n&&p&&i){const t={width:"plotline.helpDrawer.width",pinned:"plotline.helpDrawer.pinned",key:"plotline.helpDrawer.key",collapsed:"plotline.helpDrawer.collapsed"},b=(n,t,i)=>Math.min(i,Math.max(t,n)),e=n=>{try{return localStorage.getItem(n)==="true"}catch{return!1}},o=(n,t)=>{try{localStorage.setItem(n,t)}catch{}},v=i=>{const r=`${b(Number.parseFloat(i)||25,20,50)}%`;n.style.setProperty("--help-drawer-width",r);o(t.width,r)},c=n=>{o(t.pinned,String(n)),u?.setAttribute("aria-pressed",String(n)),u&&(u.textContent=n?"Unpin":"Pin")},r=i=>{n.classList.toggle("help-collapsed",i),o(t.collapsed,String(i)),h&&(h.textContent=i?"Expand":"Collapse")},s=async(u,e={})=>{const s=u||"";n.classList.add("help-open");e.keepCollapsed||r(!1);o(t.key,s);f&&(f.textContent=s||"Help");i.innerHTML='<p class="muted">Loading help...<\/p>';try{const n=await fetch(`/HelpDrawer/Article?key=${encodeURIComponent(s)}`,{headers:{"X-Requested-With":"XMLHttpRequest"}});i.innerHTML=n.ok?await n.text():'<p class="muted">Help for this feature has not yet been written.<\/p>';const t=i.querySelector(".help-drawer-article h2")?.textContent?.trim();f&&t&&(f.textContent=t)}catch{i.innerHTML='<p class="muted">Help for this feature has not yet been written.<\/p>'}},k=()=>{n.classList.remove("help-open","help-collapsed"),c(!1)};window.PlotLineHelpDrawer={isActive(){return n.classList.contains("help-open")},open(n){return s(n)}};v(localStorage.getItem(t.width)||"25%");c(e(t.pinned));r(e(t.collapsed));document.addEventListener("plotline:help-more",n=>{const t=n.detail?.key||n.target?.dataset?.helpKey||"";window.PlotLineHelpPopovers?.hideAll();s(t)});document.addEventListener("click",n=>{const t=n.target instanceof Element?n.target.closest("[data-help-open]"):null;t&&(n.preventDefault(),window.PlotLineHelpPopovers?.hideAll(),s(t.dataset.helpOpen||""))});w?.addEventListener("click",k);h?.addEventListener("click",()=>r(!n.classList.contains("help-collapsed")));a?.addEventListener("click",()=>r(!1));a?.addEventListener("keydown",n=>{(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),r(!1))});u?.addEventListener("click",()=>c(!e(t.pinned)));l?.addEventListener("pointerdown",t=>{if(n.classList.contains("help-open")&&!n.classList.contains("help-collapsed")){t.preventDefault();document.body.classList.add("help-resizing");l.setPointerCapture?.(t.pointerId);const i=t=>{const i=n.getBoundingClientRect(),r=(i.right-t.clientX)/i.width*100;v(r)},r=()=>{document.body.classList.remove("help-resizing"),document.removeEventListener("pointermove",i),document.removeEventListener("pointerup",r)};document.addEventListener("pointermove",i);document.addEventListener("pointerup",r)}});const y=(()=>{try{return localStorage.getItem(t.key)||""}catch{return""}})();e(t.pinned)&&y&&s(y,{keepCollapsed:!0})}})();(()=>{const n=document.querySelectorAll(".scene-inspector-root");if(n.length){const t=n=>n.toLowerCase().replace(/&/g,"and").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");n.forEach(n=>{const o=n.dataset.sceneId||n.querySelector(".scene-inspector-form [name='SceneID']")?.value||"new",f=`plotline.inspector.${o}.`,s=new Set(["overview","timing","characters"]),u=[...n.querySelectorAll(".inspector-section")],r=n.querySelector(".scene-inspector-form"),i=n.querySelector("[data-scene-time-editor]");if(r&&i&&i.dataset.enhanced!=="true"){i.dataset.enhanced="true";const n=r.querySelector("[name='TimeModeID']"),e=i.dataset.exactDatetimeModeId||"",o=i.dataset.exactDateModeId||"",s=[...i.querySelectorAll("[data-time-part-field]")],h={StartDateTime:i.querySelector("[data-date-part-for='StartDateTime']"),EndDateTime:i.querySelector("[data-date-part-for='EndDateTime']")},c={StartDateTime:i.querySelector("[data-time-part-for='StartDateTime']"),EndDateTime:i.querySelector("[data-time-part-for='EndDateTime']")},l={StartDateTime:r.querySelector("input[type='hidden'][name='StartDateTime']"),EndDateTime:r.querySelector("input[type='hidden'][name='EndDateTime']")},t=()=>n?.value===e,a=()=>n?.value===o,u=()=>{const n=t();s.forEach(t=>{t.hidden=!n,t.classList.toggle("d-none",!n)})},f=n=>{const i=l[n],r=h[n]?.value||"",f=c[n]?.value||"";if(i){if(!r){i.value="";return}if(t()){i.value=`${r}T${f||"00:00"}`;return}if(a()){i.value=r;return}const u=i.value.match(/^\\d{4}-\\d{2}-\\d{2}T(\\d{2}:\\d{2})/)?.[1];i.value=u?`${r}T${u}`:r}},v=()=>{f("StartDateTime"),f("EndDateTime")};n?.addEventListener("change",u);r.addEventListener("submit",v);u()}u.forEach(n=>{if(n.dataset.enhanced!=="true"){const l=n.querySelector("h3, h2"),u=n.dataset.sectionTitle||l?.textContent?.trim()||"Overview",a=t(u),o=n.dataset.sectionId||`inspector-${a}`,e=o.replace(/^inspector-/,"");n.id=o;n.dataset.searchText=`${u} ${n.textContent}`.toLowerCase();n.dataset.enhanced="true";const r=document.createElement("div");for(r.className="inspector-accordion-body";n.firstChild;)r.appendChild(n.firstChild);const i=document.createElement("button");i.type="button";i.className="inspector-accordion-toggle";i.setAttribute("aria-expanded","false");n.append(i,r);const v=r.querySelectorAll(".asset-event-item, .thread-event-item, .warning-mini-card, .dependency-card, .metric-slider").length;i.innerHTML=`<span>${u}</span><span class="accordion-count">${v||""}</span>`;const h=localStorage.getItem(`${f}${e}`),c=h===null?s.has(e):h==="open";n.classList.toggle("is-open",c);i.setAttribute("aria-expanded",String(c));i.addEventListener("click",()=>{const t=!n.classList.contains("is-open");n.classList.toggle("is-open",t);i.setAttribute("aria-expanded",String(t));localStorage.setItem(`${f}${e}`,t?"open":"closed")})}});n.querySelectorAll(".inspector-nav-links a").forEach(t=>{t.addEventListener("click",i=>{const r=n.querySelector(t.getAttribute("href"));r&&(i.preventDefault(),r.classList.add("is-open"),r.querySelector(".inspector-accordion-toggle")?.setAttribute("aria-expanded","true"),r.scrollIntoView({behavior:"smooth",block:"start"}))})});const e=n.querySelector("[data-inspector-search]");if(e?.addEventListener("input",()=>{const n=e.value.trim().toLowerCase();u.forEach(t=>{const i=!n||t.dataset.searchText.includes(n);t.classList.toggle("is-filtered-out",!i);i&&n&&(t.classList.add("is-open"),t.querySelector(".inspector-accordion-toggle")?.setAttribute("aria-expanded","true"))})}),"IntersectionObserver"in window){const t=[...n.querySelectorAll(".inspector-nav-links a")],i=new IntersectionObserver(n=>{n.forEach(n=>{n.isIntersecting&&t.forEach(t=>{t.classList.toggle("active",t.getAttribute("href")===`#${n.target.id}`)})})},{root:n.closest(".scene-inspector-panel")||null,rootMargin:"-10% 0px -70% 0px"});u.forEach(n=>i.observe(n))}})}})();(()=>{const n=document.querySelectorAll("[data-metric-shape-chart]");if(n.length){const t=["#2f6f63","#b35f3b","#6750a4","#c58b2b","#7d3f52","#33759b","#9c4d8f","#a33d3d","#6f8f3d","#d08a2f","#444b57"],u=new Set(["Overall Intensity","Tension","Emotional Weight","Action"]),i=[],r=()=>{window.requestAnimationFrame(()=>{i.forEach(n=>n.resize())})};window.PlotLineMetricCharts={resizeAll:r};window.addEventListener("plotline:timeline-layout-changed",r);const f={id:"plotlineChapterGuides",afterDraw(n){const u=n.config.options.plugins.plotlineScenes||[],{ctx:t,chartArea:r,scales:f}=n;if(u.length&&f.x){t.save();t.strokeStyle="rgba(47, 111, 99, 0.16)";t.fillStyle="rgba(82, 75, 65, 0.58)";t.lineWidth=1;t.font="700 10px system-ui, -apple-system, Segoe UI, sans-serif";const e=[];u.forEach((n,t)=>{t&&n.chapterLabel===u[t-1].chapterLabel||e.push({scene:n,index:t})});const i=e.filter(n=>!n.scene.isPlaceholder),o=i.length>40?5:i.length>24?4:i.length>14?2:1;i.forEach((n,u)=>{const{scene:s,index:c}=n,e=f.x.getPixelForValue(c+.5);t.beginPath();t.moveTo(e,r.top);t.lineTo(e,r.bottom);t.stroke();const h=Number.parseInt(String(s.chapterLabel).replace(/[^0-9]/g,""),10),l=u===0||u===i.length-1||o===1||Number.isFinite(h)&&h%o==0;l&&t.fillText(s.chapterLabel,Math.min(e+4,r.right-70),r.bottom+22)});t.restore()}}};window.Chart&&Chart.register(f);n.forEach(n=>{const p=n.querySelector("script[type='application/json']"),o=n.querySelector("canvas"),w=n.querySelector("[data-metric-toggles]"),b=n.querySelector("[data-chart-fallback]"),k=n.querySelector("[data-timeline-metric-edit-toolbar]"),c=n.querySelector("[data-edit-metric-status]"),v=n.hasAttribute("data-timeline-metric-editor")&&k&&c;if(p&&o){let f;try{f=JSON.parse(p.textContent||"{}")}catch{b?.classList.remove("d-none");return}if(!window.Chart||!f.scenes?.length||!f.metrics?.length){b?.classList.remove("d-none");return}const tt=new Set(f.defaultMetrics?.length?f.defaultMetrics:u),s=f.scenes.length,it=s>80?10:s>40?5:s>24?4:s>14?2:1,l=f.metrics.map((n,i)=>{const u=new Map(n.points.map(n=>[n.sceneId,n.value])),r=t[i%t.length];return{label:n.metricName,metricTypeId:n.metricTypeId,minValue:Number.isFinite(n.minValue)?n.minValue:1,maxValue:Number.isFinite(n.maxValue)?n.maxValue:10,data:f.scenes.map((n,t)=>{const i=u.get(n.sceneId);return i==null?{x:t+.5,y:null,sceneId:n.sceneId}:{x:t+.5,y:i,sceneId:n.sceneId}}),borderColor:r,backgroundColor:r,pointBackgroundColor:r,pointBorderColor:"#fff",pointBorderWidth:1.5,pointRadius:3,pointHoverRadius:5,borderWidth:2,tension:.35,spanGaps:!0,hidden:!tt.has(n.metricName)}});let e=null,a=!1;const h=n=>{c&&(c.textContent=n)},d=()=>l.map((n,t)=>t).filter(n=>r?.isDatasetVisible(n)),g=()=>{const n=d();return n.length===1?n[0]:-1},nt=()=>{if(v){const n=g();l.forEach((t,i)=>{t.pointRadius=i===n?5:3,t.pointHoverRadius=i===n?7:5,t.borderWidth=i===n?3:2});const t=d(),i=n>=0;o.classList.toggle("metric-curve-editing",i);c.classList.toggle("d-none",t.length===0);t.length===0?h(""):i?h("Drag points up or down to edit this metric."):h("Show only one metric line to edit its curve.");r?.update()}};w.innerHTML="";l.forEach((n,t)=>{const u=`metric-toggle-${Math.random().toString(36).slice(2)}-${t}`,i=document.createElement("label");i.className="metric-toggle-pill";i.style.setProperty("--metric-colour",n.borderColor);i.innerHTML=`<input type="checkbox" id="${u}" ${n.hidden?"":"checked"}><span>${n.label}</span>`;w.appendChild(i);i.querySelector("input").addEventListener("change",n=>{r.setDatasetVisibility(t,n.target.checked),nt(),r.update()})});const r=new Chart(o,{type:"line",data:{datasets:l},options:{responsive:!0,maintainAspectRatio:!1,interaction:{mode:"nearest",intersect:!1},scales:{y:{min:1,max:10,ticks:{stepSize:1},grid:{color:"rgba(82, 75, 65, 0.10)"}},x:{type:"linear",min:0,max:s,bounds:"ticks",ticks:{maxRotation:0,autoSkip:!1,maxTicksLimit:16,callback(n){const t=Math.floor(Number(n));if(t<0||t>=s)return"";const i=f.scenes[t];return t!==0&&t!==s-1&&t%it!=0?"":i?.isPlaceholder?"":i?.axisLabel||""}},afterBuildTicks(n){n.ticks=f.scenes.map((n,t)=>({value:t+.5}))},grid:{display:!1}}},plugins:{legend:{display:!1},plotlineScenes:f.scenes,tooltip:{callbacks:{title(n){const i=Math.floor(n[0].parsed.x),t=f.scenes[i];return t.isPlaceholder?t.sceneTitle||"No scene":`${t.chapterLabel}, Scene ${t.sceneNumber}: ${t.sceneTitle}`},label(n){return`${n.dataset.label}: ${n.formattedValue}`},afterBody(n){const r=Math.floor(n[0].parsed.x),t=f.scenes[r],i=[];return t.characterPresence?i.push(t.characterPresence):t.characterAppears!==undefined&&i.push(t.characterAppears?"Character appears":"Character does not appear"),t.bookTitle&&i.push(t.bookTitle),i}}}}}}),rt=n=>{if(!v)return null;const u=g();if(u<0)return null;const f=r.getElementsAtEventForMode(n,"nearest",{intersect:!0},!0),t=f.find(n=>n.datasetIndex===u);if(!t)return null;const e=r.data.datasets[t.datasetIndex],i=e?.data?.[t.index];return!i?.sceneId||i.y==null?null:{datasetIndex:t.datasetIndex,pointIndex:t.index,previousValue:i.y}},ut=(n,t)=>{const i=r.scales.y.getValueForPixel(n.offsetY),u=Math.round(i);return Math.max(t.minValue,Math.min(t.maxValue,u))},ft=async t=>{const u=r.data.datasets[t.datasetIndex],i=u?.data?.[t.pointIndex];if(u&&i?.sceneId){const f=k?.querySelector("input[name='__RequestVerificationToken']")?.value||"";h("Saving...");try{const e=await fetch(n.dataset.updateUrl||"/SceneMetrics/UpdateSceneMetricValue",{method:"POST",headers:{"Content-Type":"application/json",RequestVerificationToken:f},body:JSON.stringify({sceneId:Number(i.sceneId),metricId:Number(u.metricTypeId),value:Number(i.y)})}),t=await e.json().catch(()=>({}));if(!e.ok||!t.success)throw new Error(t.message||"Save failed");i.y=t.value;h("Saved");r.update()}catch{i.y=t.previousValue;h("Save failed");r.update()}}};v&&(nt(),o.addEventListener("pointerdown",n=>{(e=rt(n),e)&&(n.preventDefault(),o.setPointerCapture?.(n.pointerId),a=!0)}),o.addEventListener("pointermove",n=>{if(e){const t=r.data.datasets[e.datasetIndex],i=t?.data?.[e.pointIndex];t&&i&&(i.y=ut(n,t),r.update("none"))}}),o.addEventListener("pointerup",async n=>{if(e){const t=e;e=null;o.releasePointerCapture?.(n.pointerId);await ft(t);window.setTimeout(()=>{a=!1},0)}}),o.addEventListener("pointercancel",n=>{if(e){const i=r.data.datasets[e.datasetIndex],t=i?.data?.[e.pointIndex];t&&(t.y=e.previousValue);e=null;o.releasePointerCapture?.(n.pointerId);a=!1;r.update()}}));o.addEventListener("click",n=>{if(a){n.preventDefault();return}const t=r.getElementsAtEventForMode(n,"nearest",{intersect:!0},!0);if(t.length){const i=r.data.datasets[t[0].datasetIndex]?.data?.[t[0].index],e=i?Math.floor(i.x):t[0].index,u=f.scenes[e];u?.timelineUrl&&(window.PlotLineTimelineScroll?.save(),window.location.href=u.timelineUrl)}});i.push(r);const y=window.ResizeObserver?new ResizeObserver(()=>r.resize()):null;y&&(y.observe(n),y.observe(n.querySelector(".metric-chart-frame")||n))}})}})();(()=>{const i=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-timeline-controls]");if(i&&t){const r="plotline.timeline.visibility",u={"scene-cards":!0,"plot-lines":!0,assets:!0,characters:!0,locations:!0,warnings:!0,metrics:!0},e=()=>{try{return{...u,...JSON.parse(localStorage.getItem(r)||"{}")}}catch{return{...u}}},f=n=>{Object.entries(n).forEach(([n,t])=>{i.classList.toggle(`hide-${n}`,!t),document.querySelectorAll(`[data-timeline-section='${n}']`).forEach(n=>{n.classList.toggle("timeline-section-hidden",!t)})}),document.querySelectorAll("[data-location-marker]").forEach(t=>{t.classList.toggle("timeline-section-hidden",!n.locations)}),document.querySelectorAll("[data-warning-marker]").forEach(t=>{t.classList.toggle("timeline-section-hidden",!n.warnings)}),document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(n))},n=e();t.querySelectorAll("[data-timeline-toggle]").forEach(t=>{const i=t.dataset.timelineToggle;t.checked=n[i]??!0;t.addEventListener("change",()=>{n[i]=t.checked,localStorage.setItem(r,JSON.stringify(n)),f(n)})});t.querySelector("[data-timeline-preset-form]")?.addEventListener("submit",()=>{document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(n))});f(n)}})();(()=>{const n=document.querySelector("[data-timeline-root]");if(n){const r=n.querySelector(".timeline-workspace")||n,o=document.querySelector("[data-timeline-density]"),u=document.querySelector("[data-timeline-zoom]"),t=document.querySelector("[data-timeline-search]"),c=n.querySelector("[data-timeline-minimap]"),ot=document.querySelectorAll("[data-timeline-jump], [data-timeline-warning-jump], [data-timeline-entity-jump]"),st=[...n.querySelectorAll(".timeline-scene-card[data-scene-id]")],l="plotline.timeline.collapsed",nt="plotline.timeline.density",tt="plotline.timeline.zoom",s="plotline.timeline.pending-scroll",it="plotdirector.timelineNavigationDiscovered",a="plotline.timeline.inspectorWidth",v=8,ht=".timeline-shell,.plot-lane-board,.asset-lane-board,.character-lane-board,.metric-graph-stack,[data-timeline-minimap]",rt=["compact","comfortable","expanded"],h=["book","chapter","scene-detail","scene-overview"],y=n.querySelector("[data-inspector-drawer]"),i=n.querySelector("[data-inspector-resizer]"),ct=n.querySelector("[data-inspector-close]"),lt=n=>{try{return new Set(JSON.parse(localStorage.getItem(n)||"[]"))}catch{return new Set}},at=localStorage.getItem(l)!==null,f=lt(l),p=()=>[...n.querySelectorAll(ht)],vt=()=>{const t=n.querySelector("[data-timeline-navigation-card]");if(t){const nt=t.querySelector("[data-timeline-navigation-dismiss]"),tt=t.querySelector("[data-timeline-navigation-demo]"),rt=()=>{try{return localStorage.getItem(it)==="true"}catch{return!1}},y=()=>n.querySelector(".timeline-shell"),e=n=>Boolean(n&&n.scrollWidth>n.clientWidth+v),w=n=>Math.max(0,n.scrollWidth-n.clientWidth),s=()=>[r,...p()].filter((n,t,i)=>n&&i.indexOf(n)===t);let b=!1,h=!1,k=!1,l=null;const d=new WeakMap,f=n=>d.set(n,n.scrollLeft),ut=()=>window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches,ft=()=>{window.clearTimeout(l),t.hidden=!1,t.classList.remove("is-dismissing")},g=(n=false)=>{if(window.clearTimeout(l),!n||ut()||t.hidden){t.classList.remove("is-dismissing");t.hidden=!0;return}t.classList.add("is-dismissing");l=window.setTimeout(()=>{t.hidden=!0,t.classList.remove("is-dismissing")},1300)},c=n=>{const t=e(n),i=w(n);n.classList.toggle("timeline-horizontal-overflow",t);n.classList.toggle("has-scroll-left",t&&n.scrollLeft>v);n.classList.toggle("has-scroll-right",t&&n.scrollLeft<i-v)},i=()=>{const n=e(y())&&!rt()&&!b;n?ft():t.classList.contains("is-dismissing")||g(!1);s().forEach(c)},a=()=>{b=!0;g(!0);try{localStorage.setItem(it,"true")}catch{}},et=n=>{if(c(n),!k||h){f(n);return}const t=d.get(n)??n.scrollLeft;if(Math.abs(n.scrollLeft-t)>4&&e(n)){a();return}f(n)},ot=s();ot.forEach(n=>{f(n),n.addEventListener("wheel",t=>{if(!t.defaultPrevented&&t.shiftKey&&Math.abs(t.deltaY)>Math.abs(t.deltaX)&&e(n)){const i=n.scrollLeft;t.preventDefault();n.scrollLeft+=t.deltaY;window.requestAnimationFrame(()=>{c(n),!h&&Math.abs(n.scrollLeft-i)>4&&a(),f(n)})}},{passive:!1}),n.addEventListener("scroll",()=>et(n),{passive:!0})});nt?.addEventListener("click",a);tt?.addEventListener("click",()=>{const n=y();if(!e(n)){i();return}const t=n.scrollLeft,o=w(n)-t;let r=Math.min(220,o),u=1;(r<24&&t>0&&(r=Math.min(220,t),u=-1),r<4)||(h=!0,n.scrollTo({left:t+r*u,behavior:"smooth"}),window.setTimeout(()=>{n.scrollTo({left:t,behavior:"smooth"})},420),window.setTimeout(()=>{h=!1,s().forEach(n=>{f(n),c(n)}),i()},950))});window.setTimeout(()=>{k=!0,s().forEach(f)},800);window.requestAnimationFrame(i);window.addEventListener("resize",i);window.addEventListener("plotline:timeline-layout-changed",i);o?.addEventListener("change",()=>window.requestAnimationFrame(i));u?.addEventListener("change",()=>window.requestAnimationFrame(i))}},yt=()=>{const t=n.getBoundingClientRect().width||window.innerWidth,i=Math.min(420,Math.max(320,t-360)),r=Math.max(i,Math.min(t*.65,t-420));return{minimum:i,maximum:r,defaultWidth:Math.max(i,Math.min(r,t*.4))}},w=t=>{if(y){const{minimum:i,maximum:r,defaultWidth:u}=yt(),f=Math.max(i,Math.min(r,Number(t)||u));n.style.setProperty("--timeline-inspector-width",`${Math.round(f)}px`)}};y&&(w(localStorage.getItem(a)),window.addEventListener("resize",()=>w(localStorage.getItem(a))));vt();i?.addEventListener("pointerdown",t=>{t.preventDefault();document.body.classList.add("timeline-inspector-resizing");i.setPointerCapture(t.pointerId);const u=t=>{const i=n.getBoundingClientRect().right,r=i-t.clientX;w(r)},r=()=>{document.body.classList.remove("timeline-inspector-resizing");const n=Math.round(y?.getBoundingClientRect().width||0);n>0&&localStorage.setItem(a,String(n));window.dispatchEvent(new Event("plotline:timeline-layout-changed"));i.removeEventListener("pointermove",u);i.removeEventListener("pointerup",r);i.removeEventListener("pointercancel",r)};i.addEventListener("pointermove",u);i.addEventListener("pointerup",r);i.addEventListener("pointercancel",r)});ct?.addEventListener("click",n=>{n.preventDefault();const t=new URL(window.location.href),i=t.searchParams.get("FocusType")||t.searchParams.get("focusType");t.searchParams.delete("SelectedSceneID");t.searchParams.delete("selectedSceneId");t.searchParams.delete("selectedsceneid");i&&i.toLowerCase()!=="scene"||(t.searchParams.delete("FocusType"),t.searchParams.delete("focusType"),t.searchParams.delete("FocusID"),t.searchParams.delete("focusId"));b();window.location.href=t.toString()});const b=()=>{try{sessionStorage.setItem(s,JSON.stringify({path:window.location.pathname,top:window.scrollY,left:window.scrollX,workspaceLeft:r.scrollLeft,workspaceTop:r.scrollTop,containers:p().map((n,t)=>({index:t,left:n.scrollLeft,top:n.scrollTop}))}))}catch{}},pt=()=>{let n=null;try{n=JSON.parse(sessionStorage.getItem(s)||"null");sessionStorage.removeItem(s)}catch{try{sessionStorage.removeItem(s)}catch{}return}n&&n.path===window.location.pathname&&window.requestAnimationFrame(()=>{r.scrollLeft=Number(n.workspaceLeft)||0;r.scrollTop=Number(n.workspaceTop)||0;const t=p();(n.containers||[]).forEach(n=>{const i=t[n.index];i&&(i.scrollLeft=Number(n.left)||0,i.scrollTop=Number(n.top)||0)});window.scrollTo(Number(n.left)||0,Number(n.top)||0)})};window.PlotLineTimelineScroll={save:b};const ut=(t,i,r)=>{r.forEach(i=>n.classList.remove(`${t}-${i}`)),n.classList.add(`${t}-${i}`)},ft=n=>{const t=rt.includes(n)?n:"comfortable";ut("density",t,rt);o&&(o.value=t);localStorage.setItem(nt,t);window.requestAnimationFrame(e)},k=n=>{const t=h.includes(n)?n:"chapter";ut("zoom",t,h);u&&(u.value=t);localStorage.setItem(tt,t);window.requestAnimationFrame(e)},wt=t=>n.querySelector(`[data-scene-id='${CSS.escape(String(t))}']`),d=(n,t=true)=>{const i=wt(n);i&&(i.classList.add("keyboard-focus"),i.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),t&&i.focus({preventScroll:!0}),window.setTimeout(()=>i.classList.remove("keyboard-focus"),1800))},bt=(n="auto")=>{const r=c?.querySelector("[data-active-chapter='true']");if(c&&r){const t=c.getBoundingClientRect(),i=r.getBoundingClientRect(),u=i.left+i.width/2,f=t.left+t.width/2;(i.left<t.left||i.right>t.right)&&c.scrollBy({left:u-f,behavior:n})}},kt=()=>localStorage.setItem(l,JSON.stringify([...f])),e=()=>{n.querySelectorAll("[data-plot-line-svg-board]").forEach(n=>{const u=n.querySelector("[data-plot-line-svg]");if(u){let i=n.querySelector("[data-plot-line-health-svg]");i||(i=document.createElementNS("http://www.w3.org/2000/svg","svg"),i.setAttribute("class","plot-lane-svg-overlay plot-lane-svg-health-overlay"),i.setAttribute("data-plot-line-health-svg",""),i.setAttribute("aria-hidden","true"),u.after(i));u.replaceChildren();i.replaceChildren();const r=n.getBoundingClientRect(),o=Math.max(n.scrollWidth,r.width),s=Math.max(n.scrollHeight,r.height);[u,i].forEach(n=>{n.setAttribute("viewBox",`0 0 ${o} ${s}`),n.setAttribute("width",String(o)),n.setAttribute("height",String(s))});const t=(n,t={})=>{const i=document.createElementNS("http://www.w3.org/2000/svg",n);return Object.entries(t).forEach(([n,t])=>i.setAttribute(n,String(t))),i},v=n=>{const t=n.getBoundingClientRect();return t.top-r.top+t.height/2},y=(n,t)=>{const u=t.closest("[data-plot-line-slot]");if(!u)return null;const f=u.getBoundingClientRect(),i=t.getBoundingClientRect(),e=i.left-r.left+i.width/2,o=i.top-r.top+i.height/2;return{x:e,y:o,sceneX:f.left-r.left+f.width/2,radius:Math.max(i.width,i.height)/2,sceneIndex:0,type:(t.dataset.plotEventType||"Progress").toLowerCase(),targetPlotLineId:t.dataset.targetPlotLineId||"",targetPlotLineIds:(t.dataset.targetPlotLineIds||"").split(",").map(n=>n.trim()).filter(Boolean)}},h=[...new Set([...n.querySelectorAll("[data-plot-line-slot]")].map(n=>{const t=n.getBoundingClientRect();return Math.round(t.left-r.left+t.width/2)}))].sort((n,t)=>n-t),c=Math.max(h.length-1,0),e=new Map;n.querySelectorAll("[data-plot-line-row]").forEach(n=>{n.dataset.plotLineId&&e.set(n.dataset.plotLineId,n)});const f=[...e.values()].map(n=>({row:n,plotLineId:n.dataset.plotLineId,colour:n.dataset.plotColour||"#2f6f63",points:[...n.querySelectorAll("[data-plot-event-node]")].map(t=>y(n,t)).filter(Boolean).map(n=>({...n,sceneIndex:Math.max(0,h.findIndex(t=>t===Math.round(n.sceneX)))})).sort((n,t)=>n.x-t.x),incomingStartXs:[]})),l=new Map(f.map(n=>[n.plotLineId,n])),p=n=>{const t=n.some(n=>n.type==="resolve"),i=n.some(n=>n.type==="abandon"),u=n.find(n=>n.type==="resolve"),l=n.find(n=>n.type==="abandon"),f=n.some(n=>n.type==="merge"&&n.targetPlotLineId),e=n.some(n=>n.type==="split"&&n.targetPlotLineIds.length>=2),a=Boolean(u)&&n.some(n=>n.type==="branch"&&n.targetPlotLineId&&n.sceneIndex>=u.sceneIndex),o=n.reduce((n,t)=>Math.max(n,t.sceneIndex),0),s=!t&&!i&&c-o>=10,v=!t&&!i&&!f&&!e&&c>=o,h=t&&!f&&!e&&!a;let r="active";return i?r="abandoned":t?r=h?"questionable":"resolved":s&&(r="neglected"),{state:r,isNeglected:s,isDangling:v,isQuestionable:h,isAbandoned:i,resolvePoint:u,abandonPoint:l}},w=n=>n.map(n=>[`Warning: ${n.label}`,`Reason: ${n.reason}`,`Suggested Action: ${n.action}`].join("\n")).join("\n\n"),b=(n,r)=>{const h=Math.max(18,(n.radius||12)+8),o=r[0]?.kind||"warning",e=t("g",{"class":`plotline-health-marker plotline-health-marker-${o}`}),s=t("title");s.textContent=w(r);e.append(s);const u=n.x+h,f=n.y-13;if(o==="questionable"){e.append(t("circle",{cx:u,cy:f,r:9}));const n=t("text",{x:u,y:f+4,"text-anchor":"middle"});n.textContent=r.length>1?String(r.length):"?";e.append(n)}else if(e.append(t("circle",{cx:u,cy:f,r:10})),e.append(t("path",{d:`M ${u} ${f-6} L ${u+6} ${f+5} L ${u-6} ${f+5} Z`})),e.append(t("line",{x1:u,y1:f-2,x2:u,y2:f+2.5})),e.append(t("circle",{cx:u,cy:f+5.3,r:1.2})),r.length>1){const n=t("text",{x:u+11,y:f-6,"text-anchor":"middle","class":"plotline-health-count"});n.textContent=String(r.length);e.append(n)}i.append(e)},a=(n,i,r,f)=>{if(!i){window.console?.warn&&window.console.warn(`Plot line ${f} connector skipped because target plot line data is missing.`);return}const e={x:n.x,y:v(i)},s=Math.max(28,Math.min(46,Math.abs(e.y-n.y)*.28)),c=Math.max(8,n.radius||12),o=n.x+c,l=o+s,h=o+s/2;u.append(t("path",{"class":`plot-line-svg-connector plot-line-svg-connector-${f}`,d:`M ${o} ${n.y} C ${h} ${n.y}, ${h} ${e.y}, ${l} ${e.y}`,stroke:r}))};f.forEach(n=>{n.points.forEach(n=>{n.type==="branch"&&n.targetPlotLineId&&l.get(n.targetPlotLineId)?.incomingStartXs.push(n.x),n.type==="split"&&n.targetPlotLineIds.forEach(t=>{l.get(t)?.incomingStartXs.push(n.x)})})});f.forEach(n=>{n.health=p(n.points),n.state=n.health.state});f.forEach(n=>{const{colour:f,points:i,incomingStartXs:r,state:e}=n;if(i.length>0){const l=i.find(n=>n.type==="start"),o=r.length?Math.min(...r):null,h=i.find(n=>n.type==="split"||n.type==="merge"),a=i[0],y=i[i.length-1],b=o!==null&&!(l&&l.x<o)?o:a.x,p=h?h.x:y.x;n.lineEndPoint={x:p,y:a.y};const c=[...i].sort((n,t)=>n.x-t.x),v=h?c.indexOf(h):-1,s=v>=0?c.slice(0,v+1):c;o!==null&&o<s[0].x&&s.unshift({...s[0],x:o,sceneIndex:Math.max(0,s[0].sceneIndex-1)});const w=10;s.forEach((n,i)=>{const r=s[i+1];if(r&&!(r.x<=n.x)){const o=Math.abs(r.sceneIndex-n.sceneIndex)>=w;u.append(t("path",{"class":`plot-line-svg-path plot-line-svg-segment ${o?"plot-line-svg-segment-dormant":""} plotline-state-${e}`,d:`M ${n.x} ${n.y} L ${r.x} ${r.y}`,stroke:f}))}})}});f.forEach(({points:t,colour:n})=>{t.forEach(t=>{(t.type==="branch"||t.type==="merge")&&a(t,e.get(t.targetPlotLineId),n,t.type),t.type==="split"&&t.targetPlotLineIds.length>=2?t.targetPlotLineIds.forEach(i=>{a(t,e.get(i),n,t.type)}):t.type==="split"&&t.targetPlotLineIds.length>0&&window.console?.warn&&window.console.warn("Plot line split connector skipped because fewer than two targets are available.")})});f.forEach(n=>{const i=n.lineEndPoint||n.points[n.points.length-1],t=[];n.health?.isNeglected&&i&&t.push({point:i,kind:"neglected",label:"Dormant Thread",reason:"No activity for 10+ scenes.",action:"Add a Progress, Reveal or Resolve event."});n.health?.isDangling&&i&&t.push({point:i,kind:"dangling",label:"Unresolved Thread",reason:"Thread contains no Resolve or Abandon event by the end of the current timeline.",action:"Review ending scenes and add Resolve or Abandon where appropriate."});n.health?.isQuestionable&&n.health.resolvePoint&&t.push({point:n.health.resolvePoint,kind:"questionable",label:"Missing Closure",reason:"Resolved without merge, split, or branch.",action:"Verify this thread has sufficient payoff."});n.health?.isAbandoned&&n.health.abandonPoint&&t.push({point:n.health.abandonPoint,kind:"abandoned",label:"Abandoned Thread",reason:"This plot line was abandoned.",action:"Check this is intentional."});const r=new Map;t.forEach(n=>{const t=`${Math.round(n.point.x)}:${Math.round(n.point.y)}`;r.has(t)||r.set(t,{point:n.point,warnings:[]});r.get(t).warnings.push(n)});r.forEach(({point:n,warnings:t})=>b(n,t))})}})},et=(n,t)=>{document.querySelectorAll(`[data-collapse-body='${CSS.escape(n)}']`).forEach(n=>{n.classList.toggle("timeline-section-collapsed",t)}),document.querySelectorAll(`[data-collapse-toggle='${CSS.escape(n)}']`).forEach(n=>{n.classList.toggle("is-collapsed",t),n.setAttribute("aria-expanded",String(!t))})};document.querySelectorAll("[data-collapse-toggle]").forEach(n=>{const t=n.dataset.collapseToggle;at||n.dataset.collapseDefault!=="collapsed"||f.add(t);const i=f.has(t);et(t,i);n.addEventListener("click",()=>{const n=!f.has(t);n?f.add(t):f.delete(t);et(t,n);kt();e();window.dispatchEvent(new Event("plotline:timeline-layout-changed"))})});o?.addEventListener("change",()=>ft(o.value));u?.addEventListener("change",()=>k(u.value));ft(localStorage.getItem(nt)||"comfortable");k(localStorage.getItem(tt)||"chapter");const dt=(()=>{try{return sessionStorage.getItem(s)!==null}catch{return!1}})();window.requestAnimationFrame(()=>{e(),bt("auto"),dt&&pt()});window.addEventListener("resize",e);window.addEventListener("plotline:timeline-layout-changed",e);const gt=t=>{if(t.button===0&&!t.metaKey&&!t.ctrlKey&&!t.shiftKey&&!t.altKey){const r=t.target instanceof Element?t.target:null,i=r?.closest("a[href]");if(i&&n.contains(i)){const u=new URL(i.href,window.location.href);[...u.searchParams.keys()].some(n=>n.toLowerCase()==="selectedsceneid")&&b()}}};n.addEventListener("click",gt,{capture:!0});const g=()=>{const n=t?.value.trim().toLowerCase()||"";let i=null;return st.forEach(t=>{const r=!n||(t.dataset.timelineSearchText||"").toLowerCase().includes(n);t.classList.toggle("search-hit",Boolean(n&&r));t.classList.toggle("search-dim",Boolean(n&&!r));!i&&n&&r&&(i=t)}),i};t?.addEventListener("input",g);t?.addEventListener("keydown",n=>{if(n.key==="Enter"){const t=g();t&&(n.preventDefault(),d(t.dataset.sceneId))}});ot.forEach(n=>{n.addEventListener("change",()=>{n.value&&d(n.value)})});document.querySelectorAll("[data-jump-scene]").forEach(n=>{n.addEventListener("click",()=>{n.dataset.jumpScene&&d(n.dataset.jumpScene)})});document.addEventListener("keydown",n=>{const i=document.activeElement?.tagName?.toLowerCase(),f=["input","select","textarea"].includes(i)||document.activeElement?.isContentEditable;if((n.ctrlKey||n.metaKey)&&n.key.toLowerCase()==="f"&&t){n.preventDefault();t.focus();t.select();return}if(n.key==="Escape"){const n=new URL(window.location.href),i=n.searchParams.has("FocusType")||n.searchParams.has("FocusID");n.searchParams.delete("FocusType");n.searchParams.delete("FocusID");i?window.location.href=n.toString():t?.value&&(t.value="",g());return}if(!f){if(n.key==="ArrowLeft"||n.key==="ArrowRight"){n.preventDefault();r.scrollBy({left:n.key==="ArrowLeft"?-260:260,behavior:"smooth"});return}if(n.key==="+"||n.key==="="||n.key==="-"){const t=h.indexOf(u?.value||"chapter"),i=n.key==="-"?-1:1,r=Math.min(h.length-1,Math.max(0,t+i));k(h[r])}}})}})();(()=>{const e=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-drag-drop-panel]");if(t){const i=new Map([...document.querySelectorAll("[data-drag-form]")].map(n=>[n.dataset.dragForm,n])),g=t.querySelector("[data-drag-panel-title]"),nt=t.querySelector("[data-drag-panel-kicker]"),tt=t.querySelector("[data-drag-panel-summary]"),o=document.querySelector("[data-scene-move-modal]"),v=o&&window.bootstrap?.Modal?window.bootstrap.Modal.getOrCreateInstance(o):null,y=o?.querySelector("[data-scene-move-title]"),p=o?.querySelector("[data-scene-move-summary]"),w=o?.querySelector("[data-scene-move-submit]"),s="plotline.timeline.scroll";let h=null,c=0;const n=(n,t,i)=>{const r=n?.querySelector(`[name='${CSS.escape(t)}']`);r&&(r.value=i??"")},l=n=>n?.selectedOptions?.[0]?.textContent?.trim()||"",a=n=>`“${n||"this scene"}”`,b=(n,t,i,r,u)=>t&&i&&t===i?`Move ${a(n.label)} to this new position within ${i}?`:t&&i?`Move ${a(n.label)} from ${t} to ${i}?`:`Move ${a(n.label)} ${r.toLowerCase()} ${u}?`,r=(n,r,u,f)=>{if(n==="scene"){t.classList.add("d-none");t.querySelectorAll("[data-drag-form]").forEach(n=>n.classList.add("d-none"));i.get("scene")?.classList.remove("d-none");y&&(y.textContent=r);p&&(p.textContent=u);w&&w.classList.toggle("d-none",r.toLowerCase().startsWith("cannot")||r.toLowerCase().startsWith("scene already"));v?v.show():t.classList.remove("d-none");return}i.forEach((t,i)=>t.classList.toggle("d-none",i!==n));t.classList.remove("d-none");g.textContent=r;nt.textContent=f;tt.textContent=u;t.scrollIntoView({behavior:"smooth",block:"center"})},it=()=>{t.classList.add("d-none"),i.forEach(n=>n.classList.add("d-none"))},k=()=>{document.querySelectorAll(".drop-target-active, .drop-before, .drop-after, .chapter-drop-active, .drop-chapter-end").forEach(n=>n.classList.remove("drop-target-active","drop-before","drop-after","chapter-drop-active","drop-chapter-end"))},rt=()=>{const n=e?.querySelector(".timeline-shell");try{sessionStorage.setItem(s,JSON.stringify({left:n?.scrollLeft||0,top:window.scrollY||0}))}catch{}};try{const n=JSON.parse(sessionStorage.getItem(s)||"null");n&&(sessionStorage.removeItem(s),requestAnimationFrame(()=>{const t=e?.querySelector(".timeline-shell");t&&(t.scrollLeft=n.left||0);window.scrollTo({top:n.top||0})}))}catch{sessionStorage.removeItem(s)}const f=n=>{try{const t=JSON.parse(n.dataTransfer.getData("application/json")||"{}");return t.type?t:h||{}}catch{return h||{}}},ut=(n,t)=>{const i={type:t.dataset.dragType,id:t.dataset.dragId,label:t.dataset.dragLabel||t.textContent.trim()};n.dataTransfer.effectAllowed="copyMove";n.dataTransfer.setData("application/json",JSON.stringify(i));n.dataTransfer.setData("text/plain",`${i.type}:${i.id}`);h=i;document.body.classList.add("drag-active",`dragging-${i.type}`);t.classList.add("is-dragging")};document.querySelectorAll("[data-drag-type][draggable='true']").forEach(n=>{n.addEventListener("dragstart",t=>ut(t,n)),n.addEventListener("dragend",()=>{n.classList.remove("is-dragging"),document.body.classList.remove("drag-active","dragging-scene","dragging-character","dragging-asset","dragging-location"),h=null,k()})});t.querySelectorAll("[data-drag-cancel]").forEach(n=>n.addEventListener("click",it));i.get("scene")?.addEventListener("submit",rt);e?.querySelectorAll(".timeline-scene-card[data-scene-id]").forEach(t=>{t.addEventListener("click",n=>{Date.now()<c&&(n.preventDefault(),n.stopPropagation())}),t.addEventListener("dragover",n=>{const i=f(n);["scene","character","asset"].includes(i.type)&&(n.preventDefault(),n.stopPropagation(),n.dataTransfer.dropEffect=i.type==="scene"?"move":"copy",t.classList.add("drop-target-active"),t.classList.toggle("drop-before",i.type==="scene"&&n.offsetX<t.clientWidth/2),t.classList.toggle("drop-after",i.type==="scene"&&n.offsetX>=t.clientWidth/2))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active","drop-before","drop-after")),t.addEventListener("drop",u=>{const o=f(u);if(["scene","character","asset"].includes(o.type)){u.preventDefault();u.stopPropagation();c=Date.now()+800;t.classList.remove("drop-target-active","drop-before","drop-after");const s=t.dataset.sceneId;if(o.type==="scene"){if(o.id===s){r("scene","Cannot move scene","Drop a scene before or after a different scene.","Scene move");return}const f=i.get("scene"),h=u.offsetX<t.clientWidth/2?"Before":"After",c=e.querySelector(`.timeline-scene-card[data-scene-id='${CSS.escape(o.id)}']`),l=c?.dataset.chapterLabel||"",a=t.dataset.chapterLabel||"";n(f,"SceneID",o.id);n(f,"AnchorSceneID",s);n(f,"TargetChapterID","");n(f,"Position",h);r("scene","Move Scene?",b(o,l,a,h,t.dataset.dragLabel),"Scene move");return}if(o.type==="character"){const u=i.get("character");n(u,"SceneID",s);n(u,"CharacterID",o.id);r("character","Add character to scene",`Add ${o.label} to ${t.dataset.dragLabel}.`,"Character drop");return}const h=i.get("asset-event");n(h,"SceneID",s);n(h,"StoryAssetID",o.id);n(h,"EventTitle","");r("asset-event","What happens to this asset?",`Add an event for ${o.label} in ${t.dataset.dragLabel}.`,"Asset drop")}})});e?.querySelectorAll(".timeline-scenes[data-chapter-drop-target]").forEach(t=>{const u=t.closest("[data-chapter-drop-target]"),o=t.dataset.chapterId||u?.dataset.chapterId,s=t.dataset.chapterLabel||u?.dataset.chapterLabel||"this chapter";t.addEventListener("dragover",n=>{const i=f(n);i.type==="scene"&&o&&(n.target.closest(".timeline-scene-card[data-scene-id]")||(n.preventDefault(),n.dataTransfer.dropEffect="move",t.classList.add("drop-target-active","drop-chapter-end"),u?.classList.add("chapter-drop-active")))});t.addEventListener("dragleave",n=>{t.contains(n.relatedTarget)||(t.classList.remove("drop-target-active","drop-chapter-end"),u?.classList.remove("chapter-drop-active"))});t.addEventListener("drop",u=>{const h=f(u);if(h.type==="scene"&&o&&!u.target.closest(".timeline-scene-card[data-scene-id]")){u.preventDefault();u.stopPropagation();c=Date.now()+800;k();const a=e.querySelector(`.timeline-scene-card[data-scene-id='${CSS.escape(h.id)}']`);if(a?.dataset.chapterId===o&&t.lastElementChild?.dataset?.sceneId===h.id){r("scene","Scene already here",`${h.label} is already at the end of ${s}.`,"Scene move");return}const l=i.get("scene");n(l,"SceneID",h.id);n(l,"AnchorSceneID","");n(l,"TargetChapterID",o);n(l,"Position","End");r("scene","Move Scene?",b(h,a?.dataset.chapterLabel||"",s,"After",s),"Scene move")}})});const u=i.get("asset-dependency"),d=()=>{const n=u?.dataset.sourceLabel||"Source asset",i=u?.dataset.targetLabel||"Target asset",f=l(u?.querySelector("[name='AssetDependencyTypeID']"))||"Requires",e=l(u?.querySelector("[name='SourceRequiredStateID']"))||"the required state",o=l(u?.querySelector("[name='TargetBlockedStateID']"))||"the blocked state",r=t.querySelector("[data-asset-dependency-preview]");r&&(r.textContent=`${n} ${f.toLowerCase()} ${i}. ${n} should be ${e} before ${i} can be ${o}.`)};u?.querySelectorAll("select").forEach(n=>n.addEventListener("change",d));document.querySelectorAll("[data-asset-drop-target], .drag-chip.asset").forEach(t=>{t.addEventListener("dragover",n=>{const i=f(n);i.type==="asset"&&(n.preventDefault(),n.dataTransfer.dropEffect="copy",t.classList.add("drop-target-active"))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active")),t.addEventListener("drop",i=>{const e=f(i),o=t.dataset.assetId||t.dataset.dragId,s=t.dataset.assetLabel||t.dataset.dragLabel||t.textContent.trim();if(e.type==="asset"&&o){if(i.preventDefault(),t.classList.remove("drop-target-active"),e.id===o){r("asset-dependency","Cannot create dependency","Drop one asset onto a different asset.","Asset dependency");return}n(u,"SourceAssetID",e.id);n(u,"TargetAssetID",o);u.dataset.sourceLabel=e.label;u.dataset.targetLabel=s;d();r("asset-dependency","Create asset dependency",`${e.label} -> ${s}`,"Asset dependency")}})});document.querySelectorAll("[data-drag-type='location']").forEach(t=>{t.addEventListener("dragover",n=>{const i=f(n);i.type==="location"&&(n.preventDefault(),n.dataTransfer.dropEffect="copy",t.classList.add("drop-target-active"))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active")),t.addEventListener("drop",u=>{const e=f(u),o=t.dataset.dragId,h=t.dataset.dragLabel||t.textContent.trim();if(e.type==="location"){if(u.preventDefault(),t.classList.remove("drop-target-active"),e.id===o){r("location-relationship","Cannot create relationship","Drop one location onto a different location.","Location relationship");return}const s=i.get("location-relationship");n(s,"FromLocationID",e.id);n(s,"ToLocationID",o);r("location-relationship","Create location relationship",`${e.label} -> ${h}`,"Location drop")}})})}})(); |