Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions html-css-slide/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# HTML/CSS を始めよう — Web ページの構造と見た目

> この資料は jig.jp のインターン/勉強会向けに作成されたものです。

## 目的

HTML(構造)・CSS(見た目)・JavaScript(動き)の役割を、**スライド内の実行環境で手を動かしながら**学びます。

元になった自習用ドキュメント([jigintern/intern-dev-tutorial の html-css](https://github.com/jigintern/intern-dev-tutorial/blob/main/html-css/DOCS.md))を、勉強会向けに再構成したものです。

## 対象

- プログラミング/Web 開発の初学者
- 特別な前提知識は不要です

## 準備

- **ブラウザだけで OK**。スライド内の「▶ 実行」できるコード欄でその場で試せます
- インストールは不要

## 困ったときは

- 質問がある場合、スライド右下の通し番号(章節)といっしょに質問してもらえると対応しやすいです。

## 本編

- [markdown版](./slide.md)
- [HTML版](https://jigintern.github.io/study_session_materials/html-css-start-2026/slide.html)
Binary file added html-css-slide/imgs/box-model-in-art.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added html-css-slide/imgs/open-dev-tool.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added html-css-slide/imgs/show-conputed-tab.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added html-css-slide/imgs/visbug-guide-tool.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions html-css-slide/rehearsal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# リハーサル計測

スライドを通しで読んだときの所要時間を、章ごと・スライドごとに記録するためのブックマークレットです。

読み上げながらページを送るだけで記録が残ります。
リハーサル中に操作を足す必要はありません。

## 準備

ブックマークを 1 つ作り、URL 欄に [bookmarklet.txt](./bookmarklet.txt) の中身をそのまま貼り付けます。
名前は「リハ計測」など分かるものにしてください。

`javascript:` で始まる長い 1 行ですが、途中で改行せずに全体を貼れば動きます。

## 使い方

1. スライドを開きます。ローカルの `slide.html` でも、公開されている HTML 版でも構いません。
2. ブックマーク「リハ計測」をクリックします。右上に `REC` のバッジが出れば記録中です。
3. いつもどおりスライドを送ります。
4. 読み終えたら Ctrl+Shift+E を押します。Markdown ファイルがダウンロードされ、同じ内容がクリップボードにも入ります。
5. ダウンロードしたファイルを [records/](./records/) に置いてコミットします。

## キー操作

- Ctrl+Shift+E — 記録を Markdown で書き出します
- Ctrl+Shift+P — 一時停止と再開を切り替えます (右上のバッジをクリックしても同じです)
- Ctrl+Shift+X — 記録を破棄して最初から測り直します

休憩スライドで止まるときや、途中で中断するときは一時停止してください。
止めていた時間は所要時間から除かれます。

## 記録される内容

書き出される Markdown は 2 段構えです。

章ごとの表には、章の所要時間と、通しの何分時点でその章に入ったかが並びます。
資料冒頭の「所要時間目安: 180 分」と突き合わせるのはこちらです。

スライドごとの表には、1 枚あたりの滞在時間と訪問回数が並びます。
どの節で時間を使ったかを追うのはこちらです。
戻って読み直したスライドは訪問回数が 2 以上になり、滞在時間は合算されます。

章の区切りは、スライドの h1 見出しから自動で判定します。
`# Chapter 1` のように h1 を置いたスライドが章の扉として扱われるため、章を増やしても設定を直す必要はありません。

## 途中で閉じてしまったとき

記録は 5 秒ごとにブラウザの localStorage へ保存しています。
同じスライドをもう一度開いてブックマークレットを起動すると、続きから記録するか、破棄して最初からやり直すかを聞かれます。

保存は URL のパスごとに分かれています。
ローカルの `slide.html` と公開版の記録は混ざりません。

## bookmarklet.js を編集したとき

`bookmarklet.txt` は `bookmarklet.js` から生成しています。
ソースを直したら再生成して、ブックマークに貼り直してください。

```sh
node rehearsal/build.mjs
```

## 発表練習(読み上げ台本)

このフォルダの [`transcript/`](./transcript/) には、`slide.md` から起こした「講師の話し言葉」の台本を置きます。
`say` コマンドで読み上げて発表練習ができます。作り方と使い方は [`transcript/README.md`](./transcript/README.md) を参照してください。
120 changes: 120 additions & 0 deletions html-css-slide/rehearsal/build-notes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* slide.md と rehearsal/transcript/*.md から、発表者ノート入りの slide.notes.md を生成します。
*
* node rehearsal/build-notes.mjs # このスクリプトのある教材に対して実行
* node rehearsal/build-notes.mjs <教材dir> # 別教材を指定(例: 2026/shinkei-suijaku)
*
* transcript の各ブロック(## 見出し)を、slide.md の「同じ見出し」のスライドに
* 発表者ノート(HTML コメント)として差し込みます。
* → 見出しは transcript 側で、スライドの見出しを一字一句そのままコピーしておくのが前提です
* (transcript/README.md の「生成ルール」を参照)。
*
* 生成した slide.notes.md を marp でビルドし、ブラウザで P キーを押すと
* 発表者ビューに台本が表示されます(投影スライドには出ません)。
*/
import { readFileSync, writeFileSync, readdirSync, existsSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const scriptDir = dirname(fileURLToPath(import.meta.url)); // .../rehearsal
const arg = process.argv[2];
const materialDir = arg ? resolve(arg) : dirname(scriptDir);
const rehearsalDir = arg ? join(materialDir, 'rehearsal') : scriptDir;
const transcriptDir = join(rehearsalDir, 'transcript');
const slidePath = join(materialDir, 'slide.md');
const outPath = join(materialDir, 'slide.notes.md');

// 見出しの表記ゆれ(全角半角・空白・末尾記号・強調)を吸収して比較するためのキー。
const norm = s =>
s.normalize('NFKC').replace(/[`*_]/g, '').replace(/\s+/g, '').replace(/[.:。、]+$/, '').toLowerCase();

// スライド/ブロックの先頭見出し(コードブロック内は無視)。
const firstHeading = block => {
let inFence = false;
for (const line of block.split(/\r?\n/)) {
if (/^```/.test(line)) { inFence = !inFence; continue; }
if (inFence) continue;
const m = line.match(/^#{1,6}\s+(.*\S)\s*$/);
if (m) return m[1];
}
return null;
};

if (!existsSync(transcriptDir)) {
console.error('transcript/ が見つかりません: ' + transcriptDir);
process.exit(1);
}
if (!existsSync(slidePath)) {
console.error('slide.md が見つかりません: ' + slidePath);
process.exit(1);
}

// --- transcript を「## 見出し」ブロックに分解 ---
const files = readdirSync(transcriptDir).filter(f => /^\d{2}-.*\.md$/.test(f)).sort();
const blocks = []; // { heading, note }
for (const f of files) {
const text = readFileSync(join(transcriptDir, f), 'utf8');
for (const part of text.split(/^(?=##\s)/m)) {
const m = part.match(/^##\s+(.*\S)\s*$/m);
if (!m) continue; // 先頭の「# タイトル」や「> 前書き」は無視
const note = part.replace(/^##\s+.*$/m, '').trim();
if (note) blocks.push({ heading: m[1], note });
}
}
const noteByHeading = new Map();
for (const b of blocks) {
const k = norm(b.heading);
if (!noteByHeading.has(k)) noteByHeading.set(k, []);
noteByHeading.get(k).push(b.note);
}

// --- slide.md を frontmatter + スライド配列に分解 ---
const lines = readFileSync(slidePath, 'utf8').split(/\r?\n/);
let fmEnd = -1;
if (lines[0].trim() === '---') {
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '---') { fmEnd = i; break; }
}
}
const frontmatter = fmEnd >= 0 ? lines.slice(0, fmEnd + 1).join('\n') : '';
const body = (fmEnd >= 0 ? lines.slice(fmEnd + 1) : lines).join('\n');
const slides = body.split(/^---$/m);

// --- 各スライドにノートを差し込む ---
const used = new Set();
let matched = 0;
const unmatchedSlides = [];
const merged = slides.map(slide => {
const h = firstHeading(slide);
if (!h) return slide;
const q = noteByHeading.get(norm(h));
if (q && q.length) {
// HTML コメントを閉じてしまう "--" を全角ダッシュに置換してから差し込む。
const note = q.shift().replace(/-{2,}/g, '—');
matched++;
used.add(norm(h));
return slide.replace(/\s*$/, '') + `\n\n<!--\n${note}\n-->\n`;
}
unmatchedSlides.push(h);
return slide;
});

writeFileSync(outPath, (frontmatter ? frontmatter + '\n' : '') + merged.join('---'));

// --- レポート ---
const withHeading = slides.filter(s => firstHeading(s)).length;
const leftover = blocks.filter(b => !used.has(norm(b.heading))).map(b => b.heading);
const rel = arg ? arg.replace(/\/$/, '') + '/' : '';
console.log(`生成しました: ${outPath}`);
console.log(`スライド ${slides.length} 枚(見出しあり ${withHeading} 枚)/ ノートを差し込めた: ${matched} 枚`);
if (unmatchedSlides.length) {
console.log(`\n台本が見つからなかったスライド (${unmatchedSlides.length}) — transcript 側の見出しをスライドに合わせると埋まります:`);
unmatchedSlides.forEach(h => console.log(' - ' + h));
}
if (leftover.length) {
console.log(`\nどのスライドにも一致しなかった台本ブロック (${leftover.length}):`);
leftover.forEach(h => console.log(' - ' + h));
}
console.log(`\n次のコマンドで発表者ノート付き HTML を作成:`);
console.log(` pnpm exec marp ${rel}slide.notes.md --html -o ${rel}slide.notes.html --allow-local-files`);
console.log(` → ブラウザで開き、P キーで発表者ビュー(台本が表示されます)`);
Empty file.
98 changes: 98 additions & 0 deletions html-css-slide/rehearsal/transcript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# 発表文字起こし(読み上げ用台本)

このフォルダには、**完成した `slide.md` を元にした「講師の話し言葉」の台本**を置きます。
macOS の `say` コマンドで読み上げれば、授業を受けている感覚で聞けて、**発表練習**にも使えます。

台本は Claude に生成してもらう前提です。以下はその**指示書**と、`say` での練習方法です。

---

## 使い方(3 ステップ)

1. `slide.md` を仕上げる(`{{ }}` プレースホルダを残さない)。
2. Claude に台本を生成してもらう(下の「Claude への依頼」)。
3. `say` で読み上げて練習する(下の「say で発表練習」)。

---

## Claude への依頼(コピペ用)

Claude に次のように頼んでください(このファイルを指し示すだけでも動きます)。

```
rehearsal/transcript/README.md の「生成ルール」に従って、slide.md から発表用の文字起こし台本を作ってください。
章ごとにファイルを分け、講師の話し言葉で書いてください。読み上げは自分で行うので、台本の生成だけで大丈夫です。
```

Claude に頼むのは**台本の生成まで**です。`say` での読み上げは、下のコマンドを自分で実行します。

### 生成ルール(Claude はこれに従って書く)

- **入力**: 一つ上の階層の `../../slide.md`。**出力**: このフォルダ(`transcript/`)に Markdown で保存。
- **ファイル分割と命名**: スライドの章立てに合わせて、聞く順に並ぶ 2 桁プレフィックスを付ける。
- `00-intro.md`(タイトル〜「進め方」「詰まったとき」までの導入)
- `01-chapter1.md`, `02-chapter2.md`, …(各 Chapter に 1 ファイル)
- `99-appendix.md`(完成・お疲れさま・応用課題・付録)
- **文体**: 講師の一人称の話し言葉。聞き手に語りかける丁寧語(「〜します」「〜してみましょう」)。読み上げて自然な、耳で分かる文章にする。
- **スライドとの対応(重要)**: 台本は**スライドの並び順どおり**に書き、各ブロックの `##` 見出しには、対応するスライドの見出しを**一字一句そのままコピー**する(句読点・全角半角も変えない。例: スライドが `## 準備: StackBlitz テンプレを開こう` なら台本も同じ文字列)。こうしておくと、`build-notes.mjs` が見出しをキーにして各スライドの**発表者ノート**へ機械的に流し込めます(→「発表者ノートに流し込む」)。手元のスライドと合わせて聞くのにも役立ちます。
- **コード**: 記号を 1 文字ずつ読み上げない。「何をしているか」を言葉で説明する(例: `getElementById("board")` →「board という id の要素を取ってくる」)。`===` は「厳密に等しいかを比べる」のように意味で言う。
- **記号・絵文字**: 言葉にする(🍎🍌🍇 →「りんご」「ばなな」「ぶどう」)。
- **演習タイマー**: スライドの `data-seconds` を分に直して「ここで◯分ほど時間を取ります」と案内する。
- **休憩スライド**(`section.break`): 「ここで◯分休憩します」と案内する。
- **各ファイルの冒頭**: タイトル(`# …`)と、`>` の 1 段落(この台本の位置づけの注記)を置く。
- **プレースホルダ**: `slide.md` に `{{ }}` が残っている場合は題材が未確定なので、**生成せずに先に slide.md を仕上げるよう伝える**。

---

## say で発表練習

読み上げは自分で実行します。macOS の `say` を使います(日本語音声は `Kyoko` が手軽。全一覧は `say -v '?'`)。

```sh
# 1 章をそのまま読み上げる
say -v Kyoko -f 01-chapter1.md

# 見出し(#)・区切り線(---)・引用(>)・画像行を除いてきれいに読む
sed -E 's/^#+ +//; s/^> ?//; /^---$/d; /^!\[/d' 01-chapter1.md | say -v Kyoko

# 速度を上げる(1 分あたりの語数。既定は約 175)
say -r 210 -v Kyoko -f 01-chapter1.md

# 全章を順に通し読み
for f in [0-9][0-9]-*.md; do echo "▶ $f"; say -v Kyoko -f "$f"; done
```

- 途中で止めるときは `Ctrl+C`。
- 音声を書き出して倍速で聞くなら `say -v Kyoko -f 01-chapter1.md -o 01.aiff`。

---

## 発表者ノートに流し込む(本番用)

台本を、スライドの**発表者ノート**に自動で差し込めます。本番では投影スライドの裏で台本を見ながら話せます。

```sh
# slide.md + transcript/ から、ノート入りの slide.notes.md を生成
node rehearsal/build-notes.mjs

# ノート入りスライドをビルド
npx marp slide.notes.md --html -o slide.notes.html --allow-local-files
```

- 生成された `slide.notes.html` をブラウザで開き、**P キー**で発表者ビューを開くと、各スライドに台本が表示されます(投影側には出ません)。
- ひも付けは**見出しの一致**で行います。実行後のレポートに「台本が見つからなかったスライド」が並んだら、transcript 側の `##` 見出しをそのスライドの見出しにそろえてください(上の生成ルール参照)。
- `slide.notes.md` / `slide.notes.html` は生成物です(`slide.md` 本体は変更しません)。コミット対象から外して構いません。

---

## 聞く順番(生成後に埋める)

台本を作ったら、この表を実際のファイルに合わせて更新しておくと便利です。

| 順 | ファイル | 内容 |
| --- | -------------- | -------------------------------------------- |
| 1 | 00-intro.md | 導入(ゴール・流れ・進め方・準備) |
| 2 | 01-chapter1.md | Chapter 1 … |
| … | … | … |
| — | (休憩 ◯分) | |
| n | 99-appendix.md | 完成・応用課題・付録(あとで自分のペースで) |
1,458 changes: 1,458 additions & 0 deletions html-css-slide/slide.html

Large diffs are not rendered by default.

Loading