sovereign-in-violet 1.0.0 → 1.0.2
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 +41 -13
- package/package.json +3 -2
package/dist/game.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/* sovereign-in-violet 1.0.
|
|
1
|
+
/* sovereign-in-violet 1.0.2, 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: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" + "</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.2";
|
|
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">
|
|
@@ -863,7 +863,7 @@ function identityRule(g){
|
|
|
863
863
|
const r = rom === "men" ? "Any romantic partners, past or present (exes, crushes, spouses), are men." : rom === "women" ? "Any romantic partners, past or present (exes, crushes, spouses), are women." : rom === "none" ? "Leave romance out entirely: no exes, crushes or partners." : "Romantic partners may be of any gender.";
|
|
864
864
|
return `PLAYER IDENTITY: ${p} ${r}`;
|
|
865
865
|
}
|
|
866
|
-
function pacingRules(){
|
|
866
|
+
function pacingRules(action){
|
|
867
867
|
const cn = chScene(), camp = G.mode !== "oneshot";
|
|
868
868
|
G.wins = G.wins || {you:0, him:0}; G.chapter = G.chapter || 1;
|
|
869
869
|
const unit = camp ? `a CAMPAIGN, CHAPTER ${ROMAN[G.chapter]||G.chapter}, in ACT ${actOf()} of 3: ${ACTS[actOf()].name.toUpperCase()}. The campaign has three acts: Manifestation (he becomes real), Establishment (he becomes a known public figure everyone agrees has always existed) and Outbreak (the gates open and our reality is assimilated into his). Each chapter he wins moves the campaign to the next act; if the player wins 3 chapters first, they seal him away` : "a ONE-SHOT";
|
|
@@ -874,7 +874,7 @@ function pacingRules(){
|
|
|
874
874
|
if (G.mode !== "oneshot") t += ` In Act ${actOf()} the Work's reach grows: ${["","it reaches a few people","it goes public","it spreads everywhere, live and viral"][actOf()]}.`;
|
|
875
875
|
t += G.goal ? `\nTHE GOAL of this ${what}, from the player's side: ${G.goal}` : `\nThis ${what} has no goal yet. Establish one tied to the Work: making it, finishing it or sharing it, in a way that tests the player's tensions. Set "goal" in the JSON to one short sentence from the player's point of view.`;
|
|
876
876
|
if ((G.worldChanges||[]).length) t += `\nLASTING CHANGES he has already made to the world: ${G.worldChanges.join("; ")}`;
|
|
877
|
-
const e = G.pendingEnd;
|
|
877
|
+
const e = (action && action.kind === "face") ? null : G.pendingEnd; // at the brink, the ending waits until the price is paid
|
|
878
878
|
if (e){
|
|
879
879
|
const final = !camp || e === "lost" || e === "faded" || (e==="free" && G.wins.you>=2) || (e==="real" && G.wins.him>=2);
|
|
880
880
|
const who = {
|
|
@@ -1008,17 +1008,37 @@ function reflRule(){
|
|
|
1008
1008
|
const asked = (G.reflections||[]).filter(r=>!r.intruded).slice(-8).map(r=>"- "+r.q).join("\n");
|
|
1009
1009
|
return `"reflection": only if this scene has meaningfully touched a tension, set it to one gentle open question under 20 words linking the story to the player's own life; otherwise null. It must explore a genuinely different angle from every question already asked, not a rephrasing of one.${asked ? " Questions already asked:\n"+asked : ""}`;
|
|
1010
1010
|
}
|
|
1011
|
+
/* Paranoia: faint at first, a jolt when you first see him in your life, then slowly, noticeably worse */
|
|
1012
|
+
function paranoiaLevel(){
|
|
1013
|
+
if (!G || G.paraStart == null) return 0;
|
|
1014
|
+
return Math.min(10, 1 + (G.state.hold||0) + Math.floor((G.sceneCount - G.paraStart) / 6));
|
|
1015
|
+
}
|
|
1016
|
+
function paranoiaStage(p){
|
|
1017
|
+
if (p >= 9) return "the whispers are almost clear now: somewhere inside them is the character's own name, and his";
|
|
1018
|
+
if (p >= 7) return "the certainty that everyone is watching and talking: crowds turning as one, whispers in every room, no privacy left anywhere, and no way to be sure what is real and what is him";
|
|
1019
|
+
if (p >= 5) return "conversations that stop when they walk in, strangers who seem to know about their gifts, a coworker repeating something they only thought, messages read and never answered";
|
|
1020
|
+
if (p >= 3) return "whispers just below hearing, in a language they almost know, that they somehow know are about them; the feeling of eyes on them even in the most private places, a bathroom mirror, under the blankets";
|
|
1021
|
+
return "a faint sense of being watched, a stranger glancing twice, easy to dismiss";
|
|
1022
|
+
}
|
|
1023
|
+
function paranoiaRule(){
|
|
1024
|
+
const p = paranoiaLevel(); if (!p) return "";
|
|
1025
|
+
const freq = p >= 7 ? "in most scenes" : p >= 3 ? "in roughly every other scene" : "only now and then";
|
|
1026
|
+
let t = `PARANOIA (hidden level ${p}/10): his meddling makes the character feel watched, ${freq}. Right now: ${paranoiaStage(p)}. Keep it ambiguous and woven into ordinary moments: it might be him, it might be their own nerves, and the story never settles which. Unnerving, but curious too: what does he want from all this attention?`;
|
|
1027
|
+
if (G.joltPending) t = `THE JOLT: this scene is the first time they unmistakably glimpse him in their own ordinary life, outside any dream or mirror world: a detail that cannot be explained away, gone the next instant. Let it land hard, then let life go on as if nothing happened.\n` + t;
|
|
1028
|
+
return t + ` It is supernatural and his doing: never frame it as the player's real mind, and never suggest the real player is being watched outside the story.`;
|
|
1029
|
+
}
|
|
1011
1030
|
function scenePrompt(action){
|
|
1012
1031
|
const off = [];
|
|
1013
1032
|
const p = scenePromptRaw(action).replace(/^"(\w+)": must be (?:false|null) this scene[^\n]*\n?/gm, (m, k)=>{ off.push(k); return ""; });
|
|
1014
|
-
|
|
1033
|
+
const pr = paranoiaRule();
|
|
1034
|
+
return (off.length ? p + `\nNot allowed this scene (leave false or null): ${off.join(", ")}.` : p) + (pr ? "\n" + pr : "");
|
|
1015
1035
|
}
|
|
1016
1036
|
function scenePromptRaw(action){
|
|
1017
1037
|
const s = G.state;
|
|
1018
1038
|
const n = G.sceneCount + 1;
|
|
1019
|
-
const pacing = identityRule(G) + "\n" + pacingRules() + "\n" + raceRules() + "\n" + giftRules() + "\n" + choiceLens() + "\n" + readerRule() + (G.nextProbe ? "\n" + probeRule() : "") + `\nOnly set "ending" to true when told this scene is an ending.`;
|
|
1039
|
+
const pacing = identityRule(G) + "\n" + pacingRules(action) + "\n" + raceRules() + "\n" + giftRules() + "\n" + choiceLens() + "\n" + readerRule() + (G.nextProbe ? "\n" + probeRule() : "") + `\nOnly set "ending" to true when told this scene is an ending.`;
|
|
1020
1040
|
const door = action.kind==="door"
|
|
1021
|
-
? `The player has just paid this price to survive the brink: "${action.price||action.text}". Narrate their survival and the price taking hold in a way that will matter later. Then return to normal play
|
|
1041
|
+
? `The player has just paid this price to survive the brink: "${action.price||action.text}". Narrate their survival and the price taking hold in a way that will matter later.${G.pendingEnd ? "" : " Then return to normal play."}`
|
|
1022
1042
|
: action.kind==="face"
|
|
1023
1043
|
? `DEATH'S DOOR: The player has reached the brink (wounds 5/5 or stability 0/5). This scene is the brink itself. They will not die. The Sovereign or another power offers survival for a price. The 4 choices must be 4 different prices: losing part of themselves (a memory, a sense, a bond, a year), owing a debt to something that will collect, a lasting scar, or something stranger. Each choice needs a "price" field: the lasting mark as a short phrase for their sheet.`
|
|
1024
1044
|
: `If this scene pushes wounds to 5 or stability to 0, end the scene at the very brink without resolving it.`;
|
|
@@ -1116,7 +1136,8 @@ function applyScene(action, narrative, d){
|
|
|
1116
1136
|
if (G.known.length > 30) G.known = G.known.slice(-30);
|
|
1117
1137
|
if (G.chronicle.length > 120) G.chronicle = G.chronicle.slice(-120);
|
|
1118
1138
|
G.sceneCount++;
|
|
1119
|
-
const
|
|
1139
|
+
const endingNow = !!G.pendingEnd && action.kind !== "face"; // the scene that closes a chapter or story never opens Death's Door
|
|
1140
|
+
const reached = !endingNow && action.kind!=="face" && !s.deathsDoor && (s.wounds>=5 || s.stability<=0);
|
|
1120
1141
|
if (reached) s.deathsDoor = true;
|
|
1121
1142
|
const omen = action.roll ? OMENS[action.roll.outcome] : "";
|
|
1122
1143
|
G.history.push({action: action.kind==="start" ? "Began the story." : action.kind==="chapter" ? "A new chapter." : (action.label||action.text), omen, text: narrative, whisper: (typeof d.whisper==="string" && d.whisper.trim()) ? d.whisper.trim() : ""});
|
|
@@ -1189,15 +1210,17 @@ function applyScene(action, narrative, d){
|
|
|
1189
1210
|
if (t){ G.tensionSet = [...set, t]; G.state.tensions[k] = clamp((b||0)-(a||0), -3, 3); }
|
|
1190
1211
|
}
|
|
1191
1212
|
}
|
|
1213
|
+
if (G.joltPending && G.paraStart != null && G.sceneCount > G.paraStart) G.joltPending = false;
|
|
1214
|
+
if (G.paraStart == null && ((G.known||[]).length || (s.hold||0) >= 2 || s.lastGift)){ G.paraStart = G.sceneCount; G.joltPending = true; }
|
|
1192
1215
|
if (typeof d.goal === "string" && d.goal.trim() && !G.goal) G.goal = d.goal.trim().slice(0,200);
|
|
1193
1216
|
if (typeof d.work === "string" && d.work.trim()) G.work = d.work.trim().slice(0,160);
|
|
1194
|
-
if (
|
|
1195
|
-
else {
|
|
1217
|
+
if (endingNow){ finishRace(G.pendingEnd, d); }
|
|
1218
|
+
else if (!G.pendingEnd) {
|
|
1196
1219
|
const cs = G.sceneCount - (G.chapterStart||0);
|
|
1197
1220
|
let w = (s.hold||0) >= 10 ? "lost" : (s.fade||0) >= 10 ? "faded" : (s.resolve||0) >= 10 ? "free" : (s.manifest||0) >= 10 ? "real" : null;
|
|
1198
1221
|
if (!w && cs >= STORY_LEN - 1) w = (s.resolve||0) >= (s.manifest||0) ? "free" : "real";
|
|
1199
1222
|
if (w) G.pendingEnd = w;
|
|
1200
|
-
if (cs >= HARD_END) finishRace(G.pendingEnd, d); // safety net: never let it run on
|
|
1223
|
+
if (cs >= HARD_END && !s.deathsDoor) finishRace(G.pendingEnd, d); // safety net: never let it run on (at the brink, the price scene ends it)
|
|
1201
1224
|
}
|
|
1202
1225
|
}
|
|
1203
1226
|
|
|
@@ -1715,7 +1738,12 @@ function renderStory(){
|
|
|
1715
1738
|
if (cur.picture) html += picHtml("scenePic", G.sceneCount, cur.picture.caption ? `Plate ${G.sceneCount}. ${cur.picture.caption}` : "");
|
|
1716
1739
|
let sceneHtml = paras(cur.text);
|
|
1717
1740
|
if (cur.underline){ const u = esc(cur.underline); const at = sceneHtml.indexOf(u); if (u.length > 3 && at >= 0) sceneHtml = sceneHtml.slice(0,at) + `<u class="vu">${u}</u>` + sceneHtml.slice(at+u.length); }
|
|
1718
|
-
|
|
1741
|
+
let noteGold = (last && last.whisper) ? `<aside class="note gold">${esc(last.whisper)}</aside>` : "";
|
|
1742
|
+
const pl = paranoiaLevel();
|
|
1743
|
+
if (!noteGold && pl >= 3){ const rr = inkRng(G.sceneCount*7919 + (G.chapter||1)); if (rr() < (pl >= 7 ? 0.6 : 0.3)){
|
|
1744
|
+
const gib = () => Array.from({length: 4 + Math.floor(rr()*6)}, ()=>"abcdefghijklmnopqrstuvwxyzæœþð"[Math.floor(rr()*30)]).join("");
|
|
1745
|
+
const words = Array.from({length: 5 + Math.floor(rr()*4)}, ()=>`<span class="g">${gib()}</span>`); if (pl >= 6 && G.char && G.char.name) words.splice(2 + Math.floor(rr()*3), 0, `<span class="clear">${esc(G.char.name.split(" ")[0])}</span>`);
|
|
1746
|
+
noteGold = `<aside class="note gold whisperling" aria-hidden="true">${words.join(" ")}</aside>`; } }
|
|
1719
1747
|
const noteReader = cur.readerNote ? `<aside class="note reader">${esc(cur.readerNote)}${cur.readerBy ? `<span class="sig">— ${esc(cur.readerBy)}</span>` : ""}</aside>` : "";
|
|
1720
1748
|
const ps = sceneHtml.split("</p>").filter(x=>x.trim()).map(x=>x+"</p>");
|
|
1721
1749
|
if (noteGold) ps.splice(Math.min(1, ps.length), 0, noteGold);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sovereign-in-violet",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "The Sovereign in Violet: a solo, AI-narrated horror storybook game. Loaded by a small page published as a claude.ai artifact.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"files": [
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "node tools/build.mjs",
|
|
11
11
|
"test": "node tools/build.mjs && node tools/test-loader.mjs",
|
|
12
|
-
"prepublishOnly": "node tools/build.mjs"
|
|
12
|
+
"prepublishOnly": "node tools/build.mjs",
|
|
13
|
+
"playtest": "node tools/playtest.mjs"
|
|
13
14
|
}
|
|
14
15
|
}
|