1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScript

1
Last updated at Posted at 2026-03-09
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM操作リファレンス</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
  --orange: #D97706;
  --orange-light: #FEF3C7;
  --cream: #FFFBF5;
  --sand: #F5F0E8;
  --brown: #92400E;
  --text-dark: #1C1917;
  --text-mid: #44403C;
  --text-light: #78716C;
  --border: #E7E0D8;
  --green: #059669;
  --green-light: #ECFDF5;
  --blue: #2563EB;
  --blue-light: #EFF6FF;
  --red: #DC2626;
  --purple: #7C3AED;
  --purple-light: #F5F3FF;
  --white: #FFFFFF;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Noto Sans JP', sans-serif; background: var(--cream); color: var(--text-dark); line-height: 1.7; }

.hero { background: linear-gradient(135deg, #FFFBF5 0%, #FEF3C7 50%, #FDE68A 100%); padding: 64px 24px 48px; text-align: center; border-bottom: 2px solid var(--border); }
.badge { display: inline-block; background: var(--orange); color: white; font-size: 11px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; padding: 4px 14px; border-radius: 100px; margin-bottom: 16px; }
.hero h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); font-weight: 700; margin-bottom: 10px; }
.hero h1 span { color: var(--orange); }
.hero p { font-size: 0.97rem; color: var(--text-mid); max-width: 520px; margin: 0 auto; }

.toc { background: var(--white); border-bottom: 1px solid var(--border); padding: 12px 24px; display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; position: sticky; top: 0; z-index: 100; box-shadow: 0 1px 8px rgba(0,0,0,0.06); }
.toc a { font-size: 0.79rem; font-weight: 600; text-decoration: none; padding: 5px 12px; border-radius: 100px; border: 1.5px solid var(--border); color: var(--text-mid); transition: all 0.2s; white-space: nowrap; }
.toc a:hover { background: var(--orange); color: white; border-color: var(--orange); }

.container { max-width: 960px; margin: 0 auto; padding: 0 20px; }
.section-intro { padding: 52px 24px 10px; max-width: 960px; margin: 0 auto; }
.section-label { font-size: 11px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--orange); margin-bottom: 5px; }
.section-intro h2 { font-size: 1.55rem; font-weight: 700; margin-bottom: 5px; }
.section-intro p { color: var(--text-mid); font-size: 0.9rem; }

.card { background: var(--white); border: 1.5px solid var(--border); border-radius: 16px; margin: 0 auto 28px; max-width: 960px; overflow: hidden; box-shadow: 0 2px 12px rgba(0,0,0,0.04); }
.card-header { padding: 16px 22px 12px; border-bottom: 1px solid var(--border); display: flex; align-items: flex-start; gap: 12px; }
.method-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 1.1rem; flex-shrink: 0; }
.icon-prop   { background: var(--purple-light); }
.icon-search { background: var(--blue-light); }
.icon-manip  { background: var(--green-light); }
.card-header-text h3 { font-size: 1.02rem; font-weight: 700; margin-bottom: 2px; }
.mname { font-family: 'Fira Code', monospace; background: var(--sand); padding: 1px 7px; border-radius: 5px; font-size: 0.83rem; color: var(--brown); font-weight: 500; }
.card-header-text p { font-size: 0.83rem; color: var(--text-light); margin-top: 2px; }

/* USE CASE */
.usecase { background: linear-gradient(90deg, #FEF3C7, #FFFBF5); border-left: 3px solid var(--orange); padding: 9px 18px; margin: 12px 20px 0; border-radius: 0 8px 8px 0; }
.usecase-text { font-size: 0.85rem; font-weight: 600; color: var(--brown); }
.usecase-text em { color: var(--orange); font-style: normal; }

/* OUTPUT CARD */
.output-card {
  margin: 14px 22px 0;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.output-card-label {
  background: var(--sand);
  padding: 8px 16px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-mid);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.output-card-desc {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-mid);
  font-family: 'Fira Code', monospace;
}
.output-card-body {
  background: var(--white);
  padding: 14px 16px;
  min-height: 52px;
}

/* INITIAL VALUE STRIP */
.output-initial {
  background: #F8F5F0;
  border-bottom: 1px solid var(--border);
  padding: 6px 16px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.initial-badge {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--orange-light);
  color: var(--brown);
  border: 1px solid #FCD34D;
  border-radius: 4px;
  padding: 1px 7px;
  white-space: nowrap;
  flex-shrink: 0;
}
.initial-code {
  font-family: 'Fira Code', monospace;
  font-size: 0.75rem;
  color: var(--text-light);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* DEMO AREA */
.demo-area { padding: 16px 22px 20px; }

/* SPLIT: left=steps, right=code */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
@media(max-width:660px) { .split { grid-template-columns: 1fr; } }

/* STEPS PANEL */
.steps-panel { background: var(--sand); border-radius: 10px; padding: 14px; }
.code-panel  { background: #1C1917; border-radius: 10px; padding: 14px; }

.panel-lbl { font-size: 10px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 10px; display: flex; align-items: center; gap: 5px; }
.panel-lbl.sp { color: var(--text-mid); }
.panel-lbl.cd { color: #FDE68A; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }

/* CODE LINES — highlight only, no callouts */
.code-lines { font-family: 'Fira Code', monospace; font-size: 0.79rem; line-height: 1.9; }
.cl { display: flex; align-items: center; border-radius: 4px; transition: background 0.2s; padding: 0 4px; }
.ln { color: #374151; user-select: none; min-width: 16px; text-align: right; margin-right: 10px; font-size: 0.7rem; flex-shrink: 0; }
.lb { color: #D1D5DB; flex: 1; white-space: pre; overflow: hidden; }
.lb .kw  { color: #F97316; }
.lb .str { color: #A3E635; }
.lb .com { color: #4B5563; font-style: italic; }
.lb .pp  { color: #38BDF8; }
.lb .fn  { color: #C084FC; }
.lb .vr  { color: #FDE68A; }
.cl.cur  { background: rgba(217,119,6,0.26); border-left: 3px solid var(--orange); margin-left: -3px; padding-left: 7px; }
.cl.cur  .ln { color: var(--orange); }
.cl.done { background: rgba(5,150,105,0.10); }
.cl.done .ln { color: var(--green); }

/* EXPLANATION BOX — fixed height so button never moves */
.explanation {
  background: var(--blue-light);
  border: 1.5px solid #BFDBFE;
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 12px;
  font-size: 0.83rem;
  height: 112px;
  overflow-y: auto;
}
.ex-step { font-weight: 700; color: var(--blue); margin-bottom: 3px; font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; }
.ex-text { color: var(--text-dark); line-height: 1.55; }
.explanation.idle { background: #F9FAFB; border-color: var(--border); }
.explanation.idle .ex-step { color: var(--text-light); }
.explanation.idle .ex-text { color: var(--text-light); }
.explanation.done-all { background: var(--green-light); border-color: #A7F3D0; }
.explanation.done-all .ex-step { color: var(--green); }

/* INDEX GRID */
.index-section { background: var(--white); border-bottom: 1px solid var(--border); padding: 28px 24px 32px; }
.index-section h2 { font-size: 0.82rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-light); text-align: center; margin-bottom: 20px; }
.index-group { max-width: 960px; margin: 0 auto 20px; }
.index-group-label { font-size: 10px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-light); margin-bottom: 8px; padding-left: 2px; display: flex; align-items: center; gap: 6px; }
.index-group-label::before { content: ''; display: block; height: 1px; flex: 1; background: var(--border); }
.index-group-label::after  { content: ''; display: block; height: 1px; flex: 1; background: var(--border); }
.index-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 8px; max-width: 960px; margin: 0 auto; }
.index-card {
  display: block;
  text-decoration: none;
  background: var(--cream);
  border: 1.5px solid var(--border);
  border-radius: 10px;
  padding: 10px 13px;
  transition: all 0.18s;
  cursor: pointer;
}
.index-card:hover { background: var(--white); border-color: var(--orange); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
.index-card-name { font-family: 'Fira Code', monospace; font-size: 0.8rem; font-weight: 500; color: var(--brown); margin-bottom: 4px; }
.index-card-desc { font-size: 0.75rem; color: var(--text-light); line-height: 1.4; }

/* value highlight inside explanation */
.val { display: inline-block; font-family: 'Fira Code', monospace; background: #1C1917; color: #FDE68A; padding: 1px 7px; border-radius: 4px; font-size: 0.82rem; }

/* STEP CONTROLS */
.step-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.btn-step { background: var(--orange); color: white; border: none; padding: 8px 18px; border-radius: 8px; font-family: 'Noto Sans JP', sans-serif; font-size: 0.82rem; font-weight: 700; cursor: pointer; transition: all 0.15s; }
.btn-step:hover { background: var(--brown); transform: translateY(-1px); }
.btn-step:disabled { background: #D1D5DB; color: #9CA3AF; cursor: not-allowed; transform: none; }
.btn-reset-s { background: transparent; color: var(--text-light); border: 1.5px solid var(--border); padding: 7px 13px; border-radius: 8px; font-family: 'Noto Sans JP', sans-serif; font-size: 0.79rem; font-weight: 600; cursor: pointer; transition: all 0.15s; }
.btn-reset-s:hover { background: var(--white); color: var(--text-dark); }
.step-counter { font-size: 0.77rem; color: var(--text-light); font-family: 'Fira Code', monospace; }
.step-counter b { color: var(--orange); }
.step-progress { height: 3px; background: var(--border); border-radius: 100px; margin-top: 10px; overflow: hidden; }
.step-progress-fill { height: 100%; background: var(--orange); border-radius: 100px; transition: width 0.25s ease; }

/* LIVE ELEMENTS inside output card */
.live-el { font-size: 0.87rem; transition: all 0.3s; padding: 4px 0; }
.live-el.flash { color: var(--green); }
.list-live { list-style: none; }
.list-live li { background: var(--sand); border: 1.5px solid var(--border); border-radius: 6px; padding: 7px 11px; margin-bottom: 5px; font-size: 0.82rem; display: flex; align-items: center; gap: 7px; transition: all 0.3s; }
.list-live li::before { content: "▸"; color: var(--orange); flex-shrink: 0; }
.new-item { background: #FEF3C7 !important; border-color: var(--orange) !important; }
.out-placeholder { color: var(--text-light); font-size: 0.83rem; font-style: italic; }

.divider { height: 1px; background: linear-gradient(90deg, transparent, var(--border), transparent); margin: 44px auto; max-width: 960px; }

.tag { border-radius: 100px; padding: 2px 10px; font-size: 0.71rem; font-weight: 700; }
.tp { background: var(--purple-light); color: var(--purple); }
.ts { background: var(--blue-light);   color: var(--blue); }
.tm { background: var(--green-light);  color: var(--green); }
.pad { padding: 0 22px; }
</style>
</head>
<body>

<div class="hero">
  <div class="badge">JavaScript DOM</div>
  <h1>DOM操作を<span>1ステップずつ</span>理解する</h1>
  <p>「次のステップへ →」を押すたびにコードが1行ずつ実行され、出力がどう変わるかがわかる。</p>
</div>

<div class="index-section">
  <h2>📋 メソッド一覧 — クリックで該当箇所へ</h2>

  <div class="index-group">
    <div class="index-group-label">📦 プロパティ</div>
    <div class="index-grid">
      <a class="index-card" href="#card-tc"><span class="index-card-name">textContent</span><span class="index-card-desc">文字だけ変えたい</span></a>
      <a class="index-card" href="#card-ih"><span class="index-card-name">innerHTML</span><span class="index-card-desc">HTMLタグを含めて書き換えたい</span></a>
      <a class="index-card" href="#card-tg"><span class="index-card-name">tagName / id</span><span class="index-card-desc">要素の種類・識別子を調べたい</span></a>
    </div>
  </div>

  <div class="index-group">
    <div class="index-group-label">🔍 検索メソッド</div>
    <div class="index-grid">
      <a class="index-card" href="#card-qs"><span class="index-card-name">querySelector()</span><span class="index-card-desc">最初の1要素を取得</span></a>
      <a class="index-card" href="#card-qsa"><span class="index-card-name">querySelectorAll()</span><span class="index-card-desc">条件に合う全要素を取得</span></a>
    </div>
  </div>

  <div class="index-group" style="margin-bottom:0;">
    <div class="index-group-label">⚙️ 操作メソッド</div>
    <div class="index-grid">
      <a class="index-card" href="#card-ac"><span class="index-card-name">appendChild()</span><span class="index-card-desc">末尾に要素を追加</span></a>
      <a class="index-card" href="#card-ib"><span class="index-card-name">insertBefore()</span><span class="index-card-desc">指定要素の前に挿入</span></a>
      <a class="index-card" href="#card-rc"><span class="index-card-name">removeChild()</span><span class="index-card-desc">子要素を削除</span></a>
      <a class="index-card" href="#card-rp"><span class="index-card-name">replaceChild()</span><span class="index-card-desc">別の要素と置き換え</span></a>
    </div>
  </div>
</div>

<nav class="toc">
  <a href="#props">📦 プロパティ</a>
  <a href="#search">🔍 検索メソッド</a>
  <a href="#manip">⚙️ 操作メソッド</a>
  <a href="#summary">📋 まとめ</a>
</nav>

<!-- ================================================================ PROPERTIES -->
<section id="props">
<div class="section-intro">
  <div class="section-label">📦 Properties</div>
  <h2>プロパティ — 情報を取得・設定する</h2>
  <p>要素が持つデータを読み書きする「窓口」。代入するだけで画面が変わる。</p>
</div>

<!-- textContent -->
<div class="card container pad" id="card-tc">
  <div class="card-header">
    <div class="method-icon icon-prop">📝</div>
    <div class="card-header-text">
      <h3><span class="mname">textContent</span> — テキストを書き換える</h3>
      <p>タグを含まない純粋なテキスト内容を取得・設定。HTMLは無視される。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「ボタンを押したら<em>ステータス文字</em>を変えたい!」→ textContent</span>
  </div>

  <!-- OUTPUT CARD -->
  <div class="output-card">
    <div class="output-card-label">出力 <span class="output-card-desc">— el.textContent の現在値</span></div>
    <div class="output-initial">
      <span class="initial-badge">初期HTML</span>
      <span class="initial-code">&lt;div id="status"&gt;読み込み中...&lt;/div&gt;</span>
    </div>
    <div class="output-card-body">
      <div class="live-el" id="tc-out">読み込み中...</div>
    </div>
  </div>

  <div class="demo-area">
    <div class="split">
      <div class="steps-panel">
        <div class="panel-lbl sp"><span class="dot"></span> 手順</div>
        <div class="explanation idle" id="tc-ex">
          <div class="ex-step">開始前</div>
          <div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
        </div>
        <div class="step-progress"><div class="step-progress-fill" id="tc-prog" style="width:0%"></div></div>
        <div class="step-controls" style="margin-top:10px;">
          <button class="btn-step" id="tc-next" onclick="step('tc')">次のステップへ →</button>
          <button class="btn-reset-s" onclick="reset('tc')">リセット</button>
          <span class="step-counter">ステップ <b id="tc-cur">0</b> / <b id="tc-tot"></b></span>
        </div>
      </div>
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl" id="tc-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">el</span> = document.<span class="fn">querySelector</span>(<span class="str">'#status'</span>);</span></div>
          <div class="cl" id="tc-s1"><span class="ln">2</span><span class="lb"><span class="vr">el</span>.<span class="pp">textContent</span> = <span class="str">'データ取得しました ✓'</span>;</span></div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- innerHTML -->
<div class="card container pad" id="card-ih">
  <div class="card-header">
    <div class="method-icon icon-prop">🏗️</div>
    <div class="card-header-text">
      <h3><span class="mname">innerHTML</span> — HTMLごと書き換える</h3>
      <p>内部のHTML文字列を丸ごと取得・設定。タグも自由に挿入できる。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「タグ付きHTMLを<em>そのままレンダリング</em>したい!」→ innerHTML</span>
  </div>
  <div class="output-card">
    <div class="output-card-label">出力 <span class="output-card-desc">— card.innerHTML のレンダリング結果</span></div>
    <div class="output-initial">
      <span class="initial-badge">初期HTML</span>
      <span class="initial-code">&lt;div id="result"&gt;コンテンツなし&lt;/div&gt;</span>
    </div>
    <div class="output-card-body">
      <div class="live-el" id="ih-out">コンテンツなし</div>
    </div>
  </div>
  <div class="demo-area">
    <div class="split">
      <div class="steps-panel">
        <div class="panel-lbl sp"><span class="dot"></span> 手順</div>
        <div class="explanation idle" id="ih-ex">
          <div class="ex-step">開始前</div>
          <div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
        </div>
        <div class="step-progress"><div class="step-progress-fill" id="ih-prog" style="width:0%"></div></div>
        <div class="step-controls" style="margin-top:10px;">
          <button class="btn-step" id="ih-next" onclick="step('ih')">次のステップへ →</button>
          <button class="btn-reset-s" onclick="reset('ih')">リセット</button>
          <span class="step-counter">ステップ <b id="ih-cur">0</b> / <b id="ih-tot"></b></span>
        </div>
      </div>
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl" id="ih-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">card</span> = document.<span class="fn">querySelector</span>(<span class="str">'#result'</span>);</span></div>
          <div class="cl" id="ih-s1"><span class="ln">2</span><span class="lb"><span class="vr">card</span>.<span class="pp">innerHTML</span> = <span class="str">`&lt;b&gt;🎉 成功!&lt;/b&gt; `</span></span></div>
          <div class="cl" id="ih-s2"><span class="ln">3</span><span class="lb">  + <span class="str">`&lt;em&gt;@taro&lt;/em&gt; がログイン`</span>;</span></div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- tagName / id -->
<div class="card container pad" id="card-tg">
  <div class="card-header">
    <div class="method-icon icon-prop">🏷️</div>
    <div class="card-header-text">
      <h3><span class="mname">tagName</span> / <span class="mname">id</span> — 要素の素性を調べる</h3>
      <p>tagName はタグ種別("DIV" / "P"...)、id は識別子を返す。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「クリックされた要素が<em>何のタグか</em>によって処理を分けたい!」→ tagName</span>
  </div>

  <!-- clickable elements -->
  <div style="padding: 14px 22px 0; display:flex; gap:8px; flex-wrap:wrap;">
    <button id="probe-btn" style="background:var(--brown);color:white;border:none;padding:7px 15px;border-radius:7px;font-size:0.82rem;font-weight:600;cursor:pointer;" onclick="probeClick(this)">ボタン要素</button>
    <p id="probe-p" style="cursor:pointer;padding:7px 15px;background:var(--white);border:1.5px solid var(--border);border-radius:7px;font-size:0.82rem;" onclick="probeClick(this)">P 要素</p>
    <div id="probe-div" style="cursor:pointer;padding:7px 15px;background:var(--orange-light);border:1.5px solid var(--border);border-radius:7px;font-size:0.82rem;" onclick="probeClick(this)">DIV 要素</div>
  </div>

  <div class="output-card" style="margin-top:14px;">
    <div class="output-card-label">出力 <span class="output-card-desc">— e.target.tagName / e.target.id の値</span></div>
    <div class="output-initial">
      <span class="initial-badge">クリック待ち</span>
      <span class="initial-code">上の要素をクリックすると tagName と id の値が確認できる</span>
    </div>
    <div class="output-card-body">
      <div id="tg-out" class="out-placeholder">← 上の要素をクリックすると tagName と id が表示される</div>
    </div>
  </div>

  <div class="demo-area">
    <!-- explanation only — no steps needed -->
    <div id="tg-explain" class="explanation idle" style="margin-bottom:0;">
      <div class="ex-step">解説</div>
      <div class="ex-text">上の要素をクリックすると <span class="val">e.target.tagName</span> と <span class="val">e.target.id</span> がどんな値を持つか確認できる。</div>
    </div>
    <div style="margin-top:14px;">
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl"><span class="ln">1</span><span class="lb"><span class="vr">el</span>.<span class="fn">addEventListener</span>(<span class="str">'click'</span>, (e) =&gt; {</span></div>
          <div class="cl"><span class="ln">2</span><span class="lb">  <span class="kw">const</span> <span class="vr">tag</span> = e.<span class="pp">target</span>.<span class="pp">tagName</span>;</span></div>
          <div class="cl"><span class="ln">3</span><span class="lb">  <span class="kw">const</span> <span class="vr">id</span>  = e.<span class="pp">target</span>.<span class="pp">id</span>;</span></div>
          <div class="cl"><span class="ln">4</span><span class="lb">  console.<span class="fn">log</span>(<span class="vr">tag</span>, <span class="vr">id</span>);</span></div>
          <div class="cl"><span class="ln">5</span><span class="lb">});</span></div>
        </div>
      </div>
    </div>
  </div>
</div>
</section>

<div class="divider"></div>

<!-- ================================================================ SEARCH -->
<section id="search">
<div class="section-intro">
  <div class="section-label">🔍 Search Methods</div>
  <h2>検索メソッド — 要素を見つける</h2>
  <p>CSSセレクタを使ってページ内の要素を自在に取得する。</p>
</div>

<!-- querySelector -->
<div class="card container pad" id="card-qs">
  <div class="card-header">
    <div class="method-icon icon-search">🎯</div>
    <div class="card-header-text">
      <h3><span class="mname">querySelector()</span> — 最初の1つを取得</h3>
      <p>CSSセレクタに一致する最初の要素を返す。見つからなければ null。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「フォームの<em>送信ボタン1つ</em>を取得して disabled にしたい!」→ querySelector</span>
  </div>
  <div class="output-card">
    <div class="output-card-label">出力 <span class="output-card-desc">— querySelector で取得したボタンの状態</span></div>
    <div class="output-initial">
      <span class="initial-badge">初期HTML</span>
      <span class="initial-code">&lt;button class="submit-btn"&gt;✉️ メイン送信&lt;/button&gt;</span>
    </div>
    <div class="output-card-body" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
      <button style="background:#78716C;color:white;border:none;padding:7px 14px;border-radius:7px;font-size:0.8rem;font-weight:600;">送信</button>
      <button style="background:#78716C;color:white;border:none;padding:7px 14px;border-radius:7px;font-size:0.8rem;font-weight:600;">キャンセル</button>
      <button id="qs-main" class="submit-btn" style="background:var(--orange);color:white;border:none;padding:7px 14px;border-radius:7px;font-size:0.8rem;font-weight:600;">✉️ メイン送信</button>
    </div>
  </div>
  <div class="demo-area">
    <div class="split">
      <div class="steps-panel">
        <div class="panel-lbl sp"><span class="dot"></span> 手順</div>
        <div class="explanation idle" id="qs-ex">
          <div class="ex-step">開始前</div>
          <div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
        </div>
        <div class="step-progress"><div class="step-progress-fill" id="qs-prog" style="width:0%"></div></div>
        <div class="step-controls" style="margin-top:10px;">
          <button class="btn-step" id="qs-next" onclick="step('qs')">次のステップへ →</button>
          <button class="btn-reset-s" onclick="reset('qs')">リセット</button>
          <span class="step-counter">ステップ <b id="qs-cur">0</b> / <b id="qs-tot"></b></span>
        </div>
      </div>
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl" id="qs-s0"><span class="ln">1</span><span class="lb"><span class="com">// .submit-btn の最初の1つだけ取得</span></span></div>
          <div class="cl" id="qs-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">btn</span> = document.<span class="fn">querySelector</span>(<span class="str">'.submit-btn'</span>);</span></div>
          <div class="cl" id="qs-s2"><span class="ln">3</span><span class="lb"><span class="vr">btn</span>.<span class="pp">disabled</span> = <span class="kw">true</span>;</span></div>
          <div class="cl" id="qs-s3"><span class="ln">4</span><span class="lb"><span class="vr">btn</span>.<span class="pp">textContent</span> = <span class="str">'送信済み ✓'</span>;</span></div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- querySelectorAll -->
<div class="card container pad" id="card-qsa">
  <div class="card-header">
    <div class="method-icon icon-search">🔦</div>
    <div class="card-header-text">
      <h3><span class="mname">querySelectorAll()</span> — 全てを取得</h3>
      <p>CSSセレクタに一致する全要素を NodeList で返す。forEach でまとめて処理できる。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「未完了の<em>全タスクをまとめて</em>グレーアウトしたい!」→ querySelectorAll</span>
  </div>
  <div class="output-card">
    <div class="output-card-label">出力 <span class="output-card-desc">— querySelectorAll で取得した要素への適用結果</span></div>
    <div class="output-initial">
      <span class="initial-badge">初期HTML</span>
      <span class="initial-code">&lt;li class="task-item"&gt;コードレビュー&lt;/li&gt; など、done なしが3件</span>
    </div>
    <div class="output-card-body">
      <ul class="list-live" id="qsa-list">
        <li class="task-item done">✅ デザイン確認</li>
        <li class="task-item">コードレビュー</li>
        <li class="task-item done">✅ テスト実行</li>
        <li class="task-item">デプロイ</li>
        <li class="task-item">ドキュメント更新</li>
      </ul>
    </div>
  </div>
  <div class="demo-area">
    <div class="split">
      <div class="steps-panel">
        <div class="panel-lbl sp"><span class="dot"></span> 手順</div>
        <div class="explanation idle" id="qsa-ex">
          <div class="ex-step">開始前</div>
          <div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
        </div>
        <div class="step-progress"><div class="step-progress-fill" id="qsa-prog" style="width:0%"></div></div>
        <div class="step-controls" style="margin-top:10px;">
          <button class="btn-step" id="qsa-next" onclick="step('qsa')">次のステップへ →</button>
          <button class="btn-reset-s" onclick="reset('qsa')">リセット</button>
          <span class="step-counter">ステップ <b id="qsa-cur">0</b> / <b id="qsa-tot"></b></span>
        </div>
      </div>
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl" id="qsa-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">items</span> = document.<span class="fn">querySelectorAll</span>(</span></div>
          <div class="cl" id="qsa-s1"><span class="ln">2</span><span class="lb">  <span class="str">'.task-item:not(.done)'</span>);</span></div>
          <div class="cl" id="qsa-s2"><span class="ln">3</span><span class="lb"><span class="vr">items</span>.<span class="fn">forEach</span>((<span class="vr">item</span>) =&gt; {</span></div>
          <div class="cl" id="qsa-s3"><span class="ln">4</span><span class="lb">  <span class="vr">item</span>.<span class="pp">style</span>.opacity = <span class="str">'0.3'</span>;</span></div>
          <div class="cl" id="qsa-s4"><span class="ln">5</span><span class="lb">  <span class="vr">item</span>.<span class="pp">style</span>.textDecoration = <span class="str">'line-through'</span>;</span></div>
          <div class="cl"><span class="ln">6</span><span class="lb">});</span></div>
        </div>
      </div>
    </div>
  </div>
</div>
</section>

<div class="divider"></div>

<!-- ================================================================ MANIPULATION -->
<section id="manip">
<div class="section-intro">
  <div class="section-label">⚙️ Manipulation Methods</div>
  <h2>操作メソッド — 要素を追加・削除・移動する</h2>
  <p>DOMツリーを自在に組み替えて、動的なUIを作る。</p>
</div>

<!-- appendChild -->
<div class="card container pad" id="card-ac">
  <div class="card-header">
    <div class="method-icon icon-manip">➕</div>
    <div class="card-header-text">
      <h3><span class="mname">appendChild()</span> — 末尾に追加</h3>
      <p>指定した親要素の子として、末尾に新しい要素を追加する。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「ボタンを押すたびに<em>コメントを末尾へ追加</em>したい!」→ appendChild</span>
  </div>
  <div class="output-card">
    <div class="output-card-label">出力 <span class="output-card-desc">— appendChild 後の &lt;ul&gt; の中身</span></div>
    <div class="output-initial">
      <span class="initial-badge">初期HTML</span>
      <span class="initial-code">&lt;ul id="comments"&gt;&lt;li&gt;最初のコメント 👋&lt;/li&gt;&lt;/ul&gt;</span>
    </div>
    <div class="output-card-body">
      <ul class="list-live" id="ac-list">
        <li>最初のコメント 👋</li>
      </ul>
    </div>
  </div>
  <div class="demo-area">
    <div class="split">
      <div class="steps-panel">
        <div class="panel-lbl sp"><span class="dot"></span> 手順</div>
        <div class="explanation idle" id="ac-ex">
          <div class="ex-step">開始前</div>
          <div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
        </div>
        <div class="step-progress"><div class="step-progress-fill" id="ac-prog" style="width:0%"></div></div>
        <div class="step-controls" style="margin-top:10px;">
          <button class="btn-step" id="ac-next" onclick="step('ac')">次のステップへ →</button>
          <button class="btn-reset-s" onclick="reset('ac')">リセット</button>
          <span class="step-counter">ステップ <b id="ac-cur">0</b> / <b id="ac-tot"></b></span>
        </div>
      </div>
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl" id="ac-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">list</span> = document.<span class="fn">querySelector</span>(<span class="str">'#comments'</span>);</span></div>
          <div class="cl" id="ac-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">li</span> = document.<span class="fn">createElement</span>(<span class="str">'li'</span>);</span></div>
          <div class="cl" id="ac-s2"><span class="ln">3</span><span class="lb"><span class="vr">li</span>.<span class="pp">textContent</span> = <span class="str">'新しいコメント 🗨️'</span>;</span></div>
          <div class="cl" id="ac-s3"><span class="ln">4</span><span class="lb"><span class="vr">list</span>.<span class="fn">appendChild</span>(<span class="vr">li</span>);</span></div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- insertBefore -->
<div class="card container pad" id="card-ib">
  <div class="card-header">
    <div class="method-icon icon-manip">⬆️</div>
    <div class="card-header-text">
      <h3><span class="mname">insertBefore()</span> — 指定要素の前に挿入</h3>
      <p>親要素に対して「新要素を、この既存要素の直前に入れろ」と指示する。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「緊急通知を<em>リストの先頭</em>に割り込みで入れたい!」→ insertBefore</span>
  </div>
  <div class="output-card">
    <div class="output-card-label">出力 <span class="output-card-desc">— insertBefore 後の &lt;ul&gt; の中身</span></div>
    <div class="output-initial">
      <span class="initial-badge">初期HTML</span>
      <span class="initial-code">&lt;ul id="notices"&gt;&lt;li&gt;📌 通常のお知らせ A&lt;/li&gt; ...&lt;/ul&gt;</span>
    </div>
    <div class="output-card-body">
      <ul class="list-live" id="ib-list">
        <li id="ib-first">📌 通常のお知らせ A</li>
        <li>📌 通常のお知らせ B</li>
        <li>📌 通常のお知らせ C</li>
      </ul>
    </div>
  </div>
  <div class="demo-area">
    <div class="split">
      <div class="steps-panel">
        <div class="panel-lbl sp"><span class="dot"></span> 手順</div>
        <div class="explanation idle" id="ib-ex">
          <div class="ex-step">開始前</div>
          <div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
        </div>
        <div class="step-progress"><div class="step-progress-fill" id="ib-prog" style="width:0%"></div></div>
        <div class="step-controls" style="margin-top:10px;">
          <button class="btn-step" id="ib-next" onclick="step('ib')">次のステップへ →</button>
          <button class="btn-reset-s" onclick="reset('ib')">リセット</button>
          <span class="step-counter">ステップ <b id="ib-cur">0</b> / <b id="ib-tot"></b></span>
        </div>
      </div>
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl" id="ib-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">list</span>   = document.<span class="fn">querySelector</span>(<span class="str">'#notices'</span>);</span></div>
          <div class="cl" id="ib-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">first</span>  = list.<span class="fn">querySelector</span>(<span class="str">'li:first-child'</span>);</span></div>
          <div class="cl" id="ib-s2"><span class="ln">3</span><span class="lb"><span class="kw">const</span> <span class="vr">urgent</span> = document.<span class="fn">createElement</span>(<span class="str">'li'</span>);</span></div>
          <div class="cl" id="ib-s3"><span class="ln">4</span><span class="lb"><span class="vr">urgent</span>.<span class="pp">textContent</span> = <span class="str">'🚨 緊急メンテナンス 22:00〜'</span>;</span></div>
          <div class="cl" id="ib-s4"><span class="ln">5</span><span class="lb"><span class="vr">list</span>.<span class="fn">insertBefore</span>(<span class="vr">urgent</span>, <span class="vr">first</span>);</span></div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- removeChild -->
<div class="card container pad" id="card-rc">
  <div class="card-header">
    <div class="method-icon icon-manip">🗑️</div>
    <div class="card-header-text">
      <h3><span class="mname">removeChild()</span> — 子要素を削除</h3>
      <p>親要素から指定した子要素を取り除く。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「完了ボタンを押した<em>タスク行を消したい!</em>」→ removeChild</span>
  </div>
  <div class="output-card">
    <div class="output-card-label">出力 <span class="output-card-desc">— removeChild 後の &lt;ul&gt; の中身</span></div>
    <div class="output-initial">
      <span class="initial-badge">初期HTML</span>
      <span class="initial-code">&lt;ul&gt;&lt;li id="rc-1"&gt;📧 メールに返信&lt;/li&gt; ...&lt;/ul&gt;</span>
    </div>
    <div class="output-card-body">
      <ul class="list-live" id="rc-list">
        <li id="rc-1">📧 メールに返信</li>
        <li id="rc-2">📅 会議資料を準備</li>
        <li id="rc-3">📊 レポートを提出</li>
      </ul>
    </div>
  </div>
  <div class="demo-area">
    <div class="split">
      <div class="steps-panel">
        <div class="panel-lbl sp"><span class="dot"></span> 手順</div>
        <div class="explanation idle" id="rc-ex">
          <div class="ex-step">開始前</div>
          <div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
        </div>
        <div class="step-progress"><div class="step-progress-fill" id="rc-prog" style="width:0%"></div></div>
        <div class="step-controls" style="margin-top:10px;">
          <button class="btn-step" id="rc-next" onclick="step('rc')">次のステップへ →</button>
          <button class="btn-reset-s" onclick="reset('rc')">リセット</button>
          <span class="step-counter">ステップ <b id="rc-cur">0</b> / <b id="rc-tot"></b></span>
        </div>
      </div>
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl" id="rc-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">el</span>     = document.<span class="fn">getElementById</span>(<span class="str">'rc-1'</span>);</span></div>
          <div class="cl" id="rc-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">parent</span> = <span class="vr">el</span>.<span class="pp">parentElement</span>;</span></div>
          <div class="cl" id="rc-s2"><span class="ln">3</span><span class="lb"><span class="vr">parent</span>.<span class="fn">removeChild</span>(<span class="vr">el</span>);</span></div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- replaceChild -->
<div class="card container pad" id="card-rp">
  <div class="card-header">
    <div class="method-icon icon-manip">🔄</div>
    <div class="card-header-text">
      <h3><span class="mname">replaceChild()</span> — 別の要素と置き換え</h3>
      <p>既存の子要素を新しい要素と入れ替える。</p>
    </div>
  </div>
  <div class="usecase">
    <span class="usecase-text">💡 「「読み込み中」スピナーを<em>実際のカード</em>に差し替えたい!」→ replaceChild</span>
  </div>
  <div class="output-card">
    <div class="output-card-label">出力 <span class="output-card-desc">— replaceChild 前後のコンテンツエリア</span></div>
    <div class="output-initial">
      <span class="initial-badge">初期HTML</span>
      <span class="initial-code">&lt;div id="loading"&gt;⏳ データを読み込み中...&lt;/div&gt;</span>
    </div>
    <div class="output-card-body">
      <div id="rp-wrap">
        <div id="rp-loader" style="color:var(--text-light);font-size:0.86rem;">⏳ データを読み込み中...</div>
      </div>
    </div>
  </div>
  <div class="demo-area">
    <div class="split">
      <div class="steps-panel">
        <div class="panel-lbl sp"><span class="dot"></span> 手順</div>
        <div class="explanation idle" id="rp-ex">
          <div class="ex-step">開始前</div>
          <div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
        </div>
        <div class="step-progress"><div class="step-progress-fill" id="rp-prog" style="width:0%"></div></div>
        <div class="step-controls" style="margin-top:10px;">
          <button class="btn-step" id="rp-next" onclick="step('rp')">次のステップへ →</button>
          <button class="btn-reset-s" onclick="reset('rp')">リセット</button>
          <span class="step-counter">ステップ <b id="rp-cur">0</b> / <b id="rp-tot"></b></span>
        </div>
      </div>
      <div class="code-panel">
        <div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
        <div class="code-lines">
          <div class="cl" id="rp-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">wrap</span>   = document.<span class="fn">querySelector</span>(<span class="str">'#wrapper'</span>);</span></div>
          <div class="cl" id="rp-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">loader</span> = document.<span class="fn">querySelector</span>(<span class="str">'#loading'</span>);</span></div>
          <div class="cl" id="rp-s2"><span class="ln">3</span><span class="lb"><span class="kw">const</span> <span class="vr">card</span>   = document.<span class="fn">createElement</span>(<span class="str">'div'</span>);</span></div>
          <div class="cl" id="rp-s3"><span class="ln">4</span><span class="lb"><span class="vr">card</span>.<span class="pp">innerHTML</span> = <span class="str">'&lt;b&gt;👤 山田太郎&lt;/b&gt;...'</span>;</span></div>
          <div class="cl" id="rp-s4"><span class="ln">5</span><span class="lb"><span class="vr">wrap</span>.<span class="fn">replaceChild</span>(<span class="vr">card</span>, <span class="vr">loader</span>);</span></div>
        </div>
      </div>
    </div>
  </div>
</div>
</section>

<div style="height:60px;"></div>

<script>
// ================================================================
//  STEP DEFINITIONS
// ================================================================
const DEMOS = {};

// ── textContent ──
DEMOS['tc'] = {
  steps: [
    {
      sid: 'tc-s0',
      exStep: 'Step 1 — querySelector で要素を取得',
      exText: '<code>#status</code> セレクタに一致する要素を探して変数 <span class="val">el</span> に格納した。まだ出力は変わらない。'
    },
    {
      sid: 'tc-s1',
      exStep: 'Step 2 — textContent に代入',
      exText: '<span class="val">el.textContent</span> に文字列を代入した瞬間、ブラウザが自動でテキストを書き換える。HTMLタグは挿入できないが、その分安全。',
      action: () => {
        const el = document.getElementById('tc-out');
        el.textContent = 'データ取得しました ✓';
        el.style.color = 'var(--green)';
        el.style.fontWeight = '600';
      }
    }
  ],
  reset: () => {
    const el = document.getElementById('tc-out');
    el.textContent = '読み込み中...';
    el.style.color = '';
    el.style.fontWeight = '';
  }
};

// ── innerHTML ──
DEMOS['ih'] = {
  steps: [
    {
      sid: 'ih-s0',
      exStep: 'Step 1 — querySelector で要素を取得',
      exText: '書き換え先の要素を変数 <span class="val">card</span> に格納した。まだ出力は変わらない。'
    },
    {
      sid: 'ih-s1',
      exStep: 'Step 2 — innerHTML への代入が始まった',
      exText: '文字列の中に <span class="val">&lt;b&gt;</span> や <span class="val">&lt;em&gt;</span> などのHTMLタグが含まれている。これは textContent では不可能。'
    },
    {
      sid: 'ih-s2',
      exStep: 'Step 3 — 代入完了、HTMLとしてレンダリング',
      exText: 'ブラウザが文字列をHTMLとして解釈し描画した。<span class="val">&lt;b&gt;</span> は太字、<span class="val">&lt;em&gt;</span> はオレンジのイタリックとして表示されている。',
      action: () => {
        const el = document.getElementById('ih-out');
        el.innerHTML = '<b style="color:#2563EB;">🎉 成功!</b> ユーザー <em style="color:var(--orange);">@taro</em> がログインしました。';
      }
    }
  ],
  reset: () => {
    document.getElementById('ih-out').innerHTML = 'コンテンツなし';
  }
};

// ── tagName / id ── (no step engine — click shows result directly)
function probeClick(el) {
  const out = document.getElementById('tg-out');
  out.className = '';
  out.innerHTML =
    `<div style="font-family:'Fira Code',monospace;font-size:0.82rem;line-height:2.2;">` +
    `<div><span style="color:var(--text-light)">tag</span> = <span style="background:#1C1917;color:#FDE68A;padding:2px 8px;border-radius:4px;">"${el.tagName}"</span> &nbsp;<span style="color:var(--text-light);font-size:0.75rem;">← tagName には "${el.tagName}" が入っていた</span></div>` +
    `<div><span style="color:var(--text-light)">id &nbsp;</span>= <span style="background:#1C1917;color:#A3E635;padding:2px 8px;border-radius:4px;">"${el.id || '(なし)'}"</span> &nbsp;<span style="color:var(--text-light);font-size:0.75rem;">← id には "${el.id || '(なし)'}" が入っていた</span></div>` +
    `</div>`;

  const exEl = document.getElementById('tg-explain');
  exEl.className = 'explanation done-all';
  exEl.innerHTML =
    `<div class="ex-step">✅ クリック検出</div>` +
    `<div class="ex-text">` +
    `<span class="val">&lt;${el.tagName.toLowerCase()}&gt;</span> がクリックされた。` +
    `<span class="val">e.target.tagName</span> には <span class="val">"${el.tagName}"</span> が、` +
    `<span class="val">e.target.id</span> には <span class="val">"${el.id || '(なし)'}"</span> が入っている。` +
    `タグの種類によって if 分岐したいときに使う。` +
    `</div>`;
}

// ── querySelector ──
DEMOS['qs'] = {
  steps: [
    {
      sid: 'qs-s0',
      exStep: 'Step 1 — コメント確認',
      exText: '<span class="val">.submit-btn</span> クラスを持つ要素を探す。グレーの「送信」「キャンセル」ボタンはこのクラスを持たないので対象外になる。'
    },
    {
      sid: 'qs-s1',
      exStep: 'Step 2 — querySelector でスキャン',
      exText: 'ページを上から順にスキャンし、<span class="val">.submit-btn</span> に<b>最初にヒットした</b>「✉️ メイン送信」を変数 <span class="val">btn</span> に格納した。'
    },
    {
      sid: 'qs-s2',
      exStep: 'Step 3 — disabled = true',
      exText: '<span class="val">btn.disabled</span> に <span class="val">true</span> を代入した。出力のボタンがグレーになりクリック不可になった。',
      action: () => {
        const b = document.getElementById('qs-main');
        b.disabled = true;
        b.style.background = '#9CA3AF';
      }
    },
    {
      sid: 'qs-s3',
      exStep: 'Step 4 — textContent も変更',
      exText: '<span class="val">btn.textContent</span> を書き換えた。他の2つのボタンは <span class="val">.submit-btn</span> を持たないので無傷のまま。',
      action: () => {
        document.getElementById('qs-main').textContent = '送信済み ✓';
      }
    }
  ],
  reset: () => {
    const b = document.getElementById('qs-main');
    b.disabled = false; b.textContent = '✉️ メイン送信'; b.style.background = 'var(--orange)';
  }
};

// ── querySelectorAll ──
DEMOS['qsa'] = {
  steps: [
    {
      sid: 'qsa-s0',
      exStep: 'Step 1 — querySelectorAll 開始',
      exText: 'ページ全体をスキャンして条件に合う要素を<b>全件</b>集める。querySelector との違いは「1件だけ」か「全件」か。'
    },
    {
      sid: 'qsa-s1',
      exStep: 'Step 2 — 絞り込み完了',
      exText: '<span class="val">.task-item</span> を持ち、かつ <span class="val">.done</span> を持たない要素を抽出。「コードレビュー」「デプロイ」「ドキュメント更新」の3件がヒットした。'
    },
    {
      sid: 'qsa-s2',
      exStep: 'Step 3 — forEach ループ開始',
      exText: '取得した3件を1件ずつ処理するループが始まる。'
    },
    {
      sid: 'qsa-s3',
      exStep: 'Step 4 — 全件を透明化',
      exText: '3件全てに <span class="val">opacity: 0.3</span> を一気に適用した。出力のリストを確認しよう。',
      action: () => {
        document.querySelectorAll('#qsa-list .task-item:not(.done)').forEach(i => { i.style.opacity = '0.3'; });
      }
    },
    {
      sid: 'qsa-s4',
      exStep: 'Step 5 — 取り消し線を追加',
      exText: '3件全てに <span class="val">text-decoration: line-through</span> も適用。<b>querySelectorAll</b> だから全件まとめて処理できた。',
      action: () => {
        document.querySelectorAll('#qsa-list .task-item:not(.done)').forEach(i => { i.style.textDecoration = 'line-through'; });
      }
    }
  ],
  reset: () => {
    document.querySelectorAll('#qsa-list .task-item').forEach(i => { i.style.opacity = ''; i.style.textDecoration = ''; });
  }
};

// ── appendChild ──
DEMOS['ac'] = {
  steps: [
    {
      sid: 'ac-s0',
      exStep: 'Step 1 — 親の ul を取得',
      exText: '追加先のリスト(<span class="val">&lt;ul&gt;</span>)を変数 <span class="val">list</span> に格納した。出力のリストに変化はない。'
    },
    {
      sid: 'ac-s1',
      exStep: 'Step 2 — createElement で li を生成',
      exText: '<span class="val">createElement("li")</span> で新しい要素を「作成」したが、まだDOMに追加されていないので出力には現れない。',
      action: () => { window._acLi = document.createElement('li'); }
    },
    {
      sid: 'ac-s2',
      exStep: 'Step 3 — textContent をセット',
      exText: '生成した <span class="val">li</span> にテキストを設定した。まだ非表示。DOMに追加されて初めて見えるようになる。',
      action: () => { if (window._acLi) window._acLi.textContent = '新しいコメント 🗨️'; }
    },
    {
      sid: 'ac-s3',
      exStep: 'Step 4 — appendChild でDOMに追加',
      exText: '<span class="val">appendChild</span> を呼んだ瞬間にDOMに追加された。出力のリスト末尾に現れた!<span class="val">createElement → appendChild</span> の2段階が重要。',
      action: () => {
        const list = document.getElementById('ac-list');
        if (window._acLi) {
          window._acLi.classList.add('new-item');
          list.appendChild(window._acLi);
          setTimeout(() => { if (window._acLi) window._acLi.classList.remove('new-item'); }, 800);
        }
      }
    }
  ],
  reset: () => {
    window._acLi = null;
    document.getElementById('ac-list').innerHTML = '<li>最初のコメント 👋</li>';
  }
};

// ── insertBefore ──
DEMOS['ib'] = {
  steps: [
    {
      sid: 'ib-s0',
      exStep: 'Step 1 — 親の ul を取得',
      exText: '挿入先の親要素(<span class="val">&lt;ul&gt;</span>)を変数 <span class="val">list</span> に格納した。変化なし。'
    },
    {
      sid: 'ib-s1',
      exStep: 'Step 2 — 挿入位置の基準を取得',
      exText: '「通知A」を変数 <span class="val">first</span> に格納した。これが挿入位置の<b>基準点</b>になる。出力の「通知A」がハイライトされた。',
      action: () => {
        const f = document.getElementById('ib-first');
        if (f) { f.style.background = '#FEF3C7'; f.style.borderColor = 'var(--orange)'; }
      }
    },
    {
      sid: 'ib-s2',
      exStep: 'Step 3 — 新しい li を生成',
      exText: '<span class="val">createElement("li")</span> で緊急通知要素を生成。まだDOMには存在しない。',
      action: () => { window._ibUrgent = document.createElement('li'); }
    },
    {
      sid: 'ib-s3',
      exStep: 'Step 4 — テキストをセット',
      exText: '緊急通知のテキストを設定した。まだ非表示。',
      action: () => { if (window._ibUrgent) window._ibUrgent.textContent = '🚨 緊急メンテナンス 22:00〜'; }
    },
    {
      sid: 'ib-s4',
      exStep: 'Step 5 — insertBefore で挿入',
      exText: '<span class="val">insertBefore(urgent, first)</span> → <span class="val">first</span> の直前に <span class="val">urgent</span> が入った。<b>appendChild</b> なら末尾だが、<b>insertBefore</b> なら任意の位置に挿入できる。',
      action: () => {
        const list = document.getElementById('ib-list');
        const first = document.getElementById('ib-first');
        if (first) { first.style.background = ''; first.style.borderColor = ''; }
        if (window._ibUrgent && first) {
          window._ibUrgent.style.cssText = 'background:#FEF2F2;border-color:#FECACA;color:var(--red);font-weight:700;';
          list.insertBefore(window._ibUrgent, first);
        }
      }
    }
  ],
  reset: () => {
    window._ibUrgent = null;
    document.getElementById('ib-list').innerHTML = `<li id="ib-first">📌 通常のお知らせ A</li><li>📌 通常のお知らせ B</li><li>📌 通常のお知らせ C</li>`;
  }
};

// ── removeChild ──
DEMOS['rc'] = {
  steps: [
    {
      sid: 'rc-s0',
      exStep: 'Step 1 — 削除対象の要素を取得',
      exText: '<span class="val">getElementById("rc-1")</span> で「メールに返信」の <span class="val">&lt;li&gt;</span> を取得した。出力でハイライトされた。',
      action: () => {
        const el = document.getElementById('rc-1');
        if (el) { el.style.background = '#FEF3C7'; el.style.borderColor = 'var(--orange)'; }
      }
    },
    {
      sid: 'rc-s1',
      exStep: 'Step 2 — 親要素を取得',
      exText: '<span class="val">el.parentElement</span> で親の <span class="val">&lt;ul&gt;</span> を取得した。<b>removeChild は親から呼ぶ</b>必要があるため、先に親を取得しておく。'
    },
    {
      sid: 'rc-s2',
      exStep: 'Step 3 — removeChild で削除',
      exText: '<span class="val">parent.removeChild(el)</span> を実行した。DOMから完全に取り除かれ、出力のリストから消えた。',
      action: () => {
        const el = document.getElementById('rc-1');
        if (el && el.parentElement) el.parentElement.removeChild(el);
      }
    }
  ],
  reset: () => {
    document.getElementById('rc-list').innerHTML = `<li id="rc-1">📧 メールに返信</li><li id="rc-2">📅 会議資料を準備</li><li id="rc-3">📊 レポートを提出</li>`;
  }
};

// ── replaceChild ──
DEMOS['rp'] = {
  steps: [
    {
      sid: 'rp-s0',
      exStep: 'Step 1 — 親コンテナを取得',
      exText: '親の <span class="val">&lt;div&gt;</span> を変数 <span class="val">wrap</span> に格納した。変化なし。'
    },
    {
      sid: 'rp-s1',
      exStep: 'Step 2 — ローダー要素を取得',
      exText: '「読み込み中」要素を変数 <span class="val">loader</span> に格納した。これが<b>置き換えられる側</b>になる。',
      action: () => {
        const l = document.getElementById('rp-loader');
        if (l) { l.style.color = 'var(--orange)'; l.style.fontWeight = '600'; }
      }
    },
    {
      sid: 'rp-s2',
      exStep: 'Step 3 — 新しいカードを生成',
      exText: '<span class="val">createElement("div")</span> で新しいカード要素を生成した。まだDOMには存在しない。',
      action: () => {
        window._rpCard = document.createElement('div');
      }
    },
    {
      sid: 'rp-s3',
      exStep: 'Step 4 — カードに中身をセット',
      exText: '<span class="val">innerHTML</span> でリッチなカードの中身を設定した。まだ出力には現れない。',
      action: () => {
        if (window._rpCard) {
          window._rpCard.style.cssText = 'font-size:0.86rem;';
          window._rpCard.innerHTML = '<b style="color:#2563EB;">👤 山田 太郎</b><br><span style="color:var(--text-light);font-size:0.82rem;">エンジニア · 東京</span>';
        }
      }
    },
    {
      sid: 'rp-s4',
      exStep: 'Step 5 — replaceChild で差し替え',
      exText: '<span class="val">replaceChild(card, loader)</span> を実行。<span class="val">loader</span> が取り除かれ <span class="val">card</span> が同じ位置に挿入された。<b>removeChild + appendChild</b> を1行で行う操作。',
      action: () => {
        const wrap = document.getElementById('rp-wrap');
        const loader = document.getElementById('rp-loader');
        if (window._rpCard && loader) wrap.replaceChild(window._rpCard, loader);
      }
    }
  ],
  reset: () => {
    window._rpCard = null;
    document.getElementById('rp-wrap').innerHTML = '<div id="rp-loader" style="color:var(--text-light);font-size:0.86rem;">⏳ データを読み込み中...</div>';
  }
};

// ================================================================
//  STATE ENGINE
// ================================================================
const STATE = {};

function initState(id) {
  STATE[id] = { cur: -1, total: DEMOS[id].steps.length };
  document.getElementById(id + '-tot').textContent = DEMOS[id].steps.length;
  document.getElementById(id + '-cur').textContent = 0;
  updateProgress(id);
}

function resetState(id) {
  DEMOS[id].steps.forEach(s => {
    const el = document.getElementById(s.sid);
    if (el) el.classList.remove('cur', 'done');
  });
  STATE[id] = { cur: -1, total: DEMOS[id].steps.length };
  document.getElementById(id + '-cur').textContent = 0;
  updateProgress(id);
  setExIdle(id);
  const nb = document.getElementById(id + '-next');
  if (nb) nb.disabled = (id === 'tg');
}

function reset(id) {
  if (id === 'tg') {
    // tagName has no step engine — just reset output and explanation
    const out = document.getElementById('tg-out');
    out.className = 'out-placeholder';
    out.innerHTML = '← 上の要素をクリックすると tagName と id が表示される';
    const exEl = document.getElementById('tg-explain');
    exEl.className = 'explanation idle';
    exEl.innerHTML = '<div class="ex-step">解説</div><div class="ex-text">上の要素をクリックすると <span class="val">e.target.tagName</span> と <span class="val">e.target.id</span> がどんな値を持つか確認できる。</div>';
    return;
  }
  resetState(id);
  DEMOS[id].reset();
}

function step(id) {
  if (!STATE[id]) initState(id);
  const s = STATE[id];
  const d = DEMOS[id];
  if (s.cur >= d.steps.length - 1) return;

  // mark previous as done
  if (s.cur >= 0) {
    const prev = d.steps[s.cur];
    const pel = document.getElementById(prev.sid);
    if (pel) { pel.classList.remove('cur'); pel.classList.add('done'); }
  }

  s.cur++;
  const current = d.steps[s.cur];

  // highlight current line
  const cel = document.getElementById(current.sid);
  if (cel) cel.classList.add('cur');

  // run action
  if (current.action) current.action();

  // update explanation
  const exEl = document.getElementById(id + '-ex');
  const exStep = typeof current.exStep === 'function' ? current.exStep() : current.exStep;
  const exText = typeof current.exText === 'function' ? current.exText() : current.exText;
  const isLast = s.cur === d.steps.length - 1;
  exEl.className = 'explanation' + (isLast ? ' done-all' : '');
  exEl.innerHTML = `<div class="ex-step">${isLast ? '✅ ' : ''}${exStep}</div><div class="ex-text">${exText}</div>`;

  document.getElementById(id + '-cur').textContent = s.cur + 1;
  updateProgress(id);

  if (isLast) {
    const nb = document.getElementById(id + '-next');
    if (nb) nb.disabled = true;
    setTimeout(() => { if (cel) { cel.classList.remove('cur'); cel.classList.add('done'); } }, 300);
  }
}

function updateProgress(id) {
  const s = STATE[id] || { cur: -1, total: DEMOS[id].steps.length };
  const pct = s.total > 0 ? Math.round(((s.cur + 1) / s.total) * 100) : 0;
  const fill = document.getElementById(id + '-prog');
  if (fill) fill.style.width = pct + '%';
}

function setExIdle(id) {
  if (id === 'tg') return;
  const exEl = document.getElementById(id + '-ex');
  if (!exEl) return;
  exEl.className = 'explanation idle';
  exEl.innerHTML = `<div class="ex-step">開始前</div><div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>`;
}

// init totals
['tc','ih','qs','qsa','ac','ib','rc','rp'].forEach(id => {
  document.getElementById(id + '-tot').textContent = DEMOS[id].steps.length;
});
</script>
</body>
</html>
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?