From 59c56309506884a9e3af534e09cbf797dce34536 Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Thu, 18 Jun 2026 21:16:49 +0100 Subject: [PATCH] Review all Save, Add, Update and Delete actions used by the Scene Inspector. --- PlotLine/Controllers/ScenesController.cs | 121 ++++++++++++++------- PlotLine/Controllers/WarningsController.cs | 24 ++++ PlotLine/ViewModels/CoreViewModels.cs | 1 + PlotLine/Views/Scenes/MovePreview.cshtml | 20 +++- PlotLine/wwwroot/js/site.js | 28 +++++ PlotLine/wwwroot/js/site.min.js | 2 +- 6 files changed, 149 insertions(+), 47 deletions(-) diff --git a/PlotLine/Controllers/ScenesController.cs b/PlotLine/Controllers/ScenesController.cs index eafc316..c45df15 100644 --- a/PlotLine/Controllers/ScenesController.cs +++ b/PlotLine/Controllers/ScenesController.cs @@ -66,6 +66,11 @@ public sealed class ScenesController( return View("Edit", createModel); } + if (TryGetLocalReturnUrl(out var returnUrl)) + { + return LocalRedirect(returnUrl); + } + if (model.ReturnToTimeline && model.ReturnProjectID.HasValue) { return RedirectToAction("Index", "Timeline", new @@ -85,6 +90,11 @@ public sealed class ScenesController( { await scenes.ArchiveAsync(id); TempData["ArchiveMessage"] = "Scene archived. You can restore it from Archived Items."; + if (TryGetLocalReturnUrl(out var returnUrl)) + { + return LocalRedirect(RemoveSelectedScene(returnUrl)); + } + return RedirectToAction("Details", "Chapters", new { id = chapterId }); } @@ -141,12 +151,7 @@ public sealed class ScenesController( public async Task DeleteThreadEvent(int id, int sceneId, int? projectId, int? bookId) { await plots.DeleteThreadEventAsync(id); - if (projectId.HasValue) - { - return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId }); - } - - return RedirectToAction(nameof(Edit), new { id = sceneId }); + return RedirectForScene(sceneId, projectId, bookId); } [HttpPost] @@ -154,17 +159,7 @@ public sealed class ScenesController( public async Task AddAssetEvent(AssetEventCreateViewModel model) { await assets.AddAssetEventAsync(model); - if (model.ReturnProjectID.HasValue) - { - return RedirectToAction("Index", "Timeline", new - { - projectId = model.ReturnProjectID.Value, - bookId = model.ReturnBookID, - selectedSceneId = model.SceneID - }); - } - - return RedirectToAction(nameof(Edit), new { id = model.SceneID }); + return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID); } [HttpPost] @@ -172,12 +167,7 @@ public sealed class ScenesController( public async Task DeleteAssetEvent(int id, int sceneId, int? projectId, int? bookId) { await assets.DeleteAssetEventAsync(id); - if (projectId.HasValue) - { - return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId }); - } - - return RedirectToAction(nameof(Edit), new { id = sceneId }); + return RedirectForScene(sceneId, projectId, bookId); } [HttpPost] @@ -185,17 +175,7 @@ public sealed class ScenesController( public async Task AddAssetCustodyEvent(AssetCustodyCreateViewModel model) { await assets.AddCustodyEventAsync(model); - if (model.ReturnProjectID.HasValue) - { - return RedirectToAction("Index", "Timeline", new - { - projectId = model.ReturnProjectID.Value, - bookId = model.ReturnBookID, - selectedSceneId = model.SceneID - }); - } - - return RedirectToAction(nameof(Edit), new { id = model.SceneID }); + return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID); } [HttpPost] @@ -219,12 +199,7 @@ public sealed class ScenesController( public async Task DeleteAssetCustodyEvent(int id, int sceneId, int? projectId, int? bookId) { await assets.DeleteCustodyEventAsync(id); - if (projectId.HasValue) - { - return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId }); - } - - return RedirectToAction(nameof(Edit), new { id = sceneId }); + return RedirectForScene(sceneId, projectId, bookId); } [HttpPost] @@ -279,6 +254,7 @@ public sealed class ScenesController( [ValidateAntiForgeryToken] public async Task PreviewMove(SceneMoveRequestViewModel model) { + model.ReturnUrl = GetLocalReturnUrl(); var preview = await scenes.GetMovePreviewAsync(model); return preview is null ? NotFound() : View("MovePreview", preview); } @@ -288,6 +264,11 @@ public sealed class ScenesController( public async Task ConfirmMove(SceneMoveRequestViewModel model) { await scenes.MoveToChapterAsync(model); + if (TryGetLocalReturnUrl(model.ReturnUrl, out var returnUrl)) + { + return LocalRedirect(returnUrl); + } + return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID); } @@ -475,6 +456,11 @@ public sealed class ScenesController( private IActionResult RedirectForScene(int sceneId, int? projectId, int? bookId) { + if (TryGetLocalReturnUrl(out var returnUrl)) + { + return LocalRedirect(returnUrl); + } + if (projectId.HasValue) { return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId }); @@ -483,6 +469,61 @@ public sealed class ScenesController( return RedirectToAction(nameof(Edit), new { id = sceneId }); } + private string? GetLocalReturnUrl(string? returnUrl = null) + { + if (string.IsNullOrWhiteSpace(returnUrl) + && Request.HasFormContentType + && Request.Form.TryGetValue("ReturnUrl", out var postedReturnUrl)) + { + returnUrl = postedReturnUrl.ToString(); + } + + return !string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl) + ? returnUrl + : null; + } + + private bool TryGetLocalReturnUrl(out string returnUrl) => + TryGetLocalReturnUrl(null, out returnUrl); + + private bool TryGetLocalReturnUrl(string? candidate, out string returnUrl) + { + var localReturnUrl = GetLocalReturnUrl(candidate); + if (!string.IsNullOrWhiteSpace(localReturnUrl)) + { + returnUrl = localReturnUrl; + return true; + } + + returnUrl = string.Empty; + return false; + } + + private static string RemoveSelectedScene(string returnUrl) + { + var hashIndex = returnUrl.IndexOf('#'); + var hash = hashIndex >= 0 ? returnUrl[hashIndex..] : string.Empty; + var withoutHash = hashIndex >= 0 ? returnUrl[..hashIndex] : returnUrl; + var queryIndex = withoutHash.IndexOf('?'); + if (queryIndex < 0) + { + return returnUrl; + } + + var path = withoutHash[..queryIndex]; + var query = withoutHash[(queryIndex + 1)..] + .Split('&', StringSplitOptions.RemoveEmptyEntries) + .Where(part => + { + var equalsIndex = part.IndexOf('='); + var key = equalsIndex >= 0 ? part[..equalsIndex] : part; + return !string.Equals(Uri.UnescapeDataString(key), "selectedSceneId", StringComparison.OrdinalIgnoreCase); + }) + .ToList(); + + return query.Count == 0 ? path + hash : $"{path}?{string.Join("&", query)}{hash}"; + } + private static void CopyPostedSceneFields(SceneEditViewModel source, SceneEditViewModel target) { target.SceneTitle = source.SceneTitle; diff --git a/PlotLine/Controllers/WarningsController.cs b/PlotLine/Controllers/WarningsController.cs index ad42b12..134cdb6 100644 --- a/PlotLine/Controllers/WarningsController.cs +++ b/PlotLine/Controllers/WarningsController.cs @@ -49,6 +49,11 @@ public sealed class WarningsController(IContinuityValidationService validation) public async Task ValidateScene(int projectId, int? bookId, int sceneId, bool returnToTimeline = false) { await validation.ValidateSceneAsync(sceneId); + if (TryGetLocalReturnUrl(out var returnUrl)) + { + return LocalRedirect(returnUrl); + } + if (returnToTimeline) { return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId }); @@ -137,6 +142,11 @@ public sealed class WarningsController(IContinuityValidationService validation) private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline) { + if (TryGetLocalReturnUrl(out var returnUrl)) + { + return LocalRedirect(returnUrl); + } + if (returnToTimeline && sceneId.HasValue) { return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId.Value }); @@ -145,6 +155,20 @@ public sealed class WarningsController(IContinuityValidationService validation) return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId }); } + private bool TryGetLocalReturnUrl(out string returnUrl) + { + if (Request.HasFormContentType + && Request.Form.TryGetValue("ReturnUrl", out var postedReturnUrl) + && Url.IsLocalUrl(postedReturnUrl.ToString())) + { + returnUrl = postedReturnUrl.ToString(); + return true; + } + + returnUrl = string.Empty; + return false; + } + private IActionResult RedirectToDashboard(WarningDashboardReturnFilter filter) { return RedirectToAction(nameof(Index), new diff --git a/PlotLine/ViewModels/CoreViewModels.cs b/PlotLine/ViewModels/CoreViewModels.cs index 1fbe22b..4c6683f 100644 --- a/PlotLine/ViewModels/CoreViewModels.cs +++ b/PlotLine/ViewModels/CoreViewModels.cs @@ -1917,6 +1917,7 @@ public sealed class SceneMoveRequestViewModel public bool RenumberScenes { get; set; } public int? ReturnProjectID { get; set; } public int? ReturnBookID { get; set; } + public string? ReturnUrl { get; set; } } public sealed class SceneMovePreviewViewModel diff --git a/PlotLine/Views/Scenes/MovePreview.cshtml b/PlotLine/Views/Scenes/MovePreview.cshtml index 3d82f4e..d729d05 100644 --- a/PlotLine/Views/Scenes/MovePreview.cshtml +++ b/PlotLine/Views/Scenes/MovePreview.cshtml @@ -55,13 +55,21 @@ + - Cancel + @if (!string.IsNullOrWhiteSpace(Model.Request.ReturnUrl)) + { + Cancel + } + else + { + Cancel + } diff --git a/PlotLine/wwwroot/js/site.js b/PlotLine/wwwroot/js/site.js index 3faaa22..3a3bcb3 100644 --- a/PlotLine/wwwroot/js/site.js +++ b/PlotLine/wwwroot/js/site.js @@ -63,6 +63,34 @@ }); })(); +(() => { + const inspectorRoots = document.querySelectorAll(".scene-inspector-root"); + if (!inspectorRoots.length) { + return; + } + + const currentReturnUrl = () => `${window.location.pathname}${window.location.search}${window.location.hash}`; + + inspectorRoots.forEach((root) => { + root.addEventListener("submit", (event) => { + const form = event.target; + if (!(form instanceof HTMLFormElement)) { + return; + } + + let returnUrlInput = form.querySelector("input[name='ReturnUrl']"); + if (!returnUrlInput) { + returnUrlInput = document.createElement("input"); + returnUrlInput.type = "hidden"; + returnUrlInput.name = "ReturnUrl"; + form.appendChild(returnUrlInput); + } + + returnUrlInput.value = currentReturnUrl(); + }, true); + }); +})(); + (() => { const modalElement = document.getElementById("plotlineConfirmModal"); const modal = modalElement && window.bootstrap ? new bootstrap.Modal(modalElement) : null; diff --git a/PlotLine/wwwroot/js/site.min.js b/PlotLine/wwwroot/js/site.min.js index e9c2719..03b087d 100644 --- a/PlotLine/wwwroot/js/site.min.js +++ b/PlotLine/wwwroot/js/site.min.js @@ -1 +1 @@ -(()=>{const n="plotline.theme",t=document.querySelectorAll("[data-theme-choice]"),r=()=>{try{return localStorage.getItem(n)}catch{return null}},u=t=>{try{localStorage.setItem(n,t)}catch{}},i=n=>{const i=n==="dark"?"dark":"light";document.documentElement.dataset.theme=i;document.documentElement.dataset.bsTheme=i;u(i);t.forEach(n=>{const t=n.dataset.themeChoice===i;n.classList.toggle("active",t);n.setAttribute("aria-pressed",String(t))})};t.forEach(n=>{n.addEventListener("click",()=>i(n.dataset.themeChoice))});i(r()||"light")})();(()=>{const t=document.getElementById("plotlineConfirmModal"),f=t&&window.bootstrap?new bootstrap.Modal(t):null,e=t?.querySelector("#plotlineConfirmModalTitle"),o=t?.querySelector("#plotlineConfirmModalMessage"),i=t?.querySelector("[data-confirm-submit]"),s=t?.querySelector("[data-delete-confirm-field]"),r=t?.querySelector("[data-delete-confirm-input]");let n=null,u=null;const h=n=>{const t=n.split(/\n\s*\n/);return{title:t.shift()?.trim()||"Confirm action",body:t.join("\n\n").trim()}},c=()=>{n=null,u=null,r&&(r.value=""),s?.classList.add("d-none"),i?.removeAttribute("disabled")};t?.addEventListener("hidden.bs.modal",c);r?.addEventListener("input",()=>{n?.dataset.requireDeleteText==="true"&&i&&(i.disabled=r.value.trim()!=="DELETE")});i?.addEventListener("click",()=>{if(n){if(n.dataset.requireDeleteText==="true"){const t=n.querySelector("[data-delete-confirm-target], [name='confirmationText']");t&&(t.value=r?.value.trim()||"")}n.dataset.confirmed="true";f?.hide();n.requestSubmit&&u?n.requestSubmit(u):n.requestSubmit?n.requestSubmit():n.submit()}});document.addEventListener("submit",c=>{const l=c.target;if(l instanceof HTMLFormElement&&l.dataset.confirmed!=="true"&&l.dataset.confirmSkip!=="true"&&l.dataset.noDeleteConfirm!=="true"){const b=l.dataset.confirmMessage,k=c.submitter instanceof HTMLElement?c.submitter:l.querySelector("button[type='submit']"),a=k?.textContent||"",p=l.getAttribute("action")||"",y=/restore/i.test(a)||/\/restore\b/i.test(p),v=/delete forever/i.test(a)||/\/deleteforever\b/i.test(p),d=!y&&!v&&(/archive/i.test(a)||/\/archive\b/i.test(p)),nt=!v&&/(delete|remove)/i.test(a);if(b||d||y||nt||v){const tt=b||(d?"Archive this item?\n\nThis will hide it from active lists and timelines, but the data will be kept and can be restored later.":y?"Restore this item?\n\nThis will return it to active lists and timelines where applicable.":v?"Permanently delete this item?\n\nThis cannot be undone. Any linked planning data may also be removed or become unavailable. Only continue if you are certain.":"Remove this item?\n\nThis removes the link or supporting record. Major story items are archived instead of permanently deleted.");if(f&&t&&e&&o&&i){c.preventDefault();const g=h(tt);n=l;u=k;e.textContent=l.dataset.confirmTitle||g.title;o.textContent=l.dataset.confirmBody||g.body;i.textContent=l.dataset.confirmButtonText||a.trim()||"Confirm";i.className=`btn ${l.dataset.confirmButtonClass||(y?"btn-primary":"btn-danger")}`;const w=l.dataset.requireDeleteText==="true"||v;s?.classList.toggle("d-none",!w);w&&i&&(i.disabled=!0,r?.focus());f.show();w&&window.setTimeout(()=>r?.focus(),200)}}}})})();(()=>{const r=document.querySelectorAll("[data-help-icon]");if(r.length&&window.bootstrap){const u=new Map;let n=null;const f=n=>n.querySelector("[data-help-tooltip]")||n,i=n=>bootstrap.Tooltip.getInstance(f(n))?.hide(),t=(t=null)=>{n&&n!==t&&bootstrap.Popover.getInstance(n)?.hide()},e=()=>r.forEach(n=>i(n));r.forEach(r=>{const o=f(r),s=o.dataset.helpMicro||"Help article not found.",h=r.dataset.helpTitle||"Help unavailable",c=r.dataset.helpSummary||"Help content has not yet been written for this feature.";bootstrap.Tooltip.getOrCreateInstance(o,{title:s,trigger:"hover focus",placement:"top",container:"body"});const l=bootstrap.Popover.getOrCreateInstance(r,{title:h,trigger:"click",placement:"auto",container:"body",html:!0,customClass:"plotline-help-popover",content(){const u=document.createElement("div"),i=document.createElement("p");i.className="mb-2";i.textContent=c;const n=document.createElement("a");return n.href="#",n.textContent="More...",n.className="plotline-help-more",n.addEventListener("click",n=>{n.preventDefault(),t(),e(),r.dispatchEvent(new CustomEvent("plotline:help-more",{bubbles:!0,detail:{key:r.dataset.helpKey||""}}))}),u.append(i,n),u}});u.set(r,l);r.addEventListener("click",n=>{window.PlotLineHelpDrawer?.isActive?.()&&(n.preventDefault(),n.stopPropagation(),t(),i(r),window.PlotLineHelpPopovers?.hideAll(),window.PlotLineHelpDrawer.open(r.dataset.helpKey||""))});r.addEventListener("show.bs.popover",n=>{if(window.PlotLineHelpDrawer?.isActive?.()){n.preventDefault();return}t(r);i(r)});r.addEventListener("shown.bs.popover",()=>{n=r,i(r)});r.addEventListener("hidden.bs.popover",()=>{n===r&&(n=null)})});document.addEventListener("click",i=>{const r=i.target instanceof Element?i.target:null;if(r&&n&&!r.closest("[data-help-icon]")){const u=document.querySelector(`#${n.getAttribute("aria-describedby")}`);u?.contains(r)||t()}});document.addEventListener("keydown",n=>{n.key==="Escape"&&t()});window.PlotLineHelpPopovers={hideAll(){u.forEach(n=>n.hide());e();n=null}}}})();(()=>{const n=document.querySelector("[data-help-layout]"),p=document.querySelector("[data-help-drawer]"),i=document.querySelector("[data-help-content]"),l=document.querySelector("[data-help-splitter]"),w=document.querySelector("[data-help-close]"),h=document.querySelector("[data-help-collapse]"),a=document.querySelector("[data-help-expand]"),u=document.querySelector("[data-help-pin]"),f=document.querySelector("[data-help-drawer-heading]");if(n&&p&&i){const t={width:"plotline.helpDrawer.width",pinned:"plotline.helpDrawer.pinned",key:"plotline.helpDrawer.key",collapsed:"plotline.helpDrawer.collapsed"},b=(n,t,i)=>Math.min(i,Math.max(t,n)),e=n=>{try{return localStorage.getItem(n)==="true"}catch{return!1}},o=(n,t)=>{try{localStorage.setItem(n,t)}catch{}},v=i=>{const r=`${b(Number.parseFloat(i)||25,20,50)}%`;n.style.setProperty("--help-drawer-width",r);o(t.width,r)},c=n=>{o(t.pinned,String(n)),u?.setAttribute("aria-pressed",String(n)),u&&(u.textContent=n?"Unpin":"Pin")},r=i=>{n.classList.toggle("help-collapsed",i),o(t.collapsed,String(i)),h&&(h.textContent=i?"Expand":"Collapse")},s=async(u,e={})=>{const s=u||"";n.classList.add("help-open");e.keepCollapsed||r(!1);o(t.key,s);f&&(f.textContent=s||"Help");i.innerHTML='

Loading help...<\/p>';try{const n=await fetch(`/HelpDrawer/Article?key=${encodeURIComponent(s)}`,{headers:{"X-Requested-With":"XMLHttpRequest"}});i.innerHTML=n.ok?await n.text():'

Help for this feature has not yet been written.<\/p>';const t=i.querySelector(".help-drawer-article h2")?.textContent?.trim();f&&t&&(f.textContent=t)}catch{i.innerHTML='

Help for this feature has not yet been written.<\/p>'}},k=()=>{n.classList.remove("help-open","help-collapsed"),c(!1)};window.PlotLineHelpDrawer={isActive(){return n.classList.contains("help-open")},open(n){return s(n)}};v(localStorage.getItem(t.width)||"25%");c(e(t.pinned));r(e(t.collapsed));document.addEventListener("plotline:help-more",n=>{const t=n.detail?.key||n.target?.dataset?.helpKey||"";window.PlotLineHelpPopovers?.hideAll();s(t)});document.addEventListener("click",n=>{const t=n.target instanceof Element?n.target.closest("[data-help-open]"):null;t&&(n.preventDefault(),window.PlotLineHelpPopovers?.hideAll(),s(t.dataset.helpOpen||""))});w?.addEventListener("click",k);h?.addEventListener("click",()=>r(!n.classList.contains("help-collapsed")));a?.addEventListener("click",()=>r(!1));a?.addEventListener("keydown",n=>{(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),r(!1))});u?.addEventListener("click",()=>c(!e(t.pinned)));l?.addEventListener("pointerdown",t=>{if(n.classList.contains("help-open")&&!n.classList.contains("help-collapsed")){t.preventDefault();document.body.classList.add("help-resizing");l.setPointerCapture?.(t.pointerId);const i=t=>{const i=n.getBoundingClientRect(),r=(i.right-t.clientX)/i.width*100;v(r)},r=()=>{document.body.classList.remove("help-resizing"),document.removeEventListener("pointermove",i),document.removeEventListener("pointerup",r)};document.addEventListener("pointermove",i);document.addEventListener("pointerup",r)}});const y=(()=>{try{return localStorage.getItem(t.key)||""}catch{return""}})();e(t.pinned)&&y&&s(y,{keepCollapsed:!0})}})();(()=>{const n=document.querySelectorAll(".scene-inspector-root");if(n.length){const t=n=>n.toLowerCase().replace(/&/g,"and").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");n.forEach(n=>{const o=n.dataset.sceneId||n.querySelector(".scene-inspector-form [name='SceneID']")?.value||"new",f=`plotline.inspector.${o}.`,s=new Set(["overview","timing","characters"]),u=[...n.querySelectorAll(".inspector-section")],r=n.querySelector(".scene-inspector-form"),i=n.querySelector("[data-scene-time-editor]");if(r&&i&&i.dataset.enhanced!=="true"){i.dataset.enhanced="true";const n=r.querySelector("[name='TimeModeID']"),e=i.dataset.exactDatetimeModeId||"",o=i.dataset.exactDateModeId||"",s=[...i.querySelectorAll("[data-time-part-field]")],h={StartDateTime:i.querySelector("[data-date-part-for='StartDateTime']"),EndDateTime:i.querySelector("[data-date-part-for='EndDateTime']")},c={StartDateTime:i.querySelector("[data-time-part-for='StartDateTime']"),EndDateTime:i.querySelector("[data-time-part-for='EndDateTime']")},l={StartDateTime:r.querySelector("input[type='hidden'][name='StartDateTime']"),EndDateTime:r.querySelector("input[type='hidden'][name='EndDateTime']")},t=()=>n?.value===e,a=()=>n?.value===o,u=()=>{const n=t();s.forEach(t=>{t.hidden=!n,t.classList.toggle("d-none",!n)})},f=n=>{const i=l[n],r=h[n]?.value||"",f=c[n]?.value||"";if(i){if(!r){i.value="";return}if(t()){i.value=`${r}T${f||"00:00"}`;return}if(a()){i.value=r;return}const u=i.value.match(/^\\d{4}-\\d{2}-\\d{2}T(\\d{2}:\\d{2})/)?.[1];i.value=u?`${r}T${u}`:r}},v=()=>{f("StartDateTime"),f("EndDateTime")};n?.addEventListener("change",u);r.addEventListener("submit",v);u()}u.forEach(n=>{if(n.dataset.enhanced!=="true"){const l=n.querySelector("h3, h2"),u=n.dataset.sectionTitle||l?.textContent?.trim()||"Overview",a=t(u),o=n.dataset.sectionId||`inspector-${a}`,e=o.replace(/^inspector-/,"");n.id=o;n.dataset.searchText=`${u} ${n.textContent}`.toLowerCase();n.dataset.enhanced="true";const r=document.createElement("div");for(r.className="inspector-accordion-body";n.firstChild;)r.appendChild(n.firstChild);const i=document.createElement("button");i.type="button";i.className="inspector-accordion-toggle";i.setAttribute("aria-expanded","false");n.append(i,r);const v=r.querySelectorAll(".asset-event-item, .thread-event-item, .warning-mini-card, .dependency-card, .metric-slider").length;i.innerHTML=`${u}${v||""}`;const h=localStorage.getItem(`${f}${e}`),c=h===null?s.has(e):h==="open";n.classList.toggle("is-open",c);i.setAttribute("aria-expanded",String(c));i.addEventListener("click",()=>{const t=!n.classList.contains("is-open");n.classList.toggle("is-open",t);i.setAttribute("aria-expanded",String(t));localStorage.setItem(`${f}${e}`,t?"open":"closed")})}});n.querySelectorAll(".inspector-nav-links a").forEach(t=>{t.addEventListener("click",i=>{const r=n.querySelector(t.getAttribute("href"));r&&(i.preventDefault(),r.classList.add("is-open"),r.querySelector(".inspector-accordion-toggle")?.setAttribute("aria-expanded","true"),r.scrollIntoView({behavior:"smooth",block:"start"}))})});const e=n.querySelector("[data-inspector-search]");if(e?.addEventListener("input",()=>{const n=e.value.trim().toLowerCase();u.forEach(t=>{const i=!n||t.dataset.searchText.includes(n);t.classList.toggle("is-filtered-out",!i);i&&n&&(t.classList.add("is-open"),t.querySelector(".inspector-accordion-toggle")?.setAttribute("aria-expanded","true"))})}),"IntersectionObserver"in window){const t=[...n.querySelectorAll(".inspector-nav-links a")],i=new IntersectionObserver(n=>{n.forEach(n=>{n.isIntersecting&&t.forEach(t=>{t.classList.toggle("active",t.getAttribute("href")===`#${n.target.id}`)})})},{root:n.closest(".scene-inspector-panel")||null,rootMargin:"-10% 0px -70% 0px"});u.forEach(n=>i.observe(n))}})}})();(()=>{const n=document.querySelectorAll("[data-metric-shape-chart]");if(n.length){const t=["#2f6f63","#b35f3b","#6750a4","#c58b2b","#7d3f52","#33759b","#9c4d8f","#a33d3d","#6f8f3d","#d08a2f","#444b57"],u=new Set(["Overall Intensity","Tension","Emotional Weight","Action"]),i=[],r=()=>{window.requestAnimationFrame(()=>{i.forEach(n=>n.resize())})};window.PlotLineMetricCharts={resizeAll:r};window.addEventListener("plotline:timeline-layout-changed",r);const f={id:"plotlineChapterGuides",afterDraw(n){const u=n.config.options.plugins.plotlineScenes||[],{ctx:t,chartArea:r,scales:f}=n;if(u.length&&f.x){t.save();t.strokeStyle="rgba(47, 111, 99, 0.16)";t.fillStyle="rgba(82, 75, 65, 0.58)";t.lineWidth=1;t.font="700 10px system-ui, -apple-system, Segoe UI, sans-serif";const e=[];u.forEach((n,t)=>{t&&n.chapterLabel===u[t-1].chapterLabel||e.push({scene:n,index:t})});const i=e.filter(n=>!n.scene.isPlaceholder),o=i.length>40?5:i.length>24?4:i.length>14?2:1;i.forEach((n,u)=>{const{scene:s,index:c}=n,e=f.x.getPixelForValue(c+.5);t.beginPath();t.moveTo(e,r.top);t.lineTo(e,r.bottom);t.stroke();const h=Number.parseInt(String(s.chapterLabel).replace(/[^0-9]/g,""),10),l=u===0||u===i.length-1||o===1||Number.isFinite(h)&&h%o==0;l&&t.fillText(s.chapterLabel,Math.min(e+4,r.right-70),r.bottom+22)});t.restore()}}};window.Chart&&Chart.register(f);n.forEach(n=>{const p=n.querySelector("script[type='application/json']"),o=n.querySelector("canvas"),w=n.querySelector("[data-metric-toggles]"),b=n.querySelector("[data-chart-fallback]"),k=n.querySelector("[data-timeline-metric-edit-toolbar]"),c=n.querySelector("[data-edit-metric-status]"),v=n.hasAttribute("data-timeline-metric-editor")&&k&&c;if(p&&o){let f;try{f=JSON.parse(p.textContent||"{}")}catch{b?.classList.remove("d-none");return}if(!window.Chart||!f.scenes?.length||!f.metrics?.length){b?.classList.remove("d-none");return}const tt=new Set(f.defaultMetrics?.length?f.defaultMetrics:u),s=f.scenes.length,it=s>80?10:s>40?5:s>24?4:s>14?2:1,l=f.metrics.map((n,i)=>{const u=new Map(n.points.map(n=>[n.sceneId,n.value])),r=t[i%t.length];return{label:n.metricName,metricTypeId:n.metricTypeId,minValue:Number.isFinite(n.minValue)?n.minValue:1,maxValue:Number.isFinite(n.maxValue)?n.maxValue:10,data:f.scenes.map((n,t)=>{const i=u.get(n.sceneId);return i==null?{x:t+.5,y:null,sceneId:n.sceneId}:{x:t+.5,y:i,sceneId:n.sceneId}}),borderColor:r,backgroundColor:r,pointBackgroundColor:r,pointBorderColor:"#fff",pointBorderWidth:1.5,pointRadius:3,pointHoverRadius:5,borderWidth:2,tension:.35,spanGaps:!0,hidden:!tt.has(n.metricName)}});let e=null,a=!1;const h=n=>{c&&(c.textContent=n)},d=()=>l.map((n,t)=>t).filter(n=>r?.isDatasetVisible(n)),g=()=>{const n=d();return n.length===1?n[0]:-1},nt=()=>{if(v){const n=g();l.forEach((t,i)=>{t.pointRadius=i===n?5:3,t.pointHoverRadius=i===n?7:5,t.borderWidth=i===n?3:2});const t=d(),i=n>=0;o.classList.toggle("metric-curve-editing",i);c.classList.toggle("d-none",t.length===0);t.length===0?h(""):i?h("Drag points up or down to edit this metric."):h("Show only one metric line to edit its curve.");r?.update()}};w.innerHTML="";l.forEach((n,t)=>{const u=`metric-toggle-${Math.random().toString(36).slice(2)}-${t}`,i=document.createElement("label");i.className="metric-toggle-pill";i.style.setProperty("--metric-colour",n.borderColor);i.innerHTML=`${n.label}`;w.appendChild(i);i.querySelector("input").addEventListener("change",n=>{r.setDatasetVisibility(t,n.target.checked),nt(),r.update()})});const r=new Chart(o,{type:"line",data:{datasets:l},options:{responsive:!0,maintainAspectRatio:!1,interaction:{mode:"nearest",intersect:!1},scales:{y:{min:1,max:10,ticks:{stepSize:1},grid:{color:"rgba(82, 75, 65, 0.10)"}},x:{type:"linear",min:0,max:s,bounds:"ticks",ticks:{maxRotation:0,autoSkip:!1,maxTicksLimit:16,callback(n){const t=Math.floor(Number(n));if(t<0||t>=s)return"";const i=f.scenes[t];return t!==0&&t!==s-1&&t%it!=0?"":i?.isPlaceholder?"":i?.axisLabel||""}},afterBuildTicks(n){n.ticks=f.scenes.map((n,t)=>({value:t+.5}))},grid:{display:!1}}},plugins:{legend:{display:!1},plotlineScenes:f.scenes,tooltip:{callbacks:{title(n){const i=Math.floor(n[0].parsed.x),t=f.scenes[i];return t.isPlaceholder?t.sceneTitle||"No scene":`${t.chapterLabel}, Scene ${t.sceneNumber}: ${t.sceneTitle}`},label(n){return`${n.dataset.label}: ${n.formattedValue}`},afterBody(n){const r=Math.floor(n[0].parsed.x),t=f.scenes[r],i=[];return t.characterPresence?i.push(t.characterPresence):t.characterAppears!==undefined&&i.push(t.characterAppears?"Character appears":"Character does not appear"),t.bookTitle&&i.push(t.bookTitle),i}}}}}}),rt=n=>{if(!v)return null;const u=g();if(u<0)return null;const f=r.getElementsAtEventForMode(n,"nearest",{intersect:!0},!0),t=f.find(n=>n.datasetIndex===u);if(!t)return null;const e=r.data.datasets[t.datasetIndex],i=e?.data?.[t.index];return!i?.sceneId||i.y==null?null:{datasetIndex:t.datasetIndex,pointIndex:t.index,previousValue:i.y}},ut=(n,t)=>{const i=r.scales.y.getValueForPixel(n.offsetY),u=Math.round(i);return Math.max(t.minValue,Math.min(t.maxValue,u))},ft=async t=>{const u=r.data.datasets[t.datasetIndex],i=u?.data?.[t.pointIndex];if(u&&i?.sceneId){const f=k?.querySelector("input[name='__RequestVerificationToken']")?.value||"";h("Saving...");try{const e=await fetch(n.dataset.updateUrl||"/SceneMetrics/UpdateSceneMetricValue",{method:"POST",headers:{"Content-Type":"application/json",RequestVerificationToken:f},body:JSON.stringify({sceneId:Number(i.sceneId),metricId:Number(u.metricTypeId),value:Number(i.y)})}),t=await e.json().catch(()=>({}));if(!e.ok||!t.success)throw new Error(t.message||"Save failed");i.y=t.value;h("Saved");r.update()}catch{i.y=t.previousValue;h("Save failed");r.update()}}};v&&(nt(),o.addEventListener("pointerdown",n=>{(e=rt(n),e)&&(n.preventDefault(),o.setPointerCapture?.(n.pointerId),a=!0)}),o.addEventListener("pointermove",n=>{if(e){const t=r.data.datasets[e.datasetIndex],i=t?.data?.[e.pointIndex];t&&i&&(i.y=ut(n,t),r.update("none"))}}),o.addEventListener("pointerup",async n=>{if(e){const t=e;e=null;o.releasePointerCapture?.(n.pointerId);await ft(t);window.setTimeout(()=>{a=!1},0)}}),o.addEventListener("pointercancel",n=>{if(e){const i=r.data.datasets[e.datasetIndex],t=i?.data?.[e.pointIndex];t&&(t.y=e.previousValue);e=null;o.releasePointerCapture?.(n.pointerId);a=!1;r.update()}}));o.addEventListener("click",n=>{if(a){n.preventDefault();return}const t=r.getElementsAtEventForMode(n,"nearest",{intersect:!0},!0);if(t.length){const i=r.data.datasets[t[0].datasetIndex]?.data?.[t[0].index],e=i?Math.floor(i.x):t[0].index,u=f.scenes[e];u?.timelineUrl&&(window.PlotLineTimelineScroll?.save(),window.location.href=u.timelineUrl)}});i.push(r);const y=window.ResizeObserver?new ResizeObserver(()=>r.resize()):null;y&&(y.observe(n),y.observe(n.querySelector(".metric-chart-frame")||n))}})}})();(()=>{const i=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-timeline-controls]");if(i&&t){const r="plotline.timeline.visibility",u={"scene-cards":!0,"plot-lines":!0,assets:!0,characters:!0,locations:!0,warnings:!0,metrics:!0},e=()=>{try{return{...u,...JSON.parse(localStorage.getItem(r)||"{}")}}catch{return{...u}}},f=n=>{Object.entries(n).forEach(([n,t])=>{i.classList.toggle(`hide-${n}`,!t),document.querySelectorAll(`[data-timeline-section='${n}']`).forEach(n=>{n.classList.toggle("timeline-section-hidden",!t)})}),document.querySelectorAll("[data-location-marker]").forEach(t=>{t.classList.toggle("timeline-section-hidden",!n.locations)}),document.querySelectorAll("[data-warning-marker]").forEach(t=>{t.classList.toggle("timeline-section-hidden",!n.warnings)}),document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(n))},n=e();t.querySelectorAll("[data-timeline-toggle]").forEach(t=>{const i=t.dataset.timelineToggle;t.checked=n[i]??!0;t.addEventListener("change",()=>{n[i]=t.checked,localStorage.setItem(r,JSON.stringify(n)),f(n)})});t.querySelector("[data-timeline-preset-form]")?.addEventListener("submit",()=>{document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(n))});f(n)}})();(()=>{const n=document.querySelector("[data-timeline-root]");if(n){const f=n.querySelector(".timeline-workspace")||n,h=document.querySelector("[data-timeline-density]"),e=document.querySelector("[data-timeline-zoom]"),t=document.querySelector("[data-timeline-search]"),c=n.querySelector("[data-timeline-minimap]"),ft=document.querySelectorAll("[data-timeline-jump], [data-timeline-warning-jump], [data-timeline-entity-jump]"),et=[...n.querySelectorAll(".timeline-scene-card[data-scene-id]")],l="plotline.timeline.collapsed",d="plotline.timeline.density",g="plotline.timeline.zoom",o="plotline.timeline.pending-scroll",a="plotline.timeline.inspectorWidth",ot=".timeline-shell,.plot-lane-board,.asset-lane-board,.character-lane-board,.metric-graph-stack,[data-timeline-minimap]",nt=["compact","comfortable","expanded"],s=["book","chapter","scene-detail"],v=n.querySelector("[data-inspector-drawer]"),i=n.querySelector("[data-inspector-resizer]"),st=n.querySelector("[data-inspector-close]"),ht=n=>{try{return new Set(JSON.parse(localStorage.getItem(n)||"[]"))}catch{return new Set}},ct=localStorage.getItem(l)!==null,r=ht(l),tt=()=>[...n.querySelectorAll(ot)],lt=()=>{const t=n.getBoundingClientRect().width||window.innerWidth,i=Math.min(420,Math.max(320,t-360)),r=Math.max(i,Math.min(t*.65,t-420));return{minimum:i,maximum:r,defaultWidth:Math.max(i,Math.min(r,t*.4))}},y=t=>{if(v){const{minimum:i,maximum:r,defaultWidth:u}=lt(),f=Math.max(i,Math.min(r,Number(t)||u));n.style.setProperty("--timeline-inspector-width",`${Math.round(f)}px`)}};v&&(y(localStorage.getItem(a)),window.addEventListener("resize",()=>y(localStorage.getItem(a))));i?.addEventListener("pointerdown",t=>{t.preventDefault();document.body.classList.add("timeline-inspector-resizing");i.setPointerCapture(t.pointerId);const u=t=>{const i=n.getBoundingClientRect().right,r=i-t.clientX;y(r)},r=()=>{document.body.classList.remove("timeline-inspector-resizing");const n=Math.round(v?.getBoundingClientRect().width||0);n>0&&localStorage.setItem(a,String(n));window.dispatchEvent(new Event("plotline:timeline-layout-changed"));i.removeEventListener("pointermove",u);i.removeEventListener("pointerup",r);i.removeEventListener("pointercancel",r)};i.addEventListener("pointermove",u);i.addEventListener("pointerup",r);i.addEventListener("pointercancel",r)});st?.addEventListener("click",n=>{n.preventDefault();const t=new URL(window.location.href),i=t.searchParams.get("FocusType")||t.searchParams.get("focusType");t.searchParams.delete("SelectedSceneID");t.searchParams.delete("selectedSceneId");t.searchParams.delete("selectedsceneid");i&&i.toLowerCase()!=="scene"||(t.searchParams.delete("FocusType"),t.searchParams.delete("focusType"),t.searchParams.delete("FocusID"),t.searchParams.delete("focusId"));p();window.location.href=t.toString()});const p=()=>{try{sessionStorage.setItem(o,JSON.stringify({path:window.location.pathname,top:window.scrollY,left:window.scrollX,workspaceLeft:f.scrollLeft,workspaceTop:f.scrollTop,containers:tt().map((n,t)=>({index:t,left:n.scrollLeft,top:n.scrollTop}))}))}catch{}},at=()=>{let n=null;try{n=JSON.parse(sessionStorage.getItem(o)||"null");sessionStorage.removeItem(o)}catch{try{sessionStorage.removeItem(o)}catch{}return}n&&n.path===window.location.pathname&&window.requestAnimationFrame(()=>{f.scrollLeft=Number(n.workspaceLeft)||0;f.scrollTop=Number(n.workspaceTop)||0;const t=tt();(n.containers||[]).forEach(n=>{const i=t[n.index];i&&(i.scrollLeft=Number(n.left)||0,i.scrollTop=Number(n.top)||0)});window.scrollTo(Number(n.left)||0,Number(n.top)||0)})};window.PlotLineTimelineScroll={save:p};const it=(t,i,r)=>{r.forEach(i=>n.classList.remove(`${t}-${i}`)),n.classList.add(`${t}-${i}`)},rt=n=>{const t=nt.includes(n)?n:"comfortable";it("density",t,nt);h&&(h.value=t);localStorage.setItem(d,t);window.requestAnimationFrame(u)},w=n=>{const t=s.includes(n)?n:"chapter";it("zoom",t,s);e&&(e.value=t);localStorage.setItem(g,t);window.requestAnimationFrame(u)},vt=t=>n.querySelector(`[data-scene-id='${CSS.escape(String(t))}']`),b=(n,t=true)=>{const i=vt(n);i&&(i.classList.add("keyboard-focus"),i.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),t&&i.focus({preventScroll:!0}),window.setTimeout(()=>i.classList.remove("keyboard-focus"),1800))},yt=(n="auto")=>{const r=c?.querySelector("[data-active-chapter='true']");if(c&&r){const t=c.getBoundingClientRect(),i=r.getBoundingClientRect(),u=i.left+i.width/2,f=t.left+t.width/2;(i.leftt.right)&&c.scrollBy({left:u-f,behavior:n})}},pt=()=>localStorage.setItem(l,JSON.stringify([...r])),u=()=>{n.querySelectorAll("[data-plot-line-svg-board]").forEach(n=>{const i=n.querySelector("[data-plot-line-svg]");if(i){i.replaceChildren();const u=n.getBoundingClientRect(),o=Math.max(n.scrollWidth,u.width),s=Math.max(n.scrollHeight,u.height);i.setAttribute("viewBox",`0 0 ${o} ${s}`);i.setAttribute("width",String(o));i.setAttribute("height",String(s));const t=(n,t={})=>{const i=document.createElementNS("http://www.w3.org/2000/svg",n);return Object.entries(t).forEach(([n,t])=>i.setAttribute(n,String(t))),i},h=n=>{const t=n.getBoundingClientRect();return t.top-u.top+t.height/2},y=(n,t)=>{const i=t.closest("[data-plot-line-slot]");if(!i)return null;const r=i.getBoundingClientRect();return{x:r.left-u.left+r.width/2,y:h(n),sceneIndex:0,type:(t.dataset.plotEventType||"Progress").toLowerCase(),targetPlotLineId:t.dataset.targetPlotLineId||"",targetPlotLineIds:(t.dataset.targetPlotLineIds||"").split(",").map(n=>n.trim()).filter(Boolean)}},c=[...new Set([...n.querySelectorAll("[data-plot-line-slot]")].map(n=>{const t=n.getBoundingClientRect();return Math.round(t.left-u.left+t.width/2)}))].sort((n,t)=>n-t),l=Math.max(c.length-1,0),f=new Map;n.querySelectorAll("[data-plot-line-row]").forEach(n=>{n.dataset.plotLineId&&f.set(n.dataset.plotLineId,n)});const r=[...f.values()].map(n=>({row:n,plotLineId:n.dataset.plotLineId,colour:n.dataset.plotColour||"#2f6f63",points:[...n.querySelectorAll("[data-plot-event-node]")].map(t=>y(n,t)).filter(Boolean).map(n=>({...n,sceneIndex:Math.max(0,c.findIndex(t=>t===Math.round(n.x)))})).sort((n,t)=>n.x-t.x),incomingStartXs:[]})),a=new Map(r.map(n=>[n.plotLineId,n])),p=n=>{const t=n.some(n=>n.type==="resolve"),i=n.some(n=>n.type==="abandon"),u=n.find(n=>n.type==="resolve"),c=n.find(n=>n.type==="abandon"),f=n.some(n=>n.type==="merge"&&n.targetPlotLineId),e=n.some(n=>n.type==="split"&&n.targetPlotLineIds.length>=2),a=Boolean(u)&&n.some(n=>n.type==="branch"&&n.targetPlotLineId&&n.sceneIndex>=u.sceneIndex),o=n.reduce((n,t)=>Math.max(n,t.sceneIndex),0),s=!t&&!i&&l-o>=10,v=!t&&!i&&!f&&!e&&l>=o,h=t&&!f&&!e&&!a;let r="active";return i?r="abandoned":t?r=h?"questionable":"resolved":s&&(r="neglected"),{state:r,isNeglected:s,isDangling:v,isQuestionable:h,isAbandoned:i,resolvePoint:u,abandonPoint:c}},w=(n,r,u)=>{const f=t("g",{"class":`plot-event-svg-node plot-event-svg-node-${n.type} plotline-state-${u}`}),e=n.type==="branch"||n.type==="split"||n.type==="merge";n.type==="twist"?f.append(t("path",{d:`M ${n.x} ${n.y-9} L ${n.x+9} ${n.y} L ${n.x} ${n.y+9} L ${n.x-9} ${n.y} Z`,fill:r})):n.type==="abandon"?(f.append(t("circle",{cx:n.x,cy:n.y,r:9,fill:"#fff5f5"})),f.append(t("line",{x1:n.x-7,y1:n.y-7,x2:n.x+7,y2:n.y+7})),f.append(t("line",{x1:n.x+7,y1:n.y-7,x2:n.x-7,y2:n.y+7}))):n.type==="reveal"?f.append(t("path",{d:`M ${n.x} ${n.y-10} L ${n.x+3} ${n.y-3} L ${n.x+10} ${n.y-3} L ${n.x+4} ${n.y+2} L ${n.x+6} ${n.y+10} L ${n.x} ${n.y+5} L ${n.x-6} ${n.y+10} L ${n.x-4} ${n.y+2} L ${n.x-10} ${n.y-3} L ${n.x-3} ${n.y-3} Z`,fill:r})):(f.append(t("circle",{cx:n.x,cy:n.y,r:e?9:7.5,fill:r})),n.type==="resolve"&&f.append(t("circle",{cx:n.x,cy:n.y,r:12,fill:"none",stroke:r})),e&&f.append(t("circle",{cx:n.x,cy:n.y,r:13,fill:"none",stroke:r})));i.append(f)},e=(n,r,u,f=0)=>{const e=13+f*15,o=t("g",{"class":`plotline-health-marker plotline-health-marker-${r}`}),s=t("title");if(s.textContent=u,o.append(s),r==="questionable"){o.append(t("circle",{cx:n.x+e,cy:n.y-10,r:7}));const i=t("text",{x:n.x+e,y:n.y-6,"text-anchor":"middle"});i.textContent="?";o.append(i)}else o.append(t("path",{d:`M ${n.x+e} ${n.y-18} L ${n.x+e+8} ${n.y-3} L ${n.x+e-8} ${n.y-3} Z`})),o.append(t("line",{x1:n.x+e,y1:n.y-13,x2:n.x+e,y2:n.y-8})),o.append(t("circle",{cx:n.x+e,cy:n.y-5.6,r:1.3}));i.append(o)},v=(n,r,u,f)=>{if(!r){window.console?.warn&&window.console.warn(`Plot line ${f} connector skipped because target plot line data is missing.`);return}const e={x:n.x,y:h(r)},o=Math.max(28,Math.min(46,Math.abs(e.y-n.y)*.28)),c=n.x+o,s=n.x+o/2;i.append(t("path",{"class":`plot-line-svg-connector plot-line-svg-connector-${f}`,d:`M ${n.x} ${n.y} C ${s} ${n.y}, ${s} ${e.y}, ${c} ${e.y}`,stroke:u}))};r.forEach(n=>{n.points.forEach(n=>{n.type==="branch"&&n.targetPlotLineId&&a.get(n.targetPlotLineId)?.incomingStartXs.push(n.x),n.type==="split"&&n.targetPlotLineIds.forEach(t=>{a.get(t)?.incomingStartXs.push(n.x)})})});r.forEach(n=>{n.health=p(n.points),n.state=n.health.state});r.forEach(n=>{const{colour:f,points:r,incomingStartXs:u,state:e}=n;if(r.length>0){const c=r.find(n=>n.type==="start"),s=u.length?Math.min(...u):null,l=r.find(n=>n.type==="split"||n.type==="merge"),o=r[0],v=r[r.length-1],a=s!==null&&!(c&&c.xa&&i.append(t("path",{"class":`plot-line-svg-path plotline-state-${e}`,d:`M ${a} ${o.y} L ${h} ${o.y}`,stroke:f}))}});r.forEach(({points:t,colour:n})=>{t.forEach(t=>{(t.type==="branch"||t.type==="merge")&&v(t,f.get(t.targetPlotLineId),n,t.type),t.type==="split"&&t.targetPlotLineIds.length>=2?t.targetPlotLineIds.forEach(i=>{v(t,f.get(i),n,t.type)}):t.type==="split"&&t.targetPlotLineIds.length>0&&window.console?.warn&&window.console.warn("Plot line split connector skipped because fewer than two targets are available.")})});r.forEach(({points:t,colour:n,state:i})=>{t.forEach(t=>{w(t,n,i)})});r.forEach(n=>{const t=n.lineEndPoint||n.points[n.points.length-1];let i=0;n.health?.isNeglected&&t&&e(t,"neglected","No plot event for 10+ scenes. This thread may have been forgotten.",i++);n.health?.isDangling&&t&&e(t,"dangling","This plot line is still unresolved at the end of the current timeline.",i++);n.health?.isQuestionable&&n.health.resolvePoint&&e(n.health.resolvePoint,"questionable","Resolved without merge, split, or branch. Verify this thread has sufficient payoff.");n.health?.isAbandoned&&n.health.abandonPoint&&e(n.health.abandonPoint,"abandoned","This plot line was abandoned. Check this is intentional.")})}})},ut=(n,t)=>{document.querySelectorAll(`[data-collapse-body='${CSS.escape(n)}']`).forEach(n=>{n.classList.toggle("timeline-section-collapsed",t)}),document.querySelectorAll(`[data-collapse-toggle='${CSS.escape(n)}']`).forEach(n=>{n.classList.toggle("is-collapsed",t),n.setAttribute("aria-expanded",String(!t))})};document.querySelectorAll("[data-collapse-toggle]").forEach(n=>{const t=n.dataset.collapseToggle;ct||n.dataset.collapseDefault!=="collapsed"||r.add(t);const i=r.has(t);ut(t,i);n.addEventListener("click",()=>{const n=!r.has(t);n?r.add(t):r.delete(t);ut(t,n);pt();u();window.dispatchEvent(new Event("plotline:timeline-layout-changed"))})});h?.addEventListener("change",()=>rt(h.value));e?.addEventListener("change",()=>w(e.value));rt(localStorage.getItem(d)||"comfortable");w(localStorage.getItem(g)||"chapter");const wt=(()=>{try{return sessionStorage.getItem(o)!==null}catch{return!1}})();window.requestAnimationFrame(()=>{u(),yt("auto"),wt&&at()});window.addEventListener("resize",u);window.addEventListener("plotline:timeline-layout-changed",u);const bt=t=>{if(t.button===0&&!t.metaKey&&!t.ctrlKey&&!t.shiftKey&&!t.altKey){const r=t.target instanceof Element?t.target:null,i=r?.closest("a[href]");if(i&&n.contains(i)){const u=new URL(i.href,window.location.href);[...u.searchParams.keys()].some(n=>n.toLowerCase()==="selectedsceneid")&&p()}}};n.addEventListener("click",bt,{capture:!0});const k=()=>{const n=t?.value.trim().toLowerCase()||"";let i=null;return et.forEach(t=>{const r=!n||(t.dataset.timelineSearchText||"").toLowerCase().includes(n);t.classList.toggle("search-hit",Boolean(n&&r));t.classList.toggle("search-dim",Boolean(n&&!r));!i&&n&&r&&(i=t)}),i};t?.addEventListener("input",k);t?.addEventListener("keydown",n=>{if(n.key==="Enter"){const t=k();t&&(n.preventDefault(),b(t.dataset.sceneId))}});ft.forEach(n=>{n.addEventListener("change",()=>{n.value&&b(n.value)})});document.querySelectorAll("[data-jump-scene]").forEach(n=>{n.addEventListener("click",()=>{n.dataset.jumpScene&&b(n.dataset.jumpScene)})});document.addEventListener("keydown",n=>{const i=document.activeElement?.tagName?.toLowerCase(),r=["input","select","textarea"].includes(i)||document.activeElement?.isContentEditable;if((n.ctrlKey||n.metaKey)&&n.key.toLowerCase()==="f"&&t){n.preventDefault();t.focus();t.select();return}if(n.key==="Escape"){const n=new URL(window.location.href),i=n.searchParams.has("FocusType")||n.searchParams.has("FocusID");n.searchParams.delete("FocusType");n.searchParams.delete("FocusID");i?window.location.href=n.toString():t?.value&&(t.value="",k());return}if(!r){if(n.key==="ArrowLeft"||n.key==="ArrowRight"){n.preventDefault();f.scrollBy({left:n.key==="ArrowLeft"?-260:260,behavior:"smooth"});return}if(n.key==="+"||n.key==="="||n.key==="-"){const t=s.indexOf(e?.value||"chapter"),i=n.key==="-"?-1:1,r=Math.min(s.length-1,Math.max(0,t+i));w(s[r])}}})}})();(()=>{const e=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-drag-drop-panel]");if(t){const i=new Map([...document.querySelectorAll("[data-drag-form]")].map(n=>[n.dataset.dragForm,n])),g=t.querySelector("[data-drag-panel-title]"),nt=t.querySelector("[data-drag-panel-kicker]"),tt=t.querySelector("[data-drag-panel-summary]"),o=document.querySelector("[data-scene-move-modal]"),v=o&&window.bootstrap?.Modal?window.bootstrap.Modal.getOrCreateInstance(o):null,y=o?.querySelector("[data-scene-move-title]"),p=o?.querySelector("[data-scene-move-summary]"),w=o?.querySelector("[data-scene-move-submit]"),s="plotline.timeline.scroll";let h=null,c=0;const n=(n,t,i)=>{const r=n?.querySelector(`[name='${CSS.escape(t)}']`);r&&(r.value=i??"")},l=n=>n?.selectedOptions?.[0]?.textContent?.trim()||"",a=n=>`“${n||"this scene"}”`,b=(n,t,i,r,u)=>t&&i&&t===i?`Move ${a(n.label)} to this new position within ${i}?`:t&&i?`Move ${a(n.label)} from ${t} to ${i}?`:`Move ${a(n.label)} ${r.toLowerCase()} ${u}?`,r=(n,r,u,f)=>{if(n==="scene"){t.classList.add("d-none");t.querySelectorAll("[data-drag-form]").forEach(n=>n.classList.add("d-none"));i.get("scene")?.classList.remove("d-none");y&&(y.textContent=r);p&&(p.textContent=u);w&&w.classList.toggle("d-none",r.toLowerCase().startsWith("cannot")||r.toLowerCase().startsWith("scene already"));v?v.show():t.classList.remove("d-none");return}i.forEach((t,i)=>t.classList.toggle("d-none",i!==n));t.classList.remove("d-none");g.textContent=r;nt.textContent=f;tt.textContent=u;t.scrollIntoView({behavior:"smooth",block:"center"})},it=()=>{t.classList.add("d-none"),i.forEach(n=>n.classList.add("d-none"))},k=()=>{document.querySelectorAll(".drop-target-active, .drop-before, .drop-after, .chapter-drop-active, .drop-chapter-end").forEach(n=>n.classList.remove("drop-target-active","drop-before","drop-after","chapter-drop-active","drop-chapter-end"))},rt=()=>{const n=e?.querySelector(".timeline-shell");try{sessionStorage.setItem(s,JSON.stringify({left:n?.scrollLeft||0,top:window.scrollY||0}))}catch{}};try{const n=JSON.parse(sessionStorage.getItem(s)||"null");n&&(sessionStorage.removeItem(s),requestAnimationFrame(()=>{const t=e?.querySelector(".timeline-shell");t&&(t.scrollLeft=n.left||0);window.scrollTo({top:n.top||0})}))}catch{sessionStorage.removeItem(s)}const f=n=>{try{const t=JSON.parse(n.dataTransfer.getData("application/json")||"{}");return t.type?t:h||{}}catch{return h||{}}},ut=(n,t)=>{const i={type:t.dataset.dragType,id:t.dataset.dragId,label:t.dataset.dragLabel||t.textContent.trim()};n.dataTransfer.effectAllowed="copyMove";n.dataTransfer.setData("application/json",JSON.stringify(i));n.dataTransfer.setData("text/plain",`${i.type}:${i.id}`);h=i;document.body.classList.add("drag-active",`dragging-${i.type}`);t.classList.add("is-dragging")};document.querySelectorAll("[data-drag-type][draggable='true']").forEach(n=>{n.addEventListener("dragstart",t=>ut(t,n)),n.addEventListener("dragend",()=>{n.classList.remove("is-dragging"),document.body.classList.remove("drag-active","dragging-scene","dragging-character","dragging-asset","dragging-location"),h=null,k()})});t.querySelectorAll("[data-drag-cancel]").forEach(n=>n.addEventListener("click",it));i.get("scene")?.addEventListener("submit",rt);e?.querySelectorAll(".timeline-scene-card[data-scene-id]").forEach(t=>{t.addEventListener("click",n=>{Date.now(){const i=f(n);["scene","character","asset"].includes(i.type)&&(n.preventDefault(),n.stopPropagation(),n.dataTransfer.dropEffect=i.type==="scene"?"move":"copy",t.classList.add("drop-target-active"),t.classList.toggle("drop-before",i.type==="scene"&&n.offsetX=t.clientWidth/2))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active","drop-before","drop-after")),t.addEventListener("drop",u=>{const o=f(u);if(["scene","character","asset"].includes(o.type)){u.preventDefault();u.stopPropagation();c=Date.now()+800;t.classList.remove("drop-target-active","drop-before","drop-after");const s=t.dataset.sceneId;if(o.type==="scene"){if(o.id===s){r("scene","Cannot move scene","Drop a scene before or after a different scene.","Scene move");return}const f=i.get("scene"),h=u.offsetX{const u=t.closest("[data-chapter-drop-target]"),o=t.dataset.chapterId||u?.dataset.chapterId,s=t.dataset.chapterLabel||u?.dataset.chapterLabel||"this chapter";t.addEventListener("dragover",n=>{const i=f(n);i.type==="scene"&&o&&(n.target.closest(".timeline-scene-card[data-scene-id]")||(n.preventDefault(),n.dataTransfer.dropEffect="move",t.classList.add("drop-target-active","drop-chapter-end"),u?.classList.add("chapter-drop-active")))});t.addEventListener("dragleave",n=>{t.contains(n.relatedTarget)||(t.classList.remove("drop-target-active","drop-chapter-end"),u?.classList.remove("chapter-drop-active"))});t.addEventListener("drop",u=>{const h=f(u);if(h.type==="scene"&&o&&!u.target.closest(".timeline-scene-card[data-scene-id]")){u.preventDefault();u.stopPropagation();c=Date.now()+800;k();const a=e.querySelector(`.timeline-scene-card[data-scene-id='${CSS.escape(h.id)}']`);if(a?.dataset.chapterId===o&&t.lastElementChild?.dataset?.sceneId===h.id){r("scene","Scene already here",`${h.label} is already at the end of ${s}.`,"Scene move");return}const l=i.get("scene");n(l,"SceneID",h.id);n(l,"AnchorSceneID","");n(l,"TargetChapterID",o);n(l,"Position","End");r("scene","Move Scene?",b(h,a?.dataset.chapterLabel||"",s,"After",s),"Scene move")}})});const u=i.get("asset-dependency"),d=()=>{const n=u?.dataset.sourceLabel||"Source asset",i=u?.dataset.targetLabel||"Target asset",f=l(u?.querySelector("[name='AssetDependencyTypeID']"))||"Requires",e=l(u?.querySelector("[name='SourceRequiredStateID']"))||"the required state",o=l(u?.querySelector("[name='TargetBlockedStateID']"))||"the blocked state",r=t.querySelector("[data-asset-dependency-preview]");r&&(r.textContent=`${n} ${f.toLowerCase()} ${i}. ${n} should be ${e} before ${i} can be ${o}.`)};u?.querySelectorAll("select").forEach(n=>n.addEventListener("change",d));document.querySelectorAll("[data-asset-drop-target], .drag-chip.asset").forEach(t=>{t.addEventListener("dragover",n=>{const i=f(n);i.type==="asset"&&(n.preventDefault(),n.dataTransfer.dropEffect="copy",t.classList.add("drop-target-active"))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active")),t.addEventListener("drop",i=>{const e=f(i),o=t.dataset.assetId||t.dataset.dragId,s=t.dataset.assetLabel||t.dataset.dragLabel||t.textContent.trim();if(e.type==="asset"&&o){if(i.preventDefault(),t.classList.remove("drop-target-active"),e.id===o){r("asset-dependency","Cannot create dependency","Drop one asset onto a different asset.","Asset dependency");return}n(u,"SourceAssetID",e.id);n(u,"TargetAssetID",o);u.dataset.sourceLabel=e.label;u.dataset.targetLabel=s;d();r("asset-dependency","Create asset dependency",`${e.label} -> ${s}`,"Asset dependency")}})});document.querySelectorAll("[data-drag-type='location']").forEach(t=>{t.addEventListener("dragover",n=>{const i=f(n);i.type==="location"&&(n.preventDefault(),n.dataTransfer.dropEffect="copy",t.classList.add("drop-target-active"))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active")),t.addEventListener("drop",u=>{const e=f(u),o=t.dataset.dragId,h=t.dataset.dragLabel||t.textContent.trim();if(e.type==="location"){if(u.preventDefault(),t.classList.remove("drop-target-active"),e.id===o){r("location-relationship","Cannot create relationship","Drop one location onto a different location.","Location relationship");return}const s=i.get("location-relationship");n(s,"FromLocationID",e.id);n(s,"ToLocationID",o);r("location-relationship","Create location relationship",`${e.label} -> ${h}`,"Location drop")}})})}})();(()=>{const t=document.querySelector("[data-trial-banner]");if(t){const e=`plotline.trialBanner.dismissed.${t.dataset.trialBannerKey||"current"}`;try{if(localStorage.getItem(e)==="true"){t.hidden=!0;return}}catch{}t.querySelector("[data-trial-banner-dismiss]")?.addEventListener("click",()=>{t.hidden=!0;try{localStorage.setItem(e,"true")}catch{}})}})(); \ No newline at end of file +(()=>{const n="plotline.theme",t=document.querySelectorAll("[data-theme-choice]"),r=()=>{try{return localStorage.getItem(n)}catch{return null}},u=t=>{try{localStorage.setItem(n,t)}catch{}},i=n=>{const i=n==="dark"?"dark":"light";document.documentElement.dataset.theme=i;document.documentElement.dataset.bsTheme=i;u(i);t.forEach(n=>{const t=n.dataset.themeChoice===i;n.classList.toggle("active",t);n.setAttribute("aria-pressed",String(t))})};t.forEach(n=>{n.addEventListener("click",()=>i(n.dataset.themeChoice))});i(r()||"light")})();(()=>{const n=document.querySelector("[data-trial-banner]");if(n){const t=`plotline.trialBanner.dismissed.${n.dataset.trialBannerKey||"current"}`;try{if(localStorage.getItem(t)==="true"){n.hidden=!0;return}}catch{}n.querySelector("[data-trial-banner-dismiss]")?.addEventListener("click",()=>{n.hidden=!0;try{localStorage.setItem(t,"true")}catch{}})}})();(()=>{const n=document.querySelectorAll(".scene-inspector-root");if(n.length){const t=()=>`${window.location.pathname}${window.location.search}${window.location.hash}`;n.forEach(n=>{n.addEventListener("submit",n=>{const r=n.target;if(r instanceof HTMLFormElement){let i=r.querySelector("input[name='ReturnUrl']");i||(i=document.createElement("input"),i.type="hidden",i.name="ReturnUrl",r.appendChild(i));i.value=t()}},!0)})}})();(()=>{const t=document.getElementById("plotlineConfirmModal"),f=t&&window.bootstrap?new bootstrap.Modal(t):null,e=t?.querySelector("#plotlineConfirmModalTitle"),o=t?.querySelector("#plotlineConfirmModalMessage"),i=t?.querySelector("[data-confirm-submit]"),s=t?.querySelector("[data-delete-confirm-field]"),r=t?.querySelector("[data-delete-confirm-input]");let n=null,u=null;const h=n=>{const t=n.split(/\n\s*\n/);return{title:t.shift()?.trim()||"Confirm action",body:t.join("\n\n").trim()}},c=()=>{n=null,u=null,r&&(r.value=""),s?.classList.add("d-none"),i?.removeAttribute("disabled")};t?.addEventListener("hidden.bs.modal",c);r?.addEventListener("input",()=>{n?.dataset.requireDeleteText==="true"&&i&&(i.disabled=r.value.trim()!=="DELETE")});i?.addEventListener("click",()=>{if(n){if(n.dataset.requireDeleteText==="true"){const t=n.querySelector("[data-delete-confirm-target], [name='confirmationText']");t&&(t.value=r?.value.trim()||"")}n.dataset.confirmed="true";f?.hide();n.requestSubmit&&u?n.requestSubmit(u):n.requestSubmit?n.requestSubmit():n.submit()}});document.addEventListener("submit",c=>{const l=c.target;if(l instanceof HTMLFormElement&&l.dataset.confirmed!=="true"&&l.dataset.confirmSkip!=="true"&&l.dataset.noDeleteConfirm!=="true"){const b=l.dataset.confirmMessage,k=c.submitter instanceof HTMLElement?c.submitter:l.querySelector("button[type='submit']"),a=k?.textContent||"",p=l.getAttribute("action")||"",y=/restore/i.test(a)||/\/restore\b/i.test(p),v=/delete forever/i.test(a)||/\/deleteforever\b/i.test(p),d=!y&&!v&&(/archive/i.test(a)||/\/archive\b/i.test(p)),nt=!v&&/(delete|remove)/i.test(a);if(b||d||y||nt||v){const tt=b||(d?"Archive this item?\n\nThis will hide it from active lists and timelines, but the data will be kept and can be restored later.":y?"Restore this item?\n\nThis will return it to active lists and timelines where applicable.":v?"Permanently delete this item?\n\nThis cannot be undone. Any linked planning data may also be removed or become unavailable. Only continue if you are certain.":"Remove this item?\n\nThis removes the link or supporting record. Major story items are archived instead of permanently deleted.");if(f&&t&&e&&o&&i){c.preventDefault();const g=h(tt);n=l;u=k;e.textContent=l.dataset.confirmTitle||g.title;o.textContent=l.dataset.confirmBody||g.body;i.textContent=l.dataset.confirmButtonText||a.trim()||"Confirm";i.className=`btn ${l.dataset.confirmButtonClass||(y?"btn-primary":"btn-danger")}`;const w=l.dataset.requireDeleteText==="true"||v;s?.classList.toggle("d-none",!w);w&&i&&(i.disabled=!0,r?.focus());f.show();w&&window.setTimeout(()=>r?.focus(),200)}}}})})();(()=>{const r=document.querySelectorAll("[data-help-icon]");if(r.length&&window.bootstrap){const u=new Map;let n=null;const f=n=>n.querySelector("[data-help-tooltip]")||n,i=n=>bootstrap.Tooltip.getInstance(f(n))?.hide(),t=(t=null)=>{n&&n!==t&&bootstrap.Popover.getInstance(n)?.hide()},e=()=>r.forEach(n=>i(n));r.forEach(r=>{const o=f(r),s=o.dataset.helpMicro||"Help article not found.",h=r.dataset.helpTitle||"Help unavailable",c=r.dataset.helpSummary||"Help content has not yet been written for this feature.";bootstrap.Tooltip.getOrCreateInstance(o,{title:s,trigger:"hover focus",placement:"top",container:"body"});const l=bootstrap.Popover.getOrCreateInstance(r,{title:h,trigger:"click",placement:"auto",container:"body",html:!0,customClass:"plotline-help-popover",content(){const u=document.createElement("div"),i=document.createElement("p");i.className="mb-2";i.textContent=c;const n=document.createElement("a");return n.href="#",n.textContent="More...",n.className="plotline-help-more",n.addEventListener("click",n=>{n.preventDefault(),t(),e(),r.dispatchEvent(new CustomEvent("plotline:help-more",{bubbles:!0,detail:{key:r.dataset.helpKey||""}}))}),u.append(i,n),u}});u.set(r,l);r.addEventListener("click",n=>{window.PlotLineHelpDrawer?.isActive?.()&&(n.preventDefault(),n.stopPropagation(),t(),i(r),window.PlotLineHelpPopovers?.hideAll(),window.PlotLineHelpDrawer.open(r.dataset.helpKey||""))});r.addEventListener("show.bs.popover",n=>{if(window.PlotLineHelpDrawer?.isActive?.()){n.preventDefault();return}t(r);i(r)});r.addEventListener("shown.bs.popover",()=>{n=r,i(r)});r.addEventListener("hidden.bs.popover",()=>{n===r&&(n=null)})});document.addEventListener("click",i=>{const r=i.target instanceof Element?i.target:null;if(r&&n&&!r.closest("[data-help-icon]")){const u=document.querySelector(`#${n.getAttribute("aria-describedby")}`);u?.contains(r)||t()}});document.addEventListener("keydown",n=>{n.key==="Escape"&&t()});window.PlotLineHelpPopovers={hideAll(){u.forEach(n=>n.hide());e();n=null}}}})();(()=>{const n=document.querySelector("[data-help-layout]"),p=document.querySelector("[data-help-drawer]"),i=document.querySelector("[data-help-content]"),l=document.querySelector("[data-help-splitter]"),w=document.querySelector("[data-help-close]"),h=document.querySelector("[data-help-collapse]"),a=document.querySelector("[data-help-expand]"),u=document.querySelector("[data-help-pin]"),f=document.querySelector("[data-help-drawer-heading]");if(n&&p&&i){const t={width:"plotline.helpDrawer.width",pinned:"plotline.helpDrawer.pinned",key:"plotline.helpDrawer.key",collapsed:"plotline.helpDrawer.collapsed"},b=(n,t,i)=>Math.min(i,Math.max(t,n)),e=n=>{try{return localStorage.getItem(n)==="true"}catch{return!1}},o=(n,t)=>{try{localStorage.setItem(n,t)}catch{}},v=i=>{const r=`${b(Number.parseFloat(i)||25,20,50)}%`;n.style.setProperty("--help-drawer-width",r);o(t.width,r)},c=n=>{o(t.pinned,String(n)),u?.setAttribute("aria-pressed",String(n)),u&&(u.textContent=n?"Unpin":"Pin")},r=i=>{n.classList.toggle("help-collapsed",i),o(t.collapsed,String(i)),h&&(h.textContent=i?"Expand":"Collapse")},s=async(u,e={})=>{const s=u||"";n.classList.add("help-open");e.keepCollapsed||r(!1);o(t.key,s);f&&(f.textContent=s||"Help");i.innerHTML='

Loading help...<\/p>';try{const n=await fetch(`/HelpDrawer/Article?key=${encodeURIComponent(s)}`,{headers:{"X-Requested-With":"XMLHttpRequest"}});i.innerHTML=n.ok?await n.text():'

Help for this feature has not yet been written.<\/p>';const t=i.querySelector(".help-drawer-article h2")?.textContent?.trim();f&&t&&(f.textContent=t)}catch{i.innerHTML='

Help for this feature has not yet been written.<\/p>'}},k=()=>{n.classList.remove("help-open","help-collapsed"),c(!1)};window.PlotLineHelpDrawer={isActive(){return n.classList.contains("help-open")},open(n){return s(n)}};v(localStorage.getItem(t.width)||"25%");c(e(t.pinned));r(e(t.collapsed));document.addEventListener("plotline:help-more",n=>{const t=n.detail?.key||n.target?.dataset?.helpKey||"";window.PlotLineHelpPopovers?.hideAll();s(t)});document.addEventListener("click",n=>{const t=n.target instanceof Element?n.target.closest("[data-help-open]"):null;t&&(n.preventDefault(),window.PlotLineHelpPopovers?.hideAll(),s(t.dataset.helpOpen||""))});w?.addEventListener("click",k);h?.addEventListener("click",()=>r(!n.classList.contains("help-collapsed")));a?.addEventListener("click",()=>r(!1));a?.addEventListener("keydown",n=>{(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),r(!1))});u?.addEventListener("click",()=>c(!e(t.pinned)));l?.addEventListener("pointerdown",t=>{if(n.classList.contains("help-open")&&!n.classList.contains("help-collapsed")){t.preventDefault();document.body.classList.add("help-resizing");l.setPointerCapture?.(t.pointerId);const i=t=>{const i=n.getBoundingClientRect(),r=(i.right-t.clientX)/i.width*100;v(r)},r=()=>{document.body.classList.remove("help-resizing"),document.removeEventListener("pointermove",i),document.removeEventListener("pointerup",r)};document.addEventListener("pointermove",i);document.addEventListener("pointerup",r)}});const y=(()=>{try{return localStorage.getItem(t.key)||""}catch{return""}})();e(t.pinned)&&y&&s(y,{keepCollapsed:!0})}})();(()=>{const n=document.querySelectorAll(".scene-inspector-root");if(n.length){const t=n=>n.toLowerCase().replace(/&/g,"and").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");n.forEach(n=>{const o=n.dataset.sceneId||n.querySelector(".scene-inspector-form [name='SceneID']")?.value||"new",f=`plotline.inspector.${o}.`,s=new Set(["overview","timing","characters"]),u=[...n.querySelectorAll(".inspector-section")],r=n.querySelector(".scene-inspector-form"),i=n.querySelector("[data-scene-time-editor]");if(r&&i&&i.dataset.enhanced!=="true"){i.dataset.enhanced="true";const n=r.querySelector("[name='TimeModeID']"),e=i.dataset.exactDatetimeModeId||"",o=i.dataset.exactDateModeId||"",s=[...i.querySelectorAll("[data-time-part-field]")],h={StartDateTime:i.querySelector("[data-date-part-for='StartDateTime']"),EndDateTime:i.querySelector("[data-date-part-for='EndDateTime']")},c={StartDateTime:i.querySelector("[data-time-part-for='StartDateTime']"),EndDateTime:i.querySelector("[data-time-part-for='EndDateTime']")},l={StartDateTime:r.querySelector("input[type='hidden'][name='StartDateTime']"),EndDateTime:r.querySelector("input[type='hidden'][name='EndDateTime']")},t=()=>n?.value===e,a=()=>n?.value===o,u=()=>{const n=t();s.forEach(t=>{t.hidden=!n,t.classList.toggle("d-none",!n)})},f=n=>{const i=l[n],r=h[n]?.value||"",f=c[n]?.value||"";if(i){if(!r){i.value="";return}if(t()){i.value=`${r}T${f||"00:00"}`;return}if(a()){i.value=r;return}const u=i.value.match(/^\\d{4}-\\d{2}-\\d{2}T(\\d{2}:\\d{2})/)?.[1];i.value=u?`${r}T${u}`:r}},v=()=>{f("StartDateTime"),f("EndDateTime")};n?.addEventListener("change",u);r.addEventListener("submit",v);u()}u.forEach(n=>{if(n.dataset.enhanced!=="true"){const l=n.querySelector("h3, h2"),u=n.dataset.sectionTitle||l?.textContent?.trim()||"Overview",a=t(u),o=n.dataset.sectionId||`inspector-${a}`,e=o.replace(/^inspector-/,"");n.id=o;n.dataset.searchText=`${u} ${n.textContent}`.toLowerCase();n.dataset.enhanced="true";const r=document.createElement("div");for(r.className="inspector-accordion-body";n.firstChild;)r.appendChild(n.firstChild);const i=document.createElement("button");i.type="button";i.className="inspector-accordion-toggle";i.setAttribute("aria-expanded","false");n.append(i,r);const v=r.querySelectorAll(".asset-event-item, .thread-event-item, .warning-mini-card, .dependency-card, .metric-slider").length;i.innerHTML=`${u}${v||""}`;const h=localStorage.getItem(`${f}${e}`),c=h===null?s.has(e):h==="open";n.classList.toggle("is-open",c);i.setAttribute("aria-expanded",String(c));i.addEventListener("click",()=>{const t=!n.classList.contains("is-open");n.classList.toggle("is-open",t);i.setAttribute("aria-expanded",String(t));localStorage.setItem(`${f}${e}`,t?"open":"closed")})}});n.querySelectorAll(".inspector-nav-links a").forEach(t=>{t.addEventListener("click",i=>{const r=n.querySelector(t.getAttribute("href"));r&&(i.preventDefault(),r.classList.add("is-open"),r.querySelector(".inspector-accordion-toggle")?.setAttribute("aria-expanded","true"),r.scrollIntoView({behavior:"smooth",block:"start"}))})});const e=n.querySelector("[data-inspector-search]");if(e?.addEventListener("input",()=>{const n=e.value.trim().toLowerCase();u.forEach(t=>{const i=!n||t.dataset.searchText.includes(n);t.classList.toggle("is-filtered-out",!i);i&&n&&(t.classList.add("is-open"),t.querySelector(".inspector-accordion-toggle")?.setAttribute("aria-expanded","true"))})}),"IntersectionObserver"in window){const t=[...n.querySelectorAll(".inspector-nav-links a")],i=new IntersectionObserver(n=>{n.forEach(n=>{n.isIntersecting&&t.forEach(t=>{t.classList.toggle("active",t.getAttribute("href")===`#${n.target.id}`)})})},{root:n.closest(".scene-inspector-panel")||null,rootMargin:"-10% 0px -70% 0px"});u.forEach(n=>i.observe(n))}})}})();(()=>{const n=document.querySelectorAll("[data-metric-shape-chart]");if(n.length){const t=["#2f6f63","#b35f3b","#6750a4","#c58b2b","#7d3f52","#33759b","#9c4d8f","#a33d3d","#6f8f3d","#d08a2f","#444b57"],u=new Set(["Overall Intensity","Tension","Emotional Weight","Action"]),i=[],r=()=>{window.requestAnimationFrame(()=>{i.forEach(n=>n.resize())})};window.PlotLineMetricCharts={resizeAll:r};window.addEventListener("plotline:timeline-layout-changed",r);const f={id:"plotlineChapterGuides",afterDraw(n){const u=n.config.options.plugins.plotlineScenes||[],{ctx:t,chartArea:r,scales:f}=n;if(u.length&&f.x){t.save();t.strokeStyle="rgba(47, 111, 99, 0.16)";t.fillStyle="rgba(82, 75, 65, 0.58)";t.lineWidth=1;t.font="700 10px system-ui, -apple-system, Segoe UI, sans-serif";const e=[];u.forEach((n,t)=>{t&&n.chapterLabel===u[t-1].chapterLabel||e.push({scene:n,index:t})});const i=e.filter(n=>!n.scene.isPlaceholder),o=i.length>40?5:i.length>24?4:i.length>14?2:1;i.forEach((n,u)=>{const{scene:s,index:c}=n,e=f.x.getPixelForValue(c+.5);t.beginPath();t.moveTo(e,r.top);t.lineTo(e,r.bottom);t.stroke();const h=Number.parseInt(String(s.chapterLabel).replace(/[^0-9]/g,""),10),l=u===0||u===i.length-1||o===1||Number.isFinite(h)&&h%o==0;l&&t.fillText(s.chapterLabel,Math.min(e+4,r.right-70),r.bottom+22)});t.restore()}}};window.Chart&&Chart.register(f);n.forEach(n=>{const p=n.querySelector("script[type='application/json']"),o=n.querySelector("canvas"),w=n.querySelector("[data-metric-toggles]"),b=n.querySelector("[data-chart-fallback]"),k=n.querySelector("[data-timeline-metric-edit-toolbar]"),c=n.querySelector("[data-edit-metric-status]"),v=n.hasAttribute("data-timeline-metric-editor")&&k&&c;if(p&&o){let f;try{f=JSON.parse(p.textContent||"{}")}catch{b?.classList.remove("d-none");return}if(!window.Chart||!f.scenes?.length||!f.metrics?.length){b?.classList.remove("d-none");return}const tt=new Set(f.defaultMetrics?.length?f.defaultMetrics:u),s=f.scenes.length,it=s>80?10:s>40?5:s>24?4:s>14?2:1,l=f.metrics.map((n,i)=>{const u=new Map(n.points.map(n=>[n.sceneId,n.value])),r=t[i%t.length];return{label:n.metricName,metricTypeId:n.metricTypeId,minValue:Number.isFinite(n.minValue)?n.minValue:1,maxValue:Number.isFinite(n.maxValue)?n.maxValue:10,data:f.scenes.map((n,t)=>{const i=u.get(n.sceneId);return i==null?{x:t+.5,y:null,sceneId:n.sceneId}:{x:t+.5,y:i,sceneId:n.sceneId}}),borderColor:r,backgroundColor:r,pointBackgroundColor:r,pointBorderColor:"#fff",pointBorderWidth:1.5,pointRadius:3,pointHoverRadius:5,borderWidth:2,tension:.35,spanGaps:!0,hidden:!tt.has(n.metricName)}});let e=null,a=!1;const h=n=>{c&&(c.textContent=n)},d=()=>l.map((n,t)=>t).filter(n=>r?.isDatasetVisible(n)),g=()=>{const n=d();return n.length===1?n[0]:-1},nt=()=>{if(v){const n=g();l.forEach((t,i)=>{t.pointRadius=i===n?5:3,t.pointHoverRadius=i===n?7:5,t.borderWidth=i===n?3:2});const t=d(),i=n>=0;o.classList.toggle("metric-curve-editing",i);c.classList.toggle("d-none",t.length===0);t.length===0?h(""):i?h("Drag points up or down to edit this metric."):h("Show only one metric line to edit its curve.");r?.update()}};w.innerHTML="";l.forEach((n,t)=>{const u=`metric-toggle-${Math.random().toString(36).slice(2)}-${t}`,i=document.createElement("label");i.className="metric-toggle-pill";i.style.setProperty("--metric-colour",n.borderColor);i.innerHTML=`${n.label}`;w.appendChild(i);i.querySelector("input").addEventListener("change",n=>{r.setDatasetVisibility(t,n.target.checked),nt(),r.update()})});const r=new Chart(o,{type:"line",data:{datasets:l},options:{responsive:!0,maintainAspectRatio:!1,interaction:{mode:"nearest",intersect:!1},scales:{y:{min:1,max:10,ticks:{stepSize:1},grid:{color:"rgba(82, 75, 65, 0.10)"}},x:{type:"linear",min:0,max:s,bounds:"ticks",ticks:{maxRotation:0,autoSkip:!1,maxTicksLimit:16,callback(n){const t=Math.floor(Number(n));if(t<0||t>=s)return"";const i=f.scenes[t];return t!==0&&t!==s-1&&t%it!=0?"":i?.isPlaceholder?"":i?.axisLabel||""}},afterBuildTicks(n){n.ticks=f.scenes.map((n,t)=>({value:t+.5}))},grid:{display:!1}}},plugins:{legend:{display:!1},plotlineScenes:f.scenes,tooltip:{callbacks:{title(n){const i=Math.floor(n[0].parsed.x),t=f.scenes[i];return t.isPlaceholder?t.sceneTitle||"No scene":`${t.chapterLabel}, Scene ${t.sceneNumber}: ${t.sceneTitle}`},label(n){return`${n.dataset.label}: ${n.formattedValue}`},afterBody(n){const r=Math.floor(n[0].parsed.x),t=f.scenes[r],i=[];return t.characterPresence?i.push(t.characterPresence):t.characterAppears!==undefined&&i.push(t.characterAppears?"Character appears":"Character does not appear"),t.bookTitle&&i.push(t.bookTitle),i}}}}}}),rt=n=>{if(!v)return null;const u=g();if(u<0)return null;const f=r.getElementsAtEventForMode(n,"nearest",{intersect:!0},!0),t=f.find(n=>n.datasetIndex===u);if(!t)return null;const e=r.data.datasets[t.datasetIndex],i=e?.data?.[t.index];return!i?.sceneId||i.y==null?null:{datasetIndex:t.datasetIndex,pointIndex:t.index,previousValue:i.y}},ut=(n,t)=>{const i=r.scales.y.getValueForPixel(n.offsetY),u=Math.round(i);return Math.max(t.minValue,Math.min(t.maxValue,u))},ft=async t=>{const u=r.data.datasets[t.datasetIndex],i=u?.data?.[t.pointIndex];if(u&&i?.sceneId){const f=k?.querySelector("input[name='__RequestVerificationToken']")?.value||"";h("Saving...");try{const e=await fetch(n.dataset.updateUrl||"/SceneMetrics/UpdateSceneMetricValue",{method:"POST",headers:{"Content-Type":"application/json",RequestVerificationToken:f},body:JSON.stringify({sceneId:Number(i.sceneId),metricId:Number(u.metricTypeId),value:Number(i.y)})}),t=await e.json().catch(()=>({}));if(!e.ok||!t.success)throw new Error(t.message||"Save failed");i.y=t.value;h("Saved");r.update()}catch{i.y=t.previousValue;h("Save failed");r.update()}}};v&&(nt(),o.addEventListener("pointerdown",n=>{(e=rt(n),e)&&(n.preventDefault(),o.setPointerCapture?.(n.pointerId),a=!0)}),o.addEventListener("pointermove",n=>{if(e){const t=r.data.datasets[e.datasetIndex],i=t?.data?.[e.pointIndex];t&&i&&(i.y=ut(n,t),r.update("none"))}}),o.addEventListener("pointerup",async n=>{if(e){const t=e;e=null;o.releasePointerCapture?.(n.pointerId);await ft(t);window.setTimeout(()=>{a=!1},0)}}),o.addEventListener("pointercancel",n=>{if(e){const i=r.data.datasets[e.datasetIndex],t=i?.data?.[e.pointIndex];t&&(t.y=e.previousValue);e=null;o.releasePointerCapture?.(n.pointerId);a=!1;r.update()}}));o.addEventListener("click",n=>{if(a){n.preventDefault();return}const t=r.getElementsAtEventForMode(n,"nearest",{intersect:!0},!0);if(t.length){const i=r.data.datasets[t[0].datasetIndex]?.data?.[t[0].index],e=i?Math.floor(i.x):t[0].index,u=f.scenes[e];u?.timelineUrl&&(window.PlotLineTimelineScroll?.save(),window.location.href=u.timelineUrl)}});i.push(r);const y=window.ResizeObserver?new ResizeObserver(()=>r.resize()):null;y&&(y.observe(n),y.observe(n.querySelector(".metric-chart-frame")||n))}})}})();(()=>{const i=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-timeline-controls]");if(i&&t){const r="plotline.timeline.visibility",u={"scene-cards":!0,"plot-lines":!0,assets:!0,characters:!0,locations:!0,warnings:!0,metrics:!0},e=()=>{try{return{...u,...JSON.parse(localStorage.getItem(r)||"{}")}}catch{return{...u}}},f=n=>{Object.entries(n).forEach(([n,t])=>{i.classList.toggle(`hide-${n}`,!t),document.querySelectorAll(`[data-timeline-section='${n}']`).forEach(n=>{n.classList.toggle("timeline-section-hidden",!t)})}),document.querySelectorAll("[data-location-marker]").forEach(t=>{t.classList.toggle("timeline-section-hidden",!n.locations)}),document.querySelectorAll("[data-warning-marker]").forEach(t=>{t.classList.toggle("timeline-section-hidden",!n.warnings)}),document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(n))},n=e();t.querySelectorAll("[data-timeline-toggle]").forEach(t=>{const i=t.dataset.timelineToggle;t.checked=n[i]??!0;t.addEventListener("change",()=>{n[i]=t.checked,localStorage.setItem(r,JSON.stringify(n)),f(n)})});t.querySelector("[data-timeline-preset-form]")?.addEventListener("submit",()=>{document.querySelector("[data-visibility-json]")?.setAttribute("value",JSON.stringify(n))});f(n)}})();(()=>{const n=document.querySelector("[data-timeline-root]");if(n){const f=n.querySelector(".timeline-workspace")||n,h=document.querySelector("[data-timeline-density]"),e=document.querySelector("[data-timeline-zoom]"),t=document.querySelector("[data-timeline-search]"),c=n.querySelector("[data-timeline-minimap]"),ft=document.querySelectorAll("[data-timeline-jump], [data-timeline-warning-jump], [data-timeline-entity-jump]"),et=[...n.querySelectorAll(".timeline-scene-card[data-scene-id]")],l="plotline.timeline.collapsed",d="plotline.timeline.density",g="plotline.timeline.zoom",o="plotline.timeline.pending-scroll",a="plotline.timeline.inspectorWidth",ot=".timeline-shell,.plot-lane-board,.asset-lane-board,.character-lane-board,.metric-graph-stack,[data-timeline-minimap]",nt=["compact","comfortable","expanded"],s=["book","chapter","scene-detail"],v=n.querySelector("[data-inspector-drawer]"),i=n.querySelector("[data-inspector-resizer]"),st=n.querySelector("[data-inspector-close]"),ht=n=>{try{return new Set(JSON.parse(localStorage.getItem(n)||"[]"))}catch{return new Set}},ct=localStorage.getItem(l)!==null,r=ht(l),tt=()=>[...n.querySelectorAll(ot)],lt=()=>{const t=n.getBoundingClientRect().width||window.innerWidth,i=Math.min(420,Math.max(320,t-360)),r=Math.max(i,Math.min(t*.65,t-420));return{minimum:i,maximum:r,defaultWidth:Math.max(i,Math.min(r,t*.4))}},y=t=>{if(v){const{minimum:i,maximum:r,defaultWidth:u}=lt(),f=Math.max(i,Math.min(r,Number(t)||u));n.style.setProperty("--timeline-inspector-width",`${Math.round(f)}px`)}};v&&(y(localStorage.getItem(a)),window.addEventListener("resize",()=>y(localStorage.getItem(a))));i?.addEventListener("pointerdown",t=>{t.preventDefault();document.body.classList.add("timeline-inspector-resizing");i.setPointerCapture(t.pointerId);const u=t=>{const i=n.getBoundingClientRect().right,r=i-t.clientX;y(r)},r=()=>{document.body.classList.remove("timeline-inspector-resizing");const n=Math.round(v?.getBoundingClientRect().width||0);n>0&&localStorage.setItem(a,String(n));window.dispatchEvent(new Event("plotline:timeline-layout-changed"));i.removeEventListener("pointermove",u);i.removeEventListener("pointerup",r);i.removeEventListener("pointercancel",r)};i.addEventListener("pointermove",u);i.addEventListener("pointerup",r);i.addEventListener("pointercancel",r)});st?.addEventListener("click",n=>{n.preventDefault();const t=new URL(window.location.href),i=t.searchParams.get("FocusType")||t.searchParams.get("focusType");t.searchParams.delete("SelectedSceneID");t.searchParams.delete("selectedSceneId");t.searchParams.delete("selectedsceneid");i&&i.toLowerCase()!=="scene"||(t.searchParams.delete("FocusType"),t.searchParams.delete("focusType"),t.searchParams.delete("FocusID"),t.searchParams.delete("focusId"));p();window.location.href=t.toString()});const p=()=>{try{sessionStorage.setItem(o,JSON.stringify({path:window.location.pathname,top:window.scrollY,left:window.scrollX,workspaceLeft:f.scrollLeft,workspaceTop:f.scrollTop,containers:tt().map((n,t)=>({index:t,left:n.scrollLeft,top:n.scrollTop}))}))}catch{}},at=()=>{let n=null;try{n=JSON.parse(sessionStorage.getItem(o)||"null");sessionStorage.removeItem(o)}catch{try{sessionStorage.removeItem(o)}catch{}return}n&&n.path===window.location.pathname&&window.requestAnimationFrame(()=>{f.scrollLeft=Number(n.workspaceLeft)||0;f.scrollTop=Number(n.workspaceTop)||0;const t=tt();(n.containers||[]).forEach(n=>{const i=t[n.index];i&&(i.scrollLeft=Number(n.left)||0,i.scrollTop=Number(n.top)||0)});window.scrollTo(Number(n.left)||0,Number(n.top)||0)})};window.PlotLineTimelineScroll={save:p};const it=(t,i,r)=>{r.forEach(i=>n.classList.remove(`${t}-${i}`)),n.classList.add(`${t}-${i}`)},rt=n=>{const t=nt.includes(n)?n:"comfortable";it("density",t,nt);h&&(h.value=t);localStorage.setItem(d,t);window.requestAnimationFrame(u)},w=n=>{const t=s.includes(n)?n:"chapter";it("zoom",t,s);e&&(e.value=t);localStorage.setItem(g,t);window.requestAnimationFrame(u)},vt=t=>n.querySelector(`[data-scene-id='${CSS.escape(String(t))}']`),b=(n,t=true)=>{const i=vt(n);i&&(i.classList.add("keyboard-focus"),i.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),t&&i.focus({preventScroll:!0}),window.setTimeout(()=>i.classList.remove("keyboard-focus"),1800))},yt=(n="auto")=>{const r=c?.querySelector("[data-active-chapter='true']");if(c&&r){const t=c.getBoundingClientRect(),i=r.getBoundingClientRect(),u=i.left+i.width/2,f=t.left+t.width/2;(i.leftt.right)&&c.scrollBy({left:u-f,behavior:n})}},pt=()=>localStorage.setItem(l,JSON.stringify([...r])),u=()=>{n.querySelectorAll("[data-plot-line-svg-board]").forEach(n=>{const i=n.querySelector("[data-plot-line-svg]");if(i){i.replaceChildren();const u=n.getBoundingClientRect(),o=Math.max(n.scrollWidth,u.width),s=Math.max(n.scrollHeight,u.height);i.setAttribute("viewBox",`0 0 ${o} ${s}`);i.setAttribute("width",String(o));i.setAttribute("height",String(s));const t=(n,t={})=>{const i=document.createElementNS("http://www.w3.org/2000/svg",n);return Object.entries(t).forEach(([n,t])=>i.setAttribute(n,String(t))),i},h=n=>{const t=n.getBoundingClientRect();return t.top-u.top+t.height/2},y=(n,t)=>{const i=t.closest("[data-plot-line-slot]");if(!i)return null;const r=i.getBoundingClientRect();return{x:r.left-u.left+r.width/2,y:h(n),sceneIndex:0,type:(t.dataset.plotEventType||"Progress").toLowerCase(),targetPlotLineId:t.dataset.targetPlotLineId||"",targetPlotLineIds:(t.dataset.targetPlotLineIds||"").split(",").map(n=>n.trim()).filter(Boolean)}},c=[...new Set([...n.querySelectorAll("[data-plot-line-slot]")].map(n=>{const t=n.getBoundingClientRect();return Math.round(t.left-u.left+t.width/2)}))].sort((n,t)=>n-t),l=Math.max(c.length-1,0),f=new Map;n.querySelectorAll("[data-plot-line-row]").forEach(n=>{n.dataset.plotLineId&&f.set(n.dataset.plotLineId,n)});const r=[...f.values()].map(n=>({row:n,plotLineId:n.dataset.plotLineId,colour:n.dataset.plotColour||"#2f6f63",points:[...n.querySelectorAll("[data-plot-event-node]")].map(t=>y(n,t)).filter(Boolean).map(n=>({...n,sceneIndex:Math.max(0,c.findIndex(t=>t===Math.round(n.x)))})).sort((n,t)=>n.x-t.x),incomingStartXs:[]})),a=new Map(r.map(n=>[n.plotLineId,n])),p=n=>{const t=n.some(n=>n.type==="resolve"),i=n.some(n=>n.type==="abandon"),u=n.find(n=>n.type==="resolve"),c=n.find(n=>n.type==="abandon"),f=n.some(n=>n.type==="merge"&&n.targetPlotLineId),e=n.some(n=>n.type==="split"&&n.targetPlotLineIds.length>=2),a=Boolean(u)&&n.some(n=>n.type==="branch"&&n.targetPlotLineId&&n.sceneIndex>=u.sceneIndex),o=n.reduce((n,t)=>Math.max(n,t.sceneIndex),0),s=!t&&!i&&l-o>=10,v=!t&&!i&&!f&&!e&&l>=o,h=t&&!f&&!e&&!a;let r="active";return i?r="abandoned":t?r=h?"questionable":"resolved":s&&(r="neglected"),{state:r,isNeglected:s,isDangling:v,isQuestionable:h,isAbandoned:i,resolvePoint:u,abandonPoint:c}},w=(n,r,u)=>{const f=t("g",{"class":`plot-event-svg-node plot-event-svg-node-${n.type} plotline-state-${u}`}),e=n.type==="branch"||n.type==="split"||n.type==="merge";n.type==="twist"?f.append(t("path",{d:`M ${n.x} ${n.y-9} L ${n.x+9} ${n.y} L ${n.x} ${n.y+9} L ${n.x-9} ${n.y} Z`,fill:r})):n.type==="abandon"?(f.append(t("circle",{cx:n.x,cy:n.y,r:9,fill:"#fff5f5"})),f.append(t("line",{x1:n.x-7,y1:n.y-7,x2:n.x+7,y2:n.y+7})),f.append(t("line",{x1:n.x+7,y1:n.y-7,x2:n.x-7,y2:n.y+7}))):n.type==="reveal"?f.append(t("path",{d:`M ${n.x} ${n.y-10} L ${n.x+3} ${n.y-3} L ${n.x+10} ${n.y-3} L ${n.x+4} ${n.y+2} L ${n.x+6} ${n.y+10} L ${n.x} ${n.y+5} L ${n.x-6} ${n.y+10} L ${n.x-4} ${n.y+2} L ${n.x-10} ${n.y-3} L ${n.x-3} ${n.y-3} Z`,fill:r})):(f.append(t("circle",{cx:n.x,cy:n.y,r:e?9:7.5,fill:r})),n.type==="resolve"&&f.append(t("circle",{cx:n.x,cy:n.y,r:12,fill:"none",stroke:r})),e&&f.append(t("circle",{cx:n.x,cy:n.y,r:13,fill:"none",stroke:r})));i.append(f)},e=(n,r,u,f=0)=>{const e=13+f*15,o=t("g",{"class":`plotline-health-marker plotline-health-marker-${r}`}),s=t("title");if(s.textContent=u,o.append(s),r==="questionable"){o.append(t("circle",{cx:n.x+e,cy:n.y-10,r:7}));const i=t("text",{x:n.x+e,y:n.y-6,"text-anchor":"middle"});i.textContent="?";o.append(i)}else o.append(t("path",{d:`M ${n.x+e} ${n.y-18} L ${n.x+e+8} ${n.y-3} L ${n.x+e-8} ${n.y-3} Z`})),o.append(t("line",{x1:n.x+e,y1:n.y-13,x2:n.x+e,y2:n.y-8})),o.append(t("circle",{cx:n.x+e,cy:n.y-5.6,r:1.3}));i.append(o)},v=(n,r,u,f)=>{if(!r){window.console?.warn&&window.console.warn(`Plot line ${f} connector skipped because target plot line data is missing.`);return}const e={x:n.x,y:h(r)},o=Math.max(28,Math.min(46,Math.abs(e.y-n.y)*.28)),c=n.x+o,s=n.x+o/2;i.append(t("path",{"class":`plot-line-svg-connector plot-line-svg-connector-${f}`,d:`M ${n.x} ${n.y} C ${s} ${n.y}, ${s} ${e.y}, ${c} ${e.y}`,stroke:u}))};r.forEach(n=>{n.points.forEach(n=>{n.type==="branch"&&n.targetPlotLineId&&a.get(n.targetPlotLineId)?.incomingStartXs.push(n.x),n.type==="split"&&n.targetPlotLineIds.forEach(t=>{a.get(t)?.incomingStartXs.push(n.x)})})});r.forEach(n=>{n.health=p(n.points),n.state=n.health.state});r.forEach(n=>{const{colour:f,points:r,incomingStartXs:u,state:e}=n;if(r.length>0){const c=r.find(n=>n.type==="start"),s=u.length?Math.min(...u):null,l=r.find(n=>n.type==="split"||n.type==="merge"),o=r[0],v=r[r.length-1],a=s!==null&&!(c&&c.xa&&i.append(t("path",{"class":`plot-line-svg-path plotline-state-${e}`,d:`M ${a} ${o.y} L ${h} ${o.y}`,stroke:f}))}});r.forEach(({points:t,colour:n})=>{t.forEach(t=>{(t.type==="branch"||t.type==="merge")&&v(t,f.get(t.targetPlotLineId),n,t.type),t.type==="split"&&t.targetPlotLineIds.length>=2?t.targetPlotLineIds.forEach(i=>{v(t,f.get(i),n,t.type)}):t.type==="split"&&t.targetPlotLineIds.length>0&&window.console?.warn&&window.console.warn("Plot line split connector skipped because fewer than two targets are available.")})});r.forEach(({points:t,colour:n,state:i})=>{t.forEach(t=>{w(t,n,i)})});r.forEach(n=>{const t=n.lineEndPoint||n.points[n.points.length-1];let i=0;n.health?.isNeglected&&t&&e(t,"neglected","No plot event for 10+ scenes. This thread may have been forgotten.",i++);n.health?.isDangling&&t&&e(t,"dangling","This plot line is still unresolved at the end of the current timeline.",i++);n.health?.isQuestionable&&n.health.resolvePoint&&e(n.health.resolvePoint,"questionable","Resolved without merge, split, or branch. Verify this thread has sufficient payoff.");n.health?.isAbandoned&&n.health.abandonPoint&&e(n.health.abandonPoint,"abandoned","This plot line was abandoned. Check this is intentional.")})}})},ut=(n,t)=>{document.querySelectorAll(`[data-collapse-body='${CSS.escape(n)}']`).forEach(n=>{n.classList.toggle("timeline-section-collapsed",t)}),document.querySelectorAll(`[data-collapse-toggle='${CSS.escape(n)}']`).forEach(n=>{n.classList.toggle("is-collapsed",t),n.setAttribute("aria-expanded",String(!t))})};document.querySelectorAll("[data-collapse-toggle]").forEach(n=>{const t=n.dataset.collapseToggle;ct||n.dataset.collapseDefault!=="collapsed"||r.add(t);const i=r.has(t);ut(t,i);n.addEventListener("click",()=>{const n=!r.has(t);n?r.add(t):r.delete(t);ut(t,n);pt();u();window.dispatchEvent(new Event("plotline:timeline-layout-changed"))})});h?.addEventListener("change",()=>rt(h.value));e?.addEventListener("change",()=>w(e.value));rt(localStorage.getItem(d)||"comfortable");w(localStorage.getItem(g)||"chapter");const wt=(()=>{try{return sessionStorage.getItem(o)!==null}catch{return!1}})();window.requestAnimationFrame(()=>{u(),yt("auto"),wt&&at()});window.addEventListener("resize",u);window.addEventListener("plotline:timeline-layout-changed",u);const bt=t=>{if(t.button===0&&!t.metaKey&&!t.ctrlKey&&!t.shiftKey&&!t.altKey){const r=t.target instanceof Element?t.target:null,i=r?.closest("a[href]");if(i&&n.contains(i)){const u=new URL(i.href,window.location.href);[...u.searchParams.keys()].some(n=>n.toLowerCase()==="selectedsceneid")&&p()}}};n.addEventListener("click",bt,{capture:!0});const k=()=>{const n=t?.value.trim().toLowerCase()||"";let i=null;return et.forEach(t=>{const r=!n||(t.dataset.timelineSearchText||"").toLowerCase().includes(n);t.classList.toggle("search-hit",Boolean(n&&r));t.classList.toggle("search-dim",Boolean(n&&!r));!i&&n&&r&&(i=t)}),i};t?.addEventListener("input",k);t?.addEventListener("keydown",n=>{if(n.key==="Enter"){const t=k();t&&(n.preventDefault(),b(t.dataset.sceneId))}});ft.forEach(n=>{n.addEventListener("change",()=>{n.value&&b(n.value)})});document.querySelectorAll("[data-jump-scene]").forEach(n=>{n.addEventListener("click",()=>{n.dataset.jumpScene&&b(n.dataset.jumpScene)})});document.addEventListener("keydown",n=>{const i=document.activeElement?.tagName?.toLowerCase(),r=["input","select","textarea"].includes(i)||document.activeElement?.isContentEditable;if((n.ctrlKey||n.metaKey)&&n.key.toLowerCase()==="f"&&t){n.preventDefault();t.focus();t.select();return}if(n.key==="Escape"){const n=new URL(window.location.href),i=n.searchParams.has("FocusType")||n.searchParams.has("FocusID");n.searchParams.delete("FocusType");n.searchParams.delete("FocusID");i?window.location.href=n.toString():t?.value&&(t.value="",k());return}if(!r){if(n.key==="ArrowLeft"||n.key==="ArrowRight"){n.preventDefault();f.scrollBy({left:n.key==="ArrowLeft"?-260:260,behavior:"smooth"});return}if(n.key==="+"||n.key==="="||n.key==="-"){const t=s.indexOf(e?.value||"chapter"),i=n.key==="-"?-1:1,r=Math.min(s.length-1,Math.max(0,t+i));w(s[r])}}})}})();(()=>{const e=document.querySelector("[data-timeline-root]"),t=document.querySelector("[data-drag-drop-panel]");if(t){const i=new Map([...document.querySelectorAll("[data-drag-form]")].map(n=>[n.dataset.dragForm,n])),g=t.querySelector("[data-drag-panel-title]"),nt=t.querySelector("[data-drag-panel-kicker]"),tt=t.querySelector("[data-drag-panel-summary]"),o=document.querySelector("[data-scene-move-modal]"),v=o&&window.bootstrap?.Modal?window.bootstrap.Modal.getOrCreateInstance(o):null,y=o?.querySelector("[data-scene-move-title]"),p=o?.querySelector("[data-scene-move-summary]"),w=o?.querySelector("[data-scene-move-submit]"),s="plotline.timeline.scroll";let h=null,c=0;const n=(n,t,i)=>{const r=n?.querySelector(`[name='${CSS.escape(t)}']`);r&&(r.value=i??"")},l=n=>n?.selectedOptions?.[0]?.textContent?.trim()||"",a=n=>`“${n||"this scene"}”`,b=(n,t,i,r,u)=>t&&i&&t===i?`Move ${a(n.label)} to this new position within ${i}?`:t&&i?`Move ${a(n.label)} from ${t} to ${i}?`:`Move ${a(n.label)} ${r.toLowerCase()} ${u}?`,r=(n,r,u,f)=>{if(n==="scene"){t.classList.add("d-none");t.querySelectorAll("[data-drag-form]").forEach(n=>n.classList.add("d-none"));i.get("scene")?.classList.remove("d-none");y&&(y.textContent=r);p&&(p.textContent=u);w&&w.classList.toggle("d-none",r.toLowerCase().startsWith("cannot")||r.toLowerCase().startsWith("scene already"));v?v.show():t.classList.remove("d-none");return}i.forEach((t,i)=>t.classList.toggle("d-none",i!==n));t.classList.remove("d-none");g.textContent=r;nt.textContent=f;tt.textContent=u;t.scrollIntoView({behavior:"smooth",block:"center"})},it=()=>{t.classList.add("d-none"),i.forEach(n=>n.classList.add("d-none"))},k=()=>{document.querySelectorAll(".drop-target-active, .drop-before, .drop-after, .chapter-drop-active, .drop-chapter-end").forEach(n=>n.classList.remove("drop-target-active","drop-before","drop-after","chapter-drop-active","drop-chapter-end"))},rt=()=>{const n=e?.querySelector(".timeline-shell");try{sessionStorage.setItem(s,JSON.stringify({left:n?.scrollLeft||0,top:window.scrollY||0}))}catch{}};try{const n=JSON.parse(sessionStorage.getItem(s)||"null");n&&(sessionStorage.removeItem(s),requestAnimationFrame(()=>{const t=e?.querySelector(".timeline-shell");t&&(t.scrollLeft=n.left||0);window.scrollTo({top:n.top||0})}))}catch{sessionStorage.removeItem(s)}const f=n=>{try{const t=JSON.parse(n.dataTransfer.getData("application/json")||"{}");return t.type?t:h||{}}catch{return h||{}}},ut=(n,t)=>{const i={type:t.dataset.dragType,id:t.dataset.dragId,label:t.dataset.dragLabel||t.textContent.trim()};n.dataTransfer.effectAllowed="copyMove";n.dataTransfer.setData("application/json",JSON.stringify(i));n.dataTransfer.setData("text/plain",`${i.type}:${i.id}`);h=i;document.body.classList.add("drag-active",`dragging-${i.type}`);t.classList.add("is-dragging")};document.querySelectorAll("[data-drag-type][draggable='true']").forEach(n=>{n.addEventListener("dragstart",t=>ut(t,n)),n.addEventListener("dragend",()=>{n.classList.remove("is-dragging"),document.body.classList.remove("drag-active","dragging-scene","dragging-character","dragging-asset","dragging-location"),h=null,k()})});t.querySelectorAll("[data-drag-cancel]").forEach(n=>n.addEventListener("click",it));i.get("scene")?.addEventListener("submit",rt);e?.querySelectorAll(".timeline-scene-card[data-scene-id]").forEach(t=>{t.addEventListener("click",n=>{Date.now(){const i=f(n);["scene","character","asset"].includes(i.type)&&(n.preventDefault(),n.stopPropagation(),n.dataTransfer.dropEffect=i.type==="scene"?"move":"copy",t.classList.add("drop-target-active"),t.classList.toggle("drop-before",i.type==="scene"&&n.offsetX=t.clientWidth/2))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active","drop-before","drop-after")),t.addEventListener("drop",u=>{const o=f(u);if(["scene","character","asset"].includes(o.type)){u.preventDefault();u.stopPropagation();c=Date.now()+800;t.classList.remove("drop-target-active","drop-before","drop-after");const s=t.dataset.sceneId;if(o.type==="scene"){if(o.id===s){r("scene","Cannot move scene","Drop a scene before or after a different scene.","Scene move");return}const f=i.get("scene"),h=u.offsetX{const u=t.closest("[data-chapter-drop-target]"),o=t.dataset.chapterId||u?.dataset.chapterId,s=t.dataset.chapterLabel||u?.dataset.chapterLabel||"this chapter";t.addEventListener("dragover",n=>{const i=f(n);i.type==="scene"&&o&&(n.target.closest(".timeline-scene-card[data-scene-id]")||(n.preventDefault(),n.dataTransfer.dropEffect="move",t.classList.add("drop-target-active","drop-chapter-end"),u?.classList.add("chapter-drop-active")))});t.addEventListener("dragleave",n=>{t.contains(n.relatedTarget)||(t.classList.remove("drop-target-active","drop-chapter-end"),u?.classList.remove("chapter-drop-active"))});t.addEventListener("drop",u=>{const h=f(u);if(h.type==="scene"&&o&&!u.target.closest(".timeline-scene-card[data-scene-id]")){u.preventDefault();u.stopPropagation();c=Date.now()+800;k();const a=e.querySelector(`.timeline-scene-card[data-scene-id='${CSS.escape(h.id)}']`);if(a?.dataset.chapterId===o&&t.lastElementChild?.dataset?.sceneId===h.id){r("scene","Scene already here",`${h.label} is already at the end of ${s}.`,"Scene move");return}const l=i.get("scene");n(l,"SceneID",h.id);n(l,"AnchorSceneID","");n(l,"TargetChapterID",o);n(l,"Position","End");r("scene","Move Scene?",b(h,a?.dataset.chapterLabel||"",s,"After",s),"Scene move")}})});const u=i.get("asset-dependency"),d=()=>{const n=u?.dataset.sourceLabel||"Source asset",i=u?.dataset.targetLabel||"Target asset",f=l(u?.querySelector("[name='AssetDependencyTypeID']"))||"Requires",e=l(u?.querySelector("[name='SourceRequiredStateID']"))||"the required state",o=l(u?.querySelector("[name='TargetBlockedStateID']"))||"the blocked state",r=t.querySelector("[data-asset-dependency-preview]");r&&(r.textContent=`${n} ${f.toLowerCase()} ${i}. ${n} should be ${e} before ${i} can be ${o}.`)};u?.querySelectorAll("select").forEach(n=>n.addEventListener("change",d));document.querySelectorAll("[data-asset-drop-target], .drag-chip.asset").forEach(t=>{t.addEventListener("dragover",n=>{const i=f(n);i.type==="asset"&&(n.preventDefault(),n.dataTransfer.dropEffect="copy",t.classList.add("drop-target-active"))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active")),t.addEventListener("drop",i=>{const e=f(i),o=t.dataset.assetId||t.dataset.dragId,s=t.dataset.assetLabel||t.dataset.dragLabel||t.textContent.trim();if(e.type==="asset"&&o){if(i.preventDefault(),t.classList.remove("drop-target-active"),e.id===o){r("asset-dependency","Cannot create dependency","Drop one asset onto a different asset.","Asset dependency");return}n(u,"SourceAssetID",e.id);n(u,"TargetAssetID",o);u.dataset.sourceLabel=e.label;u.dataset.targetLabel=s;d();r("asset-dependency","Create asset dependency",`${e.label} -> ${s}`,"Asset dependency")}})});document.querySelectorAll("[data-drag-type='location']").forEach(t=>{t.addEventListener("dragover",n=>{const i=f(n);i.type==="location"&&(n.preventDefault(),n.dataTransfer.dropEffect="copy",t.classList.add("drop-target-active"))}),t.addEventListener("dragleave",()=>t.classList.remove("drop-target-active")),t.addEventListener("drop",u=>{const e=f(u),o=t.dataset.dragId,h=t.dataset.dragLabel||t.textContent.trim();if(e.type==="location"){if(u.preventDefault(),t.classList.remove("drop-target-active"),e.id===o){r("location-relationship","Cannot create relationship","Drop one location onto a different location.","Location relationship");return}const s=i.get("location-relationship");n(s,"FromLocationID",e.id);n(s,"ToLocationID",o);r("location-relationship","Create location relationship",`${e.label} -> ${h}`,"Location drop")}})})}})(); \ No newline at end of file