リッチポップオーバー
文中の語句にカーソルを合わせてしばらく待つと、写真・見出し・説明・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 });
})();
🤖 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。