diff --git a/PlotLine.Tests/Program.cs b/PlotLine.Tests/Program.cs index 1022a3e..9a38c42 100644 --- a/PlotLine.Tests/Program.cs +++ b/PlotLine.Tests/Program.cs @@ -7513,9 +7513,10 @@ static void StoryCalendarDaySelectionUsesProgressiveAjaxHooks() { var view = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/Views/StoryCalendar/Index.cshtml")); var surface = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/Views/StoryCalendar/_StoryCalendarSurface.cshtml")); - var script = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/wwwroot/js/site.js")); + var script = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/wwwroot/js/story-calendar.js")); Assert(view.Contains("data-story-calendar-root", StringComparison.Ordinal), "Calendar page should expose a replaceable AJAX root."); + Assert(view.Contains("~/js/story-calendar.js", StringComparison.Ordinal), "Calendar page should load the dedicated Calendar AJAX script."); Assert(surface.Contains("data-story-calendar-day", StringComparison.Ordinal), "Calendar date links should expose a progressive enhancement hook."); Assert(surface.Contains("data-story-calendar-nav", StringComparison.Ordinal), "Calendar month navigation links should expose a progressive enhancement hook."); Assert(surface.Contains("data-story-calendar-filter", StringComparison.Ordinal), "Calendar filter form should expose a progressive enhancement hook."); diff --git a/PlotLine/Views/StoryCalendar/Index.cshtml b/PlotLine/Views/StoryCalendar/Index.cshtml index d2821db..106d76a 100644 --- a/PlotLine/Views/StoryCalendar/Index.cshtml +++ b/PlotLine/Views/StoryCalendar/Index.cshtml @@ -15,3 +15,7 @@
+ +@section Scripts { + +} diff --git a/PlotLine/wwwroot/js/site.js b/PlotLine/wwwroot/js/site.js index 534cb07..3bd37fe 100644 --- a/PlotLine/wwwroot/js/site.js +++ b/PlotLine/wwwroot/js/site.js @@ -2451,112 +2451,4 @@ }); }); - if (document.querySelector("[data-story-calendar-root]")) { - const calendarRoot = () => document.querySelector("[data-story-calendar-root]"); - const calendarDetail = () => calendarRoot()?.querySelector("[data-story-calendar-detail]"); - - const clearCalendarNotice = () => { - calendarRoot()?.querySelectorAll("[data-story-calendar-notice]").forEach((notice) => notice.remove()); - }; - - const showCalendarNotice = (message, state) => { - clearCalendarNotice(); - const detail = calendarDetail(); - const root = calendarRoot(); - const target = detail || root; - if (!target) { - return; - } - - const notice = document.createElement("p"); - notice.className = `story-calendar-detail-notice ${state === "error" ? "is-error" : ""}`.trim(); - notice.dataset.storyCalendarNotice = "true"; - notice.textContent = message; - target.prepend(notice); - }; - - const loadCalendarSurface = async (url, historyMode = "push") => { - const root = calendarRoot(); - if (!root || !window.fetch) { - return false; - } - - showCalendarNotice("Loading...", "loading"); - - try { - const response = await fetch(url, { - headers: { "X-Requested-With": "XMLHttpRequest" }, - credentials: "same-origin" - }); - - if (!response.ok) { - throw new Error("Calendar load failed"); - } - - root.innerHTML = await response.text(); - if (window.history?.pushState && historyMode !== "none") { - const nextUrl = new URL(url, window.location.href); - const state = { storyCalendarUrl: nextUrl.toString() }; - if (historyMode === "replace") { - window.history.replaceState(state, "", nextUrl.toString()); - } else { - window.history.pushState(state, "", nextUrl.toString()); - } - } - return true; - } catch { - showCalendarNotice("The calendar could not be loaded. Use the link or Apply button to retry.", "error"); - return false; - } - }; - - const buildFilterUrl = (form) => { - const formData = new FormData(form); - const url = new URL(form.dataset.storyCalendarIndexUrl || form.action || window.location.href, window.location.href); - url.search = ""; - for (const [key, value] of formData.entries()) { - if (key === "__RequestVerificationToken" || value === "") { - continue; - } - url.searchParams.set(key, value); - } - return url.toString(); - }; - - window.history?.replaceState?.({ storyCalendarUrl: window.location.href }, "", window.location.href); - - document.addEventListener("click", async (event) => { - const link = event.target.closest("[data-story-calendar-day], [data-story-calendar-nav]"); - if (!link || !link.isConnected || !calendarRoot()?.contains(link) || !window.fetch) { - return; - } - - event.preventDefault(); - await loadCalendarSurface(link.href, "push"); - }); - - document.addEventListener("submit", async (event) => { - const form = event.target.closest("[data-story-calendar-filter]"); - if (!form || !form.isConnected || !window.fetch) { - return; - } - - event.preventDefault(); - await loadCalendarSurface(buildFilterUrl(form), "push"); - }); - - document.addEventListener("change", async (event) => { - const select = event.target.closest("[data-story-calendar-filter] select"); - const form = select?.closest("[data-story-calendar-filter]"); - if (!select || !form || !window.fetch) { - return; - } - - await loadCalendarSurface(buildFilterUrl(form), "push"); - }); - - window.addEventListener("popstate", async () => { - await loadCalendarSurface(window.location.href, "none"); - }); - } })(); diff --git a/PlotLine/wwwroot/js/site.min.js b/PlotLine/wwwroot/js/site.min.js index 3d362a4..c843786 100644 --- a/PlotLine/wwwroot/js/site.min.js +++ b/PlotLine/wwwroot/js/site.min.js @@ -1 +1 @@ -(()=>{const e="plotline.theme",t=document.querySelectorAll("[data-theme-choice]"),n=n=>{const a="dark"===n?"dark":"light";document.documentElement.dataset.theme=a,document.documentElement.dataset.bsTheme=a,(t=>{try{localStorage.setItem(e,t)}catch{}})(a),t.forEach(e=>{const t=e.dataset.themeChoice===a;e.classList.toggle("active",t),e.setAttribute("aria-pressed",String(t))})};t.forEach(e=>{e.addEventListener("click",()=>n(e.dataset.themeChoice))}),n((()=>{try{return localStorage.getItem(e)}catch{return null}})()||"light")})(),(()=>{const e=document.querySelector("[data-trial-banner]");if(!e)return;const t=`plotline.trialBanner.dismissed.${e.dataset.trialBannerKey||"current"}`;try{if("true"===localStorage.getItem(t))return void(e.hidden=!0)}catch{}e.querySelector("[data-trial-banner-dismiss]")?.addEventListener("click",()=>{e.hidden=!0;try{localStorage.setItem(t,"true")}catch{}})})(),(()=>{const e="plotdirector:story-position-changed",t=(e,t)=>{const n=e?.position||e?.Position||e||{},a=(...e)=>{for(const t of e){const e=Number.parseInt(t||"",10);if(Number.isInteger(e)&&e>0)return e}return null};return{source:t||e?.source||e?.Source||"Navigation",projectId:a(n.projectId,n.ProjectId,e?.projectId,e?.ProjectId),bookId:a(n.bookId,n.BookId,e?.bookId,e?.BookId),chapterId:a(n.chapterId,n.ChapterId,e?.chapterId,e?.ChapterId),sceneId:a(n.sceneId,n.SceneId,e?.sceneId,e?.SceneId),canonicalUrl:e?.canonicalUrl||e?.CanonicalUrl||e?.sceneUrl||e?.SceneUrl||""}};window.PlotDirectorStoryPosition={eventName:e,normalise:t,publishChanged:(n,a)=>{const o=t(n,a);return o.projectId?(document.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:o})),o):o}}})(),(()=>{const e=document.querySelector("[data-word-companion-follow-toggle]");if(!e||!window.signalR)return;const t="plotdirector.followWordCompanion",n=["/pricing","/about","/contact","/privacy","/terms","/admin","/billing","/account","/login","/register","/feature-requests/admin","/featurerequests/admin","/manageaccount"];let a=null,o=null;const r=()=>{try{return"true"===localStorage.getItem(t)}catch{return!1}},i=t=>{e.setAttribute("aria-pressed",String(t)),e.classList.toggle("active",t),e.title=t?"PlotDirector will follow the current Word Companion scene.":"Enable this to let PlotDirector follow the current Word Companion scene."},s=(e,t)=>{if("string"!=typeof e||!e.startsWith(t))return"";try{const t=new URL(e,window.location.origin);return t.origin===window.location.origin?`${t.pathname}${t.search}${t.hash}`:""}catch{return""}},l=e=>{if(!r()||!(()=>{const e=window.location.pathname.toLowerCase();return!("/"===e||""===e||document.querySelector("[data-writing-mode]")||"/writing/context"===e||e.startsWith("/writing/context/")||"/writing/intent"===e||e.startsWith("/writing/intent/")||n.some(t=>e===t||e.startsWith(`${t}/`)))})()||!e)return;`${window.location.pathname}${window.location.search}${window.location.hash}`!==e&&window.location.assign(e)};i(r()),e.addEventListener("click",()=>{const e=!r();(e=>{try{localStorage.setItem(t,e?"true":"false")}catch{}})(e),i(e)});const c=(new signalR.HubConnectionBuilder).withUrl("/hubs/word-companion-follow").withAutomaticReconnect().build();c.on("wordCompanionSceneChanged",e=>{const t=(window.PlotDirectorStoryPosition?.publishChanged(e,"WordCompanion")||{sceneId:Number.parseInt(e?.sceneId||e?.SceneId||"",10)}).sceneId;if(!Number.isInteger(t)||t<=0||t===a)return;a=t;const n=s(e?.sceneUrl||e?.SceneUrl||"","/Scenes/Edit/");l(n)}),c.on("storyPositionChanged",e=>{window.PlotDirectorStoryPosition?.publishChanged(e,e?.source||e?.Source||"WordCompanion")}),c.on("wordCompanionCharactersCreated",e=>{const t=Number.parseInt(e?.projectId||e?.ProjectId||"",10);if(!Number.isInteger(t)||t<=0||t===o)return;o=t;const n=s(e?.charactersUrl||e?.CharactersUrl||"","/Characters");l(n)}),c.start().catch(e=>{console.debug("Word Companion follow connection unavailable.",e)})})(),(()=>{const e=document.getElementById("plotlineConfirmModal"),t=e&&window.bootstrap?new bootstrap.Modal(e):null,n=e?.querySelector("#plotlineConfirmModalTitle"),a=e?.querySelector("#plotlineConfirmModalMessage"),o=e?.querySelector("[data-confirm-submit]"),r=e?.querySelector("[data-delete-confirm-field]"),i=e?.querySelector("[data-delete-confirm-input]");let s=null,l=null;e?.addEventListener("hidden.bs.modal",()=>{s=null,l=null,i&&(i.value=""),r?.classList.add("d-none"),o?.removeAttribute("disabled")}),i?.addEventListener("input",()=>{"true"===s?.dataset.requireDeleteText&&o&&(o.disabled="DELETE"!==i.value.trim())}),o?.addEventListener("click",()=>{if(s){if("true"===s.dataset.requireDeleteText){const e=s.querySelector("[data-delete-confirm-target], [name='confirmationText']");e&&(e.value=i?.value.trim()||"")}s.dataset.confirmed="true",t?.hide(),s.requestSubmit&&l?s.requestSubmit(l):s.requestSubmit?s.requestSubmit():s.submit()}}),document.addEventListener("submit",c=>{const d=c.target;if(!(d instanceof HTMLFormElement)||"true"===d.dataset.confirmed||"true"===d.dataset.confirmSkip||"true"===d.dataset.noDeleteConfirm)return;const p=d.dataset.confirmMessage,u=c.submitter instanceof HTMLElement?c.submitter:d.querySelector("button[type='submit']"),h=u?.textContent||"",m=d.getAttribute("action")||"",g=/restore/i.test(h)||/\/restore\b/i.test(m),f=/delete forever/i.test(h)||/\/deleteforever\b/i.test(m),y=!g&&!f&&(/archive/i.test(h)||/\/archive\b/i.test(m)),v=!f&&/(delete|remove)/i.test(h);if(!(p||y||g||v||f))return;const w=p||(y?"Archive this item?\n\nThis will hide it from active lists and timelines, but the data will be kept and can be restored later.":g?"Restore this item?\n\nThis will return it to active lists and timelines where applicable.":f?"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(!(t&&e&&n&&a&&o))return;c.preventDefault();const b=(e=>{const t=e.split(/\n\s*\n/);return{title:t.shift()?.trim()||"Confirm action",body:t.join("\n\n").trim()}})(w);s=d,l=u,n.textContent=d.dataset.confirmTitle||b.title,a.textContent=d.dataset.confirmBody||b.body,o.textContent=d.dataset.confirmButtonText||h.trim()||"Confirm",o.className=`btn ${d.dataset.confirmButtonClass||(g?"btn-primary":"btn-danger")}`;const S="true"===d.dataset.requireDeleteText||f;r?.classList.toggle("d-none",!S),S&&o&&(o.disabled=!0,i?.focus()),t.show(),S&&window.setTimeout(()=>i?.focus(),200)})})(),(()=>{if(!window.bootstrap)return;const e=new Map;let t=null;const n=new WeakSet,a=e=>e.querySelector("[data-help-tooltip]")||e,o=e=>bootstrap.Tooltip.getInstance(a(e))?.hide(),r=(e=null)=>{t&&t!==e&&bootstrap.Popover.getInstance(t)?.hide()},i=()=>document.querySelectorAll("[data-help-icon]").forEach(e=>o(e)),s=(s=document)=>{(e=>{if(!e)return[];const t=[];return e.matches?.("[data-help-icon]")&&t.push(e),e.querySelectorAll?.("[data-help-icon]").forEach(e=>t.push(e)),t})(s).forEach(s=>{if(n.has(s))return;n.add(s);const l=a(s),c=l.dataset.helpMicro||"Help article not found.",d=s.dataset.helpTitle||"Help unavailable",p=s.dataset.helpSummary||"Help content has not yet been written for this feature.";bootstrap.Tooltip.getOrCreateInstance(l,{title:c,trigger:"hover focus",placement:"top",container:"body"});const u=bootstrap.Popover.getOrCreateInstance(s,{title:d,trigger:"click",placement:"auto",container:"body",html:!0,customClass:"plotline-help-popover",content(){const e=document.createElement("div"),t=document.createElement("p");t.className="mb-2",t.textContent=p;const n=document.createElement("a");return n.href=s.dataset.helpUrl||"#",n.textContent="More...",n.className="plotline-help-more",n.addEventListener("click",e=>{e.preventDefault(),r(),i(),s.dispatchEvent(new CustomEvent("plotline:help-more",{bubbles:!0,detail:{key:s.dataset.helpKey||""}}))}),e.append(t,n),e}});e.set(s,u),s.addEventListener("click",e=>{window.PlotLineHelpDrawer?.isActive?.()&&(e.preventDefault(),e.stopPropagation(),r(),o(s),window.PlotLineHelpPopovers?.hideAll(),window.PlotLineHelpDrawer.open(s.dataset.helpKey||""))}),s.addEventListener("show.bs.popover",e=>{window.PlotLineHelpDrawer?.isActive?.()?e.preventDefault():(r(s),o(s))}),s.addEventListener("shown.bs.popover",()=>{t=s,o(s)}),s.addEventListener("hidden.bs.popover",()=>{t===s&&(t=null)})})};document.addEventListener("click",e=>{const n=e.target instanceof Element?e.target:null;if(!n||!t)return;if(n.closest("[data-help-icon]"))return;const a=document.querySelector(`#${t.getAttribute("aria-describedby")}`);a?.contains(n)||r()}),document.addEventListener("keydown",e=>{"Escape"===e.key&&r()}),window.PlotLineHelpPopovers={hideAll(){e.forEach(e=>e.hide()),i(),t=null}},window.PlotLineHelpIcons={init:s},s()})(),(()=>{const e=document.querySelector("[data-help-layout]"),t=document.querySelector("[data-help-drawer]"),n=document.querySelector("[data-help-content]"),a=document.querySelector("[data-help-splitter]"),o=document.querySelector("[data-help-close]"),r=document.querySelector("[data-help-collapse]"),i=document.querySelector("[data-help-expand]"),s=document.querySelector("[data-help-pin]"),l=document.querySelector("[data-help-drawer-heading]");if(!e||!t||!n)return;const c="plotline.helpDrawer.width",d="plotline.helpDrawer.pinned",p="plotline.helpDrawer.key",u="plotline.helpDrawer.collapsed",h=e=>{try{return"true"===localStorage.getItem(e)}catch{return!1}},m=(e,t)=>{try{localStorage.setItem(e,t)}catch{}},g=t=>{const n=`${((e,t,n)=>Math.min(n,Math.max(t,e)))(Number.parseFloat(t)||25,20,50)}%`;e.style.setProperty("--help-drawer-width",n),m(c,n)},f=e=>{m(d,String(e)),s?.setAttribute("aria-pressed",String(e)),s&&(s.textContent=e?"Unpin":"Pin")},y=t=>{e.classList.toggle("help-collapsed",t),m(u,String(t)),r&&(r.textContent=t?"Expand":"Collapse")},v=async(t,a={})=>{const o=t||"";e.classList.add("help-open"),a.keepCollapsed||y(!1),m(p,o),l&&(l.textContent=o||"Help"),n.innerHTML='

Loading help...

';try{const e=await fetch(`/HelpDrawer/Article?key=${encodeURIComponent(o)}`,{headers:{"X-Requested-With":"XMLHttpRequest"}});n.innerHTML=e.ok?await e.text():'

Help for this feature has not yet been written.

';const t=n.querySelector(".help-drawer-article h2")?.textContent?.trim();l&&t&&(l.textContent=t)}catch{n.innerHTML='

Help for this feature has not yet been written.

'}};window.PlotLineHelpDrawer={isActive:()=>e.classList.contains("help-open"),open:e=>v(e)},g(localStorage.getItem(c)||"25%"),f(h(d)),y(h(u)),document.addEventListener("plotline:help-more",e=>{const t=e.detail?.key||e.target?.dataset?.helpKey||"";window.PlotLineHelpPopovers?.hideAll(),v(t)}),document.addEventListener("click",e=>{const t=e.target instanceof Element?e.target.closest("[data-help-open]"):null;t&&(e.preventDefault(),window.PlotLineHelpPopovers?.hideAll(),v(t.dataset.helpOpen||""))}),o?.addEventListener("click",()=>{e.classList.remove("help-open","help-collapsed"),f(!1)}),r?.addEventListener("click",()=>y(!e.classList.contains("help-collapsed"))),i?.addEventListener("click",()=>y(!1)),i?.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),y(!1))}),s?.addEventListener("click",()=>f(!h(d))),a?.addEventListener("pointerdown",t=>{if(!e.classList.contains("help-open")||e.classList.contains("help-collapsed"))return;t.preventDefault(),document.body.classList.add("help-resizing"),a.setPointerCapture?.(t.pointerId);const n=t=>{const n=e.getBoundingClientRect(),a=(n.right-t.clientX)/n.width*100;g(a)},o=()=>{document.body.classList.remove("help-resizing"),document.removeEventListener("pointermove",n),document.removeEventListener("pointerup",o)};document.addEventListener("pointermove",n),document.addEventListener("pointerup",o)});const w=(()=>{try{return localStorage.getItem(p)||""}catch{return""}})();h(d)&&w&&v(w,{keepCollapsed:!0})})(),(()=>{const e=document.querySelectorAll("[data-metric-shape-chart]");if(!e.length)return;const t=["#2f6f63","#b35f3b","#6750a4","#c58b2b","#7d3f52","#33759b","#9c4d8f","#a33d3d","#6f8f3d","#d08a2f","#444b57"],n=new Set(["Overall Intensity","Tension","Emotional Weight","Action"]),a=[],o=e=>{const t=e.querySelector(".metric-chart-frame");if(!t)return;let n=Number(t.dataset.metricNaturalWidth||0);if(n&&"true"===t.dataset.metricCanvasConstrained||(t.style.width="",n=t.getBoundingClientRect().width,n&&(t.dataset.metricNaturalWidth=String(n))),!n)return;const a=Math.max(window.devicePixelRatio||1,1),o=Math.floor(3e4/a);n>o?(t.style.width=`${o}px`,t.dataset.metricCanvasConstrained="true"):(t.style.width="",t.dataset.metricCanvasConstrained="false")},r=()=>{window.requestAnimationFrame(()=>{a.forEach(({chart:e,root:t})=>{o(t),e.resize()})})};window.PlotLineMetricCharts={resizeAll:r},window.addEventListener("plotline:timeline-layout-changed",r);const i={id:"plotlineChapterGuides",afterDraw(e){const t=e.config.options.plugins.plotlineScenes||[],{ctx:n,chartArea:a,scales:o}=e;if(!t.length||!o.x)return;n.save(),n.strokeStyle="rgba(47, 111, 99, 0.16)",n.fillStyle="rgba(82, 75, 65, 0.58)",n.lineWidth=1,n.font="700 10px system-ui, -apple-system, Segoe UI, sans-serif";const r=[];t.forEach((e,n)=>{n&&e.chapterLabel===t[n-1].chapterLabel||r.push({scene:e,index:n})});const i=r.filter(e=>!e.scene.isPlaceholder),s=i.length>40?5:i.length>24?4:i.length>14?2:1;i.forEach((e,t)=>{const{scene:r,index:l}=e,c=o.x.getPixelForValue(l+.5);n.beginPath(),n.moveTo(c,a.top),n.lineTo(c,a.bottom),n.stroke();const d=Number.parseInt(String(r.chapterLabel).replace(/[^0-9]/g,""),10);(0===t||t===i.length-1||1===s||Number.isFinite(d)&&d%s===0)&&n.fillText(r.chapterLabel,Math.min(c+4,a.right-70),a.bottom+22)}),n.restore()}};window.Chart&&Chart.register(i),e.forEach(e=>{const r=e.querySelector("script[type='application/json']"),i=e.querySelector("canvas"),s=e.querySelector("[data-metric-toggles]"),l=e.querySelector("[data-chart-fallback]"),c=e.querySelector("[data-timeline-metric-edit-toolbar]"),d=e.querySelector("[data-edit-metric-status]"),p=e.hasAttribute("data-timeline-metric-editor")&&c&&d;if(!r||!i)return;let u;try{u=JSON.parse(r.textContent||"{}")}catch{return void l?.classList.remove("d-none")}if(!window.Chart||!u.scenes?.length||!u.metrics?.length)return void l?.classList.remove("d-none");const h=new Set(u.defaultMetrics?.length?u.defaultMetrics:n),m=u.scenes.length,g=m>80?10:m>40?5:m>24?4:m>14?2:1,f=u.metrics.map((e,n)=>{const a=new Map(e.points.map(e=>[e.sceneId,e.value])),o=t[n%t.length];return{label:e.metricName,metricTypeId:e.metricTypeId,minValue:Number.isFinite(e.minValue)?e.minValue:1,maxValue:Number.isFinite(e.maxValue)?e.maxValue:10,data:u.scenes.map((e,t)=>{const n=a.get(e.sceneId);return null==n?{x:t+.5,y:null,sceneId:e.sceneId}:{x:t+.5,y:n,sceneId:e.sceneId}}),borderColor:o,backgroundColor:o,pointBackgroundColor:o,pointBorderColor:"#fff",pointBorderWidth:1.5,pointRadius:3,pointHoverRadius:5,borderWidth:2,tension:.35,spanGaps:!0,hidden:!h.has(e.metricName)}});let y=null,v=!1;o(e);const w=e=>{d&&(d.textContent=e)},b=()=>f.map((e,t)=>t).filter(e=>E?.isDatasetVisible(e)),S=()=>{const e=b();return 1===e.length?e[0]:-1},L=()=>{if(!p)return;const e=S();f.forEach((t,n)=>{t.pointRadius=n===e?5:3,t.pointHoverRadius=n===e?7:5,t.borderWidth=n===e?3:2});const t=b(),n=e>=0;i.classList.toggle("metric-curve-editing",n),d.classList.toggle("d-none",0===t.length),0===t.length?w(""):w(n?"Drag points up or down to edit this metric.":"Show only one metric line to edit its curve."),E?.update()};s.innerHTML="",f.forEach((e,t)=>{const n=`metric-toggle-${Math.random().toString(36).slice(2)}-${t}`,a=document.createElement("label");a.className="metric-toggle-pill",a.style.setProperty("--metric-colour",e.borderColor),a.innerHTML=`${e.label}`,s.appendChild(a),a.querySelector("input").addEventListener("change",e=>{E.setDatasetVisibility(t,e.target.checked),L(),E.update()})});const E=new Chart(i,{type:"line",data:{datasets:f},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:m,bounds:"ticks",ticks:{maxRotation:0,autoSkip:!1,maxTicksLimit:16,callback(e){const t=Math.floor(Number(e));if(t<0||t>=m)return"";const n=u.scenes[t];return 0!==t&&t!==m-1&&t%g!==0||n?.isPlaceholder?"":n?.axisLabel||""}},afterBuildTicks(e){e.ticks=u.scenes.map((e,t)=>({value:t+.5}))},grid:{display:!1}}},plugins:{legend:{display:!1},plotlineScenes:u.scenes,tooltip:{callbacks:{title(e){const t=Math.floor(e[0].parsed.x),n=u.scenes[t];return n.isPlaceholder?n.sceneTitle||"No scene":`${n.chapterLabel}, Scene ${n.sceneNumber}: ${n.sceneTitle}`},label:e=>`${e.dataset.label}: ${e.formattedValue}`,afterBody(e){const t=Math.floor(e[0].parsed.x),n=u.scenes[t],a=[];return n.characterPresence?a.push(n.characterPresence):void 0!==n.characterAppears&&a.push(n.characterAppears?"Character appears":"Character does not appear"),n.bookTitle&&a.push(n.bookTitle),a}}}}}});p&&(L(),i.addEventListener("pointerdown",e=>{y=(e=>{if(!p)return null;const t=S();if(t<0)return null;const n=E.getElementsAtEventForMode(e,"nearest",{intersect:!0},!0).find(e=>e.datasetIndex===t);if(!n)return null;const a=E.data.datasets[n.datasetIndex],o=a?.data?.[n.index];return o?.sceneId&&null!=o.y?{datasetIndex:n.datasetIndex,pointIndex:n.index,previousValue:o.y}:null})(e),y&&(e.preventDefault(),i.setPointerCapture?.(e.pointerId),v=!0)}),i.addEventListener("pointermove",e=>{if(!y)return;const t=E.data.datasets[y.datasetIndex],n=t?.data?.[y.pointIndex];t&&n&&(n.y=((e,t)=>{const n=E.scales.y.getValueForPixel(e.offsetY),a=Math.round(n);return Math.max(t.minValue,Math.min(t.maxValue,a))})(e,t),E.update("none"))}),i.addEventListener("pointerup",async t=>{if(!y)return;const n=y;y=null,i.releasePointerCapture?.(t.pointerId),await(async t=>{const n=E.data.datasets[t.datasetIndex],a=n?.data?.[t.pointIndex];if(!n||!a?.sceneId)return;const o=c?.querySelector("input[name='__RequestVerificationToken']")?.value||"";w("Saving...");try{const t=await fetch(e.dataset.updateUrl||"/SceneMetrics/UpdateSceneMetricValue",{method:"POST",headers:{"Content-Type":"application/json",RequestVerificationToken:o},body:JSON.stringify({sceneId:Number(a.sceneId),metricId:Number(n.metricTypeId),value:Number(a.y)})}),r=await t.json().catch(()=>({}));if(!t.ok||!r.success)throw new Error(r.message||"Save failed");a.y=r.value,w("Saved"),E.update()}catch{a.y=t.previousValue,w("Save failed"),E.update()}})(n),window.setTimeout(()=>{v=!1},0)}),i.addEventListener("pointercancel",e=>{if(!y)return;const t=E.data.datasets[y.datasetIndex],n=t?.data?.[y.pointIndex];n&&(n.y=y.previousValue),y=null,i.releasePointerCapture?.(e.pointerId),v=!1,E.update()})),i.addEventListener("click",e=>{if(v)return void e.preventDefault();const t=E.getElementsAtEventForMode(e,"nearest",{intersect:!0},!0);if(!t.length)return;const n=E.data.datasets[t[0].datasetIndex]?.data?.[t[0].index],a=n?Math.floor(n.x):t[0].index,o=u.scenes[a];o?.timelineUrl&&(window.PlotLineTimelineScroll?.save(),window.location.href=o.timelineUrl)}),a.push({chart:E,root:e});const I=window.ResizeObserver?new ResizeObserver(()=>{o(e),E.resize()}):null;I&&(I.observe(e),I.observe(e.querySelector(".metric-chart-frame")||e))})})(),(()=>{const e=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-timeline-controls]");if(!e||!t)return;const n="plotline.timeline.visibility",a={"scene-cards":!0,"plot-lines":!0,assets:!0,characters:!0,locations:!0,warnings:!0,metrics:!0},o=t=>{Object.entries(t).forEach(([t,n])=>{e.classList.toggle(`hide-${t}`,!n),document.querySelectorAll(`[data-timeline-section='${t}']`).forEach(e=>{e.classList.toggle("timeline-section-hidden",!n)})}),document.querySelectorAll("[data-location-marker]").forEach(e=>{e.classList.toggle("timeline-section-hidden",!t.locations)}),document.querySelectorAll("[data-warning-marker]").forEach(e=>{e.classList.toggle("timeline-section-hidden",!t.warnings)}),document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(t))},r=(()=>{try{return{...a,...JSON.parse(localStorage.getItem(n)||"{}")}}catch{return{...a}}})();t.querySelectorAll("[data-timeline-toggle]").forEach(e=>{const t=e.dataset.timelineToggle;e.checked=r[t]??!0,e.addEventListener("change",()=>{r[t]=e.checked,localStorage.setItem(n,JSON.stringify(r)),o(r)})}),t.querySelector("[data-timeline-preset-form]")?.addEventListener("submit",()=>{document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(r))}),o(r)})(),(()=>{const e=document.querySelector("[data-timeline-root]");if(!e)return;const t=e.querySelector(".timeline-workspace")||e,n=document.querySelector("[data-timeline-density]"),a=document.querySelector("[data-timeline-zoom]"),o=document.querySelector("[data-timeline-search]"),r=e.querySelector("[data-timeline-minimap]"),i=document.querySelectorAll("[data-timeline-jump], [data-timeline-warning-jump], [data-timeline-entity-jump]"),s=[...e.querySelectorAll(".timeline-scene-card[data-scene-id]")],l="plotline.timeline.collapsed",c="plotline.timeline.density",d="plotline.timeline.zoom",p="plotline.timeline.pending-scroll",u="plotdirector.timelineNavigationDiscovered",h="plotline.timeline.inspectorWidth",m=[".timeline-shell",".plot-lane-board",".asset-lane-board",".character-lane-board",".metric-graph-stack","[data-timeline-minimap]"].join(","),g=["compact","comfortable","expanded"],f=["book","chapter","scene-detail","scene-overview"],y=e.querySelector("[data-inspector-drawer]"),v=e.querySelector("[data-inspector-resizer]"),w=e.querySelector("[data-inspector-close]"),b=null!==localStorage.getItem(l),S=(e=>{try{return new Set(JSON.parse(localStorage.getItem(e)||"[]"))}catch{return new Set}})(l),L=()=>[...e.querySelectorAll(m)],E=t=>{if(!y)return;const{minimum:n,maximum:a,defaultWidth:o}=(()=>{const t=e.getBoundingClientRect().width||window.innerWidth,n=Math.min(420,Math.max(320,t-360)),a=Math.max(n,Math.min(.65*t,t-420));return{minimum:n,maximum:a,defaultWidth:Math.max(n,Math.min(a,.4*t))}})(),r=Math.max(n,Math.min(a,Number(t)||o));e.style.setProperty("--timeline-inspector-width",`${Math.round(r)}px`)};y&&(E(localStorage.getItem(h)),window.addEventListener("resize",()=>E(localStorage.getItem(h)))),(()=>{const o=e.querySelector("[data-timeline-navigation-card]");if(!o)return;const r=o.querySelector("[data-timeline-navigation-dismiss]"),i=o.querySelector("[data-timeline-navigation-demo]"),s=()=>e.querySelector(".timeline-shell"),l=e=>Boolean(e&&e.scrollWidth>e.clientWidth+8),c=e=>Math.max(0,e.scrollWidth-e.clientWidth),d=()=>[t,...L()].filter((e,t,n)=>e&&n.indexOf(e)===t);let p=!1,h=!1,m=!1,g=null;const f=new WeakMap,y=e=>f.set(e,e.scrollLeft),v=(e=!1)=>{if(window.clearTimeout(g),!e||window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches||o.hidden)return o.classList.remove("is-dismissing"),void(o.hidden=!0);o.classList.add("is-dismissing"),g=window.setTimeout(()=>{o.hidden=!0,o.classList.remove("is-dismissing")},1300)},w=e=>{const t=l(e),n=c(e);e.classList.toggle("timeline-horizontal-overflow",t),e.classList.toggle("has-scroll-left",t&&e.scrollLeft>8),e.classList.toggle("has-scroll-right",t&&e.scrollLeft{l(s())&&!(()=>{try{return"true"===localStorage.getItem(u)}catch{return!1}})()&&!p?(window.clearTimeout(g),o.hidden=!1,o.classList.remove("is-dismissing")):o.classList.contains("is-dismissing")||v(!1),d().forEach(w)},S=()=>{p=!0,v(!0);try{localStorage.setItem(u,"true")}catch{}};d().forEach(e=>{y(e),e.addEventListener("wheel",t=>{if(!t.defaultPrevented&&t.shiftKey&&Math.abs(t.deltaY)>Math.abs(t.deltaX)&&l(e)){const n=e.scrollLeft;t.preventDefault(),e.scrollLeft+=t.deltaY,window.requestAnimationFrame(()=>{w(e),!h&&Math.abs(e.scrollLeft-n)>4&&S(),y(e)})}},{passive:!1}),e.addEventListener("scroll",()=>(e=>{if(w(e),!m||h)return void y(e);const t=f.get(e)??e.scrollLeft;Math.abs(e.scrollLeft-t)>4&&l(e)?S():y(e)})(e),{passive:!0})}),r?.addEventListener("click",S),i?.addEventListener("click",()=>{const e=s();if(!l(e))return void b();const t=e.scrollLeft,n=c(e)-t;let a=Math.min(220,n),o=1;a<24&&t>0&&(a=Math.min(220,t),o=-1),a<4||(h=!0,e.scrollTo({left:t+a*o,behavior:"smooth"}),window.setTimeout(()=>{e.scrollTo({left:t,behavior:"smooth"})},420),window.setTimeout(()=>{h=!1,d().forEach(e=>{y(e),w(e)}),b()},950))}),window.setTimeout(()=>{m=!0,d().forEach(y)},800),window.requestAnimationFrame(b),window.addEventListener("resize",b),window.addEventListener("plotline:timeline-layout-changed",b),n?.addEventListener("change",()=>window.requestAnimationFrame(b)),a?.addEventListener("change",()=>window.requestAnimationFrame(b))})(),v?.addEventListener("pointerdown",t=>{t.preventDefault(),document.body.classList.add("timeline-inspector-resizing"),v.setPointerCapture(t.pointerId);const n=t=>{const n=e.getBoundingClientRect().right-t.clientX;E(n)},a=()=>{document.body.classList.remove("timeline-inspector-resizing");const e=Math.round(y?.getBoundingClientRect().width||0);e>0&&localStorage.setItem(h,String(e)),window.dispatchEvent(new Event("plotline:timeline-layout-changed")),v.removeEventListener("pointermove",n),v.removeEventListener("pointerup",a),v.removeEventListener("pointercancel",a)};v.addEventListener("pointermove",n),v.addEventListener("pointerup",a),v.addEventListener("pointercancel",a)}),w?.addEventListener("click",e=>{e.preventDefault();const t=new URL(window.location.href),n=t.searchParams.get("FocusType")||t.searchParams.get("focusType");t.searchParams.delete("SelectedSceneID"),t.searchParams.delete("selectedSceneId"),t.searchParams.delete("selectedsceneid"),n&&"scene"!==n.toLowerCase()||(t.searchParams.delete("FocusType"),t.searchParams.delete("focusType"),t.searchParams.delete("FocusID"),t.searchParams.delete("focusId")),I(),window.location.href=t.toString()});const I=()=>{try{sessionStorage.setItem(p,JSON.stringify({path:window.location.pathname,top:window.scrollY,left:window.scrollX,workspaceLeft:t.scrollLeft,workspaceTop:t.scrollTop,containers:L().map((e,t)=>({index:t,left:e.scrollLeft,top:e.scrollTop}))}))}catch{}};window.PlotLineTimelineScroll={save:I};const x=(t,n,a)=>{a.forEach(n=>e.classList.remove(`${t}-${n}`)),e.classList.add(`${t}-${n}`)},C=e=>{const t=g.includes(e)?e:"comfortable";x("density",t,g),n&&(n.value=t),localStorage.setItem(c,t),window.requestAnimationFrame(A)},q=e=>{const t=f.includes(e)?e:"chapter";x("zoom",t,f),a&&(a.value=t),localStorage.setItem(d,t),window.requestAnimationFrame(A)},k=(t,n=!0)=>{const a=(t=>e.querySelector(`[data-scene-id='${CSS.escape(String(t))}']`))(t);a&&(a.classList.add("keyboard-focus"),a.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),n&&a.focus({preventScroll:!0}),window.setTimeout(()=>a.classList.remove("keyboard-focus"),1800))},A=()=>{e.querySelectorAll("[data-plot-line-svg-board]").forEach(e=>{const t=e.querySelector("[data-plot-line-svg]");if(!t)return;let n=e.querySelector("[data-plot-line-health-svg]");n||(n=document.createElementNS("http://www.w3.org/2000/svg","svg"),n.setAttribute("class","plot-lane-svg-overlay plot-lane-svg-health-overlay"),n.setAttribute("data-plot-line-health-svg",""),n.setAttribute("aria-hidden","true"),t.after(n)),t.replaceChildren(),n.replaceChildren();const a=e.getBoundingClientRect(),o=Math.max(e.scrollWidth,a.width),r=Math.max(e.scrollHeight,a.height);[t,n].forEach(e=>{e.setAttribute("viewBox",`0 0 ${o} ${r}`),e.setAttribute("width",String(o)),e.setAttribute("height",String(r))});const i=(e,t={})=>{const n=document.createElementNS("http://www.w3.org/2000/svg",e);return Object.entries(t).forEach(([e,t])=>n.setAttribute(e,String(t))),n},s=[...new Set([...e.querySelectorAll("[data-plot-line-slot]")].map(e=>{const t=e.getBoundingClientRect();return Math.round(t.left-a.left+t.width/2)}))].sort((e,t)=>e-t),l=Math.max(s.length-1,0),c=new Map;e.querySelectorAll("[data-plot-line-row]").forEach(e=>{e.dataset.plotLineId&&c.set(e.dataset.plotLineId,e)});const d=[...c.values()].map(e=>({row:e,plotLineId:e.dataset.plotLineId,colour:e.dataset.plotColour||"#2f6f63",points:[...e.querySelectorAll("[data-plot-event-node]")].map(e=>((e,t)=>{const n=t.closest("[data-plot-line-slot]");if(!n)return null;const o=n.getBoundingClientRect(),r=t.getBoundingClientRect();return{x:r.left-a.left+r.width/2,y:r.top-a.top+r.height/2,sceneX:o.left-a.left+o.width/2,radius:Math.max(r.width,r.height)/2,sceneIndex:0,type:(t.dataset.plotEventType||"Progress").toLowerCase(),targetPlotLineId:t.dataset.targetPlotLineId||"",targetPlotLineIds:(t.dataset.targetPlotLineIds||"").split(",").map(e=>e.trim()).filter(Boolean)}})(0,e)).filter(Boolean).map(e=>({...e,sceneIndex:Math.max(0,s.findIndex(t=>t===Math.round(e.sceneX)))})).sort((e,t)=>e.x-t.x),incomingStartXs:[]})),p=new Map(d.map(e=>[e.plotLineId,e])),u=(e,n,o,r)=>{if(!n)return void(window.console?.warn&&window.console.warn(`Plot line ${r} connector skipped because target plot line data is missing.`));e.x;const s=(e=>{const t=e.getBoundingClientRect();return t.top-a.top+t.height/2})(n),l=Math.max(28,Math.min(46,.28*Math.abs(s-e.y))),c=Math.max(8,e.radius||12),d=e.x+c,p=d+l,u=d+l/2;t.append(i("path",{class:`plot-line-svg-connector plot-line-svg-connector-${r}`,d:`M ${d} ${e.y} C ${u} ${e.y}, ${u} ${s}, ${p} ${s}`,stroke:o}))};d.forEach(e=>{e.points.forEach(e=>{"branch"===e.type&&e.targetPlotLineId&&p.get(e.targetPlotLineId)?.incomingStartXs.push(e.x),"split"===e.type&&e.targetPlotLineIds.forEach(t=>{p.get(t)?.incomingStartXs.push(e.x)})})}),d.forEach(e=>{e.health=(e=>{const t=e.some(e=>"resolve"===e.type),n=e.some(e=>"abandon"===e.type),a=e.find(e=>"resolve"===e.type),o=e.find(e=>"abandon"===e.type),r=e.some(e=>"merge"===e.type&&e.targetPlotLineId),i=e.some(e=>"split"===e.type&&e.targetPlotLineIds.length>=2),s=Boolean(a)&&e.some(e=>"branch"===e.type&&e.targetPlotLineId&&e.sceneIndex>=a.sceneIndex),c=e.reduce((e,t)=>Math.max(e,t.sceneIndex),0),d=!t&&!n&&l-c>=10,p=t&&!r&&!i&&!s;let u="active";return n?u="abandoned":t?u=p?"questionable":"resolved":d&&(u="neglected"),{state:u,isNeglected:d,isDangling:!t&&!n&&!r&&!i&&l>=c,isQuestionable:p,isAbandoned:n,resolvePoint:a,abandonPoint:o}})(e.points),e.state=e.health.state}),d.forEach(e=>{const{colour:n,points:a,incomingStartXs:o,state:r}=e;if(a.length>0){const s=a.find(e=>"start"===e.type),l=o.length?Math.min(...o):null,c=a.find(e=>"split"===e.type||"merge"===e.type),d=a[0],p=a[a.length-1],u=((null===l||s&&s.xe.x-t.x),m=c?h.indexOf(c):-1,g=m>=0?h.slice(0,m+1):h;null!==l&&l{const o=g[a+1];if(!o||o.x<=e.x)return;const s=Math.abs(o.sceneIndex-e.sceneIndex)>=f;t.append(i("path",{class:`plot-line-svg-path plot-line-svg-segment ${s?"plot-line-svg-segment-dormant":""} plotline-state-${r}`,d:`M ${e.x} ${e.y} L ${o.x} ${o.y}`,stroke:n}))})}}),d.forEach(({points:e,colour:t})=>{e.forEach(e=>{"branch"!==e.type&&"merge"!==e.type||u(e,c.get(e.targetPlotLineId),t,e.type),"split"===e.type&&e.targetPlotLineIds.length>=2?e.targetPlotLineIds.forEach(n=>{u(e,c.get(n),t,e.type)}):"split"===e.type&&e.targetPlotLineIds.length>0&&window.console?.warn&&window.console.warn("Plot line split connector skipped because fewer than two targets are available.")})}),d.forEach(e=>{const t=e.lineEndPoint||e.points[e.points.length-1],a=[];e.health?.isNeglected&&t&&a.push({point:t,kind:"neglected",label:"Dormant Thread",reason:"No activity for 10+ scenes.",action:"Add a Progress, Reveal or Resolve event."}),e.health?.isDangling&&t&&a.push({point:t,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."}),e.health?.isQuestionable&&e.health.resolvePoint&&a.push({point:e.health.resolvePoint,kind:"questionable",label:"Missing Closure",reason:"Resolved without merge, split, or branch.",action:"Verify this thread has sufficient payoff."}),e.health?.isAbandoned&&e.health.abandonPoint&&a.push({point:e.health.abandonPoint,kind:"abandoned",label:"Abandoned Thread",reason:"This plot line was abandoned.",action:"Check this is intentional."});const o=new Map;a.forEach(e=>{const t=`${Math.round(e.point.x)}:${Math.round(e.point.y)}`;o.has(t)||o.set(t,{point:e.point,warnings:[]}),o.get(t).warnings.push(e)}),o.forEach(({point:e,warnings:t})=>((e,t)=>{const a=Math.max(18,(e.radius||12)+8),o=t[0]?.kind||"warning",r=i("g",{class:`plotline-health-marker plotline-health-marker-${o}`}),s=i("title");s.textContent=(e=>e.map(e=>[`Warning: ${e.label}`,`Reason: ${e.reason}`,`Suggested Action: ${e.action}`].join("\n")).join("\n\n"))(t),r.append(s);const l=e.x+a,c=e.y-13;if("questionable"===o){r.append(i("circle",{cx:l,cy:c,r:9}));const e=i("text",{x:l,y:c+4,"text-anchor":"middle"});e.textContent=t.length>1?String(t.length):"?",r.append(e)}else if(r.append(i("circle",{cx:l,cy:c,r:10})),r.append(i("path",{d:`M ${l} ${c-6} L ${l+6} ${c+5} L ${l-6} ${c+5} Z`})),r.append(i("line",{x1:l,y1:c-2,x2:l,y2:c+2.5})),r.append(i("circle",{cx:l,cy:c+5.3,r:1.2})),t.length>1){const e=i("text",{x:l+11,y:c-6,"text-anchor":"middle",class:"plotline-health-count"});e.textContent=String(t.length),r.append(e)}n.append(r)})(e,t))})})},P=(e,t)=>{document.querySelectorAll(`[data-collapse-body='${CSS.escape(e)}']`).forEach(e=>{e.classList.toggle("timeline-section-collapsed",t)}),document.querySelectorAll(`[data-collapse-toggle='${CSS.escape(e)}']`).forEach(e=>{e.classList.toggle("is-collapsed",t),e.setAttribute("aria-expanded",String(!t))})};document.querySelectorAll("[data-collapse-toggle]").forEach(e=>{const t=e.dataset.collapseToggle;b||"collapsed"!==e.dataset.collapseDefault||S.add(t);const n=S.has(t);P(t,n),e.addEventListener("click",()=>{const e=!S.has(t);e?S.add(t):S.delete(t),P(t,e),localStorage.setItem(l,JSON.stringify([...S])),A(),window.dispatchEvent(new Event("plotline:timeline-layout-changed"))})}),n?.addEventListener("change",()=>C(n.value)),a?.addEventListener("change",()=>q(a.value)),C(localStorage.getItem(c)||"comfortable"),q(localStorage.getItem(d)||"chapter");const T=(()=>{try{return null!==sessionStorage.getItem(p)}catch{return!1}})();window.requestAnimationFrame(()=>{A(),((e="auto")=>{const t=r?.querySelector("[data-active-chapter='true']");if(!r||!t)return;const n=r.getBoundingClientRect(),a=t.getBoundingClientRect(),o=a.left+a.width/2,i=n.left+n.width/2;(a.leftn.right)&&r.scrollBy({left:o-i,behavior:e})})("auto"),T&&(()=>{let e=null;try{e=JSON.parse(sessionStorage.getItem(p)||"null"),sessionStorage.removeItem(p)}catch{try{sessionStorage.removeItem(p)}catch{}return}e&&e.path===window.location.pathname&&window.requestAnimationFrame(()=>{t.scrollLeft=Number(e.workspaceLeft)||0,t.scrollTop=Number(e.workspaceTop)||0;const n=L();(e.containers||[]).forEach(e=>{const t=n[e.index];t&&(t.scrollLeft=Number(e.left)||0,t.scrollTop=Number(e.top)||0)}),window.scrollTo(Number(e.left)||0,Number(e.top)||0)})})()}),window.addEventListener("resize",A),window.addEventListener("plotline:timeline-layout-changed",A);e.addEventListener("click",t=>{if(0!==t.button||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)return;const n=t.target instanceof Element?t.target:null,a=n?.closest("a[href]");if(!a||!e.contains(a))return;[...new URL(a.href,window.location.href).searchParams.keys()].some(e=>"selectedsceneid"===e.toLowerCase())&&I()},{capture:!0});const M=()=>{const e=o?.value.trim().toLowerCase()||"";let t=null;return s.forEach(n=>{const a=!e||(n.dataset.timelineSearchText||"").toLowerCase().includes(e);n.classList.toggle("search-hit",Boolean(e&&a)),n.classList.toggle("search-dim",Boolean(e&&!a)),!t&&e&&a&&(t=n)}),t};o?.addEventListener("input",M),o?.addEventListener("keydown",e=>{if("Enter"!==e.key)return;const t=M();t&&(e.preventDefault(),k(t.dataset.sceneId))}),i.forEach(e=>{e.addEventListener("change",()=>{e.value&&k(e.value)})}),document.querySelectorAll("[data-jump-scene]").forEach(e=>{e.addEventListener("click",()=>{e.dataset.jumpScene&&k(e.dataset.jumpScene)})}),document.addEventListener("keydown",e=>{const n=document.activeElement?.tagName?.toLowerCase(),r=["input","select","textarea"].includes(n)||document.activeElement?.isContentEditable;if((e.ctrlKey||e.metaKey)&&"f"===e.key.toLowerCase()&&o)return e.preventDefault(),o.focus(),void o.select();if("Escape"===e.key){const e=new URL(window.location.href),t=e.searchParams.has("FocusType")||e.searchParams.has("FocusID");return e.searchParams.delete("FocusType"),e.searchParams.delete("FocusID"),void(t?window.location.href=e.toString():o?.value&&(o.value="",M()))}if(!r){if("ArrowLeft"===e.key||"ArrowRight"===e.key)return e.preventDefault(),void t.scrollBy({left:"ArrowLeft"===e.key?-260:260,behavior:"smooth"});if("+"===e.key||"="===e.key||"-"===e.key){const t=f.indexOf(a?.value||"chapter"),n="-"===e.key?-1:1,o=Math.min(f.length-1,Math.max(0,t+n));q(f[o])}}})})(),(()=>{const e=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-drag-drop-panel]");if(!t)return;const n=new Map([...document.querySelectorAll("[data-drag-form]")].map(e=>[e.dataset.dragForm,e])),a=t.querySelector("[data-drag-panel-title]"),o=t.querySelector("[data-drag-panel-kicker]"),r=t.querySelector("[data-drag-panel-summary]"),i=document.querySelector("[data-scene-move-modal]"),s=i&&window.bootstrap?.Modal?window.bootstrap.Modal.getOrCreateInstance(i):null,l=i?.querySelector("[data-scene-move-title]"),c=i?.querySelector("[data-scene-move-summary]"),d=i?.querySelector("[data-scene-move-submit]"),p="plotline.timeline.scroll";let u=null,h=0;const m=(e,t,n)=>{const a=e?.querySelector(`[name='${CSS.escape(t)}']`);a&&(a.value=n??"")},g=e=>e?.selectedOptions?.[0]?.textContent?.trim()||"",f=e=>`“${e||"this scene"}”`,y=(e,t,n,a,o)=>t&&n&&t===n?`Move ${f(e.label)} to this new position within ${n}?`:t&&n?`Move ${f(e.label)} from ${t} to ${n}?`:`Move ${f(e.label)} ${a.toLowerCase()} ${o}?`,v=(e,i,p,u)=>{if("scene"===e)return t.classList.add("d-none"),t.querySelectorAll("[data-drag-form]").forEach(e=>e.classList.add("d-none")),n.get("scene")?.classList.remove("d-none"),l&&(l.textContent=i),c&&(c.textContent=p),d&&d.classList.toggle("d-none",i.toLowerCase().startsWith("cannot")||i.toLowerCase().startsWith("scene already")),void(s?s.show():t.classList.remove("d-none"));n.forEach((t,n)=>t.classList.toggle("d-none",n!==e)),t.classList.remove("d-none"),a.textContent=i,o.textContent=u,r.textContent=p,t.scrollIntoView({behavior:"smooth",block:"center"})},w=()=>{t.classList.add("d-none"),n.forEach(e=>e.classList.add("d-none"))},b=()=>{document.querySelectorAll(".drop-target-active, .drop-before, .drop-after, .chapter-drop-active, .drop-chapter-end").forEach(e=>e.classList.remove("drop-target-active","drop-before","drop-after","chapter-drop-active","drop-chapter-end"))};try{const t=JSON.parse(sessionStorage.getItem(p)||"null");t&&(sessionStorage.removeItem(p),requestAnimationFrame(()=>{const n=e?.querySelector(".timeline-shell");n&&(n.scrollLeft=t.left||0),window.scrollTo({top:t.top||0})}))}catch{sessionStorage.removeItem(p)}const S=e=>{try{const t=JSON.parse(e.dataTransfer.getData("application/json")||"{}");return t.type?t:u||{}}catch{return u||{}}};document.querySelectorAll("[data-drag-type][draggable='true']").forEach(e=>{e.addEventListener("dragstart",t=>((e,t)=>{const n={type:t.dataset.dragType,id:t.dataset.dragId,label:t.dataset.dragLabel||t.textContent.trim()};e.dataTransfer.effectAllowed="copyMove",e.dataTransfer.setData("application/json",JSON.stringify(n)),e.dataTransfer.setData("text/plain",`${n.type}:${n.id}`),u=n,document.body.classList.add("drag-active",`dragging-${n.type}`),t.classList.add("is-dragging")})(t,e)),e.addEventListener("dragend",()=>{e.classList.remove("is-dragging"),document.body.classList.remove("drag-active","dragging-scene","dragging-character","dragging-asset","dragging-location"),u=null,b()})}),t.querySelectorAll("[data-drag-cancel]").forEach(e=>e.addEventListener("click",w)),n.get("scene")?.addEventListener("submit",()=>{const t=e?.querySelector(".timeline-shell");try{sessionStorage.setItem(p,JSON.stringify({left:t?.scrollLeft||0,top:window.scrollY||0}))}catch{}}),e?.querySelectorAll(".timeline-scene-card[data-scene-id]").forEach(t=>{t.addEventListener("click",e=>{Date.now(){const n=S(e);["scene","character","asset"].includes(n.type)&&(e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="scene"===n.type?"move":"copy",t.classList.add("drop-target-active"),t.classList.toggle("drop-before","scene"===n.type&&e.offsetX=t.clientWidth/2))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active","drop-before","drop-after")),t.addEventListener("drop",a=>{const o=S(a);if(!["scene","character","asset"].includes(o.type))return;a.preventDefault(),a.stopPropagation(),h=Date.now()+800,t.classList.remove("drop-target-active","drop-before","drop-after");const r=t.dataset.sceneId;if("scene"===o.type){if(o.id===r)return void v("scene","Cannot move scene","Drop a scene before or after a different scene.","Scene move");const i=n.get("scene"),s=a.offsetX{const a=t.closest("[data-chapter-drop-target]"),o=t.dataset.chapterId||a?.dataset.chapterId,r=t.dataset.chapterLabel||a?.dataset.chapterLabel||"this chapter";t.addEventListener("dragover",e=>{"scene"===S(e).type&&o&&(e.target.closest(".timeline-scene-card[data-scene-id]")||(e.preventDefault(),e.dataTransfer.dropEffect="move",t.classList.add("drop-target-active","drop-chapter-end"),a?.classList.add("chapter-drop-active")))}),t.addEventListener("dragleave",e=>{t.contains(e.relatedTarget)||(t.classList.remove("drop-target-active","drop-chapter-end"),a?.classList.remove("chapter-drop-active"))}),t.addEventListener("drop",a=>{const i=S(a);if("scene"!==i.type||!o)return;if(a.target.closest(".timeline-scene-card[data-scene-id]"))return;a.preventDefault(),a.stopPropagation(),h=Date.now()+800,b();const s=e.querySelector(`.timeline-scene-card[data-scene-id='${CSS.escape(i.id)}']`);if(s?.dataset.chapterId===o&&t.lastElementChild?.dataset?.sceneId===i.id)return void v("scene","Scene already here",`${i.label} is already at the end of ${r}.`,"Scene move");const l=n.get("scene");m(l,"SceneID",i.id),m(l,"AnchorSceneID",""),m(l,"TargetChapterID",o),m(l,"Position","End"),v("scene","Move Scene?",y(i,s?.dataset.chapterLabel||"",r,"After",r),"Scene move")})});const L=n.get("asset-dependency"),E=()=>{const e=L?.dataset.sourceLabel||"Source asset",n=L?.dataset.targetLabel||"Target asset",a=g(L?.querySelector("[name='AssetDependencyTypeID']"))||"Requires",o=g(L?.querySelector("[name='SourceRequiredStateID']"))||"the required state",r=g(L?.querySelector("[name='TargetBlockedStateID']"))||"the blocked state",i=t.querySelector("[data-asset-dependency-preview]");i&&(i.textContent=`${e} ${a.toLowerCase()} ${n}. ${e} should be ${o} before ${n} can be ${r}.`)};if(L?.querySelectorAll("select").forEach(e=>e.addEventListener("change",E)),document.querySelectorAll("[data-asset-drop-target], .drag-chip.asset").forEach(e=>{e.addEventListener("dragover",t=>{"asset"===S(t).type&&(t.preventDefault(),t.dataTransfer.dropEffect="copy",e.classList.add("drop-target-active"))}),e.addEventListener("dragleave",()=>e.classList.remove("drop-target-active")),e.addEventListener("drop",t=>{const n=S(t),a=e.dataset.assetId||e.dataset.dragId,o=e.dataset.assetLabel||e.dataset.dragLabel||e.textContent.trim();"asset"===n.type&&a&&(t.preventDefault(),e.classList.remove("drop-target-active"),n.id!==a?(m(L,"SourceAssetID",n.id),m(L,"TargetAssetID",a),L.dataset.sourceLabel=n.label,L.dataset.targetLabel=o,E(),v("asset-dependency","Create asset dependency",`${n.label} -> ${o}`,"Asset dependency")):v("asset-dependency","Cannot create dependency","Drop one asset onto a different asset.","Asset dependency"))})}),document.querySelectorAll("[data-drag-type='location']").forEach(e=>{e.addEventListener("dragover",t=>{"location"===S(t).type&&(t.preventDefault(),t.dataTransfer.dropEffect="copy",e.classList.add("drop-target-active"))}),e.addEventListener("dragleave",()=>e.classList.remove("drop-target-active")),e.addEventListener("drop",t=>{const a=S(t),o=e.dataset.dragId,r=e.dataset.dragLabel||e.textContent.trim();if("location"!==a.type)return;if(t.preventDefault(),e.classList.remove("drop-target-active"),a.id===o)return void v("location-relationship","Cannot create relationship","Drop one location onto a different location.","Location relationship");const i=n.get("location-relationship");m(i,"FromLocationID",a.id),m(i,"ToLocationID",o),v("location-relationship","Create location relationship",`${a.label} -> ${r}`,"Location drop")})}),document.querySelector("[data-story-calendar-root]")){const e=()=>document.querySelector("[data-story-calendar-root]"),t=()=>e()?.querySelector("[data-story-calendar-detail]"),n=()=>{e()?.querySelectorAll("[data-story-calendar-notice]").forEach(e=>e.remove())},a=(a,o)=>{n();const r=t(),i=e(),s=r||i;if(!s)return;const l=document.createElement("p");l.className=("story-calendar-detail-notice "+("error"===o?"is-error":"")).trim(),l.dataset.storyCalendarNotice="true",l.textContent=a,s.prepend(l)},o=async(t,n="push")=>{const o=e();if(!o||!window.fetch)return!1;a("Loading...","loading");try{const e=await fetch(t,{headers:{"X-Requested-With":"XMLHttpRequest"},credentials:"same-origin"});if(!e.ok)throw new Error("Calendar load failed");if(o.innerHTML=await e.text(),window.history?.pushState&&"none"!==n){const e=new URL(t,window.location.href),a={storyCalendarUrl:e.toString()};"replace"===n?window.history.replaceState(a,"",e.toString()):window.history.pushState(a,"",e.toString())}return!0}catch{return a("The calendar could not be loaded. Use the link or Apply button to retry.","error"),!1}},r=e=>{const t=new FormData(e),n=new URL(e.dataset.storyCalendarIndexUrl||e.action||window.location.href,window.location.href);n.search="";for(const[e,a]of t.entries())"__RequestVerificationToken"!==e&&""!==a&&n.searchParams.set(e,a);return n.toString()};window.history?.replaceState?.({storyCalendarUrl:window.location.href},"",window.location.href),document.addEventListener("click",async t=>{const n=t.target.closest("[data-story-calendar-day], [data-story-calendar-nav]");n&&n.isConnected&&e()?.contains(n)&&window.fetch&&(t.preventDefault(),await o(n.href,"push"))}),document.addEventListener("submit",async e=>{const t=e.target.closest("[data-story-calendar-filter]");t&&t.isConnected&&window.fetch&&(e.preventDefault(),await o(r(t),"push"))}),document.addEventListener("change",async e=>{const t=e.target.closest("[data-story-calendar-filter] select"),n=t?.closest("[data-story-calendar-filter]");t&&n&&window.fetch&&await o(r(n),"push")}),window.addEventListener("popstate",async()=>{await o(window.location.href,"none")})}})(); \ No newline at end of file +(()=>{const e="plotline.theme",t=document.querySelectorAll("[data-theme-choice]"),n=n=>{const a="dark"===n?"dark":"light";document.documentElement.dataset.theme=a,document.documentElement.dataset.bsTheme=a,(t=>{try{localStorage.setItem(e,t)}catch{}})(a),t.forEach(e=>{const t=e.dataset.themeChoice===a;e.classList.toggle("active",t),e.setAttribute("aria-pressed",String(t))})};t.forEach(e=>{e.addEventListener("click",()=>n(e.dataset.themeChoice))}),n((()=>{try{return localStorage.getItem(e)}catch{return null}})()||"light")})(),(()=>{const e=document.querySelector("[data-trial-banner]");if(!e)return;const t=`plotline.trialBanner.dismissed.${e.dataset.trialBannerKey||"current"}`;try{if("true"===localStorage.getItem(t))return void(e.hidden=!0)}catch{}e.querySelector("[data-trial-banner-dismiss]")?.addEventListener("click",()=>{e.hidden=!0;try{localStorage.setItem(t,"true")}catch{}})})(),(()=>{const e="plotdirector:story-position-changed",t=(e,t)=>{const n=e?.position||e?.Position||e||{},a=(...e)=>{for(const t of e){const e=Number.parseInt(t||"",10);if(Number.isInteger(e)&&e>0)return e}return null};return{source:t||e?.source||e?.Source||"Navigation",projectId:a(n.projectId,n.ProjectId,e?.projectId,e?.ProjectId),bookId:a(n.bookId,n.BookId,e?.bookId,e?.BookId),chapterId:a(n.chapterId,n.ChapterId,e?.chapterId,e?.ChapterId),sceneId:a(n.sceneId,n.SceneId,e?.sceneId,e?.SceneId),canonicalUrl:e?.canonicalUrl||e?.CanonicalUrl||e?.sceneUrl||e?.SceneUrl||""}};window.PlotDirectorStoryPosition={eventName:e,normalise:t,publishChanged:(n,a)=>{const o=t(n,a);return o.projectId?(document.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:o})),o):o}}})(),(()=>{const e=document.querySelector("[data-word-companion-follow-toggle]");if(!e||!window.signalR)return;const t="plotdirector.followWordCompanion",n=["/pricing","/about","/contact","/privacy","/terms","/admin","/billing","/account","/login","/register","/feature-requests/admin","/featurerequests/admin","/manageaccount"];let a=null,o=null;const r=()=>{try{return"true"===localStorage.getItem(t)}catch{return!1}},i=t=>{e.setAttribute("aria-pressed",String(t)),e.classList.toggle("active",t),e.title=t?"PlotDirector will follow the current Word Companion scene.":"Enable this to let PlotDirector follow the current Word Companion scene."},s=(e,t)=>{if("string"!=typeof e||!e.startsWith(t))return"";try{const t=new URL(e,window.location.origin);return t.origin===window.location.origin?`${t.pathname}${t.search}${t.hash}`:""}catch{return""}},l=e=>{if(!r()||!(()=>{const e=window.location.pathname.toLowerCase();return!("/"===e||""===e||document.querySelector("[data-writing-mode]")||"/writing/context"===e||e.startsWith("/writing/context/")||"/writing/intent"===e||e.startsWith("/writing/intent/")||n.some(t=>e===t||e.startsWith(`${t}/`)))})()||!e)return;`${window.location.pathname}${window.location.search}${window.location.hash}`!==e&&window.location.assign(e)};i(r()),e.addEventListener("click",()=>{const e=!r();(e=>{try{localStorage.setItem(t,e?"true":"false")}catch{}})(e),i(e)});const c=(new signalR.HubConnectionBuilder).withUrl("/hubs/word-companion-follow").withAutomaticReconnect().build();c.on("wordCompanionSceneChanged",e=>{const t=(window.PlotDirectorStoryPosition?.publishChanged(e,"WordCompanion")||{sceneId:Number.parseInt(e?.sceneId||e?.SceneId||"",10)}).sceneId;if(!Number.isInteger(t)||t<=0||t===a)return;a=t;const n=s(e?.sceneUrl||e?.SceneUrl||"","/Scenes/Edit/");l(n)}),c.on("storyPositionChanged",e=>{window.PlotDirectorStoryPosition?.publishChanged(e,e?.source||e?.Source||"WordCompanion")}),c.on("wordCompanionCharactersCreated",e=>{const t=Number.parseInt(e?.projectId||e?.ProjectId||"",10);if(!Number.isInteger(t)||t<=0||t===o)return;o=t;const n=s(e?.charactersUrl||e?.CharactersUrl||"","/Characters");l(n)}),c.start().catch(e=>{console.debug("Word Companion follow connection unavailable.",e)})})(),(()=>{const e=document.getElementById("plotlineConfirmModal"),t=e&&window.bootstrap?new bootstrap.Modal(e):null,n=e?.querySelector("#plotlineConfirmModalTitle"),a=e?.querySelector("#plotlineConfirmModalMessage"),o=e?.querySelector("[data-confirm-submit]"),r=e?.querySelector("[data-delete-confirm-field]"),i=e?.querySelector("[data-delete-confirm-input]");let s=null,l=null;e?.addEventListener("hidden.bs.modal",()=>{s=null,l=null,i&&(i.value=""),r?.classList.add("d-none"),o?.removeAttribute("disabled")}),i?.addEventListener("input",()=>{"true"===s?.dataset.requireDeleteText&&o&&(o.disabled="DELETE"!==i.value.trim())}),o?.addEventListener("click",()=>{if(s){if("true"===s.dataset.requireDeleteText){const e=s.querySelector("[data-delete-confirm-target], [name='confirmationText']");e&&(e.value=i?.value.trim()||"")}s.dataset.confirmed="true",t?.hide(),s.requestSubmit&&l?s.requestSubmit(l):s.requestSubmit?s.requestSubmit():s.submit()}}),document.addEventListener("submit",c=>{const d=c.target;if(!(d instanceof HTMLFormElement)||"true"===d.dataset.confirmed||"true"===d.dataset.confirmSkip||"true"===d.dataset.noDeleteConfirm)return;const p=d.dataset.confirmMessage,u=c.submitter instanceof HTMLElement?c.submitter:d.querySelector("button[type='submit']"),h=u?.textContent||"",m=d.getAttribute("action")||"",g=/restore/i.test(h)||/\/restore\b/i.test(m),f=/delete forever/i.test(h)||/\/deleteforever\b/i.test(m),v=!g&&!f&&(/archive/i.test(h)||/\/archive\b/i.test(m)),y=!f&&/(delete|remove)/i.test(h);if(!(p||v||g||y||f))return;const w=p||(v?"Archive this item?\n\nThis will hide it from active lists and timelines, but the data will be kept and can be restored later.":g?"Restore this item?\n\nThis will return it to active lists and timelines where applicable.":f?"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(!(t&&e&&n&&a&&o))return;c.preventDefault();const b=(e=>{const t=e.split(/\n\s*\n/);return{title:t.shift()?.trim()||"Confirm action",body:t.join("\n\n").trim()}})(w);s=d,l=u,n.textContent=d.dataset.confirmTitle||b.title,a.textContent=d.dataset.confirmBody||b.body,o.textContent=d.dataset.confirmButtonText||h.trim()||"Confirm",o.className=`btn ${d.dataset.confirmButtonClass||(g?"btn-primary":"btn-danger")}`;const L="true"===d.dataset.requireDeleteText||f;r?.classList.toggle("d-none",!L),L&&o&&(o.disabled=!0,i?.focus()),t.show(),L&&window.setTimeout(()=>i?.focus(),200)})})(),(()=>{if(!window.bootstrap)return;const e=new Map;let t=null;const n=new WeakSet,a=e=>e.querySelector("[data-help-tooltip]")||e,o=e=>bootstrap.Tooltip.getInstance(a(e))?.hide(),r=(e=null)=>{t&&t!==e&&bootstrap.Popover.getInstance(t)?.hide()},i=()=>document.querySelectorAll("[data-help-icon]").forEach(e=>o(e)),s=(s=document)=>{(e=>{if(!e)return[];const t=[];return e.matches?.("[data-help-icon]")&&t.push(e),e.querySelectorAll?.("[data-help-icon]").forEach(e=>t.push(e)),t})(s).forEach(s=>{if(n.has(s))return;n.add(s);const l=a(s),c=l.dataset.helpMicro||"Help article not found.",d=s.dataset.helpTitle||"Help unavailable",p=s.dataset.helpSummary||"Help content has not yet been written for this feature.";bootstrap.Tooltip.getOrCreateInstance(l,{title:c,trigger:"hover focus",placement:"top",container:"body"});const u=bootstrap.Popover.getOrCreateInstance(s,{title:d,trigger:"click",placement:"auto",container:"body",html:!0,customClass:"plotline-help-popover",content(){const e=document.createElement("div"),t=document.createElement("p");t.className="mb-2",t.textContent=p;const n=document.createElement("a");return n.href=s.dataset.helpUrl||"#",n.textContent="More...",n.className="plotline-help-more",n.addEventListener("click",e=>{e.preventDefault(),r(),i(),s.dispatchEvent(new CustomEvent("plotline:help-more",{bubbles:!0,detail:{key:s.dataset.helpKey||""}}))}),e.append(t,n),e}});e.set(s,u),s.addEventListener("click",e=>{window.PlotLineHelpDrawer?.isActive?.()&&(e.preventDefault(),e.stopPropagation(),r(),o(s),window.PlotLineHelpPopovers?.hideAll(),window.PlotLineHelpDrawer.open(s.dataset.helpKey||""))}),s.addEventListener("show.bs.popover",e=>{window.PlotLineHelpDrawer?.isActive?.()?e.preventDefault():(r(s),o(s))}),s.addEventListener("shown.bs.popover",()=>{t=s,o(s)}),s.addEventListener("hidden.bs.popover",()=>{t===s&&(t=null)})})};document.addEventListener("click",e=>{const n=e.target instanceof Element?e.target:null;if(!n||!t)return;if(n.closest("[data-help-icon]"))return;const a=document.querySelector(`#${t.getAttribute("aria-describedby")}`);a?.contains(n)||r()}),document.addEventListener("keydown",e=>{"Escape"===e.key&&r()}),window.PlotLineHelpPopovers={hideAll(){e.forEach(e=>e.hide()),i(),t=null}},window.PlotLineHelpIcons={init:s},s()})(),(()=>{const e=document.querySelector("[data-help-layout]"),t=document.querySelector("[data-help-drawer]"),n=document.querySelector("[data-help-content]"),a=document.querySelector("[data-help-splitter]"),o=document.querySelector("[data-help-close]"),r=document.querySelector("[data-help-collapse]"),i=document.querySelector("[data-help-expand]"),s=document.querySelector("[data-help-pin]"),l=document.querySelector("[data-help-drawer-heading]");if(!e||!t||!n)return;const c="plotline.helpDrawer.width",d="plotline.helpDrawer.pinned",p="plotline.helpDrawer.key",u="plotline.helpDrawer.collapsed",h=e=>{try{return"true"===localStorage.getItem(e)}catch{return!1}},m=(e,t)=>{try{localStorage.setItem(e,t)}catch{}},g=t=>{const n=`${((e,t,n)=>Math.min(n,Math.max(t,e)))(Number.parseFloat(t)||25,20,50)}%`;e.style.setProperty("--help-drawer-width",n),m(c,n)},f=e=>{m(d,String(e)),s?.setAttribute("aria-pressed",String(e)),s&&(s.textContent=e?"Unpin":"Pin")},v=t=>{e.classList.toggle("help-collapsed",t),m(u,String(t)),r&&(r.textContent=t?"Expand":"Collapse")},y=async(t,a={})=>{const o=t||"";e.classList.add("help-open"),a.keepCollapsed||v(!1),m(p,o),l&&(l.textContent=o||"Help"),n.innerHTML='

Loading help...

';try{const e=await fetch(`/HelpDrawer/Article?key=${encodeURIComponent(o)}`,{headers:{"X-Requested-With":"XMLHttpRequest"}});n.innerHTML=e.ok?await e.text():'

Help for this feature has not yet been written.

';const t=n.querySelector(".help-drawer-article h2")?.textContent?.trim();l&&t&&(l.textContent=t)}catch{n.innerHTML='

Help for this feature has not yet been written.

'}};window.PlotLineHelpDrawer={isActive:()=>e.classList.contains("help-open"),open:e=>y(e)},g(localStorage.getItem(c)||"25%"),f(h(d)),v(h(u)),document.addEventListener("plotline:help-more",e=>{const t=e.detail?.key||e.target?.dataset?.helpKey||"";window.PlotLineHelpPopovers?.hideAll(),y(t)}),document.addEventListener("click",e=>{const t=e.target instanceof Element?e.target.closest("[data-help-open]"):null;t&&(e.preventDefault(),window.PlotLineHelpPopovers?.hideAll(),y(t.dataset.helpOpen||""))}),o?.addEventListener("click",()=>{e.classList.remove("help-open","help-collapsed"),f(!1)}),r?.addEventListener("click",()=>v(!e.classList.contains("help-collapsed"))),i?.addEventListener("click",()=>v(!1)),i?.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),v(!1))}),s?.addEventListener("click",()=>f(!h(d))),a?.addEventListener("pointerdown",t=>{if(!e.classList.contains("help-open")||e.classList.contains("help-collapsed"))return;t.preventDefault(),document.body.classList.add("help-resizing"),a.setPointerCapture?.(t.pointerId);const n=t=>{const n=e.getBoundingClientRect(),a=(n.right-t.clientX)/n.width*100;g(a)},o=()=>{document.body.classList.remove("help-resizing"),document.removeEventListener("pointermove",n),document.removeEventListener("pointerup",o)};document.addEventListener("pointermove",n),document.addEventListener("pointerup",o)});const w=(()=>{try{return localStorage.getItem(p)||""}catch{return""}})();h(d)&&w&&y(w,{keepCollapsed:!0})})(),(()=>{const e=document.querySelectorAll("[data-metric-shape-chart]");if(!e.length)return;const t=["#2f6f63","#b35f3b","#6750a4","#c58b2b","#7d3f52","#33759b","#9c4d8f","#a33d3d","#6f8f3d","#d08a2f","#444b57"],n=new Set(["Overall Intensity","Tension","Emotional Weight","Action"]),a=[],o=e=>{const t=e.querySelector(".metric-chart-frame");if(!t)return;let n=Number(t.dataset.metricNaturalWidth||0);if(n&&"true"===t.dataset.metricCanvasConstrained||(t.style.width="",n=t.getBoundingClientRect().width,n&&(t.dataset.metricNaturalWidth=String(n))),!n)return;const a=Math.max(window.devicePixelRatio||1,1),o=Math.floor(3e4/a);n>o?(t.style.width=`${o}px`,t.dataset.metricCanvasConstrained="true"):(t.style.width="",t.dataset.metricCanvasConstrained="false")},r=()=>{window.requestAnimationFrame(()=>{a.forEach(({chart:e,root:t})=>{o(t),e.resize()})})};window.PlotLineMetricCharts={resizeAll:r},window.addEventListener("plotline:timeline-layout-changed",r);const i={id:"plotlineChapterGuides",afterDraw(e){const t=e.config.options.plugins.plotlineScenes||[],{ctx:n,chartArea:a,scales:o}=e;if(!t.length||!o.x)return;n.save(),n.strokeStyle="rgba(47, 111, 99, 0.16)",n.fillStyle="rgba(82, 75, 65, 0.58)",n.lineWidth=1,n.font="700 10px system-ui, -apple-system, Segoe UI, sans-serif";const r=[];t.forEach((e,n)=>{n&&e.chapterLabel===t[n-1].chapterLabel||r.push({scene:e,index:n})});const i=r.filter(e=>!e.scene.isPlaceholder),s=i.length>40?5:i.length>24?4:i.length>14?2:1;i.forEach((e,t)=>{const{scene:r,index:l}=e,c=o.x.getPixelForValue(l+.5);n.beginPath(),n.moveTo(c,a.top),n.lineTo(c,a.bottom),n.stroke();const d=Number.parseInt(String(r.chapterLabel).replace(/[^0-9]/g,""),10);(0===t||t===i.length-1||1===s||Number.isFinite(d)&&d%s===0)&&n.fillText(r.chapterLabel,Math.min(c+4,a.right-70),a.bottom+22)}),n.restore()}};window.Chart&&Chart.register(i),e.forEach(e=>{const r=e.querySelector("script[type='application/json']"),i=e.querySelector("canvas"),s=e.querySelector("[data-metric-toggles]"),l=e.querySelector("[data-chart-fallback]"),c=e.querySelector("[data-timeline-metric-edit-toolbar]"),d=e.querySelector("[data-edit-metric-status]"),p=e.hasAttribute("data-timeline-metric-editor")&&c&&d;if(!r||!i)return;let u;try{u=JSON.parse(r.textContent||"{}")}catch{return void l?.classList.remove("d-none")}if(!window.Chart||!u.scenes?.length||!u.metrics?.length)return void l?.classList.remove("d-none");const h=new Set(u.defaultMetrics?.length?u.defaultMetrics:n),m=u.scenes.length,g=m>80?10:m>40?5:m>24?4:m>14?2:1,f=u.metrics.map((e,n)=>{const a=new Map(e.points.map(e=>[e.sceneId,e.value])),o=t[n%t.length];return{label:e.metricName,metricTypeId:e.metricTypeId,minValue:Number.isFinite(e.minValue)?e.minValue:1,maxValue:Number.isFinite(e.maxValue)?e.maxValue:10,data:u.scenes.map((e,t)=>{const n=a.get(e.sceneId);return null==n?{x:t+.5,y:null,sceneId:e.sceneId}:{x:t+.5,y:n,sceneId:e.sceneId}}),borderColor:o,backgroundColor:o,pointBackgroundColor:o,pointBorderColor:"#fff",pointBorderWidth:1.5,pointRadius:3,pointHoverRadius:5,borderWidth:2,tension:.35,spanGaps:!0,hidden:!h.has(e.metricName)}});let v=null,y=!1;o(e);const w=e=>{d&&(d.textContent=e)},b=()=>f.map((e,t)=>t).filter(e=>E?.isDatasetVisible(e)),L=()=>{const e=b();return 1===e.length?e[0]:-1},S=()=>{if(!p)return;const e=L();f.forEach((t,n)=>{t.pointRadius=n===e?5:3,t.pointHoverRadius=n===e?7:5,t.borderWidth=n===e?3:2});const t=b(),n=e>=0;i.classList.toggle("metric-curve-editing",n),d.classList.toggle("d-none",0===t.length),0===t.length?w(""):w(n?"Drag points up or down to edit this metric.":"Show only one metric line to edit its curve."),E?.update()};s.innerHTML="",f.forEach((e,t)=>{const n=`metric-toggle-${Math.random().toString(36).slice(2)}-${t}`,a=document.createElement("label");a.className="metric-toggle-pill",a.style.setProperty("--metric-colour",e.borderColor),a.innerHTML=`${e.label}`,s.appendChild(a),a.querySelector("input").addEventListener("change",e=>{E.setDatasetVisibility(t,e.target.checked),S(),E.update()})});const E=new Chart(i,{type:"line",data:{datasets:f},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:m,bounds:"ticks",ticks:{maxRotation:0,autoSkip:!1,maxTicksLimit:16,callback(e){const t=Math.floor(Number(e));if(t<0||t>=m)return"";const n=u.scenes[t];return 0!==t&&t!==m-1&&t%g!==0||n?.isPlaceholder?"":n?.axisLabel||""}},afterBuildTicks(e){e.ticks=u.scenes.map((e,t)=>({value:t+.5}))},grid:{display:!1}}},plugins:{legend:{display:!1},plotlineScenes:u.scenes,tooltip:{callbacks:{title(e){const t=Math.floor(e[0].parsed.x),n=u.scenes[t];return n.isPlaceholder?n.sceneTitle||"No scene":`${n.chapterLabel}, Scene ${n.sceneNumber}: ${n.sceneTitle}`},label:e=>`${e.dataset.label}: ${e.formattedValue}`,afterBody(e){const t=Math.floor(e[0].parsed.x),n=u.scenes[t],a=[];return n.characterPresence?a.push(n.characterPresence):void 0!==n.characterAppears&&a.push(n.characterAppears?"Character appears":"Character does not appear"),n.bookTitle&&a.push(n.bookTitle),a}}}}}});p&&(S(),i.addEventListener("pointerdown",e=>{v=(e=>{if(!p)return null;const t=L();if(t<0)return null;const n=E.getElementsAtEventForMode(e,"nearest",{intersect:!0},!0).find(e=>e.datasetIndex===t);if(!n)return null;const a=E.data.datasets[n.datasetIndex],o=a?.data?.[n.index];return o?.sceneId&&null!=o.y?{datasetIndex:n.datasetIndex,pointIndex:n.index,previousValue:o.y}:null})(e),v&&(e.preventDefault(),i.setPointerCapture?.(e.pointerId),y=!0)}),i.addEventListener("pointermove",e=>{if(!v)return;const t=E.data.datasets[v.datasetIndex],n=t?.data?.[v.pointIndex];t&&n&&(n.y=((e,t)=>{const n=E.scales.y.getValueForPixel(e.offsetY),a=Math.round(n);return Math.max(t.minValue,Math.min(t.maxValue,a))})(e,t),E.update("none"))}),i.addEventListener("pointerup",async t=>{if(!v)return;const n=v;v=null,i.releasePointerCapture?.(t.pointerId),await(async t=>{const n=E.data.datasets[t.datasetIndex],a=n?.data?.[t.pointIndex];if(!n||!a?.sceneId)return;const o=c?.querySelector("input[name='__RequestVerificationToken']")?.value||"";w("Saving...");try{const t=await fetch(e.dataset.updateUrl||"/SceneMetrics/UpdateSceneMetricValue",{method:"POST",headers:{"Content-Type":"application/json",RequestVerificationToken:o},body:JSON.stringify({sceneId:Number(a.sceneId),metricId:Number(n.metricTypeId),value:Number(a.y)})}),r=await t.json().catch(()=>({}));if(!t.ok||!r.success)throw new Error(r.message||"Save failed");a.y=r.value,w("Saved"),E.update()}catch{a.y=t.previousValue,w("Save failed"),E.update()}})(n),window.setTimeout(()=>{y=!1},0)}),i.addEventListener("pointercancel",e=>{if(!v)return;const t=E.data.datasets[v.datasetIndex],n=t?.data?.[v.pointIndex];n&&(n.y=v.previousValue),v=null,i.releasePointerCapture?.(e.pointerId),y=!1,E.update()})),i.addEventListener("click",e=>{if(y)return void e.preventDefault();const t=E.getElementsAtEventForMode(e,"nearest",{intersect:!0},!0);if(!t.length)return;const n=E.data.datasets[t[0].datasetIndex]?.data?.[t[0].index],a=n?Math.floor(n.x):t[0].index,o=u.scenes[a];o?.timelineUrl&&(window.PlotLineTimelineScroll?.save(),window.location.href=o.timelineUrl)}),a.push({chart:E,root:e});const I=window.ResizeObserver?new ResizeObserver(()=>{o(e),E.resize()}):null;I&&(I.observe(e),I.observe(e.querySelector(".metric-chart-frame")||e))})})(),(()=>{const e=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-timeline-controls]");if(!e||!t)return;const n="plotline.timeline.visibility",a={"scene-cards":!0,"plot-lines":!0,assets:!0,characters:!0,locations:!0,warnings:!0,metrics:!0},o=t=>{Object.entries(t).forEach(([t,n])=>{e.classList.toggle(`hide-${t}`,!n),document.querySelectorAll(`[data-timeline-section='${t}']`).forEach(e=>{e.classList.toggle("timeline-section-hidden",!n)})}),document.querySelectorAll("[data-location-marker]").forEach(e=>{e.classList.toggle("timeline-section-hidden",!t.locations)}),document.querySelectorAll("[data-warning-marker]").forEach(e=>{e.classList.toggle("timeline-section-hidden",!t.warnings)}),document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(t))},r=(()=>{try{return{...a,...JSON.parse(localStorage.getItem(n)||"{}")}}catch{return{...a}}})();t.querySelectorAll("[data-timeline-toggle]").forEach(e=>{const t=e.dataset.timelineToggle;e.checked=r[t]??!0,e.addEventListener("change",()=>{r[t]=e.checked,localStorage.setItem(n,JSON.stringify(r)),o(r)})}),t.querySelector("[data-timeline-preset-form]")?.addEventListener("submit",()=>{document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(r))}),o(r)})(),(()=>{const e=document.querySelector("[data-timeline-root]");if(!e)return;const t=e.querySelector(".timeline-workspace")||e,n=document.querySelector("[data-timeline-density]"),a=document.querySelector("[data-timeline-zoom]"),o=document.querySelector("[data-timeline-search]"),r=e.querySelector("[data-timeline-minimap]"),i=document.querySelectorAll("[data-timeline-jump], [data-timeline-warning-jump], [data-timeline-entity-jump]"),s=[...e.querySelectorAll(".timeline-scene-card[data-scene-id]")],l="plotline.timeline.collapsed",c="plotline.timeline.density",d="plotline.timeline.zoom",p="plotline.timeline.pending-scroll",u="plotdirector.timelineNavigationDiscovered",h="plotline.timeline.inspectorWidth",m=[".timeline-shell",".plot-lane-board",".asset-lane-board",".character-lane-board",".metric-graph-stack","[data-timeline-minimap]"].join(","),g=["compact","comfortable","expanded"],f=["book","chapter","scene-detail","scene-overview"],v=e.querySelector("[data-inspector-drawer]"),y=e.querySelector("[data-inspector-resizer]"),w=e.querySelector("[data-inspector-close]"),b=null!==localStorage.getItem(l),L=(e=>{try{return new Set(JSON.parse(localStorage.getItem(e)||"[]"))}catch{return new Set}})(l),S=()=>[...e.querySelectorAll(m)],E=t=>{if(!v)return;const{minimum:n,maximum:a,defaultWidth:o}=(()=>{const t=e.getBoundingClientRect().width||window.innerWidth,n=Math.min(420,Math.max(320,t-360)),a=Math.max(n,Math.min(.65*t,t-420));return{minimum:n,maximum:a,defaultWidth:Math.max(n,Math.min(a,.4*t))}})(),r=Math.max(n,Math.min(a,Number(t)||o));e.style.setProperty("--timeline-inspector-width",`${Math.round(r)}px`)};v&&(E(localStorage.getItem(h)),window.addEventListener("resize",()=>E(localStorage.getItem(h)))),(()=>{const o=e.querySelector("[data-timeline-navigation-card]");if(!o)return;const r=o.querySelector("[data-timeline-navigation-dismiss]"),i=o.querySelector("[data-timeline-navigation-demo]"),s=()=>e.querySelector(".timeline-shell"),l=e=>Boolean(e&&e.scrollWidth>e.clientWidth+8),c=e=>Math.max(0,e.scrollWidth-e.clientWidth),d=()=>[t,...S()].filter((e,t,n)=>e&&n.indexOf(e)===t);let p=!1,h=!1,m=!1,g=null;const f=new WeakMap,v=e=>f.set(e,e.scrollLeft),y=(e=!1)=>{if(window.clearTimeout(g),!e||window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches||o.hidden)return o.classList.remove("is-dismissing"),void(o.hidden=!0);o.classList.add("is-dismissing"),g=window.setTimeout(()=>{o.hidden=!0,o.classList.remove("is-dismissing")},1300)},w=e=>{const t=l(e),n=c(e);e.classList.toggle("timeline-horizontal-overflow",t),e.classList.toggle("has-scroll-left",t&&e.scrollLeft>8),e.classList.toggle("has-scroll-right",t&&e.scrollLeft{l(s())&&!(()=>{try{return"true"===localStorage.getItem(u)}catch{return!1}})()&&!p?(window.clearTimeout(g),o.hidden=!1,o.classList.remove("is-dismissing")):o.classList.contains("is-dismissing")||y(!1),d().forEach(w)},L=()=>{p=!0,y(!0);try{localStorage.setItem(u,"true")}catch{}};d().forEach(e=>{v(e),e.addEventListener("wheel",t=>{if(!t.defaultPrevented&&t.shiftKey&&Math.abs(t.deltaY)>Math.abs(t.deltaX)&&l(e)){const n=e.scrollLeft;t.preventDefault(),e.scrollLeft+=t.deltaY,window.requestAnimationFrame(()=>{w(e),!h&&Math.abs(e.scrollLeft-n)>4&&L(),v(e)})}},{passive:!1}),e.addEventListener("scroll",()=>(e=>{if(w(e),!m||h)return void v(e);const t=f.get(e)??e.scrollLeft;Math.abs(e.scrollLeft-t)>4&&l(e)?L():v(e)})(e),{passive:!0})}),r?.addEventListener("click",L),i?.addEventListener("click",()=>{const e=s();if(!l(e))return void b();const t=e.scrollLeft,n=c(e)-t;let a=Math.min(220,n),o=1;a<24&&t>0&&(a=Math.min(220,t),o=-1),a<4||(h=!0,e.scrollTo({left:t+a*o,behavior:"smooth"}),window.setTimeout(()=>{e.scrollTo({left:t,behavior:"smooth"})},420),window.setTimeout(()=>{h=!1,d().forEach(e=>{v(e),w(e)}),b()},950))}),window.setTimeout(()=>{m=!0,d().forEach(v)},800),window.requestAnimationFrame(b),window.addEventListener("resize",b),window.addEventListener("plotline:timeline-layout-changed",b),n?.addEventListener("change",()=>window.requestAnimationFrame(b)),a?.addEventListener("change",()=>window.requestAnimationFrame(b))})(),y?.addEventListener("pointerdown",t=>{t.preventDefault(),document.body.classList.add("timeline-inspector-resizing"),y.setPointerCapture(t.pointerId);const n=t=>{const n=e.getBoundingClientRect().right-t.clientX;E(n)},a=()=>{document.body.classList.remove("timeline-inspector-resizing");const e=Math.round(v?.getBoundingClientRect().width||0);e>0&&localStorage.setItem(h,String(e)),window.dispatchEvent(new Event("plotline:timeline-layout-changed")),y.removeEventListener("pointermove",n),y.removeEventListener("pointerup",a),y.removeEventListener("pointercancel",a)};y.addEventListener("pointermove",n),y.addEventListener("pointerup",a),y.addEventListener("pointercancel",a)}),w?.addEventListener("click",e=>{e.preventDefault();const t=new URL(window.location.href),n=t.searchParams.get("FocusType")||t.searchParams.get("focusType");t.searchParams.delete("SelectedSceneID"),t.searchParams.delete("selectedSceneId"),t.searchParams.delete("selectedsceneid"),n&&"scene"!==n.toLowerCase()||(t.searchParams.delete("FocusType"),t.searchParams.delete("focusType"),t.searchParams.delete("FocusID"),t.searchParams.delete("focusId")),I(),window.location.href=t.toString()});const I=()=>{try{sessionStorage.setItem(p,JSON.stringify({path:window.location.pathname,top:window.scrollY,left:window.scrollX,workspaceLeft:t.scrollLeft,workspaceTop:t.scrollTop,containers:S().map((e,t)=>({index:t,left:e.scrollLeft,top:e.scrollTop}))}))}catch{}};window.PlotLineTimelineScroll={save:I};const x=(t,n,a)=>{a.forEach(n=>e.classList.remove(`${t}-${n}`)),e.classList.add(`${t}-${n}`)},C=e=>{const t=g.includes(e)?e:"comfortable";x("density",t,g),n&&(n.value=t),localStorage.setItem(c,t),window.requestAnimationFrame(A)},q=e=>{const t=f.includes(e)?e:"chapter";x("zoom",t,f),a&&(a.value=t),localStorage.setItem(d,t),window.requestAnimationFrame(A)},k=(t,n=!0)=>{const a=(t=>e.querySelector(`[data-scene-id='${CSS.escape(String(t))}']`))(t);a&&(a.classList.add("keyboard-focus"),a.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),n&&a.focus({preventScroll:!0}),window.setTimeout(()=>a.classList.remove("keyboard-focus"),1800))},A=()=>{e.querySelectorAll("[data-plot-line-svg-board]").forEach(e=>{const t=e.querySelector("[data-plot-line-svg]");if(!t)return;let n=e.querySelector("[data-plot-line-health-svg]");n||(n=document.createElementNS("http://www.w3.org/2000/svg","svg"),n.setAttribute("class","plot-lane-svg-overlay plot-lane-svg-health-overlay"),n.setAttribute("data-plot-line-health-svg",""),n.setAttribute("aria-hidden","true"),t.after(n)),t.replaceChildren(),n.replaceChildren();const a=e.getBoundingClientRect(),o=Math.max(e.scrollWidth,a.width),r=Math.max(e.scrollHeight,a.height);[t,n].forEach(e=>{e.setAttribute("viewBox",`0 0 ${o} ${r}`),e.setAttribute("width",String(o)),e.setAttribute("height",String(r))});const i=(e,t={})=>{const n=document.createElementNS("http://www.w3.org/2000/svg",e);return Object.entries(t).forEach(([e,t])=>n.setAttribute(e,String(t))),n},s=[...new Set([...e.querySelectorAll("[data-plot-line-slot]")].map(e=>{const t=e.getBoundingClientRect();return Math.round(t.left-a.left+t.width/2)}))].sort((e,t)=>e-t),l=Math.max(s.length-1,0),c=new Map;e.querySelectorAll("[data-plot-line-row]").forEach(e=>{e.dataset.plotLineId&&c.set(e.dataset.plotLineId,e)});const d=[...c.values()].map(e=>({row:e,plotLineId:e.dataset.plotLineId,colour:e.dataset.plotColour||"#2f6f63",points:[...e.querySelectorAll("[data-plot-event-node]")].map(e=>((e,t)=>{const n=t.closest("[data-plot-line-slot]");if(!n)return null;const o=n.getBoundingClientRect(),r=t.getBoundingClientRect();return{x:r.left-a.left+r.width/2,y:r.top-a.top+r.height/2,sceneX:o.left-a.left+o.width/2,radius:Math.max(r.width,r.height)/2,sceneIndex:0,type:(t.dataset.plotEventType||"Progress").toLowerCase(),targetPlotLineId:t.dataset.targetPlotLineId||"",targetPlotLineIds:(t.dataset.targetPlotLineIds||"").split(",").map(e=>e.trim()).filter(Boolean)}})(0,e)).filter(Boolean).map(e=>({...e,sceneIndex:Math.max(0,s.findIndex(t=>t===Math.round(e.sceneX)))})).sort((e,t)=>e.x-t.x),incomingStartXs:[]})),p=new Map(d.map(e=>[e.plotLineId,e])),u=(e,n,o,r)=>{if(!n)return void(window.console?.warn&&window.console.warn(`Plot line ${r} connector skipped because target plot line data is missing.`));e.x;const s=(e=>{const t=e.getBoundingClientRect();return t.top-a.top+t.height/2})(n),l=Math.max(28,Math.min(46,.28*Math.abs(s-e.y))),c=Math.max(8,e.radius||12),d=e.x+c,p=d+l,u=d+l/2;t.append(i("path",{class:`plot-line-svg-connector plot-line-svg-connector-${r}`,d:`M ${d} ${e.y} C ${u} ${e.y}, ${u} ${s}, ${p} ${s}`,stroke:o}))};d.forEach(e=>{e.points.forEach(e=>{"branch"===e.type&&e.targetPlotLineId&&p.get(e.targetPlotLineId)?.incomingStartXs.push(e.x),"split"===e.type&&e.targetPlotLineIds.forEach(t=>{p.get(t)?.incomingStartXs.push(e.x)})})}),d.forEach(e=>{e.health=(e=>{const t=e.some(e=>"resolve"===e.type),n=e.some(e=>"abandon"===e.type),a=e.find(e=>"resolve"===e.type),o=e.find(e=>"abandon"===e.type),r=e.some(e=>"merge"===e.type&&e.targetPlotLineId),i=e.some(e=>"split"===e.type&&e.targetPlotLineIds.length>=2),s=Boolean(a)&&e.some(e=>"branch"===e.type&&e.targetPlotLineId&&e.sceneIndex>=a.sceneIndex),c=e.reduce((e,t)=>Math.max(e,t.sceneIndex),0),d=!t&&!n&&l-c>=10,p=t&&!r&&!i&&!s;let u="active";return n?u="abandoned":t?u=p?"questionable":"resolved":d&&(u="neglected"),{state:u,isNeglected:d,isDangling:!t&&!n&&!r&&!i&&l>=c,isQuestionable:p,isAbandoned:n,resolvePoint:a,abandonPoint:o}})(e.points),e.state=e.health.state}),d.forEach(e=>{const{colour:n,points:a,incomingStartXs:o,state:r}=e;if(a.length>0){const s=a.find(e=>"start"===e.type),l=o.length?Math.min(...o):null,c=a.find(e=>"split"===e.type||"merge"===e.type),d=a[0],p=a[a.length-1],u=((null===l||s&&s.xe.x-t.x),m=c?h.indexOf(c):-1,g=m>=0?h.slice(0,m+1):h;null!==l&&l{const o=g[a+1];if(!o||o.x<=e.x)return;const s=Math.abs(o.sceneIndex-e.sceneIndex)>=f;t.append(i("path",{class:`plot-line-svg-path plot-line-svg-segment ${s?"plot-line-svg-segment-dormant":""} plotline-state-${r}`,d:`M ${e.x} ${e.y} L ${o.x} ${o.y}`,stroke:n}))})}}),d.forEach(({points:e,colour:t})=>{e.forEach(e=>{"branch"!==e.type&&"merge"!==e.type||u(e,c.get(e.targetPlotLineId),t,e.type),"split"===e.type&&e.targetPlotLineIds.length>=2?e.targetPlotLineIds.forEach(n=>{u(e,c.get(n),t,e.type)}):"split"===e.type&&e.targetPlotLineIds.length>0&&window.console?.warn&&window.console.warn("Plot line split connector skipped because fewer than two targets are available.")})}),d.forEach(e=>{const t=e.lineEndPoint||e.points[e.points.length-1],a=[];e.health?.isNeglected&&t&&a.push({point:t,kind:"neglected",label:"Dormant Thread",reason:"No activity for 10+ scenes.",action:"Add a Progress, Reveal or Resolve event."}),e.health?.isDangling&&t&&a.push({point:t,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."}),e.health?.isQuestionable&&e.health.resolvePoint&&a.push({point:e.health.resolvePoint,kind:"questionable",label:"Missing Closure",reason:"Resolved without merge, split, or branch.",action:"Verify this thread has sufficient payoff."}),e.health?.isAbandoned&&e.health.abandonPoint&&a.push({point:e.health.abandonPoint,kind:"abandoned",label:"Abandoned Thread",reason:"This plot line was abandoned.",action:"Check this is intentional."});const o=new Map;a.forEach(e=>{const t=`${Math.round(e.point.x)}:${Math.round(e.point.y)}`;o.has(t)||o.set(t,{point:e.point,warnings:[]}),o.get(t).warnings.push(e)}),o.forEach(({point:e,warnings:t})=>((e,t)=>{const a=Math.max(18,(e.radius||12)+8),o=t[0]?.kind||"warning",r=i("g",{class:`plotline-health-marker plotline-health-marker-${o}`}),s=i("title");s.textContent=(e=>e.map(e=>[`Warning: ${e.label}`,`Reason: ${e.reason}`,`Suggested Action: ${e.action}`].join("\n")).join("\n\n"))(t),r.append(s);const l=e.x+a,c=e.y-13;if("questionable"===o){r.append(i("circle",{cx:l,cy:c,r:9}));const e=i("text",{x:l,y:c+4,"text-anchor":"middle"});e.textContent=t.length>1?String(t.length):"?",r.append(e)}else if(r.append(i("circle",{cx:l,cy:c,r:10})),r.append(i("path",{d:`M ${l} ${c-6} L ${l+6} ${c+5} L ${l-6} ${c+5} Z`})),r.append(i("line",{x1:l,y1:c-2,x2:l,y2:c+2.5})),r.append(i("circle",{cx:l,cy:c+5.3,r:1.2})),t.length>1){const e=i("text",{x:l+11,y:c-6,"text-anchor":"middle",class:"plotline-health-count"});e.textContent=String(t.length),r.append(e)}n.append(r)})(e,t))})})},P=(e,t)=>{document.querySelectorAll(`[data-collapse-body='${CSS.escape(e)}']`).forEach(e=>{e.classList.toggle("timeline-section-collapsed",t)}),document.querySelectorAll(`[data-collapse-toggle='${CSS.escape(e)}']`).forEach(e=>{e.classList.toggle("is-collapsed",t),e.setAttribute("aria-expanded",String(!t))})};document.querySelectorAll("[data-collapse-toggle]").forEach(e=>{const t=e.dataset.collapseToggle;b||"collapsed"!==e.dataset.collapseDefault||L.add(t);const n=L.has(t);P(t,n),e.addEventListener("click",()=>{const e=!L.has(t);e?L.add(t):L.delete(t),P(t,e),localStorage.setItem(l,JSON.stringify([...L])),A(),window.dispatchEvent(new Event("plotline:timeline-layout-changed"))})}),n?.addEventListener("change",()=>C(n.value)),a?.addEventListener("change",()=>q(a.value)),C(localStorage.getItem(c)||"comfortable"),q(localStorage.getItem(d)||"chapter");const $=(()=>{try{return null!==sessionStorage.getItem(p)}catch{return!1}})();window.requestAnimationFrame(()=>{A(),((e="auto")=>{const t=r?.querySelector("[data-active-chapter='true']");if(!r||!t)return;const n=r.getBoundingClientRect(),a=t.getBoundingClientRect(),o=a.left+a.width/2,i=n.left+n.width/2;(a.leftn.right)&&r.scrollBy({left:o-i,behavior:e})})("auto"),$&&(()=>{let e=null;try{e=JSON.parse(sessionStorage.getItem(p)||"null"),sessionStorage.removeItem(p)}catch{try{sessionStorage.removeItem(p)}catch{}return}e&&e.path===window.location.pathname&&window.requestAnimationFrame(()=>{t.scrollLeft=Number(e.workspaceLeft)||0,t.scrollTop=Number(e.workspaceTop)||0;const n=S();(e.containers||[]).forEach(e=>{const t=n[e.index];t&&(t.scrollLeft=Number(e.left)||0,t.scrollTop=Number(e.top)||0)}),window.scrollTo(Number(e.left)||0,Number(e.top)||0)})})()}),window.addEventListener("resize",A),window.addEventListener("plotline:timeline-layout-changed",A);e.addEventListener("click",t=>{if(0!==t.button||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)return;const n=t.target instanceof Element?t.target:null,a=n?.closest("a[href]");if(!a||!e.contains(a))return;[...new URL(a.href,window.location.href).searchParams.keys()].some(e=>"selectedsceneid"===e.toLowerCase())&&I()},{capture:!0});const M=()=>{const e=o?.value.trim().toLowerCase()||"";let t=null;return s.forEach(n=>{const a=!e||(n.dataset.timelineSearchText||"").toLowerCase().includes(e);n.classList.toggle("search-hit",Boolean(e&&a)),n.classList.toggle("search-dim",Boolean(e&&!a)),!t&&e&&a&&(t=n)}),t};o?.addEventListener("input",M),o?.addEventListener("keydown",e=>{if("Enter"!==e.key)return;const t=M();t&&(e.preventDefault(),k(t.dataset.sceneId))}),i.forEach(e=>{e.addEventListener("change",()=>{e.value&&k(e.value)})}),document.querySelectorAll("[data-jump-scene]").forEach(e=>{e.addEventListener("click",()=>{e.dataset.jumpScene&&k(e.dataset.jumpScene)})}),document.addEventListener("keydown",e=>{const n=document.activeElement?.tagName?.toLowerCase(),r=["input","select","textarea"].includes(n)||document.activeElement?.isContentEditable;if((e.ctrlKey||e.metaKey)&&"f"===e.key.toLowerCase()&&o)return e.preventDefault(),o.focus(),void o.select();if("Escape"===e.key){const e=new URL(window.location.href),t=e.searchParams.has("FocusType")||e.searchParams.has("FocusID");return e.searchParams.delete("FocusType"),e.searchParams.delete("FocusID"),void(t?window.location.href=e.toString():o?.value&&(o.value="",M()))}if(!r){if("ArrowLeft"===e.key||"ArrowRight"===e.key)return e.preventDefault(),void t.scrollBy({left:"ArrowLeft"===e.key?-260:260,behavior:"smooth"});if("+"===e.key||"="===e.key||"-"===e.key){const t=f.indexOf(a?.value||"chapter"),n="-"===e.key?-1:1,o=Math.min(f.length-1,Math.max(0,t+n));q(f[o])}}})})(),(()=>{const e=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-drag-drop-panel]");if(!t)return;const n=new Map([...document.querySelectorAll("[data-drag-form]")].map(e=>[e.dataset.dragForm,e])),a=t.querySelector("[data-drag-panel-title]"),o=t.querySelector("[data-drag-panel-kicker]"),r=t.querySelector("[data-drag-panel-summary]"),i=document.querySelector("[data-scene-move-modal]"),s=i&&window.bootstrap?.Modal?window.bootstrap.Modal.getOrCreateInstance(i):null,l=i?.querySelector("[data-scene-move-title]"),c=i?.querySelector("[data-scene-move-summary]"),d=i?.querySelector("[data-scene-move-submit]"),p="plotline.timeline.scroll";let u=null,h=0;const m=(e,t,n)=>{const a=e?.querySelector(`[name='${CSS.escape(t)}']`);a&&(a.value=n??"")},g=e=>e?.selectedOptions?.[0]?.textContent?.trim()||"",f=e=>`“${e||"this scene"}”`,v=(e,t,n,a,o)=>t&&n&&t===n?`Move ${f(e.label)} to this new position within ${n}?`:t&&n?`Move ${f(e.label)} from ${t} to ${n}?`:`Move ${f(e.label)} ${a.toLowerCase()} ${o}?`,y=(e,i,p,u)=>{if("scene"===e)return t.classList.add("d-none"),t.querySelectorAll("[data-drag-form]").forEach(e=>e.classList.add("d-none")),n.get("scene")?.classList.remove("d-none"),l&&(l.textContent=i),c&&(c.textContent=p),d&&d.classList.toggle("d-none",i.toLowerCase().startsWith("cannot")||i.toLowerCase().startsWith("scene already")),void(s?s.show():t.classList.remove("d-none"));n.forEach((t,n)=>t.classList.toggle("d-none",n!==e)),t.classList.remove("d-none"),a.textContent=i,o.textContent=u,r.textContent=p,t.scrollIntoView({behavior:"smooth",block:"center"})},w=()=>{t.classList.add("d-none"),n.forEach(e=>e.classList.add("d-none"))},b=()=>{document.querySelectorAll(".drop-target-active, .drop-before, .drop-after, .chapter-drop-active, .drop-chapter-end").forEach(e=>e.classList.remove("drop-target-active","drop-before","drop-after","chapter-drop-active","drop-chapter-end"))};try{const t=JSON.parse(sessionStorage.getItem(p)||"null");t&&(sessionStorage.removeItem(p),requestAnimationFrame(()=>{const n=e?.querySelector(".timeline-shell");n&&(n.scrollLeft=t.left||0),window.scrollTo({top:t.top||0})}))}catch{sessionStorage.removeItem(p)}const L=e=>{try{const t=JSON.parse(e.dataTransfer.getData("application/json")||"{}");return t.type?t:u||{}}catch{return u||{}}};document.querySelectorAll("[data-drag-type][draggable='true']").forEach(e=>{e.addEventListener("dragstart",t=>((e,t)=>{const n={type:t.dataset.dragType,id:t.dataset.dragId,label:t.dataset.dragLabel||t.textContent.trim()};e.dataTransfer.effectAllowed="copyMove",e.dataTransfer.setData("application/json",JSON.stringify(n)),e.dataTransfer.setData("text/plain",`${n.type}:${n.id}`),u=n,document.body.classList.add("drag-active",`dragging-${n.type}`),t.classList.add("is-dragging")})(t,e)),e.addEventListener("dragend",()=>{e.classList.remove("is-dragging"),document.body.classList.remove("drag-active","dragging-scene","dragging-character","dragging-asset","dragging-location"),u=null,b()})}),t.querySelectorAll("[data-drag-cancel]").forEach(e=>e.addEventListener("click",w)),n.get("scene")?.addEventListener("submit",()=>{const t=e?.querySelector(".timeline-shell");try{sessionStorage.setItem(p,JSON.stringify({left:t?.scrollLeft||0,top:window.scrollY||0}))}catch{}}),e?.querySelectorAll(".timeline-scene-card[data-scene-id]").forEach(t=>{t.addEventListener("click",e=>{Date.now(){const n=L(e);["scene","character","asset"].includes(n.type)&&(e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="scene"===n.type?"move":"copy",t.classList.add("drop-target-active"),t.classList.toggle("drop-before","scene"===n.type&&e.offsetX=t.clientWidth/2))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active","drop-before","drop-after")),t.addEventListener("drop",a=>{const o=L(a);if(!["scene","character","asset"].includes(o.type))return;a.preventDefault(),a.stopPropagation(),h=Date.now()+800,t.classList.remove("drop-target-active","drop-before","drop-after");const r=t.dataset.sceneId;if("scene"===o.type){if(o.id===r)return void y("scene","Cannot move scene","Drop a scene before or after a different scene.","Scene move");const i=n.get("scene"),s=a.offsetX{const a=t.closest("[data-chapter-drop-target]"),o=t.dataset.chapterId||a?.dataset.chapterId,r=t.dataset.chapterLabel||a?.dataset.chapterLabel||"this chapter";t.addEventListener("dragover",e=>{"scene"===L(e).type&&o&&(e.target.closest(".timeline-scene-card[data-scene-id]")||(e.preventDefault(),e.dataTransfer.dropEffect="move",t.classList.add("drop-target-active","drop-chapter-end"),a?.classList.add("chapter-drop-active")))}),t.addEventListener("dragleave",e=>{t.contains(e.relatedTarget)||(t.classList.remove("drop-target-active","drop-chapter-end"),a?.classList.remove("chapter-drop-active"))}),t.addEventListener("drop",a=>{const i=L(a);if("scene"!==i.type||!o)return;if(a.target.closest(".timeline-scene-card[data-scene-id]"))return;a.preventDefault(),a.stopPropagation(),h=Date.now()+800,b();const s=e.querySelector(`.timeline-scene-card[data-scene-id='${CSS.escape(i.id)}']`);if(s?.dataset.chapterId===o&&t.lastElementChild?.dataset?.sceneId===i.id)return void y("scene","Scene already here",`${i.label} is already at the end of ${r}.`,"Scene move");const l=n.get("scene");m(l,"SceneID",i.id),m(l,"AnchorSceneID",""),m(l,"TargetChapterID",o),m(l,"Position","End"),y("scene","Move Scene?",v(i,s?.dataset.chapterLabel||"",r,"After",r),"Scene move")})});const S=n.get("asset-dependency"),E=()=>{const e=S?.dataset.sourceLabel||"Source asset",n=S?.dataset.targetLabel||"Target asset",a=g(S?.querySelector("[name='AssetDependencyTypeID']"))||"Requires",o=g(S?.querySelector("[name='SourceRequiredStateID']"))||"the required state",r=g(S?.querySelector("[name='TargetBlockedStateID']"))||"the blocked state",i=t.querySelector("[data-asset-dependency-preview]");i&&(i.textContent=`${e} ${a.toLowerCase()} ${n}. ${e} should be ${o} before ${n} can be ${r}.`)};S?.querySelectorAll("select").forEach(e=>e.addEventListener("change",E)),document.querySelectorAll("[data-asset-drop-target], .drag-chip.asset").forEach(e=>{e.addEventListener("dragover",t=>{"asset"===L(t).type&&(t.preventDefault(),t.dataTransfer.dropEffect="copy",e.classList.add("drop-target-active"))}),e.addEventListener("dragleave",()=>e.classList.remove("drop-target-active")),e.addEventListener("drop",t=>{const n=L(t),a=e.dataset.assetId||e.dataset.dragId,o=e.dataset.assetLabel||e.dataset.dragLabel||e.textContent.trim();"asset"===n.type&&a&&(t.preventDefault(),e.classList.remove("drop-target-active"),n.id!==a?(m(S,"SourceAssetID",n.id),m(S,"TargetAssetID",a),S.dataset.sourceLabel=n.label,S.dataset.targetLabel=o,E(),y("asset-dependency","Create asset dependency",`${n.label} -> ${o}`,"Asset dependency")):y("asset-dependency","Cannot create dependency","Drop one asset onto a different asset.","Asset dependency"))})}),document.querySelectorAll("[data-drag-type='location']").forEach(e=>{e.addEventListener("dragover",t=>{"location"===L(t).type&&(t.preventDefault(),t.dataTransfer.dropEffect="copy",e.classList.add("drop-target-active"))}),e.addEventListener("dragleave",()=>e.classList.remove("drop-target-active")),e.addEventListener("drop",t=>{const a=L(t),o=e.dataset.dragId,r=e.dataset.dragLabel||e.textContent.trim();if("location"!==a.type)return;if(t.preventDefault(),e.classList.remove("drop-target-active"),a.id===o)return void y("location-relationship","Cannot create relationship","Drop one location onto a different location.","Location relationship");const i=n.get("location-relationship");m(i,"FromLocationID",a.id),m(i,"ToLocationID",o),y("location-relationship","Create location relationship",`${a.label} -> ${r}`,"Location drop")})})})(); \ No newline at end of file diff --git a/PlotLine/wwwroot/js/story-calendar.js b/PlotLine/wwwroot/js/story-calendar.js new file mode 100644 index 0000000..8acada6 --- /dev/null +++ b/PlotLine/wwwroot/js/story-calendar.js @@ -0,0 +1,140 @@ +(() => { + const rootSelector = "[data-story-calendar-root]"; + const daySelector = "[data-story-calendar-day]"; + const navSelector = "[data-story-calendar-nav]"; + const filterSelector = "[data-story-calendar-filter]"; + + const calendarRoot = () => document.querySelector(rootSelector); + const calendarDetail = () => calendarRoot()?.querySelector("[data-story-calendar-detail]"); + + const eventElement = (event) => event.target instanceof Element ? event.target : null; + + const isPlainPrimaryClick = (event) => + event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey; + + const clearCalendarNotice = () => { + calendarRoot()?.querySelectorAll("[data-story-calendar-notice]").forEach((notice) => notice.remove()); + }; + + const showCalendarNotice = (message, state) => { + clearCalendarNotice(); + const target = calendarDetail() || calendarRoot(); + if (!target) { + return; + } + + const notice = document.createElement("p"); + notice.className = `story-calendar-detail-notice ${state === "error" ? "is-error" : ""}`.trim(); + notice.dataset.storyCalendarNotice = "true"; + notice.textContent = message; + target.prepend(notice); + }; + + const loadCalendarSurface = async (url, historyMode = "push") => { + const root = calendarRoot(); + if (!root || !window.fetch) { + return false; + } + + root.setAttribute("aria-busy", "true"); + showCalendarNotice("Loading...", "loading"); + + try { + const response = await fetch(url, { + headers: { "X-Requested-With": "XMLHttpRequest" }, + credentials: "same-origin" + }); + + if (!response.ok) { + throw new Error("Calendar load failed"); + } + + root.innerHTML = await response.text(); + root.removeAttribute("aria-busy"); + + if (window.history?.pushState && historyMode !== "none") { + const nextUrl = new URL(url, window.location.href); + const state = { storyCalendarUrl: nextUrl.toString() }; + if (historyMode === "replace") { + window.history.replaceState(state, "", nextUrl.toString()); + } else { + window.history.pushState(state, "", nextUrl.toString()); + } + } + + return true; + } catch { + root.removeAttribute("aria-busy"); + showCalendarNotice("The calendar could not be loaded. Use the link or Apply button to retry.", "error"); + return false; + } + }; + + const buildFilterUrl = (form) => { + const formData = new FormData(form); + const url = new URL(form.dataset.storyCalendarIndexUrl || form.action || window.location.href, window.location.href); + url.search = ""; + + for (const [key, value] of formData.entries()) { + if (key === "__RequestVerificationToken" || value === "") { + continue; + } + + url.searchParams.set(key, value); + } + + return url.toString(); + }; + + const initialise = () => { + if (!calendarRoot() || !window.fetch) { + return; + } + + window.history?.replaceState?.({ storyCalendarUrl: window.location.href }, "", window.location.href); + + document.addEventListener("click", async (event) => { + if (!isPlainPrimaryClick(event)) { + return; + } + + const link = eventElement(event)?.closest(`${daySelector}, ${navSelector}`); + if (!link || !link.isConnected || !calendarRoot()?.contains(link)) { + return; + } + + event.preventDefault(); + await loadCalendarSurface(link.href, "push"); + }, true); + + document.addEventListener("submit", async (event) => { + const form = eventElement(event)?.closest(filterSelector); + if (!form || !form.isConnected) { + return; + } + + event.preventDefault(); + await loadCalendarSurface(buildFilterUrl(form), "push"); + }, true); + + document.addEventListener("change", async (event) => { + const select = eventElement(event)?.closest(`${filterSelector} select`); + const form = select?.closest(filterSelector); + if (!select || !form) { + return; + } + + await loadCalendarSurface(buildFilterUrl(form), "push"); + }, true); + + window.addEventListener("popstate", async () => { + await loadCalendarSurface(window.location.href, "none"); + }); + }; + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initialise, { once: true }); + } else { + initialise(); + } +})();