<!DOCTYPE html>

<html lang="ja">
  <head>
    <link rel="stylesheet" href="lib/style.css" />
  </head>

  <body>
    <button id="remover">「すべての」イベントリスナーを破棄してみましょう</button>
    <main>
      <section class="card-container"></section>
    </main>
    <dialog>
      <p></p>
      <button>OK</button>
    </dialog>
    <script type="module" src="lib/script.js"></script>
  </body>
</html>
body {
  margin: 0;
  padding: 0;
  font-size: 1rem;
  line-height: 1.2;
  min-height: 100vh;
}

main {
  background-image: repeating-linear-gradient(
    #fff,
    #fff 5px,
    #fbb 5px,
    #fbb 10px
  );
  height: 100%;
}

.card-container {
  margin: 0;
  padding: 0;

  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr));
  gap: 40px;
  background-color: rgb(0, 0, 0, 0);
}

.card {
  cursor: pointer;
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  padding-block: 12px;
  row-gap: 0.8rem;
  background: rgba(255, 255, 255, 0.3);
  box-shadow: 5px 5px 8px 0 rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  transform: translateZ(0);
}

.card h1 {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  width: 100%;
  margin: 0;
}

.card h1 .close {
  width: 1em;
  height: 1em;
  display: inline-block;
  line-height: 1;
  background-color: #fff;
}
.card img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  filter: sepia(20%);
}

.card p {
  margin: 0;
}

html:has(dialog[open]) {
  overflow: hidden;
}

dialog {
  border: none;
}

dialog::backdrop {
  background-color: #0008;
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
const response = await fetch('api/card.json');
const articles = await response.json();

/** 閉じたカードのキャッシュ */
const closedCards = [];

/** クリック対象のすべてのカードのコンテナ */
const sectionNode = document.querySelector('section.card-container');
/** 「ダイアログ」APIを持つ要素 */
const dialogNode = document.querySelector('dialog');
/** 「ダイアログを閉じる」ボタン要素 */
const closeModalNode = dialogNode.querySelector('button');
// 「ダイアログを閉じる」ボタンに対するクリックイベント
closeModalNode.addEventListener('click', onCloseModal);

/** ダイアログを開くためのイベントを破棄するボタン */
const remover = document.querySelector('#remover');
// 「ダイアログを閉じる」ボタンに対するクリックイベント
remover.addEventListener('click', onClickRemove);

// articles を順番に処理
for (const article of articles) {
  /** 各カードの要素 */
  const articleNode = document.createElement('article');
  articleNode.classList = ['card'];

  /** 各カードの innerHTML */
  const template = `
  <h1>${article.title}<img class="close" src="lib/cross.svg" alt=""></h1>
  <img loading="lazy" src="https://${article.image}" width="360" height="190" alt="">
  <div>
    <p>${article.text}</p>
  </div>
`;
  articleNode.innerHTML = template;
  // 詳細情報をカード要素のdatasetに持たせる
  articleNode.dataset.detail = article.detail;
  // 閉じるボタン
  const closeNode = articleNode.querySelector('.close');
  closeNode.addEventListener('click', onClickClose);
  // カード要素に対するクリックイベント
  articleNode.addEventListener('click', onClickCard);

  // 要素を、コンテナのDOMツリーにぶら下げる
  sectionNode.append(articleNode);
}

/**
 * カードを閉じる
 */
function onClickClose(e) {
  e.stopPropagation();
  const articleNode = e.target.closest('article.card');
  sectionNode.removeChild(articleNode);

  // 閉じたカードへの参照を追加
  closedCards.push(articleNode);
}

function onClickCard() {
  dialogNode.querySelector('p').innerText = this.dataset.detail;
  dialogNode.showModal();
}

function onCloseModal() {
  dialogNode.querySelector('p').innerText = '';
  dialogNode.close();
}

function onClickRemove() {
  remover.removeEventListener('click', onClickRemove);
  // articles 内を順番に、イベント破棄する
  for (const articleNode of sectionNode.childNodes) {
    const closeNode = articleNode.querySelector('.close');
    closeNode.removeEventListener('click', onClickClose);
    articleNode.removeEventListener('click', onClickCard);
  }

  // ここにブレークポイントを張って、 `getEventListeners(closedCards[0])` をコンソールから実行してみましょう!
  closeModalNode.removeEventListener('click', onCloseModal);
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><defs><style>.cls-1,.cls-2{fill:none;}.cls-2{stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style></defs><title>cross_24</title><g id="レイヤー_2" data-name="レイヤー 2"><g id="Rectangle"><rect class="cls-1" width="48" height="48"/></g><g id="icon_data"><line class="cls-2" x1="12.13" y1="12.63" x2="36.13" y2="36.13"/><line class="cls-2" x1="12.38" y1="36.38" x2="35.88" y2="12.38"/></g></g></svg>
[
  {
    "title": "カードのタイトル",
    "image": "picsum.photos/360/190/",
    "text": "今日は何もしない、いい一日だった。",
    "detail": "2010年の秋に撮影した写真です。なかなかいい感じに撮れているんじゃないかなあ。"
  },
  {
    "title": "前十七等官 レオーノ・キュースト",
    "image": "picsum.photos/360/190/",
    "text": "そのころわたくしは、モリーオ市の博物局に勤めて居りました。",
    "detail": "十八等官でしたから役所のなかでも、ずうっと下の方でしたし俸給(ほうきゅう)もほんのわずかでしたが、受持ちが標本の採集や整理で生れ付き好きなことでしたから"
  },
  {
    "title": "わたくしは毎日",
    "image": "picsum.photos/360/190/",
    "text": "ずいぶん愉快にはたらきました。",
    "detail": "殊にそのころ、モリーオ市では競馬場を植物園に拵こしらえ直すというので、その景色のいいまわりにアカシヤを植え込んだ広い地面が、切符売場や信号所の建物のついたまま、わたくしどもの役所の方へまわって来たものですから"
  },
  {
    "title": "わたくしはすぐ",
    "image": "picsum.photos/360/190/",
    "text": "宿直という名前で月賦で買った小さな蓄音器と二十枚ばかりのレコードをもって、その番小屋にひとり住むことになりました。",
    "detail": "わたくしはそこの馬を置く場所に板で小さなしきいをつけて一疋の山羊を飼いました。"
  },
  {
    "title": "毎朝その乳をしぼってつめたいパンをひたしてたべ",
    "image": "picsum.photos/360/190/",
    "text": "それから黒い革のかばんへすこしの書類や雑誌を入れ、靴もきれいにみがき",
    "detail": "並木のポプラの影法師を大股にわたって市の役所へ出て行くのでした。"
  },
  {
    "title": "あのイーハトーヴォのすきとおった風",
    "image": "picsum.photos/360/190/",
    "text": "夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。",
    "detail": "またそのなかでいっしょになったたくさんのひとたち、ファゼーロとロザーロ、羊飼のミーロや、顔の赤いこどもたち、地主のテーモ、山猫博士のボーガント・デストゥパーゴなど"
  },
  {
    "title": "いまこの暗い",
    "image": "picsum.photos/360/190/",
    "text": "巨きな石の建物のなかで考えていると",
    "detail": "みんなむかし風のなつかしい青い幻燈のように思われます。"
  },
  {
    "title": "では",
    "image": "picsum.photos/360/190/",
    "text": "わたくしはいつかの小さなみだしをつけながら",
    "detail": "しずかにあの年のイーハトーヴォの五月から十月までを書きつけましょう。"
  },
  {
    "title": "遁げた山羊",
    "image": "picsum.photos/360/190/",
    "text": "五月のしまいの日曜でした。わたくしは賑(にぎ)やかな市の教会の鐘の音で眼をさましました。",
    "detail": "もう日はよほど登って、まわりはみんなきらきらしていました。時計を見るとちょうど六時でした。わたくしはすぐチョッキだけ着て山羊を見に行きました。"
  },
  {
    "title": "すると小屋のなかはしんとして",
    "image": "picsum.photos/360/190/",
    "text": "藁(わら)が凹んでいるだけで、あのみじかい角も白い髯も見えませんでした。",
    "detail": "「あんまりいい天気なもんだから大将ひとりででかけたな。」わたくしは半分わらうように半分つぶやくようにしながら、向うの信号所からいつも放して遊ばせる輪道の内側の野原、ポプラの中から顔をだしている市はずれの白い教会の塔までぐるっと見まわしました。"
  },
  {
    "title": "けれどもどこにも",
    "image": "picsum.photos/360/190/",
    "text": "あの白い頭もせなかも見えていませんでした。",
    "detail": "うまやを一まわりしてみましたがやっぱりどこにも居ませんでした。"
  },
  {
    "title": "「いったい山羊は馬だの犬のように",
    "image": "picsum.photos/360/190/",
    "text": "前居たところや来る道をおぼえていて、そこへ戻っているということがあるのかなあ。」",
    "detail": "わたくしはひとりで考えました。さあ、そう思うと早くそれを知りたくてたまらなくなりました。けれども役所のなかとちがって競馬場には物知りの年とった書記も居なければ、そんなことを書いた辞書もそこらにありませんでしたから、わたくしは何ということなしに輪道を半分通って、それからこの前山羊が村の人に連れられて来た路をそのまま野原の方へあるきだしました。"
  }
]