-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
278 lines (261 loc) · 15.5 KB
/
Copy pathindex.html
File metadata and controls
278 lines (261 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="エンジニア1年生向け。git とターミナルの基本コマンドを、1ページ1コマンドの小さなステップでやさしく学べる学習サイトです。">
<title>TermLearn — git & ターミナル 学習チュートリアル</title>
<link rel="stylesheet" href="styles.css?v=20260626l">
<!-- ファビコン一式(SVG優先→PNG→ICOフォールバック→Appleタッチ)。ブランドのロゴマークと同デザイン。 -->
<link rel="icon" type="image/svg+xml" href="favicon.svg?v=20260723a">
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png?v=20260723a">
<link rel="alternate icon" href="favicon.ico?v=20260723a">
<link rel="apple-touch-icon" href="apple-touch-icon.png?v=20260723a">
<meta name="theme-color" content="#C23B7C">
</head>
<body>
<!-- ===========================================================================
背景の装飾レイヤー(雲+キラキラ)。fixed・低不透明度・装飾なので aria-hidden。
コンテンツの可読性は死守(カードは白=AA)。雲はメインカードの背面(z-index:0)に置き、
不透明度低めでテキストに被らないようにする。reduced-motion でアニメ停止。
SVG は icons.js の Icon('cloud'/'spark'/'star4') を JS で流し込む(data-icon)。
=========================================================================== -->
<div class="bg-deco" aria-hidden="true">
<span class="bg-deco__cloud bg-deco__cloud--1" data-icon="cloud"></span>
<span class="bg-deco__cloud bg-deco__cloud--2" data-icon="cloud"></span>
<span class="bg-deco__cloud bg-deco__cloud--3" data-icon="cloud"></span>
<span class="bg-deco__spark bg-deco__spark--1" data-icon="spark"></span>
<span class="bg-deco__spark bg-deco__spark--2" data-icon="star4"></span>
<span class="bg-deco__spark bg-deco__spark--3" data-icon="spark"></span>
<span class="bg-deco__spark bg-deco__spark--4" data-icon="star4"></span>
<span class="bg-deco__spark bg-deco__spark--5" data-icon="spark"></span>
</div>
<!-- ===========================================================================
アプリの骨格 = 左サイドバー(フルハイト・左端フラッシュ)+ 右メイン の2カラム。
参考画像のダッシュボード構成。app-shell は画面の高さいっぱい(min-height:100vh)を占め、
サイドバーは左端にぴったり付く白いフルハイトのレール(浮いた角丸カードにしない)。
右側に細い境界+ごく淡い影。~960px ではサイドバーを上部のコンパクトバーに畳む(CSS のみ)。
=========================================================================== -->
<div class="app-shell">
<!-- ===========================================================================
レッスンページ専用のハンバーガー(YouTube風)。
- 既定は非表示。body.is-lesson のときだけ左上に固定表示(CSS)。
- ☰ クリックで左からグローバルメニューがドロワー(暗幕付き)でスライド出現。
- 再クリック/外側クリック/Esc で閉じる。aria-expanded・フォーカストラップ対応(bootstrap JS)。
- ドロワー本体はサイドバーと同じ「ホーム/復習リスト」を持つ(グローバルメニュー)。
=========================================================================== -->
<div class="lesson-bar">
<button id="navToggle" class="nav-toggle" type="button"
aria-label="メニューを開く" aria-expanded="false" aria-controls="navDrawer" aria-haspopup="menu">
<span class="nav-toggle__icon" aria-hidden="true" data-icon="menu"></span>
</button>
<a class="lesson-bar__brand" href="#/" aria-label="TermLearn ホームへ">TermLearn</a>
</div>
<!-- 暗幕(ドロワーの外側)。クリックで閉じる。 -->
<div id="navScrim" class="nav-scrim" hidden></div>
<!-- 左からスライドするドロワー(グローバルメニュー)。aria-hidden をJSでトグル。 -->
<div id="navDrawer" class="nav-drawer" role="dialog" aria-modal="true" aria-label="メインメニュー" aria-hidden="true">
<div class="nav-drawer__head">
<a class="logo nav-drawer__logo" href="#/" aria-label="TermLearn ホームへ">
<span class="logo__mark" aria-hidden="true"></span>
<span class="logo__name">TermLearn</span>
</a>
<button id="navClose" class="nav-drawer__close" type="button" aria-label="メニューを閉じる">
<span aria-hidden="true" data-icon="close"></span>
</button>
</div>
<nav class="sidenav nav-drawer__nav" aria-label="メインメニュー">
<a id="navHomeDrawer" class="sidenav__item" href="#/">
<span class="sidenav__icon" aria-hidden="true" data-icon="home"></span>
<span class="sidenav__label">ホーム</span>
</a>
<a id="navReviewDrawer" class="sidenav__item" href="#/review">
<span class="sidenav__icon" aria-hidden="true" data-icon="list"></span>
<span class="sidenav__label">復習リスト</span>
<span id="reviewBadgeDrawer" class="sidenav__count" style="display:none;"><span id="reviewCountDrawer">0</span></span>
</a>
</nav>
</div>
<!-- ===== 左サイドバー(フルハイト・縦メニュー) =====
グローバルメニュー=ホーム/復習リストの2項目だけ(ロードマップ/ブックマーク/設定は削除)。
このサイドバーはホーム/章一覧/復習リストで常時表示。レッスンページ(#/page/...)では
CSS(body.is-lesson)で隠し、左上のハンバーガー☰からドロワーで開く(YouTube風)。 -->
<aside class="sidebar" aria-label="サイドバー" id="sidebar">
<div class="sidebar__inner">
<a class="sidebar__logo logo" href="#/" aria-label="TermLearn ホームへ">
<span class="logo__mark" aria-hidden="true"></span>
<span class="logo__name">TermLearn</span>
</a>
<nav class="sidenav" aria-label="メインメニュー">
<a id="navHome" class="sidenav__item" href="#/">
<span class="sidenav__icon" aria-hidden="true" data-icon="home"></span>
<span class="sidenav__label">ホーム</span>
</a>
<a id="navReview" class="sidenav__item" href="#/review">
<span class="sidenav__icon" aria-hidden="true" data-icon="list"></span>
<span class="sidenav__label">復習リスト</span>
<span id="reviewBadge" class="sidenav__count" style="display:none;"><span id="reviewCount">0</span></span>
</a>
</nav>
<!-- 最下部:サイドバーに収まりきらない“巨大積乱雲(cumulonimbus)の上側の一部”だけが覗く装飾。
残りはサイドバー下端の overflow:hidden で隠れ、雲の大きさを想像させる。PC のみ表示。
やわらかいパステル・装飾なので aria-hidden、reduced-motion でアニメ停止。 -->
<div class="sidebar__cumulo" aria-hidden="true">
<span class="sidebar__cumulo-spark" data-icon="spark"></span>
<span class="sidebar__cumulo-img sidebar__cumulo-img--back" data-icon="cumulonimbus"></span>
<span class="sidebar__cumulo-img sidebar__cumulo-img--front" data-icon="cumulonimbus"></span>
</div>
</div>
</aside>
<!-- ===== メイン(ルーターがここに各画面を流し込む) ===== -->
<main id="app" class="page-wrap" aria-live="polite">
<noscript>
<p style="padding:24px;">このサイトは JavaScript で動作します。お使いのブラウザで JavaScript を有効にしてください。</p>
</noscript>
</main>
</div><!-- /.app-shell -->
<!--
スクリプトは「依存関係の順」で通常の <script> として読み込む。
ES Modules(import/export)は使わない:
→ file:// で開いたときに CORS で読めなくなるため。
各ファイルは window に登録して連携する。
-->
<script src="js/glossary.js?v=20260626l"></script>
<script src="js/store.js?v=20260626l"></script>
<script src="js/icons.js?v=20260626l"></script>
<script src="js/render.js?v=20260626l"></script>
<!-- コンテンツ(教材を増やすときはここに1行追加するだけ) -->
<script>window.COURSES = window.COURSES || [];</script>
<script src="content/terminal.js?v=20260626l"></script>
<script src="content/git.js?v=20260626l"></script>
<script src="content/coming-soon.js?v=20260626l"></script>
<!-- ルーター(最後。COURSES が出そろってから起動) -->
<script src="js/router.js?v=20260626l"></script>
<!-- サイドバー/ドロワー/フッター/背景装飾の SVG・nav 現在地・レッスンモード・ハンバーガーは、
icons.js / store.js が出そろってから整える。
復習件数は router.js が route 毎に updateReviewBadge() でサイドバー+ドロワー両方を最新化する。 -->
<script>
(function () {
function fillIcons() {
if (!window.Icon) return;
// data-icon="名前" の空スロットに SVG を流し込む(装飾=aria-hidden は親に付与済み)。
var slots = document.querySelectorAll('[data-icon]');
for (var i = 0; i < slots.length; i++) {
var el = slots[i];
if (el.getAttribute('data-icon-done')) continue;
el.innerHTML = window.Icon(el.getAttribute('data-icon'));
el.setAttribute('data-icon-done', '1');
}
}
function fillLogoMark() {
// ロゴマーク(サイドバー+ドロワーの2か所)。terminal ロゴを差し込む(隣に "TermLearn" 表記あり)。
var marks = document.querySelectorAll('.logo__mark');
for (var i = 0; i < marks.length; i++) {
var mark = marks[i];
if (window.Icon && !mark.getAttribute('data-icon-done')) {
mark.innerHTML = window.Icon('terminal');
mark.setAttribute('data-icon-done', '1');
}
}
}
// 現在のルートがレッスンページ(#/page/...)か。
function isLessonRoute() {
var h = (window.location.hash || '').replace(/^#/, '');
return h.replace(/^\//, '').split('/')[0] === 'page';
}
// nav の現在地ハイライト(ホーム系か 復習リストか)。サイドバー+ドロワーの両方に反映。
function updateNavCurrent() {
var h = (window.location.hash || '').replace(/^#/, '');
var isReview = (h.replace(/^\//, '').split('/')[0] === 'review');
[['navHome', 'navReview'], ['navHomeDrawer', 'navReviewDrawer']].forEach(function (ids) {
var home = document.getElementById(ids[0]);
var review = document.getElementById(ids[1]);
if (home) { home.classList.toggle('is-current', !isReview); home.removeAttribute('aria-current'); if (!isReview) home.setAttribute('aria-current', 'page'); }
if (review) { review.classList.toggle('is-current', isReview); review.removeAttribute('aria-current'); if (isReview) review.setAttribute('aria-current', 'page'); }
});
}
/* ===== レッスンページのハンバーガー・ドロワー(YouTube風 / a11y) =====
開く: ☰ クリック。閉じる: 再クリック / 暗幕クリック / × / Esc。
aria-expanded(ボタン)・aria-hidden(ドロワー)を同期。フォーカストラップ+復帰。 */
var toggleBtn = document.getElementById('navToggle');
var drawer = document.getElementById('navDrawer');
var scrim = document.getElementById('navScrim');
var closeBtn = document.getElementById('navClose');
var lastFocus = null;
var drawerOpen = false;
function focusables() {
if (!drawer) return [];
return Array.prototype.slice.call(
drawer.querySelectorAll('a[href], button:not([disabled])')
).filter(function (el) { return el.offsetParent !== null; });
}
function openDrawer() {
if (!drawer || drawerOpen) return;
drawerOpen = true;
lastFocus = document.activeElement;
if (scrim) scrim.hidden = false;
// aria-hidden=false が開の“正”(CSS はこれでスライドイン)。クラスは scrim フェード用に補助的に付ける。
drawer.setAttribute('aria-hidden', 'false');
drawer.classList.add('is-open');
requestAnimationFrame(function () {
document.body.classList.add('drawer-open');
if (scrim) scrim.classList.add('is-open');
});
if (toggleBtn) { toggleBtn.setAttribute('aria-expanded', 'true'); toggleBtn.setAttribute('aria-label', 'メニューを閉じる'); }
var f = focusables();
if (f.length) f[0].focus();
}
// 閉じる。DOM の後始末は drawerOpen の値に依らず必ず実行(状態 desync を残さない)。
function closeDrawer(restore) {
if (!drawer) return;
var wasOpen = drawerOpen;
drawerOpen = false;
document.body.classList.remove('drawer-open');
drawer.classList.remove('is-open');
if (scrim) scrim.classList.remove('is-open');
drawer.setAttribute('aria-hidden', 'true');
if (toggleBtn) { toggleBtn.setAttribute('aria-expanded', 'false'); toggleBtn.setAttribute('aria-label', 'メニューを開く'); }
// transition 後に暗幕を DOM から外す(クリックを通さない)
window.setTimeout(function () { if (!drawerOpen && scrim) scrim.hidden = true; }, 260);
if (wasOpen && restore !== false && lastFocus && lastFocus.focus) lastFocus.focus();
}
function toggleDrawer() { drawerOpen ? closeDrawer() : openDrawer(); }
if (toggleBtn) toggleBtn.addEventListener('click', toggleDrawer);
if (closeBtn) closeBtn.addEventListener('click', function () { closeDrawer(); });
if (scrim) scrim.addEventListener('click', function () { closeDrawer(); });
// ドロワー内のリンクで遷移したら閉じる(フォーカスは遷移先へ自然に移るので復帰しない)
if (drawer) drawer.addEventListener('click', function (e) {
var a = e.target.closest && e.target.closest('a[href]');
if (a) closeDrawer(false);
});
// キーボード: Esc で閉じる / Tab でフォーカストラップ
document.addEventListener('keydown', function (e) {
if (!drawerOpen) return;
if (e.key === 'Escape') { e.preventDefault(); closeDrawer(); return; }
if (e.key === 'Tab') {
var f = focusables();
if (!f.length) return;
var first = f[0], last = f[f.length - 1];
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
}
});
// ルートに応じて body.is-lesson を切替(CSS がサイドバー非表示/☰表示を出し分ける)。
// レッスンページ以外では必ずドロワーを閉じ状態にする(closeDrawer は冪等=残留状態も掃除)。
function syncLessonMode() {
var lesson = isLessonRoute();
document.body.classList.toggle('is-lesson', lesson);
if (!lesson) closeDrawer(false);
}
try { fillLogoMark(); } catch (e) {}
try { fillIcons(); } catch (e) {}
try { updateNavCurrent(); } catch (e) {}
try { syncLessonMode(); } catch (e) {}
window.addEventListener('hashchange', function () {
try { updateNavCurrent(); } catch (e) {}
try { syncLessonMode(); } catch (e) {}
});
})();
</script>
</body>
</html>