Rebuild the UI in an Apple style with spring-driven motion

Replace the look of every page (key, lobby, create, table, range, review)
with light/dark semantic colors, translucent materials, iOS-style grouped
lists and system typography. Add fluid.js, a small spring engine with
interruptible, velocity-aware animations, and use it for draggable bottom
sheets, sliding segmented controls, a custom bet slider with rubber-banding,
swipe-to-delete in the lobby, and dealt/flipped cards. Seats and cards are
now reused between renders so animations are not cut off. Honors reduced
motion, reduced transparency and increased contrast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agomizi1cmandClaude Opus 5.5 committed 2026-10-21 21:44:21 +09:00
1 parent d5ef2d310f
commit c3fbfdd12e
18 files changed
+4877 -1278

No files matched your search

+18 -5
View File
@@ -19,6 +19,12 @@
let tab = 'scores';
let replay = null; // { hand, step }
const CHEVRON = '<svg class="i chevron" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg>';
const SPARKLES =
'<svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M10 3.5l1.6 4.4L16 9.5l-4.4 1.6L10 15.5l-1.6-4.4L4 9.5l4.4-1.6z"/><path d="M18 14l.8 2.2L21 17l-2.2.8L18 20l-.8-2.2L15 17l2.2-.8z"/></svg>';
Fluid.navBar($('nav'), $('title'));
const tabsSeg = Fluid.Segmented($('tabs'));
const markOf = (score) => (score == null ? '' : score >= 90 ? '◎' : score >= 70 ? '○' : score >= 40 ? '△' : '✕');
const scoreClass = (score) => (score == null ? 'none' : MARK_CLASS[markOf(score)]);
const signed = (n) => (n > 0 ? '+' : n < 0 ? '−' : '±') + chips(Math.abs(n));
@@ -55,6 +61,7 @@
if (!id) {
$('title').textContent = 'CPU戦のレビュー';
$('back').href = '/';
$('back-label').textContent = 'ロビー';
show('list');
return loadList();
}
@@ -71,10 +78,12 @@
const h = session.hands.find((x) => x.number === hand);
if (h) {
$('back').href = `?s=${encodeURIComponent(id)}`;
$('back-label').textContent = '対局';
return openReplay(h);
}
}
$('back').href = '/review';
$('back-label').textContent = '一覧';
show('session');
renderSession();
}
@@ -105,9 +114,12 @@
$('sessions').innerHTML = list
.map(
(s) => `
<button type="button" class="rv-session" data-id="${esc(s.id)}">
<span class="rv-session-name">${esc(s.tableName)}${s.active ? '<span class="badge live">PLAYING</span>' : ''}</span>
<span class="rv-session-meta">${dateText(s.createdAt)} ・ ${s.hands}ハンド ・ ${esc(s.heroName)}</span>
<button type="button" class="cell cell-link rv-session" data-id="${esc(s.id)}">
<span class="cell-title">
<span class="rv-session-name">${esc(s.tableName)}${s.active ? '<span class="badge live">PLAYING</span>' : ''}</span>
<span class="cell-sub">${dateText(s.createdAt)} ・ ${s.hands}ハンド ・ ${esc(s.heroName)}</span>
</span>
${CHEVRON}
</button>`
)
.join('');
@@ -125,7 +137,8 @@
<div class="rv-tile"><span>ハンド数</span><b>${s.stats.hands}</b></div>
<div class="rv-tile"><span>平均点</span><b class="score-${scoreClass(s.total)}">${s.total == null ? '—' : s.total}</b></div>
<div class="rv-tile"><span>収支</span><b class="${signClass(net)}">${signed(net)}</b></div>`;
for (const b of $('tabs').children) b.classList.toggle('active', b.dataset.tab === tab);
for (const b of $('tabs').querySelectorAll('[data-tab]')) b.classList.toggle('active', b.dataset.tab === tab);
tabsSeg.update();
const render = { scores: renderScores, stats: renderStats, range: renderRange, worst: renderWorst, graph: renderGraph }[tab];
$('tab-body').innerHTML = '';
render();
@@ -370,7 +383,7 @@
</div>
<div class="rv-streets">${STREETS.map((st) => `<div class="rv-street"><span>${STREET_SHORT[st]}</span><b class="score-${scoreClass(h.streetScores[st])}">${h.streetScores[st] == null ? '—' : h.streetScores[st]}</b></div>`).join('')}</div>
${allInHtml(h)}
<button type="button" class="btn btn-dark rv-copy" id="replay-copy">🤖 AIに質問する用にコピー</button>`;
<button type="button" class="btn btn-block rv-copy" id="replay-copy">${SPARKLES}AIに質問する用にコピー</button>`;
$('replay-copy').addEventListener('click', () => copyHandPrompt(h));
$('replay-log').innerHTML = logHtml(h);
renderStep();