コメントスレッド

ネストした返信といいね・返信ボタンを備えたコメント欄。長く伸びた返信スレッドは折りたたんでおき、必要なときだけ矢印から展開できます。

#css#js#chat

ライブデモ

コード

HTML
<!-- コメントスレッド: ネスト返信、いいね/返信ボタン、長い返信の折りたたみを備えたコメント欄 -->
<div class="stage">
  <section class="cct-panel" aria-label="コメントスレッド">
    <p class="cct-title">コメント <span class="cct-title__count">4</span></p>

    <ul class="cct-list" id="cctList">
      <li class="cct-item" data-comment>
        <div class="cct-row">
          <span class="cct-avatar" style="--c1:#fca5a5;--c2:#f97316" aria-hidden="true">健</span>
          <div class="cct-body">
            <p class="cct-meta"><strong class="cct-name">田村 健</strong><span class="cct-time">3時間前</span></p>
            <p class="cct-text">このデザイン、余白の使い方がすごく綺麗ですね。参考にさせてください!</p>
            <div class="cct-actions">
              <button type="button" class="cct-like" data-like aria-pressed="false">
                <span class="cct-like__icon" aria-hidden="true">♡</span>
                <span class="cct-like__count">12</span>
              </button>
              <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
            </div>

            <form class="cct-reply-form" data-reply-form hidden>
              <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
              <button type="submit" class="cct-reply-submit">送信</button>
            </form>

            <button type="button" class="cct-collapse" data-collapse-toggle aria-expanded="false">
              <span class="cct-collapse__chevron" aria-hidden="true">▾</span>
              3件の返信を表示
            </button>
            <div class="cct-collapsible" data-collapsible>
              <ul class="cct-replies" data-replies>
                <li class="cct-item cct-item--reply" data-comment>
                  <div class="cct-row">
                    <span class="cct-avatar cct-avatar--sm" style="--c1:#93c5fd;--c2:#3b82f6" aria-hidden="true">彩</span>
                    <div class="cct-body">
                      <p class="cct-meta"><strong class="cct-name">中村 彩</strong><span class="cct-time">2時間前</span></p>
                      <p class="cct-text">わかります、グリッドの揃え方が丁寧ですよね。</p>
                      <div class="cct-actions">
                        <button type="button" class="cct-like" data-like aria-pressed="false">
                          <span class="cct-like__icon" aria-hidden="true">♡</span>
                          <span class="cct-like__count">4</span>
                        </button>
                        <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
                      </div>
                      <form class="cct-reply-form" data-reply-form hidden>
                        <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
                        <button type="submit" class="cct-reply-submit">送信</button>
                      </form>
                      <ul class="cct-replies" data-replies>
                        <li class="cct-item cct-item--reply" data-comment>
                          <div class="cct-row">
                            <span class="cct-avatar cct-avatar--sm" style="--c1:#fca5a5;--c2:#f97316" aria-hidden="true">健</span>
                            <div class="cct-body">
                              <p class="cct-meta"><strong class="cct-name">田村 健</strong><span class="cct-time">2時間前</span></p>
                              <p class="cct-text">彩さんもありがとう、今度配色も見てほしいです</p>
                            </div>
                          </div>
                        </li>
                      </ul>
                    </div>
                  </div>
                </li>
                <li class="cct-item cct-item--reply" data-comment>
                  <div class="cct-row">
                    <span class="cct-avatar cct-avatar--sm" style="--c1:#86efac;--c2:#059669" aria-hidden="true">遼</span>
                    <div class="cct-body">
                      <p class="cct-meta"><strong class="cct-name">小林 遼</strong><span class="cct-time">1時間前</span></p>
                      <p class="cct-text">配色ももう少し見てみたいです、続編待ってます</p>
                      <div class="cct-actions">
                        <button type="button" class="cct-like" data-like aria-pressed="false">
                          <span class="cct-like__icon" aria-hidden="true">♡</span>
                          <span class="cct-like__count">2</span>
                        </button>
                        <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
                      </div>
                      <form class="cct-reply-form" data-reply-form hidden>
                        <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
                        <button type="submit" class="cct-reply-submit">送信</button>
                      </form>
                      <ul class="cct-replies" data-replies></ul>
                    </div>
                  </div>
                </li>
                <li class="cct-item cct-item--reply" data-comment>
                  <div class="cct-row">
                    <span class="cct-avatar cct-avatar--sm" style="--c1:#c4b5fd;--c2:#7c3aed" aria-hidden="true">舞</span>
                    <div class="cct-body">
                      <p class="cct-meta"><strong class="cct-name">松本 舞</strong><span class="cct-time">40分前</span></p>
                      <p class="cct-text">保存しました、資料作りの参考にします!</p>
                      <div class="cct-actions">
                        <button type="button" class="cct-like" data-like aria-pressed="false">
                          <span class="cct-like__icon" aria-hidden="true">♡</span>
                          <span class="cct-like__count">1</span>
                        </button>
                        <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
                      </div>
                      <form class="cct-reply-form" data-reply-form hidden>
                        <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
                        <button type="submit" class="cct-reply-submit">送信</button>
                      </form>
                      <ul class="cct-replies" data-replies></ul>
                    </div>
                  </div>
                </li>
              </ul>
            </div>
          </div>
        </div>
      </li>

      <li class="cct-item" data-comment>
        <div class="cct-row">
          <span class="cct-avatar" style="--c1:#7dd3fc;--c2:#0ea5e9" aria-hidden="true">葵</span>
          <div class="cct-body">
            <p class="cct-meta"><strong class="cct-name">石田 葵</strong><span class="cct-time">10分前</span></p>
            <p class="cct-text">配布素材はどこかで公開予定ですか?楽しみにしています。</p>
            <div class="cct-actions">
              <button type="button" class="cct-like" data-like aria-pressed="false">
                <span class="cct-like__icon" aria-hidden="true">♡</span>
                <span class="cct-like__count">0</span>
              </button>
              <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
            </div>
            <form class="cct-reply-form" data-reply-form hidden>
              <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
              <button type="submit" class="cct-reply-submit">送信</button>
            </form>
            <ul class="cct-replies" data-replies></ul>
          </div>
        </div>
      </li>
    </ul>
  </section>
</div>
CSS
/* コメントスレッド */
* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 28px;
  font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  background:
    radial-gradient(circle at 90% 6%, rgba(251, 146, 60, .13) 0%, transparent 42%),
    radial-gradient(circle at 6% 94%, rgba(56, 189, 248, .1) 0%, transparent 45%),
    #14100b;
  color: #f2ece4;
}

.cct-panel {
  width: min(420px, 92vw);
  max-height: 82vh;
  overflow-y: auto;
  border-radius: 20px;
  padding: 20px 18px 22px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .08);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .75);
}

.cct-title {
  margin: 0 0 14px;
  font-size: 14.5px;
  font-weight: 800;
}
.cct-title__count { color: rgba(242, 236, 228, .5); font-weight: 600; }

.cct-list, .cct-replies {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 16px;
}
.cct-list { gap: 20px; }

.cct-row { display: flex; gap: 10px; align-items: flex-start; }

.cct-avatar {
  --c1: #fca5a5;
  --c2: #f97316;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #1c1206;
  font-weight: 800;
  font-size: 12.5px;
  background: linear-gradient(135deg, var(--c1), var(--c2));
}
.cct-avatar--sm { width: 28px; height: 28px; font-size: 11px; }

.cct-body { flex: 1; min-width: 0; }

.cct-meta {
  margin: 0 0 4px;
  font-size: 12.5px;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.cct-name { color: #f2ece4; }
.cct-time { color: rgba(242, 236, 228, .42); font-size: 11px; font-weight: 400; }

.cct-text {
  margin: 0 0 8px;
  font-size: 13px;
  line-height: 1.7;
  color: rgba(242, 236, 228, .88);
}

.cct-actions { display: flex; align-items: center; gap: 14px; }

.cct-like {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: inherit;
  font-size: 12px;
  color: rgba(242, 236, 228, .55);
  background: none;
  border: none;
  padding: 2px 0;
  cursor: pointer;
  transition: color .15s ease, transform .15s ease;
}
.cct-like:hover { color: #fb7185; }
.cct-like__icon { font-size: 14px; transition: transform .25s cubic-bezier(.34, 1.56, .64, 1); }
.cct-like.is-liked { color: #fb7185; }
.cct-like.is-liked .cct-like__icon { transform: scale(1.15); }

.cct-reply-btn {
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  color: rgba(242, 236, 228, .55);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: color .15s ease;
}
.cct-reply-btn:hover { color: #fdba74; }

/* 返信フォーム */
.cct-reply-form {
  display: flex;
  gap: 8px;
  margin: 10px 0 0;
}
.cct-reply-form[hidden] { display: none; }
.cct-reply-input {
  flex: 1;
  min-width: 0;
  padding: 7px 12px;
  font: inherit;
  font-size: 12.5px;
  color: #f2ece4;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 999px;
}
.cct-reply-input:focus { outline: none; border-color: rgba(251, 146, 60, .5); }
.cct-reply-input::placeholder { color: rgba(242, 236, 228, .35); }
.cct-reply-submit {
  flex: none;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  padding: 7px 14px;
  border: none;
  border-radius: 999px;
  color: #1c1206;
  background: linear-gradient(135deg, #fdba74, #f97316);
  cursor: pointer;
  transition: transform .15s ease;
}
.cct-reply-submit:hover { transform: translateY(-1px); }
.cct-reply-submit:active { transform: scale(.95); }

/* 折りたたみトグル */
.cct-collapse {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 10px;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  color: #7dd3fc;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.cct-collapse__chevron { display: inline-block; transition: transform .3s cubic-bezier(.22, 1, .36, 1); }
.cct-collapse[aria-expanded="true"] .cct-collapse__chevron { transform: rotate(-180deg); }

/* grid-template-rows トリックで高さ自動アニメーション */
.cct-collapsible {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .4s cubic-bezier(.22, 1, .36, 1);
}
.cct-collapsible.is-open { grid-template-rows: 1fr; }
.cct-collapsible > .cct-replies {
  overflow: hidden;
  min-height: 0;
  margin-top: 14px;
  padding-left: 14px;
  border-left: 2px solid rgba(255, 255, 255, .08);
}

.cct-item--reply .cct-replies {
  margin-top: 12px;
  padding-left: 12px;
  border-left: 2px solid rgba(255, 255, 255, .06);
}

@media (prefers-reduced-motion: reduce) {
  .cct-collapsible, .cct-collapse__chevron, .cct-like__icon, .cct-reply-submit {
    transition: none;
  }
}

@media (max-width: 400px) {
  .cct-panel { padding: 16px 14px 18px; }
}
JavaScript
// コメントスレッド: いいねの切替、返信フォームの開閉と投稿、返信の折りたたみをイベント委譲で処理
(() => {
  const list = document.getElementById('cctList');
  if (!list) return; // null安全

  const AVATAR_NAME = 'あなた';
  const AVATAR_COLORS = ['#93c5fd', '#3b82f6'];

  const closestDirectReplies = (body) => {
    if (!body) return null;
    return (
      body.querySelector(':scope > .cct-collapsible > [data-replies]') ||
      body.querySelector(':scope > [data-replies]')
    );
  };

  list.addEventListener('click', (e) => {
    // いいねの切替
    const likeBtn = e.target.closest('[data-like]');
    if (likeBtn) {
      const countEl = likeBtn.querySelector('.cct-like__count');
      const iconEl = likeBtn.querySelector('.cct-like__icon');
      const liked = likeBtn.classList.toggle('is-liked');
      likeBtn.setAttribute('aria-pressed', String(liked));
      if (countEl) {
        const current = parseInt(countEl.textContent, 10) || 0;
        countEl.textContent = String(liked ? current + 1 : Math.max(0, current - 1));
      }
      if (iconEl) iconEl.textContent = liked ? '♥' : '♡';
      return;
    }

    // 返信フォームの開閉
    const replyToggle = e.target.closest('[data-reply-toggle]');
    if (replyToggle) {
      const body = replyToggle.closest('.cct-body');
      const formEl = body ? body.querySelector(':scope > [data-reply-form]') : null;
      if (!formEl) return;
      const willShow = formEl.hasAttribute('hidden');
      if (willShow) {
        formEl.removeAttribute('hidden');
        const input = formEl.querySelector('.cct-reply-input');
        if (input) input.focus();
      } else {
        formEl.setAttribute('hidden', '');
      }
      return;
    }

    // 長いスレッドの折りたたみ
    const collapseToggle = e.target.closest('[data-collapse-toggle]');
    if (collapseToggle) {
      const body = collapseToggle.closest('.cct-body');
      const box = body ? body.querySelector(':scope > .cct-collapsible') : null;
      if (!box) return;
      const opening = !box.classList.contains('is-open');
      box.classList.toggle('is-open', opening);
      collapseToggle.setAttribute('aria-expanded', String(opening));
      const label = collapseToggle.textContent.replace(/^\s*▾?\s*/, '').trim();
      collapseToggle.innerHTML = '';
      const chevronSpan = document.createElement('span');
      chevronSpan.className = 'cct-collapse__chevron';
      chevronSpan.setAttribute('aria-hidden', 'true');
      chevronSpan.textContent = '▾';
      collapseToggle.appendChild(chevronSpan);
      collapseToggle.appendChild(document.createTextNode(
        opening ? label.replace('を表示', 'を隠す') : label.replace('を隠す', 'を表示')
      ));
    }
  });

  list.addEventListener('submit', (e) => {
    const formEl = e.target.closest('[data-reply-form]');
    if (!formEl) return;
    e.preventDefault();

    const input = formEl.querySelector('.cct-reply-input');
    if (!input) return;
    const text = input.value.trim();
    if (!text) return;

    const body = formEl.closest('.cct-body');
    const repliesEl = closestDirectReplies(body);
    if (!repliesEl) return;

    const li = document.createElement('li');
    li.className = 'cct-item cct-item--reply';
    li.setAttribute('data-comment', '');
    li.innerHTML =
      '<div class="cct-row">' +
      '<span class="cct-avatar cct-avatar--sm" aria-hidden="true">あ</span>' +
      '<div class="cct-body">' +
      '<p class="cct-meta"><strong class="cct-name"></strong><span class="cct-time">たった今</span></p>' +
      '<p class="cct-text"></p>' +
      '</div></div>';
    li.querySelector('.cct-avatar').style.setProperty('--c1', AVATAR_COLORS[0]);
    li.querySelector('.cct-avatar').style.setProperty('--c2', AVATAR_COLORS[1]);
    li.querySelector('.cct-name').textContent = AVATAR_NAME;
    li.querySelector('.cct-text').textContent = text;
    repliesEl.appendChild(li);

    // 折りたたみ中だった場合は投稿した返信が見えるよう開く
    const box = repliesEl.closest('.cct-collapsible');
    if (box) {
      box.classList.add('is-open');
      const toggleBtn = body.querySelector(':scope > [data-collapse-toggle]');
      if (toggleBtn) toggleBtn.setAttribute('aria-expanded', 'true');
    }

    input.value = '';
    formEl.setAttribute('hidden', '');
  });
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「コメントスレッド」の効果を追加してください。

# 追加してほしい効果
コメントスレッド(チャット & メディア)
ネストした返信といいね・返信ボタンを備えたコメント欄。長く伸びた返信スレッドは折りたたんでおき、必要なときだけ矢印から展開できます。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- コメントスレッド: ネスト返信、いいね/返信ボタン、長い返信の折りたたみを備えたコメント欄 -->
<div class="stage">
  <section class="cct-panel" aria-label="コメントスレッド">
    <p class="cct-title">コメント <span class="cct-title__count">4</span></p>

    <ul class="cct-list" id="cctList">
      <li class="cct-item" data-comment>
        <div class="cct-row">
          <span class="cct-avatar" style="--c1:#fca5a5;--c2:#f97316" aria-hidden="true">健</span>
          <div class="cct-body">
            <p class="cct-meta"><strong class="cct-name">田村 健</strong><span class="cct-time">3時間前</span></p>
            <p class="cct-text">このデザイン、余白の使い方がすごく綺麗ですね。参考にさせてください!</p>
            <div class="cct-actions">
              <button type="button" class="cct-like" data-like aria-pressed="false">
                <span class="cct-like__icon" aria-hidden="true">♡</span>
                <span class="cct-like__count">12</span>
              </button>
              <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
            </div>

            <form class="cct-reply-form" data-reply-form hidden>
              <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
              <button type="submit" class="cct-reply-submit">送信</button>
            </form>

            <button type="button" class="cct-collapse" data-collapse-toggle aria-expanded="false">
              <span class="cct-collapse__chevron" aria-hidden="true">▾</span>
              3件の返信を表示
            </button>
            <div class="cct-collapsible" data-collapsible>
              <ul class="cct-replies" data-replies>
                <li class="cct-item cct-item--reply" data-comment>
                  <div class="cct-row">
                    <span class="cct-avatar cct-avatar--sm" style="--c1:#93c5fd;--c2:#3b82f6" aria-hidden="true">彩</span>
                    <div class="cct-body">
                      <p class="cct-meta"><strong class="cct-name">中村 彩</strong><span class="cct-time">2時間前</span></p>
                      <p class="cct-text">わかります、グリッドの揃え方が丁寧ですよね。</p>
                      <div class="cct-actions">
                        <button type="button" class="cct-like" data-like aria-pressed="false">
                          <span class="cct-like__icon" aria-hidden="true">♡</span>
                          <span class="cct-like__count">4</span>
                        </button>
                        <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
                      </div>
                      <form class="cct-reply-form" data-reply-form hidden>
                        <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
                        <button type="submit" class="cct-reply-submit">送信</button>
                      </form>
                      <ul class="cct-replies" data-replies>
                        <li class="cct-item cct-item--reply" data-comment>
                          <div class="cct-row">
                            <span class="cct-avatar cct-avatar--sm" style="--c1:#fca5a5;--c2:#f97316" aria-hidden="true">健</span>
                            <div class="cct-body">
                              <p class="cct-meta"><strong class="cct-name">田村 健</strong><span class="cct-time">2時間前</span></p>
                              <p class="cct-text">彩さんもありがとう、今度配色も見てほしいです</p>
                            </div>
                          </div>
                        </li>
                      </ul>
                    </div>
                  </div>
                </li>
                <li class="cct-item cct-item--reply" data-comment>
                  <div class="cct-row">
                    <span class="cct-avatar cct-avatar--sm" style="--c1:#86efac;--c2:#059669" aria-hidden="true">遼</span>
                    <div class="cct-body">
                      <p class="cct-meta"><strong class="cct-name">小林 遼</strong><span class="cct-time">1時間前</span></p>
                      <p class="cct-text">配色ももう少し見てみたいです、続編待ってます</p>
                      <div class="cct-actions">
                        <button type="button" class="cct-like" data-like aria-pressed="false">
                          <span class="cct-like__icon" aria-hidden="true">♡</span>
                          <span class="cct-like__count">2</span>
                        </button>
                        <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
                      </div>
                      <form class="cct-reply-form" data-reply-form hidden>
                        <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
                        <button type="submit" class="cct-reply-submit">送信</button>
                      </form>
                      <ul class="cct-replies" data-replies></ul>
                    </div>
                  </div>
                </li>
                <li class="cct-item cct-item--reply" data-comment>
                  <div class="cct-row">
                    <span class="cct-avatar cct-avatar--sm" style="--c1:#c4b5fd;--c2:#7c3aed" aria-hidden="true">舞</span>
                    <div class="cct-body">
                      <p class="cct-meta"><strong class="cct-name">松本 舞</strong><span class="cct-time">40分前</span></p>
                      <p class="cct-text">保存しました、資料作りの参考にします!</p>
                      <div class="cct-actions">
                        <button type="button" class="cct-like" data-like aria-pressed="false">
                          <span class="cct-like__icon" aria-hidden="true">♡</span>
                          <span class="cct-like__count">1</span>
                        </button>
                        <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
                      </div>
                      <form class="cct-reply-form" data-reply-form hidden>
                        <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
                        <button type="submit" class="cct-reply-submit">送信</button>
                      </form>
                      <ul class="cct-replies" data-replies></ul>
                    </div>
                  </div>
                </li>
              </ul>
            </div>
          </div>
        </div>
      </li>

      <li class="cct-item" data-comment>
        <div class="cct-row">
          <span class="cct-avatar" style="--c1:#7dd3fc;--c2:#0ea5e9" aria-hidden="true">葵</span>
          <div class="cct-body">
            <p class="cct-meta"><strong class="cct-name">石田 葵</strong><span class="cct-time">10分前</span></p>
            <p class="cct-text">配布素材はどこかで公開予定ですか?楽しみにしています。</p>
            <div class="cct-actions">
              <button type="button" class="cct-like" data-like aria-pressed="false">
                <span class="cct-like__icon" aria-hidden="true">♡</span>
                <span class="cct-like__count">0</span>
              </button>
              <button type="button" class="cct-reply-btn" data-reply-toggle>返信</button>
            </div>
            <form class="cct-reply-form" data-reply-form hidden>
              <input type="text" class="cct-reply-input" placeholder="返信を入力…" aria-label="返信を入力">
              <button type="submit" class="cct-reply-submit">送信</button>
            </form>
            <ul class="cct-replies" data-replies></ul>
          </div>
        </div>
      </li>
    </ul>
  </section>
</div>

【CSS】
/* コメントスレッド */
* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 28px;
  font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  background:
    radial-gradient(circle at 90% 6%, rgba(251, 146, 60, .13) 0%, transparent 42%),
    radial-gradient(circle at 6% 94%, rgba(56, 189, 248, .1) 0%, transparent 45%),
    #14100b;
  color: #f2ece4;
}

.cct-panel {
  width: min(420px, 92vw);
  max-height: 82vh;
  overflow-y: auto;
  border-radius: 20px;
  padding: 20px 18px 22px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .08);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .75);
}

.cct-title {
  margin: 0 0 14px;
  font-size: 14.5px;
  font-weight: 800;
}
.cct-title__count { color: rgba(242, 236, 228, .5); font-weight: 600; }

.cct-list, .cct-replies {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 16px;
}
.cct-list { gap: 20px; }

.cct-row { display: flex; gap: 10px; align-items: flex-start; }

.cct-avatar {
  --c1: #fca5a5;
  --c2: #f97316;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #1c1206;
  font-weight: 800;
  font-size: 12.5px;
  background: linear-gradient(135deg, var(--c1), var(--c2));
}
.cct-avatar--sm { width: 28px; height: 28px; font-size: 11px; }

.cct-body { flex: 1; min-width: 0; }

.cct-meta {
  margin: 0 0 4px;
  font-size: 12.5px;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.cct-name { color: #f2ece4; }
.cct-time { color: rgba(242, 236, 228, .42); font-size: 11px; font-weight: 400; }

.cct-text {
  margin: 0 0 8px;
  font-size: 13px;
  line-height: 1.7;
  color: rgba(242, 236, 228, .88);
}

.cct-actions { display: flex; align-items: center; gap: 14px; }

.cct-like {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: inherit;
  font-size: 12px;
  color: rgba(242, 236, 228, .55);
  background: none;
  border: none;
  padding: 2px 0;
  cursor: pointer;
  transition: color .15s ease, transform .15s ease;
}
.cct-like:hover { color: #fb7185; }
.cct-like__icon { font-size: 14px; transition: transform .25s cubic-bezier(.34, 1.56, .64, 1); }
.cct-like.is-liked { color: #fb7185; }
.cct-like.is-liked .cct-like__icon { transform: scale(1.15); }

.cct-reply-btn {
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  color: rgba(242, 236, 228, .55);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: color .15s ease;
}
.cct-reply-btn:hover { color: #fdba74; }

/* 返信フォーム */
.cct-reply-form {
  display: flex;
  gap: 8px;
  margin: 10px 0 0;
}
.cct-reply-form[hidden] { display: none; }
.cct-reply-input {
  flex: 1;
  min-width: 0;
  padding: 7px 12px;
  font: inherit;
  font-size: 12.5px;
  color: #f2ece4;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 999px;
}
.cct-reply-input:focus { outline: none; border-color: rgba(251, 146, 60, .5); }
.cct-reply-input::placeholder { color: rgba(242, 236, 228, .35); }
.cct-reply-submit {
  flex: none;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  padding: 7px 14px;
  border: none;
  border-radius: 999px;
  color: #1c1206;
  background: linear-gradient(135deg, #fdba74, #f97316);
  cursor: pointer;
  transition: transform .15s ease;
}
.cct-reply-submit:hover { transform: translateY(-1px); }
.cct-reply-submit:active { transform: scale(.95); }

/* 折りたたみトグル */
.cct-collapse {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 10px;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  color: #7dd3fc;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.cct-collapse__chevron { display: inline-block; transition: transform .3s cubic-bezier(.22, 1, .36, 1); }
.cct-collapse[aria-expanded="true"] .cct-collapse__chevron { transform: rotate(-180deg); }

/* grid-template-rows トリックで高さ自動アニメーション */
.cct-collapsible {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .4s cubic-bezier(.22, 1, .36, 1);
}
.cct-collapsible.is-open { grid-template-rows: 1fr; }
.cct-collapsible > .cct-replies {
  overflow: hidden;
  min-height: 0;
  margin-top: 14px;
  padding-left: 14px;
  border-left: 2px solid rgba(255, 255, 255, .08);
}

.cct-item--reply .cct-replies {
  margin-top: 12px;
  padding-left: 12px;
  border-left: 2px solid rgba(255, 255, 255, .06);
}

@media (prefers-reduced-motion: reduce) {
  .cct-collapsible, .cct-collapse__chevron, .cct-like__icon, .cct-reply-submit {
    transition: none;
  }
}

@media (max-width: 400px) {
  .cct-panel { padding: 16px 14px 18px; }
}

【JavaScript】
// コメントスレッド: いいねの切替、返信フォームの開閉と投稿、返信の折りたたみをイベント委譲で処理
(() => {
  const list = document.getElementById('cctList');
  if (!list) return; // null安全

  const AVATAR_NAME = 'あなた';
  const AVATAR_COLORS = ['#93c5fd', '#3b82f6'];

  const closestDirectReplies = (body) => {
    if (!body) return null;
    return (
      body.querySelector(':scope > .cct-collapsible > [data-replies]') ||
      body.querySelector(':scope > [data-replies]')
    );
  };

  list.addEventListener('click', (e) => {
    // いいねの切替
    const likeBtn = e.target.closest('[data-like]');
    if (likeBtn) {
      const countEl = likeBtn.querySelector('.cct-like__count');
      const iconEl = likeBtn.querySelector('.cct-like__icon');
      const liked = likeBtn.classList.toggle('is-liked');
      likeBtn.setAttribute('aria-pressed', String(liked));
      if (countEl) {
        const current = parseInt(countEl.textContent, 10) || 0;
        countEl.textContent = String(liked ? current + 1 : Math.max(0, current - 1));
      }
      if (iconEl) iconEl.textContent = liked ? '♥' : '♡';
      return;
    }

    // 返信フォームの開閉
    const replyToggle = e.target.closest('[data-reply-toggle]');
    if (replyToggle) {
      const body = replyToggle.closest('.cct-body');
      const formEl = body ? body.querySelector(':scope > [data-reply-form]') : null;
      if (!formEl) return;
      const willShow = formEl.hasAttribute('hidden');
      if (willShow) {
        formEl.removeAttribute('hidden');
        const input = formEl.querySelector('.cct-reply-input');
        if (input) input.focus();
      } else {
        formEl.setAttribute('hidden', '');
      }
      return;
    }

    // 長いスレッドの折りたたみ
    const collapseToggle = e.target.closest('[data-collapse-toggle]');
    if (collapseToggle) {
      const body = collapseToggle.closest('.cct-body');
      const box = body ? body.querySelector(':scope > .cct-collapsible') : null;
      if (!box) return;
      const opening = !box.classList.contains('is-open');
      box.classList.toggle('is-open', opening);
      collapseToggle.setAttribute('aria-expanded', String(opening));
      const label = collapseToggle.textContent.replace(/^\s*▾?\s*/, '').trim();
      collapseToggle.innerHTML = '';
      const chevronSpan = document.createElement('span');
      chevronSpan.className = 'cct-collapse__chevron';
      chevronSpan.setAttribute('aria-hidden', 'true');
      chevronSpan.textContent = '▾';
      collapseToggle.appendChild(chevronSpan);
      collapseToggle.appendChild(document.createTextNode(
        opening ? label.replace('を表示', 'を隠す') : label.replace('を隠す', 'を表示')
      ));
    }
  });

  list.addEventListener('submit', (e) => {
    const formEl = e.target.closest('[data-reply-form]');
    if (!formEl) return;
    e.preventDefault();

    const input = formEl.querySelector('.cct-reply-input');
    if (!input) return;
    const text = input.value.trim();
    if (!text) return;

    const body = formEl.closest('.cct-body');
    const repliesEl = closestDirectReplies(body);
    if (!repliesEl) return;

    const li = document.createElement('li');
    li.className = 'cct-item cct-item--reply';
    li.setAttribute('data-comment', '');
    li.innerHTML =
      '<div class="cct-row">' +
      '<span class="cct-avatar cct-avatar--sm" aria-hidden="true">あ</span>' +
      '<div class="cct-body">' +
      '<p class="cct-meta"><strong class="cct-name"></strong><span class="cct-time">たった今</span></p>' +
      '<p class="cct-text"></p>' +
      '</div></div>';
    li.querySelector('.cct-avatar').style.setProperty('--c1', AVATAR_COLORS[0]);
    li.querySelector('.cct-avatar').style.setProperty('--c2', AVATAR_COLORS[1]);
    li.querySelector('.cct-name').textContent = AVATAR_NAME;
    li.querySelector('.cct-text').textContent = text;
    repliesEl.appendChild(li);

    // 折りたたみ中だった場合は投稿した返信が見えるよう開く
    const box = repliesEl.closest('.cct-collapsible');
    if (box) {
      box.classList.add('is-open');
      const toggleBtn = body.querySelector(':scope > [data-collapse-toggle]');
      if (toggleBtn) toggleBtn.setAttribute('aria-expanded', 'true');
    }

    input.value = '';
    formEl.setAttribute('hidden', '');
  });
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。