diff --git a/server.ts b/server.ts index cacb8e9e129ffc111e45c4d6d1abefd706ef2f0b..39639e64651d927f35ccbd200eb0c3834a44bd68 100644 --- a/server.ts +++ b/server.ts @@ -542,6 +542,10 @@ serve(async (req) => { const css = await Deno.readTextFile("./static/style.css"); return new Response(css, { headers: { "content-type": "text/css" } }); } + if (url.pathname === "/palette.css") { + const css = await Deno.readTextFile("./static/palette.css"); + return new Response(css, { headers: { "content-type": "text/css" } }); + } if (url.pathname === "/app.js") { const js = await Deno.readTextFile("./static/app.js"); return new Response(js, { headers: { "content-type": "application/javascript" } }); diff --git a/static/app.js b/static/app.js index b364517bbee794c02815d730f6bf455550a2acdf..a4732da6d60fee072cfc2bd35bf0be06bf834741 100644 --- a/static/app.js +++ b/static/app.js @@ -50,6 +50,9 @@ document.getElementById('leave-btn').addEventListener('click', () => { items = []; messageQueue = []; listContainer.innerHTML = ''; + + // Clear URL when leaving room + history.replaceState(null, '', location.pathname); }); document.getElementById('copy-btn').addEventListener('click', async () => { @@ -229,6 +232,11 @@ function handleMessage(msg) { } isReady = true; setUIEnabled(true); + + // Update URL to include room code for easy copying + const newUrl = `${location.origin}${location.pathname}?room=${currentRoom}`; + history.replaceState(null, '', newUrl); + renderTitle(); render(); break; @@ -305,6 +313,13 @@ function vote(itemId, voteType) { const sorted = getSortedItems(); selectedPosition = sorted.findIndex(i => i.id === itemId); + // Keep the button visually pressed briefly to avoid flicker + const btn = document.querySelector(`[data-item-id="${itemId}"] [data-action="vote"][data-vote-type="${voteType}"]`); + if (btn) { + btn.classList.add('press-lock'); + setTimeout(() => btn.classList.remove('press-lock'), 220); + } + lastAction = { type: 'vote', itemId, previousVote: currentVote }; if (currentVote === voteType) { @@ -569,15 +584,31 @@ function render() { // Sort: vetoed items last, then by score const sorted = getSortedItems(); + // Check if any votes have been cast + const hasAnyVotes = sorted.some(item => Object.keys(item.votes).length > 0); + + // Calculate highest score among non-vetoed items + const nonVetoedItems = sorted.filter(item => + !Object.values(item.votes).includes('veto') + ); + const highestScore = nonVetoedItems.length > 0 + ? Math.max(...nonVetoedItems.map(item => + Object.values(item.votes).reduce((sum, v) => + sum + (v === 'up' ? 1 : v === 'down' ? -1 : 0), 0) + )) + : -Infinity; + listContainer.innerHTML = sorted.map(item => { const myVote = item.votes[userId]; const score = Object.values(item.votes).reduce((sum, v) => sum + (v === 'up' ? 1 : v === 'down' ? -1 : 0), 0); const isVetoed = Object.values(item.votes).includes('veto'); const isSelected = item.id === selectedItemId; + const isTopVoted = hasAnyVotes && !isVetoed && score === highestScore && nonVetoedItems.length > 0; return ` -
+
+ ${isTopVoted ? '' : ''}
${escapeHtml(item.text)}
${score > 0 ? '+' : ''}${score}
@@ -662,6 +693,15 @@ listContainer.addEventListener('dblclick', (e) => { editItem(itemId); }); +// Minimal press-lock for non-vote buttons to smooth release +document.addEventListener('click', (e) => { + const btn = e.target.closest('button'); + if (!btn) return; + if (btn.classList.contains('vote-btn')) return; // handled in vote() + btn.classList.add('press-lock'); + setTimeout(() => btn.classList.remove('press-lock'), 180); +}); + // Make functions global window.vote = vote; window.deleteItem = deleteItem; diff --git a/static/index.html b/static/index.html index 6df8731dc25e52aa79aa35c8398411c0a42a4782..94e15797ca7340aa7d8d6b1ee70b20c19822fc38 100644 --- a/static/index.html +++ b/static/index.html @@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later Sift + diff --git a/static/palette.css b/static/palette.css new file mode 100644 index 0000000000000000000000000000000000000000..ce44f16fe75b8b777f0d23cd9b502da643f73249 --- /dev/null +++ b/static/palette.css @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: Amolith + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/* + Colors generated by Theme Machine | MIT License + https://tools.keithjgrant.com/theme-machine/ +*/ +:root { + --stone-1: oklch(98% 0.003 121); + --stone-2: oklch(97% 0.006 121); + --stone-3: oklch(93% 0.01 121); + --stone-4: oklch(84% 0.011 121); + --stone-5: oklch(80% 0.015 121); + --stone-6: oklch(71% 0.018 121); + --stone-7: oklch(66% 0.019 121); + --stone-8: oklch(58% 0.02 121); + --stone-9: oklch(53% 0.019 121); + --stone-10: oklch(49% 0.018 121); + --stone-11: oklch(42% 0.016 121); + --stone-12: oklch(35% 0.014 121); + --stone-13: oklch(27% 0.011 121); + --stone-14: oklch(20% 0.009 121); + --stone-15: oklch(16% 0.007 121); + --stone-16: oklch(10% 0.005 121); + --stone: var(--stone-8); + + --ash-1: oklch(98% 0.003 301); + --ash-2: oklch(97% 0.006 301); + --ash-3: oklch(93% 0.01 301); + --ash-4: oklch(84% 0.011 301); + --ash-5: oklch(80% 0.015 301); + --ash-6: oklch(71% 0.018 301); + --ash-7: oklch(66% 0.019 301); + --ash-8: oklch(58% 0.02 301); + --ash-9: oklch(53% 0.019 301); + --ash-10: oklch(49% 0.018 301); + --ash-11: oklch(42% 0.016 301); + --ash-12: oklch(35% 0.014 301); + --ash-13: oklch(27% 0.011 301); + --ash-14: oklch(20% 0.009 301); + --ash-15: oklch(16% 0.007 301); + --ash-16: oklch(10% 0.005 301); + --ash: var(--ash-8); + + --avocado-1: oklch(98% 0.018 121); + --avocado-2: oklch(97% 0.038 121); + --avocado-3: oklch(93% 0.062 121); + --avocado-4: oklch(84% 0.074 121); + --avocado-5: oklch(80% 0.099 121); + --avocado-6: oklch(71% 0.117 121); + --avocado-7: oklch(66% 0.124 121); + --avocado-8: oklch(58% 0.13 121); + --avocado-9: oklch(53% 0.124 121); + --avocado-10: oklch(49% 0.117 121); + --avocado-11: oklch(42% 0.105 121); + --avocado-12: oklch(35% 0.092 121); + --avocado-13: oklch(27% 0.074 121); + --avocado-14: oklch(20% 0.056 121); + --avocado-15: oklch(16% 0.043 121); + --avocado-16: oklch(10% 0.028 122); + --avocado: var(--avocado-8); + + --lilac-1: oklch(97.9% 0.014 306); + --lilac-2: oklch(96.7% 0.024 311); + --lilac-3: oklch(92.7% 0.049 306); + --lilac-4: oklch(84% 0.074 301); + --lilac-5: oklch(80% 0.099 301); + --lilac-6: oklch(71% 0.117 301); + --lilac-7: oklch(66% 0.124 301); + --lilac-8: oklch(58% 0.13 301); + --lilac-9: oklch(53% 0.124 301); + --lilac-10: oklch(49% 0.117 301); + --lilac-11: oklch(42% 0.105 301); + --lilac-12: oklch(35% 0.092 301); + --lilac-13: oklch(27% 0.074 301); + --lilac-14: oklch(20% 0.056 301); + --lilac-15: oklch(16% 0.043 301); + --lilac-16: oklch(10% 0.031 301); + --lilac: var(--lilac-8); + + --cranberry-1: oklch(98% 0.01 25); + --cranberry-2: oklch(96% 0.03 25); + --cranberry-3: oklch(92% 0.06 25); + --cranberry-4: oklch(84% 0.09 25); + --cranberry-5: oklch(80% 0.12 25); + --cranberry-6: oklch(72% 0.15 25); + --cranberry-7: oklch(66% 0.17 25); + --cranberry-8: oklch(58% 0.18 25); + --cranberry-9: oklch(53% 0.19 25); + --cranberry-10: oklch(49% 0.18 25); + --cranberry-11: oklch(42% 0.16 25); + --cranberry-12: oklch(35% 0.14 25); + --cranberry-13: oklch(28% 0.12 25); + --cranberry-14: oklch(22% 0.1 25); + --cranberry-15: oklch(17% 0.08 25); + --cranberry-16: oklch(12% 0.06 25); + --cranberry: var(--cranberry-8); + --danger-color: var(--cranberry-9); + + --background-1: var(--stone-3); + --background-2: var(--stone-2); + --background-3: var(--ash-4); + --background-dark-1: var(--stone-14); + --background-dark-2: var(--stone-13); + --background-dark-3: var(--stone-11); + --text-1: var(--stone-12); + --text-2: var(--stone-13); + --brand-color: var(--avocado-8); + --shadow-color: var(--stone-16); +} \ No newline at end of file diff --git a/static/style.css b/static/style.css index 67b11c04ce2519dddf64e3cbfa5ecf766ae78012..a1ca4e433f54e8181a37db78c756b72c03c1dd92 100644 --- a/static/style.css +++ b/static/style.css @@ -6,43 +6,6 @@ :root { color-scheme: light dark; - - /* Light theme (default) */ - --bg-page: #f5f5f5; - --bg-surface: white; - --bg-header-code: #e8f5e0; - - --text-primary: #333; - --text-on-accent: white; - - --accent: #5a8c3a; - --accent-hover: #4a7230; - --accent-dark: #2d5016; - --accent-danger: #b22222; - - --border: #ddd; - --border-accent: #5a8c3a; - --shadow: rgba(0, 0, 0, 0.1); -} - -@media (prefers-color-scheme: dark) { - :root { - --bg-page: #1a1a1a; - --bg-surface: #2a2a2a; - --bg-header-code: #3a4a34; - - --text-primary: #e0e0e0; - --text-on-accent: white; - - --accent: #7ab859; - --accent-hover: #8cc96f; - --accent-dark: #5a8c3a; - --accent-danger: #d84444; - - --border: #444; - --border-accent: #7ab859; - --shadow: rgba(0, 0, 0, 0.3); - } } * { @@ -53,8 +16,8 @@ body { font-family: "Atkinson Hyperlegible Next", system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif; - background: var(--bg-page); - color: var(--text-primary); + background: var(--background-1); + color: var(--text-1); line-height: 1.6; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -78,7 +41,7 @@ body { #start-screen h1 { margin-bottom: 3rem; - color: var(--accent-dark); + color: var(--avocado-11); } .buttons { @@ -95,43 +58,69 @@ body { button { padding: 0.75rem 1.5rem; - background: var(--accent); - color: var(--text-on-accent); + background: var(--brand-color); + color: var(--stone-1); border: none; - border-radius: 6px; + border-radius: 8px; cursor: pointer; font-size: 1rem; - transition: background 0.2s, transform 0.05s, box-shadow 0.2s; + position: relative; + --btn-color: var(--brand-color); + transform: translateY(0); + box-shadow: + 0 6px 0 color-mix(in oklch, var(--btn-color) 60%, black), + 0 12px 18px color-mix(in oklch, var(--stone-16) 30%, transparent), + inset 0 1px 0 color-mix(in oklch, white 20%, transparent); + transition: + transform 140ms cubic-bezier(0.2, 0.7, 0.1, 1), + box-shadow 140ms cubic-bezier(0.2, 0.7, 0.1, 1), + background 140ms ease; } button:hover { - background: var(--accent-hover); + box-shadow: + 0 7px 0 color-mix(in oklch, var(--btn-color) 60%, black), + 0 14px 20px color-mix(in oklch, var(--stone-16) 35%, transparent), + inset 0 1px 0 color-mix(in oklch, white 25%, transparent); } button:active { - transform: translateY(1px); + transform: translateY(4px); + box-shadow: + 0 2px 0 color-mix(in oklch, var(--btn-color) 60%, black), + 0 6px 12px color-mix(in oklch, var(--stone-16) 25%, transparent), + inset 0 -1px 0 color-mix(in oklch, var(--btn-color) 50%, black); +} + +button.press-lock { + transform: translateY(4px); + box-shadow: + 0 2px 0 color-mix(in oklch, var(--btn-color) 60%, black), + 0 6px 12px color-mix(in oklch, var(--stone-16) 25%, transparent), + inset 0 -1px 0 color-mix(in oklch, var(--btn-color) 50%, black); } button:focus-visible, input:focus-visible, textarea:focus-visible { - outline: 2px solid var(--border-accent); + outline: 2px solid var(--avocado-7); outline-offset: 2px; } -input, textarea { +input, +textarea { padding: 0.75rem; - border: 1px solid var(--border); + border: 1px solid var(--stone-5); border-radius: 6px; font-size: 1rem; font-family: inherit; - background: var(--bg-surface); - color: var(--text-primary); + background: var(--stone-1); + color: var(--text-1); } input::placeholder, textarea::placeholder { - color: color-mix(in srgb, var(--text-primary) 50%, transparent); + color: color-mix(in oklch, var(--text-1) 50%, transparent); } #join-code { @@ -146,7 +135,7 @@ textarea::placeholder { .expiry-notice { font-size: 0.85rem; - color: color-mix(in srgb, var(--text-primary) 60%, transparent); + color: color-mix(in oklch, var(--text-1) 60%, transparent); margin-top: 1rem; } @@ -166,7 +155,7 @@ textarea::placeholder { align-items: center; margin-bottom: 2rem; padding-bottom: 1rem; - border-bottom: 2px solid var(--border-accent); + border-bottom: 2px solid var(--brand-color); } #list-screen header h2 { @@ -178,33 +167,37 @@ textarea::placeholder { align-items: center; gap: 0.5rem; font-size: 0.85rem; - color: color-mix(in srgb, var(--text-primary) 70%, transparent); + color: color-mix(in oklch, var(--text-1) 70%, transparent); } .status-dot { width: 8px; height: 8px; border-radius: 50%; - background: #888; + background: var(--stone-8); } .status-dot.connected { - background: #4caf50; - box-shadow: 0 0 4px #4caf50; + background: var(--brand-color); + box-shadow: 0 0 4px color-mix(in oklch, var(--brand-color) 60%, transparent); } .status-dot.connecting { - background: #ff9800; + background: var(--cranberry-7); animation: pulse 1.5s ease-in-out infinite; } .status-dot.disconnected { - background: #f44336; + background: var(--cranberry-12); } @keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } } .header-actions { @@ -214,15 +207,19 @@ textarea::placeholder { #room-code { font-family: monospace; - background: var(--bg-header-code); + background: var(--background-2); padding: 0.25rem 0.5rem; border-radius: 6px; } -#copy-btn, #copy-link-btn, #set-title-btn, #reset-votes-btn { - background: transparent; - color: var(--text-primary); - border: 1px solid var(--border); +#copy-btn, +#copy-link-btn, +#set-title-btn, +#reset-votes-btn { + background: var(--stone-1); + --btn-color: var(--stone-1); + color: var(--text-1); + border: none; width: 40px; height: 40px; padding: 0; @@ -231,9 +228,11 @@ textarea::placeholder { place-items: center; } -#copy-btn:hover, #copy-link-btn:hover, #set-title-btn:hover, #reset-votes-btn:hover { - background: var(--bg-header-code); - border-color: var(--border-accent); +#copy-btn:hover, +#copy-link-btn:hover, +#set-title-btn:hover, +#reset-votes-btn:hover { + filter: brightness(1.03); } .header-actions button svg { @@ -243,13 +242,14 @@ textarea::placeholder { } #leave-btn { - background: transparent; - color: var(--accent-dark); - border: 1px solid var(--border-accent); + background: var(--stone-1); + --btn-color: var(--stone-1); + color: var(--avocado-12); + border: none; } #leave-btn:hover { - background: var(--bg-header-code); + filter: brightness(1.03); } .add-section { @@ -280,11 +280,12 @@ textarea::placeholder { } .list-item { - background: var(--bg-surface); + position: relative; + background: var(--stone-1); padding: 1rem; margin-bottom: 0.5rem; border-radius: 8px; - box-shadow: 0 1px 3px var(--shadow); + box-shadow: 0 1px 3px color-mix(in oklch, var(--shadow-color) 20%, transparent); display: flex; justify-content: space-between; align-items: center; @@ -294,12 +295,13 @@ textarea::placeholder { .list-item:hover { transform: translateY(-1px); - box-shadow: 0 4px 12px var(--shadow); + box-shadow: 0 4px 12px color-mix(in oklch, var(--shadow-color) 25%, transparent); } .list-item.selected { - outline: 2px solid var(--border-accent); + outline: 2px solid var(--brand-color); outline-offset: 2px; + border-radius: 10px; } .list-item.vetoed { @@ -308,13 +310,15 @@ textarea::placeholder { overflow: hidden; } -.list-item:has(.vote-btn.active) { - border-left-color: var(--accent); +.top-check { + position: absolute; + left: -40px; + top: 50%; + transform: translateY(-50%); + color: var(--avocado-9); + flex-shrink: 0; } -.list-item:has(.vote-btn.veto-active) { - border-left-color: var(--accent-danger); -} .list-item-text { flex: 1; @@ -346,25 +350,40 @@ textarea::placeholder { .delete-btn { padding: 0.5rem; font-size: 0.9rem; - background: var(--accent-danger); - opacity: 0.7; + background: var(--danger-color); + --btn-color: var(--danger-color); + color: var(--stone-1); + opacity: 0.8; transition: opacity 0.2s, background 0.2s; } .delete-btn:hover { opacity: 1; - background: var(--accent-danger); + background: var(--cranberry-8); } .vote-btn.active { - background: var(--accent-dark); + background: var(--avocado-11); + color: var(--stone-1); + transform: translateY(4px); + box-shadow: + 0 2px 0 color-mix(in oklch, var(--btn-color) 60%, black), + 0 6px 12px color-mix(in oklch, var(--stone-16) 25%, transparent), + inset 0 -1px 0 color-mix(in oklch, var(--btn-color) 50%, black); } .vote-btn.veto-active { - background: var(--accent-danger); + background: var(--avocado-11); + color: var(--stone-1); + transform: translateY(4px); + box-shadow: + 0 2px 0 color-mix(in oklch, var(--btn-color) 60%, black), + 0 6px 12px color-mix(in oklch, var(--stone-16) 25%, transparent), + inset 0 -1px 0 color-mix(in oklch, var(--btn-color) 50%, black); } -.vote-btn svg, .delete-btn svg { +.vote-btn svg, +.delete-btn svg { width: 16px; height: 16px; display: block; @@ -374,8 +393,8 @@ textarea::placeholder { min-width: 40px; text-align: center; font-weight: bold; - color: var(--accent); - background: var(--bg-header-code); + color: var(--brand-color); + background: var(--stone-2); padding: 0.25rem 0.5rem; border-radius: 999px; } @@ -387,7 +406,7 @@ textarea::placeholder { left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.5); + background: color-mix(in oklch, var(--stone-16) 60%, transparent); display: flex; align-items: center; justify-content: center; @@ -395,14 +414,14 @@ textarea::placeholder { } .modal-content { - background: var(--bg-surface); + background: var(--stone-1); padding: 2rem; border-radius: 8px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; - box-shadow: 0 4px 20px var(--shadow); + box-shadow: 0 4px 20px color-mix(in oklch, var(--shadow-color) 35%, transparent); } .modal-header { @@ -411,17 +430,17 @@ textarea::placeholder { align-items: center; margin-bottom: 1.5rem; padding-bottom: 0.5rem; - border-bottom: 2px solid var(--border); + border-bottom: 2px solid var(--stone-5); } .modal-header h3 { margin: 0; - color: var(--accent-dark); + color: var(--avocado-12); } .modal-close { background: transparent; - color: var(--text-primary); + color: var(--text-1); border: none; font-size: 2rem; line-height: 1; @@ -432,11 +451,11 @@ textarea::placeholder { } .modal-close:hover { - color: var(--accent-danger); + color: var(--cranberry-9); } .modal-body h4 { - color: var(--accent); + color: var(--brand-color); margin-top: 1rem; margin-bottom: 0.5rem; } @@ -459,10 +478,10 @@ kbd { padding: 0.2rem 0.4rem; font-size: 0.85rem; font-family: monospace; - background: var(--bg-header-code); - border: 1px solid var(--border); + background: var(--background-2); + border: 1px solid var(--stone-5); border-radius: 4px; - box-shadow: 0 1px 2px var(--shadow); + box-shadow: 0 1px 2px color-mix(in oklch, var(--shadow-color) 20%, transparent); } /* Screen reader only */ @@ -484,3 +503,165 @@ kbd { animation: none !important; } } + +@media (prefers-color-scheme: dark) { + body { + background: var(--background-dark-1); + color: var(--stone-3); + } + + button { + background: var(--avocado-9); + --btn-color: var(--avocado-9); + color: var(--stone-1); + } + + button:hover { + background: var(--avocado-8); + } + + input, + textarea { + background: var(--background-dark-2); + border: 1px solid var(--stone-9); + color: var(--stone-3); + } + + input::placeholder, + textarea::placeholder { + color: color-mix(in oklch, var(--stone-3) 50%, transparent); + } + + .expiry-notice { + color: color-mix(in oklch, var(--stone-3) 60%, transparent); + } + + #list-screen header { + border-bottom: 2px solid var(--avocado-9); + } + + .connection-status { + color: color-mix(in oklch, var(--stone-3) 60%, transparent); + } + + .status-dot { + background: var(--stone-10); + } + + .status-dot.connected { + background: var(--avocado-9); + box-shadow: 0 0 4px color-mix(in oklch, var(--avocado-9) 60%, transparent); + } + + .status-dot.connecting { + background: var(--cranberry-8); + } + + .status-dot.disconnected { + background: var(--cranberry-13); + } + + #room-code { + background: var(--background-dark-2); + color: var(--stone-3); + } + + #copy-btn, + #copy-link-btn, + #set-title-btn, + #reset-votes-btn { + background: var(--background-dark-2); + --btn-color: var(--background-dark-2); + color: var(--stone-3); + border: none; + } + + #copy-btn:hover, + #copy-link-btn:hover, + #set-title-btn:hover, + #reset-votes-btn:hover { + filter: brightness(1.04); + } + + #leave-btn { + background: var(--background-dark-2); + color: var(--stone-3); + border: none; + --btn-color: var(--background-dark-2); + } + + #leave-btn:hover { + filter: brightness(1.04); + } + + .list-item { + background: var(--background-dark-2); + box-shadow: 0 1px 3px color-mix(in oklch, var(--shadow-color) 30%, transparent); + } + + .list-item:hover { + box-shadow: 0 4px 12px color-mix(in oklch, var(--shadow-color) 45%, transparent); + } + + .list-item.selected { + outline-color: var(--avocado-9); + } + + + .delete-btn { + background: var(--cranberry-10); + --btn-color: var(--cranberry-10); + } + + .delete-btn:hover { + background: var(--cranberry-9); + } + + .vote-btn.active { + background: var(--avocado-11); + } + + .vote-btn.veto-active { + background: var(--avocado-12); + } + + .score { + color: var(--avocado-8); + background: var(--stone-14); + } + + .modal { + background: color-mix(in oklch, var(--background-dark-3) 75%, transparent); + } + + .modal-content { + background: var(--background-dark-2); + box-shadow: 0 4px 20px color-mix(in oklch, var(--shadow-color) 70%, transparent); + } + + .modal-header { + border-bottom: 2px solid var(--stone-11); + } + + .modal-header h3 { + color: var(--avocado-8); + } + + .modal-close { + color: var(--stone-3); + } + + .modal-close:hover { + color: var(--cranberry-8); + } + + .modal-body h4 { + color: var(--avocado-8); + } + + kbd { + background: var(--background-dark-2); + border: 1px solid var(--stone-11); + box-shadow: 0 1px 2px color-mix(in oklch, var(--shadow-color) 50%, transparent); + } +}