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

+12 -10
View File
@@ -16,8 +16,8 @@
const timerInput = $('timer');
const intervalInput = $('interval');
const scheduleEl = $('schedule');
const modeHint = $('mode-hint');
const cpuOptions = $('cpu-options');
const intervalRow = $('interval-row');
const blindHint = $('blind-hint');
const errorEl = $('error');
const submitBtn = $('submit');
@@ -49,7 +49,7 @@
const stack = int(stackInput);
const bb = int(bbInput);
if (blindsTouched && stack > 0 && bb > 0) {
blindHint.innerHTML = `開始スタック = ${Math.floor(stack / bb)} BB ・ <a href="#" id="reset-blinds" class="link-gold">おすすめに戻す</a>`;
blindHint.innerHTML = `開始スタック = ${Math.floor(stack / bb)} BB ・ <a href="#" id="reset-blinds" class="link">おすすめに戻す</a>`;
} else {
blindHint.textContent = 'スタックに合わせて自動設定(BB ≒ スタックの1/100)';
}
@@ -60,6 +60,7 @@
const bb = int(bbInput);
const on = timerInput.checked;
intervalInput.disabled = !on;
setOpen(intervalRow, on);
scheduleEl.hidden = !on || !(sb > 0 && bb > sb);
if (scheduleEl.hidden) return;
const mins = int(intervalInput) > 0 ? int(intervalInput) : 0;
@@ -68,18 +69,17 @@
.join('');
}
const MODE_HINTS = {
chips: '実物のカードで遊び、サイトはチップだけを管理。ショーダウンの勝者はホストが選ぶ',
friends: '各自のスマホに手札が配られ、勝敗とポットの分配は自動',
cpu: 'CPU と対戦(友達も空いている席に参加できる)。カードの配布・勝敗の判定は自動',
review: 'あなたと CPU だけで対戦(友達は入れない)。ハンドごとにハンドレンジの色・行動の点数を表示し、対局全体の成績も振り返れる',
};
const radioValue = (name) => (form.querySelector(`input[name="${name}"]:checked`) || {}).value;
// 行の開け閉め(高さはばねで変わる)。閉じている間はフォーカスも届かないようにする
function setOpen(el, open) {
el.classList.toggle('collapsed', !open);
el.inert = !open;
}
function updateMode() {
const mode = radioValue('mode');
modeHint.textContent = MODE_HINTS[mode] || '';
cpuOptions.hidden = mode !== 'cpu' && mode !== 'review';
setOpen(cpuOptions, mode === 'cpu' || mode === 'review');
}
function refresh() {
@@ -110,6 +110,8 @@
intervalInput.addEventListener('input', refresh);
form.querySelectorAll('input[type="radio"]').forEach((el) => el.addEventListener('change', refresh));
$('reroll').addEventListener('click', suggestName);
Fluid.Segmented(form.querySelector('.segmented.numbers'));
Fluid.navBar($('nav'), $('large-title'));
function validate() {
const name = nameInput.value.trim();