Junhoee commited on
Commit
f8d9250
Β·
verified Β·
1 Parent(s): 1c9c13f

Upload 125 files

Browse files
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/router.py CHANGED
@@ -270,7 +270,9 @@ async def meta():
270
  # ── 정적 ν”„λ‘ νŠΈ ─────────────────────────────────────────────
271
  @app.get("/")
272
  async def index():
273
- return FileResponse(ROOT / "web" / "index.html")
 
 
274
 
275
 
276
  app.mount("/static", StaticFiles(directory=ROOT / "web"), name="static")
 
270
  # ── 정적 ν”„λ‘ νŠΈ ─────────────────────────────────────────────
271
  @app.get("/")
272
  async def index():
273
+ # no-cache: λ§€ μ ‘μ†λ§ˆλ‹€ μž¬κ²€μ¦ β€” 배포 ν›„ λΈŒλΌμš°μ €κ°€ μ˜› HTML을 계속 λ³΄μ—¬μ£ΌλŠ” 사고 λ°©μ§€
274
+ return FileResponse(ROOT / "web" / "index.html",
275
+ headers={"Cache-Control": "no-cache"})
276
 
277
 
278
  app.mount("/static", StaticFiles(directory=ROOT / "web"), name="static")
web/index.html CHANGED
@@ -69,10 +69,6 @@
69
  .vn-stage::after { content: ""; position: absolute; inset: 0;
70
  background: linear-gradient(rgba(11,14,20,.12) 0%, transparent 22%,
71
  transparent 40%, rgba(11,14,20,.96) 82%); }
72
- .vn-lead { position: absolute; top: 0; left: 0; right: 0; z-index: 1;
73
- padding: 16px 22px 26px; font-size: .96rem; color: var(--text);
74
- word-break: keep-all; text-shadow: 0 1px 8px rgba(0,0,0,.95);
75
- background: linear-gradient(rgba(11,14,20,.82), transparent); }
76
  .vn-stage-panel { position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
77
  padding: 0 22px 20px; text-align: center; }
78
  .vn-stage-panel .vn-line { font-size: 1.02rem; line-height: 1.9; margin: .3em 0;
@@ -347,11 +343,10 @@ function charLines(g) { // μ„œμ‚¬ 속 λ°œν™” β€” 쒌 ν™”μžλͺ… Β·
347
  `<div class="char-line fade"><span class="spk">${esc(name)}</span>β€œ${esc(t)}”</div>`).join('');
348
  }
349
 
350
- function vnStage(g, choices, lead) { // μ΄ˆμƒμ΄ ν”„λ ˆμž„μ„ μ±„μš°κ³  ν•˜λ‹¨ νŒ¨λ„μ— λŒ€μ‚¬+선택지
351
  let h = `<div class="vn-stage fade">` +
352
- `<img class="vn-stage-art" src="${encodeURI(g.portrait)}" alt="${esc(g.spk)}">`;
353
- if (lead) h += `<div class="vn-lead">${esc(lead)}</div>`; // λ¦¬λ“œμΈ β€” 이미지 상단에
354
- h += `<div class="vn-stage-panel"><div class="vn-name">${esc(g.spk)}</div>`;
355
  for (const t of g.lines) h += `<div class="vn-line">β€œ${esc(t)}”</div>`;
356
  return h + `<div id="choice-area">${choices}</div></div></div>`;
357
  }
@@ -377,9 +372,9 @@ function groupNarration(blocks) {
377
 
378
  /* μ„œλ²„ νŽ˜μ΄μ§€ β†’ ν™”λ©΄λ“€ (슀크둀 μ—†λŠ” λ„˜κΉ€).
379
  - 타이틀은 μ—ν”Όμ†Œλ“œ 첫 νŽ˜μ΄μ§€μ˜ ν™”(θ©±) ν—€λ”λ§Œ β€” μΉ΄λ“œ μ†Œμ œλͺ©μ€ ν‘œμ‹œν•˜μ§€ μ•ŠλŠ”λ‹€ (μ±…μ˜ 흐름)
380
- - μ„œμ‚¬ 속 λ°œν™”λŠ” char-line으둜 본문에 흑수, μŠ€ν…Œμ΄μ§€(μ΄ˆμƒ ν™”λ©΄)λŠ”
381
- μΈν„°λž™μ…˜ νŽ˜μ΄μ§€(μ„ νƒμ§€Β·μ±„νŒ…)의 λ§ˆμ§€λ§‰ μ΄ˆμƒ λ°œν™”μ—λ§Œ (단독 ν™”λ©΄)
382
- - λ‚˜λ¨Έμ§€ 블둝은 μ‹€μΈ‘(#page-measure)으둜 뷰포트 μ˜ˆμ‚° μ•ˆμ—μ„œ μ±„μš΄λ‹€ */
383
  function buildScreens(step, fresh) {
384
  const c = step.card;
385
  let head = '';
@@ -396,42 +391,56 @@ function buildScreens(step, fresh) {
396
  if (g.lines && g.portrait && !PLAYER_SPEAKERS.has(g.spk)) { stageIdx = k; break; }
397
  }
398
  }
 
399
 
400
  const meas = $('page-measure');
401
  const budget = Math.max((window.innerHeight || 900) - 190, 320);
 
 
402
  const screens = [];
403
- let cur = head;
404
- const flush = () => { if (cur.trim()) screens.push(cur); cur = ''; };
405
-
406
- const stageIsLast = stageIdx === groups.length - 1; // λ§ˆμ§€λ§‰ 그룹일 λ•Œλ§Œ 선택지 λ‚΄μž₯
407
- // μŠ€ν…Œμ΄μ§€ μ§μ „μ˜ λ¦¬λ“œμΈ(role=lead_in 단일 블둝)은 μŠ€ν…Œμ΄μ§€ 화면에 μ–ΉλŠ”λ‹€ β€” 파편 ν™”λ©΄ λ°©μ§€
408
- let leadIdx = -1;
409
- if (stageIdx > 0) {
410
- const pg = groups[stageIdx - 1];
411
- if (pg.block && pg.block.role === 'lead_in') leadIdx = stageIdx - 1;
412
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  groups.forEach((g, k) => {
414
- if (k === leadIdx) return; // μŠ€ν…Œμ΄μ§€μ— 포함
415
- if (k === stageIdx) { // μŠ€ν…Œμ΄μ§€ = 단독 ν™”λ©΄
416
- flush();
417
- screens.push(vnStage(g, stageIsLast ? choices : '',
418
- leadIdx >= 0 ? groups[leadIdx].block.text : null));
419
  return;
420
  }
421
  const html = g.lines ? charLines(g) : block(g.block);
422
- meas.innerHTML = cur + html;
423
- if (cur.trim() && meas.offsetHeight > budget) flush();
424
- cur += html;
425
  });
426
- if (choices && !stageIsLast) { // κ·Έ μ™Έμ—λŠ” 선택지λ₯Ό λ§ˆμ§€λ§‰ ν™”λ©΄ ν•˜λ‹¨μ—
427
  const area = `<div id="choice-area">${choices}</div>`;
428
- meas.innerHTML = cur + area;
429
- if (cur.trim() && meas.offsetHeight > budget) flush();
430
- cur += area;
431
  }
432
- flush();
433
  meas.innerHTML = '';
434
- return screens.length ? screens : [head];
 
 
 
435
  }
436
 
437
  function choicesHtml(step) {
 
69
  .vn-stage::after { content: ""; position: absolute; inset: 0;
70
  background: linear-gradient(rgba(11,14,20,.12) 0%, transparent 22%,
71
  transparent 40%, rgba(11,14,20,.96) 82%); }
 
 
 
 
72
  .vn-stage-panel { position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
73
  padding: 0 22px 20px; text-align: center; }
74
  .vn-stage-panel .vn-line { font-size: 1.02rem; line-height: 1.9; margin: .3em 0;
 
343
  `<div class="char-line fade"><span class="spk">${esc(name)}</span>β€œ${esc(t)}”</div>`).join('');
344
  }
345
 
346
+ function vnStage(g, choices) { // 독립 ν™”λ©΄: μ΄ˆμƒ + μ΄λ¦„ν‘œ + κ·Έ μΊλ¦­ν„°μ˜ λŒ€μ‚¬ (+선택지)만
347
  let h = `<div class="vn-stage fade">` +
348
+ `<img class="vn-stage-art" src="${encodeURI(g.portrait)}" alt="${esc(g.spk)}">` +
349
+ `<div class="vn-stage-panel"><div class="vn-name">${esc(g.spk)}</div>`;
 
350
  for (const t of g.lines) h += `<div class="vn-line">β€œ${esc(t)}”</div>`;
351
  return h + `<div id="choice-area">${choices}</div></div></div>`;
352
  }
 
372
 
373
  /* μ„œλ²„ νŽ˜μ΄μ§€ β†’ ν™”λ©΄λ“€ (슀크둀 μ—†λŠ” λ„˜κΉ€).
374
  - 타이틀은 μ—ν”Όμ†Œλ“œ 첫 νŽ˜μ΄μ§€μ˜ ν™”(θ©±) ν—€λ”λ§Œ β€” μΉ΄λ“œ μ†Œμ œλͺ©μ€ ν‘œμ‹œν•˜μ§€ μ•ŠλŠ”λ‹€ (μ±…μ˜ 흐름)
375
+ - μŠ€ν…Œμ΄μ§€(μ΄ˆμƒ ν™”λ©΄)λŠ” 이미지+κ·Έ μΊλ¦­ν„°μ˜ λŒ€μ‚¬(+선택지)만 β€” 지문은 μ„žμ§€ μ•ŠλŠ”λ‹€
376
+ - μ§€λ¬ΈΒ·λ°œν™”(char-line)λŠ” μ„Έκ·Έλ¨ΌνŠΈ(μŠ€ν…Œμ΄μ§€ 경계 사이)λ³„λ‘œ μ‹€μΈ‘ κ· ν˜• λΆ„ν• :
377
+ ν™”λ©΄ 수 k = ceil(총높이/μ˜ˆμ‚°)λ₯Ό λ¨Όμ € μ •ν•˜κ³  κ· λ“± κ²½κ³„μ—μ„œ μ ˆλ‹¨ β€” ν•œ λ¬Έμž₯ κ³ μ•„ ν™”λ©΄ λ°©μ§€ */
378
  function buildScreens(step, fresh) {
379
  const c = step.card;
380
  let head = '';
 
391
  if (g.lines && g.portrait && !PLAYER_SPEAKERS.has(g.spk)) { stageIdx = k; break; }
392
  }
393
  }
394
+ const stageIsLast = stageIdx === groups.length - 1;
395
 
396
  const meas = $('page-measure');
397
  const budget = Math.max((window.innerHeight || 900) - 190, 320);
398
+ const measure = (html) => { meas.innerHTML = html; return meas.offsetHeight; };
399
+
400
  const screens = [];
401
+ let seg = []; // ν˜„μž¬ μ„Έκ·Έλ¨ΌνŠΈμ˜ μœ λ‹›λ“€ {html, h}
402
+ const flushSeg = () => { // μ„Έκ·Έλ¨ΌνŠΈλ₯Ό κ· ν˜• λΆ„ν• ν•΄ ν™”λ©΄μœΌλ‘œ
403
+ if (!seg.length) return;
404
+ const total = seg.reduce((s, u) => s + u.h, 0);
405
+ const k = Math.max(1, Math.ceil(total / budget));
406
+ const target = total / k;
407
+ let cur = '', acc = 0, cuts = 0;
408
+ seg.forEach((u, i) => {
409
+ cur += u.html; acc += u.h;
410
+ const remainUnits = seg.length - i - 1;
411
+ const remainCuts = (k - 1) - cuts;
412
+ if (cuts < k - 1 && remainUnits > 0) {
413
+ const boundary = target * (cuts + 1);
414
+ const nextAcc = acc + seg[i + 1].h;
415
+ if (Math.abs(acc - boundary) <= Math.abs(nextAcc - boundary)
416
+ || remainUnits === remainCuts) {
417
+ screens.push(cur); cur = ''; cuts++;
418
+ }
419
+ }
420
+ });
421
+ if (cur) screens.push(cur);
422
+ seg = [];
423
+ };
424
+
425
  groups.forEach((g, k) => {
426
+ if (k === stageIdx) { // μŠ€ν…Œμ΄μ§€ = 독립 ν™”λ©΄ (이미지+λŒ€μ‚¬λ§Œ)
427
+ flushSeg();
428
+ screens.push(vnStage(g, stageIsLast ? choices : ''));
 
 
429
  return;
430
  }
431
  const html = g.lines ? charLines(g) : block(g.block);
432
+ seg.push({html, h: measure(html)});
 
 
433
  });
434
+ if (choices && !stageIsLast) {
435
  const area = `<div id="choice-area">${choices}</div>`;
436
+ seg.push({html: area, h: measure(area)});
 
 
437
  }
438
+ flushSeg();
439
  meas.innerHTML = '';
440
+
441
+ if (screens.length) screens[0] = head + screens[0]; // ν™”(θ©±) ν—€λ”λŠ” 첫 화면에
442
+ else screens.push(head);
443
+ return screens;
444
  }
445
 
446
  function choicesHtml(step) {