(function(storyContent) { // Create ink story from the content using inkjs var story = new inkjs.Story(storyContent); var savePoint = ""; let savedTheme; let globalTagTheme; // Global tags - those at the top of the ink file // We support: // # theme: dark // # author: Your Name var globalTags = story.globalTags; if( globalTags ) { for(var i=0; i { console.log(`scrollingto ${previousBottomEdge}`) scrollDown(previousBottomEdge) } showAfter(delay, imageElement); delay += 200.0; } // LINK: url else if( splitTag && splitTag.property == "LINK" ) { window.location.href = splitTag.val; } // LINKOPEN: url else if( splitTag && splitTag.property == "LINKOPEN" ) { window.open(splitTag.val); } // BACKGROUND: src else if( splitTag && splitTag.property == "BACKGROUND" ) { outerScrollContainer.style.backgroundImage = 'url('+splitTag.val+')'; } // CLASS: className else if( splitTag && splitTag.property == "CLASS" ) { customClasses.push(splitTag.val); } // CLEAR - removes all existing content. // RESTART - clears everything and restarts the story from the beginning else if( tag == "CLEAR" || tag == "RESTART" ) { removeAll("p"); removeAll("img"); // Comment out this line if you want to leave the header visible when clearing setVisible(".header", false); if( tag == "RESTART" ) { restart(); return; } } } // Check if paragraphText is empty if (paragraphText.trim().length == 0) { continue; // Skip empty paragraphs } // Create paragraph element (initially hidden) var paragraphElement = document.createElement('p'); paragraphElement.innerHTML = paragraphText; storyContainer.appendChild(paragraphElement); // Add any custom classes derived from ink tags for(var i=0; i${choice.text}` }else{ choiceParagraphElement.innerHTML = `${choice.text}` } storyContainer.appendChild(choiceParagraphElement); // Fade choice in after a short delay showAfter(delay, choiceParagraphElement); delay += 200.0; // Click on choice if(isClickable){ var choiceAnchorEl = choiceParagraphElement.querySelectorAll("a")[0]; choiceAnchorEl.addEventListener("click", function(event) { // Don't follow link event.preventDefault(); // Extend height to fit // We do this manually so that removing elements and creating new ones doesn't // cause the height (and therefore scroll) to jump backwards temporarily. storyContainer.style.height = contentBottomEdgeY()+"px"; // Remove all existing choices removeAll(".choice"); // Tell the story where to go next story.ChooseChoiceIndex(choice.index); // This is where the save button will save from savePoint = story.state.toJson(); // Aaand loop continueStory(); }); } }); // Unset storyContainer's height, allowing it to resize itself storyContainer.style.height = ""; if( !firstTime ) scrollDown(previousBottomEdge); } function restart() { story.ResetState(); setVisible(".header", true); // set save point to here savePoint = story.state.toJson(); continueStory(true); outerScrollContainer.scrollTo(0, 0); } // ----------------------------------- // Various Helper functions // ----------------------------------- // Detects whether the user accepts animations function isAnimationEnabled() { return window.matchMedia('(prefers-reduced-motion: no-preference)').matches; } // Fades in an element after a specified delay function showAfter(delay, el) { if( isAnimationEnabled() ) { el.classList.add("hide"); setTimeout(function() { el.classList.remove("hide") }, delay); } else { // If the user doesn't want animations, show immediately el.classList.remove("hide"); } } // Scrolls the page down, but no further than the bottom edge of what you could // see previously, so it doesn't go too far. function scrollDown(previousBottomEdge) { // If the user doesn't want animations, let them scroll manually if ( !isAnimationEnabled() ) { return; } // Line up top of screen with the bottom of where the previous content ended var target = previousBottomEdge; // Can't go further than the very bottom of the page var limit = outerScrollContainer.scrollHeight - outerScrollContainer.clientHeight; if( target > limit ) target = limit; var start = outerScrollContainer.scrollTop; var dist = target - start; var duration = 300 + 300*dist/100; var startTime = null; function step(time) { if( startTime == null ) startTime = time; var t = (time-startTime) / duration; var lerp = 3*t*t - 2*t*t*t; // ease in/out outerScrollContainer.scrollTo(0, (1.0-lerp)*start + lerp*target); if( t < 1 ) requestAnimationFrame(step); } requestAnimationFrame(step); } // The Y coordinate of the bottom end of all the story content, used // for growing the container, and deciding how far to scroll. function contentBottomEdgeY() { var bottomElement = storyContainer.lastElementChild; return bottomElement ? bottomElement.offsetTop + bottomElement.offsetHeight : 0; } // Remove all elements that match the given selector. Used for removing choices after // you've picked one, as well as for the CLEAR and RESTART tags. function removeAll(selector) { var allElements = storyContainer.querySelectorAll(selector); for(var i=0; i