ippon-slides 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tanahiro2010
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,136 @@
1
- # Temporary Holding Version
1
+ # ippon-slides
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **1 枚 1 メッセージ** のスライドを Markdown から作る CLI。
4
+
5
+ - 黒背景・大きな文字・切り替え時のフェードイン
6
+ - 出力は CSS / JS まで全部入った **HTML 1 ファイル**(会場がオフラインでも開ける)
7
+ - `--pdf` で 16:9 の **PDF** も出せる
8
+ - 依存パッケージなし
9
+
10
+ ## 使い方
11
+
12
+ ```sh
13
+ npx ippon-slides init # サンプルの slide.md を作る
14
+ npx ippon-slides build slide.md --html -w # 保存するたびに slide.html を作り直す
15
+ npx ippon-slides build slide.md --pdf # slide.pdf
16
+ ```
17
+
18
+ グローバルに入れると `ippon` コマンドとして使えます。
19
+
20
+ ```sh
21
+ npm i -g ippon-slides
22
+ ippon build slide.md --html --pdf -o dist/
23
+ ```
24
+
25
+ ### オプション
26
+
27
+ | オプション | |
28
+ |---|---|
29
+ | `--html` | HTML を出力する(`--pdf` がなければデフォルト) |
30
+ | `--pdf` | PDF を出力する(Chrome / Chromium / Edge が必要) |
31
+ | `-o, --output <path>` | 出力先。ディレクトリを指定するとその中に `<input>.html` / `.pdf` を作る。`--html --pdf` を両方指定したときは拡張子だけ差し替える |
32
+ | `-w, --watch` | Markdown と `--css` の変更を監視して再ビルドする |
33
+ | `--css <file>` | 追加の CSS(色やフォントの上書きなど) |
34
+ | `--browser-path <path>` | PDF に使うブラウザ。環境変数 `CHROME_PATH` でも指定できる |
35
+
36
+ `ippon slide.md` は `ippon build slide.md` の省略形です。
37
+
38
+ ## 書き方
39
+
40
+ ````md
41
+ ---
42
+ title: タイトル
43
+ author: 名前
44
+ ---
45
+
46
+ ::title::
47
+
48
+ # タイトル
49
+
50
+ サブタイトル
51
+
52
+ 名前 ・ 日付
53
+
54
+ ---
55
+
56
+ ::center::
57
+
58
+ 1 枚に
59
+
60
+ 1 メッセージ
61
+
62
+ ---
63
+
64
+ ::center size=2 bg=blue::
65
+
66
+ **大きく**
67
+
68
+ -# 小さい注釈
69
+
70
+ ---
71
+
72
+ ## 見出しと箇条書き
73
+
74
+ - **太字** はアクセントカラー
75
+ - ネストもできる
76
+
77
+ ::right::
78
+
79
+ ```ts
80
+ const app = new Hono()
81
+ !! app.get('/', (c) => c.text('Hello!'))
82
+ ```
83
+ ````
84
+
85
+ | 記法 | |
86
+ |---|---|
87
+ | `---` | スライドの区切り |
88
+ | `::center::` | 1 枚 1 メッセージ(中央寄せの大きな文字) |
89
+ | `::title::` | 表紙。最後の段落はページ下に寄る |
90
+ | `::wide::` | 左右の余白を詰める(スクリーンショット向け) |
91
+ | `::bg=blue::` | 背景色。`blue` / `orange` / `purple` / `red` / `green` / `black` / `white`、または `#rrggbb` |
92
+ | `::size=1.5::` | 文字サイズの倍率 |
93
+ | `::right::` | ここから右カラム。先頭の見出しはカラムの上に出る |
94
+ | `-# 注釈` | 小さい注釈 |
95
+ | ` ```hcl ` | コード。`hcl` / `console` / `sh` / `json` / `js` / `ts` をハイライト |
96
+ | `!! ` | コードの行頭に付けるとその行を強調 |
97
+
98
+ 指示は `::center bg=blue size=2::` のようにまとめて書けます。
99
+ `console` のコードは `$ ` で始まる行をコマンド、`Error` を含む行をエラーとして色分けします。
100
+
101
+ ## 発表中の操作
102
+
103
+ | キー | |
104
+ |---|---|
105
+ | `→` / `Space` / 画面右側クリック | 次へ |
106
+ | `←` / 画面左側クリック | 前へ |
107
+ | `Home` / `End` | 最初 / 最後 |
108
+ | `f` | フルスクリーン |
109
+ | `o` / `Esc` | スライド一覧 |
110
+
111
+ 表紙から進むと右下でタイマーが動き、表紙に戻るとリセットされます。
112
+ URL の `#12` で 12 枚目から開けます。
113
+
114
+ ## 見た目を変える
115
+
116
+ `--css` で CSS 変数を上書きできます。
117
+
118
+ ```css
119
+ :root {
120
+ --slide-accent: #7b42bc; /* 進捗バー */
121
+ --slide-accent-2: #ff6a00; /* 太字・強調 */
122
+ --slide-display-font: 'Hiragino Sans', sans-serif;
123
+ }
124
+ ```
125
+
126
+ ## ライブラリとして使う
127
+
128
+ ```js
129
+ import { render, toPdf } from 'ippon-slides'
130
+
131
+ const { html } = render(markdown, { baseDir: '.', outDir: 'dist' })
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
package/assets/deck.js ADDED
@@ -0,0 +1,211 @@
1
+ // スライドのナビゲーション。ippon が HTML に埋め込む。
2
+ // → / Space / PageDown / クリック右側 次へ
3
+ // ← / PageUp / クリック左側 前へ
4
+ // Home / End 最初 / 最後
5
+ // f フルスクリーン
6
+ // o / Esc 一覧
7
+ ;(function () {
8
+ var slides = Array.prototype.slice.call(document.querySelectorAll('#deck .slide'))
9
+ if (!slides.length) return
10
+
11
+ var idx = 0
12
+ var progress = document.getElementById('progress')
13
+ var counter = document.getElementById('counter')
14
+ var timerEl = document.getElementById('timer')
15
+
16
+ function clamp(n) {
17
+ return Math.max(0, Math.min(slides.length - 1, n))
18
+ }
19
+
20
+ // ---------- タイマー(表紙から進んだら開始、表紙に戻ったらリセット) ----------
21
+
22
+ var startedAt = null
23
+ var ticking = null
24
+ function fmt(ms) {
25
+ var s = Math.floor(ms / 1000)
26
+ var m = Math.floor(s / 60)
27
+ s = s % 60
28
+ return m + ':' + (s < 10 ? '0' : '') + s
29
+ }
30
+ function syncTimer() {
31
+ if (idx === 0) {
32
+ startedAt = null
33
+ clearInterval(ticking)
34
+ ticking = null
35
+ timerEl.textContent = ''
36
+ return
37
+ }
38
+ if (startedAt === null) startedAt = Date.now()
39
+ if (!ticking) {
40
+ ticking = setInterval(function () {
41
+ timerEl.textContent = fmt(Date.now() - startedAt)
42
+ }, 500)
43
+ }
44
+ timerEl.textContent = fmt(Date.now() - startedAt)
45
+ }
46
+
47
+ // ---------- 描画 ----------
48
+
49
+ function render() {
50
+ slides.forEach(function (s, i) {
51
+ s.classList.toggle('active', i === idx)
52
+ })
53
+ slides[idx].scrollTop = 0
54
+ counter.textContent = idx + 1 + ' / ' + slides.length
55
+ progress.style.width = ((idx + 1) / slides.length) * 100 + '%'
56
+ syncTimer()
57
+ }
58
+ function go(n) {
59
+ n = clamp(n)
60
+ if (n === idx) return
61
+ idx = n
62
+ render()
63
+ history.replaceState(null, '', '#' + (idx + 1))
64
+ }
65
+ window.addEventListener('hashchange', function () {
66
+ var n = parseInt(location.hash.slice(1), 10)
67
+ if (n) go(n - 1)
68
+ })
69
+
70
+ // ---------- 一覧 ----------
71
+
72
+ var overview = document.getElementById('overview')
73
+ var ovItems = null
74
+ var ovOpen = false
75
+ var THUMB = 240
76
+
77
+ function buildOverview() {
78
+ ovItems = slides.map(function (s, i) {
79
+ var item = document.createElement('div')
80
+ item.className = 'ov-item'
81
+ var frame = document.createElement('div')
82
+ frame.className = 'ov-frame'
83
+ var scale = document.createElement('div')
84
+ scale.className = 'ov-scale'
85
+ var clone = s.cloneNode(true)
86
+ clone.classList.remove('active')
87
+ scale.appendChild(clone)
88
+ frame.appendChild(scale)
89
+ var num = document.createElement('div')
90
+ num.className = 'ov-num'
91
+ num.textContent = i + 1
92
+ item.appendChild(frame)
93
+ item.appendChild(num)
94
+ item.addEventListener('click', function () {
95
+ idx = i
96
+ closeOverview()
97
+ render()
98
+ history.replaceState(null, '', '#' + (idx + 1))
99
+ })
100
+ overview.appendChild(item)
101
+ return { item: item, frame: frame, scale: scale }
102
+ })
103
+ }
104
+ function layoutOverview() {
105
+ var w = window.innerWidth
106
+ var h = window.innerHeight
107
+ var k = THUMB / w
108
+ ovItems.forEach(function (t) {
109
+ t.frame.style.width = THUMB + 'px'
110
+ t.frame.style.height = h * k + 'px'
111
+ t.scale.style.width = w + 'px'
112
+ t.scale.style.height = h + 'px'
113
+ t.scale.style.transform = 'scale(' + k + ')'
114
+ })
115
+ }
116
+ function openOverview() {
117
+ if (!ovItems) buildOverview()
118
+ layoutOverview()
119
+ ovItems.forEach(function (t, i) {
120
+ t.item.classList.toggle('current', i === idx)
121
+ })
122
+ ovOpen = true
123
+ document.body.classList.add('overview')
124
+ ovItems[idx].item.scrollIntoView({ block: 'center' })
125
+ }
126
+ function closeOverview() {
127
+ ovOpen = false
128
+ document.body.classList.remove('overview')
129
+ }
130
+ window.addEventListener('resize', function () {
131
+ if (ovOpen) layoutOverview()
132
+ })
133
+
134
+ // ---------- 入力 ----------
135
+
136
+ document.addEventListener('keydown', function (e) {
137
+ if (e.metaKey || e.ctrlKey || e.altKey) return
138
+ if (ovOpen) {
139
+ if (e.key === 'Escape' || e.key === 'o') closeOverview()
140
+ return
141
+ }
142
+ switch (e.key) {
143
+ case 'ArrowRight':
144
+ case 'PageDown':
145
+ case ' ':
146
+ e.preventDefault()
147
+ go(idx + 1)
148
+ break
149
+ case 'ArrowLeft':
150
+ case 'PageUp':
151
+ e.preventDefault()
152
+ go(idx - 1)
153
+ break
154
+ case 'Home':
155
+ go(0)
156
+ break
157
+ case 'End':
158
+ go(slides.length - 1)
159
+ break
160
+ case 'f':
161
+ if (document.fullscreenElement) document.exitFullscreen()
162
+ else document.documentElement.requestFullscreen()
163
+ break
164
+ case 'o':
165
+ openOverview()
166
+ break
167
+ case 'Escape':
168
+ if (!document.fullscreenElement) openOverview()
169
+ break
170
+ }
171
+ })
172
+
173
+ // 画面の右 3 割で次へ、左 3 割で前へ(リンクと文字選択は邪魔しない)
174
+ var deck = document.getElementById('deck')
175
+ deck.addEventListener('click', function (e) {
176
+ if (e.target.closest('a, button')) return
177
+ if (String(window.getSelection() || '')) return
178
+ var x = e.clientX / window.innerWidth
179
+ if (x > 0.7) go(idx + 1)
180
+ else if (x < 0.3) go(idx - 1)
181
+ })
182
+
183
+ // 横スワイプ
184
+ var tx = null
185
+ var ty = null
186
+ deck.addEventListener(
187
+ 'touchstart',
188
+ function (e) {
189
+ if (e.touches.length !== 1) return (tx = null)
190
+ tx = e.touches[0].clientX
191
+ ty = e.touches[0].clientY
192
+ },
193
+ { passive: true }
194
+ )
195
+ deck.addEventListener(
196
+ 'touchend',
197
+ function (e) {
198
+ if (tx === null) return
199
+ var t = e.changedTouches[0]
200
+ var dx = t.clientX - tx
201
+ var dy = t.clientY - ty
202
+ tx = null
203
+ if (Math.abs(dx) > 50 && Math.abs(dx) > Math.abs(dy) * 1.5) go(dx < 0 ? idx + 1 : idx - 1)
204
+ },
205
+ { passive: true }
206
+ )
207
+
208
+ var start = parseInt(location.hash.slice(1), 10)
209
+ if (start) idx = clamp(start - 1)
210
+ render()
211
+ })()