ippon-slides 0.1.1 → 0.1.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/README.md +9 -0
- package/assets/deck.js +170 -11
- package/assets/style.css +126 -2
- package/dist/cli.js +5 -0
- package/dist/markdown.js +9 -1
- package/dist/render.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,7 @@ const app = new Hono()
|
|
|
91
91
|
| `::bg=blue::` | 背景色。`blue` / `orange` / `purple` / `red` / `green` / `black` / `white`、または `#rrggbb` |
|
|
92
92
|
| `::size=1.5::` | 文字サイズの倍率 |
|
|
93
93
|
| `::right::` | ここから右カラム。先頭の見出しはカラムの上に出る |
|
|
94
|
+
| `::note::` | この行以降は発表者ノート。本番には出ず発表者ビュー(`p`)だけに出る |
|
|
94
95
|
| `-# 注釈` | 小さい注釈 |
|
|
95
96
|
| ` ```hcl ` | コード。`hcl` / `console` / `sh` / `json` / `js` / `ts` をハイライト |
|
|
96
97
|
| `!! ` | コードの行頭に付けるとその行を強調 |
|
|
@@ -107,10 +108,18 @@ const app = new Hono()
|
|
|
107
108
|
| `Home` / `End` | 最初 / 最後 |
|
|
108
109
|
| `f` | フルスクリーン |
|
|
109
110
|
| `o` / `Esc` | スライド一覧 |
|
|
111
|
+
| `p` | 発表者ビュー(別ウィンドウ) |
|
|
110
112
|
|
|
111
113
|
表紙から進むと右下でタイマーが動き、表紙に戻るとリセットされます。
|
|
112
114
|
URL の `#12` で 12 枚目から開けます。
|
|
113
115
|
|
|
116
|
+
### 発表者ビュー
|
|
117
|
+
|
|
118
|
+
`p` を押すと別ウィンドウで発表者ビューが開きます。現在のスライド・次のスライド・
|
|
119
|
+
`::note::` で書いたノート・経過タイマー・時計が手元に出て、本番側(プロジェクタに映す画面)は
|
|
120
|
+
通常表示のままです。どちらのウィンドウで `→` / `←` を押しても両画面が同期します。
|
|
121
|
+
発表者ビューは `p` / `Esc` で閉じます。
|
|
122
|
+
|
|
114
123
|
## 見た目を変える
|
|
115
124
|
|
|
116
125
|
`--css` で CSS 変数を上書きできます。
|
package/assets/deck.js
CHANGED
|
@@ -5,11 +5,28 @@
|
|
|
5
5
|
// Home / End 最初 / 最後
|
|
6
6
|
// f フルスクリーン
|
|
7
7
|
// o / Esc 一覧
|
|
8
|
+
// p 発表者ビュー(別ウィンドウ)
|
|
8
9
|
(() => {
|
|
9
10
|
const slides = Array.from(document.querySelectorAll('#deck .slide'));
|
|
10
11
|
if (!slides.length)
|
|
11
12
|
return;
|
|
12
13
|
const $ = (id) => document.getElementById(id);
|
|
14
|
+
const div = (className) => {
|
|
15
|
+
const el = document.createElement('div');
|
|
16
|
+
el.className = className;
|
|
17
|
+
return el;
|
|
18
|
+
};
|
|
19
|
+
function fmt(ms) {
|
|
20
|
+
const total = Math.floor(ms / 1000);
|
|
21
|
+
const m = Math.floor(total / 60);
|
|
22
|
+
const s = total % 60;
|
|
23
|
+
return m + ':' + (s < 10 ? '0' : '') + s;
|
|
24
|
+
}
|
|
25
|
+
// 別ウィンドウで開いたときは発表者ビューとして動く(本番のナビは動かさない)
|
|
26
|
+
if (window.name === 'ippon-presenter') {
|
|
27
|
+
presenterMode();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
13
30
|
let idx = 0;
|
|
14
31
|
const progress = $('progress');
|
|
15
32
|
const counter = $('counter');
|
|
@@ -18,12 +35,6 @@
|
|
|
18
35
|
// ---------- タイマー(表紙から進んだら開始、表紙に戻ったらリセット) ----------
|
|
19
36
|
let startedAt = null;
|
|
20
37
|
let ticking;
|
|
21
|
-
function fmt(ms) {
|
|
22
|
-
const total = Math.floor(ms / 1000);
|
|
23
|
-
const m = Math.floor(total / 60);
|
|
24
|
-
const s = total % 60;
|
|
25
|
-
return m + ':' + (s < 10 ? '0' : '') + s;
|
|
26
|
-
}
|
|
27
38
|
function syncTimer() {
|
|
28
39
|
if (idx === 0) {
|
|
29
40
|
startedAt = null;
|
|
@@ -45,6 +56,7 @@
|
|
|
45
56
|
counter.textContent = idx + 1 + ' / ' + slides.length;
|
|
46
57
|
progress.style.width = ((idx + 1) / slides.length) * 100 + '%';
|
|
47
58
|
syncTimer();
|
|
59
|
+
broadcast();
|
|
48
60
|
}
|
|
49
61
|
function go(n) {
|
|
50
62
|
n = clamp(n);
|
|
@@ -59,15 +71,32 @@
|
|
|
59
71
|
if (n)
|
|
60
72
|
go(n - 1);
|
|
61
73
|
});
|
|
74
|
+
// ---------- 発表者ビュー(別ウィンドウ)との同期 ----------
|
|
75
|
+
let presenterWin = null;
|
|
76
|
+
function openPresenter() {
|
|
77
|
+
if (presenterWin && !presenterWin.closed) {
|
|
78
|
+
presenterWin.focus();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
presenterWin = window.open(location.href, 'ippon-presenter', 'width=1024,height=700');
|
|
82
|
+
}
|
|
83
|
+
function broadcast() {
|
|
84
|
+
if (presenterWin && !presenterWin.closed)
|
|
85
|
+
presenterWin.postMessage({ ippon: 'state', idx, total: slides.length, startedAt }, '*');
|
|
86
|
+
}
|
|
87
|
+
window.addEventListener('message', (e) => {
|
|
88
|
+
const m = e.data;
|
|
89
|
+
if (!m || typeof m !== 'object')
|
|
90
|
+
return;
|
|
91
|
+
if (m.ippon === 'hello')
|
|
92
|
+
broadcast();
|
|
93
|
+
else if (m.ippon === 'go' && typeof m.n === 'number')
|
|
94
|
+
go(m.n);
|
|
95
|
+
});
|
|
62
96
|
const overview = $('overview');
|
|
63
97
|
let ovItems = null;
|
|
64
98
|
let ovOpen = false;
|
|
65
99
|
const THUMB = 240;
|
|
66
|
-
const div = (className) => {
|
|
67
|
-
const el = document.createElement('div');
|
|
68
|
-
el.className = className;
|
|
69
|
-
return el;
|
|
70
|
-
};
|
|
71
100
|
function buildOverview() {
|
|
72
101
|
return slides.map((s, i) => {
|
|
73
102
|
const item = div('ov-item');
|
|
@@ -155,6 +184,9 @@
|
|
|
155
184
|
case 'o':
|
|
156
185
|
openOverview();
|
|
157
186
|
break;
|
|
187
|
+
case 'p':
|
|
188
|
+
openPresenter();
|
|
189
|
+
break;
|
|
158
190
|
case 'Escape':
|
|
159
191
|
if (!document.fullscreenElement)
|
|
160
192
|
openOverview();
|
|
@@ -194,4 +226,131 @@
|
|
|
194
226
|
if (start)
|
|
195
227
|
idx = clamp(start - 1);
|
|
196
228
|
render();
|
|
229
|
+
// ---------- 発表者ビュー本体(別ウィンドウで動く) ----------
|
|
230
|
+
function presenterMode() {
|
|
231
|
+
document.body.classList.add('presenter');
|
|
232
|
+
const root = $('presenter');
|
|
233
|
+
const left = div('pv-left');
|
|
234
|
+
const right = div('pv-right');
|
|
235
|
+
root.appendChild(left);
|
|
236
|
+
root.appendChild(right);
|
|
237
|
+
const makeSlot = (labelText) => {
|
|
238
|
+
const slot = div('pv-slot');
|
|
239
|
+
const label = div('pv-label');
|
|
240
|
+
label.textContent = labelText;
|
|
241
|
+
const frame = div('pv-frame');
|
|
242
|
+
const scale = div('pv-scale');
|
|
243
|
+
frame.appendChild(scale);
|
|
244
|
+
slot.appendChild(label);
|
|
245
|
+
slot.appendChild(frame);
|
|
246
|
+
left.appendChild(slot);
|
|
247
|
+
return { label, frame, scale };
|
|
248
|
+
};
|
|
249
|
+
const cur = makeSlot('現在');
|
|
250
|
+
const nxt = makeSlot('次へ');
|
|
251
|
+
const clock = div('pv-clock');
|
|
252
|
+
const elapsedEl = div('pv-elapsed');
|
|
253
|
+
const wallEl = div('pv-wall');
|
|
254
|
+
clock.appendChild(elapsedEl);
|
|
255
|
+
clock.appendChild(wallEl);
|
|
256
|
+
const controls = div('pv-controls');
|
|
257
|
+
const prevBtn = document.createElement('button');
|
|
258
|
+
prevBtn.textContent = '◀';
|
|
259
|
+
const nextBtn = document.createElement('button');
|
|
260
|
+
nextBtn.textContent = '▶';
|
|
261
|
+
controls.appendChild(prevBtn);
|
|
262
|
+
controls.appendChild(nextBtn);
|
|
263
|
+
const notesEl = div('pv-notes');
|
|
264
|
+
right.appendChild(clock);
|
|
265
|
+
right.appendChild(controls);
|
|
266
|
+
right.appendChild(notesEl);
|
|
267
|
+
let curIdx = 0;
|
|
268
|
+
let total = slides.length;
|
|
269
|
+
let startedAt = null;
|
|
270
|
+
function fitPreview(frame, scale) {
|
|
271
|
+
const w = window.innerWidth;
|
|
272
|
+
const h = window.innerHeight;
|
|
273
|
+
const fw = frame.clientWidth;
|
|
274
|
+
const k = fw / w;
|
|
275
|
+
frame.style.height = fw * (h / w) + 'px';
|
|
276
|
+
scale.style.width = w + 'px';
|
|
277
|
+
scale.style.height = h + 'px';
|
|
278
|
+
scale.style.transform = 'scale(' + k + ')';
|
|
279
|
+
}
|
|
280
|
+
function setPreview(frame, scale, i) {
|
|
281
|
+
scale.textContent = '';
|
|
282
|
+
const s = slides[i];
|
|
283
|
+
if (s)
|
|
284
|
+
scale.appendChild(s.cloneNode(true));
|
|
285
|
+
fitPreview(frame, scale);
|
|
286
|
+
}
|
|
287
|
+
function draw() {
|
|
288
|
+
cur.label.textContent = '現在 ' + (curIdx + 1) + ' / ' + total;
|
|
289
|
+
setPreview(cur.frame, cur.scale, curIdx);
|
|
290
|
+
const hasNext = curIdx + 1 < total;
|
|
291
|
+
nxt.label.textContent = hasNext ? '次へ ' + (curIdx + 2) + ' / ' + total : 'おわり';
|
|
292
|
+
setPreview(nxt.frame, nxt.scale, hasNext ? curIdx + 1 : -1);
|
|
293
|
+
const note = slides[curIdx]?.querySelector('.notes');
|
|
294
|
+
notesEl.innerHTML = note ? note.innerHTML : '<p class="pv-empty">(ノートなし)</p>';
|
|
295
|
+
}
|
|
296
|
+
function tick() {
|
|
297
|
+
elapsedEl.textContent = startedAt == null ? '0:00' : fmt(Date.now() - startedAt);
|
|
298
|
+
wallEl.textContent = new Date().toLocaleTimeString();
|
|
299
|
+
}
|
|
300
|
+
const send = (n) => {
|
|
301
|
+
if (window.opener)
|
|
302
|
+
window.opener.postMessage({ ippon: 'go', n }, '*');
|
|
303
|
+
};
|
|
304
|
+
prevBtn.addEventListener('click', () => send(curIdx - 1));
|
|
305
|
+
nextBtn.addEventListener('click', () => send(curIdx + 1));
|
|
306
|
+
window.addEventListener('message', (e) => {
|
|
307
|
+
const m = e.data;
|
|
308
|
+
if (!m || m.ippon !== 'state')
|
|
309
|
+
return;
|
|
310
|
+
if (typeof m.idx === 'number')
|
|
311
|
+
curIdx = m.idx;
|
|
312
|
+
if (typeof m.total === 'number')
|
|
313
|
+
total = m.total;
|
|
314
|
+
startedAt = m.startedAt ?? null;
|
|
315
|
+
draw();
|
|
316
|
+
tick();
|
|
317
|
+
});
|
|
318
|
+
document.addEventListener('keydown', (e) => {
|
|
319
|
+
if (e.metaKey || e.ctrlKey || e.altKey)
|
|
320
|
+
return;
|
|
321
|
+
switch (e.key) {
|
|
322
|
+
case 'ArrowRight':
|
|
323
|
+
case 'PageDown':
|
|
324
|
+
case ' ':
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
send(curIdx + 1);
|
|
327
|
+
break;
|
|
328
|
+
case 'ArrowLeft':
|
|
329
|
+
case 'PageUp':
|
|
330
|
+
e.preventDefault();
|
|
331
|
+
send(curIdx - 1);
|
|
332
|
+
break;
|
|
333
|
+
case 'Home':
|
|
334
|
+
send(0);
|
|
335
|
+
break;
|
|
336
|
+
case 'End':
|
|
337
|
+
send(total - 1);
|
|
338
|
+
break;
|
|
339
|
+
case 'p':
|
|
340
|
+
case 'Escape':
|
|
341
|
+
window.close();
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
window.addEventListener('resize', () => {
|
|
346
|
+
fitPreview(cur.frame, cur.scale);
|
|
347
|
+
fitPreview(nxt.frame, nxt.scale);
|
|
348
|
+
});
|
|
349
|
+
window.setInterval(tick, 500);
|
|
350
|
+
// 本番ウィンドウに現在の状態を要求する
|
|
351
|
+
if (window.opener)
|
|
352
|
+
window.opener.postMessage({ ippon: 'hello' }, '*');
|
|
353
|
+
draw();
|
|
354
|
+
tick();
|
|
355
|
+
}
|
|
197
356
|
})();
|
package/assets/style.css
CHANGED
|
@@ -424,7 +424,8 @@ body.overview #deck,
|
|
|
424
424
|
body.overview #progress,
|
|
425
425
|
body.overview #counter,
|
|
426
426
|
body.overview #timer,
|
|
427
|
-
body.overview #hint
|
|
427
|
+
body.overview #hint,
|
|
428
|
+
body.overview #presenter {
|
|
428
429
|
display: none;
|
|
429
430
|
}
|
|
430
431
|
.ov-item {
|
|
@@ -466,6 +467,128 @@ body.overview #hint {
|
|
|
466
467
|
font-weight: 600;
|
|
467
468
|
}
|
|
468
469
|
|
|
470
|
+
/* ---------- presenter(p / 別ウィンドウ) ---------- */
|
|
471
|
+
|
|
472
|
+
/* 発表者ノートは本番・一覧・PDF には出さない(発表者ビューが innerHTML を別途読む) */
|
|
473
|
+
.notes {
|
|
474
|
+
display: none;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
#presenter {
|
|
478
|
+
position: fixed;
|
|
479
|
+
inset: 0;
|
|
480
|
+
z-index: 30;
|
|
481
|
+
display: none;
|
|
482
|
+
box-sizing: border-box;
|
|
483
|
+
padding: 24px;
|
|
484
|
+
gap: 24px;
|
|
485
|
+
background: #1c1c1f;
|
|
486
|
+
color: #fff;
|
|
487
|
+
font-family: system-ui, -apple-system, 'Hiragino Sans', sans-serif;
|
|
488
|
+
}
|
|
489
|
+
body.presenter #presenter {
|
|
490
|
+
display: grid;
|
|
491
|
+
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
|
|
492
|
+
}
|
|
493
|
+
body.presenter #deck,
|
|
494
|
+
body.presenter #progress,
|
|
495
|
+
body.presenter #counter,
|
|
496
|
+
body.presenter #timer,
|
|
497
|
+
body.presenter #hint,
|
|
498
|
+
body.presenter #overview {
|
|
499
|
+
display: none;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.pv-left {
|
|
503
|
+
display: flex;
|
|
504
|
+
flex-direction: column;
|
|
505
|
+
gap: 18px;
|
|
506
|
+
min-width: 0;
|
|
507
|
+
}
|
|
508
|
+
.pv-slot:last-child {
|
|
509
|
+
opacity: 0.85;
|
|
510
|
+
}
|
|
511
|
+
.pv-slot:last-child .pv-frame {
|
|
512
|
+
width: 62%;
|
|
513
|
+
}
|
|
514
|
+
.pv-label {
|
|
515
|
+
font-size: 0.85rem;
|
|
516
|
+
color: #9a9aa0;
|
|
517
|
+
margin-bottom: 6px;
|
|
518
|
+
font-variant-numeric: tabular-nums;
|
|
519
|
+
}
|
|
520
|
+
.pv-frame {
|
|
521
|
+
position: relative;
|
|
522
|
+
overflow: hidden;
|
|
523
|
+
border-radius: 10px;
|
|
524
|
+
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
525
|
+
background: var(--slide-bg);
|
|
526
|
+
box-shadow: 0 2px 14px rgba(0, 0, 0, 0.4);
|
|
527
|
+
}
|
|
528
|
+
.pv-scale {
|
|
529
|
+
position: relative;
|
|
530
|
+
transform-origin: 0 0;
|
|
531
|
+
}
|
|
532
|
+
#presenter .slide {
|
|
533
|
+
display: flex;
|
|
534
|
+
animation: none;
|
|
535
|
+
overflow: hidden;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.pv-right {
|
|
539
|
+
display: flex;
|
|
540
|
+
flex-direction: column;
|
|
541
|
+
gap: 18px;
|
|
542
|
+
min-width: 0;
|
|
543
|
+
}
|
|
544
|
+
.pv-clock {
|
|
545
|
+
display: flex;
|
|
546
|
+
align-items: baseline;
|
|
547
|
+
gap: 16px;
|
|
548
|
+
}
|
|
549
|
+
.pv-elapsed {
|
|
550
|
+
font-size: 2.8rem;
|
|
551
|
+
font-weight: 700;
|
|
552
|
+
font-variant-numeric: tabular-nums;
|
|
553
|
+
line-height: 1;
|
|
554
|
+
}
|
|
555
|
+
.pv-wall {
|
|
556
|
+
font-size: 1rem;
|
|
557
|
+
color: #9a9aa0;
|
|
558
|
+
font-variant-numeric: tabular-nums;
|
|
559
|
+
}
|
|
560
|
+
.pv-controls {
|
|
561
|
+
display: flex;
|
|
562
|
+
gap: 10px;
|
|
563
|
+
}
|
|
564
|
+
.pv-controls button {
|
|
565
|
+
flex: 1;
|
|
566
|
+
padding: 10px 0;
|
|
567
|
+
font-size: 1.1rem;
|
|
568
|
+
color: #fff;
|
|
569
|
+
background: #2e2e33;
|
|
570
|
+
border: 1px solid rgba(255, 255, 255, 0.16);
|
|
571
|
+
border-radius: 8px;
|
|
572
|
+
cursor: pointer;
|
|
573
|
+
}
|
|
574
|
+
.pv-controls button:hover {
|
|
575
|
+
background: var(--slide-accent);
|
|
576
|
+
border-color: var(--slide-accent);
|
|
577
|
+
}
|
|
578
|
+
.pv-notes {
|
|
579
|
+
flex: 1;
|
|
580
|
+
overflow-y: auto;
|
|
581
|
+
padding: 16px 18px;
|
|
582
|
+
background: #242428;
|
|
583
|
+
border-radius: 10px;
|
|
584
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
585
|
+
font-size: 1.05rem;
|
|
586
|
+
line-height: 1.7;
|
|
587
|
+
}
|
|
588
|
+
.pv-notes .pv-empty {
|
|
589
|
+
color: #76767c;
|
|
590
|
+
}
|
|
591
|
+
|
|
469
592
|
/* ---------- PDF(ブラウザの印刷 → PDF に保存) ---------- */
|
|
470
593
|
|
|
471
594
|
@media print {
|
|
@@ -511,7 +634,8 @@ body.overview #hint {
|
|
|
511
634
|
#counter,
|
|
512
635
|
#timer,
|
|
513
636
|
#hint,
|
|
514
|
-
#overview
|
|
637
|
+
#overview,
|
|
638
|
+
#presenter {
|
|
515
639
|
display: none !important;
|
|
516
640
|
}
|
|
517
641
|
}
|
package/dist/cli.js
CHANGED
package/dist/markdown.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// --- スライドの区切り
|
|
4
4
|
// ::center:: 1 行目に置くとスライドの指示(center / title / wide / bg=xxx / size=1.5)
|
|
5
5
|
// ::right:: ここから右カラム
|
|
6
|
+
// ::note:: 発表者ノート(この行以降は本番に出ず発表者ビューだけに出る)
|
|
6
7
|
// -# 注釈 小さい注釈
|
|
7
8
|
// ```hcl コード(hcl / console / sh / json / js / ts)
|
|
8
9
|
// !! 行 コードの行を強調(行頭の "!! " は消える)
|
|
@@ -165,6 +166,13 @@ export function slide(src) {
|
|
|
165
166
|
const classes = ['slide'];
|
|
166
167
|
let style = '';
|
|
167
168
|
let body = src.replace(/^\s+/, '');
|
|
169
|
+
// 発表者ノート(::note:: 以降。本番スライドには出さない)
|
|
170
|
+
let notes = '';
|
|
171
|
+
const noteParts = body.split(/^::note::[ \t]*$/m);
|
|
172
|
+
if (noteParts.length > 1) {
|
|
173
|
+
body = noteParts[0];
|
|
174
|
+
notes = blocks(noteParts.slice(1).join('\n').trim());
|
|
175
|
+
}
|
|
168
176
|
const d = body.match(/^::([^:]+)::[ \t]*(\n|$)/);
|
|
169
177
|
const tokens = d ? d[1].trim().split(/\s+/) : [];
|
|
170
178
|
if (d && tokens.every((t) => DIRECTIVES.test(t))) {
|
|
@@ -198,7 +206,7 @@ export function slide(src) {
|
|
|
198
206
|
else {
|
|
199
207
|
html = blocks(body);
|
|
200
208
|
}
|
|
201
|
-
return `<section class="${classes.join(' ')}"${style ? ` style="${style}"` : ''}>${html}</section>`;
|
|
209
|
+
return `<section class="${classes.join(' ')}"${style ? ` style="${style}"` : ''}>${html}${notes ? `<aside class="notes">${notes}</aside>` : ''}</section>`;
|
|
202
210
|
}
|
|
203
211
|
export function frontmatter(md) {
|
|
204
212
|
const m = md.replace(/^\uFEFF/, '').match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
package/dist/render.js
CHANGED
|
@@ -39,10 +39,11 @@ ${meta.description ? `<meta name="description" content="${esc(meta.description)}
|
|
|
39
39
|
${body}
|
|
40
40
|
</div>
|
|
41
41
|
<div id="overview"></div>
|
|
42
|
+
<div id="presenter"></div>
|
|
42
43
|
<div id="progress"></div>
|
|
43
44
|
<div id="timer"></div>
|
|
44
45
|
<div id="counter"></div>
|
|
45
|
-
<div id="hint">← → 移動 ・ f フルスクリーン ・ o
|
|
46
|
+
<div id="hint">← → 移動 ・ f フルスクリーン ・ o 一覧 ・ p 発表者</div>
|
|
46
47
|
<script>${script}</script>
|
|
47
48
|
</body>
|
|
48
49
|
</html>
|