diff --git a/index.html b/index.html index ccd3d41..f0f681c 100644 --- a/index.html +++ b/index.html @@ -39,38 +39,45 @@ - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - + + +
+ + - + \ No newline at end of file diff --git a/script.js b/script.js index 2adf737..fd754b1 100644 --- a/script.js +++ b/script.js @@ -1,10 +1,10 @@ // ========= MTG Life Counter ========= // ---------- Config ---------- -const STARTING_LIFE = 20; -const POISON_LOSS = 10; // poison counters to lose -const COMMANDER_LOSS = 21; // commander damage from a single opponent to lose -const NAME_MAX_CHARS = 18; // trim player names +const STARTING_LIFE = 20; +const POISON_LOSS = 10; +const COMMANDER_LOSS = 21; +const NAME_MAX_CHARS = 18; // ---------- Global state ---------- let players = []; @@ -14,15 +14,18 @@ let isTwoPlayer = false; let isPoisonMatch = false; // ---------- DOM ---------- -const mainContainer = document.querySelector('main'); -const addPlayerButton = document.querySelector('.fa-circle-plus'); -const removePlayerButton = document.querySelector('.fa-circle-minus'); -const resetButton = document.querySelector('.fa-rotate-left'); -const commanderToggleButton = document.querySelector('#commander-toggle'); -const poisonToggleButton = document.querySelector('#poison-toggle'); -const twoPlayerToggleButton = document.querySelector('.fa-user-group'); -const fullscreenButton = document.querySelector('.fa-expand'); -const navContainer = document.getElementById('navContainer'); +const mainContainer = document.querySelector('main'); +const addPlayerButton = document.querySelector('.fa-circle-plus'); +const removePlayerButton = document.querySelector('.fa-circle-minus'); +const resetButton = document.querySelector('.fa-rotate-left'); +const commanderToggleButton = document.querySelector('#commander-toggle'); +const poisonToggleButton = document.querySelector('#poison-toggle'); +const twoPlayerToggleButton = document.querySelector('.fa-user-group'); +const fullscreenButton = document.querySelector('.fa-expand'); +const navContainer = document.getElementById('navContainer'); +const diceButton = document.getElementById('dice-button'); +const diceModal = document.getElementById('dice-modal'); +const closeButton = document.querySelector('.close-button'); // ---------- Utilities ---------- const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); @@ -76,8 +79,8 @@ const createPlayerElement = (playerObj, index) => {
-
-
+
+
${playerObj.life}
@@ -100,10 +103,63 @@ const createPlayerElement = (playerObj, index) => { // ---------- Rendering / Persistence ---------- const renderPlayers = () => { - mainContainer.innerHTML = ''; - players.forEach((player, index) => { - const el = createPlayerElement(player, index); - mainContainer.appendChild(el); + const currentPlayers = Array.from(mainContainer.children); + + // Remove player elements that no longer have a corresponding player object. + // This handles when a player is removed. + currentPlayers.forEach((playerEl, index) => { + if (!players[index]) { + playerEl.remove(); + } + }); + + // Iterate through the players state and either update existing DOM elements + // or create and append new ones. + players.forEach((playerObj, index) => { + let playerEl = document.getElementById(`player${index + 1}`); + if (playerEl) { + // Player card exists, update its content instead of re-creating it. + playerEl.querySelector('.name').textContent = sanitizeName(playerObj.name); + playerEl.querySelector('.life-count').textContent = playerObj.life; + setLifeRingOn(playerEl, playerObj.life); + + const specialCountersDiv = playerEl.querySelector('.special-counters'); + + const poisonCounterHTML = isPoisonMatch ? ` +
+ Poison: + + ${playerObj.poison} + +
+ ` : ''; + + let commanderDamageHTML = ''; + if (isCommanderMatch) { + commanderDamageHTML = players.map((opponent, oppIndex) => { + if (index === oppIndex) return ''; + const val = playerObj.commanderDamage?.[oppIndex] ?? 0; + return ` +
+ ${sanitizeName(opponent.name)} +
+ + ${val} + +
+
+ `; + }).join(''); + } + + specialCountersDiv.innerHTML = `${poisonCounterHTML} ${commanderDamageHTML}`; + + } else { + // Player card does not exist, create a new one. + const newEl = createPlayerElement(playerObj, index); + mainContainer.appendChild(newEl); + } + checkElimination(index); }); document.body.classList.toggle('is-commander-mode', isCommanderMatch); @@ -111,9 +167,6 @@ const renderPlayers = () => { document.body.classList.toggle('is-poison-mode', isPoisonMatch); initLifeRings(); - - // After DOM exists, check elimination state for each - players.forEach((_, i) => checkElimination(i)); }; const saveState = () => { @@ -129,11 +182,11 @@ const loadState = () => { try { saved = JSON.parse(localStorage.getItem('mtgLifeCounterState')); } catch {} if (saved?.players?.length) { - players = saved.players; - playerCount = saved.playerCount; + players = saved.players; + playerCount = saved.playerCount; isCommanderMatch = !!saved.isCommanderMatch; - isTwoPlayer = !!saved.isTwoPlayer; - isPoisonMatch = !!saved.isPoisonMatch; + isTwoPlayer = !!saved.isTwoPlayer; + isPoisonMatch = !!saved.isPoisonMatch; } else { players = [ { name: "Player 1", life: STARTING_LIFE, poison: 0, commanderDamage: {} }, @@ -284,10 +337,10 @@ const toggleTwoPlayerMode = () => { // ---------- Event handlers ---------- const handleLifeChange = (event) => { - const btn = event.target.closest('.life-btn'); + const btn = event.target.closest('.life-btn'); const card = event.target.closest('.player'); if (!btn || !card) return; - if (card.classList.contains('is-out')) return; // ignore eliminated + if (card.classList.contains('is-out')) return; const playerIndex = Array.from(mainContainer.children).indexOf(card); const amount = num(btn.dataset.amount, 0); @@ -310,12 +363,9 @@ const handlePoisonChange = (event) => { if (playerIndex < 0) return; const amount = btn.classList.contains('poison-up') ? 1 : -1; - players[playerIndex].poison += amount; + players[playerIndex].poison = clamp(players[playerIndex].poison + amount, 0, POISON_LOSS); - const poisonValue = document.querySelector(`#player${playerIndex + 1} .poison-value`); - if (poisonValue) poisonValue.textContent = players[playerIndex].poison; - - checkElimination(playerIndex); + renderPlayers(); // Rerender to update the value and check elimination saveState(); }; @@ -324,7 +374,7 @@ const handleCommanderChange = (event) => { if (!btn) return; const playerIndex = num(btn.dataset.playerIndex, -1); - const oppIndex = num(btn.dataset.opponentIndex, -1); + const oppIndex = num(btn.dataset.opponentIndex, -1); if (playerIndex < 0 || oppIndex < 0) return; const amount = btn.classList.contains('commander-up') ? 1 : -1; @@ -332,12 +382,9 @@ const handleCommanderChange = (event) => { if (!players[playerIndex].commanderDamage[oppIndex]) { players[playerIndex].commanderDamage[oppIndex] = 0; } - players[playerIndex].commanderDamage[oppIndex] += amount; + players[playerIndex].commanderDamage[oppIndex] = clamp(players[playerIndex].commanderDamage[oppIndex] + amount, 0, COMMANDER_LOSS); - const valEl = document.querySelector(`.commander-value[data-player-index="${playerIndex}"][data-opponent-index="${oppIndex}"]`); - if (valEl) valEl.textContent = players[playerIndex].commanderDamage[oppIndex]; - - checkElimination(playerIndex); + renderPlayers(); // Rerender to update the value and check elimination saveState(); }; @@ -419,7 +466,6 @@ resetButton?.addEventListener('click', async () => { // Poison toggle (normal click) poisonToggleButton?.addEventListener('click', () => { - // If long-press fired, its handler swallows this click in capture phase. isPoisonMatch = !isPoisonMatch; renderPlayers(); saveState(); @@ -470,12 +516,29 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. if (navContainer) navContainer.style.display = 'none'; } +// ---------- Die Roller Modal Logic ---------- +diceButton?.addEventListener('click', () => { + diceModal.style.display = 'flex'; +}); + +closeButton?.addEventListener('click', () => { + diceModal.style.display = 'none'; +}); + +window.addEventListener('click', (event) => { + if (event.target === diceModal) { + diceModal.style.display = 'none'; + } +}); + // ---------- D20 no-jump roller with bowl wobble ---------- (() => { - const die = document.querySelector('.die'); + const dieContainer = document.querySelector('.die-container'); + if (!dieContainer) return; + const die = dieContainer.querySelector('.die'); + if (!die) return; - // Wrap .die in .roller once if (!die.parentElement.classList.contains('roller')) { const roller = document.createElement('div'); roller.className = 'roller'; @@ -484,7 +547,6 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. } const roller = die.parentElement; - // NEW: wrap .roller in .track once (for bowl translation) if (!roller.parentElement.classList.contains('track')) { const track = document.createElement('div'); track.className = 'track'; @@ -497,9 +559,7 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. let lastFace = null; let timer = null; - // Read duration from CSS var (--roll-ms) - const msStr = getComputedStyle(document.documentElement) - .getPropertyValue('--roll-ms').trim(); + const msStr = getComputedStyle(document.documentElement).getPropertyValue('--roll-ms').trim(); const ANIM_MS = Number(msStr.replace('ms','')) || 1500; function randomFace() { @@ -519,14 +579,11 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. function rollOnce(targetFace) { clearTimeout(timer); - // Set final face FIRST (hidden under spin/wobble) const face = typeof targetFace === 'number' ? targetFace : randomFace(); setFace(face); - // Randomize direction of bowl wobble track.classList.toggle('reverse', Math.random() < 0.5); - // Start spin + wobble roller.classList.add('rolling'); track.classList.add('rolling'); @@ -537,21 +594,22 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. }, ANIM_MS); } - // Click to roll - (document.querySelector('.content') || roller).addEventListener('click', () => rollOnce()); + dieContainer.addEventListener('click', (e) => { + // Prevent event from bubbling up to close the modal + e.stopPropagation(); + rollOnce(); + }); - // Hotkeys (optional) document.addEventListener('keydown', (e) => { const k = e.key.toLowerCase(); - if (k === 'r') rollOnce(); - if (k === 'f') { - const v = parseInt(prompt('Roll to face (1–20):') || '', 10); - if (v) rollOnce(v); + if (k === 'r' && diceModal.style.display === 'flex') { + rollOnce(); + e.preventDefault(); } }); if (!die.hasAttribute('data-face')) setFace(1); - window.rollTo = (n) => rollOnce(n); // handy for console/testing + window.rollTo = (n) => rollOnce(n); })(); // ---------- Easter Egg: long-press the skull to toggle Infect Mode ---------- @@ -567,7 +625,7 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. document.body.classList.toggle('infect-mode', on); try { localStorage.setItem('infectMode', on ? '1' : '0'); } catch {} } - // Restore persisted + try { setInfectMode(localStorage.getItem('infectMode') === '1'); } catch {} function onDown(e){ @@ -578,11 +636,13 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. icon.classList.add('egg-hold'); t = setTimeout(()=> { armed = true; }, HOLD_MS); } + function onMove(e){ if (!t) return; const p = (e.touches && e.touches[0]) || e; if (Math.hypot(p.clientX - startX, p.clientY - startY) > MOVE_CANCEL) cancel(); } + function onUp(){ if (!t) return; clearTimeout(t); t = null; @@ -590,14 +650,13 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. if (armed){ armed = false; - swallowClick = true; // prevent the normal click handler after long-press + swallowClick = true; const willEnable = !document.body.classList.contains('infect-mode'); setInfectMode(willEnable); vibrate(willEnable ? [20,40,20] : 28); - // Optional confetti hook const rect = icon.getBoundingClientRect(); const x = rect.left + rect.width/2, y = rect.top + rect.height/2; if (typeof window.confettiBurstAt === 'function') window.confettiBurstAt(x, y, 70); @@ -611,17 +670,16 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. } } } + function cancel(){ clearTimeout(t); t = null; armed = false; icon.classList.remove('egg-hold'); } - // Pointer events (mouse & touch) icon.addEventListener('pointerdown', onDown); icon.addEventListener('pointermove', onMove); ['pointerup','pointercancel','pointerleave'].forEach(ev => icon.addEventListener(ev, onUp)); - // Swallow the click if it immediately follows a successful long-press icon.addEventListener('click', (e)=>{ if (swallowClick){ e.preventDefault(); e.stopImmediatePropagation(); @@ -629,16 +687,57 @@ if (window.matchMedia('(display-mode: standalone)').matches || window.navigator. } }, true); - // Avoid context menu popups on long-press (Android Chrome etc.) icon.addEventListener('contextmenu', e => e.preventDefault()); })(); // ---------- Keyboard: small accessibility niceties ---------- document.addEventListener('keydown', (e) => { - // Ctrl/Cmd+F toggles fullscreen (optional) if (e.code === 'KeyF' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); if (document.fullscreenElement) document.exitFullscreen(); else document.documentElement.requestFullscreen(); } }); + +// --- Fit bowl+die to the toolbar using height and true remaining width --- +(() => { + const bar = document.getElementById('buttonWrapper'); + const dice = bar?.querySelector('.dice'); + + const toNum = v => { + const n = parseFloat(v); + return Number.isFinite(n) ? n : 0; + }; + + function resizeDie(){ + if (!bar || !dice) return; + + const root = document.documentElement; + const csBar = getComputedStyle(bar); + + const innerW = bar.clientWidth - toNum(csBar.paddingLeft) - toNum(csBar.paddingRight); + const innerH = bar.clientHeight - toNum(csBar.paddingTop) - toNum(csBar.paddingBottom); + const gap = toNum(csBar.columnGap || csBar.gap || 0); + + let usedBefore = 0, countBefore = 0; + for (const el of bar.children) { + if (el === dice) break; + usedBefore += el.getBoundingClientRect().width; + countBefore++; + } + if (countBefore > 0) usedBefore += gap * countBefore; + + const remainingW = Math.max(0, innerW - usedBefore); + const ratio = parseFloat(getComputedStyle(root).getPropertyValue('--bowl-ratio')) || 1.55; + + const dieFromHeight = innerH / ratio; + const dieFromWidth = remainingW / ratio; +const die = Math.max(70, Math.min(200, Math.floor(Math.min(dieFromHeight, dieFromWidth)))); root.style.setProperty('--die-size', die + 'px'); + } + + const ro = new ResizeObserver(resizeDie); + if (bar) ro.observe(bar); + window.addEventListener('resize', resizeDie, { passive:true }); + document.addEventListener('visibilitychange', resizeDie); + resizeDie(); +})(); \ No newline at end of file diff --git a/style.css b/style.css index ba5a867..5ffdf31 100644 --- a/style.css +++ b/style.css @@ -1,26 +1,32 @@ /* - MTG Life Counter — Full CSS - - modern player cards with depleting ring - - contained die slot with wooden (or black) bowl - - no duplicate rules / cleaned media queries +  MTG Life Counter — Full CSS +  - modern player cards with depleting ring +  - contained die slot with black bowl +  - no duplicate rules / cleaned media queries */ -/* ===== Base / Resets =================================================== */ -*, -*::before, -*::after { box-sizing: border-box; } - -html, body { height: 100%; } -html { min-height: 100%; margin: 0; } +/* Put a solid fallback on the root */ +html { background-color: #6c7f83; } /* match your gradient's far color */ +/* Let body grow; don't paint the bg here */ body { - background: radial-gradient(circle at 50% 40%, #792f22 0%, #4f644f 47%, #6c7f83 100%); - margin: 0; - color: #333; - user-select: none; - font-family: 'Lucida Grande','Lucida Sans Unicode','Lucida Sans',Geneva,Verdana,sans-serif; + min-height: 100%; + background: transparent; + position: relative; } +/* Fixed, full-viewport gradient layer to kill seams */ +body::before{ + content: ""; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + background: radial-gradient(circle at 50% 40%, #792f22 0%, #4f644f 47%, #6c7f83 100%); + transform: translateZ(0); /* GPU promote to avoid subpixel gaps */ +} + + /* Animatable property for the ring sweep */ @property --life-deg { syntax: ''; @@ -29,289 +35,465 @@ body { } /* ===== Theme Variables ================================================= */ -:root{ +:root { /* layout */ --border-radius: 30px; /* dice / animation */ - --roll-ms: 1500ms; /* change to slow/fast the roll */ - --die-size: 96px; /* change to scale die & bowl together */ + --roll-ms: 1500ms; + --die-base: 96; /* px, the geometry was authored at this size */ + --bowl-ratio: 1.55; /* bowl diameter ÷ die size */ + --die-size: clamp(70px, 32svmin, 200px) !important; + --bowl-size: calc(var(--die-size) * var(--bowl-ratio)); /* cards */ --card-border: hsl(0 0% 100% / 0.16); --ring-thickness: 10px; +} - /* bowl (wood theme) */ - --wood-h: 30; /* hue 20–40 = warm brown */ - --wood-s: 55%; /* saturation 40–70% */ - --wood-l: 35%; /* lightness 30–45% */ - --wood-sheen: .05; /* tiny linear grain */ - --wood-rim: .18; /* rim highlight strength */ +/* Tiny phones: a slightly tighter bowl so the rim never touches edges */ +@media (max-width: 420px) { + :root { + --bowl-ratio: 1.48; + } } /* ===== Main Layout ===================================================== */ -main{ +main { display: flex; flex-wrap: wrap; justify-content: center; align-content: center; width: 100vw; min-height: 100vh; - padding-top: 100px; /* space for toolbar */ - gap: clamp(18px, 4vw, 44px); /* gap between cards */ + padding-top: 100px; + gap: clamp(18px, 4vw, 44px); } /* ===== Toolbar ========================================================= */ -#buttonWrapper{ +#buttonWrapper { position: fixed; top: 10px; left: 50%; transform: translateX(-50%); width: min(92%, 760px); display: flex; - justify-content: space-evenly; align-items: center; + justify-content: space-evenly; + gap: 10px; padding: 6px; border-radius: 20px; background: #eee; opacity: .9; - box-shadow: 0 4px 8px rgba(0,0,0,.2); + box-shadow: 0 4px 8px rgba(0, 0, 0, .2); + overflow: hidden; z-index: 10; } -.icons{ +#buttonWrapper > * { + flex: 0 0 auto; +} + +#buttonWrapper .dice { + min-width: 0; +} + +.icons { font-size: clamp(18px, 1.6vw, 26px); padding: 10px; color: #697069; cursor: pointer; transition: color .2s, transform .2s; } -.icons:hover { color: #333; transform: scale(1.08); } -.icons:active { transform: scale(0.94); } -/* ===== Dice slot + bowl =============================================== */ -/* Dice hitbox – no visual box */ -.dice{ -position: relative; -display: inline-flex; -align-items: center; -justify-content: center; -width: var(--bowl-size); -height: var(--bowl-size); -background: none !important; -border: none !important; -box-shadow: none !important; +.icons:hover { + color: #333; + transform: scale(1.08); } -/* 3D canvas for die (sized by --die-size) */ -.dice .content{ +.icons:active { + transform: scale(0.94); +} + +/* ===== Die Roller Modal ================================================ */ +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 100; /* Sit on top */ + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + justify-content: center; + align-items: center; +} + +.modal-content { + background: linear-gradient(180deg, hsl(0 0% 100% / .10), hsl(0 0% 100% / .05)); + border: 1px solid hsl(0 0% 100% / .16); + padding: 20px; + border-radius: 20px; position: relative; - width: var(--die-size); - height: var(--die-size); - perspective: calc(var(--die-size) * 15); + width: 90%; + max-width: 400px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4); } -/* --- Wooden bowl (default) --- */ -.dice .content::before{ - content:""; - position: absolute; inset: 6px; border-radius: 50%; - z-index: 0; - background: - /* concentric rings */ - repeating-conic-gradient( - at 50% 52%, - hsl(var(--wood-h) var(--wood-s) calc(var(--wood-l) + 7%)) 0deg 5deg, - hsl(var(--wood-h) var(--wood-s) calc(var(--wood-l) - 7%)) 5deg 10deg - ), - /* subtle straight grain */ - repeating-linear-gradient( - 14deg, - rgba(255,255,255,var(--wood-sheen)) 0 2px, - rgba(0,0,0,var(--wood-sheen)) 2px 4px - ), - /* concave shading */ - radial-gradient(120% 120% at 50% 42%, - rgba(255,255,255,.18) 0%, - rgba(255,255,255,.10) 30%, - rgba(0,0,0,.22) 70%, - rgba(0,0,0,.34) 100%); - background-blend-mode: multiply, overlay, normal; - box-shadow: - inset 0 12px 26px rgba(0,0,0,.45), - inset 0 -8px 18px rgba(255,255,255,.08), - 0 2px 8px rgba(0,0,0,.28); +.close-button { + color: #fff; + float: right; + font-size: 36px; + font-weight: bold; + cursor: pointer; + position: absolute; + top: 10px; + right: 20px; + z-index: 101; } -/* rim highlight */ -.dice .content::after{ - content:""; - position: absolute; inset: 0; border-radius: 50%; - z-index: 0; pointer-events: none; - background: - radial-gradient(80% 80% at 50% 25%, - rgba(255,255,255,var(--wood-rim)) 0%, - rgba(255,255,255,0) 60%); - box-shadow: - 0 0 0 2px hsl(var(--wood-h) var(--wood-s) calc(var(--wood-l) - 4%) / .45), - inset 0 0 18px rgba(255,255,255,.08); +.close-button:hover { + color: #ccc; } -/* --- Black bowl variant (add class .bowl--black to .dice) --- */ -.content::before{ -content:""; -position:absolute; inset:6px; / inner lip */ -border-radius:50%; -z-index:0; -background: -radial-gradient(circle at 50% 45%, #4a4a4a 0%, #2a2a2a 62%, #111 80%, #000 100%); -box-shadow: -inset 0 10px 24px rgba(255,255,255,.08), -inset 0 -12px 24px rgba(0,0,0,.65), -0 2px 10px rgba(0,0,0,.55); +.die-container { + display: flex; + justify-content: center; + align-items: center; + height: 300px; + width: 100%; } -/* Subtle rim highlight */ -.content::after{ -content:""; -position:absolute; inset:0; -border-radius:50%; -z-index:0; -background: radial-gradient(circle at 50% 30%, rgba(255,255,255,.14) 0%, rgba(0,0,0,0) 60%); -box-shadow: -0 0 0 2px rgba(255,255,255,.08), -inset 0 0 20px rgba(0,0,0,.6); -pointer-events:none; +.die-container .content { + width: 200px; + height: 200px; + perspective: 2000px; } /* ===== Die (D20) ====================================================== */ +.die-container .content > .track { + position: absolute; + inset: 0; + z-index: 1; + transform: scale(calc(var(--die-size) / (var(--die-base) * 1px))); + transform-origin: 50% 50%; + will-change: transform; +} + +.die-container .content > .track > .roller { + position: absolute; + inset: 0; +} + +/* Bowl background */ +.die-container .content::before { + content: ""; + position: absolute; + inset: clamp(3px, calc(var(--die-size) * .065), 12px); + border-radius: 50%; + z-index: 0; + background: radial-gradient(circle at 50% 45%, #4a4a4a 0%, #2a2a2a 62%, #111 80%, #000 100%); + box-shadow: + inset 0 10px 24px rgba(255, 255, 255, .08), + inset 0 -12px 24px rgba(0, 0, 0, .65), + 0 2px 10px rgba(0, 0, 0, .55); +} + +/* Bowl rim highlight */ +.die-container .content::after { + content: ""; + position: absolute; + inset: 0; + border-radius: 50%; + z-index: 0; + background: radial-gradient(circle at 50% 30%, rgba(255, 255, 255, .14) 0%, rgba(0, 0, 0, 0) 60%); + box-shadow: + 0 0 0 calc(var(--die-size) * .018) rgba(255, 255, 255, .08), + inset 0 0 calc(var(--die-size) * .20) rgba(0, 0, 0, .6); + pointer-events: none; +} /* wrapper that owns the animation */ -.roller{ +.roller { position: absolute; inset: 0; transform-style: preserve-3d; - z-index: 1; /* above bowl */ + z-index: 1; } /* elliptical path that eases back to center */ @keyframes orbit-wobble { - 0% { transform: translate3d(0,0,2px) rotateZ(0deg); } - 15% { transform: translate3d(16px,10px,-4px) rotateZ(.6deg); } - 35% { transform: translate3d(-14px,12px,-2px) rotateZ(-.7deg); } - 55% { transform: translate3d(12px,-10px,1px) rotateZ(.5deg); } - 75% { transform: translate3d(-6px,4px,0) rotateZ(-.25deg); } - 100% { transform: translate3d(0,0,0) rotateZ(0deg); } + 0% { + transform: translate3d(0, 0, 2px) rotateZ(0deg); + } + + 15% { + transform: translate3d(16px, 10px, -4px) rotateZ(.6deg); + } + + 35% { + transform: translate3d(-14px, 12px, -2px) rotateZ(-.7deg); + } + + 55% { + transform: translate3d(12px, -10px, 1px) rotateZ(.5deg); + } + + 75% { + transform: translate3d(-6px, 4px, 0) rotateZ(-.25deg); + } + + 100% { + transform: translate3d(0, 0, 0) rotateZ(0deg); + } } /* clean 0→100% spin */ @keyframes roll { - 0% { transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); } - 25% { transform: rotateX(180deg) rotateY(360deg) rotateZ(0deg); } - 50% { transform: rotateX(360deg) rotateY(720deg) rotateZ(0deg); } - 75% { transform: rotateX(540deg) rotateY(1080deg) rotateZ(0deg); } - 100% { transform: rotateX(720deg) rotateY(1440deg) rotateZ(0deg); } + 0% { + transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); + } + + 25% { + transform: rotateX(180deg) rotateY(360deg) rotateZ(0deg); + } + + 50% { + transform: rotateX(360deg) rotateY(720deg) rotateZ(0deg); + } + + 75% { + transform: rotateX(540deg) rotateY(1080deg) rotateZ(0deg); + } + + 100% { + transform: rotateX(720deg) rotateY(1440deg) rotateZ(0deg); + } } /* run both animations together */ -.roller.rolling{ +.roller.rolling { animation: orbit-wobble var(--roll-ms) ease-in-out, - roll var(--roll-ms) linear; + roll var(--roll-ms) linear; } -.die{ +.die { position: absolute; inset: 0; transform-style: preserve-3d; transition: transform .5s ease-out; transform-origin: 50% 50% 10px; cursor: pointer; - - /* FIX: reset a local counter so numbering starts at 1 here */ counter-reset: d20num 0; } -.die:not([data-face]) { transform: rotateX(-53deg) rotateY(0deg); } -.die[data-face="1"] { transform: rotateX(-53deg) rotateY(0deg); } -.die[data-face="2"] { transform: rotateX(-53deg) rotateY(72deg); } -.die[data-face="3"] { transform: rotateX(-53deg) rotateY(144deg); } -.die[data-face="4"] { transform: rotateX(-53deg) rotateY(216deg); } -.die[data-face="5"] { transform: rotateX(-53deg) rotateY(288deg); } -.die[data-face="16"] { transform: rotateX(127deg) rotateY(-72deg); } -.die[data-face="17"] { transform: rotateX(127deg) rotateY(-144deg); } -.die[data-face="18"] { transform: rotateX(127deg) rotateY(-216deg); } -.die[data-face="19"] { transform: rotateX(127deg) rotateY(-288deg); } -.die[data-face="20"] { transform: rotateX(127deg) rotateY(-360deg); } -.die[data-face="6"] { transform: rotateX(11deg) rotateZ(180deg) rotateY(0deg); } -.die[data-face="7"] { transform: rotateX(11deg) rotateZ(180deg) rotateY(72deg); } -.die[data-face="8"] { transform: rotateX(11deg) rotateZ(180deg) rotateY(144deg); } -.die[data-face="9"] { transform: rotateX(11deg) rotateZ(180deg) rotateY(216deg); } -.die[data-face="10"] { transform: rotateX(11deg) rotateZ(180deg) rotateY(288deg); } -.die[data-face="11"] { transform: rotateX(11deg) rotateY(-252deg); } -.die[data-face="12"] { transform: rotateX(11deg) rotateY(-324deg); } -.die[data-face="13"] { transform: rotateX(11deg) rotateY(-396deg); } -.die[data-face="14"] { transform: rotateX(11deg) rotateY(-468deg); } -.die[data-face="15"] { transform: rotateX(11deg) rotateY(-540deg); } +.die:not([data-face]) { + transform: rotateX(-53deg) rotateY(0deg); +} -.die .face{ +.die[data-face="1"] { + transform: rotateX(-53deg) rotateY(0deg); +} + +.die[data-face="2"] { + transform: rotateX(-53deg) rotateY(72deg); +} + +.die[data-face="3"] { + transform: rotateX(-53deg) rotateY(144deg); +} + +.die[data-face="4"] { + transform: rotateX(-53deg) rotateY(216deg); +} + +.die[data-face="5"] { + transform: rotateX(-53deg) rotateY(288deg); +} + +.die[data-face="16"] { + transform: rotateX(127deg) rotateY(-72deg); +} + +.die[data-face="17"] { + transform: rotateX(127deg) rotateY(-144deg); +} + +.die[data-face="18"] { + transform: rotateX(127deg) rotateY(-216deg); +} + +.die[data-face="19"] { + transform: rotateX(127deg) rotateY(-288deg); +} + +.die[data-face="20"] { + transform: rotateX(127deg) rotateY(-360deg); +} + +.die[data-face="6"] { + transform: rotateX(11deg) rotateZ(180deg) rotateY(0deg); +} + +.die[data-face="7"] { + transform: rotateX(11deg) rotateZ(180deg) rotateY(72deg); +} + +.die[data-face="8"] { + transform: rotateX(11deg) rotateZ(180deg) rotateY(144deg); +} + +.die[data-face="9"] { + transform: rotateX(11deg) rotateZ(180deg) rotateY(216deg); +} + +.die[data-face="10"] { + transform: rotateX(11deg) rotateZ(180deg) rotateY(288deg); +} + +.die[data-face="11"] { + transform: rotateX(11deg) rotateY(-252deg); +} + +.die[data-face="12"] { + transform: rotateX(11deg) rotateY(-324deg); +} + +.die[data-face="13"] { + transform: rotateX(11deg) rotateY(-396deg); +} + +.die[data-face="14"] { + transform: rotateX(11deg) rotateY(-468deg); +} + +.die[data-face="15"] { + transform: rotateY(540deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); +} + +.die .face { position: absolute; left: 50%; top: 0; margin: 0 -12.5px; border-left: 12.5px solid transparent; border-right: 12.5px solid transparent; - border-bottom: 21.5px solid rgba(94,134,91,0.9); - width: 0; height: 0; + border-bottom: 21.5px solid rgba(106, 67, 151, 0.9); + width: 0; + height: 0; transform-style: preserve-3d; backface-visibility: hidden; - - /* FIX: unique counter to avoid clashes (was steps) */ counter-increment: d20num 1; } -.die .face::before{ + +.die .face::before { content: counter(d20num); position: absolute; top: 5.375px; left: -25px; - width: 50px; height: 21.5px; + width: 50px; + height: 21.5px; color: #fff; text-shadow: 1px 1px 3px #000; font-size: 10.75px; - text-align: center; line-height: 19.35px; - transform: translateZ(0.1px); /* avoid z-fighting */ + text-align: center; + line-height: 19.35px; + transform: translateZ(0.1px); } /* Face placement */ -.die .face:nth-child(1) { transform: rotateY(0deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); } -.die .face:nth-child(2) { transform: rotateY(-72deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); } -.die .face:nth-child(3) { transform: rotateY(-144deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); } -.die .face:nth-child(4) { transform: rotateY(-216deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); } -.die .face:nth-child(5) { transform: rotateY(-288deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); } +.die .face:nth-child(1) { + transform: rotateY(0deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); +} -.die .face:nth-child(16) { transform: rotateY(-108deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); } -.die .face:nth-child(17) { transform: rotateY(-36deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); } -.die .face:nth-child(18) { transform: rotateY(36deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); } -.die .face:nth-child(19) { transform: rotateY(108deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); } -.die .face:nth-child(20) { transform: rotateY(180deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); } +.die .face:nth-child(2) { + transform: rotateY(-72deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); +} -.die .face:nth-child(6) { transform: rotateY(360deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); } -.die .face:nth-child(7) { transform: rotateY(288deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); } -.die .face:nth-child(8) { transform: rotateY(216deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); } -.die .face:nth-child(9) { transform: rotateY(144deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); } -.die .face:nth-child(10) { transform: rotateY(72deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); } -.die .face:nth-child(11) { transform: rotateY(252deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); } -.die .face:nth-child(12) { transform: rotateY(324deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); } -.die .face:nth-child(13) { transform: rotateY(396deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); } -.die .face:nth-child(14) { transform: rotateY(468deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); } -.die .face:nth-child(15) { transform: rotateY(540deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); } +.die .face:nth-child(3) { + transform: rotateY(-144deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); +} + +.die .face:nth-child(4) { + transform: rotateY(-216deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); +} + +.die .face:nth-child(5) { + transform: rotateY(-288deg) translateZ(8.375px) translateY(-3.225px) rotateX(53deg); +} + +.die .face:nth-child(16) { + transform: rotateY(-108deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); +} + +.die .face:nth-child(17) { + transform: rotateY(-36deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); +} + +.die .face:nth-child(18) { + transform: rotateY(36deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); +} + +.die .face:nth-child(19) { + transform: rotateY(108deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); +} + +.die .face:nth-child(20) { + transform: rotateY(180deg) translateZ(8.375px) translateY(30.315px) rotateZ(180deg) rotateX(53deg); +} + +.die .face:nth-child(6) { + transform: rotateY(360deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); +} + +.die .face:nth-child(7) { + transform: rotateY(288deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); +} + +.die .face:nth-child(8) { + transform: rotateY(216deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); +} + +.die .face:nth-child(9) { + transform: rotateY(144deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); +} + +.die .face:nth-child(10) { + transform: rotateY(72deg) translateZ(18.75px) translateY(13.545px) rotateZ(180deg) rotateX(-11deg); +} + +.die .face:nth-child(11) { + transform: rotateY(252deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); +} + +.die .face:nth-child(12) { + transform: rotateY(324deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); +} + +.die .face:nth-child(13) { + transform: rotateY(396deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); +} + +.die .face:nth-child(14) { + transform: rotateY(468deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); +} + +.die .face:nth-child(15) { + transform: rotateY(540deg) translateZ(18.75px) translateY(13.545px) rotateX(-11deg); +} /* Optional micro-alignment of face 1 (tweak if needed) */ -:root{ +:root { --face1-ry: 0deg; --face1-dy: 32px; --face1-rz: .6deg; } -.die .face:nth-child(1){ + +.die .face:nth-child(1) { transform: rotateY(calc(0deg + var(--face1-ry))) translateZ(8.375px) @@ -321,20 +503,41 @@ pointer-events:none; } /* ===== Player Cards ==================================================== */ - /* varied accents */ -main .player:nth-child(6n+1){ --accent: hsl(200 100% 65%); } -main .player:nth-child(6n+2){ --accent: hsl(280 100% 70%); } -main .player:nth-child(6n+3){ --accent: hsl(150 95% 54%); } -main .player:nth-child(6n+4){ --accent: hsl(20 100% 65%); } -main .player:nth-child(6n+5){ --accent: hsl(45 100% 62%); } -main .player:nth-child(6n+6){ --accent: hsl(325 100% 68%); } +main .player:nth-child(6n+1) { + --accent: hsl(200 100% 65%); +} + +main .player:nth-child(6n+2) { + --accent: hsl(280 100% 70%); +} + +main .player:nth-child(6n+3) { + --accent: hsl(150 95% 54%); +} + +main .player:nth-child(6n+4) { + --accent: hsl(20 100% 65%); +} + +main .player:nth-child(6n+5) { + --accent: hsl(45 100% 62%); +} + +main .player:nth-child(6n+6) { + --accent: hsl(325 100% 68%); +} /* 2-player: distinct */ -body.is-two-player-mode main .player:nth-child(1){ --accent: #45c5ff; } -body.is-two-player-mode main .player:nth-child(2){ --accent: #b86bff; } +body.is-two-player-mode main .player:nth-child(1) { + --accent: #45c5ff; +} -.player{ +body.is-two-player-mode main .player:nth-child(2) { + --accent: #b86bff; +} + +.player { position: relative; display: flex; flex-direction: column; @@ -349,40 +552,50 @@ body.is-two-player-mode main .player:nth-child(2){ --accent: #b86bff; } transition: transform .18s, box-shadow .18s; max-width: clamp(540px, 46vw, 960px); } -.player:hover{ + +.player:hover { transform: translateY(-2px); box-shadow: 0 16px 40px hsl(0 0% 0% / .33), inset 0 1px 0 hsl(0 0% 100% / .18); } + /* accent edge glow */ -.player::after{ - content:""; - position:absolute; inset:-1px; border-radius: 20px; pointer-events:none; +.player::after { + content: ""; + position: absolute; + inset: -1px; + border-radius: 20px; + pointer-events: none; background: linear-gradient(135deg, transparent 20%, color-mix(in oklab, var(--accent), white 25%) 50%, transparent 80%); - opacity:.25; + opacity: .25; } /* name pill */ -.name{ +.name { width: 100%; max-width: 360px; padding: 10px 16px; border-radius: 14px; background: hsl(0 0% 100% / .08); border: 1px solid hsl(0 0% 100% / .18); - color: #fff; text-align: center; + color: #fff; + text-align: center; font-size: clamp(16px, 2.2vw, 20px); - outline: none; caret-color: var(--accent); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + outline: none; + caret-color: var(--accent); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; transition: box-shadow .18s, border-color .18s, background .18s; } -.name:focus{ + +.name:focus { background: hsl(0 0% 100% / .12); border-color: color-mix(in oklab, var(--accent), white 30%); box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent), transparent 80%); } /* life area */ -.life{ +.life { display: grid; grid-template-columns: auto 1fr auto; align-items: center; @@ -391,123 +604,281 @@ body.is-two-player-mode main .player:nth-child(2){ --accent: #b86bff; } border: 0; padding: 6px 0; } -.vert{ display:flex; flex-direction:column; gap: 8px; align-items:center; } + +.vert { + display: flex; + flex-direction: column; + gap: 8px; + align-items: center; +} /* ringed number */ -.life-count{ +.life-count { position: relative; width: clamp(96px, 14vw, 142px); height: clamp(96px, 14vw, 142px); border-radius: 50%; - display: grid; place-items: center; - color: #fff; font-weight: 800; + display: grid; + place-items: center; + color: #fff; + font-weight: 800; text-shadow: 0 1px 0 hsl(0 0% 0% / .35); font-size: clamp(36px, 8vh, 64px); isolation: isolate; transition: --life-deg 280ms linear; } -.life-count::before{ - content:""; position:absolute; inset:0; border-radius: inherit; z-index:-2; - background: conic-gradient(from -90deg, var(--accent) 0 var(--life-deg, calc(var(--life-pct,1)*1turn)), hsl(0 0% 100% / .10) 0 360deg); + +.life-count::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + z-index: -2; + background: conic-gradient(from -90deg, var(--accent) 0 var(--life-deg, calc(var(--life-pct, 1)*1turn)), hsl(0 0% 100% / .10) 0 360deg); filter: drop-shadow(0 4px 10px color-mix(in oklab, var(--accent), black 70%)); } -.life-count::after{ - content:""; position:absolute; inset: var(--ring-thickness); border-radius: inherit; z-index:-1; + +.life-count::after { + content: ""; + position: absolute; + inset: var(--ring-thickness); + border-radius: inherit; + z-index: -1; background: hsl(0 0% 100% / .06); border: 1px solid hsl(0 0% 100% / .12); } /* life buttons */ -.life-btn{ - width: 44px; height: 44px; border-radius: 50%; - display:grid; place-items:center; - color:#fff; font-size: 20px; cursor: pointer; +.life-btn { + width: 44px; + height: 44px; + border-radius: 50%; + display: grid; + place-items: center; + color: #fff; + font-size: 20px; + cursor: pointer; background: linear-gradient(180deg, hsl(0 0% 100% / .18), hsl(0 0% 100% / .06)); border: 1px solid hsl(0 0% 100% / .18); box-shadow: 0 6px 16px hsl(0 0% 0% / .25), inset 0 1px 0 hsl(0 0% 100% / .25); transition: transform .12s, box-shadow .12s, background .12s; } -.life-btn:hover{ + +.life-btn:hover { transform: translateY(-1px); box-shadow: 0 10px 20px hsl(0 0% 0% / .35), inset 0 1px 0 hsl(0 0% 100% / .30); } -.life-btn:active{ transform: translateY(0) scale(.97); } -.life-up-1,.life-up-5{ color: color-mix(in oklab, var(--accent), white 10%); } -.life-down-1,.life-down-5{ color: hsl(0 90% 60%); } + +.life-btn:active { + transform: translateY(0) scale(.97); +} + +.life-up-1, +.life-up-5 { + color: color-mix(in oklab, var(--accent), white 10%); +} + +.life-down-1, +.life-down-5 { + color: hsl(0 90% 60%); +} /* special counters */ -.special-counters{ - width: 100%; max-width: 520px; padding-top: 10px; background: none; gap: 10px; +.special-counters { + width: 100%; + max-width: 520px; + padding-top: 10px; + background: none; + gap: 10px; } + .poison-counter, -.commander-damage-counter{ - display:flex; align-items:center; gap:10px; +.commander-damage-counter { + display: flex; + align-items: center; + gap: 10px; background: hsl(0 0% 100% / .07); border: 1px solid hsl(0 0% 100% / .14); border-radius: 12px; padding: 8px 10px; - color:#fff; + color: #fff; font-size: clamp(12px, 1.6vw, 16px); } -.commander-name{ - flex:1 1 auto; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; opacity:.9; -} -.commander-controls{ display:inline-flex; align-items:center; gap:6px; } -.poison-btn, .commander-btn{ - width:28px; height:28px; min-width:28px; min-height:28px; - display:grid; place-items:center; - font-size:14px; line-height:1; padding:0; border-radius:8px; - color:#fff; cursor:pointer; +.commander-name { + flex: 1 1 auto; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + opacity: .9; +} + +.commander-controls { + display: inline-flex; + align-items: center; + gap: 6px; +} + +.poison-btn, +.commander-btn { + width: 28px; + height: 28px; + min-width: 28px; + min-height: 28px; + display: grid; + place-items: center; + font-size: 14px; + line-height: 1; + padding: 0; + border-radius: 8px; + color: #fff; + cursor: pointer; background: hsl(0 0% 100% / .12); border: 1px solid hsl(0 0% 100% / .18); transition: transform .1s, background .1s; } -.poison-btn:hover, .commander-btn:hover{ transform: translateY(-1px); } -.poison-value, .commander-value{ min-width: 1.8ch; text-align:center; color:#fff; } + +.poison-btn:hover, +.commander-btn:hover { + transform: translateY(-1px); +} + +.poison-value, +.commander-value { + min-width: 1.8ch; + text-align: center; + color: #fff; +} /* poison: label left, controls dock right */ -.poison-counter .counter-label{ margin-right: 8px; } -.poison-counter .poison-down{ margin-left: auto; } -.poison-counter .poison-value{ min-width:2ch; } +.poison-counter .counter-label { + margin-right: 8px; +} + +.poison-counter .poison-down { + margin-left: auto; +} + +.poison-counter .poison-value { + min-width: 2ch; +} /* commander panel show/hide */ -body.is-commander-mode .commander-damage { display: block; } -.commander-damage { display: none; } +body.is-commander-mode .commander-damage { + display: block; +} + +.commander-damage { + display: none; +} /* zero-life style */ -.player[data-life-zero="true"] .life-count{ +.player[data-life-zero="true"] .life-count { color: hsl(0 100% 70%); filter: drop-shadow(0 0 18px hsl(0 100% 60% / .45)); } -.player[data-life-zero="true"] .life-count::before{ + +.player[data-life-zero="true"] .life-count::before { background: conic-gradient(hsl(0 100% 62%) 360deg, transparent 0); } /* ===== Responsive ====================================================== */ -@media (orientation: landscape) and (max-height: 500px){ - .player{ padding: 20px; } - .life{ gap: 12px; } - .life-count{ width:150px; height:150px; font-size: clamp(44px,5vw,78px); --ring-thickness:12px; } - .life-btn{ width:52px; height:52px; font-size:24px; } - .name{ font-size: clamp(16px, 2.4vw, 22px); } +@media (orientation: landscape) and (max-height: 500px) { + .player { + padding: 20px; + } + + .life { + gap: 12px; + } + + .life-count { + width: 150px; + height: 150px; + font-size: clamp(44px, 5vw, 78px); + --ring-thickness: 12px; + } + + .life-btn { + width: 52px; + height: 52px; + font-size: 24px; + } + + .name { + font-size: clamp(16px, 2.4vw, 22px); + } } -@media (min-width: 768px){ - .player{ padding: 22px; border-radius: 22px; } - .life{ gap: 14px; } - .life-count{ width:180px; height:180px; font-size: clamp(52px,4.6vw,86px); --ring-thickness:14px; } - .life-btn{ width:58px; height:58px; font-size:26px; } - .special-counters{ max-width: 640px; } - .poison-counter,.commander-damage-counter{ font-size:16px; } - .poison-btn,.commander-btn{ width:32px; height:32px; font-size:16px; } + +@media (min-width: 768px) { + .player { + padding: 22px; + border-radius: 22px; + } + + .life { + gap: 14px; + } + + .life-count { + width: 180px; + height: 180px; + font-size: clamp(52px, 4.6vw, 86px); + --ring-thickness: 14px; + } + + .life-btn { + width: 58px; + height: 58px; + font-size: 26px; + } + + .special-counters { + max-width: 640px; + } + + .poison-counter, + .commander-damage-counter { + font-size: 16px; + } + + .poison-btn, + .commander-btn { + width: 32px; + height: 32px; + font-size: 16px; + } } -@media (min-width: 1200px){ - .life-count{ width:220px; height:220px; font-size: clamp(64px,3.6vw,96px); --ring-thickness:16px; } - .life-btn{ width:64px; height:64px; font-size:30px; } + +@media (min-width: 1200px) { + .life-count { + width: 220px; + height: 220px; + font-size: clamp(64px, 3.6vw, 96px); + --ring-thickness: 16px; + } + + .life-btn { + width: 64px; + height: 64px; + font-size: 30px; + } } -@media (min-width: 1600px){ - .life-count{ width:260px; height:260px; font-size: clamp(72px,3vw,112px); --ring-thickness:18px; } - .life-btn{ width:70px; height:70px; font-size:32px; } + +@media (min-width: 1600px) { + .life-count { + width: 260px; + height: 260px; + font-size: clamp(72px, 3vw, 112px); + --ring-thickness: 18px; + } + + .life-btn { + width: 70px; + height: 70px; + font-size: 32px; + } } /* ===== Infect Mode (secret) ===== */ @@ -515,33 +886,37 @@ body.infect-mode { filter: hue-rotate(-25deg) saturate(1.25); } -body.infect-mode .life-count::before{ - background: - conic-gradient( - from -90deg, - #35ff96 0 var(--life-deg, calc(var(--life-pct, 1)*1turn)), - rgba(255,255,255,0.10) 0 360deg - ); +body.infect-mode .life-count::before { + background: conic-gradient(from -90deg, #35ff96 0 var(--life-deg, calc(var(--life-pct, 1)*1turn)), rgba(255, 255, 255, 0.10) 0 360deg); filter: drop-shadow(0 6px 16px rgba(11, 232, 129, 0.55)); } -body.infect-mode .poison-counter{ +body.infect-mode .poison-counter { border-color: rgba(53, 255, 150, 0.35); box-shadow: 0 0 12px rgba(53, 255, 150, 0.25) inset; } -body.infect-mode .poison-btn{ - background: linear-gradient(180deg, rgba(53,255,150,.22), rgba(53,255,150,.12)); - border-color: rgba(53,255,150,.45); +body.infect-mode .poison-btn { + background: linear-gradient(180deg, rgba(53, 255, 150, .22), rgba(53, 255, 150, .12)); + border-color: rgba(53, 255, 150, .45); } /* Skull hint while holding */ -#poison-toggle.egg-hold{ +#poison-toggle.egg-hold { animation: skull-hold 1.2s linear; - filter: drop-shadow(0 0 10px rgba(53,255,150,.45)); + filter: drop-shadow(0 0 10px rgba(53, 255, 150, .45)); } -@keyframes skull-hold{ - 0%{ transform: scale(1); } - 90%{ transform: scale(1.12); } - 100%{ transform: scale(1.08); } + +@keyframes skull-hold { + 0% { + transform: scale(1); + } + + 90% { + transform: scale(1.12); + } + + 100% { + transform: scale(1.08); + } } \ No newline at end of file