Pythonデータ集計ダッシュボード
Python標準のstatisticsでランダム売上データを集計し、合計・平均・中央値・標準偏差をカード表示しつつバーチャート化。データ分析UIの雛形に使える。
外部ライブラリ: https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.js
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- FlowDesk:月次レポート。Python statisticsでチーム別工数を集計 -->
<section class="fd-report" aria-label="FlowDesk 月次レポート">
<header class="fd-report__head">
<div>
<span class="fd-report__brand"><b>Flow</b>Desk</span>
<h1 class="fd-report__title">月次アクティビティ・レポート</h1>
<p class="fd-report__sub">チーム別の完了タスク数を集計</p>
</div>
<button id="reroll" class="fd-report__btn" disabled>別の月を表示</button>
</header>
<div class="fd-report__grid">
<!-- 左:チーム別バー -->
<div class="fd-panel">
<h2 class="fd-panel__h">チーム別 完了タスク</h2>
<ul class="fd-bars" id="bars" aria-label="チーム別バーチャート"></ul>
</div>
<!-- 右:統計KPI -->
<div class="fd-panel fd-panel--kpi">
<h2 class="fd-panel__h">サマリー</h2>
<dl class="fd-stats" id="stats">
<div class="fd-stats__row"><dt>合計</dt><dd id="s-sum">—</dd></div>
<div class="fd-stats__row"><dt>平均/チーム</dt><dd id="s-mean">—</dd></div>
<div class="fd-stats__row"><dt>中央値</dt><dd id="s-median">—</dd></div>
<div class="fd-stats__row"><dt>ばらつき(σ)</dt><dd id="s-std">—</dd></div>
<div class="fd-stats__row fd-stats__row--top"><dt>MVPチーム</dt><dd id="s-top">—</dd></div>
</dl>
</div>
</div>
<footer class="fd-report__foot" id="foot" data-state="boot">集計エンジン起動中…</footer>
</section>
CSS
/* FlowDesk:月次アクティビティ・レポート */
:root {
--navy: #0f1b34;
--navy2: #16264a;
--blue: #4f7cff;
--ink: #2c3650;
--mut: #8390ad;
--line: #e6eaf3;
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 400px;
display: grid;
place-items: center;
background: #eef2fa;
font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
color: var(--ink);
overflow: hidden;
}
.fd-report {
width: min(600px, 95vw);
padding: 18px 22px;
border-radius: 16px;
background: #fff;
box-shadow: 0 18px 44px rgba(15, 27, 52, 0.12);
}
/* ヘッダ */
.fd-report__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 14px;
}
.fd-report__brand { font-size: 12px; color: var(--navy); letter-spacing: 0.02em; }
.fd-report__brand b { color: var(--blue); }
.fd-report__title { margin: 4px 0 2px; font-size: 18px; font-weight: 800; color: var(--navy); }
.fd-report__sub { margin: 0; font-size: 11.5px; color: var(--mut); }
.fd-report__btn {
font: inherit;
font-size: 12px;
font-weight: 700;
padding: 8px 15px;
border: 1px solid var(--blue);
border-radius: 9px;
cursor: pointer;
color: var(--blue);
background: #fff;
transition: background 0.2s ease, color 0.2s ease;
}
.fd-report__btn:disabled { opacity: 0.5; cursor: default; }
.fd-report__btn:not(:disabled):hover { background: var(--blue); color: #fff; }
.fd-report__grid { display: grid; grid-template-columns: 1.3fr 1fr; gap: 16px; }
.fd-panel {
padding: 14px 16px;
border-radius: 12px;
background: #f6f8fd;
border: 1px solid var(--line);
}
.fd-panel__h { margin: 0 0 12px; font-size: 12px; font-weight: 700; color: var(--mut); letter-spacing: 0.04em; }
/* バーチャート */
.fd-bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.fd-bar__top { display: flex; justify-content: space-between; margin-bottom: 5px; }
.fd-bar__name { font-size: 12px; font-weight: 600; color: var(--ink); }
.fd-bar__val { font-size: 11.5px; font-weight: 700; color: var(--blue); font-variant-numeric: tabular-nums; }
.fd-bar__track { height: 8px; border-radius: 99px; background: #e3e9f5; overflow: hidden; }
.fd-bar__fill {
height: 100%;
width: 0;
border-radius: 99px;
background: linear-gradient(90deg, #6f93ff, var(--blue));
transition: width 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
/* KPI */
.fd-stats { margin: 0; }
.fd-stats__row {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 8px 0;
border-bottom: 1px solid var(--line);
}
.fd-stats__row:last-child { border-bottom: none; }
.fd-stats dt { font-size: 12px; color: var(--mut); }
.fd-stats dd { margin: 0; font-size: 15px; font-weight: 800; color: var(--navy); font-variant-numeric: tabular-nums; }
.fd-stats__row--top dd { font-size: 13px; color: var(--blue); }
/* フッタ */
.fd-report__foot {
margin-top: 14px;
font-size: 11px;
color: var(--mut);
text-align: right;
}
.fd-report__foot[data-state="ready"]::before { content: "● "; color: #2ecc90; }
.fd-report__foot[data-state="ready"] { color: #4f6088; }
JavaScript
// 要素取得(null安全)
const $ = (id) => document.getElementById(id);
const barsEl = $("bars"), foot = $("foot"), reroll = $("reroll");
const out = {
sum: $("s-sum"), mean: $("s-mean"), median: $("s-median"),
std: $("s-std"), top: $("s-top")
};
if (barsEl && foot && reroll && Object.values(out).every(Boolean)) {
let pyodide = null;
// Pythonでチーム別の完了タスク数を生成し statistics で集計
function aggregate() {
return pyodide.runPython(`
import random, statistics, json
teams = ["開発", "デザイン", "営業", "CS", "経営企画"]
data = {t: sum(random.randint(2, 9) for _ in range(random.randint(6, 16))) for t in teams}
values = list(data.values())
top = max(data, key=data.get)
result = {
"rows": [{"name": k, "val": v} for k, v in sorted(data.items(), key=lambda kv: -kv[1])],
"sum": sum(values),
"mean": round(statistics.mean(values), 1),
"median": round(statistics.median(values), 1),
"std": round(statistics.pstdev(values), 1),
"top": top,
}
json.dumps(result)
`);
}
// 結果をDOMへ反映
function update() {
const res = JSON.parse(aggregate());
const max = Math.max(...res.rows.map((r) => r.val), 1);
barsEl.replaceChildren();
res.rows.forEach((r, i) => {
const li = document.createElement("li");
li.className = "fd-bar";
li.innerHTML = `
<div class="fd-bar__top">
<span class="fd-bar__name">${r.name}</span>
<span class="fd-bar__val">${r.val} 件</span>
</div>
<div class="fd-bar__track"><div class="fd-bar__fill"></div></div>`;
barsEl.append(li);
const fill = li.querySelector(".fd-bar__fill");
// 次フレームで幅を入れてトランジション
requestAnimationFrame(() => {
setTimeout(() => { fill.style.width = (r.val / max * 100) + "%"; }, i * 70);
});
});
out.sum.textContent = res.sum + " 件";
out.mean.textContent = res.mean.toLocaleString();
out.median.textContent = res.median.toLocaleString();
out.std.textContent = res.std.toLocaleString();
out.top.textContent = res.top;
}
reroll.addEventListener("click", update);
// Pyodide起動
(async () => {
try {
const mod = await import("https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.mjs");
pyodide = await mod.loadPyodide();
foot.dataset.state = "ready";
foot.textContent = "集計: Python statistics / 更新 2026-06";
reroll.disabled = false;
update();
} catch (e) {
foot.textContent = "レポートを読み込めませんでした";
}
})();
}
コード
HTML
<!-- Pythonの標準ライブラリでデータ集計し表+バーで表示するデモ -->
<main class="board" aria-label="Pythonデータ集計">
<header class="board__head">
<div>
<h1 class="board__title">売上集計ダッシュボード</h1>
<p class="board__desc">CSV風データを Python (statistics) で集計</p>
</div>
<button id="reroll" class="board__btn" disabled>データ再生成</button>
</header>
<section class="board__grid">
<!-- カテゴリ別バーチャート -->
<div class="card">
<h2 class="card__h">カテゴリ別 売上合計</h2>
<ul class="bars" id="bars" aria-label="バーチャート"></ul>
</div>
<!-- 統計サマリー -->
<div class="card card--stats">
<h2 class="card__h">統計サマリー</h2>
<dl class="stats" id="stats">
<div class="stats__row"><dt>合計</dt><dd id="s-sum">—</dd></div>
<div class="stats__row"><dt>平均</dt><dd id="s-mean">—</dd></div>
<div class="stats__row"><dt>中央値</dt><dd id="s-median">—</dd></div>
<div class="stats__row"><dt>標準偏差</dt><dd id="s-std">—</dd></div>
<div class="stats__row"><dt>最大カテゴリ</dt><dd id="s-top">—</dd></div>
</dl>
</div>
</section>
<footer class="board__foot" id="foot" data-state="boot">Pyodide 起動中…</footer>
</main>
CSS
:root {
--bg: #0f1117;
--card: #181b25;
--card-2: #1d2130;
--ink: #eef1f8;
--muted: #9aa3b8;
--accent: #6ee7b7;
--accent-2: #60a5fa;
--line: #262b3a;
--sans: system-ui, "Segoe UI", "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 14px;
font-family: var(--sans);
color: var(--ink);
background:
radial-gradient(700px 300px at 100% 0%, #15233a 0%, transparent 60%),
var(--bg);
}
.board {
width: min(100%, 660px);
background: var(--card);
border: 1px solid var(--line);
border-radius: 16px;
padding: 18px;
box-shadow: 0 26px 60px -26px rgba(0,0,0,.75);
}
.board__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.board__title { margin: 0; font-size: 17px; font-weight: 800; letter-spacing: .01em; }
.board__desc { margin: 4px 0 0; font-size: 11px; color: var(--muted); }
.board__btn {
flex: none;
font: inherit; font-size: 12px; font-weight: 600;
color: #07120c;
background: linear-gradient(180deg, #8df0c4, var(--accent));
border: none; padding: 8px 16px; border-radius: 10px; cursor: pointer;
transition: transform .12s ease, box-shadow .12s ease;
}
.board__btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 8px 18px -6px rgba(110,231,183,.5); }
.board__btn:disabled { opacity: .5; cursor: not-allowed; }
.board__grid {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 14px;
}
@media (max-width: 540px) { .board__grid { grid-template-columns: 1fr; } }
.card {
background: var(--card-2);
border: 1px solid var(--line);
border-radius: 12px;
padding: 14px;
}
.card__h { margin: 0 0 12px; font-size: 12px; color: var(--muted); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
/* バーチャート */
.bars { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.bar { font-size: 12px; }
.bar__top { display: flex; justify-content: space-between; margin-bottom: 4px; }
.bar__name { color: var(--ink); }
.bar__val { color: var(--accent); font-variant-numeric: tabular-nums; }
.bar__track { height: 8px; background: #11141d; border-radius: 6px; overflow: hidden; }
.bar__fill {
height: 100%;
width: 0;
border-radius: 6px;
background: linear-gradient(90deg, var(--accent-2), var(--accent));
transition: width .7s cubic-bezier(.22,1,.36,1);
}
/* 統計リスト */
.stats { margin: 0; }
.stats__row { display: flex; justify-content: space-between; align-items: baseline; padding: 9px 0; border-bottom: 1px dashed var(--line); }
.stats__row:last-child { border-bottom: none; }
.stats dt { font-size: 12px; color: var(--muted); }
.stats dd { margin: 0; font-size: 15px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.card--stats dd#s-top { font-size: 13px; color: var(--accent); }
.board__foot {
margin-top: 14px;
font-size: 10px;
text-align: center;
padding: 6px;
border-radius: 8px;
}
.board__foot[data-state="boot"] { color: #ffd166; background: rgba(255,209,102,.08); }
.board__foot[data-state="ready"] { color: var(--accent); background: rgba(110,231,183,.08); }
@media (prefers-reduced-motion: reduce) {
.bar__fill { transition: none; }
.board__btn { transition: none; }
}
JavaScript
// 要素取得(null安全)
const $ = (id) => document.getElementById(id);
const barsEl = $("bars"), foot = $("foot"), reroll = $("reroll");
const out = {
sum: $("s-sum"), mean: $("s-mean"), median: $("s-median"),
std: $("s-std"), top: $("s-top")
};
if (barsEl && foot && reroll && Object.values(out).every(Boolean)) {
let pyodide = null;
// Pythonでランダムな売上データを生成し集計(statisticsモジュール使用)
function aggregate() {
return pyodide.runPython(`
import random, statistics, json
cats = ["家電", "書籍", "食品", "衣料", "玩具"]
data = {c: sum(random.randint(20, 240) for _ in range(random.randint(8, 20))) for c in cats}
values = list(data.values())
top = max(data, key=data.get)
result = {
"rows": [{"name": k, "val": v} for k, v in sorted(data.items(), key=lambda kv: -kv[1])],
"sum": sum(values),
"mean": round(statistics.mean(values), 1),
"median": round(statistics.median(values), 1),
"std": round(statistics.pstdev(values), 1),
"top": top,
}
json.dumps(result)
`);
}
// 結果をDOMへ反映
function update() {
const res = JSON.parse(aggregate());
const max = Math.max(...res.rows.map((r) => r.val), 1);
// バー再構築
barsEl.replaceChildren();
res.rows.forEach((r, i) => {
const li = document.createElement("li");
li.className = "bar";
li.innerHTML = `
<div class="bar__top">
<span class="bar__name">${r.name}</span>
<span class="bar__val">¥${r.val.toLocaleString()}</span>
</div>
<div class="bar__track"><div class="bar__fill"></div></div>`;
barsEl.append(li);
const fill = li.querySelector(".bar__fill");
// 次フレームで幅を入れてトランジションを効かせる
requestAnimationFrame(() => {
setTimeout(() => { fill.style.width = (r.val / max * 100) + "%"; }, i * 70);
});
});
out.sum.textContent = "¥" + res.sum.toLocaleString();
out.mean.textContent = res.mean.toLocaleString();
out.median.textContent = res.median.toLocaleString();
out.std.textContent = res.std.toLocaleString();
out.top.textContent = res.top;
}
reroll.addEventListener("click", update);
// Pyodide起動
(async () => {
try {
const mod = await import("https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.mjs");
pyodide = await mod.loadPyodide();
foot.dataset.state = "ready";
foot.textContent = "集計エンジン: Python statistics";
reroll.disabled = false;
update();
} catch (e) {
foot.textContent = "読込失敗: " + e.message;
}
})();
}
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「Pythonデータ集計ダッシュボード」の効果を追加してください。
# 追加してほしい効果
Pythonデータ集計ダッシュボード(Python (Pyodideブラウザ実行))
Python標準のstatisticsでランダム売上データを集計し、合計・平均・中央値・標準偏差をカード表示しつつバーチャート化。データ分析UIの雛形に使える。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- Pythonの標準ライブラリでデータ集計し表+バーで表示するデモ -->
<main class="board" aria-label="Pythonデータ集計">
<header class="board__head">
<div>
<h1 class="board__title">売上集計ダッシュボード</h1>
<p class="board__desc">CSV風データを Python (statistics) で集計</p>
</div>
<button id="reroll" class="board__btn" disabled>データ再生成</button>
</header>
<section class="board__grid">
<!-- カテゴリ別バーチャート -->
<div class="card">
<h2 class="card__h">カテゴリ別 売上合計</h2>
<ul class="bars" id="bars" aria-label="バーチャート"></ul>
</div>
<!-- 統計サマリー -->
<div class="card card--stats">
<h2 class="card__h">統計サマリー</h2>
<dl class="stats" id="stats">
<div class="stats__row"><dt>合計</dt><dd id="s-sum">—</dd></div>
<div class="stats__row"><dt>平均</dt><dd id="s-mean">—</dd></div>
<div class="stats__row"><dt>中央値</dt><dd id="s-median">—</dd></div>
<div class="stats__row"><dt>標準偏差</dt><dd id="s-std">—</dd></div>
<div class="stats__row"><dt>最大カテゴリ</dt><dd id="s-top">—</dd></div>
</dl>
</div>
</section>
<footer class="board__foot" id="foot" data-state="boot">Pyodide 起動中…</footer>
</main>
【CSS】
:root {
--bg: #0f1117;
--card: #181b25;
--card-2: #1d2130;
--ink: #eef1f8;
--muted: #9aa3b8;
--accent: #6ee7b7;
--accent-2: #60a5fa;
--line: #262b3a;
--sans: system-ui, "Segoe UI", "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 14px;
font-family: var(--sans);
color: var(--ink);
background:
radial-gradient(700px 300px at 100% 0%, #15233a 0%, transparent 60%),
var(--bg);
}
.board {
width: min(100%, 660px);
background: var(--card);
border: 1px solid var(--line);
border-radius: 16px;
padding: 18px;
box-shadow: 0 26px 60px -26px rgba(0,0,0,.75);
}
.board__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.board__title { margin: 0; font-size: 17px; font-weight: 800; letter-spacing: .01em; }
.board__desc { margin: 4px 0 0; font-size: 11px; color: var(--muted); }
.board__btn {
flex: none;
font: inherit; font-size: 12px; font-weight: 600;
color: #07120c;
background: linear-gradient(180deg, #8df0c4, var(--accent));
border: none; padding: 8px 16px; border-radius: 10px; cursor: pointer;
transition: transform .12s ease, box-shadow .12s ease;
}
.board__btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 8px 18px -6px rgba(110,231,183,.5); }
.board__btn:disabled { opacity: .5; cursor: not-allowed; }
.board__grid {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 14px;
}
@media (max-width: 540px) { .board__grid { grid-template-columns: 1fr; } }
.card {
background: var(--card-2);
border: 1px solid var(--line);
border-radius: 12px;
padding: 14px;
}
.card__h { margin: 0 0 12px; font-size: 12px; color: var(--muted); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
/* バーチャート */
.bars { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.bar { font-size: 12px; }
.bar__top { display: flex; justify-content: space-between; margin-bottom: 4px; }
.bar__name { color: var(--ink); }
.bar__val { color: var(--accent); font-variant-numeric: tabular-nums; }
.bar__track { height: 8px; background: #11141d; border-radius: 6px; overflow: hidden; }
.bar__fill {
height: 100%;
width: 0;
border-radius: 6px;
background: linear-gradient(90deg, var(--accent-2), var(--accent));
transition: width .7s cubic-bezier(.22,1,.36,1);
}
/* 統計リスト */
.stats { margin: 0; }
.stats__row { display: flex; justify-content: space-between; align-items: baseline; padding: 9px 0; border-bottom: 1px dashed var(--line); }
.stats__row:last-child { border-bottom: none; }
.stats dt { font-size: 12px; color: var(--muted); }
.stats dd { margin: 0; font-size: 15px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.card--stats dd#s-top { font-size: 13px; color: var(--accent); }
.board__foot {
margin-top: 14px;
font-size: 10px;
text-align: center;
padding: 6px;
border-radius: 8px;
}
.board__foot[data-state="boot"] { color: #ffd166; background: rgba(255,209,102,.08); }
.board__foot[data-state="ready"] { color: var(--accent); background: rgba(110,231,183,.08); }
@media (prefers-reduced-motion: reduce) {
.bar__fill { transition: none; }
.board__btn { transition: none; }
}
【JavaScript】
// 要素取得(null安全)
const $ = (id) => document.getElementById(id);
const barsEl = $("bars"), foot = $("foot"), reroll = $("reroll");
const out = {
sum: $("s-sum"), mean: $("s-mean"), median: $("s-median"),
std: $("s-std"), top: $("s-top")
};
if (barsEl && foot && reroll && Object.values(out).every(Boolean)) {
let pyodide = null;
// Pythonでランダムな売上データを生成し集計(statisticsモジュール使用)
function aggregate() {
return pyodide.runPython(`
import random, statistics, json
cats = ["家電", "書籍", "食品", "衣料", "玩具"]
data = {c: sum(random.randint(20, 240) for _ in range(random.randint(8, 20))) for c in cats}
values = list(data.values())
top = max(data, key=data.get)
result = {
"rows": [{"name": k, "val": v} for k, v in sorted(data.items(), key=lambda kv: -kv[1])],
"sum": sum(values),
"mean": round(statistics.mean(values), 1),
"median": round(statistics.median(values), 1),
"std": round(statistics.pstdev(values), 1),
"top": top,
}
json.dumps(result)
`);
}
// 結果をDOMへ反映
function update() {
const res = JSON.parse(aggregate());
const max = Math.max(...res.rows.map((r) => r.val), 1);
// バー再構築
barsEl.replaceChildren();
res.rows.forEach((r, i) => {
const li = document.createElement("li");
li.className = "bar";
li.innerHTML = `
<div class="bar__top">
<span class="bar__name">${r.name}</span>
<span class="bar__val">¥${r.val.toLocaleString()}</span>
</div>
<div class="bar__track"><div class="bar__fill"></div></div>`;
barsEl.append(li);
const fill = li.querySelector(".bar__fill");
// 次フレームで幅を入れてトランジションを効かせる
requestAnimationFrame(() => {
setTimeout(() => { fill.style.width = (r.val / max * 100) + "%"; }, i * 70);
});
});
out.sum.textContent = "¥" + res.sum.toLocaleString();
out.mean.textContent = res.mean.toLocaleString();
out.median.textContent = res.median.toLocaleString();
out.std.textContent = res.std.toLocaleString();
out.top.textContent = res.top;
}
reroll.addEventListener("click", update);
// Pyodide起動
(async () => {
try {
const mod = await import("https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.mjs");
pyodide = await mod.loadPyodide();
foot.dataset.state = "ready";
foot.textContent = "集計エンジン: Python statistics";
reroll.disabled = false;
update();
} catch (e) {
foot.textContent = "読込失敗: " + e.message;
}
})();
}
# 外部ライブラリ
https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.js
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。