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:
1 parent
d5ef2d310f
commit
c3fbfdd12e
18 files changed
+4877
-1278
No files matched your search
+18
-5
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user