リッチポップオーバー

文中の語句にカーソルを合わせてしばらく待つと、写真・見出し・説明・CTAを備えた大きめのカードがふわりと現れます。クリックで固定表示もできます。

#css#js#popover

ライブデモ

コード

HTML
<!-- リッチポップオーバー: 文中の語句にhoverしてしばらく待つと画像+見出し+説明+CTAの大きめカードが現れる。クリックで開いたまま固定も可能 -->
<section class="rp-stage" aria-label="リッチポップオーバーの見本">
  <p class="rp-eyebrow">RICH POPOVER</p>
  <h2 class="rp-title">気になる語句に、そっと詳細を</h2>
  <p class="rp-lead">下線の語句にカーソルを合わせてしばらく待つと、詳しいカードが現れます。クリックで固定表示にもできます。</p>

  <p class="rp-copy">
    今週のおすすめは
    <button type="button" class="rp-trigger" aria-haspopup="dialog" aria-expanded="false" aria-controls="rp-pop-coat">アトリエ限定コート</button>
    と
    <button type="button" class="rp-trigger" aria-haspopup="dialog" aria-expanded="false" aria-controls="rp-pop-paper">手漉き和紙ノート</button>
    です。
  </p>

  <div class="rp-pop" id="rp-pop-coat" role="dialog" aria-modal="false" aria-labelledby="rp-pop-coat-title" aria-hidden="true">
    <div class="rp-pop__art" aria-hidden="true"><span>仕</span></div>
    <div class="rp-pop__body">
      <h3 class="rp-pop__title" id="rp-pop-coat-title">アトリエ限定コート</h3>
      <p class="rp-pop__desc">職人が一着ずつ仕立てるウールコート。裏地には季節の柄をそっと忍ばせました。</p>
      <a class="rp-pop__cta" href="#" tabindex="-1">詳しく見る →</a>
    </div>
  </div>

  <div class="rp-pop" id="rp-pop-paper" role="dialog" aria-modal="false" aria-labelledby="rp-pop-paper-title" aria-hidden="true">
    <div class="rp-pop__art rp-pop__art--alt" aria-hidden="true"><span>紙</span></div>
    <div class="rp-pop__body">
      <h3 class="rp-pop__title" id="rp-pop-paper-title">手漉き和紙ノート</h3>
      <p class="rp-pop__desc">越前の職人による手漉き和紙を表紙に使用。使うほどに風合いが増していきます。</p>
      <a class="rp-pop__cta" href="#" tabindex="-1">詳しく見る →</a>
    </div>
  </div>
</section>
CSS
/* リッチポップオーバー: 生成りの読み物カードから、遅延表示の大きめプレビューが浮かぶ */
:root{
  --bg-1:#f7f1e6;
  --bg-2:#ecdfc6;
  --surface:#fffdf9;
  --line:#e6dac2;
  --ink:#2a2420;
  --muted:#8b7d6a;
  --gold:#a9803f;
  --gold-soft:#caa25f;
}
*{box-sizing:border-box}
body{
  margin:0;
  min-height:100vh;
  display:grid;
  place-items:center;
  padding:44px 20px;
  overflow-x:hidden;
  overflow-y:auto;
  font-family:"Segoe UI",system-ui,-apple-system,"Hiragino Kaku Gothic ProN",sans-serif;
  color:var(--ink);
  background:
    radial-gradient(560px 300px at 12% 0%,#fffaf0 0%,transparent 60%),
    radial-gradient(520px 280px at 100% 100%,#f2e2c4 0%,transparent 55%),
    linear-gradient(180deg,var(--bg-1),var(--bg-2));
}

.rp-stage{
  position:relative;
  width:min(540px,100%);
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:20px;
  box-shadow:0 26px 60px -30px rgba(60,42,10,.35),0 2px 10px rgba(60,42,10,.06);
  padding:34px 32px 40px;
}
.rp-eyebrow{
  margin:0 0 6px;
  font-size:.7rem;
  letter-spacing:.16em;
  color:var(--gold);
  font-weight:700;
  text-align:center;
}
.rp-title{
  margin:0 0 10px;
  font-size:1.28rem;
  text-align:center;
  font-family:Georgia,"Yu Mincho","Hiragino Mincho ProN",serif;
}
.rp-lead{
  margin:0 0 22px;
  font-size:.85rem;
  line-height:1.6;
  color:var(--muted);
  text-align:center;
}
.rp-copy{
  margin:0;
  font-size:1rem;
  line-height:2.1;
  text-align:center;
}

.rp-trigger{
  all:unset;
  display:inline;
  cursor:pointer;
  color:var(--gold);
  font-weight:700;
  border-bottom:1px dashed var(--gold-soft);
  padding:0 1px;
  transition:color .15s ease,border-color .15s ease;
}
.rp-trigger:hover,
.rp-trigger:focus-visible,
.rp-trigger[aria-expanded="true"]{color:var(--ink);border-bottom-color:var(--ink)}
.rp-trigger:focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:2px}

.rp-pop{
  position:absolute;
  top:0;
  left:0;
  width:min(300px,78vw);
  display:flex;
  gap:14px;
  padding:16px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:16px;
  box-shadow:0 30px 60px -24px rgba(40,28,8,.4),0 4px 14px rgba(40,28,8,.08);
  opacity:0;
  transform:translate(-50%,8px);
  pointer-events:none;
  transition:opacity .22s ease,transform .3s cubic-bezier(.22,1,.36,1);
  z-index:40;
}
.rp-pop.is-open{opacity:1;pointer-events:auto;transform:translate(-50%,0)}
.rp-pop::before{
  content:"";
  position:absolute;
  top:-6px;
  left:calc(50% + var(--arrow-x, 0px));
  transform:translateX(-50%);
  border:6px solid transparent;
  border-bottom-color:var(--surface);
}

.rp-pop__art{
  flex:0 0 60px;
  width:60px;height:60px;
  border-radius:12px;
  display:grid;place-items:center;
  background:linear-gradient(155deg,#3a2c1c,#1c140c);
  color:var(--gold-soft);
  font-family:Georgia,"Yu Mincho",serif;
  font-size:1.3rem;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.05);
}
.rp-pop__art--alt{background:linear-gradient(155deg,#233226,#12190f);color:#bcd1a8}

.rp-pop__body{flex:1;min-width:0;text-align:left}
.rp-pop__title{margin:0 0 4px;font-size:.96rem;font-family:Georgia,"Yu Mincho",serif}
.rp-pop__desc{margin:0 0 10px;font-size:.78rem;line-height:1.6;color:var(--muted)}
.rp-pop__cta{
  display:inline-flex;align-items:center;gap:4px;
  font-size:.78rem;font-weight:700;color:var(--gold);
  text-decoration:none;border-bottom:1px solid transparent;
  transition:border-color .15s ease,color .15s ease;
}
.rp-pop__cta:hover,.rp-pop__cta:focus-visible{color:var(--ink);border-bottom-color:var(--ink)}
.rp-pop__cta:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:2px}

@media (prefers-reduced-motion:reduce){
  .rp-pop{transition:opacity .15s ease}
}
JavaScript
// リッチポップオーバー: hoverから少し遅れて画像+見出し+説明+CTAの大きめカードを表示。
// クリックで開閉を固定(タッチ対応)、フォーカスでも表示、Escや外側クリックで閉じる。
(() => {
  const stage = document.querySelector('.rp-stage');
  if (!stage) return; // null安全

  const triggers = Array.from(stage.querySelectorAll('.rp-trigger'));
  if (!triggers.length) return;

  const SHOW_DELAY = 380;
  const HIDE_DELAY = 220;

  let showTimer = null;
  let hideTimer = null;
  let current = null; // { trigger, pop, pinned }

  const clearTimers = () => {
    window.clearTimeout(showTimer);
    window.clearTimeout(hideTimer);
  };

  const position = (trigger, pop) => {
    const tr = trigger.getBoundingClientRect();
    const sr = stage.getBoundingClientRect();
    const top = tr.bottom - sr.top + 14;
    const centerLeft = tr.left - sr.left + tr.width / 2;
    const popRect = pop.getBoundingClientRect();
    const popWidth = popRect.width || 300;
    const margin = 14;
    const minLeft = popWidth / 2 + margin;
    const maxLeft = Math.max(minLeft, sr.width - popWidth / 2 - margin);
    const clampedLeft = Math.min(Math.max(centerLeft, minLeft), maxLeft);
    const arrowHalf = Math.max(popWidth / 2 - 18, 0);
    const arrowOffset = Math.min(Math.max(centerLeft - clampedLeft, -arrowHalf), arrowHalf);
    pop.style.top = `${top}px`;
    pop.style.left = `${clampedLeft}px`;
    pop.style.setProperty('--arrow-x', `${arrowOffset}px`);
  };

  const closeCurrent = () => {
    if (!current) return;
    const { trigger, pop } = current;
    if (pop.contains(document.activeElement)) document.activeElement.blur();
    pop.classList.remove('is-open');
    pop.setAttribute('aria-hidden', 'true');
    trigger.setAttribute('aria-expanded', 'false');
    const cta = pop.querySelector('.rp-pop__cta');
    if (cta) cta.tabIndex = -1;
    current = null;
  };

  const openFor = (trigger, pop, { pinned = false } = {}) => {
    clearTimers();
    if (current && current.pop !== pop) closeCurrent();
    position(trigger, pop);
    pop.classList.add('is-open');
    pop.setAttribute('aria-hidden', 'false');
    trigger.setAttribute('aria-expanded', 'true');
    const cta = pop.querySelector('.rp-pop__cta');
    if (cta) cta.tabIndex = 0;
    current = { trigger, pop, pinned };
  };

  triggers.forEach((trigger) => {
    const id = trigger.getAttribute('aria-controls');
    const pop = id ? document.getElementById(id) : null;
    if (!pop) return;

    const cta = pop.querySelector('.rp-pop__cta');
    if (cta) cta.addEventListener('click', (e) => e.preventDefault()); // デモ用に遷移無効化

    trigger.addEventListener('mouseenter', () => {
      clearTimers();
      showTimer = window.setTimeout(() => openFor(trigger, pop), SHOW_DELAY);
    });
    trigger.addEventListener('mouseleave', () => {
      window.clearTimeout(showTimer);
      if (current && current.pinned) return;
      hideTimer = window.setTimeout(closeCurrent, HIDE_DELAY);
    });
    trigger.addEventListener('focus', () => {
      clearTimers();
      openFor(trigger, pop);
    });
    trigger.addEventListener('blur', () => {
      if (current && current.pinned) return;
      hideTimer = window.setTimeout(() => {
        if (!pop.matches(':hover') && !pop.contains(document.activeElement)) closeCurrent();
      }, HIDE_DELAY);
    });
    trigger.addEventListener('click', () => {
      clearTimers();
      if (current && current.pop === pop && current.pinned) {
        closeCurrent();
      } else {
        openFor(trigger, pop, { pinned: true });
      }
    });

    pop.addEventListener('mouseenter', () => window.clearTimeout(hideTimer));
    pop.addEventListener('mouseleave', () => {
      if (current && current.pinned) return;
      hideTimer = window.setTimeout(closeCurrent, HIDE_DELAY);
    });
  });

  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && current) {
      const t = current.trigger;
      t.focus();
      closeCurrent();
    }
  });

  document.addEventListener('pointerdown', (e) => {
    if (!current) return;
    if (e.target.closest('.rp-trigger') || e.target.closest('.rp-pop')) return;
    closeCurrent();
  });

  window.addEventListener('resize', () => {
    if (current) position(current.trigger, current.pop);
  });

  // --- 待機時オートプレビュー: 誰も操作していない間、最初のカードを一度だけそっと見せる ---
  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce || !triggers[0]) return;

  const firstTrigger = triggers[0];
  const firstPop = document.getElementById(firstTrigger.getAttribute('aria-controls'));
  let autoShown = false;
  const idleTimer = window.setTimeout(() => {
    if (autoShown || current || !firstPop) return;
    autoShown = true;
    openFor(firstTrigger, firstPop);
    window.setTimeout(() => {
      if (current && current.trigger === firstTrigger && !current.pinned) closeCurrent();
    }, 2400);
  }, 2800);
  stage.addEventListener('pointerenter', () => window.clearTimeout(idleTimer), { once: true });
})();

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

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

# 追加してほしい効果
リッチポップオーバー(ツールチップ & メニュー)
文中の語句にカーソルを合わせてしばらく待つと、写真・見出し・説明・CTAを備えた大きめのカードがふわりと現れます。クリックで固定表示もできます。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- リッチポップオーバー: 文中の語句にhoverしてしばらく待つと画像+見出し+説明+CTAの大きめカードが現れる。クリックで開いたまま固定も可能 -->
<section class="rp-stage" aria-label="リッチポップオーバーの見本">
  <p class="rp-eyebrow">RICH POPOVER</p>
  <h2 class="rp-title">気になる語句に、そっと詳細を</h2>
  <p class="rp-lead">下線の語句にカーソルを合わせてしばらく待つと、詳しいカードが現れます。クリックで固定表示にもできます。</p>

  <p class="rp-copy">
    今週のおすすめは
    <button type="button" class="rp-trigger" aria-haspopup="dialog" aria-expanded="false" aria-controls="rp-pop-coat">アトリエ限定コート</button>
    と
    <button type="button" class="rp-trigger" aria-haspopup="dialog" aria-expanded="false" aria-controls="rp-pop-paper">手漉き和紙ノート</button>
    です。
  </p>

  <div class="rp-pop" id="rp-pop-coat" role="dialog" aria-modal="false" aria-labelledby="rp-pop-coat-title" aria-hidden="true">
    <div class="rp-pop__art" aria-hidden="true"><span>仕</span></div>
    <div class="rp-pop__body">
      <h3 class="rp-pop__title" id="rp-pop-coat-title">アトリエ限定コート</h3>
      <p class="rp-pop__desc">職人が一着ずつ仕立てるウールコート。裏地には季節の柄をそっと忍ばせました。</p>
      <a class="rp-pop__cta" href="#" tabindex="-1">詳しく見る →</a>
    </div>
  </div>

  <div class="rp-pop" id="rp-pop-paper" role="dialog" aria-modal="false" aria-labelledby="rp-pop-paper-title" aria-hidden="true">
    <div class="rp-pop__art rp-pop__art--alt" aria-hidden="true"><span>紙</span></div>
    <div class="rp-pop__body">
      <h3 class="rp-pop__title" id="rp-pop-paper-title">手漉き和紙ノート</h3>
      <p class="rp-pop__desc">越前の職人による手漉き和紙を表紙に使用。使うほどに風合いが増していきます。</p>
      <a class="rp-pop__cta" href="#" tabindex="-1">詳しく見る →</a>
    </div>
  </div>
</section>

【CSS】
/* リッチポップオーバー: 生成りの読み物カードから、遅延表示の大きめプレビューが浮かぶ */
:root{
  --bg-1:#f7f1e6;
  --bg-2:#ecdfc6;
  --surface:#fffdf9;
  --line:#e6dac2;
  --ink:#2a2420;
  --muted:#8b7d6a;
  --gold:#a9803f;
  --gold-soft:#caa25f;
}
*{box-sizing:border-box}
body{
  margin:0;
  min-height:100vh;
  display:grid;
  place-items:center;
  padding:44px 20px;
  overflow-x:hidden;
  overflow-y:auto;
  font-family:"Segoe UI",system-ui,-apple-system,"Hiragino Kaku Gothic ProN",sans-serif;
  color:var(--ink);
  background:
    radial-gradient(560px 300px at 12% 0%,#fffaf0 0%,transparent 60%),
    radial-gradient(520px 280px at 100% 100%,#f2e2c4 0%,transparent 55%),
    linear-gradient(180deg,var(--bg-1),var(--bg-2));
}

.rp-stage{
  position:relative;
  width:min(540px,100%);
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:20px;
  box-shadow:0 26px 60px -30px rgba(60,42,10,.35),0 2px 10px rgba(60,42,10,.06);
  padding:34px 32px 40px;
}
.rp-eyebrow{
  margin:0 0 6px;
  font-size:.7rem;
  letter-spacing:.16em;
  color:var(--gold);
  font-weight:700;
  text-align:center;
}
.rp-title{
  margin:0 0 10px;
  font-size:1.28rem;
  text-align:center;
  font-family:Georgia,"Yu Mincho","Hiragino Mincho ProN",serif;
}
.rp-lead{
  margin:0 0 22px;
  font-size:.85rem;
  line-height:1.6;
  color:var(--muted);
  text-align:center;
}
.rp-copy{
  margin:0;
  font-size:1rem;
  line-height:2.1;
  text-align:center;
}

.rp-trigger{
  all:unset;
  display:inline;
  cursor:pointer;
  color:var(--gold);
  font-weight:700;
  border-bottom:1px dashed var(--gold-soft);
  padding:0 1px;
  transition:color .15s ease,border-color .15s ease;
}
.rp-trigger:hover,
.rp-trigger:focus-visible,
.rp-trigger[aria-expanded="true"]{color:var(--ink);border-bottom-color:var(--ink)}
.rp-trigger:focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:2px}

.rp-pop{
  position:absolute;
  top:0;
  left:0;
  width:min(300px,78vw);
  display:flex;
  gap:14px;
  padding:16px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:16px;
  box-shadow:0 30px 60px -24px rgba(40,28,8,.4),0 4px 14px rgba(40,28,8,.08);
  opacity:0;
  transform:translate(-50%,8px);
  pointer-events:none;
  transition:opacity .22s ease,transform .3s cubic-bezier(.22,1,.36,1);
  z-index:40;
}
.rp-pop.is-open{opacity:1;pointer-events:auto;transform:translate(-50%,0)}
.rp-pop::before{
  content:"";
  position:absolute;
  top:-6px;
  left:calc(50% + var(--arrow-x, 0px));
  transform:translateX(-50%);
  border:6px solid transparent;
  border-bottom-color:var(--surface);
}

.rp-pop__art{
  flex:0 0 60px;
  width:60px;height:60px;
  border-radius:12px;
  display:grid;place-items:center;
  background:linear-gradient(155deg,#3a2c1c,#1c140c);
  color:var(--gold-soft);
  font-family:Georgia,"Yu Mincho",serif;
  font-size:1.3rem;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.05);
}
.rp-pop__art--alt{background:linear-gradient(155deg,#233226,#12190f);color:#bcd1a8}

.rp-pop__body{flex:1;min-width:0;text-align:left}
.rp-pop__title{margin:0 0 4px;font-size:.96rem;font-family:Georgia,"Yu Mincho",serif}
.rp-pop__desc{margin:0 0 10px;font-size:.78rem;line-height:1.6;color:var(--muted)}
.rp-pop__cta{
  display:inline-flex;align-items:center;gap:4px;
  font-size:.78rem;font-weight:700;color:var(--gold);
  text-decoration:none;border-bottom:1px solid transparent;
  transition:border-color .15s ease,color .15s ease;
}
.rp-pop__cta:hover,.rp-pop__cta:focus-visible{color:var(--ink);border-bottom-color:var(--ink)}
.rp-pop__cta:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:2px}

@media (prefers-reduced-motion:reduce){
  .rp-pop{transition:opacity .15s ease}
}

【JavaScript】
// リッチポップオーバー: hoverから少し遅れて画像+見出し+説明+CTAの大きめカードを表示。
// クリックで開閉を固定(タッチ対応)、フォーカスでも表示、Escや外側クリックで閉じる。
(() => {
  const stage = document.querySelector('.rp-stage');
  if (!stage) return; // null安全

  const triggers = Array.from(stage.querySelectorAll('.rp-trigger'));
  if (!triggers.length) return;

  const SHOW_DELAY = 380;
  const HIDE_DELAY = 220;

  let showTimer = null;
  let hideTimer = null;
  let current = null; // { trigger, pop, pinned }

  const clearTimers = () => {
    window.clearTimeout(showTimer);
    window.clearTimeout(hideTimer);
  };

  const position = (trigger, pop) => {
    const tr = trigger.getBoundingClientRect();
    const sr = stage.getBoundingClientRect();
    const top = tr.bottom - sr.top + 14;
    const centerLeft = tr.left - sr.left + tr.width / 2;
    const popRect = pop.getBoundingClientRect();
    const popWidth = popRect.width || 300;
    const margin = 14;
    const minLeft = popWidth / 2 + margin;
    const maxLeft = Math.max(minLeft, sr.width - popWidth / 2 - margin);
    const clampedLeft = Math.min(Math.max(centerLeft, minLeft), maxLeft);
    const arrowHalf = Math.max(popWidth / 2 - 18, 0);
    const arrowOffset = Math.min(Math.max(centerLeft - clampedLeft, -arrowHalf), arrowHalf);
    pop.style.top = `${top}px`;
    pop.style.left = `${clampedLeft}px`;
    pop.style.setProperty('--arrow-x', `${arrowOffset}px`);
  };

  const closeCurrent = () => {
    if (!current) return;
    const { trigger, pop } = current;
    if (pop.contains(document.activeElement)) document.activeElement.blur();
    pop.classList.remove('is-open');
    pop.setAttribute('aria-hidden', 'true');
    trigger.setAttribute('aria-expanded', 'false');
    const cta = pop.querySelector('.rp-pop__cta');
    if (cta) cta.tabIndex = -1;
    current = null;
  };

  const openFor = (trigger, pop, { pinned = false } = {}) => {
    clearTimers();
    if (current && current.pop !== pop) closeCurrent();
    position(trigger, pop);
    pop.classList.add('is-open');
    pop.setAttribute('aria-hidden', 'false');
    trigger.setAttribute('aria-expanded', 'true');
    const cta = pop.querySelector('.rp-pop__cta');
    if (cta) cta.tabIndex = 0;
    current = { trigger, pop, pinned };
  };

  triggers.forEach((trigger) => {
    const id = trigger.getAttribute('aria-controls');
    const pop = id ? document.getElementById(id) : null;
    if (!pop) return;

    const cta = pop.querySelector('.rp-pop__cta');
    if (cta) cta.addEventListener('click', (e) => e.preventDefault()); // デモ用に遷移無効化

    trigger.addEventListener('mouseenter', () => {
      clearTimers();
      showTimer = window.setTimeout(() => openFor(trigger, pop), SHOW_DELAY);
    });
    trigger.addEventListener('mouseleave', () => {
      window.clearTimeout(showTimer);
      if (current && current.pinned) return;
      hideTimer = window.setTimeout(closeCurrent, HIDE_DELAY);
    });
    trigger.addEventListener('focus', () => {
      clearTimers();
      openFor(trigger, pop);
    });
    trigger.addEventListener('blur', () => {
      if (current && current.pinned) return;
      hideTimer = window.setTimeout(() => {
        if (!pop.matches(':hover') && !pop.contains(document.activeElement)) closeCurrent();
      }, HIDE_DELAY);
    });
    trigger.addEventListener('click', () => {
      clearTimers();
      if (current && current.pop === pop && current.pinned) {
        closeCurrent();
      } else {
        openFor(trigger, pop, { pinned: true });
      }
    });

    pop.addEventListener('mouseenter', () => window.clearTimeout(hideTimer));
    pop.addEventListener('mouseleave', () => {
      if (current && current.pinned) return;
      hideTimer = window.setTimeout(closeCurrent, HIDE_DELAY);
    });
  });

  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && current) {
      const t = current.trigger;
      t.focus();
      closeCurrent();
    }
  });

  document.addEventListener('pointerdown', (e) => {
    if (!current) return;
    if (e.target.closest('.rp-trigger') || e.target.closest('.rp-pop')) return;
    closeCurrent();
  });

  window.addEventListener('resize', () => {
    if (current) position(current.trigger, current.pop);
  });

  // --- 待機時オートプレビュー: 誰も操作していない間、最初のカードを一度だけそっと見せる ---
  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce || !triggers[0]) return;

  const firstTrigger = triggers[0];
  const firstPop = document.getElementById(firstTrigger.getAttribute('aria-controls'));
  let autoShown = false;
  const idleTimer = window.setTimeout(() => {
    if (autoShown || current || !firstPop) return;
    autoShown = true;
    openFor(firstTrigger, firstPop);
    window.setTimeout(() => {
      if (current && current.trigger === firstTrigger && !current.pinned) closeCurrent();
    }, 2400);
  }, 2800);
  stage.addEventListener('pointerenter', () => window.clearTimeout(idleTimer), { once: true });
})();

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

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