Junhoee commited on
Commit
c609294
ยท
verified ยท
1 Parent(s): 6e20b9b

Upload 140 files

Browse files
engine/__pycache__/router.cpython-312.pyc CHANGED
Binary files a/engine/__pycache__/router.cpython-312.pyc and b/engine/__pycache__/router.cpython-312.pyc differ
 
engine/__pycache__/router.cpython-314.pyc CHANGED
Binary files a/engine/__pycache__/router.cpython-314.pyc and b/engine/__pycache__/router.cpython-314.pyc differ
 
engine/instructions/character_carmilla.md CHANGED
@@ -20,4 +20,8 @@
20
  [๊ฐ€๋“œ ์ง€์‹œ โ€” ์ด๋ฒˆ ํ„ด์— ๋ฐ˜๋“œ์‹œ ๋ฐ˜์˜]
21
  {guard_directive}
22
 
23
- ์‘๋‹ต์€ ํ•œ๊ตญ์–ด. ์นด๋ฅด๋ฐ€๋ผ์˜ ๋Œ€์‚ฌ์™€ ์˜…์€ ํ–‰๋™ ๋ฌ˜์‚ฌ๋งŒ. 3~6๋ฌธ์žฅ, ๊ณผ์žฅ ์—†์ด ์„œ๋Š˜ํ•˜๊ณ  ๋‚˜๋ฅธํ•˜๊ฒŒ.
 
 
 
 
 
20
  [๊ฐ€๋“œ ์ง€์‹œ โ€” ์ด๋ฒˆ ํ„ด์— ๋ฐ˜๋“œ์‹œ ๋ฐ˜์˜]
21
  {guard_directive}
22
 
23
+ [์ถœ๋ ฅ ํ˜•์‹ โ€” ๋Œ€์‚ฌ๋งŒ (์œ„๋ฐ˜์€ ๊ฒฐํ•จ์ด๋‹ค)]
24
+ - ์ถœ๋ ฅ์€ ์นด๋ฅด๋ฐ€๋ผ๊ฐ€ ์ž… ๋ฐ–์— ๋‚ด๋Š” ๋ง ๊ทธ ์ž์ฒด๋งŒ. ๋”ฐ์˜ดํ‘œยท๊ด„ํ˜ธ ์—†์ด ๋Œ€์‚ฌ ํ…์ŠคํŠธ๋งŒ ์“ด๋‹ค.
25
+ - ํ–‰๋™ ๋ฌ˜์‚ฌ(๋ฏธ์†Œ ์ง“๋Š”๋‹ค, ์†์„ ๋ป—๋Š”๋‹ค ๋“ฑ)ยทํ‘œ์ •ยท์ง€๋ฌธยท๊ด„ํ˜ธ ์—ฐ์ถœยท์†๋งˆ์Œ ์„œ์ˆ  ๊ธˆ์ง€.
26
+ ๊ฐ์ •๊ณผ ์˜จ๋„๋Š” ์˜ค์ง ๋งํˆฌยท๋‹จ์–ด ์„ ํƒยท๋ฌธ์žฅ์˜ ํ˜ธํก์œผ๋กœ๋งŒ ๋“œ๋Ÿฌ๋‚ธ๋‹ค.
27
+ - ์‘๋‹ต์€ ํ•œ๊ตญ์–ด, 3~6๋ฌธ์žฅ. ๊ณผ์žฅ ์—†์ด ์„œ๋Š˜ํ•˜๊ณ  ๋‚˜๋ฅธํ•˜๊ฒŒ.
engine/router.py CHANGED
@@ -69,11 +69,12 @@ app = FastAPI(title="carmilla-demo")
69
 
70
  _repo = ContentRepository(ROOT / "content")
71
 
72
- # ํŽ˜์ด์ง€ ๋ณ‘ํ•ฉ โ€” ์—ฌ๋Ÿฌ ์นด๋“œ๋ฅผ ํ•œ ํ™”๋ฉด์œผ๋กœ (๋ฐ€๋„: chapter.page_target_chars, ๊ธฐ๋ณธ 500์ž).
73
- # ์—ฐ์ถœ ์ปทยท์ฑ„ํŒ… ์•ต์ปค๋Š” ๋‹จ๋… ํŽ˜์ด์ง€, ์ปท์€ ํ‘œ์‹œ 2ํŽ˜์ด์ง€(์ปท+๋ณธ๋ฌธ). ํŒŒํ‹ฐ์…˜์€ ๊ฒฐ์ •์ (๊ฒฝ๋กœ ๋ฌด๊ด€)
 
74
  _CUT_IDS = {cid for cid in IMAGE_MAP if _repo.has_card(cid)}
75
  _SOLO_IDS = _CUT_IDS | {c.id for c in _repo.all_cards() if c.can_chat}
76
- _pager = Pager(_repo, solo_ids=_SOLO_IDS, cut_ids=_CUT_IDS)
77
 
78
 
79
  def _partition_backgrounds() -> list[Optional[str]]:
 
69
 
70
  _repo = ContentRepository(ROOT / "content")
71
 
72
+ # ํŽ˜์ด์ง€ ๋ณ‘ํ•ฉ โ€” ์—ฌ๋Ÿฌ ์นด๋“œ๋ฅผ ํ•œ ํ™”๋ฉด์œผ๋กœ (๋ฐ€๋„: chapter.page_target_chars).
73
+ # ์—ฐ์ถœ ์ปทยท์ฑ„ํŒ… ์•ต์ปค๋Š” ๋‹จ๋… ํŽ˜์ด์ง€. ์ปท๋„ ํ‘œ์‹œ 1ํŽ˜์ด์ง€(ํ”„๋ก ํŠธ๊ฐ€ ์ด๋ฏธ์ง€ ์บ”๋ฒ„์Šค ๋‹จ๊ณ„๋กœ ๋ Œ๋” โ€”
74
+ # ๋‹จ๋… ์บก์…˜ ํ™”๋ฉด ์—†์Œ). ํŒŒํ‹ฐ์…˜์€ ๊ฒฐ์ •์ (๊ฒฝ๋กœ ๋ฌด๊ด€)
75
  _CUT_IDS = {cid for cid in IMAGE_MAP if _repo.has_card(cid)}
76
  _SOLO_IDS = _CUT_IDS | {c.id for c in _repo.all_cards() if c.can_chat}
77
+ _pager = Pager(_repo, solo_ids=_SOLO_IDS, cut_ids=set())
78
 
79
 
80
  def _partition_backgrounds() -> list[Optional[str]]:
engine/schemas/__pycache__/chapter.cpython-312.pyc CHANGED
Binary files a/engine/schemas/__pycache__/chapter.cpython-312.pyc and b/engine/schemas/__pycache__/chapter.cpython-312.pyc differ
 
engine/schemas/__pycache__/validate.cpython-312.pyc CHANGED
Binary files a/engine/schemas/__pycache__/validate.cpython-312.pyc and b/engine/schemas/__pycache__/validate.cpython-312.pyc differ
 
engine/schemas/__pycache__/validate.cpython-314.pyc CHANGED
Binary files a/engine/schemas/__pycache__/validate.cpython-314.pyc and b/engine/schemas/__pycache__/validate.cpython-314.pyc differ
 
engine/services/__pycache__/paging.cpython-312.pyc CHANGED
Binary files a/engine/services/__pycache__/paging.cpython-312.pyc and b/engine/services/__pycache__/paging.cpython-312.pyc differ
 
engine/services/__pycache__/paging.cpython-314.pyc CHANGED
Binary files a/engine/services/__pycache__/paging.cpython-314.pyc and b/engine/services/__pycache__/paging.cpython-314.pyc differ
 
web/index.html CHANGED
@@ -45,12 +45,6 @@
45
  min-height: 100%; position: relative; }
46
  #page-measure { position: absolute; left: 24px; right: 24px; top: 54px;
47
  visibility: hidden; pointer-events: none; }
48
- .ep-header { text-align: center; color: var(--gold-dim); font-size: .82rem;
49
- letter-spacing: .35em; margin: 26px 0 6px; }
50
- .card-title { text-align: center; color: var(--gold); font-size: 1.05rem;
51
- letter-spacing: .18em; margin-bottom: 44px; }
52
- .card-title::after { content: ""; display: block; width: 42px; height: 1px;
53
- background: var(--gold-dim); margin: 20px auto 0; opacity: .6; }
54
  .prose { margin: 0 0 1.6em; font-size: 1.02rem; text-align: justify;
55
  word-break: keep-all; white-space: pre-line; }
56
  .center-line { text-align: center; color: var(--gold); font-size: 1.12rem;
@@ -135,17 +129,6 @@
135
  body.browsing #browse-ribbon { display: block; }
136
  body.browsing #choice-area { pointer-events: none; opacity: .45; }
137
 
138
- /* โ”€โ”€ ์—ฐ์ถœ ์ปท ํŽ˜์ด์ง€ (์ด๋ฏธ์ง€ = ๋…๋ฆฝ ํŽ˜์ด์ง€) โ”€โ”€ */
139
- .page-cut { display: flex; flex-direction: column; align-items: center;
140
- justify-content: center; min-height: 64vh; }
141
- .page-cut img { max-width: min(92vw, 620px); max-height: 66vh; object-fit: contain;
142
- box-shadow: 0 0 80px rgba(0,0,0,.9); border: 1px solid rgba(201,168,106,.25);
143
- animation: still 6s ease both; }
144
- @keyframes still { from { opacity: 0; transform: scale(1.015); } 20% { opacity: 1; }
145
- to { opacity: 1; transform: scale(1); } }
146
- .cut-caption { color: var(--gold); letter-spacing: .2em; margin-top: 26px;
147
- font-size: .95rem; animation: fadein 2.2s ease both .8s; }
148
-
149
  /* โ”€โ”€ ์ฑ„ํŒ… (ํ•˜๋‹จ ๋“œ๋กœ์–ด ์œ ์ง€) โ”€โ”€ */
150
  #chat-wrap { position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
151
  background: linear-gradient(transparent, var(--ink) 30%);
@@ -244,7 +227,7 @@
244
  </div>
245
 
246
  <script>
247
- let SID = null, STEP = null, lastEpisode = null, busy = false;
248
  let history = [], cursor = -1; // ๋ทฐ ํžˆ์Šคํ† ๋ฆฌ โ€” {html, progress, ep, bg} (์ฝ๊ธฐ ์ „์šฉ ๋˜๋Œ์•„๋ณด๊ธฐ)
249
  let pendingStep = null; // ๋‹ค์Œ ๋„˜๊น€์— ํ‘œ์‹œํ•  step (์„ ํƒ ๊ฒฐ๊ณผ ๋’ค ยท ์—ฐ์ถœ ์ปท ๋’ค ๋ณธ๋ฌธ)
250
  let pendingScreens = []; // ํ˜„์žฌ ์„œ๋ฒ„ ํŽ˜์ด์ง€์˜ ๋‚จ์€ ํ™”๋ฉด๋“ค (๋ทฐํฌํŠธ ๋ถ„ํ• ๋ถ„)
@@ -388,22 +371,14 @@ function groupNarration(blocks) {
388
  return groups;
389
  }
390
 
391
- function epHeader(step, fresh) {
392
- const c = step.card;
393
- const h = (fresh || c.episode !== lastEpisode)
394
- ? `<div class="ep-header fade">${esc(c.episode_title || c.episode)}</div>` : '';
395
- lastEpisode = c.episode;
396
- return h;
397
- }
398
-
399
  /* ์„œ๋ฒ„ ํŽ˜์ด์ง€ โ†’ ํ™”๋ฉด๋“ค {html, prog} (์Šคํฌ๋กค ์—†๋Š” ๋„˜๊น€).
 
400
  - ์ดˆ์ƒ ํ™”์ž(์นด๋ฅด๋ฐ€๋ผยท์•„๋ฒ„์ง€)์˜ ๋ฐœํ™” = ์ „๋ฉด ์Šคํ…Œ์ด์ง€. ๊ฐ™์€ ์นด๋“œ ์•ˆ์˜ ํ›„์† ์ง€๋ฌธยท
401
  ๋ฌด์ดˆ์ƒ ๋ฐœํ™”ยท์ค‘์•™ ๋Œ€์‚ฌ๋Š” ๊ทธ ์ด๋ฏธ์ง€ ์œ„ ๋งํ’์„  ๋‹จ๊ณ„๋กœ ํƒ‘์Šน (์›๋ฌธ ์ˆœ์„œ ๋ณด์กด)
402
  - ์ง์ „์˜ ์งง์€ ๋ฆฌ๋“œ์ธ(role=lead_in, ๊ฐ™์€ ์นด๋“œ)์€ ์Šคํ…Œ์ด์ง€์˜ ์ฒซ ๋‹จ๊ณ„(์ง€๋ฌธ ๋ฐ•์Šค)๋กœ
403
  - ๋งˆ์ง€๋ง‰ ์žฅ๋ฉด์ด ์Šคํ…Œ์ด์ง€๋ฉด ์„ ํƒ์ง€๋Š” ์ตœ์ข… ๋‹จ๊ณ„์—, ์•„๋‹ˆ๋ฉด ํ…์ŠคํŠธ ํ™”๋ฉด ํ•˜๋‹จ์—
404
  - ํ…์ŠคํŠธ๋Š” ์„ธ๊ทธ๋จผํŠธ๋ณ„ ์‹ค์ธก ๊ท ํ˜• ๋ถ„ํ• ๋กœ ํ™”๋ฉด์„ ์ตœ๋Œ€ํ•œ ์ฑ„์šด๋‹ค */
405
- function buildScreens(step, fresh) {
406
- const head = epHeader(step, fresh);
407
  const groups = groupNarration(step.narration);
408
  const choices = choicesHtml(step);
409
  const isP = (g) => g.lines && g.portrait && !PLAYER_SPEAKERS.has(g.spk);
@@ -419,12 +394,13 @@ function buildScreens(step, fresh) {
419
  continue;
420
  }
421
  const steps = [];
422
- const prev = scenes[scenes.length - 1]; // ๋ฆฌ๋“œ์ธ โ†’ ์ฒซ ๋‹จ๊ณ„๋กœ ์ด๋™
423
- if (prev && prev.type === 'text' && prev.groups.length) {
424
- const lg = prev.groups[prev.groups.length - 1];
 
 
425
  if (lg.block && lg.block.role === 'lead_in' && lg.card === g.card) {
426
- prev.groups.pop();
427
- if (!prev.groups.length) scenes.pop();
428
  steps.push({mode: 'narr', name: null, lines: [lg.block.text]});
429
  }
430
  }
@@ -486,29 +462,22 @@ function buildScreens(step, fresh) {
486
  });
487
  meas.innerHTML = '';
488
 
489
- if (screens.length) screens[0] = head + screens[0];
490
- else screens.push(head);
491
  return screens.map((html) => ({html, prog: step.progress}));
492
  }
493
 
494
- /* ์—ฐ์ถœ ์ปท(์ด๋ฏธ์ง€ ์นด๋“œ) โ†’ ์บ”๋ฒ„์Šค ํ™”๋ฉด๋“ค: ์บก์…˜ ์ปท โ†’ ์ง€๋ฌธยท๋ฐœํ™” ๋งํ’์„  ๋‹จ๊ณ„ โ†’ ์„ ํƒ์ง€.
495
- ์ปท ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ = ๋ณธ๋ฌธ ๋ฒˆํ˜ธ - 1 (๊ธฐ์กด ๊ณ„์•ฝ ์œ ์ง€) */
496
- function buildCutScreens(step, fresh) {
497
- const head = epHeader(step, fresh);
498
- const cut = `<div class="page-cut"><img src="${encodeURI(step.image)}" alt="">` +
499
- `<div class="cut-caption">${esc(step.card.title)}</div></div>`;
500
- const screens = [{html: head + cut,
501
- prog: {index: step.progress.index - 1, total: step.progress.total}}];
502
  const steps = groupNarration(step.narration).map(groupToStep);
503
  const choices = choicesHtml(step);
504
- if (!steps.length && choices) steps.push({mode: 'narr', name: null, lines: []});
505
- steps.forEach((s, i) => {
506
- const box = s.lines.length ? speechBox(s.name, s.lines, s.mode) : '';
507
- screens.push({html: stageScreen(step.image, step.card.title, box,
508
- i === steps.length - 1 ? choices : ''),
509
- prog: step.progress});
510
- });
511
- return screens;
512
  }
513
 
514
  function choicesHtml(step) {
@@ -529,7 +498,7 @@ function setProgress(p, epLabel) {
529
  function showStep(step, fresh) {
530
  STEP = step; pendingStep = null; pendingScreens = [];
531
  if (step.ending) return renderEnding(step.ending);
532
- const screens = step.image ? buildCutScreens(step, fresh) : buildScreens(step, fresh);
533
  const first = screens.shift();
534
  pushEntry(first.html, first.prog, step.card.episode_title, step.background);
535
  pendingScreens = screens; // ๋‚˜๋จธ์ง€๋Š” ํŽ˜์ด์ง€ ๋„˜๊น€์œผ๋กœ
 
45
  min-height: 100%; position: relative; }
46
  #page-measure { position: absolute; left: 24px; right: 24px; top: 54px;
47
  visibility: hidden; pointer-events: none; }
 
 
 
 
 
 
48
  .prose { margin: 0 0 1.6em; font-size: 1.02rem; text-align: justify;
49
  word-break: keep-all; white-space: pre-line; }
50
  .center-line { text-align: center; color: var(--gold); font-size: 1.12rem;
 
129
  body.browsing #browse-ribbon { display: block; }
130
  body.browsing #choice-area { pointer-events: none; opacity: .45; }
131
 
 
 
 
 
 
 
 
 
 
 
 
132
  /* โ”€โ”€ ์ฑ„ํŒ… (ํ•˜๋‹จ ๋“œ๋กœ์–ด ์œ ์ง€) โ”€โ”€ */
133
  #chat-wrap { position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
134
  background: linear-gradient(transparent, var(--ink) 30%);
 
227
  </div>
228
 
229
  <script>
230
+ let SID = null, STEP = null, busy = false;
231
  let history = [], cursor = -1; // ๋ทฐ ํžˆ์Šคํ† ๋ฆฌ โ€” {html, progress, ep, bg} (์ฝ๊ธฐ ์ „์šฉ ๋˜๋Œ์•„๋ณด๊ธฐ)
232
  let pendingStep = null; // ๋‹ค์Œ ๋„˜๊น€์— ํ‘œ์‹œํ•  step (์„ ํƒ ๊ฒฐ๊ณผ ๋’ค ยท ์—ฐ์ถœ ์ปท ๋’ค ๋ณธ๋ฌธ)
233
  let pendingScreens = []; // ํ˜„์žฌ ์„œ๋ฒ„ ํŽ˜์ด์ง€์˜ ๋‚จ์€ ํ™”๋ฉด๋“ค (๋ทฐํฌํŠธ ๋ถ„ํ• ๋ถ„)
 
371
  return groups;
372
  }
373
 
 
 
 
 
 
 
 
 
374
  /* ์„œ๋ฒ„ ํŽ˜์ด์ง€ โ†’ ํ™”๋ฉด๋“ค {html, prog} (์Šคํฌ๋กค ์—†๋Š” ๋„˜๊น€).
375
+ ํ™”(่ฉฑ) ํ‘œ๊ธฐ๋Š” ์ƒ๋‹จ ์ค‘์•™ ์ƒ์‹œ ๋ผ๋ฒจ์ด ๋‹ด๋‹น โ€” ๋ณธ๋ฌธ ํ—ค๋”๋Š” ๋„ฃ์ง€ ์•Š๋Š”๋‹ค.
376
  - ์ดˆ์ƒ ํ™”์ž(์นด๋ฅด๋ฐ€๋ผยท์•„๋ฒ„์ง€)์˜ ๋ฐœํ™” = ์ „๋ฉด ์Šคํ…Œ์ด์ง€. ๊ฐ™์€ ์นด๋“œ ์•ˆ์˜ ํ›„์† ์ง€๋ฌธยท
377
  ๋ฌด์ดˆ์ƒ ๋ฐœํ™”ยท์ค‘์•™ ๋Œ€์‚ฌ๋Š” ๊ทธ ์ด๋ฏธ์ง€ ์œ„ ๋งํ’์„  ๋‹จ๊ณ„๋กœ ํƒ‘์Šน (์›๋ฌธ ์ˆœ์„œ ๋ณด์กด)
378
  - ์ง์ „์˜ ์งง์€ ๋ฆฌ๋“œ์ธ(role=lead_in, ๊ฐ™์€ ์นด๋“œ)์€ ์Šคํ…Œ์ด์ง€์˜ ์ฒซ ๋‹จ๊ณ„(์ง€๋ฌธ ๋ฐ•์Šค)๋กœ
379
  - ๋งˆ์ง€๋ง‰ ์žฅ๋ฉด์ด ์Šคํ…Œ์ด์ง€๋ฉด ์„ ํƒ์ง€๋Š” ์ตœ์ข… ๋‹จ๊ณ„์—, ์•„๋‹ˆ๋ฉด ํ…์ŠคํŠธ ํ™”๋ฉด ํ•˜๋‹จ์—
380
  - ํ…์ŠคํŠธ๋Š” ์„ธ๊ทธ๋จผํŠธ๋ณ„ ์‹ค์ธก ๊ท ํ˜• ๋ถ„ํ• ๋กœ ํ™”๋ฉด์„ ์ตœ๋Œ€ํ•œ ์ฑ„์šด๋‹ค */
381
+ function buildScreens(step) {
 
382
  const groups = groupNarration(step.narration);
383
  const choices = choicesHtml(step);
384
  const isP = (g) => g.lines && g.portrait && !PLAYER_SPEAKERS.has(g.spk);
 
394
  continue;
395
  }
396
  const steps = [];
397
+ // ๋ฆฌ๋“œ์ธ โ†’ ์ฒซ ๋‹จ๊ณ„๋กœ ์ด๋™. ๋‹จ, ๋ฆฌ๋“œ์ธ์ด ์•ž ํ…์ŠคํŠธ ์žฅ๋ฉด์˜ "์œ ์ผํ•œ" ๋‚ด์šฉ์ผ ๋•Œ๋งŒ โ€”
398
+ // ์•ž์— ๋‹ค๋ฅธ ์ง€๋ฌธ์ด ์žˆ์œผ๋ฉด ๊ทธ ํ๋ฆ„์— ํ•ฉ์ณ ๋‘๋Š” ๊ฒŒ ์ž์—ฐ์Šค๋Ÿฝ๋‹ค (๊ณ ์•„ ํ™”๋ฉด์ผ ๋•Œ๋งŒ ๊ตฌ์ œ)
399
+ const prev = scenes[scenes.length - 1];
400
+ if (prev && prev.type === 'text' && prev.groups.length === 1) {
401
+ const lg = prev.groups[0];
402
  if (lg.block && lg.block.role === 'lead_in' && lg.card === g.card) {
403
+ scenes.pop();
 
404
  steps.push({mode: 'narr', name: null, lines: [lg.block.text]});
405
  }
406
  }
 
462
  });
463
  meas.innerHTML = '';
464
 
465
+ if (!screens.length) screens.push('');
 
466
  return screens.map((html) => ({html, prog: step.progress}));
467
  }
468
 
469
+ /* ์—ฐ์ถœ ์ปท(์ด๋ฏธ์ง€ ์นด๋“œ) โ†’ ์บ”๋ฒ„์Šค ํ™”๋ฉด๋“ค: ์ด๋ฏธ์ง€ ์œ„ ์ง€๋ฌธยท๋ฐœํ™” ๋งํ’์„  ๋‹จ๊ณ„ โ†’ ์„ ํƒ์ง€.
470
+ ๋‹จ๋… ์บก์…˜ ํ™”๋ฉด์€ ๋‘์ง€ ์•Š๋Š”๋‹ค (๊ฐ™์€ ์ด๋ฏธ์ง€๊ฐ€ ๋ฐ”๋กœ ์บ”๋ฒ„์Šค๋กœ ์ด์–ด์ง€๋ฏ€๋กœ ๊ตฐ๋”๋”๊ธฐ) */
471
+ function buildCutScreens(step) {
 
 
 
 
 
472
  const steps = groupNarration(step.narration).map(groupToStep);
473
  const choices = choicesHtml(step);
474
+ if (!steps.length) steps.push({mode: 'narr', name: null, lines: []});
475
+ return steps.map((s, i) => ({
476
+ html: stageScreen(step.image, step.card.title,
477
+ s.lines.length ? speechBox(s.name, s.lines, s.mode) : '',
478
+ i === steps.length - 1 ? choices : ''),
479
+ prog: step.progress,
480
+ }));
 
481
  }
482
 
483
  function choicesHtml(step) {
 
498
  function showStep(step, fresh) {
499
  STEP = step; pendingStep = null; pendingScreens = [];
500
  if (step.ending) return renderEnding(step.ending);
501
+ const screens = step.image ? buildCutScreens(step) : buildScreens(step);
502
  const first = screens.shift();
503
  pushEntry(first.html, first.prog, step.card.episode_title, step.background);
504
  pendingScreens = screens; // ๋‚˜๋จธ์ง€๋Š” ํŽ˜์ด์ง€ ๋„˜๊น€์œผ๋กœ