{"id":7151,"date":"2026-10-13T16:28:24","date_gmt":"2026-10-13T16:28:24","guid":{"rendered":"https:\/\/schoolict.net\/tools\/?page_id=7151"},"modified":"2026-10-13T16:28:27","modified_gmt":"2026-10-13T16:28:27","slug":"ai-story-script-generator","status":"publish","type":"page","link":"https:\/\/schoolict.net\/tools\/ai-story-script-generator\/","title":{"rendered":"AI Story Script Generator"},"content":{"rendered":"<div class=\"pth-hero-section\">\n<div class=\"pth-hero-content\">\n<h2>AI Story Script Generator \u2014 Write &#038; Edit Narrated Stories<\/h2>\n<p>Generate a 4-act narrated script from your own instruction, then redirect any single section with your own idea and aim \u2014 never the whole script. Keeps a Story Bible for character\/setting consistency, splits into sentence-safe editable parts, keeps a 3-version Undo history with Old vs New compare, and flags unsafe wording. Bring your own key (Gemini, OpenAI or Claude).<\/p>\n<div id=\"pth-toc-placeholder\"><\/div>\n<\/p><\/div>\n<div class=\"pth-hero-image\">\n    <img data-no-lazy=\"1\"\n         src=\"https:\/\/schoolict.net\/wp-content\/uploads\/2026\/09\/AI_story_script_generator-800x447.jpg\"\n         width=\"800\"\n         height=\"447\"\n         alt=\"ai-story-script-generator\"\n         fetchpriority=\"high\"\n         loading=\"eager\"\n         decoding=\"async\"\n         style=\"width:100%; height:auto; display:block;\">\n  <\/div>\n<\/div>\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#\ud83d\udd34-who-actually-reaches-for-this-ai-story-generator\">\ud83d\udd34 Who actually reaches for this AI story generator<\/a><\/li><li><a href=\"#\ud83d\udfe1-what-redirect-a-section-actually-does\">\ud83d\udfe1 What &#8220;redirect a section&#8221; actually does<\/a><\/li><li><a href=\"#\ud83d\udfe2-common-mistakes\">\ud83d\udfe2 Common mistakes<\/a><\/li><li><a href=\"#\ud83d\udd34-honest-limitations\">\ud83d\udd34 Honest limitations<\/a><\/li><\/ul><\/nav><\/div>\n\n\n<!-- noptimize -->\r\n<div id=\"sict-storygen-wrap\">\r\n<style>\r\n#sict-storygen-wrap{box-sizing:border-box;min-width:0;max-width:100%;width:100%;margin:0;font-family:'Inter',system-ui,-apple-system,\"Segoe UI\",Roboto,Arial,sans-serif;color:#1e293b;}\r\n#sict-storygen-wrap *{box-sizing:border-box;min-width:0;}\r\n#sict-storygen-wrap h2{font-size:1.5rem;font-weight:800;color:#0f172a;margin:0 0 4px;}\r\n#sict-storygen-wrap h3{font-size:1.05rem;font-weight:800;color:#0f172a;margin:0 0 10px;}\r\n#sict-storygen-wrap p{margin:0 0 10px;color:#1e293b;}\r\n#sict-storygen-wrap .sg-meta{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 14px;}\r\n#sict-storygen-wrap .sg-badge{display:inline-flex;align-items:center;gap:5px;font-size:.72rem;font-weight:700;padding:4px 10px;border-radius:999px;background:#eef2f7;color:#334155;}\r\n#sict-storygen-wrap .sg-badge.sg-ai{background:#fef3c7;color:#92400e;}\r\n#sict-storygen-wrap .sg-btnrow{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 16px;}\r\n#sict-storygen-wrap button{font-family:inherit;font-size:.85rem;font-weight:700;border-radius:8px;padding:9px 14px;cursor:pointer;border:1px solid transparent;line-height:1.2;}\r\n#sict-storygen-wrap button:disabled{opacity:.55;cursor:not-allowed;}\r\n#sict-storygen-wrap .sg-btn-primary{background:#2563eb;color:#fff;}\r\n#sict-storygen-wrap .sg-btn-secondary{background:#fff;color:#1d4ed8;border-color:#c7d2fe;}\r\n#sict-storygen-wrap .sg-btn-success{background:#059669;color:#fff;}\r\n#sict-storygen-wrap .sg-btn-danger{background:#fff;color:#dc2626;border-color:#fecaca;}\r\n#sict-storygen-wrap .sg-btn-ghost{background:#f1f5f9;color:#334155;}\r\n#sict-storygen-wrap .sg-btn-sm{padding:6px 10px;font-size:.75rem;}\r\n#sict-storygen-wrap .sg-grid{display:grid;grid-template-columns:1.8fr 1fr;gap:18px;align-items:start;}\r\n#sict-storygen-wrap.sg-full .sg-grid{grid-template-columns:1fr;}\r\n#sict-storygen-wrap.sg-full{position:fixed;top:0;left:0;right:0;bottom:0;z-index:99998;max-width:100%;width:100%;height:100%;margin:0;padding:20px;overflow:auto;-webkit-overflow-scrolling:touch;background:#f8fafc;}\r\nbody.sg-locked{overflow:hidden;}\r\n#sict-storygen-wrap .sg-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;margin:0 0 16px;}\r\n#sict-storygen-wrap label{display:block;font-size:.78rem;font-weight:700;color:#334155;margin:0 0 5px;}\r\n#sict-storygen-wrap input[type=text],#sict-storygen-wrap input[type=password],#sict-storygen-wrap input[type=number],#sict-storygen-wrap select,#sict-storygen-wrap textarea{width:100%;font-family:inherit;font-size:.85rem;padding:8px 10px;border:1px solid #cbd5e1;border-radius:8px;color:#1e293b;background:#fff;margin:0 0 12px;}\r\n#sict-storygen-wrap textarea{resize:vertical;line-height:1.55;}\r\n#sict-storygen-wrap .sg-field-row{display:flex;gap:10px;}\r\n#sict-storygen-wrap .sg-field-row > div{flex:1;}\r\n#sict-storygen-wrap .sg-act{border:1px solid #e2e8f0;border-radius:12px;padding:16px;margin:0 0 16px;background:#fff;}\r\n#sict-storygen-wrap .sg-act-head{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin:0 0 8px;}\r\n#sict-storygen-wrap .sg-act-title{font-weight:800;color:#0f172a;font-size:1rem;}\r\n#sict-storygen-wrap .sg-wc{font-size:.72rem;color:#64748b;font-weight:600;}\r\n#sict-storygen-wrap .sg-actbtns{display:flex;gap:6px;flex-wrap:wrap;}\r\n#sict-storygen-wrap .sg-compare{display:grid;grid-template-columns:1fr 1fr;gap:10px;background:#fffbeb;border:1px solid #fde68a;border-radius:10px;padding:12px;margin:10px 0;}\r\n#sict-storygen-wrap .sg-compare h4{margin:0 0 6px;font-size:.78rem;font-weight:800;color:#92400e;}\r\n#sict-storygen-wrap .sg-compare-text{white-space:pre-wrap;font-size:.82rem;line-height:1.5;max-height:220px;overflow:auto;background:#fff;border:1px solid #fde68a;border-radius:8px;padding:8px;}\r\n#sict-storygen-wrap .sg-flags{background:#fef2f2;border:1px solid #fecaca;border-radius:8px;padding:8px 10px;margin:8px 0;font-size:.8rem;}\r\n#sict-storygen-wrap .sg-flags ul{margin:6px 0 0;padding-left:18px;}\r\n#sict-storygen-wrap .sg-flags li{margin:0 0 6px;color:#991b1b;}\r\n#sict-storygen-wrap .sg-history{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:8px 10px;margin:8px 0;font-size:.78rem;}\r\n#sict-storygen-wrap .sg-history-row{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:4px 0;border-top:1px dashed #e2e8f0;}\r\n#sict-storygen-wrap .sg-history-row:first-child{border-top:none;}\r\n#sict-storygen-wrap .sg-output{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:10px;white-space:pre-wrap;font-size:.85rem;line-height:1.6;min-height:60px;}\r\n#sict-storygen-wrap .sg-status{font-size:.75rem;color:#64748b;margin:0 0 10px;}\r\n#sict-storygen-wrap .sg-toast-wrap{position:fixed;bottom:16px;right:16px;z-index:999999;display:flex;flex-direction:column;gap:8px;}\r\n#sict-storygen-wrap .sg-toast{padding:10px 14px;border-radius:8px;color:#fff;font-size:.85rem;font-weight:700;box-shadow:0 4px 12px rgba(0,0,0,.15);}\r\n#sict-storygen-wrap .sg-lib-row{display:flex;gap:8px;margin:0 0 10px;}\r\n#sict-storygen-wrap .sg-lib-row select{margin:0;}\r\n#sict-storygen-wrap .sg-tabs{display:flex;gap:6px;margin:0 0 14px;border-bottom:2px solid #e2e8f0;}\r\n#sict-storygen-wrap .sg-tab{background:#f1f5f9;color:#334155;border:1px solid #e2e8f0;border-bottom:none;border-radius:8px 8px 0 0;padding:9px 16px;}\r\n#sict-storygen-wrap .sg-tab.sg-tab-active{background:#2563eb;color:#fff;border-color:#2563eb;}\r\n@media(max-width:768px){#sict-storygen-wrap .sg-grid{grid-template-columns:1fr;}#sict-storygen-wrap .sg-compare{grid-template-columns:1fr;}#sict-storygen-wrap .sg-field-row{flex-direction:column;}}\r\n<\/style>\r\n\r\n<div class=\"sg-meta\">\r\n  <h2>AI Story Script Generator<\/h2>\r\n  <span class=\"sg-badge sg-version\">v1.2<\/span>\r\n  <span class=\"sg-badge sg-ai\">AI-Powered \/ Needs Internet<\/span>\r\n<\/div>\r\n<p style=\"color:#475569;\">Generate a 4-act narrated story from your own instruction (MD), edit and redirect any act, convert to natural spoken Sinhala, and export a clean script.<\/p>\r\n\r\n<div class=\"sg-btnrow\">\r\n  <button type=\"button\" class=\"sg-btn-secondary\" id=\"sg-load-sample\">Load Sample<\/button>\r\n  <button type=\"button\" class=\"sg-btn-ghost\" id=\"sg-fullwidth\">Full Width<\/button>\r\n  <button type=\"button\" class=\"sg-btn-danger\" id=\"sg-clear-all\">Clear Project<\/button>\r\n<\/div>\r\n\r\n<div class=\"sg-grid\">\r\n\r\n<div class=\"sg-main\">\r\n\r\n  <div class=\"sg-tabs\" role=\"tablist\">\r\n    <button type=\"button\" class=\"sg-tab sg-tab-active\" id=\"sg-tab-story-btn\" role=\"tab\">Story<\/button>\r\n    <button type=\"button\" class=\"sg-tab\" id=\"sg-tab-parts-btn\" role=\"tab\">Parts (mini sections)<\/button>\r\n  <\/div>\r\n\r\n  <div id=\"sg-panel-story\">\r\n\r\n  <div class=\"sg-card\" id=\"sg-import-card\">\r\n    <h3>Import an Existing Story (optional)<\/h3>\r\n    <p class=\"sg-status\">Already have a script? Paste or upload it and split it into editable acts instead of generating from scratch.<\/p>\r\n    <label for=\"sg-import-text\">Paste existing story text<\/label>\r\n    <textarea id=\"sg-import-text\" rows=\"5\" aria-label=\"Paste existing story text\"><\/textarea>\r\n    <div class=\"sg-field-row\">\r\n      <div>\r\n        <label for=\"sg-import-file\">Or upload a .txt \/ .md file<\/label>\r\n        <input type=\"file\" id=\"sg-import-file\" accept=\".txt,.md\" aria-label=\"Upload story file\">\r\n      <\/div>\r\n      <div>\r\n        <label for=\"sg-import-parts\">Split into how many acts?<\/label>\r\n        <input type=\"number\" id=\"sg-import-parts\" value=\"4\" min=\"1\" max=\"8\" aria-label=\"Number of acts to split into\">\r\n      <\/div>\r\n    <\/div>\r\n    <button type=\"button\" class=\"sg-btn-primary\" id=\"sg-import-split\">Split Into Acts<\/button>\r\n  <\/div>\r\n\r\n  <div class=\"sg-card\" id=\"sg-premise-card\">\r\n    <h3>New Story Premise<\/h3>\r\n    <label for=\"sg-premise\">Story idea \/ premise<\/label>\r\n    <textarea id=\"sg-premise\" rows=\"3\" aria-label=\"Story premise\"><\/textarea>\r\n    <div class=\"sg-field-row\">\r\n      <div>\r\n        <label for=\"sg-bible-chars\">Characters (names, short note)<\/label>\r\n        <input type=\"text\" id=\"sg-bible-chars\" aria-label=\"Characters\">\r\n      <\/div>\r\n      <div>\r\n        <label for=\"sg-bible-village\">Village \/ setting<\/label>\r\n        <input type=\"text\" id=\"sg-bible-village\" aria-label=\"Village or setting\">\r\n      <\/div>\r\n    <\/div>\r\n    <div class=\"sg-field-row\">\r\n      <div>\r\n        <label for=\"sg-bible-era\">Era<\/label>\r\n        <input type=\"text\" id=\"sg-bible-era\" aria-label=\"Era\">\r\n      <\/div>\r\n      <div>\r\n        <label for=\"sg-bible-rule\">Supernatural rule<\/label>\r\n        <input type=\"text\" id=\"sg-bible-rule\" aria-label=\"Supernatural rule\">\r\n      <\/div>\r\n    <\/div>\r\n    <button type=\"button\" class=\"sg-btn-primary\" id=\"sg-start-story\">Start New Story (Act 1)<\/button>\r\n  <\/div>\r\n  \r\n  <!-- NEW: Spoken Sinhala Converter -->\r\n  <div class=\"sg-card\" style=\"background:#eef2ff; border-color:#c7d2fe;\">\r\n    <h3>Spoken Sinhala Converter<\/h3>\r\n    <p class=\"sg-status\" style=\"color:#4f46e5;\">Convert all generated acts from formal written language (\u0dbd\u0dd2\u0d9b\u0dd2\u0dad \u0db7\u0dcf\u0dc2\u0dcf\u0dc0) to natural spoken language (\u0dc3\u0dca\u0dc0\u0dcf\u0db7\u0dcf\u0dc0\u0dd2\u0d9a \u0d9a\u0dad\u0dcf \u0d9a\u0dbb\u0db1 \u0db7\u0dcf\u0dc2\u0dcf\u0dc0) suitable for YouTube storytelling. This will replace the text in all acts.<\/p>\r\n    <button type=\"button\" class=\"sg-btn-primary\" id=\"sg-convert-spoken\" style=\"background:#4f46e5;\">Convert Entire Story to Spoken Sinhala<\/button>\r\n  <\/div>\r\n\r\n  <div id=\"sg-acts-holder\"><\/div>\r\n\r\n  <div class=\"sg-card\">\r\n    <h3>Continuity Checker<\/h3>\r\n    <label for=\"sg-key-names\">Key names to check (comma separated)<\/label>\r\n    <input type=\"text\" id=\"sg-key-names\" aria-label=\"Key names to check\">\r\n    <button type=\"button\" class=\"sg-btn-secondary sg-btn-sm\" id=\"sg-check-continuity\">Check Continuity<\/button>\r\n    <div id=\"sg-continuity-result\"><\/div>\r\n  <\/div>\r\n\r\n  <div class=\"sg-card\">\r\n    <h3>SEO Data (optional)<\/h3>\r\n    <button type=\"button\" class=\"sg-btn-secondary\" id=\"sg-gen-seo\">Generate SEO<\/button>\r\n    <div class=\"sg-output\" id=\"sg-seo-output\" style=\"margin-top:10px;display:none;\"><\/div>\r\n  <\/div>\r\n\r\n  <div class=\"sg-card\">\r\n    <h3>Vocabulary List (optional)<\/h3>\r\n    <button type=\"button\" class=\"sg-btn-secondary\" id=\"sg-gen-vocab\">Generate Vocabulary List<\/button>\r\n    <div class=\"sg-output\" id=\"sg-vocab-output\" style=\"margin-top:10px;display:none;\"><\/div>\r\n  <\/div>\r\n\r\n  <!-- UPDATED: Export Area -->\r\n  <div class=\"sg-card\">\r\n    <h3>Export<\/h3>\r\n    <div class=\"sg-btnrow\">\r\n      <button type=\"button\" class=\"sg-btn-success\" id=\"sg-export-full\">Download Full (.md)<\/button>\r\n      <button type=\"button\" class=\"sg-btn-success\" id=\"sg-export-clean\">Download Clean (.txt)<\/button>\r\n      <button type=\"button\" class=\"sg-btn-primary\" id=\"sg-export-word\">Download Word (.doc)<\/button>\r\n      <button type=\"button\" class=\"sg-btn-primary\" id=\"sg-export-pdf\">Print \/ Save as PDF<\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <\/div>\r\n\r\n  <div id=\"sg-panel-parts\" style=\"display:none;\">\r\n    <div class=\"sg-card\">\r\n      <h3>Split the Full Story Into Mini Sections<\/h3>\r\n      <p class=\"sg-status\">Uses the text from the Story tab. Sentences are never cut in the middle. Edit any mini section, then merge everything back.<\/p>\r\n      <div class=\"sg-field-row\">\r\n        <div>\r\n          <label for=\"sg-parts-words\">Words per part<\/label>\r\n          <input type=\"number\" id=\"sg-parts-words\" value=\"150\" min=\"30\" max=\"2000\" aria-label=\"Words per part\">\r\n        <\/div>\r\n        <div>\r\n          <label> <\/label>\r\n          <button type=\"button\" class=\"sg-btn-primary\" id=\"sg-parts-split\">Split Story Into Parts<\/button>\r\n        <\/div>\r\n      <\/div>\r\n      <p class=\"sg-status\" id=\"sg-parts-status\"><\/p>\r\n      <div class=\"sg-btnrow\">\r\n        <button type=\"button\" class=\"sg-btn-success\" id=\"sg-parts-merge\">Merge Parts Back Into Story<\/button>\r\n      <\/div>\r\n    <\/div>\r\n    <div id=\"sg-parts-holder\"><\/div>\r\n  <\/div>\r\n<\/div>\r\n\r\n<div class=\"sg-side\">\r\n\r\n  <div class=\"sg-card\">\r\n    <h3>Story Library<\/h3>\r\n    <div class=\"sg-lib-row\">\r\n      <select id=\"sg-lib-select\" aria-label=\"Saved stories\"><\/select>\r\n    <\/div>\r\n    <div class=\"sg-btnrow\">\r\n      <button type=\"button\" class=\"sg-btn-secondary sg-btn-sm\" id=\"sg-lib-new\">New<\/button>\r\n      <button type=\"button\" class=\"sg-btn-ghost sg-btn-sm\" id=\"sg-lib-rename\">Rename<\/button>\r\n      <button type=\"button\" class=\"sg-btn-danger sg-btn-sm\" id=\"sg-lib-delete\">Delete<\/button>\r\n    <\/div>\r\n    <div id=\"sg-rename-row\" style=\"display:none;\">\r\n      <label for=\"sg-rename-input\">New name<\/label>\r\n      <input type=\"text\" id=\"sg-rename-input\" aria-label=\"New story name\">\r\n      <div class=\"sg-btnrow\">\r\n        <button type=\"button\" class=\"sg-btn-primary sg-btn-sm\" id=\"sg-rename-save\">Save Name<\/button>\r\n        <button type=\"button\" class=\"sg-btn-ghost sg-btn-sm\" id=\"sg-rename-cancel\">Cancel<\/button>\r\n      <\/div>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div class=\"sg-card\">\r\n    <h3>AI Provider<\/h3>\r\n    <label for=\"sg-provider\">Provider<\/label>\r\n    <select id=\"sg-provider\" aria-label=\"AI provider\">\r\n      <option value=\"gemini\">Google Gemini<\/option>\r\n      <option value=\"openai\">OpenAI<\/option>\r\n      <option value=\"anthropic\">Anthropic<\/option>\r\n    <\/select>\r\n    <label for=\"sg-model\">Model<\/label>\r\n    <input type=\"text\" id=\"sg-model\" aria-label=\"Model name\">\r\n    <label for=\"sg-apikey\">API Key (stays in your browser)<\/label>\r\n    <input type=\"password\" id=\"sg-apikey\" aria-label=\"API key\">\r\n  <\/div>\r\n\r\n  <div class=\"sg-card\">\r\n    <h3>Instruction (MD)<\/h3>\r\n    <a href=\"https:\/\/schoolict.net\/sampledata\/holman-katha-story-writer-TOOL-v1.0.md\" target=\"_blank\" download style=\"font-size: .8rem; color: #2563eb; display: inline-block; margin-bottom: 10px; text-decoration: underline; font-weight: 600;\">\ud83d\udce5 Download Sample Instruction (.md)<\/a>\r\n    <p class=\"sg-status\" id=\"sg-md-status\">Using built-in generic instruction.<\/p>\r\n    <label for=\"sg-md-url\">Load from URL<\/label>\r\n    <input type=\"text\" id=\"sg-md-url\" placeholder=\"https:\/\/schoolict.net\/sampledata\/holman-katha-story-writer-TOOL-v1.0.md\" aria-label=\"Instruction URL\">\r\n    <button type=\"button\" class=\"sg-btn-secondary sg-btn-sm\" id=\"sg-md-fetch\">Fetch<\/button>\r\n    <label for=\"sg-md-paste\" style=\"margin-top:10px;\">Or paste your instruction here<\/label>\r\n    <textarea id=\"sg-md-paste\" rows=\"5\" aria-label=\"Paste instruction\"><\/textarea>\r\n    <button type=\"button\" class=\"sg-btn-primary sg-btn-sm\" id=\"sg-md-save\">Use This Instruction<\/button>\r\n  <\/div>\r\n\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n<div class=\"sg-toast-wrap\" id=\"sg-toast-wrap\"><\/div>\r\n\r\n<\/div>\r\n\r\n<script data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\">\r\n(function () {\r\n  'use strict';\r\n\r\n  var ACT_LABELS = ['Act 1 - Setup', 'Act 2 - Escalation', 'Act 3 - Deepening Dread', 'Act 4 - Climax'];\r\n  var AIM_OPTIONS = [\r\n    { value: '', label: 'Aim (choose one)' },\r\n    { value: 'expand-detail', label: 'Add more detail \/ description' },\r\n    { value: 'more-fear', label: 'Increase fear \/ dread' },\r\n    { value: 'add-event', label: 'Add a new event' },\r\n    { value: 'speed-up', label: 'Speed up the pacing' },\r\n    { value: 'add-dialogue', label: 'Add more dialogue' },\r\n    { value: 'tighten', label: 'Shorten \/ tighten' },\r\n    { value: 'spoken', label: 'Make it more natural spoken style' },\r\n    { value: 'custom', label: 'Custom (use idea box only)' }\r\n  ];\r\n\r\n  var FORBIDDEN_FORMAL = [\r\n    \"u0DC4u0DD2u0DB8u0DD2 u0DC0u0DD2u0DBA\",\r\n    \"u0DB4u0DD2u0DBAu0DCF u0DC0u0DD2u0DBA\",\r\n    \"u0D87u0DC3u0DD4u0DABu0DD2\",\r\n    \"u0DB8u0DD2u0DBA u0DB4u0DBBu0DBDu0DDCu0DC0 u0D9Cu0DD2u0DBAu0DDAu0DBA\",\r\n    \"u0D9Au0DBBu0DB1 u0DBDu0DAFu0DD3\"\r\n  ];\r\n  var FORBIDDEN_GORE = [\r\n    \"u0DB8u0DC3u0DCA u0DAFu0DD2u0DBAu0DC0u0DD9u0DBDu0DCF\",\r\n    \"u0D87u0DC3u0DCA u0D9Cu0DD9u0DA9u0DD2 u0D91u0DC5u0DD2u0DA7 u0DB4u0DD0u0DB1u0DB4u0DD4\",\r\n    \"u0DBDu0DDA u0D9Cu0DD0u0DBDu0DD3u0DB8\",\r\n    \"u0D95u0DA2u0DC3u0DCA u0D9Cu0DBDu0DB1\",\r\n    \"u0D9Au0DD1u0DBDu0DD2 u0D9Au0DD0u0DB4u0DD3u0DB8\"\r\n  ];\r\n\r\n  var FALLBACK_MD =\r\n    'You are a careful narrative fiction writer. Write a continuous, immersive, PG-rated ' +\r\n    'story in four acts based on the given premise and story bible. Keep character names, ' +\r\n    'setting and the core supernatural or dramatic rule fully consistent across every act. ' +\r\n    'Never use graphic violence or gore. Do not greet the reader or break the fourth wall. ' +\r\n    'Mark the start of each act clearly.';\r\n\r\n  var LS = {\r\n    library: 'sictStoryGenLibrary',\r\n    active: 'sictStoryGenActive',\r\n    keys: 'sictStoryGenKeys',\r\n    md: 'sictStoryGenMD'\r\n  };\r\n\r\n  var DEFAULT_MODELS = { gemini: 'gemini-3.5-flash', openai: 'gpt-4o-mini', anthropic: 'claude-3-5-sonnet-latest' };\r\n\r\n  var state = {\r\n    library: [],\r\n    activeId: null,\r\n    mdText: FALLBACK_MD,\r\n    mdSource: 'fallback',\r\n    pendingCompare: {}\r\n  };\r\n\r\n  function $(id) { return document.getElementById(id); }\r\n\r\n  function newProjectObj(name, premise) {\r\n    return {\r\n      id: 'p' + Date.now() + Math.floor(Math.random() * 1000),\r\n      name: name || 'Untitled Story',\r\n      premise: premise || '',\r\n      bible: { characters: '', village: '', era: '', rule: '' },\r\n      acts: [\r\n        { label: ACT_LABELS[0], text: '', aim: '', idea: '', history: [] },\r\n        { label: ACT_LABELS[1], text: '', aim: '', idea: '', history: [] },\r\n        { label: ACT_LABELS[2], text: '', aim: '', idea: '', history: [] },\r\n        { label: ACT_LABELS[3], text: '', aim: '', idea: '', history: [] }\r\n      ],\r\n      parts: [],\r\n      seoText: '',\r\n      vocabText: '',\r\n      updatedAt: Date.now()\r\n    }\r\n  }\r\n\r\n  function getActive() {\r\n    var i;\r\n    for (i = 0; i < state.library.length; i++) {\r\n      if (state.library[i].id === state.activeId) { return state.library[i]; }\r\n    }\r\n    return null;\r\n  }\r\n\r\n  function saveLibrary() {\r\n    try { window.localStorage.setItem(LS.library, JSON.stringify(state.library)); } catch (e) {}\r\n    try { window.localStorage.setItem(LS.active, state.activeId || ''); } catch (e) {}\r\n  }\r\n\r\n  function loadLibrary() {\r\n    var raw, id;\r\n    try { raw = window.localStorage.getItem(LS.library); } catch (e) { raw = null; }\r\n    if (raw) {\r\n      try { state.library = JSON.parse(raw) || []; } catch (e) { state.library = []; }\r\n    }\r\n    try { id = window.localStorage.getItem(LS.active); } catch (e) { id = null; }\r\n    if (id && getActiveById(id)) { state.activeId = id; }\r\n    else if (state.library.length) { state.activeId = state.library[0].id; }\r\n    else {\r\n      var p = newProjectObj('My First Story', '');\r\n      state.library.push(p);\r\n      state.activeId = p.id;\r\n    }\r\n  }\r\n\r\n  function getActiveById(id) {\r\n    var i;\r\n    for (i = 0; i < state.library.length; i++) { if (state.library[i].id === id) { return state.library[i]; } }\r\n    return null;\r\n  }\r\n\r\n  function renderLibrarySelect() {\r\n    var sel = $('sg-lib-select');\r\n    if (!sel) { return; }\r\n    sel.innerHTML = '';\r\n    var i, opt;\r\n    for (i = 0; i < state.library.length; i++) {\r\n      opt = document.createElement('option');\r\n      opt.value = state.library[i].id;\r\n      opt.textContent = state.library[i].name;\r\n      if (state.library[i].id === state.activeId) { opt.selected = true; }\r\n      sel.appendChild(opt);\r\n    }\r\n  }\r\n\r\n  function showToast(msg, type) {\r\n    var wrap = $('sg-toast-wrap');\r\n    if (!wrap) { return; }\r\n    var el = document.createElement('div');\r\n    el.className = 'sg-toast';\r\n    el.setAttribute('role', 'status');\r\n    el.setAttribute('aria-live', 'polite');\r\n    el.style.background = (type === 'error') ? '#dc2626' : (type === 'success') ? '#059669' : '#2563eb';\r\n    el.textContent = msg;\r\n    wrap.appendChild(el);\r\n    setTimeout(function () { if (el.parentNode) { el.parentNode.removeChild(el); } }, 3200);\r\n  }\r\n\r\n  function wordCount(text) {\r\n    var t = (text || '').replace(\/^s+|s+$\/g, '');\r\n    if (!t) { return 0; }\r\n    return t.split(\/s+\/).length;\r\n  }\r\n\r\n  function apiKeys() {\r\n    var raw, obj;\r\n    try { raw = window.localStorage.getItem(LS.keys); } catch (e) { raw = null; }\r\n    obj = {};\r\n    if (raw) { try { obj = JSON.parse(raw) || {}; } catch (e) { obj = {}; } }\r\n    return obj;\r\n  }\r\n\r\n  function saveApiKey(provider, key) {\r\n    var obj = apiKeys();\r\n    obj[provider] = key;\r\n    try { window.localStorage.setItem(LS.keys, JSON.stringify(obj)); } catch (e) {}\r\n  }\r\n\r\n  function callAI(systemPrompt, userPrompt, cb) {\r\n    var provider = $('sg-provider').value;\r\n    var model = $('sg-model').value || DEFAULT_MODELS[provider];\r\n    var key = $('sg-apikey').value;\r\n    if (!key) { showToast('Please enter an API key first.', 'error'); return; }\r\n\r\n    if (provider === 'gemini') {\r\n      fetch('https:\/\/generativelanguage.googleapis.com\/v1beta\/models\/' + model + ':generateContent?key=' + encodeURIComponent(key), {\r\n        method: 'POST',\r\n        headers: { 'Content-Type': 'application\/json' },\r\n        body: JSON.stringify({\r\n          system_instruction: { parts: [{ text: systemPrompt }] },\r\n          contents: [{ role: 'user', parts: [{ text: userPrompt }] }]\r\n        })\r\n      }).then(function (r) { return r.json(); }).then(function (data) {\r\n        var text = '';\r\n        try { text = data.candidates[0].content.parts[0].text; } catch (e) { text = ''; }\r\n        if (!text) { showToast('No response from Gemini. Check your key\/model.', 'error'); return; }\r\n        cb(text);\r\n      }).catch(function () { showToast('Gemini request failed.', 'error'); });\r\n    } else if (provider === 'openai') {\r\n      fetch('https:\/\/api.openai.com\/v1\/chat\/completions', {\r\n        method: 'POST',\r\n        headers: { 'Content-Type': 'application\/json', 'Authorization': 'Bearer ' + key },\r\n        body: JSON.stringify({\r\n          model: model,\r\n          messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }]\r\n        })\r\n      }).then(function (r) { return r.json(); }).then(function (data) {\r\n        var text = '';\r\n        try { text = data.choices[0].message.content; } catch (e) { text = ''; }\r\n        if (!text) { showToast('No response from OpenAI. Check your key\/model.', 'error'); return; }\r\n        cb(text);\r\n      }).catch(function () { showToast('OpenAI request failed.', 'error'); });\r\n    } else {\r\n      fetch('https:\/\/api.anthropic.com\/v1\/messages', {\r\n        method: 'POST',\r\n        headers: {\r\n          'Content-Type': 'application\/json',\r\n          'x-api-key': key,\r\n          'anthropic-version': '2023-06-01',\r\n          'anthropic-dangerous-direct-browser-access': 'true'\r\n        },\r\n        body: JSON.stringify({\r\n          model: model,\r\n          max_tokens: 4096,\r\n          system: systemPrompt,\r\n          messages: [{ role: 'user', content: userPrompt }]\r\n        })\r\n      }).then(function (r) { return r.json(); }).then(function (data) {\r\n        var text = '';\r\n        try { text = data.content[0].text; } catch (e) { text = ''; }\r\n        if (!text) { showToast('No response from Anthropic. Check your key\/model.', 'error'); return; }\r\n        cb(text);\r\n      }).catch(function () { showToast('Anthropic request failed.', 'error'); });\r\n    }\r\n  }\r\n\r\n  function bibleText(p) {\r\n    return 'Characters: ' + p.bible.characters + 'nSetting\/village: ' + p.bible.village +\r\n      'nEra: ' + p.bible.era + 'nSupernatural rule: ' + p.bible.rule;\r\n  }\r\n\r\n  function storyContext(p, uptoIdx) {\r\n    var out = [];\r\n    var i;\r\n    for (i = 0; i < uptoIdx; i++) {\r\n      if (p.acts[i].text) { out.push('[' + p.acts[i].label + ']n' + p.acts[i].text); }\r\n    }\r\n    return out.join('nn');\r\n  }\r\n\r\n  function fullStoryText(p) {\r\n    var out = [];\r\n    var i;\r\n    for (i = 0; i < p.acts.length; i++) {\r\n      if (p.acts[i].text) { out.push('[' + p.acts[i].label + ']n' + p.acts[i].text); }\r\n    }\r\n    return out.join('nn');\r\n  }\r\n\r\n  function generateAct(idx) {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    var sys = state.mdText;\r\n    var user = 'PREMISE: ' + p.premise + 'nnSTORY BIBLE (must stay fully consistent):n' + bibleText(p) +\r\n      'nnPREVIOUS ACTS SO FAR:n' + (storyContext(p, idx) || '(none yet - this is the first act)') +\r\n      'nnNow write ONLY ' + p.acts[idx].label + '. Continue seamlessly with no repetition, no contradiction, no changed names. Do not write any other act.';\r\n    setActLoading('a', idx, true);\r\n    callAI(sys, user, function (text) {\r\n      setActLoading('a', idx, false);\r\n      p.acts[idx].text = text;\r\n      p.updatedAt = Date.now();\r\n      saveLibrary();\r\n      renderActs();\r\n      showToast(p.acts[idx].label + ' generated.', 'success');\r\n    });\r\n  }\r\n\r\n  function listOf(p, kind) { return (kind === 'p') ? p.parts : p.acts; }\r\n\r\n  function renderKind(kind) { if (kind === 'p') { renderParts(); } else { renderActs(); } }\r\n\r\n  function aimLabel(val) {\r\n    var i;\r\n    for (i = 0; i < AIM_OPTIONS.length; i++) { if (AIM_OPTIONS[i].value === val) { return AIM_OPTIONS[i].label; } }\r\n    return '';\r\n  }\r\n\r\n  function regenerateItem(kind, idx) {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    var item = listOf(p, kind)[idx];\r\n    var aimText = aimLabel(item.aim);\r\n    var ctx, label, lengthNote;\r\n    if (kind === 'p') {\r\n      label = 'Part ' + (idx + 1);\r\n      var prevT = idx > 0 ? p.parts[idx - 1].text : '(this is the first part)';\r\n      var nextT = idx < p.parts.length - 1 ? p.parts[idx + 1].text : '(this is the last part)';\r\n      ctx = 'PART BEFORE:n' + prevT + 'nnPART AFTER:n' + nextT;\r\n      lengthNote = ' Keep it close to ' + wordCount(item.text) + ' words unless the direction asks for more or less, and make it connect smoothly with the part before and the part after.';\r\n    } else {\r\n      label = item.label;\r\n      ctx = 'OTHER ACTS FOR CONTEXT:n' + fullStoryText(p);\r\n      lengthNote = '';\r\n    }\r\n    var sys = state.mdText;\r\n    var user = 'PREMISE: ' + p.premise + 'nnSTORY BIBLE (must stay fully consistent):n' + bibleText(p) +\r\n      'nn' + ctx +\r\n      'nnNow REWRITE only ' + label + ' (current version below), applying this direction: ' +\r\n      (aimText || 'follow the idea below') + '. Extra idea from the writer: ' + (item.idea || '(none)') +\r\n      'nnCURRENT VERSION:n' + item.text +\r\n      'nnKeep character names, setting and the supernatural rule fully consistent.' + lengthNote +\r\n      ' Output ONLY the rewritten text.';\r\n    setActLoading(kind, idx, true);\r\n    callAI(sys, user, function (text) {\r\n      setActLoading(kind, idx, false);\r\n      state.pendingCompare[kind + idx] = { oldText: item.text, newText: text };\r\n      renderKind(kind);\r\n    });\r\n  }\r\n\r\n  function keepOld(kind, idx) { delete state.pendingCompare[kind + idx]; renderKind(kind); }\r\n\r\n  function pushHistory(item, oldText) {\r\n    item.history = item.history || [];\r\n    item.history.unshift({ text: oldText, savedAt: Date.now() });\r\n    item.history = item.history.slice(0, 3);\r\n  }\r\n\r\n  function useNew(kind, idx) {\r\n    var p = getActive();\r\n    var item = listOf(p, kind)[idx];\r\n    var pending = state.pendingCompare[kind + idx];\r\n    if (!pending) { return; }\r\n    pushHistory(item, item.text);\r\n    item.text = pending.newText;\r\n    delete state.pendingCompare[kind + idx];\r\n    p.updatedAt = Date.now();\r\n    saveLibrary();\r\n    renderKind(kind);\r\n    showToast('Updated.', 'success');\r\n  }\r\n\r\n  function restoreVersion(kind, idx, histIdx) {\r\n    var p = getActive();\r\n    var item = listOf(p, kind)[idx];\r\n    var snap = item.history[histIdx];\r\n    if (!snap) { return; }\r\n    var current = item.text;\r\n    item.text = snap.text;\r\n    item.history.splice(histIdx, 1);\r\n    pushHistory(item, current);\r\n    saveLibrary();\r\n    renderKind(kind);\r\n    showToast('Restored previous version.', 'success');\r\n  }\r\n\r\n  function scanForbidden(text) {\r\n    var lines = (text || '').split(\/n+\/);\r\n    var flags = [];\r\n    var i, j, line;\r\n    for (i = 0; i < lines.length; i++) {\r\n      line = lines[i];\r\n      if (!line) { continue; }\r\n      for (j = 0; j < FORBIDDEN_FORMAL.length; j++) {\r\n        if (line.indexOf(FORBIDDEN_FORMAL[j]) > -1) { flags.push({ line: line, word: FORBIDDEN_FORMAL[j], type: 'formal' }); }\r\n      }\r\n      for (j = 0; j < FORBIDDEN_GORE.length; j++) {\r\n        if (line.indexOf(FORBIDDEN_GORE[j]) > -1) { flags.push({ line: line, word: FORBIDDEN_GORE[j], type: 'gore' }); }\r\n      }\r\n    }\r\n    return flags;\r\n  }\r\n\r\n  function fixLine(kind, idx, lineText) {\r\n    var sys = state.mdText;\r\n    var user = 'Rewrite ONLY this single sentence so it avoids any formal written-style words and avoids any graphic\/gore words, ' +\r\n      'while staying spoken, natural, and consistent with the story. Return ONLY the rewritten sentence, nothing else.nnSENTENCE:n' + lineText;\r\n    callAI(sys, user, function (newLine) {\r\n      var p2 = getActive();\r\n      var item = listOf(p2, kind)[idx];\r\n      item.text = item.text.split(lineText).join(newLine);\r\n      saveLibrary();\r\n      renderKind(kind);\r\n      showToast('Line fixed.', 'success');\r\n    });\r\n  }\r\n\r\n  function setActLoading(kind, idx, loading) {\r\n    var card = document.querySelector('.sg-act[data-kind=\"' + kind + '\"][data-idx=\"' + idx + '\"]');\r\n    if (!card) { return; }\r\n    var btns = card.querySelectorAll('button');\r\n    var i;\r\n    for (i = 0; i < btns.length; i++) { btns[i].disabled = loading; }\r\n  }\r\n\r\n  function renderActs() {\r\n    var holder = $('sg-acts-holder');\r\n    if (!holder) { return; }\r\n    holder.innerHTML = '';\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    var i;\r\n    for (i = 0; i < p.acts.length; i++) {\r\n      holder.appendChild(buildCard(p, 'a', i));\r\n    }\r\n  }\r\n\r\n  var TRAIL_RE = new RegExp('[' + String.fromCharCode(34, 39, 41) + '\\]' + String.fromCharCode(0x201D, 0x2019, 0xBB) + ']+$');\r\n\r\n  function endsSentence(tok) {\r\n    var w = tok.replace(\/s+$\/, '').replace(TRAIL_RE, '');\r\n    return \/[.!?]$\/.test(w);\r\n  }\r\n\r\n  function splitByWords(text, chunk) {\r\n    var tokens = (text || '').match(\/S+s*\/g) || [];\r\n    var hard = Math.ceil(chunk * 1.6);\r\n    var out = [];\r\n    var cur = [];\r\n    var count = 0;\r\n    var i, tok, rest;\r\n    for (i = 0; i < tokens.length; i++) {\r\n      tok = tokens[i];\r\n      cur.push(tok);\r\n      count++;\r\n      if ((count >= chunk && endsSentence(tok)) || count >= hard) {\r\n        out.push(cur.join('').replace(\/^s+|s+$\/g, ''));\r\n        cur = [];\r\n        count = 0;\r\n      }\r\n    }\r\n    if (cur.length) {\r\n      rest = cur.join('').replace(\/^s+|s+$\/g, '');\r\n      if (out.length && count < Math.ceil(chunk \/ 3)) { out[out.length - 1] = out[out.length - 1] + ' ' + rest; }\r\n      else { out.push(rest); }\r\n    }\r\n    return out;\r\n  }\r\n\r\n  function plainStory(p) {\r\n    var out = [];\r\n    var i;\r\n    for (i = 0; i < p.acts.length; i++) { if (p.acts[i].text) { out.push(p.acts[i].text); } }\r\n    return out.join('nn');\r\n  }\r\n\r\n  function mergedParts(p) {\r\n    var out = [];\r\n    var i;\r\n    for (i = 0; i < p.parts.length; i++) { if (p.parts[i].text) { out.push(p.parts[i].text); } }\r\n    return out.join('nn');\r\n  }\r\n\r\n  function renderParts() {\r\n    var holder = $('sg-parts-holder');\r\n    if (!holder) { return; }\r\n    holder.innerHTML = '';\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    p.parts = p.parts || [];\r\n    var status = $('sg-parts-status');\r\n    if (status) {\r\n      status.textContent = p.parts.length ?\r\n        (p.parts.length + ' parts, ' + wordCount(mergedParts(p)) + ' words in total.') :\r\n        'No parts yet. Choose a size and click Split Story Into Parts.';\r\n    }\r\n    var i;\r\n    for (i = 0; i < p.parts.length; i++) { holder.appendChild(buildCard(p, 'p', i)); }\r\n  }\r\n\r\n  function doSplitParts() {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    var text = plainStory(p);\r\n    if (!text) { showToast('The Story tab is empty. Generate or import a story first.', 'error'); return; }\r\n    var btn = $('sg-parts-split');\r\n    p.parts = p.parts || [];\r\n    if (p.parts.length && !state.splitArmed) {\r\n      state.splitArmed = true;\r\n      btn.textContent = 'Click again to overwrite existing parts';\r\n      setTimeout(function () { state.splitArmed = false; btn.textContent = 'Split Story Into Parts'; }, 4000);\r\n      return;\r\n    }\r\n    state.splitArmed = false;\r\n    btn.textContent = 'Split Story Into Parts';\r\n    var chunk = parseInt($('sg-parts-words').value, 10) || 150;\r\n    if (chunk < 30) { chunk = 30; }\r\n    if (chunk > 2000) { chunk = 2000; }\r\n    var chunks = splitByWords(text, chunk);\r\n    var arr = [];\r\n    var i;\r\n    for (i = 0; i < chunks.length; i++) { arr.push({ text: chunks[i], aim: '', idea: '', history: [] }); }\r\n    p.parts = arr;\r\n    state.pendingCompare = {};\r\n    p.updatedAt = Date.now();\r\n    saveLibrary();\r\n    renderParts();\r\n    showToast('Split into ' + arr.length + ' parts.', 'success');\r\n  }\r\n\r\n  function mergePartsBack() {\r\n    var p = getActive();\r\n    if (!p || !p.parts || !p.parts.length) { showToast('No parts to merge yet.', 'error'); return; }\r\n    var full = mergedParts(p);\r\n    var n = p.acts.length;\r\n    var total = wordCount(full);\r\n    var chunk = Math.max(1, Math.ceil(total \/ n));\r\n    var arr = splitByWords(full, chunk);\r\n    var last;\r\n    while (arr.length > n) { last = arr.pop(); arr[arr.length - 1] = arr[arr.length - 1] + 'nn' + last; }\r\n    while (arr.length < n) { arr.push(''); }\r\n    var i;\r\n    for (i = 0; i < n; i++) {\r\n      if (p.acts[i].text !== arr[i]) {\r\n        if (p.acts[i].text) { pushHistory(p.acts[i], p.acts[i].text); }\r\n        p.acts[i].text = arr[i];\r\n      }\r\n    }\r\n    p.updatedAt = Date.now();\r\n    saveLibrary();\r\n    renderActs();\r\n    showToast('Merged into the Story tab. Act boundaries were rebalanced by word count.', 'success');\r\n  }\r\n\r\n  function showTab(name) {\r\n    var isParts = (name === 'parts');\r\n    $('sg-panel-story').style.display = isParts ? 'none' : 'block';\r\n    $('sg-panel-parts').style.display = isParts ? 'block' : 'none';\r\n    $('sg-tab-story-btn').className = isParts ? 'sg-tab' : 'sg-tab sg-tab-active';\r\n    $('sg-tab-parts-btn').className = isParts ? 'sg-tab sg-tab-active' : 'sg-tab';\r\n    if (isParts) { renderParts(); } else { renderActs(); }\r\n  }\r\n\r\n  function buildCard(p, kind, idx) {\r\n    var list = listOf(p, kind);\r\n    var item = list[idx];\r\n    var key = kind + idx;\r\n    var label = (kind === 'p') ? ('Part ' + (idx + 1)) : item.label;\r\n    var card = document.createElement('div');\r\n    card.className = 'sg-act';\r\n    card.setAttribute('data-idx', idx);\r\n    card.setAttribute('data-kind', kind);\r\n\r\n    var head = document.createElement('div');\r\n    head.className = 'sg-act-head';\r\n    var title = document.createElement('div');\r\n    title.className = 'sg-act-title';\r\n    title.textContent = label;\r\n    var wc = document.createElement('div');\r\n    wc.className = 'sg-wc';\r\n    wc.textContent = wordCount(item.text) + ' words';\r\n    head.appendChild(title);\r\n    head.appendChild(wc);\r\n    card.appendChild(head);\r\n\r\n    var textarea = document.createElement('textarea');\r\n    textarea.rows = (kind === 'p') ? 6 : 8;\r\n    textarea.value = item.text;\r\n    textarea.setAttribute('aria-label', label + ' text');\r\n    textarea.addEventListener('input', function () { item.text = textarea.value; wc.textContent = wordCount(item.text) + ' words'; });\r\n    textarea.addEventListener('change', function () { item.text = textarea.value; saveLibrary(); });\r\n    card.appendChild(textarea);\r\n\r\n    var btnRow = document.createElement('div');\r\n    btnRow.className = 'sg-actbtns';\r\n\r\n    if (kind === 'a') {\r\n      var genBtn = document.createElement('button');\r\n      genBtn.type = 'button';\r\n      genBtn.className = 'sg-btn-primary sg-btn-sm';\r\n      genBtn.textContent = item.text ? 'Regenerate (no direction)' : 'Generate';\r\n      genBtn.addEventListener('click', function () { generateAct(idx); });\r\n      btnRow.appendChild(genBtn);\r\n    }\r\n\r\n    var readBtn = document.createElement('button');\r\n    readBtn.type = 'button';\r\n    readBtn.className = 'sg-btn-ghost sg-btn-sm';\r\n    readBtn.textContent = 'Read Aloud';\r\n    readBtn.addEventListener('click', function () { readAloud(item.text); });\r\n    btnRow.appendChild(readBtn);\r\n\r\n    var stopBtn = document.createElement('button');\r\n    stopBtn.type = 'button';\r\n    stopBtn.className = 'sg-btn-ghost sg-btn-sm';\r\n    stopBtn.textContent = 'Stop';\r\n    stopBtn.addEventListener('click', function () { if (window.speechSynthesis) { window.speechSynthesis.cancel(); } });\r\n    btnRow.appendChild(stopBtn);\r\n\r\n    if (kind === 'p') {\r\n      if (idx < list.length - 1) {\r\n        var mergeBtn = document.createElement('button');\r\n        mergeBtn.type = 'button';\r\n        mergeBtn.className = 'sg-btn-ghost sg-btn-sm';\r\n        mergeBtn.textContent = 'Merge With Next';\r\n        mergeBtn.addEventListener('click', function () {\r\n          var nxt = p.parts[idx + 1];\r\n          if (!nxt) { return; }\r\n          pushHistory(item, item.text);\r\n          item.text = item.text + 'nn' + nxt.text;\r\n          p.parts.splice(idx + 1, 1);\r\n          p.updatedAt = Date.now();\r\n          saveLibrary();\r\n          renderParts();\r\n        });\r\n        btnRow.appendChild(mergeBtn);\r\n      }\r\n      var delBtn = document.createElement('button');\r\n      var armed = false;\r\n      delBtn.type = 'button';\r\n      delBtn.className = 'sg-btn-danger sg-btn-sm';\r\n      delBtn.textContent = 'Delete';\r\n      delBtn.addEventListener('click', function () {\r\n        if (!armed) {\r\n          armed = true;\r\n          delBtn.textContent = 'Sure? Click again';\r\n          setTimeout(function () { armed = false; delBtn.textContent = 'Delete'; }, 3000);\r\n          return;\r\n        }\r\n        p.parts.splice(idx, 1);\r\n        p.updatedAt = Date.now();\r\n        saveLibrary();\r\n        renderParts();\r\n      });\r\n      btnRow.appendChild(delBtn);\r\n    }\r\n\r\n    card.appendChild(btnRow);\r\n\r\n    var aimSel = document.createElement('select');\r\n    aimSel.setAttribute('aria-label', 'Aim for ' + label);\r\n    var oi, opt;\r\n    for (oi = 0; oi < AIM_OPTIONS.length; oi++) {\r\n      opt = document.createElement('option');\r\n      opt.value = AIM_OPTIONS[oi].value;\r\n      opt.textContent = AIM_OPTIONS[oi].label;\r\n      if (AIM_OPTIONS[oi].value === item.aim) { opt.selected = true; }\r\n      aimSel.appendChild(opt);\r\n    }\r\n    aimSel.addEventListener('change', function () { item.aim = aimSel.value; saveLibrary(); });\r\n    card.appendChild(aimSel);\r\n\r\n    var ideaBox = document.createElement('textarea');\r\n    ideaBox.rows = 2;\r\n    ideaBox.placeholder = 'Your idea \/ direction for this section...';\r\n    ideaBox.value = item.idea;\r\n    ideaBox.setAttribute('aria-label', 'Idea for ' + label);\r\n    ideaBox.addEventListener('input', function () { item.idea = ideaBox.value; });\r\n    ideaBox.addEventListener('change', function () { item.idea = ideaBox.value; saveLibrary(); });\r\n    card.appendChild(ideaBox);\r\n\r\n    var regenBtn = document.createElement('button');\r\n    regenBtn.type = 'button';\r\n    regenBtn.className = 'sg-btn-success sg-btn-sm';\r\n    regenBtn.textContent = 'Regenerate With This Direction';\r\n    regenBtn.disabled = !item.text;\r\n    regenBtn.addEventListener('click', function () { regenerateItem(kind, idx); });\r\n    card.appendChild(regenBtn);\r\n\r\n    var pending = state.pendingCompare[key];\r\n    if (pending) {\r\n      var cmp = document.createElement('div');\r\n      cmp.className = 'sg-compare';\r\n      var oldCol = document.createElement('div');\r\n      var oldH = document.createElement('h4'); oldH.textContent = 'Old version';\r\n      var oldBox = document.createElement('div'); oldBox.className = 'sg-compare-text'; oldBox.textContent = pending.oldText;\r\n      oldCol.appendChild(oldH); oldCol.appendChild(oldBox);\r\n      var newCol = document.createElement('div');\r\n      var newH = document.createElement('h4'); newH.textContent = 'New version';\r\n      var newBox = document.createElement('div'); newBox.className = 'sg-compare-text'; newBox.textContent = pending.newText;\r\n      newCol.appendChild(newH); newCol.appendChild(newBox);\r\n      cmp.appendChild(oldCol); cmp.appendChild(newCol);\r\n      card.appendChild(cmp);\r\n\r\n      var cmpBtns = document.createElement('div');\r\n      cmpBtns.className = 'sg-actbtns';\r\n      var keepBtn = document.createElement('button');\r\n      keepBtn.type = 'button'; keepBtn.className = 'sg-btn-ghost sg-btn-sm'; keepBtn.textContent = 'Keep Old';\r\n      keepBtn.addEventListener('click', function () { keepOld(kind, idx); });\r\n      var useBtn = document.createElement('button');\r\n      useBtn.type = 'button'; useBtn.className = 'sg-btn-primary sg-btn-sm'; useBtn.textContent = 'Use New';\r\n      useBtn.addEventListener('click', function () { useNew(kind, idx); });\r\n      cmpBtns.appendChild(keepBtn); cmpBtns.appendChild(useBtn);\r\n      card.appendChild(cmpBtns);\r\n    }\r\n\r\n    if (item.history && item.history.length) {\r\n      var hist = document.createElement('div');\r\n      hist.className = 'sg-history';\r\n      var hTitle = document.createElement('strong');\r\n      hTitle.textContent = 'Version history';\r\n      hist.appendChild(hTitle);\r\n      var hi;\r\n      for (hi = 0; hi < item.history.length; hi++) {\r\n        (function (hIdx) {\r\n          var row = document.createElement('div');\r\n          row.className = 'sg-history-row';\r\n          var lbl = document.createElement('span');\r\n          lbl.textContent = new Date(item.history[hIdx].savedAt).toLocaleString();\r\n          var rBtn = document.createElement('button');\r\n          rBtn.type = 'button'; rBtn.className = 'sg-btn-ghost sg-btn-sm'; rBtn.textContent = 'Restore';\r\n          rBtn.addEventListener('click', function () { restoreVersion(kind, idx, hIdx); });\r\n          row.appendChild(lbl); row.appendChild(rBtn);\r\n          hist.appendChild(row);\r\n        })(hi);\r\n      }\r\n      card.appendChild(hist);\r\n    }\r\n\r\n    var flags = scanForbidden(item.text);\r\n    if (flags.length) {\r\n      var flagBox = document.createElement('div');\r\n      flagBox.className = 'sg-flags';\r\n      flagBox.setAttribute('role', 'alert');\r\n      var ftitle = document.createElement('strong');\r\n      ftitle.textContent = 'Possible issue(s) found:';\r\n      flagBox.appendChild(ftitle);\r\n      var ul = document.createElement('ul');\r\n      var fi;\r\n      for (fi = 0; fi < flags.length; fi++) {\r\n        (function (f) {\r\n          var li = document.createElement('li');\r\n          li.textContent = f.line;\r\n          var fixBtn = document.createElement('button');\r\n          fixBtn.type = 'button'; fixBtn.className = 'sg-btn-danger sg-btn-sm'; fixBtn.style.marginLeft = '8px';\r\n          fixBtn.textContent = 'Fix This Line';\r\n          fixBtn.addEventListener('click', function () { fixLine(kind, idx, f.line); });\r\n          li.appendChild(fixBtn);\r\n          ul.appendChild(li);\r\n        })(flags[fi]);\r\n      }\r\n      flagBox.appendChild(ul);\r\n      card.appendChild(flagBox);\r\n    }\r\n\r\n    return card;\r\n  }\r\n\r\n  function readAloud(text) {\r\n    if (!text) { return; }\r\n    if (!window.speechSynthesis) { showToast('Read aloud is not supported in this browser.', 'error'); return; }\r\n    var utter = new SpeechSynthesisUtterance(text);\r\n    var voices = window.speechSynthesis.getVoices();\r\n    var i;\r\n    for (i = 0; i < voices.length; i++) {\r\n      if (voices[i].lang && voices[i].lang.toLowerCase().indexOf('si') === 0) { utter.voice = voices[i]; break; }\r\n    }\r\n    window.speechSynthesis.cancel();\r\n    window.speechSynthesis.speak(utter);\r\n  }\r\n\r\n  function checkContinuity() {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    var namesRaw = $('sg-key-names').value || '';\r\n    var names = namesRaw.split(',').map(function (s) { return s.replace(\/^s+|s+$\/g, ''); }).filter(function (s) { return s.length; });\r\n    var result = $('sg-continuity-result');\r\n    result.innerHTML = '';\r\n    if (!names.length) { showToast('Enter at least one key name to check.', 'error'); return; }\r\n    var full = fullStoryText(p);\r\n    var missing = [];\r\n    var i;\r\n    for (i = 0; i < names.length; i++) {\r\n      if (full.indexOf(names[i]) === -1) { missing.push(names[i]); }\r\n    }\r\n    var box = document.createElement('div');\r\n    box.className = missing.length ? 'sg-flags' : 'sg-history';\r\n    if (missing.length) {\r\n      box.textContent = 'Not found anywhere in the story: ' + missing.join(', ');\r\n    } else {\r\n      box.textContent = 'All key names appear consistently in the story.';\r\n    }\r\n    result.appendChild(box);\r\n  }\r\n\r\n  function generateSEO() {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    var full = fullStoryText(p);\r\n    if (!full) { showToast('Generate the story first.', 'error'); return; }\r\n    var user = 'Based on this finished story, produce YouTube SEO data: 2-3 title options, ' +\r\n      'a short bilingual description (native language then English), 15-20 tags, and 2-4 word thumbnail text. ' +\r\n      'STORY:n' + full;\r\n    callAI(state.mdText, user, function (text) {\r\n      p.seoText = text;\r\n      saveLibrary();\r\n      var out = $('sg-seo-output');\r\n      out.style.display = 'block';\r\n      out.textContent = text;\r\n      showToast('SEO data generated.', 'success');\r\n    });\r\n  }\r\n\r\n  function generateVocab() {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    var full = fullStoryText(p);\r\n    if (!full) { showToast('Generate the story first.', 'error'); return; }\r\n    var user = 'From this story, list 5-10 difficult or old vernacular words used, each with a simple plain-language meaning.nnSTORY:n' + full;\r\n    callAI(state.mdText, user, function (text) {\r\n      p.vocabText = text;\r\n      saveLibrary();\r\n      var out = $('sg-vocab-output');\r\n      out.style.display = 'block';\r\n      out.textContent = text;\r\n      showToast('Vocabulary list generated.', 'success');\r\n    });\r\n  }\r\n\r\n  \/\/ --- NEW: Convert to Spoken Sinhala ---\r\n  function convertToSpoken() {\r\n    var p = getActive();\r\n    if (!p) return;\r\n    \r\n    var hasText = false;\r\n    for (var j = 0; j < p.acts.length; j++) { if (p.acts[j].text) hasText = true; }\r\n    if (!hasText) { showToast('Generate the story first before converting.', 'error'); return; }\r\n\r\n    var i = 0;\r\n    var btn = $('sg-convert-spoken');\r\n    btn.disabled = true;\r\n    btn.textContent = 'Converting... Please wait';\r\n\r\n    function nextAct() {\r\n      if (i >= p.acts.length) {\r\n        showToast('Successfully converted entire story to Spoken Sinhala!', 'success');\r\n        p.updatedAt = Date.now();\r\n        saveLibrary();\r\n        renderActs();\r\n        btn.disabled = false;\r\n        btn.textContent = 'Convert Entire Story to Spoken Sinhala';\r\n        return;\r\n      }\r\n      \r\n      if (!p.acts[i].text) { \r\n        i++; \r\n        nextAct(); \r\n        return; \r\n      }\r\n      \r\n      setActLoading('a', i, true);\r\n      var sysPrompt = \"You are an expert Sinhala scriptwriter specializing in natural, spoken YouTube narrations (\u0d9a\u0dad\u0dcf \u0d9a\u0dbb\u0db1 \u0dc3\u0dd2\u0d82\u0dc4\u0dbd).\";\r\n      var userPrompt = \"Please rewrite the following story part into natural Spoken Sinhala (\u0dc3\u0dca\u0dc0\u0dcf\u0db7\u0dcf\u0dc0\u0dd2\u0d9a \u0d9a\u0dad\u0dcf \u0d9a\u0dbb\u0db1 \u0dc3\u0dd2\u0d82\u0dc4\u0dbd) suitable for a YouTube storytelling video. Remove all formal written language (\u0dbd\u0dd2\u0d9b\u0dd2\u0dad \u0db7\u0dcf\u0dc2\u0dcf\u0dc0) such as '\u0dc0\u0dd2\u0dba', '\u0d87\u0dad', '\u0dba\u0db1\u0dca\u0db1', '\u0db6\u0dc0', etc. Make it sound exactly like a real person telling a scary story. Keep the meaning, pacing, and details exactly the same. Output ONLY the rewritten Sinhala text.nnTEXT:n\" + p.acts[i].text;\r\n\r\n      callAI(sysPrompt, userPrompt, function(newText) {\r\n        pushHistory(p.acts[i], p.acts[i].text);\r\n        p.acts[i].text = newText;\r\n        setActLoading('a', i, false);\r\n        i++;\r\n        nextAct();\r\n      });\r\n    }\r\n    nextAct();\r\n  }\r\n\r\n  function stripMarkers(text) {\r\n    var lines = (text || '').split('n');\r\n    var out = [];\r\n    var i;\r\n    for (i = 0; i < lines.length; i++) {\r\n      var line = lines[i];\r\n      if (\/^s*[[^]]+]s*$\/.test(line)) { continue; }\r\n      out.push(line);\r\n    }\r\n    return out.join('n').replace(\/n{3,}\/g, 'nn');\r\n  }\r\n\r\n  function downloadText(filename, content, mime) {\r\n    var blob = new Blob([content], { type: mime || 'text\/plain' });\r\n    var url = URL.createObjectURL(blob);\r\n    var a = document.createElement('a');\r\n    a.href = url;\r\n    a.download = filename;\r\n    document.body.appendChild(a);\r\n    a.click();\r\n    setTimeout(function () { document.body.removeChild(a); URL.revokeObjectURL(url); }, 200);\r\n  }\r\n\r\n  function exportFull() {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    var out = '# ' + p.name + 'nn## Premisen' + p.premise + 'nn## Biblen' + bibleText(p) + 'nn## Storyn' + fullStoryText(p);\r\n    if (p.seoText) { out += 'nn## SEOn' + p.seoText; }\r\n    if (p.vocabText) { out += 'nn## Vocabularyn' + p.vocabText; }\r\n    downloadText(p.name.replace(\/s+\/g, '-') + '-full.md', out, 'text\/markdown');\r\n  }\r\n\r\n  function exportClean() {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    downloadText(p.name.replace(\/s+\/g, '-') + '-narration.txt', stripMarkers(fullStoryText(p)), 'text\/plain');\r\n  }\r\n\r\n  \/\/ --- NEW: Export as Word (.doc) ---\r\n  function exportWord() {\r\n    var p = getActive();\r\n    if (!p) return;\r\n    \r\n    var title = p.name;\r\n    var html = \"<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http:\/\/www.w3.org\/TR\/REC-html40'>\";\r\n    html += \"<head><meta charset='utf-8'><title>\" + title + \"<\/title><\/head><body>\";\r\n    html += \"<h1 style='text-align:center; font-family: sans-serif;'>\" + title + \"<\/h1>\";\r\n    html += \"<h3 style='font-family: sans-serif;'>Premise<\/h3><p style='font-family: sans-serif;'>\" + p.premise + \"<\/p>\";\r\n    html += \"<h3 style='font-family: sans-serif;'>Story<\/h3>\";\r\n    \r\n    for (var i = 0; i < p.acts.length; i++) {\r\n      if (p.acts[i].text) {\r\n        html += \"<h4 style='font-family: sans-serif;'>\" + p.acts[i].label + \"<\/h4>\";\r\n        var paragraphs = p.acts[i].text.split('n');\r\n        for (var j = 0; j < paragraphs.length; j++) {\r\n          if(paragraphs[j].trim()) html += \"<p style='font-family: sans-serif; line-height: 1.6;'>\" + paragraphs[j] + \"<\/p>\";\r\n        }\r\n      }\r\n    }\r\n    html += \"<\/body><\/html>\";\r\n    \r\n    var blob = new Blob(['ufeff', html], { type: 'application\/msword' });\r\n    var url = URL.createObjectURL(blob);\r\n    var a = document.createElement('a');\r\n    a.href = url;\r\n    a.download = title.replace(\/s+\/g, '-') + '.doc';\r\n    document.body.appendChild(a);\r\n    a.click();\r\n    setTimeout(function() { document.body.removeChild(a); URL.revokeObjectURL(url); }, 200);\r\n  }\r\n\r\n  \/\/ --- NEW: Export as PDF (via Print window) ---\r\n  function exportPDF() {\r\n    var p = getActive();\r\n    if (!p) return;\r\n    \r\n    var printWindow = window.open('', '_blank');\r\n    if(!printWindow) { \r\n      showToast('Popup blocked. Please allow popups to print\/save PDF.', 'error'); \r\n      return; \r\n    }\r\n    \r\n    var html = \"<html><head><meta charset='utf-8'><title>\" + p.name + \"<\/title>\";\r\n    html += \"<style>body{font-family: 'Inter', system-ui, sans-serif; padding: 40px; line-height: 1.7; color: #111; max-width: 800px; margin: auto;} h1{text-align: center; border-bottom: 2px solid #ddd; padding-bottom: 10px;} h4{margin-top:25px; color:#333; border-bottom: 1px dashed #ccc; padding-bottom: 5px;} p{margin-bottom: 12px; font-size: 11pt;}<\/style><\/head><body>\";\r\n    html += \"<h1>\" + p.name + \"<\/h1>\";\r\n    \r\n    for (var i = 0; i < p.acts.length; i++) {\r\n      if (p.acts[i].text) {\r\n        html += \"<h4>\" + p.acts[i].label + \"<\/h4>\";\r\n        var paragraphs = p.acts[i].text.split('n');\r\n        for (var j = 0; j < paragraphs.length; j++) {\r\n          if(paragraphs[j].trim()) html += \"<p>\" + paragraphs[j].trim() + \"<\/p>\";\r\n        }\r\n      }\r\n    }\r\n    html += \"<\/body><\/html>\";\r\n    \r\n    printWindow.document.write(html);\r\n    printWindow.document.close();\r\n    printWindow.focus();\r\n    \r\n    \/\/ Slight delay to allow CSS\/fonts to render before print dialog\r\n    setTimeout(function(){ \r\n      printWindow.print(); \r\n      \/\/ Option to auto-close window after print: printWindow.close();\r\n    }, 500);\r\n  }\r\n\r\n  function splitIntoActs(text, parts) {\r\n    var words = (text || '').replace(\/^s+|s+$\/g, '').split(\/s+\/);\r\n    var total = words.length;\r\n    var perPart = Math.max(1, Math.ceil(total \/ parts));\r\n    var acts = [];\r\n    var i, chunk;\r\n    for (i = 0; i < parts; i++) {\r\n      chunk = words.slice(i * perPart, (i + 1) * perPart).join(' ');\r\n      if (chunk) { acts.push(chunk); }\r\n    }\r\n    return acts;\r\n  }\r\n\r\n  function doImportSplit() {\r\n    var text = $('sg-import-text').value;\r\n    var fileInput = $('sg-import-file');\r\n    var partsCount = parseInt($('sg-import-parts').value, 10) || 4;\r\n\r\n    function finish(finalText) {\r\n      var chunks = splitIntoActs(finalText, partsCount);\r\n      var p = newProjectObj('Imported Story', '');\r\n      p.acts = [];\r\n      var i;\r\n      for (i = 0; i < chunks.length; i++) {\r\n        p.acts.push({ label: 'Act ' + (i + 1), text: chunks[i], aim: '', idea: '', history: [] });\r\n      }\r\n      state.library.push(p);\r\n      state.activeId = p.id;\r\n      saveLibrary();\r\n      renderAll();\r\n      showToast('Story imported and split into ' + chunks.length + ' acts.', 'success');\r\n    }\r\n\r\n    if (fileInput && fileInput.files && fileInput.files.length) {\r\n      var reader = new FileReader();\r\n      reader.onload = function (e) { finish(e.target.result); };\r\n      reader.readAsText(fileInput.files[0]);\r\n    } else if (text && text.replace(\/^s+|s+$\/g, '')) {\r\n      finish(text);\r\n    } else {\r\n      showToast('Paste text or choose a file first.', 'error');\r\n    }\r\n  }\r\n\r\n  function loadPremiseFields() {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    $('sg-premise').value = p.premise;\r\n    $('sg-bible-chars').value = p.bible.characters;\r\n    $('sg-bible-village').value = p.bible.village;\r\n    $('sg-bible-era').value = p.bible.era;\r\n    $('sg-bible-rule').value = p.bible.rule;\r\n  }\r\n\r\n  function bindPremiseFields() {\r\n    var map = [\r\n      ['sg-premise', 'premise'],\r\n      ['sg-bible-chars', 'characters'],\r\n      ['sg-bible-village', 'village'],\r\n      ['sg-bible-era', 'era'],\r\n      ['sg-bible-rule', 'rule']\r\n    ];\r\n    var i;\r\n    for (i = 0; i < map.length; i++) {\r\n      (function (fieldId, key) {\r\n        $(fieldId).addEventListener('change', function () {\r\n          var p = getActive();\r\n          if (!p) { return; }\r\n          if (key === 'premise') { p.premise = $(fieldId).value; }\r\n          else { p.bible[key] = $(fieldId).value; }\r\n          saveLibrary();\r\n        });\r\n      })(map[i][0], map[i][1]);\r\n    }\r\n  }\r\n\r\n  function renderAll() {\r\n    renderLibrarySelect();\r\n    loadPremiseFields();\r\n    renderActs();\r\n    renderParts();\r\n    var p = getActive();\r\n    $('sg-seo-output').style.display = p && p.seoText ? 'block' : 'none';\r\n    if (p && p.seoText) { $('sg-seo-output').textContent = p.seoText; }\r\n    $('sg-vocab-output').style.display = p && p.vocabText ? 'block' : 'none';\r\n    if (p && p.vocabText) { $('sg-vocab-output').textContent = p.vocabText; }\r\n  }\r\n\r\n  function loadSample() {\r\n    var p = getActive();\r\n    if (!p) { return; }\r\n    p.premise = 'A young woman waiting by a well every evening for a husband who never returns from the tea estate.';\r\n    p.bible.characters = 'Somawathi, Podi Mahattaya';\r\n    p.bible.village = 'a small hill-country village';\r\n    p.bible.era = '1940s';\r\n    p.bible.rule = 'the well answers only after dark, and only once';\r\n    saveLibrary();\r\n    loadPremiseFields();\r\n    showToast('Sample premise loaded.', 'success');\r\n  }\r\n\r\n  function attachEvents() {\r\n    $('sg-load-sample').addEventListener('click', loadSample);\r\n    $('sg-tab-story-btn').addEventListener('click', function () { showTab('story'); });\r\n    $('sg-tab-parts-btn').addEventListener('click', function () { showTab('parts'); });\r\n    $('sg-parts-split').addEventListener('click', doSplitParts);\r\n    $('sg-parts-merge').addEventListener('click', mergePartsBack);\r\n    $('sg-fullwidth').addEventListener('click', toggleFullWidth);\r\n    $('sg-clear-all').addEventListener('click', function () {\r\n      var p = getActive();\r\n      if (!p) { return; }\r\n      var idx = state.library.indexOf(p);\r\n      state.library[idx] = newProjectObj(p.name, '');\r\n      saveLibrary();\r\n      renderAll();\r\n      showToast('Project cleared.', 'success');\r\n    });\r\n\r\n    $('sg-import-split').addEventListener('click', doImportSplit);\r\n    $('sg-start-story').addEventListener('click', function () { generateAct(0); });\r\n    \r\n    \/\/ NEW EVENT LISTENERS\r\n    $('sg-convert-spoken').addEventListener('click', convertToSpoken);\r\n    $('sg-export-word').addEventListener('click', exportWord);\r\n    $('sg-export-pdf').addEventListener('click', exportPDF);\r\n\r\n    $('sg-check-continuity').addEventListener('click', checkContinuity);\r\n    $('sg-gen-seo').addEventListener('click', generateSEO);\r\n    $('sg-gen-vocab').addEventListener('click', generateVocab);\r\n    $('sg-export-full').addEventListener('click', exportFull);\r\n    $('sg-export-clean').addEventListener('click', exportClean);\r\n\r\n    $('sg-lib-select').addEventListener('change', function () {\r\n      state.activeId = $('sg-lib-select').value;\r\n      saveLibrary();\r\n      renderAll();\r\n    });\r\n    $('sg-lib-new').addEventListener('click', function () {\r\n      var p = newProjectObj('New Story ' + (state.library.length + 1), '');\r\n      state.library.push(p);\r\n      state.activeId = p.id;\r\n      saveLibrary();\r\n      renderAll();\r\n      showToast('New story created.', 'success');\r\n    });\r\n    $('sg-lib-rename').addEventListener('click', function () {\r\n      var p = getActive();\r\n      if (!p) { return; }\r\n      $('sg-rename-input').value = p.name;\r\n      $('sg-rename-row').style.display = 'block';\r\n    });\r\n    $('sg-rename-cancel').addEventListener('click', function () {\r\n      $('sg-rename-row').style.display = 'none';\r\n    });\r\n    $('sg-rename-save').addEventListener('click', function () {\r\n      var p = getActive();\r\n      if (!p) { return; }\r\n      var val = $('sg-rename-input').value;\r\n      if (val && val.replace(\/^s+|s+$\/g, '')) {\r\n        p.name = val;\r\n        saveLibrary();\r\n        renderAll();\r\n        showToast('Story renamed.', 'success');\r\n      }\r\n      $('sg-rename-row').style.display = 'none';\r\n    });\r\n    $('sg-lib-delete').addEventListener('click', function () {\r\n      if (state.library.length <= 1) { showToast('At least one story must remain.', 'error'); return; }\r\n      var p = getActive();\r\n      var idx = state.library.indexOf(p);\r\n      state.library.splice(idx, 1);\r\n      state.activeId = state.library[0].id;\r\n      saveLibrary();\r\n      renderAll();\r\n      showToast('Story deleted.', 'success');\r\n    });\r\n\r\n    $('sg-provider').addEventListener('change', function () {\r\n      var provider = $('sg-provider').value;\r\n      $('sg-model').value = DEFAULT_MODELS[provider];\r\n      var keys = apiKeys();\r\n      $('sg-apikey').value = keys[provider] || '';\r\n    });\r\n    $('sg-apikey').addEventListener('change', function () {\r\n      saveApiKey($('sg-provider').value, $('sg-apikey').value);\r\n    });\r\n\r\n    $('sg-md-fetch').addEventListener('click', function () {\r\n      var url = $('sg-md-url').value;\r\n      if (!url) { showToast('Enter a URL first.', 'error'); return; }\r\n      fetch(url).then(function (r) { return r.text(); }).then(function (txt) {\r\n        state.mdText = txt;\r\n        state.mdSource = 'url';\r\n        $('sg-md-paste').value = txt; \/\/ UPDATED\r\n        try { window.localStorage.setItem(LS.md, JSON.stringify({ source: 'url', text: txt, url: url })); } catch (e) {}\r\n        updateMdStatus();\r\n        showToast('Instruction loaded from URL.', 'success');\r\n      }).catch(function () { showToast('Could not fetch that URL.', 'error'); });\r\n    });\r\n    $('sg-md-save').addEventListener('click', function () {\r\n      var txt = $('sg-md-paste').value;\r\n      if (!txt || !txt.replace(\/^s+|s+$\/g, '')) { showToast('Paste an instruction first.', 'error'); return; }\r\n      state.mdText = txt;\r\n      state.mdSource = 'paste';\r\n      try { window.localStorage.setItem(LS.md, JSON.stringify({ source: 'paste', text: txt })); } catch (e) {}\r\n      updateMdStatus();\r\n      showToast('Instruction saved.', 'success');\r\n    });\r\n  }\r\n\r\n  function updateMdStatus() {\r\n    var status = $('sg-md-status');\r\n    if (state.mdSource === 'url') { status.textContent = 'Using instruction loaded from URL.'; }\r\n    else if (state.mdSource === 'paste') { status.textContent = 'Using your pasted instruction.'; }\r\n    else { status.textContent = 'Using built-in generic instruction (paste your own for full control).'; }\r\n  }\r\n\r\n  function toggleFullWidth() {\r\n    var w = $('sict-storygen-wrap');\r\n    if (!w) { return; }\r\n    var on = w.className.indexOf('sg-full') === -1;\r\n    w.className = on ? 'sg-full' : '';\r\n    if (document.body) {\r\n      if (on) { document.body.className += ' sg-locked'; }\r\n      else { document.body.className = document.body.className.replace(\/s*sg-locked\/g, ''); }\r\n    }\r\n    var btn = $('sg-fullwidth');\r\n    if (btn) { btn.textContent = on ? 'Exit Full Width' : 'Full Width'; }\r\n  }\r\n\r\n  function loadMdFromStorage() {\r\n    var raw;\r\n    try { raw = window.localStorage.getItem(LS.md); } catch (e) { raw = null; }\r\n    if (raw) {\r\n      try {\r\n        var obj = JSON.parse(raw);\r\n        if (obj && obj.text) {\r\n          state.mdText = obj.text;\r\n          state.mdSource = obj.source || 'paste';\r\n          $('sg-md-paste').value = obj.text; \/\/ UPDATED\r\n          if (obj.url) { $('sg-md-url').value = obj.url; }\r\n          updateMdStatus();\r\n          return;\r\n        }\r\n      } catch (e) {}\r\n    }\r\n    \r\n    \/\/ Automatically load default instruction from the requested URL\r\n    var defaultUrl = 'https:\/\/schoolict.net\/sampledata\/holman-katha-story-writer-TOOL-v1.0.md';\r\n    $('sg-md-url').value = defaultUrl;\r\n    $('sg-md-status').textContent = 'Loading default instruction...';\r\n    fetch(defaultUrl).then(function (r) { return r.text(); }).then(function (txt) {\r\n      state.mdText = txt;\r\n      state.mdSource = 'url';\r\n      $('sg-md-paste').value = txt; \/\/ UPDATED\r\n      updateMdStatus();\r\n    }).catch(function () {\r\n      updateMdStatus();\r\n    });\r\n  }\r\n\r\n  function init() {\r\n    loadLibrary();\r\n    var provider = 'gemini';\r\n    $('sg-provider').value = provider;\r\n    $('sg-model').value = DEFAULT_MODELS[provider];\r\n    var keys = apiKeys();\r\n    $('sg-apikey').value = keys[provider] || '';\r\n    loadMdFromStorage();\r\n    bindPremiseFields();\r\n    attachEvents();\r\n    renderAll();\r\n  }\r\n\r\n  function runInit() {\r\n    if (!window.sictStoryGenApp.initialized && document.getElementById('sict-storygen-wrap')) {\r\n      window.sictStoryGenApp.initialized = true;\r\n      window.sictStoryGenApp.init();\r\n    }\r\n  }\r\n\r\n  window.sictStoryGenApp = { initialized: false, init: init };\r\n\r\n  runInit();\r\n  if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runInit); }\r\n  window.addEventListener('load', function () { setTimeout(runInit, 50); });\r\n  if (!window.sictStoryGenApp.initialized && typeof MutationObserver !== 'undefined') {\r\n    var sgObs = new MutationObserver(function () {\r\n      if (document.getElementById('sict-storygen-wrap')) { sgObs.disconnect(); runInit(); }\r\n    });\r\n    sgObs.observe(document.body || document.documentElement, { childList: true, subtree: true });\r\n    setTimeout(function () { sgObs.disconnect(); }, 10000);\r\n  }\r\n})();\r\n<\/script>\n\n\n\n<div id=\"sict-storygen-bottom\">\n<style>\n#sict-storygen-bottom{max-width:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Arial,sans-serif;color:#1e293b;line-height:1.7;}\n#sict-storygen-bottom h2{font-size:1.4rem;margin:26px 0 12px;color:#0f172a;}\n#sict-storygen-bottom h3{font-size:1.05rem;margin:0 0 8px;color:#0f172a;}\n#sict-storygen-bottom p{margin:0 0 14px;}\n#sict-storygen-bottom .sict-sg-features{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin:18px 0;}\n#sict-storygen-bottom .sict-sg-box{background:#f0f9ff;border:1px solid #bae6fd;border-left:4px solid #0369A1;border-radius:12px;padding:18px;}\n#sict-storygen-bottom .sict-sg-box h3{color:#0369A1;}\n#sict-storygen-bottom .sict-sg-box p{margin:0;font-size:.95rem;color:#334155;}\n#sict-storygen-bottom .sict-sg-steps{counter-reset:sictsg;list-style:none;padding:0;margin:18px 0;}\n#sict-storygen-bottom .sict-sg-steps li{position:relative;padding:0 0 16px 42px;color:#1e293b;}\n#sict-storygen-bottom .sict-sg-num{counter-increment:sictsg;position:absolute;left:0;top:0;width:28px;height:28px;border-radius:50%;background:#0369A1;color:#fff;font-weight:700;font-size:.85rem;display:flex;align-items:center;justify-content:center;}\n#sict-storygen-bottom .sict-sg-num:before{content:counter(sictsg);}\n#sict-storygen-bottom a{color:#0369A1;font-weight:600;}\n@media(max-width:820px){#sict-storygen-bottom .sict-sg-features{grid-template-columns:1fr;}}\n<\/style>\n \n<div class=\"sict-sg-features\">\n  <div class=\"sict-sg-box\">\n    <h3>\ud83d\udfe2 Your own instruction, not a fixed template<\/h3>\n    <p>Paste any writing instruction into the Instruction (MD) box \u2014 tone, structure, language rules, whatever you want \u2014 and every act follows it, not a hardcoded script style.<\/p>\n  <\/div>\n  <div class=\"sict-sg-box\">\n    <h3>\ud83d\udd35 Redirect any section, not the whole script<\/h3>\n    <p>Pick a section, choose an aim like &#8220;increase fear&#8221; or &#8220;speed up the pacing&#8221;, add your own idea, and only that section is rewritten \u2014 the rest of the story stays untouched.<\/p>\n  <\/div>\n  <div class=\"sict-sg-box\">\n    <h3>\ud83d\udfe3 Undo without losing your edit<\/h3>\n    <p>Every regeneration keeps your last versions on hand. Compare the old and new text side by side and choose which one to keep before anything is overwritten.<\/p>\n  <\/div>\n<\/div>\n \n<ol class=\"sict-sg-steps\">\n  <li><span class=\"sict-sg-num\"><\/span><strong>Set your premise and bible.<\/strong> Fill in the Story Premise, Characters, Setting and Rule fields, then press <strong>Start New Story (Act 1)<\/strong> \u2014 or paste\/upload an existing script instead.<\/li>\n  <li><span class=\"sict-sg-num\"><\/span><strong>Split into working sections.<\/strong> Switch to the <strong>Parts (mini sections)<\/strong> tab and press <strong>Split Story Into Parts<\/strong> to break the story into small, sentence-safe chunks you can edit one at a time.<\/li>\n  <li><span class=\"sict-sg-num\"><\/span><strong>Redirect a section.<\/strong> Open any part, choose an Aim, type your idea, and press <strong>Regenerate With This Direction<\/strong> \u2014 then compare and keep the version you prefer.<\/li>\n  <li><span class=\"sict-sg-num\"><\/span><strong>Export what you need.<\/strong> Press <strong>Download Clean Narration (.txt)<\/strong> for a voiceover-ready file, or <strong>Download Full Copy (.md)<\/strong> to keep the markers, bible, SEO and vocabulary together.<\/li>\n<\/ol>\n<\/div>\n\n\n\n<h2 id=\"\ud83d\udd34-who-actually-reaches-for-this-ai-story-generator\" class=\"wp-block-heading\">\ud83d\udd34 Who actually reaches for this AI story generator<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A few real situations this tool is built for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd35A solo YouTube narrator who has a premise but not four acts of finished script, and wants a full draft to edit rather than a blank page.<\/li>\n\n\n\n<li>\ud83d\udfe0A writer who already has a script from somewhere else and just wants to tighten one weak act without retyping the whole thing.<\/li>\n\n\n\n<li>\ud83d\udfe3A teacher building a short audio drama for a class project, who needs the story split into small, gradable sections.<\/li>\n<\/ul>\n\n\n\n<h2 id=\"\ud83d\udfe1-what-redirect-a-section-actually-does\" class=\"wp-block-heading\">\ud83d\udfe1 What &#8220;redirect a section&#8221; actually does<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Say Act 2 of a horror story feels rushed. You open its card, choose the Aim <strong>Add more detail \/ description<\/strong>, type the idea &#8220;spend more time on the sound of the well at night&#8221;, and press Regenerate. The tool sends the model your premise, your story bible, the surrounding acts for continuity, the current text of Act 2, and your exact idea \u2014 then shows you the old and new versions side by side. Nothing is overwritten until you press Use New. If you press Keep Old instead, nothing changes at all.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe2-common-mistakes\" class=\"wp-block-heading\">\ud83d\udfe2 Common mistakes<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd35Leaving the Story Bible fields empty \u2014 without a locked character list and setting, later acts can drift and invent new names.<\/li>\n\n\n\n<li>\ud83d\udfe0Splitting into very small parts (under 50 words) \u2014 short chunks lose surrounding context and regenerate awkwardly.<\/li>\n\n\n\n<li>\ud83d\udfe3Forgetting to press Merge Parts Back Into Story after editing in the Parts tab, then exporting the old, unedited Story tab text.<\/li>\n<\/ul>\n\n\n\n<h2 id=\"\ud83d\udd34-honest-limitations\" class=\"wp-block-heading\">\ud83d\udd34 Honest limitations<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is not a fully offline tool \u2014 generating and regenerating text calls your chosen AI provider over the internet, using your own API key. The forbidden-word scanner catches exact matches only, so a rephrased problem word can slip through; always read a section before exporting it. Read Aloud uses your browser&#8217;s own <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Web_Speech_API\" rel=\"noopener\" target=\"_blank\">Web Speech API<\/a>, so voice quality and language support depend on your browser, not on the tool. And like any AI writer, it can repeat a phrase or lose a small detail across a very long story \u2014 the version history exists precisely so a bad regeneration never costs you the good draft you already had.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To turn a finished script into shot-by-shot visual prompts, pair this with the <a href=\"https:\/\/schoolict.net\/ai-storyboard-generator\/\">AI Story-to-Storyboard Generator<\/a> . For the idea behind the Undo\/Compare feature, see How Undo and Redo Work: Version History as a Stack .<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Last updated: October 2026<\/p>\n\n\n\n<div id=\"sict-storygen-bottom\">\n<h2>\ud83e\udd14 Frequently asked questions<\/h2>\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:16px;\">\n \n<div style=\"background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;\">\n<p class=\"sict-faq-q\" style=\"font-weight:700;color:#0f172a;margin:0 0 6px;\">Where is my API key stored?<\/p>\n<p style=\"margin:0;color:#334155;font-size:.95rem;\">Only in your own browser&#8217;s local storage. It is sent directly from your browser to your chosen provider and never passes through any other server.<\/p>\n<\/div>\n \n<div style=\"background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;\">\n<p class=\"sict-faq-q\" style=\"font-weight:700;color:#0f172a;margin:0 0 6px;\">Which AI providers can this AI story generator use?<\/p>\n<p style=\"margin:0;color:#334155;font-size:.95rem;\">Google Gemini, OpenAI or Anthropic \u2014 pick one, enter your own key and model name, and switch providers any time.<\/p>\n<\/div>\n \n<div style=\"background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;\">\n<p class=\"sict-faq-q\" style=\"font-weight:700;color:#0f172a;margin:0 0 6px;\">Can I import a script I already wrote?<\/p>\n<p style=\"margin:0;color:#334155;font-size:.95rem;\">Yes. Paste it or upload a .txt\/.md file, choose how many acts to split it into, and it becomes fully editable with the same regenerate and undo tools.<\/p>\n<\/div>\n \n<div style=\"background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;\">\n<p class=\"sict-faq-q\" style=\"font-weight:700;color:#0f172a;margin:0 0 6px;\">How many past versions can I recover?<\/p>\n<p style=\"margin:0;color:#334155;font-size:.95rem;\">The last three versions of each section are kept. Restoring one pushes the current text back into that history, so you can go back and forth safely.<\/p>\n<\/div>\n \n<div style=\"background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;\">\n<p class=\"sict-faq-q\" style=\"font-weight:700;color:#0f172a;margin:0 0 6px;\">Does it check for unsafe or overly graphic wording?<\/p>\n<p style=\"margin:0;color:#334155;font-size:.95rem;\">Yes \u2014 sections are scanned against a forbidden-word list you can extend through your own instruction, and any flagged line can be rewritten on its own with one click.<\/p>\n<\/div>\n \n<div style=\"background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;\">\n<p class=\"sict-faq-q\" style=\"font-weight:700;color:#0f172a;margin:0 0 6px;\">What&#8217;s the difference between the Story tab and the Parts tab?<\/p>\n<p style=\"margin:0;color:#334155;font-size:.95rem;\">Story holds the four main acts. Parts breaks the whole story into smaller, sentence-safe sections for finer editing \u2014 merge them back into Story when you&#8217;re done.<\/p>\n<\/div>\n \n<div style=\"background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;\">\n<p class=\"sict-faq-q\" style=\"font-weight:700;color:#0f172a;margin:0 0 6px;\">Is my story saved if I close the tab?<\/p>\n<p style=\"margin:0;color:#334155;font-size:.95rem;\">Yes, it stays in your browser&#8217;s local storage under the Story Library until you delete that project or clear your browser data.<\/p>\n<\/div>\n \n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>AI Story Script Generator \u2014 Write &#038; Edit Narrated Stories Generate a 4-act narrated script from your own instruction, then redirect any single section with your own idea and aim \u2014 never the whole script. Keeps a Story Bible for character\/setting consistency, splits into sentence-safe editable parts, keeps a 3-version Undo history with Old vs &#8230; <a title=\"AI Story Script Generator\" class=\"read-more\" href=\"https:\/\/schoolict.net\/tools\/ai-story-script-generator\/\" aria-label=\"Read more about AI Story Script Generator\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-7151","page","type-page","status-publish"],"_links":{"self":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/7151","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/comments?post=7151"}],"version-history":[{"count":3,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/7151\/revisions"}],"predecessor-version":[{"id":7154,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/7151\/revisions\/7154"}],"wp:attachment":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media?parent=7151"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}