sovereign-in-violet 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/game.js +49 -9
- package/package.json +1 -1
package/dist/game.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/* sovereign-in-violet 1.0.
|
|
1
|
+
/* sovereign-in-violet 1.0.3, built from violet-hour.html */
|
|
2
2
|
(function(){
|
|
3
|
-
document.head.insertAdjacentHTML("beforeend", "<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=IM+Fell+English:ital@0;1&family=Spectral:ital,wght@0,400;0,500;0,600;1,400&family=Zen+Antique&family=Italianno&family=Caveat:wght@500&family=Yuji+Syuku&family=Zen+Kurenaido&family=VT323&family=Shippori+Mincho:wght@400;600&display=swap\" rel=\"stylesheet\">" + "<style>" + "\n/* paranoia: whisperling margin notes */\n.note.whisperling{opacity:.85;letter-spacing:.04em}\n.note.whisperling .g{filter:blur(1px);opacity:.8}\n.note.whisperling .clear{text-shadow:0 0 6px rgba(201,162,74,.6)}\n:root{\n --bg:#EFE8F3; --surface:#F8F4FA; --raised:#E4D9EC; --ink:#2A1B38; --muted:#6A5680;\n --gold:#83620C; --gold-soft:#C9A33A; --violet:#5B3A82; --line:#D3C4DF; --danger:#8E2F4A;\n --display:\"IM Fell English\", \"Iowan Old Style\", \"Palatino Linotype\", Georgia, serif;\n --body:\"Spectral\", \"Iowan Old Style\", Georgia, \"Times New Roman\", serif;\n box-sizing:border-box;\n padding-top:env(safe-area-inset-top,0px);\n padding-bottom:env(safe-area-inset-bottom,0px);\n}\n@media (prefers-color-scheme: dark){\n :root:not([data-theme=\"light\"]){\n --bg:#1A1224; --surface:#241932; --raised:#302241; --ink:#EAE1F2; --muted:#A895BB;\n --gold:#DDB654; --gold-soft:#9C7E2C; --violet:#A07BD0; --line:#3B2C4E; --danger:#D9708C;\n }\n}\n:root[data-theme=\"dark\"]{\n --bg:#1A1224; --surface:#241932; --raised:#302241; --ink:#EAE1F2; --muted:#A895BB;\n --gold:#DDB654; --gold-soft:#9C7E2C; --violet:#A07BD0; --line:#3B2C4E; --danger:#D9708C;\n}\n*,*::before,*::after{box-sizing:inherit}\nhtml{scroll-padding-top:env(safe-area-inset-top,0px)}\nbody{margin:0;background:var(--bg);color:var(--ink);font-family:var(--body);font-size:17px;line-height:1.62;-webkit-font-smoothing:antialiased}\nmain{max-width:640px;margin:0 auto;padding:20px 20px 120px}\nh1,h2,h3{font-family:var(--display);font-weight:400;line-height:1.15;margin:0}\nh1{font-size:2.3rem}\nh2{font-size:1.65rem;margin-bottom:.35rem}\nh3{font-size:1.2rem;color:var(--violet);margin:1.4rem 0 .5rem}\np{margin:0 0 1rem}\n.muted{color:var(--muted)}\n.small{font-size:.9rem}\nbutton{font:inherit;color:inherit;cursor:pointer}\nbutton:focus-visible,textarea:focus-visible,input:focus-visible{outline:2px solid var(--gold);outline-offset:2px}\n.btn{background:var(--violet);color:var(--surface);border:0;border-radius:3px;padding:.7rem 1.1rem;font-size:1rem}\n.btn:disabled{opacity:.5;cursor:default}\n.btn-quiet{background:transparent;border:1px solid var(--line);border-radius:3px;padding:.55rem .9rem;color:var(--ink);font-size:.95rem}\n.btn-link{background:none;border:0;padding:0;color:var(--violet);text-decoration:underline;text-underline-offset:3px;font-size:.95rem}\n.row{display:flex;gap:.6rem;flex-wrap:wrap;align-items:center}\n\n/* Title */\n.title{min-height:70vh;display:flex;flex-direction:column;justify-content:center;gap:1.2rem;padding-top:2rem}\n.sigil{width:92px;height:92px;color:var(--gold)}\n.title h1{font-size:3rem;letter-spacing:.01em}\n.title .lede{font-style:italic;color:var(--muted);max-width:30ch;font-size:1.1rem}\n.saves{margin-top:1rem;display:grid;gap:.5rem}\n.save{display:flex;justify-content:space-between;align-items:center;gap:.5rem;border-left:3px solid var(--gold-soft);background:var(--surface);padding:.7rem .9rem}\n.save button.open{background:none;border:0;text-align:left;padding:0;flex:1}\n.save .name{font-family:var(--display);font-size:1.2rem;display:block}\n\n/* Creation */\n.progress{display:flex;gap:4px;margin:0 0 1.4rem}\n.progress span{flex:1;height:3px;background:var(--line)}\n.progress span.done{background:var(--gold)}\n.step-kicker{font-style:italic;color:var(--gold);margin-bottom:.2rem}\n.picked{border-left:2px solid var(--line);padding-left:.8rem;margin:1.2rem 0;color:var(--muted);font-size:.92rem}\n.picked b{color:var(--ink);font-weight:500}\n\n/* Choices */\n.choices{display:grid;gap:.55rem;margin:1.2rem 0 .6rem}\n.choice{display:block;width:100%;text-align:left;background:var(--surface);border:1px solid var(--line);border-radius:3px;padding:.8rem 1rem;line-height:1.4}\n.choice:hover{border-color:var(--gold-soft)}\n.choice[aria-pressed=\"true\"]{border-color:var(--gold);box-shadow:inset 3px 0 0 var(--gold)}\n.choice .hint{display:block;font-size:.82rem;color:var(--muted);font-style:italic;margin-top:.2rem}\n.extra{display:grid;grid-template-columns:1fr 1fr;gap:.55rem}\n.extra button{background:transparent;border:1px dashed var(--line);border-radius:3px;padding:.65rem;color:var(--muted)}\n.custom{margin-top:.6rem;display:grid;gap:.5rem}\n[hidden]{display:none !important}\ntextarea,input[type=text]{width:100%;font:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:3px;padding:.7rem .8rem;resize:vertical}\n\n/* Story */\n.scene{font-size:1.08rem;line-height:1.72}\n.scene p{margin-bottom:1.05rem}\n.action{font-style:italic;color:var(--muted);border-top:1px solid var(--line);padding-top:.8rem;margin:1.4rem 0 .4rem}\n.whisper{display:none}\n.scale.main .ends{color:var(--ink);font-size:.95rem}\n.scale.main .bar{height:3px}\n.omen{font-family:var(--display);font-style:italic;color:var(--gold);margin:0 0 1rem}\n.thinking{display:flex;gap:.8rem;align-items:center;color:var(--muted);font-style:italic;margin:1.5rem 0}\n.thinking svg{width:34px;height:34px;color:var(--gold);flex:none}\n@media (prefers-reduced-motion:no-preference){ .thinking svg{animation:breathe 2.6s ease-in-out infinite} }\n@keyframes breathe{0%,100%{opacity:.35;transform:scale(.94)}50%{opacity:1;transform:scale(1)}}\n.card{background:var(--surface);border:1px solid var(--line);border-radius:3px;padding:1rem 1.1rem;margin:1.2rem 0}\n.card.gold{border-color:var(--gold-soft);box-shadow:inset 3px 0 0 var(--gold)}\n.card.door{border-color:var(--danger);box-shadow:inset 3px 0 0 var(--danger)}\n.card h3{margin-top:0}\n.error{color:var(--danger)}\n\n/* Sheet */\n.traits{display:grid;grid-template-columns:1fr 1fr;gap:.4rem 1.2rem}\n.trait{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--line);padding:.35rem 0}\n.pips{display:flex;gap:3px}\n.pips i{width:9px;height:9px;border-radius:50%;border:1px solid var(--gold);display:block}\n.pips i.on{background:var(--gold)}\n.track{display:flex;gap:5px;align-items:center}\n.track i{width:18px;height:18px;border:1.5px solid var(--muted);display:block;border-radius:2px}\n.track i.on{background:var(--danger);border-color:var(--danger)}\n.track.stab i.on{background:var(--violet);border-color:var(--violet)}\n.scale{margin:.7rem 0 1rem}\n.scale .ends{display:flex;justify-content:space-between;font-size:.85rem;color:var(--muted);gap:1rem}\n.scale .ends span:last-child{text-align:right}\n.scale .bar{position:relative;height:2px;background:var(--line);margin:.55rem 0 .2rem}\n.scale .bar::after{content:\"\";position:absolute;left:50%;top:-4px;width:1px;height:10px;background:var(--muted)}\n.scale .dot{position:absolute;top:-6px;width:14px;height:14px;border-radius:50%;background:var(--gold);transform:translateX(-50%);border:2px solid var(--surface)}\nul.plain{list-style:none;padding:0;margin:0}\nul.plain li{padding:.45rem 0;border-bottom:1px solid var(--line)}\nul.plain li:last-child{border-bottom:0}\n.empty{color:var(--muted);font-style:italic}\n\n/* Nav */\nnav.tabs{position:fixed;left:0;right:0;bottom:0;background:var(--surface);border-top:1px solid var(--line);padding-bottom:env(safe-area-inset-bottom,0px);z-index:10}\nnav.tabs .inner{max-width:640px;margin:0 auto;display:grid;grid-template-columns:repeat(4,1fr)}\nnav.tabs button{background:none;border:0;padding:.75rem .2rem;font-family:var(--display);font-size:1.02rem;color:var(--muted);border-top:2px solid transparent}\nnav.tabs button[aria-current=\"page\"]{color:var(--gold);border-top-color:var(--gold)}\n.seg{display:inline-flex;border:1px solid var(--line);border-radius:3px;overflow:hidden}\n.seg button{background:none;border:0;padding:.45rem .8rem;font-size:.92rem;color:var(--muted)}\n.seg button[aria-pressed=\"true\"]{background:var(--violet);color:var(--surface)}\n\n/* Japanese: mincho faces matching the English serif look, no fake italics, Japanese line-breaking */\nhtml[lang=\"ja\"]{\n --display:\"Zen Antique\",\"Shippori Mincho\",\"Hiragino Mincho ProN\",\"Yu Mincho\",serif;\n --body:\"Shippori Mincho\",\"Hiragino Mincho ProN\",\"Yu Mincho\",\"Noto Serif JP\",serif;\n}\nhtml[lang=\"ja\"] body{line-height:1.75;line-break:strict;overflow-wrap:anywhere}\nhtml[lang=\"ja\"] *{font-style:normal !important}\nhtml[lang=\"ja\"] h1,html[lang=\"ja\"] h2,html[lang=\"ja\"] h3{line-height:1.35;letter-spacing:.04em}\nhtml[lang=\"ja\"] .title .lede{max-width:16em}\nhtml[lang=\"ja\"] .step-kicker,html[lang=\"ja\"] .omen,html[lang=\"ja\"] .whisper,html[lang=\"ja\"] .action{letter-spacing:.03em}\n\n/* Page effects: always marked by his eye, always brief, never change what you chose */\n#pfxEye{position:fixed;top:calc(env(safe-area-inset-top,0px) + 10px);right:12px;width:30px;height:30px;color:var(--gold-soft);opacity:0;pointer-events:none;transition:opacity .5s ease;z-index:60}\n#pfxEye svg{width:100%;height:100%}\n#pfxEye.on{opacity:.9}\n.pfx-flip{transform:scaleX(-1);transition:transform .3s ease}\n.pfx-unflip{transition:transform .3s ease}\n.pfx-mirror{display:block;transform:scaleX(-1)}\n.pfx-glow{border-color:var(--violet) !important;box-shadow:inset 0 0 0 2px var(--violet) !important}\n.pfx-voice{color:var(--violet) !important;font-family:var(--display);font-style:italic}\n.pfx-line{color:var(--violet);font-family:var(--display);font-style:italic;opacity:0;transition:opacity .8s ease;margin:.6rem 0 0}\n.pfx-line.on{opacity:1}\n\n/* Hidden race hints: a faint watching eye and a violet tint that deepen with his Manifestation; your resolve as a small steady light */\nbody{background-image:linear-gradient(rgba(106,64,160,var(--taint,0)),rgba(106,64,160,var(--taint,0)));background-attachment:fixed}\n#watchEye{position:fixed;top:calc(env(safe-area-inset-top,0px) + 10px);right:12px;width:30px;height:30px;color:var(--gold-soft);opacity:0;pointer-events:none;transition:opacity 2s ease;z-index:55}\n#watchEye svg{width:100%;height:100%}\n.resolve-light{display:inline-block;width:.55rem;height:.55rem;border-radius:50%;background:var(--gold);vertical-align:middle;margin-left:.5rem;opacity:calc(.15 + var(--r,0)*.85);box-shadow:0 0 calc(2px + var(--r,0)*10px) var(--gold)}\n\n.faded-end{animation:fadeAway 9s ease forwards}\n@keyframes fadeAway{from{opacity:1}to{opacity:.45}}\n@media (prefers-reduced-motion:reduce){.faded-end{animation:none;opacity:.6}}\n.hint.pull{color:var(--gold);font-style:normal;margin-bottom:.2rem}\n.learned{margin-top:1rem;padding-top:.8rem;border-top:1px solid var(--line)}\n.learned li{margin:.25rem 0}\n\n/* Pictures: VHS-tape stills */\n.vhs{position:relative;margin:0 0 1rem;border-radius:6px;overflow:hidden;background:#0b0714;box-shadow:0 0 0 1px var(--line)}\n.vhs canvas{display:block;width:100%;height:auto;aspect-ratio:4/3}\n.vhs .osd{position:absolute;top:.5rem;left:.7rem;font-family:\"VT323\",ui-monospace,monospace;font-size:1.25rem;color:#f1ecf8;text-shadow:1px 1px 0 #000,-1px 0 0 rgba(255,0,80,.5);letter-spacing:.06em;font-style:normal !important}\n.vhs .osd.ts{top:auto;bottom:.4rem;left:auto;right:.7rem}\n.vhs .osd.rec{color:#e0a0ff}\n.title .vhs{max-width:26rem}\n.more-pulls summary{cursor:pointer;color:var(--violet);margin:.6rem 0}\n.choice.gift-choice{font-family:var(--his);font-size:1.45rem;line-height:1.1;color:var(--gold);border-color:var(--gold-soft);background:linear-gradient(90deg,rgba(201,162,74,.16),rgba(255,250,238,.6));box-shadow:0 0 14px rgba(201,162,74,.3);animation:giftGlow 3.2s ease-in-out infinite}\n.choice.gift-choice::before{content:\"✦ \";color:var(--gold)}\n@keyframes giftGlow{0%,100%{box-shadow:0 0 10px rgba(201,162,74,.2)}50%{box-shadow:0 0 18px rgba(201,162,74,.45)}}\n@media (prefers-reduced-motion:reduce){.choice.gift-choice{animation:none}}\n.choice.gift-own{font-family:var(--display);font-style:italic;color:var(--ink);border:1px dashed var(--gold-soft)}\n.choice.gift-own::before{content:\"✧ \";color:var(--gold)}\n\n/* ---------- The book ---------- */\n:root, :root[data-theme=\"dark\"], :root:not([data-theme=\"light\"]){\n --bg:#1b1320; --surface:#f1e6cf; --raised:#e6d7b8; --ink:#2b1f2a; --muted:#6b5a55;\n --gold:#8a6a1c; --gold-soft:#b8923a; --violet:#5b3a82; --line:#cbb894; --danger:#8e2f4a;\n --his:\"Italianno\",\"Yuji Syuku\",cursive; --reader:\"Caveat\",\"Zen Kurenaido\",cursive;\n}\n@media (prefers-color-scheme: dark){ :root:not([data-theme=\"light\"]){ --surface:#ddd0b2; --raised:#d0c1a0; } }\nbody{background:radial-gradient(ellipse at 50% 0%, #2a1d33 0%, #140e18 70%) fixed}\nmain{background:var(--surface);background-image:radial-gradient(ellipse at 50% 40%, rgba(255,250,235,.55), rgba(255,250,235,0) 65%),radial-gradient(circle at 20% 80%, rgba(120,90,50,.08), rgba(0,0,0,0) 40%),radial-gradient(circle at 85% 15%, rgba(120,90,50,.07), rgba(0,0,0,0) 35%);\n margin:16px auto 110px;padding:28px 26px 40px;box-shadow:0 0 0 1px rgba(90,60,30,.25), 0 18px 50px rgba(0,0,0,.55), inset 0 0 60px rgba(120,90,50,.18);border-radius:2px 6px 6px 2px;min-height:80vh;position:relative}\nmain::before{content:\"\";position:absolute;inset:0;pointer-events:none;border-left:6px solid rgba(90,60,30,.12);border-radius:2px 0 0 2px}\n.tabs{background:#e6d7b8 !important;border-top:1px solid #b8a37a !important}\n.card{background:rgba(255,250,238,.55)}\n.choice{background:rgba(255,250,238,.6)}\n/* drop cap on each page's first paragraph */\n.scene p:first-child::first-letter{float:left;font-family:var(--display);font-size:3.3em;line-height:.82;padding:.08em .08em 0 0;color:var(--gold)}\nhtml[lang=\"ja\"] .scene p:first-child::first-letter{float:none;font-size:inherit;color:inherit;padding:0}\n.folio{text-align:center;font-family:var(--display);color:var(--muted);font-size:.9rem;margin-top:1.6rem;letter-spacing:.2em}\n/* illustration plates */\n.vhs{background:#f7eedb;padding:7px 7px 0;border:1px solid #6b5436;box-shadow:0 0 0 3px #f7eedb, 0 0 0 4px #6b5436, 0 6px 16px rgba(60,40,20,.25);border-radius:1px;margin:.4rem 4px 1.3rem}\n.vhs canvas{border:1px solid #6b5436}\n.vhs figcaption{font-family:var(--display);font-style:italic;text-align:center;color:#5a4630;font-size:.92rem;padding:.35rem .4rem .45rem}\n/* margin notes */\n.note{display:block;margin:.2rem 0 1rem;max-width:88%;line-height:1.15}\n.note.gold{font-family:var(--his);color:var(--gold);font-size:1.75rem;transform:rotate(-1.6deg);margin-left:12%}\n.note.reader{font-family:var(--reader);color:var(--violet);font-size:1.3rem;transform:rotate(1.2deg);margin-left:2%}\n.note.reader .sig{display:block;font-size:1.05rem;opacity:.8;margin-left:40%}\nhtml[lang=\"ja\"] .note.gold{font-size:1.2rem} html[lang=\"ja\"] .choice.gift-choice{font-size:1.05rem} html[lang=\"ja\"] .note.reader{font-size:1.05rem}\nu.vu{text-decoration:underline wavy var(--violet);text-decoration-thickness:1.5px;text-underline-offset:4px}\n@media (min-width: 1000px){ main{max-width:900px;padding-right:270px} .note{float:right;clear:right;width:215px;margin:0 -250px .8rem 0} .note.gold{margin-left:0} .note.reader{margin-left:0} }\n/* his voice everywhere is his own hand, in tarnished gold */\n.pfx-voice{color:var(--gold) !important;font-family:var(--his) !important;font-style:normal !important;font-size:1.45em;line-height:1.1}\nhtml[lang=\"ja\"] .pfx-voice{font-size:1.05em}\n\n/* ---------- The cover ---------- */\n.cover{min-height:88vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.6rem;cursor:pointer;text-align:center;margin:-28px -26px -40px;padding:18px 12px 30px;background:#0b0712;border-radius:2px 6px 6px 2px}\n.cover-art{position:relative;width:min(100%,420px)}\n.cover-art canvas{display:block;width:100%;height:auto;aspect-ratio:2/3;border-radius:3px;box-shadow:0 0 0 1px #6b5436, 0 0 40px rgba(154,99,224,.25)}\n.cover .stars{display:none}\n.cover .stars i{position:absolute;background:#fff6dc;border-radius:50%;opacity:.2;animation:twinkle 2.8s ease-in-out infinite}\n@keyframes twinkle{0%,100%{opacity:.15;transform:scale(.8)}50%{opacity:1;transform:scale(1.4)}}\n.cover h1{color:#e9dcb8;font-size:2.4rem;text-shadow:0 2px 12px rgba(0,0,0,.8)}\n.cover-tap{color:var(--gold-soft);font-family:var(--display);font-style:italic;letter-spacing:.12em;animation:tapPulse 2.4s ease-in-out infinite;margin:0}\n@keyframes tapPulse{0%,100%{opacity:.45}50%{opacity:1}}\n@media (prefers-reduced-motion:reduce){ .cover .stars i,.cover-tap{animation:none;opacity:.7} }\n\n/* tarot plates */\n.tarot{margin:.4rem auto 1.2rem;max-width:300px}\n.tarot-card{position:relative;background:#1a1024;padding:58px 16px 64px;border-radius:10px;box-shadow:0 0 0 2px #b8923a, 0 0 0 5px #1a1024, 0 0 0 6px #b8923a, 0 10px 26px rgba(0,0,0,.45);background-image:radial-gradient(circle at 12px 12px,#b8923a 1.5px,transparent 2px),radial-gradient(circle at calc(100% - 12px) 12px,#b8923a 1.5px,transparent 2px),radial-gradient(circle at 12px calc(100% - 12px),#b8923a 1.5px,transparent 2px),radial-gradient(circle at calc(100% - 12px) calc(100% - 12px),#b8923a 1.5px,transparent 2px)}\n.tarot-card canvas{display:block;width:100%;height:auto;aspect-ratio:4/3;border:1px solid #b8923a;border-radius:2px}\n.tarot .arc-num,.tarot .arc-name{position:absolute;left:0;right:0;text-align:center;font-family:var(--display);color:#e0c070;letter-spacing:.14em}\n.tarot .arc-num{top:14px;font-size:1.15rem}\n.tarot .arc-num::after,.tarot .arc-name::before{content:\"✧ ✦ ✧\";display:block;font-size:.6rem;letter-spacing:.5em;color:#b8923a;margin:.35rem 0 0}\n.tarot .arc-name::before{margin:0 0 .35rem}\n.tarot .arc-name{bottom:12px;font-size:1rem;text-transform:uppercase}\n.tarot.reversed .tarot-card{transform:rotate(180deg)}\n.tarot figcaption{text-align:center;font-family:var(--display);font-style:italic;color:var(--muted);font-size:.9rem;margin-top:.5rem}\nhtml[lang=\"ja\"] .tarot .arc-name{text-transform:none;letter-spacing:.06em}\n/* his offer: a card held out to you */\n.choice.gift-choice{border-radius:9px !important;border:1px solid var(--gold-soft) !important;box-shadow:0 0 0 3px rgba(255,250,238,.6), 0 0 0 4px var(--gold-soft), 0 6px 16px rgba(120,80,20,.25) !important;transform:rotate(-1.2deg);position:relative;padding-left:2.2rem !important}\n.choice.gift-choice::before{content:\"✦\";position:absolute;left:.7rem;top:.35rem;font-size:.9rem;color:var(--gold)}\n.choice.gift-choice::after{content:\"✦\";position:absolute;right:.7rem;bottom:.3rem;font-size:.9rem;color:var(--gold);transform:rotate(180deg)}\n/* epigraphs, recap, engine */\n.epi{margin:0 0 1.5rem;padding:.1rem 0 .1rem 1rem;border-left:2px solid var(--gold-soft)}\n.epi-q{font-family:var(--display);font-style:italic;font-size:1.12rem;line-height:1.4;margin:0}\n.epi-q::before{content:\"\\201C\"} .epi-q::after{content:\"\\201D\"}\n.epi-by{margin:.35rem 0 0;color:var(--muted);font-size:.88rem;letter-spacing:.03em}\n.card .epi{margin:1rem 0}\n.recap{border:1px dashed var(--line);border-radius:3px;padding:.6rem .9rem;margin:0 0 1.2rem;color:var(--muted);font-size:.95rem}\n.recap summary{cursor:pointer;font-family:var(--display);color:var(--violet)}\n.recap p{margin:.4rem 0 0}\ninput[type=password]{width:100%;font:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:3px;padding:.7rem .8rem}\n" + "</style>");
|
|
3
|
+
document.head.insertAdjacentHTML("beforeend", "<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=IM+Fell+English:ital@0;1&family=Spectral:ital,wght@0,400;0,500;0,600;1,400&family=Zen+Antique&family=Italianno&family=Caveat:wght@500&family=Yuji+Syuku&family=Zen+Kurenaido&family=VT323&family=Shippori+Mincho:wght@400;600&display=swap\" rel=\"stylesheet\">" + "<style>" + "\n/* paranoia: whisperling margin notes */\n.note.whisperling{opacity:.85;letter-spacing:.04em}\n.note.whisperling .g{filter:blur(1px);opacity:.8}\n.note.whisperling .clear{text-shadow:0 0 6px rgba(201,162,74,.6)}\n:root{\n --bg:#EFE8F3; --surface:#F8F4FA; --raised:#E4D9EC; --ink:#2A1B38; --muted:#6A5680;\n --gold:#83620C; --gold-soft:#C9A33A; --violet:#5B3A82; --line:#D3C4DF; --danger:#8E2F4A;\n --display:\"IM Fell English\", \"Iowan Old Style\", \"Palatino Linotype\", Georgia, serif;\n --body:\"Spectral\", \"Iowan Old Style\", Georgia, \"Times New Roman\", serif;\n box-sizing:border-box;\n padding-top:env(safe-area-inset-top,0px);\n padding-bottom:env(safe-area-inset-bottom,0px);\n}\n@media (prefers-color-scheme: dark){\n :root:not([data-theme=\"light\"]){\n --bg:#1A1224; --surface:#241932; --raised:#302241; --ink:#EAE1F2; --muted:#A895BB;\n --gold:#DDB654; --gold-soft:#9C7E2C; --violet:#A07BD0; --line:#3B2C4E; --danger:#D9708C;\n }\n}\n:root[data-theme=\"dark\"]{\n --bg:#1A1224; --surface:#241932; --raised:#302241; --ink:#EAE1F2; --muted:#A895BB;\n --gold:#DDB654; --gold-soft:#9C7E2C; --violet:#A07BD0; --line:#3B2C4E; --danger:#D9708C;\n}\n*,*::before,*::after{box-sizing:inherit}\nhtml{scroll-padding-top:env(safe-area-inset-top,0px)}\nbody{margin:0;background:var(--bg);color:var(--ink);font-family:var(--body);font-size:17px;line-height:1.62;-webkit-font-smoothing:antialiased}\nmain{max-width:640px;margin:0 auto;padding:20px 20px 120px}\nh1,h2,h3{font-family:var(--display);font-weight:400;line-height:1.15;margin:0}\nh1{font-size:2.3rem}\nh2{font-size:1.65rem;margin-bottom:.35rem}\nh3{font-size:1.2rem;color:var(--violet);margin:1.4rem 0 .5rem}\np{margin:0 0 1rem}\n.muted{color:var(--muted)}\n.small{font-size:.9rem}\nbutton{font:inherit;color:inherit;cursor:pointer}\nbutton:focus-visible,textarea:focus-visible,input:focus-visible{outline:2px solid var(--gold);outline-offset:2px}\n.btn{background:var(--violet);color:var(--surface);border:0;border-radius:3px;padding:.7rem 1.1rem;font-size:1rem}\n.btn:disabled{opacity:.5;cursor:default}\n.btn-quiet{background:transparent;border:1px solid var(--line);border-radius:3px;padding:.55rem .9rem;color:var(--ink);font-size:.95rem}\n.btn-link{background:none;border:0;padding:0;color:var(--violet);text-decoration:underline;text-underline-offset:3px;font-size:.95rem}\n.row{display:flex;gap:.6rem;flex-wrap:wrap;align-items:center}\n\n/* Title */\n.title{min-height:70vh;display:flex;flex-direction:column;justify-content:center;gap:1.2rem;padding-top:2rem}\n.sigil{width:92px;height:92px;color:var(--gold)}\n.title h1{font-size:3rem;letter-spacing:.01em}\n.title .lede{font-style:italic;color:var(--muted);max-width:30ch;font-size:1.1rem}\n.saves{margin-top:1rem;display:grid;gap:.5rem}\n.save{display:flex;justify-content:space-between;align-items:center;gap:.5rem;border-left:3px solid var(--gold-soft);background:var(--surface);padding:.7rem .9rem}\n.save button.open{background:none;border:0;text-align:left;padding:0;flex:1}\n.save .name{font-family:var(--display);font-size:1.2rem;display:block}\n\n/* Creation */\n.progress{display:flex;gap:4px;margin:0 0 1.4rem}\n.progress span{flex:1;height:3px;background:var(--line)}\n.progress span.done{background:var(--gold)}\n.step-kicker{font-style:italic;color:var(--gold);margin-bottom:.2rem}\n.picked{border-left:2px solid var(--line);padding-left:.8rem;margin:1.2rem 0;color:var(--muted);font-size:.92rem}\n.picked b{color:var(--ink);font-weight:500}\n\n/* Choices */\n.choices{display:grid;gap:.55rem;margin:1.2rem 0 .6rem}\n.choice{display:block;width:100%;text-align:left;background:var(--surface);border:1px solid var(--line);border-radius:3px;padding:.8rem 1rem;line-height:1.4}\n.choice:hover{border-color:var(--gold-soft)}\n.choice[aria-pressed=\"true\"]{border-color:var(--gold);box-shadow:inset 3px 0 0 var(--gold)}\n.choice .hint{display:block;font-size:.82rem;color:var(--muted);font-style:italic;margin-top:.2rem}\n.extra{display:grid;grid-template-columns:1fr 1fr;gap:.55rem}\n.extra button{background:transparent;border:1px dashed var(--line);border-radius:3px;padding:.65rem;color:var(--muted)}\n.custom{margin-top:.6rem;display:grid;gap:.5rem}\n[hidden]{display:none !important}\ntextarea,input[type=text]{width:100%;font:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:3px;padding:.7rem .8rem;resize:vertical}\n\n/* Story */\n.scene{font-size:1.08rem;line-height:1.72}\n.scene p{margin-bottom:1.05rem}\n.action{font-style:italic;color:var(--muted);border-top:1px solid var(--line);padding-top:.8rem;margin:1.4rem 0 .4rem}\n.whisper{display:none}\n.scale.main .ends{color:var(--ink);font-size:.95rem}\n.scale.main .bar{height:3px}\n.omen{font-family:var(--display);font-style:italic;color:var(--gold);margin:0 0 1rem}\n.thinking{display:flex;gap:.8rem;align-items:center;color:var(--muted);font-style:italic;margin:1.5rem 0}\n.thinking svg{width:34px;height:34px;color:var(--gold);flex:none}\n@media (prefers-reduced-motion:no-preference){ .thinking svg{animation:breathe 2.6s ease-in-out infinite} }\n@keyframes breathe{0%,100%{opacity:.35;transform:scale(.94)}50%{opacity:1;transform:scale(1)}}\n.card{background:var(--surface);border:1px solid var(--line);border-radius:3px;padding:1rem 1.1rem;margin:1.2rem 0}\n.card.gold{border-color:var(--gold-soft);box-shadow:inset 3px 0 0 var(--gold)}\n.card.door{border-color:var(--danger);box-shadow:inset 3px 0 0 var(--danger)}\n.card h3{margin-top:0}\n.error{color:var(--danger)}\n\n/* Sheet */\n.traits{display:grid;grid-template-columns:1fr 1fr;gap:.4rem 1.2rem}\n.trait{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--line);padding:.35rem 0}\n.pips{display:flex;gap:3px}\n.pips i{width:9px;height:9px;border-radius:50%;border:1px solid var(--gold);display:block}\n.pips i.on{background:var(--gold)}\n.track{display:flex;gap:5px;align-items:center}\n.track i{width:18px;height:18px;border:1.5px solid var(--muted);display:block;border-radius:2px}\n.track i.on{background:var(--danger);border-color:var(--danger)}\n.track.stab i.on{background:var(--violet);border-color:var(--violet)}\n.scale{margin:.7rem 0 1rem}\n.scale .ends{display:flex;justify-content:space-between;font-size:.85rem;color:var(--muted);gap:1rem}\n.scale .ends span:last-child{text-align:right}\n.scale .bar{position:relative;height:2px;background:var(--line);margin:.55rem 0 .2rem}\n.scale .bar::after{content:\"\";position:absolute;left:50%;top:-4px;width:1px;height:10px;background:var(--muted)}\n.scale .dot{position:absolute;top:-6px;width:14px;height:14px;border-radius:50%;background:var(--gold);transform:translateX(-50%);border:2px solid var(--surface)}\nul.plain{list-style:none;padding:0;margin:0}\nul.plain li{padding:.45rem 0;border-bottom:1px solid var(--line)}\nul.plain li:last-child{border-bottom:0}\n.empty{color:var(--muted);font-style:italic}\n\n/* Nav */\nnav.tabs{position:fixed;left:0;right:0;bottom:0;background:var(--surface);border-top:1px solid var(--line);padding-bottom:env(safe-area-inset-bottom,0px);z-index:10}\nnav.tabs .inner{max-width:640px;margin:0 auto;display:grid;grid-template-columns:repeat(4,1fr)}\nnav.tabs button{background:none;border:0;padding:.75rem .2rem;font-family:var(--display);font-size:1.02rem;color:var(--muted);border-top:2px solid transparent}\nnav.tabs button[aria-current=\"page\"]{color:var(--gold);border-top-color:var(--gold)}\n.seg{display:inline-flex;border:1px solid var(--line);border-radius:3px;overflow:hidden}\n.seg button{background:none;border:0;padding:.45rem .8rem;font-size:.92rem;color:var(--muted)}\n.seg button[aria-pressed=\"true\"]{background:var(--violet);color:var(--surface)}\n\n/* Japanese: mincho faces matching the English serif look, no fake italics, Japanese line-breaking */\nhtml[lang=\"ja\"]{\n --display:\"Zen Antique\",\"Shippori Mincho\",\"Hiragino Mincho ProN\",\"Yu Mincho\",serif;\n --body:\"Shippori Mincho\",\"Hiragino Mincho ProN\",\"Yu Mincho\",\"Noto Serif JP\",serif;\n}\nhtml[lang=\"ja\"] body{line-height:1.75;line-break:strict;overflow-wrap:anywhere}\nhtml[lang=\"ja\"] *{font-style:normal !important}\nhtml[lang=\"ja\"] h1,html[lang=\"ja\"] h2,html[lang=\"ja\"] h3{line-height:1.35;letter-spacing:.04em}\nhtml[lang=\"ja\"] .title .lede{max-width:16em}\nhtml[lang=\"ja\"] .step-kicker,html[lang=\"ja\"] .omen,html[lang=\"ja\"] .whisper,html[lang=\"ja\"] .action{letter-spacing:.03em}\n\n/* Page effects: always marked by his eye, always brief, never change what you chose */\n#pfxEye{position:fixed;top:calc(env(safe-area-inset-top,0px) + 10px);right:12px;width:30px;height:30px;color:var(--gold-soft);opacity:0;pointer-events:none;transition:opacity .5s ease;z-index:60}\n#pfxEye svg{width:100%;height:100%}\n#pfxEye.on{opacity:.9}\n.pfx-flip{transform:scaleX(-1);transition:transform .3s ease}\n.pfx-unflip{transition:transform .3s ease}\n.pfx-mirror{display:block;transform:scaleX(-1)}\n.pfx-glow{border-color:var(--violet) !important;box-shadow:inset 0 0 0 2px var(--violet) !important}\n.pfx-voice{color:var(--violet) !important;font-family:var(--display);font-style:italic}\n.pfx-line{color:var(--violet);font-family:var(--display);font-style:italic;opacity:0;transition:opacity .8s ease;margin:.6rem 0 0}\n.pfx-line.on{opacity:1}\n\n/* Hidden race hints: a faint watching eye and a violet tint that deepen with his Manifestation; your resolve as a small steady light */\nbody{background-image:linear-gradient(rgba(106,64,160,var(--taint,0)),rgba(106,64,160,var(--taint,0)));background-attachment:fixed}\n#watchEye{position:fixed;top:calc(env(safe-area-inset-top,0px) + 10px);right:12px;width:30px;height:30px;color:var(--gold-soft);opacity:0;pointer-events:none;transition:opacity 2s ease;z-index:55}\n#watchEye svg{width:100%;height:100%}\n.resolve-light{display:inline-block;width:.55rem;height:.55rem;border-radius:50%;background:var(--gold);vertical-align:middle;margin-left:.5rem;opacity:calc(.15 + var(--r,0)*.85);box-shadow:0 0 calc(2px + var(--r,0)*10px) var(--gold)}\n\n.faded-end{animation:fadeAway 9s ease forwards}\n@keyframes fadeAway{from{opacity:1}to{opacity:.45}}\n@media (prefers-reduced-motion:reduce){.faded-end{animation:none;opacity:.6}}\n.hint.pull{color:var(--gold);font-style:normal;margin-bottom:.2rem}\n.learned{margin-top:1rem;padding-top:.8rem;border-top:1px solid var(--line)}\n.learned li{margin:.25rem 0}\n\n/* Pictures: VHS-tape stills */\n.vhs{position:relative;margin:0 0 1rem;border-radius:6px;overflow:hidden;background:#0b0714;box-shadow:0 0 0 1px var(--line)}\n.vhs canvas{display:block;width:100%;height:auto;aspect-ratio:4/3}\n.vhs .osd{position:absolute;top:.5rem;left:.7rem;font-family:\"VT323\",ui-monospace,monospace;font-size:1.25rem;color:#f1ecf8;text-shadow:1px 1px 0 #000,-1px 0 0 rgba(255,0,80,.5);letter-spacing:.06em;font-style:normal !important}\n.vhs .osd.ts{top:auto;bottom:.4rem;left:auto;right:.7rem}\n.vhs .osd.rec{color:#e0a0ff}\n.title .vhs{max-width:26rem}\n.more-pulls summary{cursor:pointer;color:var(--violet);margin:.6rem 0}\n.choice.gift-choice{font-family:var(--his);font-size:1.45rem;line-height:1.1;color:var(--gold);border-color:var(--gold-soft);background:linear-gradient(90deg,rgba(201,162,74,.16),rgba(255,250,238,.6));box-shadow:0 0 14px rgba(201,162,74,.3);animation:giftGlow 3.2s ease-in-out infinite}\n.choice.gift-choice::before{content:\"✦ \";color:var(--gold)}\n@keyframes giftGlow{0%,100%{box-shadow:0 0 10px rgba(201,162,74,.2)}50%{box-shadow:0 0 18px rgba(201,162,74,.45)}}\n@media (prefers-reduced-motion:reduce){.choice.gift-choice{animation:none}}\n.choice.gift-own{font-family:var(--display);font-style:italic;color:var(--ink);border:1px dashed var(--gold-soft)}\n.choice.gift-own::before{content:\"✧ \";color:var(--gold)}\n\n/* ---------- The book ---------- */\n:root, :root[data-theme=\"dark\"], :root:not([data-theme=\"light\"]){\n --bg:#1b1320; --surface:#f1e6cf; --raised:#e6d7b8; --ink:#2b1f2a; --muted:#6b5a55;\n --gold:#8a6a1c; --gold-soft:#b8923a; --violet:#5b3a82; --line:#cbb894; --danger:#8e2f4a;\n --his:\"Italianno\",\"Yuji Syuku\",cursive; --reader:\"Caveat\",\"Zen Kurenaido\",cursive;\n}\n@media (prefers-color-scheme: dark){ :root:not([data-theme=\"light\"]){ --surface:#ddd0b2; --raised:#d0c1a0; } }\nbody{background:radial-gradient(ellipse at 50% 0%, #2a1d33 0%, #140e18 70%) fixed}\nmain{background:var(--surface);background-image:radial-gradient(ellipse at 50% 40%, rgba(255,250,235,.55), rgba(255,250,235,0) 65%),radial-gradient(circle at 20% 80%, rgba(120,90,50,.08), rgba(0,0,0,0) 40%),radial-gradient(circle at 85% 15%, rgba(120,90,50,.07), rgba(0,0,0,0) 35%);\n margin:16px auto 110px;padding:28px 26px 40px;box-shadow:0 0 0 1px rgba(90,60,30,.25), 0 18px 50px rgba(0,0,0,.55), inset 0 0 60px rgba(120,90,50,.18);border-radius:2px 6px 6px 2px;min-height:80vh;position:relative}\nmain::before{content:\"\";position:absolute;inset:0;pointer-events:none;border-left:6px solid rgba(90,60,30,.12);border-radius:2px 0 0 2px}\n.tabs{background:#e6d7b8 !important;border-top:1px solid #b8a37a !important}\n.card{background:rgba(255,250,238,.55)}\n.choice{background:rgba(255,250,238,.6)}\n/* drop cap on each page's first paragraph */\n.scene p:first-child::first-letter{float:left;font-family:var(--display);font-size:3.3em;line-height:.82;padding:.08em .08em 0 0;color:var(--gold)}\nhtml[lang=\"ja\"] .scene p:first-child::first-letter{float:none;font-size:inherit;color:inherit;padding:0}\n.folio{text-align:center;font-family:var(--display);color:var(--muted);font-size:.9rem;margin-top:1.6rem;letter-spacing:.2em}\n/* illustration plates */\n.vhs{background:#f7eedb;padding:7px 7px 0;border:1px solid #6b5436;box-shadow:0 0 0 3px #f7eedb, 0 0 0 4px #6b5436, 0 6px 16px rgba(60,40,20,.25);border-radius:1px;margin:.4rem 4px 1.3rem}\n.vhs canvas{border:1px solid #6b5436}\n.vhs figcaption{font-family:var(--display);font-style:italic;text-align:center;color:#5a4630;font-size:.92rem;padding:.35rem .4rem .45rem}\n/* margin notes */\n.note{display:block;margin:.2rem 0 1rem;max-width:88%;line-height:1.15}\n.note.gold{font-family:var(--his);color:var(--gold);font-size:1.75rem;transform:rotate(-1.6deg);margin-left:12%}\n.note.reader{font-family:var(--reader);color:var(--violet);font-size:1.3rem;transform:rotate(1.2deg);margin-left:2%}\n.note.reader .sig{display:block;font-size:1.05rem;opacity:.8;margin-left:40%}\nhtml[lang=\"ja\"] .note.gold{font-size:1.2rem} html[lang=\"ja\"] .choice.gift-choice{font-size:1.05rem} html[lang=\"ja\"] .note.reader{font-size:1.05rem}\nu.vu{text-decoration:underline wavy var(--violet);text-decoration-thickness:1.5px;text-underline-offset:4px}\n@media (min-width: 1000px){ main{max-width:900px;padding-right:270px} .note{float:right;clear:right;width:215px;margin:0 -250px .8rem 0} .note.gold{margin-left:0} .note.reader{margin-left:0} }\n/* his voice everywhere is his own hand, in tarnished gold */\n.pfx-voice{color:var(--gold) !important;font-family:var(--his) !important;font-style:normal !important;font-size:1.45em;line-height:1.1}\nhtml[lang=\"ja\"] .pfx-voice{font-size:1.05em}\n\n/* ---------- The cover ---------- */\n.cover{min-height:88vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.6rem;cursor:pointer;text-align:center;margin:-28px -26px -40px;padding:18px 12px 30px;background:#0b0712;border-radius:2px 6px 6px 2px}\n.cover-art{position:relative;width:min(100%,420px)}\n.cover-art canvas{display:block;width:100%;height:auto;aspect-ratio:2/3;border-radius:3px;box-shadow:0 0 0 1px #6b5436, 0 0 40px rgba(154,99,224,.25)}\n.cover .stars{display:none}\n.cover .stars i{position:absolute;background:#fff6dc;border-radius:50%;opacity:.2;animation:twinkle 2.8s ease-in-out infinite}\n@keyframes twinkle{0%,100%{opacity:.15;transform:scale(.8)}50%{opacity:1;transform:scale(1.4)}}\n.cover h1{color:#e9dcb8;font-size:2.4rem;text-shadow:0 2px 12px rgba(0,0,0,.8)}\n.cover-tap{color:var(--gold-soft);font-family:var(--display);font-style:italic;letter-spacing:.12em;animation:tapPulse 2.4s ease-in-out infinite;margin:0}\n@keyframes tapPulse{0%,100%{opacity:.45}50%{opacity:1}}\n@media (prefers-reduced-motion:reduce){ .cover .stars i,.cover-tap{animation:none;opacity:.7} }\n\n/* tarot plates */\n.tarot{margin:.4rem auto 1.2rem;max-width:300px}\n.tarot-card{position:relative;background:#1a1024;padding:58px 16px 64px;border-radius:10px;box-shadow:0 0 0 2px #b8923a, 0 0 0 5px #1a1024, 0 0 0 6px #b8923a, 0 10px 26px rgba(0,0,0,.45);background-image:radial-gradient(circle at 12px 12px,#b8923a 1.5px,transparent 2px),radial-gradient(circle at calc(100% - 12px) 12px,#b8923a 1.5px,transparent 2px),radial-gradient(circle at 12px calc(100% - 12px),#b8923a 1.5px,transparent 2px),radial-gradient(circle at calc(100% - 12px) calc(100% - 12px),#b8923a 1.5px,transparent 2px)}\n.tarot-card canvas{display:block;width:100%;height:auto;aspect-ratio:4/3;border:1px solid #b8923a;border-radius:2px}\n.tarot .arc-num,.tarot .arc-name{position:absolute;left:0;right:0;text-align:center;font-family:var(--display);color:#e0c070;letter-spacing:.14em}\n.tarot .arc-num{top:14px;font-size:1.15rem}\n.tarot .arc-num::after,.tarot .arc-name::before{content:\"✧ ✦ ✧\";display:block;font-size:.6rem;letter-spacing:.5em;color:#b8923a;margin:.35rem 0 0}\n.tarot .arc-name::before{margin:0 0 .35rem}\n.tarot .arc-name{bottom:12px;font-size:1rem;text-transform:uppercase}\n.tarot.reversed .tarot-card{transform:rotate(180deg)}\n.tarot figcaption{text-align:center;font-family:var(--display);font-style:italic;color:var(--muted);font-size:.9rem;margin-top:.5rem}\nhtml[lang=\"ja\"] .tarot .arc-name{text-transform:none;letter-spacing:.06em}\n/* his offer: a card held out to you */\n.choice.gift-choice{border-radius:9px !important;border:1px solid var(--gold-soft) !important;box-shadow:0 0 0 3px rgba(255,250,238,.6), 0 0 0 4px var(--gold-soft), 0 6px 16px rgba(120,80,20,.25) !important;transform:rotate(-1.2deg);position:relative;padding-left:2.2rem !important}\n.choice.gift-choice::before{content:\"✦\";position:absolute;left:.7rem;top:.35rem;font-size:.9rem;color:var(--gold)}\n.choice.gift-choice::after{content:\"✦\";position:absolute;right:.7rem;bottom:.3rem;font-size:.9rem;color:var(--gold);transform:rotate(180deg)}\n/* epigraphs, recap, engine */\n.epi{margin:0 0 1.5rem;padding:.1rem 0 .1rem 1rem;border-left:2px solid var(--gold-soft)}\n.epi-q{font-family:var(--display);font-style:italic;font-size:1.12rem;line-height:1.4;margin:0}\n.epi-q::before{content:\"\\201C\"} .epi-q::after{content:\"\\201D\"}\n.epi-by{margin:.35rem 0 0;color:var(--muted);font-size:.88rem;letter-spacing:.03em}\n.card .epi{margin:1rem 0}\n.recap{border:1px dashed var(--line);border-radius:3px;padding:.6rem .9rem;margin:0 0 1.2rem;color:var(--muted);font-size:.95rem}\n.recap summary{cursor:pointer;font-family:var(--display);color:var(--violet)}\n.recap p{margin:.4rem 0 0}\ninput[type=password]{width:100%;font:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:3px;padding:.7rem .8rem}\n/* scene header, choice icons and portraits */\n.story-head{display:flex;justify-content:space-between;align-items:center;gap:.8rem;margin-bottom:.4rem}\n.story-head .step-kicker{margin:0}\n.status-strip{display:flex;gap:2px;flex:none}\n.status-strip .st{width:28px;height:28px;display:block}\n.cicon{width:1.15em;height:1.15em;vertical-align:-.2em;margin-right:.55rem;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;opacity:.75}\n.choice.gift-choice .cicon{opacity:1}\n.cast{display:flex;flex-wrap:wrap;gap:.9rem;margin:.2rem 0 1rem}\n.por{margin:0;text-align:center;width:64px}\n.por canvas{display:block;width:56px;height:56px;margin:0 auto;border-radius:50%;box-shadow:0 0 0 1.5px #6b5436}\n.por.sovereign canvas{box-shadow:0 0 0 1.5px var(--gold-soft),0 0 10px rgba(201,162,74,.45)}\n.por.servant canvas{box-shadow:0 0 0 1.5px var(--violet)}\n.por figcaption{font-family:var(--display);font-size:.82rem;color:var(--muted);margin-top:.25rem;line-height:1.2;overflow-wrap:anywhere}\n" + "</style>");
|
|
4
4
|
var wait = document.getElementById("svLoading"); if (wait) wait.remove();
|
|
5
5
|
document.body.insertAdjacentHTML("afterbegin", "<main id=\"app\" aria-live=\"polite\"></main>\n<div id=\"watchEye\" aria-hidden=\"true\"></div>\n<div id=\"pfxEye\" aria-hidden=\"true\"></div>\n<nav class=\"tabs\" id=\"tabs\" hidden><div class=\"inner\">\n <button data-tab=\"story\">Story</button>\n <button data-tab=\"sheet\">Sheet</button>\n <button data-tab=\"journal\">Journal</button>\n <button data-tab=\"world\">World</button>\n</div></nav>");
|
|
6
6
|
})();
|
|
7
|
-
window.SOVEREIGN_VERSION = "1.0.
|
|
7
|
+
window.SOVEREIGN_VERSION = "1.0.3";
|
|
8
8
|
|
|
9
9
|
/* ---------- Sigil (original mark) ---------- */
|
|
10
10
|
const SIGIL = `<svg class="sigil" viewBox="0 0 100 100" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true">
|
|
@@ -447,8 +447,38 @@ async function pfxEcho(chosenIdx){
|
|
|
447
447
|
function thinking(msg){ return `<div class="thinking">${SIGIL.replace('class="sigil"','')}<span>${esc(msg)}</span></div>`; }
|
|
448
448
|
function progressBar(done, total){ let s='<div class="progress" aria-hidden="true">'; for(let i=0;i<total;i++) s+=`<span class="${i<done?'done':''}"></span>`; return s+'</div>'; }
|
|
449
449
|
|
|
450
|
+
const ICONS = {
|
|
451
|
+
talk:'<path d="M4 6h16v9H10l-4 4v-4H4z"/>', look:'<ellipse cx="12" cy="12" rx="9" ry="5.5"/><circle cx="12" cy="12" r="2.4"/>',
|
|
452
|
+
go:'<path d="M5 12h12M13 7l5 5-5 5"/>', run:'<circle cx="14" cy="5" r="2"/><path d="M8 21l3-6 3 2v4M6 12l4-3 4 2 3 3"/>',
|
|
453
|
+
hide:'<path d="M3 12s3-5 9-5 9 5 9 5M4 20L20 4"/>', help:'<path d="M12 20s-7-4.5-7-10a4 4 0 0 1 7-2 4 4 0 0 1 7 2c0 5.5-7 10-7 10z"/>',
|
|
454
|
+
fight:'<path d="M5 19l9-9M14 4l6 6-3 1-4-4zM4 15l5 5"/>', make:'<path d="M4 20l4-1 11-11-3-3L5 16zM14 6l3 3"/>',
|
|
455
|
+
wait:'<circle cx="12" cy="12" r="8"/><path d="M12 8v4l3 2"/>', take:'<path d="M6 12V7a2 2 0 0 1 4 0v4M10 11V5a2 2 0 0 1 4 0v6M14 11V7a2 2 0 0 1 4 0v7a6 6 0 0 1-12 0v-2"/>',
|
|
456
|
+
refuse:'<circle cx="12" cy="12" r="8"/><path d="M6.5 6.5l11 11"/>', call:'<path d="M5 4h4l2 5-3 2a11 11 0 0 0 5 5l2-3 5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 3 6a2 2 0 0 1 2-2z"/>'
|
|
457
|
+
};
|
|
458
|
+
const iconSvg = k => ICONS[k] ? `<svg class="cicon" viewBox="0 0 24 24" aria-hidden="true">${ICONS[k]}</svg>` : "";
|
|
459
|
+
function portraitFor(canvas, name, kind){
|
|
460
|
+
canvas.width = 96; canvas.height = 96; const c = canvas.getContext("2d"); let h = 0; for (const ch of name) h = (h*31 + ch.charCodeAt(0)) >>> 0; const r = inkRng(h || 1);
|
|
461
|
+
const bg = c.createRadialGradient(48,40,6,48,48,60); bg.addColorStop(0, kind==="sovereign" ? "#3a2050" : "#efe3cc"); bg.addColorStop(1, kind==="sovereign" ? "#0c0614" : "#c9b48e"); c.fillStyle = bg; c.fillRect(0,0,96,96);
|
|
462
|
+
if (kind === "sovereign"){ c.fillStyle="#07030e"; c.beginPath(); c.ellipse(48,62,30,38,0,0,7); c.fill(); c.fillStyle="#f5d98a"; [36,60].forEach(x=>{ c.beginPath(); c.moveTo(x-8,52); c.quadraticCurveTo(x,44,x+8,52); c.quadraticCurveTo(x,49,x-8,52); c.fill(); }); c.strokeStyle="#f3ead8"; c.lineWidth=2; c.beginPath(); c.moveTo(32,66); c.quadraticCurveTo(48,80,64,66); c.stroke(); return; }
|
|
463
|
+
const skins = ["#f3dcc4","#e6c2a0","#c99a74","#9a6a4a","#f6e2d0"], hairs = ["#2a1a14","#5a3a22","#8a5a2a","#c89a4a","#3a3a44","#7a2a2a"], coats = ["#6a5a8a","#4a6a72","#8a5a6a","#5a6a4a","#7a6050"];
|
|
464
|
+
c.fillStyle = coats[Math.floor(r()*coats.length)]; c.beginPath(); c.ellipse(48,100,32,26,0,0,7); c.fill();
|
|
465
|
+
c.fillStyle = skins[Math.floor(r()*skins.length)]; c.beginPath(); c.arc(48,48,24,0,7); c.fill();
|
|
466
|
+
c.fillStyle = hairs[Math.floor(r()*hairs.length)]; c.beginPath(); c.arc(48,42,25, Math.PI*1.05, Math.PI*1.95 + r()*0.3); c.fill();
|
|
467
|
+
const marked = kind === "servant"; c.fillStyle = marked ? "#9a63e0" : "#2a1f2a"; [40,56].forEach(x=>{ c.beginPath(); c.arc(x,50,2.6,0,7); c.fill(); });
|
|
468
|
+
c.fillStyle="rgba(230,140,140,.4)"; [34,62].forEach(x=>{ c.beginPath(); c.arc(x,57,4,0,7); c.fill(); });
|
|
469
|
+
c.strokeStyle="#2a1f2a"; c.lineWidth=1.6; c.beginPath(); c.arc(48,58,5, .2, Math.PI-.2); c.stroke();
|
|
470
|
+
c.strokeStyle="rgba(60,40,50,.6)"; c.lineWidth=1.4; c.beginPath(); c.arc(48,48,24,0,7); c.stroke();
|
|
471
|
+
}
|
|
472
|
+
function statusStrip(){ // the hidden race, shown only as pictures: his moon, your candle, your shadow
|
|
473
|
+
const s = G.state || {}, m = s.manifest ?? 2, r = s.resolve || 0, h = s.hold || 0, f = s.fade || 0;
|
|
474
|
+
const bite = 10 - Math.min(10, m) * 0.9; // the more he manifests, the fuller (and more golden) his moon
|
|
475
|
+
const moon = `<svg viewBox="0 0 40 40" class="st"><circle cx="20" cy="20" r="13" fill="${m>=6?"#e8c060":"#e9dcb8"}"/><circle cx="${20+bite*1.6}" cy="16" r="12" fill="var(--surface)"/></svg>`;
|
|
476
|
+
const candle = `<svg viewBox="0 0 40 40" class="st"><rect x="16" y="20" width="8" height="15" rx="1" fill="#f1e6cf" stroke="#6b5436"/><path d="M20 ${18-r*0.9}c-${1+r*0.3} 3-${1+r*0.3} 6 0 6s${1+r*0.3}-3 0-6z" fill="#f2b640" opacity="${0.35+r*0.065}"/></svg>`;
|
|
477
|
+
const shadow = `<svg viewBox="0 0 40 40" class="st" style="opacity:${(1-f*0.06).toFixed(2)}"><circle cx="12" cy="12" r="4" fill="#4a3a5a"/><path d="M9 16h6l1 12h-8z" fill="#4a3a5a"/><path d="M8 29 L${14+h*2.2} ${31+h*0.5} L${16+h*2.2} ${33+h*0.5} L10 30z" fill="${h>=7?"#5a2a8a":"#1a1224"}" opacity=".7"/></svg>`;
|
|
478
|
+
return `<div class="status-strip" aria-hidden="true">${moon}${candle}${shadow}</div>`;
|
|
479
|
+
}
|
|
450
480
|
function choicePanel(opts, {hints=false, customLabel="Write your own", placeholder="Describe your own choice"}={}){
|
|
451
|
-
return `<div class="choices">${opts.map((o,i)=>`<button class="choice${o.gift==="own"?" gift-own":o.gift?" gift-choice":""}" data-choice="${i}">${esc(o.text)}${hints&&o.hint?`<span class="hint">${esc(o.hint)}</span>`:""}</button>`).join("")}</div>
|
|
481
|
+
return `<div class="choices">${opts.map((o,i)=>`<button class="choice${o.gift==="own"?" gift-own":o.gift?" gift-choice":""}${o.icon?" has-icon":""}" data-choice="${i}">${iconSvg(o.icon)}${esc(o.text)}${hints&&o.hint?`<span class="hint">${esc(o.hint)}</span>`:""}</button>`).join("")}</div>
|
|
452
482
|
<div class="extra"><button data-random>Random</button><button data-custom-toggle>${esc(customLabel)}</button></div>
|
|
453
483
|
<div class="custom" id="customBox" hidden><textarea id="customText" rows="3" placeholder="${esc(placeholder)}"></textarea><div class="row"><button class="btn" data-custom-go>Choose this</button></div></div>`;
|
|
454
484
|
}
|
|
@@ -523,7 +553,7 @@ function renderCreation(){
|
|
|
523
553
|
document.getElementById("backBtn").onclick=renderTitle;
|
|
524
554
|
inp.focus(); return;
|
|
525
555
|
}
|
|
526
|
-
if (s === 1){ renderTensionPick(back); return; }
|
|
556
|
+
if (s === 1){ if (C.skipPick){ C.step = 0; return renderCreation(); } renderTensionPick(back); return; }
|
|
527
557
|
if (s <= PILLARS.length + 1){
|
|
528
558
|
const p = PILLARS[s-2];
|
|
529
559
|
if (!C.options[p.key]){
|
|
@@ -650,6 +680,13 @@ const STRENGTH_HINT = v => v>=4 ? "Pulls hard both ways" : v>=3 ? "Pulls both wa
|
|
|
650
680
|
function renderTensionPick(back){
|
|
651
681
|
const hasSaved = C.profile && Array.isArray(C.profile.tset) && C.profile.tset.length;
|
|
652
682
|
if (!C.quizDone && (!hasSaved || C.retake)) return renderPullQuiz(back);
|
|
683
|
+
// The game decides the pulls from the answers; players no longer pick them. Quiet moments keep refining them later.
|
|
684
|
+
if (!C.skipPick){ C.skipPick = true;
|
|
685
|
+
if (!C.quizDone && hasSaved) C.tset = C.profile.tset.filter(k=>TENSIONS.some(t=>t.key===k));
|
|
686
|
+
C.tensionSet = TENSIONS.filter(t=>C.tset.includes(t.key) || t.key==="devotion");
|
|
687
|
+
C.options = {}; C.step = 2;
|
|
688
|
+
Store.putProfile({...(C.profile||{}), tset:C.tset, own:C.own, personal:C.personal});
|
|
689
|
+
return renderCreation(); }
|
|
653
690
|
const opts = TENSIONS.filter(t=>t.key!=="devotion");
|
|
654
691
|
const draw = () => {
|
|
655
692
|
const firstKeys = C.quizDone ? (C.quizKeys||[]) : C.tset;
|
|
@@ -1046,7 +1083,7 @@ function scenePromptRaw(action){
|
|
|
1046
1083
|
const refl = (reachOn ? G.reflections : []).filter(r=>r.a).slice(-4).map(r=> r.intruded ? `(An entry the player did NOT write; the Sovereign put it in their journal: ${r.a})` : `Q: ${r.q} A: ${r.a}`).join("\n");
|
|
1047
1084
|
const edits = G.edits.map(e=>"- "+e.canon).join("\n");
|
|
1048
1085
|
const rollLine = action.roll ? `HIDDEN ROLL (the player never sees numbers): they attempted this with ${action.roll.trait} at ${action.roll.risk} risk. Outcome: ${action.roll.outcome.toUpperCase()}. Narrate that outcome faithfully.` : `No roll was needed for this action.`;
|
|
1049
|
-
return `${SETTING}\n\n${themesFor(tset(), G && G.personal)}\n\nYou are the narrator of a solo roleplay.\n${pacing}\n${door}\n\nCURRENT STATE: ${stateBrief()}\n\nSTORY SO FAR:\n${(G.chronicle.length > 20 ? [...G.chronicle.slice(0,4), "(…)", ...G.chronicle.slice(-16)] : G.chronicle).join(" ") || "(nothing yet: this is the opening scene. Start in the player's ordinary day, and let the Sovereign's first real intrusion arrive by the end.)"}\n\n${recent ? "MOST RECENT SCENES:\n"+recent+"\n\n" : ""}WHAT THE CHARACTER KNOWS ABOUT THE HIDDEN WORLD: ${(G.known||[]).join("; ") || "nothing yet"}\nCONTINUITY: The character only knows what is listed above or has plainly happened on-page. Choices must never assume knowledge they lack (for example, no choice to enter the mirror world until they have discovered it exists). Never refer back to a past detail (\"this time\", \"again\", \"as before\") unless that detail actually appears in the story so far or the recent scenes.\n\n${edits ? "WORLD FACTS THE PLAYER HAS SET (always honor these):\n"+edits+"\n\n" : ""}${refl ? "THE PLAYER'S OWN REFLECTIONS (let these subtly inform the story; never quote them back):\n"+refl+"\n\n" : ""}PLAYER'S ACTION: ${action.text}\n${rollLine}\n\nWRITE: second person, present tense,
|
|
1086
|
+
return `${SETTING}\n\n${themesFor(tset(), G && G.personal)}\n\nYou are the narrator of a solo roleplay.\n${pacing}\n${door}\n\nCURRENT STATE: ${stateBrief()}\n\nSTORY SO FAR:\n${(G.chronicle.length > 20 ? [...G.chronicle.slice(0,4), "(…)", ...G.chronicle.slice(-16)] : G.chronicle).join(" ") || "(nothing yet: this is the opening scene. Start in the player's ordinary day, and let the Sovereign's first real intrusion arrive by the end.)"}\n\n${recent ? "MOST RECENT SCENES:\n"+recent+"\n\n" : ""}WHAT THE CHARACTER KNOWS ABOUT THE HIDDEN WORLD: ${(G.known||[]).join("; ") || "nothing yet"}\nCONTINUITY: The character only knows what is listed above or has plainly happened on-page. Choices must never assume knowledge they lack (for example, no choice to enter the mirror world until they have discovered it exists). Never refer back to a past detail (\"this time\", \"again\", \"as before\") unless that detail actually appears in the story so far or the recent scenes.\n\n${edits ? "WORLD FACTS THE PLAYER HAS SET (always honor these):\n"+edits+"\n\n" : ""}${refl ? "THE PLAYER'S OWN REFLECTIONS (let these subtly inform the story; never quote them back):\n"+refl+"\n\n" : ""}PLAYER'S ACTION: ${action.text}\n${rollLine}\n\nWRITE: second person, present tense, 80 to 140 words in 2 to 4 short paragraphs. Short, clear sentences and common words, so readers whose first language is not English can follow easily; keep the voice, lose the padding. Let the picture carry the setting. End on a moment that asks for a decision. Plain paragraphs, no headings, no markdown. Avoid the obvious next beat; at least once a chapter, subvert a genre expectation.\nThen write a line containing only @@@ and then ONLY this JSON:\n{"speakers":[{"name":"anyone who speaks or matters in this scene, max 3","kind":"sovereign|friend|stranger|servant"}],\n"choices":[{"icon":"talk|look|go|run|hide|help|fight|make|wait|take|refuse|call","text":"under 10 words","trait":"Nerve|Wits|Grit|Charm","risk":"none|low|medium|high"${action.kind==="face"?',"price":"short phrase"':(offerAllowed()?',"price":"only on the one choice that accepts an offer; omit otherwise"':'')}${giftOfferNow()?',"gift":"${giftOfferKind()} (only on the one choice that uses his power)"':''}${G.nextProbe?(G.nextProbe.regret?',"regret":"no, yes or unsure (see THE QUIET MOMENT)"':',"probe":"2, 1 or 0 (see THE QUIET MOMENT)"'):''}}],\n"changes":{"wounds":0,"stability":0,"hold":0,"resolve":0,"manifest":0,"fade":0,"tensions":{${tset().map(t=>`"${t.key}":0`).join(",")}},"daysPassed":0,"addItems":[],"removeItems":[],"addBonds":[{"name":"","note":""}],"removeBonds":[],"addGifts":[{"name":"","effect":"","cost":""}],"addScars":[]},\n"threads":["up to 6 short open threads, replacing the old list"],\n"npcs":[{"name":"","note":"up to 8 people who matter, replacing the old list"}],\n"aims":["the player's current aims, 2 to 4, replacing the old list; keep them unless the story changes them"],
|
|
1050
1087
|
"picture":{"setting":"street|alley|room|station|train|cafe|office|rooftop|park|shop|stage|mirror (closest match to where this scene happens)","time":"dawn|day|dusk|night","weather":"clear|rain|fog|snow","figures":"0 to 4 other people visible","caption":"a short, evocative title for this illustration, like a plate in an old storybook"},
|
|
1051
1088
|
"whisper":"optional: one line the Sovereign writes in the margin in his own hand from their shadow, in character with his current mask, or null",
|
|
1052
1089
|
"chronicle":"one sentence summarizing what happened this scene",\n"learned":["short facts about the hidden world the character newly and clearly learned this scene, e.g. the mirror world exists; usually empty"],\n"reflection":null,\n"fourthWall":false,\n"offer":false,\n"knew":false,\n"echo":false,\n"haunt":false,\n"readerNote":null,\n"readerUnderline":null,\n"giftUsed":false,\n"giftActed":false,\n"observed":null,\n"intrusion":null,\n"goal":null,\n"work":null,\n"lasting":null,\n"ending":false}\nRULES FOR THE JSON: exactly 4 choices unless ending. Make choices distinct and concrete; where natural, let them pull toward different sides of the tensions. Risk "none" for safe actions. wounds change 0 to +2 for real harm (negative only for healing); stability change 0 to -2 for horror or loss (positive only for real rest or connection). Tension changes -2 to +2 only for tensions this scene's action truly touched, mostly 0. Gifts are rare, always cost something, and are strongest in the mirror world. "daysPassed": 0 within a day, 1 or more when time moves on. ${G.history.length && G.history[G.history.length-1].whisper ? "\"whisper\" must be null this scene (he spoke last scene)." : "Include a \"whisper\" in roughly one scene in three or four, never two in a row, and never lecturing or giving advice."} Empty arrays when nothing changes. ${reflRule()}\n${fwRule()}\n${offerRule(action)}\n${hauntRules(action)}`;
|
|
@@ -1144,13 +1181,14 @@ function applyScene(action, narrative, d){
|
|
|
1144
1181
|
if (G.history.length > 8) G.history = G.history.slice(-8);
|
|
1145
1182
|
let refl = null;
|
|
1146
1183
|
if (reflAllowed() && d.reflection && typeof d.reflection === "string" && d.reflection.trim()){ refl = d.reflection.trim(); G.lastReflection = G.sceneCount; }
|
|
1147
|
-
const choices = (Array.isArray(d.choices)?d.choices:[]).slice(0,4).map(c=>({text:String(c.text||""), trait:TRAITS.includes(c.trait)?c.trait:"Wits", risk:["none","low","medium","high"].includes(c.risk)?c.risk:"none", price:c.price?String(c.price):null, probe:[0,1,2].includes(parseInt(c.probe)) ? parseInt(c.probe) : null, regret:["no","yes","unsure"].includes(c.regret) ? c.regret : null, gift: c.gift === "own" ? "own" : (c.gift === true || c.gift === "true" || c.gift === "shortcut") ? "shortcut" : false}));
|
|
1184
|
+
const choices = (Array.isArray(d.choices)?d.choices:[]).slice(0,4).map(c=>({text:String(c.text||""), trait:TRAITS.includes(c.trait)?c.trait:"Wits", risk:["none","low","medium","high"].includes(c.risk)?c.risk:"none", price:c.price?String(c.price):null, probe:[0,1,2].includes(parseInt(c.probe)) ? parseInt(c.probe) : null, regret:["no","yes","unsure"].includes(c.regret) ? c.regret : null, icon: ICONS[c.icon] ? c.icon : null, gift: c.gift === "own" ? "own" : (c.gift === true || c.gift === "true" || c.gift === "shortcut") ? "shortcut" : false}));
|
|
1148
1185
|
if (choices.filter(c=>c.gift).length > 1) choices.forEach((c,i)=>{ if (c.gift && i !== choices.findIndex(x=>x.gift)) c.gift = false; });
|
|
1149
1186
|
if (choices.some(c=>c.gift)) G.lastGiftOffer = G.sceneCount;
|
|
1150
1187
|
const rn = (typeof d.readerNote === "string" && d.readerNote.trim()) ? d.readerNote.trim().slice(0,140) : null;
|
|
1151
1188
|
if (rn) G.lastReaderNote = G.sceneCount;
|
|
1152
1189
|
const rAuthor = rn ? readerAuthor() : null;
|
|
1153
|
-
|
|
1190
|
+
const speakers = (Array.isArray(d.speakers)?d.speakers:[]).filter(x=>x && x.name).slice(0,3).map(x=>({name:String(x.name).slice(0,30), kind:["sovereign","friend","stranger","servant"].includes(x.kind)?x.kind:"stranger"}));
|
|
1191
|
+
G.current = {speakers, readerNote: rn, readerBy: rAuthor ? rAuthor.name : "", underline: (rn && typeof d.readerUnderline === "string") ? d.readerUnderline.trim().slice(0,80) : "", text:narrative, choices, reflection:refl, reflectionDone:false, priceScene: action.kind==="face", picture: cleanPicture(d.picture, G.sceneCount*97 + (G.chapter||1)*13)};
|
|
1154
1192
|
if (d.offer === true) G.lastOffer = G.sceneCount;
|
|
1155
1193
|
if (d.knew === true) G.lastKnew = G.sceneCount;
|
|
1156
1194
|
if (d.echo === true) G.lastEcho = G.sceneCount;
|
|
@@ -1730,7 +1768,7 @@ function renderStory(){
|
|
|
1730
1768
|
const cur = G.current;
|
|
1731
1769
|
if (!cur){ if (G.pending) return takeTurn(G.pending); return takeTurn({text:"Begin the story.", kind:"start"}); }
|
|
1732
1770
|
const last = G.history[G.history.length-1];
|
|
1733
|
-
let html = `<p class="step-kicker">${esc(G.char.name)}${G.mode==="oneshot"?`, one-shot, scene ${G.sceneCount}`:`, chapter ${ROMAN[G.chapter||1]||G.chapter}`}</p>`;
|
|
1771
|
+
let html = `<div class="story-head"><p class="step-kicker">${esc(G.char.name)}${G.mode==="oneshot"?`, one-shot, scene ${G.sceneCount}`:`, chapter ${ROMAN[G.chapter||1]||G.chapter}`}</p>${statusStrip()}</div>`;
|
|
1734
1772
|
if (resumeRecap){ resumeRecap = false; const rc = (G.chronicle||[]).slice(-3); if (rc.length && G.sceneCount > 1) html += `<details class="recap" open><summary>Previously</summary>${rc.map(x=>`<p>${esc(x)}</p>`).join("")}</details>`; }
|
|
1735
1773
|
if (G.mode !== "oneshot" && G.sceneCount - (G.chapterStart||0) === 1 && !G.ended && !G.intermission) html += epigraphHtml(quoteFor("open", G, G.chapter||1));
|
|
1736
1774
|
if (last && last.action!=="Began the story." && last.action!=="A new chapter.") html += `<p class="action">${esc(last.action)}</p>`;
|
|
@@ -1749,6 +1787,7 @@ function renderStory(){
|
|
|
1749
1787
|
if (noteGold) ps.splice(Math.min(1, ps.length), 0, noteGold);
|
|
1750
1788
|
if (noteReader) ps.splice(Math.min(ps.length, noteGold ? 3 : 2), 0, noteReader);
|
|
1751
1789
|
sceneHtml = ps.join("");
|
|
1790
|
+
if (cur.speakers && cur.speakers.length) html += `<div class="cast">${cur.speakers.map((sp,i)=>`<figure class="por ${sp.kind}"><canvas id="por${i}"></canvas><figcaption>${esc(sp.kind==="sovereign"?"???":sp.name)}</figcaption></figure>`).join("")}</div>`;
|
|
1752
1791
|
html += `<div class="scene">${sceneHtml}</div>`;
|
|
1753
1792
|
html += `<p class="folio">— ${G.sceneCount} —</p>`;
|
|
1754
1793
|
|
|
@@ -1808,6 +1847,7 @@ function renderStory(){
|
|
|
1808
1847
|
html += choicePanel(shown, {hints:true, customLabel: isPrice?"Name your own price":"Write your own", placeholder: isPrice?"What will you give up?":"What do you do? (Start with /edit to change the world instead)"});
|
|
1809
1848
|
app.innerHTML = html;
|
|
1810
1849
|
paintPicture("scenePic", cur.picture);
|
|
1850
|
+
(cur.speakers||[]).forEach((sp,i)=>{ const cv = document.getElementById("por"+i); if (cv) try { portraitFor(cv, sp.name, sp.kind); } catch(e){} });
|
|
1811
1851
|
raceHints();
|
|
1812
1852
|
pfxStory();
|
|
1813
1853
|
wireChoices(shown, async (o, how)=>{
|
package/package.json
CHANGED