diff --git a/README.md b/README.md
index 8ab396f..e43d4ea 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ La estructura relacional incluye usuarios, sesiones, salas/partidas, eventos, me
```sh
npm run check
+npm run design:check
npm test
npm run build
```
@@ -74,4 +75,4 @@ Modalidades casuales, sin reloj, ranking, torneos, bots, espectadores ni penaliz
Antes de abrir registro público quedan verificación y recuperación por correo (requieren proveedor SMTP), moderación/bloqueo/reportes, gestión de cuentas y políticas de conservación de datos. La versión está instalada en la VM con PostgreSQL y systemd. El acceso público requiere conectar DNS y el proxy HTTPS a juegoland.imaginacion.com; consulta deploy/server-status.md.
-La guía de instalación está en [deploy/README.md](deploy/README.md). La dirección visual y el prompt para otra herramienta están en [docs/design.md](docs/design.md).
+La guía de instalación está en [deploy/README.md](deploy/README.md). La dirección visual y el prompt para otra herramienta están en [docs/design.md](docs/design.md). Los tokens, sus roles y la política de estilos están en [docs/design-system.md](docs/design-system.md). La auditoría `design:check` detecta valores de tipografía, espaciado y radios escritos a mano y variables CSS sin definir; también se ejecuta antes de activar un despliegue.
diff --git a/deploy/deploy-native.ps1 b/deploy/deploy-native.ps1
index bb3cae8..3c09ee4 100644
--- a/deploy/deploy-native.ps1
+++ b/deploy/deploy-native.ps1
@@ -26,6 +26,7 @@ local_address=$(ip -4 -o addr show scope global | awk '{split($4,a,"/");print a[
test -n "$local_address"
runuser -u mesa -- env HOME=/var/lib/mesa-abierta npm config set local-address "$local_address" --location=user
runuser -u mesa -- env HOME=/var/lib/mesa-abierta NODE_OPTIONS=--dns-result-order=ipv4first npm ci --local-address="$local_address" --no-audit --no-fund
+runuser -u mesa -- npm run design:check
runuser -u mesa -- npm run check
runuser -u mesa -- npm test -- --maxWorkers=1
runuser -u mesa -- npm run build
diff --git a/deploy/server-status.md b/deploy/server-status.md
index f8dad78..419497f 100644
--- a/deploy/server-status.md
+++ b/deploy/server-status.md
@@ -1,6 +1,6 @@
# Servidor de Juegoland
-Versión 20261004-08. Web, API y WebSockets en mesa-game, 0.0.0.0:3000.
+Versión 20261004-10. Web, API y WebSockets en mesa-game, 0.0.0.0:3000.
El proxy Nginx existente de la red apunta a http://192.168.18.155:3000.
Nginx y el antiguo servicio mesa-web están deshabilitados en la VM.
@@ -12,4 +12,6 @@ El popup breve llega abierto en el HTML del servidor y pasa a modal nativo al in
El despliegue conserva los archivos inmutables del cliente anterior para que las pestañas abiertas puedan seguir cargando sus componentes.
Damas mantiene la ficha seleccionada durante capturas encadenadas y permite recorrido diagonal largo de las damas. Las partidas existentes siguen disponibles.
+La interfaz consume `src/tokens.css`: tipografía proporcional de 16 a 18 px, espaciados, radios, sombras y colores de claro/oscuro y de las tres paletas. Los estilos globales se concentran en `src/app.css`; la geometría de los tableros permanece en sus componentes. `npm run design:check` se ejecuta durante el despliegue. Se revisaron catálogo, perfil, menús, invitaciones, avatar y salas en el navegador integrado, con anchos de 390, 565, 1440 y 3818 px. Los nombres y datos de partidas comparten escala en catálogo, perfil y panel lateral; la navegación inferior se ajustó a su altura real.
+
PostgreSQL permanece en loopback, con base y rol dedicados. Credenciales y datos existentes se conservan. APT y npm operan por IPv4. Copias diarias activas a las 03:15 Europe/Madrid.
diff --git a/docs/design-system.md b/docs/design-system.md
new file mode 100644
index 0000000..9814e39
--- /dev/null
+++ b/docs/design-system.md
@@ -0,0 +1,33 @@
+# Sistema visual de Juegoland
+
+La identidad la llevan el logo multicolor y los tableros. La interfaz acompaña con una sola familia, controles legibles y alineaciones compartidas. Esta consolidación mantiene las paletas existentes; concentra sus valores y retira las correcciones CSS duplicadas.
+
+## Tokens
+
+`src/tokens.css` define los valores compartidos. La base clara conserva fondo #f5f7fc, superficie #ffffff, texto #202c41, texto secundario #58677e, acento #0759d9 y borde #dde4f0. Los modos claro, oscuro y sistema y las paletas azul, bosque y violeta consumen los mismos nombres semánticos.
+
+Manrope se usa para títulos y texto. El cuerpo aumenta gradualmente de 16 a 18 px con el ancho disponible; los demás roles derivan de él. Un título de tarjeta usa `--type-subtitle`, una sección `--type-section`, el nombre de una partida `--type-body` y sus datos `--type-secondary`. El texto auxiliar usa `--type-detail`. El cambio de ancho no cambia estos roles.
+
+Los espaciados siguen una base de 4 px: 4, 8, 12, 16, 20, 24, 32, 40, 48 y 64. Hay 2 px para separaciones pequeñas. Los radios distinguen controles, tarjetas, paneles y diálogos; las sombras distinguen controles, tarjetas y capas flotantes.
+
+## Composición
+
+El catálogo alinea título, descripción, datos y acción dentro de cada tarjeta. Las partidas pendientes y las salas comparten tamaños de nombre y metadatos. El texto de lectura se limita a 70 caracteres de ancho. En una sala, el tablero ocupa el espacio disponible y los controles y el chat mantienen una escala de interfaz propia.
+
+```text
+Escritorio Móvil
+menú | contenido alineado cabecera
+ | tarjetas y partidas tarjeta
+ | tarjeta
+diálogo centrado diálogo con márgenes laterales
+```
+
+La marca y los tableros concentran el color y el detalle. Los controles comparten alineación, foco visible y tamaño táctil. Las animaciones de juego conservan sus reglas y respetan movimiento reducido.
+
+## Mantenimiento
+
+`src/app.css` contiene los estilos globales consolidados en el orden necesario para preservar la cascada. Los estilos locales de Svelte consumen los mismos tokens. No se crean hojas de correcciones superpuestas.
+
+Las coordenadas SVG, el recorrido de los tableros, los puntos y caras del dado y la geometría de las ilustraciones son valores de dibujo: se mantienen en sus componentes. Los puntos de ruptura permanecen como condiciones CSS explícitas, porque las variables CSS no se pueden usar directamente en las condiciones de `@media`.
+
+Antes de publicar: ejecutar `npm run design:check`, las comprobaciones del proyecto y revisar catálogo, perfil, diálogos y sala en móvil y escritorio, tanto en claro como en oscuro. La auditoría exige tokens para tamaños, familia, peso, interlineado y espaciado tipográfico; también revisa márgenes, rellenos, separaciones, radios y referencias a variables CSS.
diff --git a/package-lock.json b/package-lock.json
index 8ece67a..aa8a281 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
"@sveltejs/vite-plugin-svelte": "^7.3.1",
"@types/node": "^24.0.0",
"@types/pg": "^8.15.5",
+ "postcss": "8.5.28",
"prettier": "^3.9.9",
"prettier-plugin-svelte": "^4.1.1",
"svelte": "^5.57.1",
diff --git a/package.json b/package.json
index 9673dc0..0e8d2ed 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"build": "vite build",
"preview": "vite preview --host 127.0.0.1",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && tsc --project tsconfig.server.json --noEmit",
+ "design:check": "node scripts/check-design-tokens.mjs",
"test": "vitest run",
"start": "node --env-file-if-exists=.env server/production.mjs",
"db:migrate": "node --env-file-if-exists=.env scripts/migrate.mjs"
@@ -32,6 +33,7 @@
"@sveltejs/vite-plugin-svelte": "^7.3.1",
"@types/node": "^24.0.0",
"@types/pg": "^8.15.5",
+ "postcss": "8.5.28",
"prettier": "^3.9.9",
"prettier-plugin-svelte": "^4.1.1",
"svelte": "^5.57.1",
diff --git a/scripts/check-design-tokens.mjs b/scripts/check-design-tokens.mjs
new file mode 100644
index 0000000..3a2b8af
--- /dev/null
+++ b/scripts/check-design-tokens.mjs
@@ -0,0 +1,98 @@
+import { readdir, readFile } from 'node:fs/promises';
+import { join } from 'node:path';
+import postcss from 'postcss';
+
+const problems = [];
+let checked = 0;
+const definitions = new Set();
+const references = [];
+const drawingFiles = new Set(['GameArt.svelte', 'ParchisMap.svelte']);
+const drawingSelector =
+ /\.(?:chess-piece|checker-piece|coordinate|parchis-token|dice-face|dice-cube|dice-stage|dice-shadow|face-\d|dice-cup|cup-shadow|throw-scene|go-target)\b/;
+
+async function inspect(path, css) {
+ const file = path.split(/[\\/]/).at(-1);
+ if (file === 'tokens.css' || drawingFiles.has(file)) return;
+ const parsed = postcss.parse(css, { from: path });
+ parsed.walkDecls((d) => {
+ let inKeyframes = false;
+ for (let parent = d.parent; parent; parent = parent.parent)
+ if (parent.type === 'atrule' && /keyframes$/.test(parent.name))
+ inKeyframes = true;
+ const selector = d.parent.type === 'rule' ? d.parent.selector : '';
+ if (
+ inKeyframes ||
+ drawingSelector.test(selector) ||
+ (file === 'Dice.svelte' && /^i(?:[.:]|$)/.test(selector)) ||
+ d.prop.startsWith('--')
+ )
+ return;
+ checked++;
+ const value = d.value.replace(/\b0(?:px|rem)\b/g, '0');
+ const absoluteSize = /\b\d+(?:\.\d+)?(?:px|rem)\b/;
+ if (
+ (d.prop === 'font-size' || d.prop === 'font') &&
+ !value.includes('var(') &&
+ !/^(?:0|inherit|initial|unset)$/.test(value)
+ )
+ problems.push(
+ `${path}:${d.source.start.line} — ${selector}: use a type token for ${d.prop}`,
+ );
+ if (
+ /^(?:font-weight|line-height|letter-spacing|font-family)$/.test(d.prop) &&
+ !value.includes('var(') &&
+ !/^(?:inherit|initial|unset|normal)$/.test(value)
+ )
+ problems.push(
+ `${path}:${d.source.start.line} — ${selector}: use a typography token for ${d.prop}`,
+ );
+ if (
+ /^(?:padding|margin)(?:-.+)?$|^(?:gap|row-gap|column-gap)$/.test(
+ d.prop,
+ ) &&
+ absoluteSize.test(value)
+ )
+ problems.push(
+ `${path}:${d.source.start.line} — ${selector}: use a spacing token for ${d.prop}`,
+ );
+ if (d.prop === 'border-radius' && absoluteSize.test(value))
+ problems.push(
+ `${path}:${d.source.start.line} — ${selector}: use a radius token`,
+ );
+ });
+}
+function collect(path, source) {
+ for (const match of source.matchAll(/(--[\w-]+)\s*:/g))
+ definitions.add(match[1]);
+ for (const match of source.matchAll(/style:(--[\w-]+)/g))
+ definitions.add(match[1]);
+ for (const match of source.matchAll(/var\((--[\w-]+)\s*\)/g))
+ references.push({ path, name: match[1] });
+}
+async function scan(directory) {
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
+ const path = join(directory, entry.name);
+ if (entry.isDirectory()) await scan(path);
+ else if (entry.name.endsWith('.css')) {
+ const source = await readFile(path, 'utf8');
+ collect(path, source);
+ await inspect(path, source);
+ } else if (entry.name.endsWith('.svelte')) {
+ const source = await readFile(path, 'utf8');
+ collect(path, source);
+ const style = source.match(/
diff --git a/src/lib/components/Dialog.svelte b/src/lib/components/Dialog.svelte
index 440b4bb..3423fda 100644
--- a/src/lib/components/Dialog.svelte
+++ b/src/lib/components/Dialog.svelte
@@ -49,8 +49,8 @@
.dialog-fallback-backdrop {
position: fixed;
inset: 0;
- z-index: 1000;
- background: #263a2d55;
+ z-index: var(--layer-backdrop);
+ background: var(--overlay);
backdrop-filter: blur(4px);
}
diff --git a/src/lib/components/Dice.svelte b/src/lib/components/Dice.svelte
index 87f6e71..71d576c 100644
--- a/src/lib/components/Dice.svelte
+++ b/src/lib/components/Dice.svelte
@@ -276,7 +276,7 @@
.dice-widget {
display: flex;
align-items: center;
- gap: 24px;
+ gap: var(--space-6);
}
.dice-result {
display: flex;
@@ -286,8 +286,8 @@
}
.dice-result small {
color: var(--secondary);
- font-size: var(--type-secondary, 14px);
- line-height: 1.4;
+ font-size: var(--type-secondary);
+ line-height: var(--leading-compact);
max-width: 160px;
text-align: center;
overflow-wrap: anywhere;
@@ -474,21 +474,19 @@
alternate;
}
.dropping .large {
- animation: drop-die var(--roll-duration, 800ms) ease-out;
+ animation: drop-die var(--roll-duration, 800ms) var(--ease-ui);
}
.dice-announcement {
min-height: 95px;
}
.dice-announcement > strong {
display: block;
- font:
- 800 52px 'Manrope',
- sans-serif;
+ font: 800 var(--type-display) var(--font-ui);
color: var(--accent-text);
- line-height: 1.2;
+ line-height: var(--leading-heading);
}
.dice-announcement p {
- font-size: 14px;
+ font-size: var(--type-secondary);
color: var(--secondary);
}
.shake-button {
@@ -508,28 +506,28 @@
z-index: 30;
display: flex;
align-items: center;
- gap: 14px;
+ gap: var(--space-3);
width: 390px;
max-width: calc(100vw - 24px);
- padding: 12px 18px;
+ padding: var(--space-3) var(--space-4);
border: 1px solid var(--border);
- border-radius: 18px;
+ border-radius: var(--radius-panel);
background: var(--surface);
color: var(--text);
- box-shadow: 0 12px 50px var(--shadow);
- animation: notice-enter 0.2s ease-out;
+ box-shadow: var(--shadow-popover);
+ animation: notice-enter 0.2s var(--ease-ui);
pointer-events: none;
}
.dice-notice > div {
min-width: 0;
}
.dice-notice strong {
- font-size: var(--type-body, 16px);
+ font-size: var(--type-body);
}
.dice-notice p {
- font-size: var(--type-secondary, 14px);
- line-height: 1.5;
- margin-top: 5px;
+ font-size: var(--type-secondary);
+ line-height: var(--leading-body);
+ margin-top: var(--space-1);
color: var(--secondary);
}
.dice-notice button {
@@ -582,7 +580,7 @@
}
@media (max-width: 500px) {
.dice-widget {
- gap: 12px;
+ gap: var(--space-3);
}
.dice-result small {
max-width: 90px;
diff --git a/src/lib/components/GoBoard.svelte b/src/lib/components/GoBoard.svelte
index 68e82b6..100bb42 100644
--- a/src/lib/components/GoBoard.svelte
+++ b/src/lib/components/GoBoard.svelte
@@ -198,47 +198,55 @@
cursor: pointer;
outline: none;
}
- .go-map g[role='button'] { outline:none; }
+ .go-map g[role='button'] {
+ outline: none;
+ }
.allowed:hover .intersection,
.allowed:focus-visible .intersection {
fill: #ffffff33;
- stroke: #0759d9;
+ stroke: var(--accent);
stroke-width: 3;
}
.go-scores {
display: flex;
flex-direction: column;
- gap: 14px;
+ gap: var(--space-3);
}
.go-scores > div {
display: grid;
grid-template-columns: 20px minmax(0, 1fr);
- gap: 3px 8px;
+ gap: var(--space-half) var(--space-2);
align-items: center;
}
.go-scores i {
width: 20px;
height: 20px;
- border-radius: 50%;
- background: #20252e;
- border: 1px solid #858b96;
+ border-radius: var(--radius-round);
+ background: var(--stone-dark);
+ border: 1px solid var(--stone-border);
grid-row: span 2;
}
.go-scores i.white {
- background: #f5f6f8;
+ background: var(--stone-light);
}
.go-scores strong {
- font-size: var(--type-secondary,14px);
- white-space:nowrap;
- overflow:hidden;
- text-overflow:ellipsis;
+ font-size: var(--type-secondary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.go-scores small {
- font-size: var(--type-detail,12px);
+ font-size: var(--type-detail);
color: var(--secondary);
}
- @media(max-width:800px) and (orientation:portrait) {
- .go-scores { flex-direction:row; gap:12px; }
- .go-scores>div { flex:1; min-width:0; }
+ @media (max-width: 800px) and (orientation: portrait) {
+ .go-scores {
+ flex-direction: row;
+ gap: var(--space-3);
+ }
+ .go-scores > div {
+ flex: 1;
+ min-width: 0;
+ }
}
diff --git a/src/lib/components/Invitation.svelte b/src/lib/components/Invitation.svelte
index 7f8d960..f2038cd 100644
--- a/src/lib/components/Invitation.svelte
+++ b/src/lib/components/Invitation.svelte
@@ -162,26 +162,26 @@
display: grid;
grid-template-columns: minmax(0, 1fr) 44px;
align-items: start;
- gap: 16px;
- margin: 0 0 24px;
+ gap: var(--space-4);
+ margin: 0 0 var(--space-6);
}
h2 {
- margin: 6px 0 0;
- font-size: 24px;
- line-height: 1.25;
- letter-spacing: -0.5px;
+ margin: var(--space-1) 0 0;
+ font-size: var(--type-section);
+ line-height: var(--leading-heading);
+ letter-spacing: var(--tracking-heading);
}
.invitation-game {
display: grid;
grid-template-columns: 96px minmax(0, 1fr);
- gap: 16px;
+ gap: var(--space-4);
align-items: center;
- margin: 0 0 24px;
+ margin: 0 0 var(--space-6);
}
.invitation-art {
width: 96px;
height: 96px;
- border-radius: 16px;
+ border-radius: var(--radius-card);
overflow: hidden;
}
.invitation-art :global(.game-art) {
@@ -192,35 +192,35 @@
}
.invitation-details {
display: grid;
- gap: 4px;
+ gap: var(--space-1);
min-width: 0;
}
.invitation-details strong {
- font-size: 18px;
- line-height: 1.4;
+ font-size: var(--type-subtitle);
+ line-height: var(--leading-compact);
overflow-wrap: anywhere;
}
.invitation-details span {
- font-size: 14px;
- line-height: 1.5;
+ font-size: var(--type-secondary);
+ line-height: var(--leading-body);
color: var(--secondary);
}
.invitation-message {
- margin: 0 0 24px;
- padding: 16px;
+ margin: 0 0 var(--space-6);
+ padding: var(--space-4);
border-left: 3px solid var(--accent);
- border-radius: 0 12px 12px 0;
+ border-radius: 0 var(--radius-control) var(--radius-control) 0;
background: var(--soft);
color: var(--text);
- font-size: 16px;
- line-height: 1.5;
+ font-size: var(--type-body);
+ line-height: var(--leading-body);
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.invitation-note {
- margin: 0 0 16px;
- font-size: 16px;
- line-height: 1.5;
+ margin: 0 0 var(--space-4);
+ font-size: var(--type-body);
+ line-height: var(--leading-body);
color: var(--secondary);
}
@media (max-width: 600px) {
@@ -230,7 +230,7 @@
.invitation-art {
width: 80px;
height: 80px;
- border-radius: 12px;
+ border-radius: var(--radius-control);
}
.invitation-art :global(.game-art) {
transform: scale(0.421053);
diff --git a/src/lib/components/OcaBoard.svelte b/src/lib/components/OcaBoard.svelte
index d38b41f..20566c4 100644
--- a/src/lib/components/OcaBoard.svelte
+++ b/src/lib/components/OcaBoard.svelte
@@ -265,7 +265,7 @@
}
.cell-enlargement {
pointer-events: none;
- filter: drop-shadow(0 8px 10px #27365250);
+ filter: drop-shadow(var(--shadow-board));
}
.cell-enlargement > rect {
stroke: var(--accent);
@@ -290,10 +290,10 @@
}
.bonus,
.goal {
- animation: arrive-jump var(--arrival-duration) ease-out;
+ animation: arrive-jump var(--arrival-duration) var(--ease-ui);
}
.penalty {
- animation: arrive-shake var(--arrival-duration) ease-out;
+ animation: arrive-shake var(--arrival-duration) var(--ease-ui);
}
@keyframes arrive-jump {
30% {
diff --git a/src/lib/components/PendingGames.svelte b/src/lib/components/PendingGames.svelte
index 8fc2ca6..8d92588 100644
--- a/src/lib/components/PendingGames.svelte
+++ b/src/lib/components/PendingGames.svelte
@@ -46,17 +46,17 @@
}
.pending-games-list {
display: grid;
- gap: 8px;
+ gap: var(--space-2);
}
.pending-game {
display: flex;
align-items: center;
justify-content: space-between;
- gap: 12px;
- padding: 16px;
+ gap: var(--space-3);
+ padding: var(--space-4);
background: var(--surface);
border: 1px solid var(--border);
- border-radius: 12px;
+ border-radius: var(--radius-control);
color: var(--text);
text-decoration: none;
}
@@ -71,24 +71,24 @@
.pending-game strong {
display: block;
font-size: var(--type-body);
- line-height: 1.4;
+ line-height: var(--leading-compact);
overflow-wrap: anywhere;
}
.pending-game small {
display: block;
font-size: var(--type-secondary);
color: var(--secondary);
- margin-top: 6px;
+ margin-top: var(--space-1);
}
.pending-status {
display: block;
- font-size: var(--type-detail);
+ font-size: var(--type-secondary);
color: var(--secondary);
- margin-top: 6px;
+ margin-top: var(--space-1);
}
.pending-status.your-turn {
color: var(--accent-text);
- font-weight: 700;
+ font-weight: var(--weight-bold);
}
.pending-empty {
font-size: var(--type-secondary);
diff --git a/src/lib/components/PlayerColorChooser.svelte b/src/lib/components/PlayerColorChooser.svelte
index 4446713..177baf7 100644
--- a/src/lib/components/PlayerColorChooser.svelte
+++ b/src/lib/components/PlayerColorChooser.svelte
@@ -55,34 +55,34 @@
diff --git a/src/lib/components/ThemeControls.svelte b/src/lib/components/ThemeControls.svelte
index ce74afb..070366a 100644
--- a/src/lib/components/ThemeControls.svelte
+++ b/src/lib/components/ThemeControls.svelte
@@ -2,7 +2,7 @@
import { appearance, saveAppearance, type Mode, type Palette } from '../theme.svelte';
import Icon from './Icon.svelte';
const modes:{id:Mode;label:string;icon:string}[]=[{id:'light',label:'Claro',icon:'sun'},{id:'dark',label:'Oscuro',icon:'moon'},{id:'system',label:'Sistema',icon:'monitor'}];
- const palettes:{id:Palette;label:string;color:string}[]=[{id:'blue',label:'Azul',color:'#4263df'},{id:'forest',label:'Bosque',color:'#247761'},{id:'violet',label:'Violeta',color:'#8756ca'}];
+ const palettes:{id:Palette;label:string;color:string}[]=[{id:'blue',label:'Azul',color:'var(--palette-blue)'},{id:'forest',label:'Bosque',color:'var(--palette-forest)'},{id:'violet',label:'Violeta',color:'var(--palette-violet)'}];
diff --git a/src/match.css b/src/match.css
deleted file mode 100644
index c07e64c..0000000
--- a/src/match.css
+++ /dev/null
@@ -1,601 +0,0 @@
-/* The table has its own viewport; platform navigation opens on demand. */
-.match-shell {
- height: 100dvh;
- overflow: hidden;
-}
-.match-shell .main-shell {
- margin: 0;
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-.match-shell .topbar {
- height: 52px;
- flex: 0 0 52px;
- padding: 0 18px;
- gap: 14px;
- background: var(--surface);
-}
-.match-shell .mobile-logo {
- display: block;
- width: 128px;
- margin-right: auto;
-}
-.match-shell .header-right {
- gap: 12px;
-}
-.match-shell .header-right .small-avatar {
- display: none;
-}
-.match-shell main {
- flex: 1;
- min-height: 0;
- width: 100%;
- max-width: none;
- margin: 0;
- padding: 0;
- overflow: hidden;
-}
-.match-shell .site-footer {
- display: none;
-}
-.match-shell .sidebar:not(:popover-open) {
- display: none;
-}
-.match-shell .sidebar:popover-open {
- display: flex;
- position: fixed;
- inset: 60px auto 12px 12px;
- margin: 0;
- height: auto;
- width: 260px;
- padding: 24px;
- border: 1px solid var(--border);
- border-radius: 18px;
- box-shadow: 0 16px 54px var(--shadow);
- z-index: 50;
- overflow: auto;
- animation: panel-enter 0.18s ease-out;
-}
-.match-shell .sidebar .brand {
- display: flex;
- width: 100%;
- margin: 0 0 24px;
- align-items: flex-start;
-}
-.match-shell .sidebar .brand-logo {
- width: 100%;
-}
-.match-shell .sidebar .brand small,
-.match-shell .sidebar-label,
-.match-shell .sidebar nav a span,
-.match-shell .sidebar-invite,
-.match-shell .sidebar-bottom {
- display: block;
-}
-.match-shell .sidebar nav {
- display: block;
- width: 100%;
-}
-.match-shell .sidebar nav a {
- display: flex;
- align-items: center;
- justify-content: flex-start;
- flex-direction: row;
- gap: 10px;
- padding: 14px 12px;
- font-size: 13px;
-}
-.match-shell .sidebar-bottom {
- margin-top: auto;
- padding-top: 18px;
-}
-.match-shell .sidebar-invite {
- margin-top: 18px;
-}
-.match-shell .settings-popover {
- top: 58px;
-}
-.match-room {
- height: 100%;
- display: flex;
- flex-direction: column;
- min-height: 0;
- background: radial-gradient(
- ellipse at 50% 40%,
- var(--soft),
- var(--background) 75%
- );
-}
-.match-room .room-page-heading {
- display: flex;
- align-items: center;
- gap: 16px;
- margin: 0;
- padding: 10px 20px;
- flex-shrink: 0;
- border-bottom: 1px solid var(--border);
-}
-.room-page-heading > div:first-child {
- min-width: 0;
-}
-.match-room .room-page-heading .eyebrow {
- font-size: 9px;
- line-height: 1;
- letter-spacing: 1px;
-}
-.match-room .room-page-heading h1 {
- font-size: 17px;
- letter-spacing: -0.4px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 36vw;
-}
-.match-turn {
- display: flex;
- align-items: center;
- gap: 7px;
- padding: 8px 12px;
- background: var(--player-soft);
- color: var(--player-ink);
- border-radius: 20px;
- font-size: 12px;
- font-weight: 650;
- white-space: nowrap;
-}
-.match-turn i,
-.panel-players i {
- display: block;
- width: 7px;
- height: 7px;
- border-radius: 50%;
- background: var(--player-solid);
- flex-shrink: 0;
-}
-.room-tools {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-left: auto;
-}
-.match-room .room-tools .button {
- padding: 9px 12px;
- margin: 0;
- font-size: 12px;
-}
-.room-tools .icon-button {
- width: 34px;
- height: 34px;
- display: grid;
- place-items: center;
- padding: 0;
-}
-.panel-trigger b {
- background: var(--accent);
- color: white;
- border-radius: 20px;
- font-size: 10px;
- padding: 2px 6px;
-}
-.match-room .room-layout {
- flex: 1;
- min-height: 0;
- display: block;
- position: relative;
- padding: 12px 18px 8px;
- transition: padding-right 0.18s ease-out;
-}
-.match-room .play-area {
- height: 100%;
- min-height: 0;
- display: flex;
- flex-direction: column;
- container-type: size;
-}
-.match-room .parchis-board {
- flex: 1;
- min-height: 0;
- padding: 0;
- border: 0;
- background: transparent;
- display: grid;
- grid-template-columns: minmax(0, 1fr) 210px;
- gap: 24px;
-}
-.board-surface {
- min-width: 0;
- min-height: 0;
- display: grid;
- place-items: center;
- container-type: size;
-}
-.match-room .parchis-map {
- width: min(100cqw, 100cqh);
- height: min(100cqw, 100cqh);
- max-width: 100%;
- max-height: 100%;
- filter: drop-shadow(0 10px 18px var(--shadow));
-}
-.match-room .parchis-controls {
- margin: 0;
- padding: 20px 16px;
- border: 1px solid var(--border);
- border-radius: 20px;
- align-self: center;
- display: flex;
- align-items: stretch;
- flex-direction: column;
- gap: 14px;
- box-shadow: 0 4px 24px var(--shadow);
-}
-.match-room .parchis-controls .dice-widget {
- flex-direction: column;
- gap: 10px;
-}
-.match-room .parchis-controls p {
- font-size: 12px;
- flex: none;
-}
-.table-players {
- display: flex;
- flex-direction: column;
- gap: 10px;
- border-top: 1px solid var(--border);
- padding-top: 14px;
-}
-.table-player {
- display: grid;
- grid-template-columns: 25px minmax(0, 1fr);
- align-items: center;
- column-gap: 8px;
- opacity: 0.7;
-}
-.table-player.playing {
- opacity: 1;
-}
-.table-player .mini-avatar {
- grid-row: span 2;
- width: 25px;
- height: 25px;
- margin: 0;
- border: 0;
- font-size: 10px;
- background: var(--player-soft);
- color: var(--player-ink);
-}
-.table-player strong {
- font-size: 11px;
- color: var(--player-ink);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-.table-player small {
- font-size: 10px;
- color: var(--secondary);
-}
-.piece-shortcuts {
- display: flex;
- gap: 8px;
- align-items: center;
- flex-wrap: wrap;
-}
-.piece-shortcuts > span {
- width: 100%;
- font-size: 10px;
- color: var(--secondary);
-}
-.piece-shortcuts .parchis-token {
- width: 32px;
- height: 32px;
- font-size: 13px;
-}
-.match-room .board-note {
- font-size: 11px;
- margin: 8px 0 0;
- height: 20px;
- flex-shrink: 0;
- justify-content: center;
-}
-.match-room .board-note > span:last-child {
- display: none;
-}
-.match-room .parchis-board + .board-note {
- display: none;
-}
-.match-room .board-container {
- padding: 8px;
- margin: auto;
- max-width: none;
- width: min(100cqw, calc(100cqh - 34px));
- aspect-ratio: 1;
- flex-shrink: 0;
-}
-.match-room .board-grid {
- height: 100%;
- grid-template-rows: repeat(8, minmax(0, 1fr));
-}
-.match-room .chess-piece {
- font-size: clamp(16px, 8cqmin, 80px);
-}
-.match-room .board-square {
- aspect-ratio: auto;
-}
-.match-room .promotion {
- position: absolute;
- bottom: 15px;
- left: 50%;
- transform: translateX(-50%);
- z-index: 10;
-}
-.match-room .room-panel:not(:popover-open) {
- display: none;
-}
-.match-room .room-panel:popover-open {
- display: flex;
- flex-direction: column;
- width: 310px;
- max-width: calc(100vw - 24px);
- position: fixed;
- inset: 124px 18px 14px auto;
- height: auto;
- margin: 0;
- z-index: 20;
- background: var(--surface);
- border-radius: 18px;
- box-shadow: 0 6px 30px var(--shadow);
- animation: panel-enter 0.18s ease-out;
-}
-.panel-heading {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 14px 16px 8px;
-}
-.panel-heading strong {
- font-size: 14px;
-}
-.panel-players {
- display: flex;
- gap: 8px 12px;
- flex-wrap: wrap;
- padding: 0 16px 10px;
-}
-.panel-players span {
- display: flex;
- align-items: center;
- gap: 5px;
- color: var(--player-ink);
- font-size: 10px;
-}
-.match-room .room-tabs {
- flex-shrink: 0;
-}
-.match-room .chat-messages,
-.match-room .room-rules,
-.match-room .event-list {
- height: auto;
- min-height: 0;
- flex: 1;
- overflow: auto;
-}
-.match-room .chat-form {
- flex-shrink: 0;
-}
-.match-room .waiting-card {
- height: 100%;
- max-width: 800px;
- margin: auto;
- overflow: auto;
- padding: 24px;
-}
-.match-room .result-banner {
- padding: 12px 20px;
- margin: 0;
- display: flex;
- gap: 12px;
- align-items: center;
- flex-wrap: wrap;
-}
-.match-room .result-banner .empty-symbol {
- display: none;
-}
-.match-room .result-banner h2 {
- font-size: 15px;
-}
-.match-room .result-banner p {
- font-size: 12px;
-}
-.match-room .result-banner .button {
- padding: 8px 12px;
- margin: 0;
-}
-.match-room .form-error {
- margin: 8px 18px 0;
- flex-shrink: 0;
-}
-@media (min-width: 850px) {
- .match-room .room-layout:has(.room-panel:popover-open) {
- padding-right: 346px;
- }
- .match-room .room-layout:has(.room-panel:popover-open) .parchis-board {
- grid-template-columns: minmax(0, 1fr) 180px;
- gap: 12px;
- }
-}
-@media (min-width: 850px) and (max-width: 1199px) {
- .match-room .room-layout:has(.room-panel:popover-open) .parchis-board {
- grid-template-columns: minmax(0, 1fr);
- grid-template-rows: minmax(0, 1fr) auto;
- }
- .match-room .room-layout:has(.room-panel:popover-open) .parchis-controls {
- width: 100%;
- padding: 10px 14px;
- display: grid;
- grid-template-columns: 180px minmax(0, 1fr);
- gap: 6px 14px;
- }
- .match-room
- .room-layout:has(.room-panel:popover-open)
- .parchis-controls
- > .eyebrow,
- .match-room .room-layout:has(.room-panel:popover-open) .table-players {
- display: none;
- }
- .match-room .room-layout:has(.room-panel:popover-open) .dice-widget {
- flex-direction: row;
- grid-row: span 2;
- }
-}
-@media (max-width: 800px) and (orientation: portrait) {
- .match-room .parchis-board {
- grid-template-columns: minmax(0, 1fr);
- grid-template-rows: minmax(0, 1fr) auto;
- gap: 10px;
- }
- .match-room .parchis-controls {
- width: 100%;
- padding: 10px 12px;
- display: grid;
- grid-template-columns: auto minmax(0, 1fr);
- gap: 6px 12px;
- align-self: stretch;
- }
- .match-room .parchis-controls > .eyebrow {
- display: none;
- }
- .match-room .parchis-controls .dice-widget {
- flex-direction: row;
- gap: 8px;
- grid-row: span 2;
- }
- .match-room .parchis-controls .dice-widget .dice-stage {
- width: 54px;
- height: 65px;
- --edge: 36px;
- --depth: 18px;
- }
- .match-room .parchis-controls .dice-widget .dice-face {
- padding: 6px;
- gap: 2px;
- border-radius: 6px;
- }
- .match-room .parchis-controls .button {
- font-size: 11px;
- padding: 10px;
- }
- .match-room .parchis-controls .dice-instruction {
- font-size: 11px;
- align-self: center;
- }
- .match-room .table-players {
- display: none;
- }
- .match-room .piece-shortcuts {
- gap: 6px;
- }
- .match-room .piece-shortcuts > span {
- width: auto;
- }
-}
-@media (max-width: 650px) {
- .match-shell .topbar {
- padding: 0 12px;
- gap: 10px;
- }
- .match-shell .header-right {
- gap: 8px;
- }
- .match-shell .connection {
- font-size: 0;
- gap: 0;
- }
- .match-shell .mobile-logo {
- width: 114px;
- }
- .match-room .room-page-heading {
- padding: 8px 12px;
- gap: 6px 8px;
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- }
- .match-room .room-page-heading > div:first-child {
- grid-row: 1;
- grid-column: 1;
- }
- .match-room .room-tools {
- grid-row: 1;
- grid-column: 2;
- }
- .match-room .room-page-heading h1 {
- font-size: 14px;
- max-width: 45vw;
- }
- .match-turn {
- padding: 5px 0;
- font-size: 11px;
- grid-row: 2;
- grid-column: 1 / -1;
- width: 100%;
- background: transparent;
- }
- .room-tools {
- gap: 4px;
- }
- .room-tools .fullscreen-trigger {
- display: none;
- }
- .match-room .panel-trigger {
- padding: 8px;
- }
- .panel-trigger > span {
- display: none;
- }
- .match-room .room-layout {
- padding: 10px 8px 8px;
- }
- .match-room .room-panel:popover-open {
- inset: 142px 8px 8px auto;
- width: 340px;
- }
- .match-room .board-note {
- font-size: 10px;
- height: auto;
- }
-}
-@media (orientation: landscape) and (max-height: 520px) {
- .match-shell .topbar {
- height: 44px;
- flex-basis: 44px;
- }
- .match-room .room-page-heading {
- padding: 5px 12px;
- }
- .match-room .parchis-board {
- gap: 12px;
- grid-template-columns: minmax(0, 1fr) 180px;
- }
- .match-room .parchis-controls {
- padding: 10px 12px;
- gap: 8px;
- }
- .match-room .table-players {
- display: none;
- }
- .match-room .parchis-controls .dice-widget {
- flex-direction: row;
- gap: 8px;
- }
- .match-room .parchis-controls .dice-widget .dice-stage {
- --edge: 36px;
- --depth: 18px;
- width: 48px;
- height: 60px;
- }
- .match-room .parchis-controls .button {
- padding: 8px;
- font-size: 10px;
- }
-}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index c42c1ed..c95f6d9 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -11,11 +11,8 @@
import BrandMark from '#lib/components/BrandMark.svelte';
import AvatarImage from '#lib/components/AvatarImage.svelte';
import { brand } from '../../shared/brand.mjs';
+ import '../tokens.css';
import '../app.css';
- import '../appearance.css';
- import '../match.css';
- import '../design.css';
- import '../terms.css';
import TermsConsent from '#lib/components/TermsConsent.svelte';
import { terms } from '../../shared/terms.mjs';
let { children, data } = $props();
diff --git a/src/terms.css b/src/terms.css
deleted file mode 100644
index 0029171..0000000
--- a/src/terms.css
+++ /dev/null
@@ -1,114 +0,0 @@
-.entry-notice {
- position: fixed;
- inset: 0;
- margin: auto;
- z-index: 1001;
- width: min(600px, calc(100vw - 32px));
- max-width: 600px;
- padding: 32px;
-}
-.entry-notice h2 {
- margin: 0 0 20px;
- font-size: 24px;
-}
-.entry-notice p {
- font-size: 16px;
- line-height: 1.55;
- margin: 0 0 16px;
- color: var(--muted);
-}
-.entry-notice .terms-author {
- color: var(--text);
-}
-.terms-read {
- display: inline-block;
- font-size: 16px;
- font-weight: 650;
- color: var(--green);
- margin: 0 0 20px;
- text-decoration: underline;
- text-underline-offset: 3px;
-}
-.entry-notice .terms-check {
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- gap: 12px;
- margin: 0 0 16px;
- color: var(--text);
- font-size: 16px;
- line-height: 1.5;
- font-weight: 600;
-}
-.terms-check input {
- flex: 0 0 22px;
- width: 22px;
- height: 22px;
- margin: 2px 0 0;
- padding: 0;
- accent-color: var(--green);
-}
-.entry-notice .terms-cookie {
- font-size: 14px;
- line-height: 1.5;
-}
-.terms-actions {
- display: flex;
- gap: 12px;
- justify-content: flex-end;
- margin-top: 24px;
-}
-.terms-actions .button {
- font-size: 16px;
-}
-.terms-actions a[aria-disabled='true'] {
- pointer-events: none;
- opacity: 0.5;
-}
-.terms-page {
- width: min(100%, 760px);
- margin: 0 auto;
- padding: 40px 32px;
-}
-.terms-page h1 {
- font-size: 28px;
- margin: 0 0 24px;
-}
-.terms-page h2 {
- font-size: 20px;
- margin: 32px 0 12px;
-}
-.terms-page p {
- font-size: 16px;
- line-height: 1.65;
- margin-bottom: 16px;
- color: var(--muted);
-}
-.terms-page .terms-introduction {
- color: var(--text);
-}
-.terms-page .terms-version {
- font-size: 14px;
- margin-top: 32px;
-}
-.terms-page .button {
- margin: 0 8px 12px 0;
- font-size: 16px;
-}
-.site-footer .footer-author {
- max-width: 42ch;
-}
-@media (max-width: 600px) {
- .entry-notice {
- padding: 24px;
- }
- .terms-page {
- padding: 24px 20px;
- }
- .terms-actions {
- flex-direction: column-reverse;
- }
- .terms-actions .button {
- width: 100%;
- }
-}
diff --git a/src/tokens.css b/src/tokens.css
new file mode 100644
index 0000000..0126e17
--- /dev/null
+++ b/src/tokens.css
@@ -0,0 +1,247 @@
+/* The only source of shared UI values. Board artwork keeps its own geometry. */
+@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;750;800&display=swap');
+
+:root {
+ color-scheme: light;
+ font-size: 100%;
+ --font-ui: 'Manrope', system-ui, sans-serif;
+ --font-title: var(--font-ui);
+ --type-body: clamp(1rem, 0.95rem + 0.1vw, 1.125rem);
+ --type-detail: calc(var(--type-body) * 0.8125);
+ --type-secondary: calc(var(--type-body) * 0.875);
+ --type-subtitle: calc(var(--type-body) * 1.25);
+ --type-section: calc(var(--type-body) * 1.5);
+ --type-heading: calc(var(--type-body) * 1.75);
+ --type-display: clamp(2rem, 1.4rem + 2vw, 3rem);
+ --leading-none: 1;
+ --leading-heading: 1.25;
+ --leading-compact: 1.4;
+ --leading-body: 1.5;
+ --leading-relaxed: 1.6;
+ --tracking-body: 0;
+ --tracking-heading: -0.025em;
+ --tracking-label: 0.06em;
+ --tracking-pin: 0.65em;
+ --weight-regular: 400;
+ --weight-medium: 500;
+ --weight-semibold: 600;
+ --weight-bold: 700;
+ --weight-display: 750;
+ --weight-heavy: 800;
+
+ --space-half: 0.125rem;
+ --space-1: 0.25rem;
+ --space-2: 0.5rem;
+ --space-3: 0.75rem;
+ --space-4: 1rem;
+ --space-5: 1.25rem;
+ --space-6: 1.5rem;
+ --space-8: 2rem;
+ --space-10: 2.5rem;
+ --space-12: 3rem;
+ --space-14: 3.5rem;
+ --space-16: 4rem;
+ --space-20: 5rem;
+ --space-24: 6rem;
+
+ --radius-xs: 0.25rem;
+ --radius-sm: 0.5rem;
+ --radius-control: 0.75rem;
+ --radius-card: 1rem;
+ --radius-panel: 1.25rem;
+ --radius-dialog: 1.5rem;
+ --radius-round: 50%;
+ --radius-pill: 999px;
+ --stroke-1: 1px;
+ --stroke-2: 2px;
+ --stroke-3: 3px;
+ --size-control: 2.75rem;
+ --size-field: 3rem;
+ --size-avatar: 2.5rem;
+ --size-avatar-small: 2rem;
+ --size-avatar-large: 5rem;
+ --size-modal: 32.5rem;
+ --size-notice: 37.5rem;
+ --size-popover: 22.5rem;
+ --size-menu: 20rem;
+ --size-navigation-mobile: 5.5rem;
+ --layout-sidebar: 15rem;
+ --layout-sidebar-medium: 12.8125rem;
+ --layout-sidebar-compact: 5.125rem;
+ --layout-content: 90rem;
+ --layout-gutter: clamp(var(--space-4), 3vw, var(--space-10));
+ --measure-copy: 70ch;
+ --measure-note: 48ch;
+ --duration-fast: 150ms;
+ --duration-normal: 200ms;
+ --duration-slow: 300ms;
+ --ease-ui: ease-out;
+ --layer-menu: 50;
+ --layer-notice: 35;
+ --layer-backdrop: 1000;
+ --layer-dialog: 1001;
+ --symbol-small: 2rem;
+ --symbol-medium: 2.5rem;
+ --symbol-large: 3rem;
+ --symbol-hero: 8rem;
+
+ --text: #202c41;
+ --background: #f5f7fc;
+ --surface: #ffffff;
+ --soft: #edf2fa;
+ --hero: #e8efff;
+ --accent: #0759d9;
+ --accent-hover: #0046b8;
+ --accent-text: #0759c4;
+ --on-accent: #ffffff;
+ --secondary: #58677e;
+ --border: #dde4f0;
+ --danger-bg: #fff0ec;
+ --danger-text: #a32a27;
+ --success: #217146;
+ --success-bg: #e6f5eb;
+ --glass-surface: #ffffffb8;
+ --glass-border: #ffffff90;
+ --overlay: #10274a55;
+ --overlay-strong: #081126b8;
+ --shadow-color: #10274a1c;
+ --palette-blue: #0759d9;
+ --palette-forest: #14744f;
+ --palette-violet: #7742bf;
+ --art-terracotta: #bc7255;
+ --board-light: #f1eddf;
+ --board-dark: #7f9871;
+ --board-selected: #d4bb68;
+ --board-target: #294b35a0;
+ --board-target-border: #234732aa;
+ --stone-dark: #20252e;
+ --stone-light: #f5f6f8;
+ --stone-border: #858b96;
+ --shadow-control: 0 var(--space-1) var(--space-2) var(--shadow-color);
+ --shadow-card: 0 var(--space-3) var(--space-8) var(--shadow-color);
+ --shadow-popover: 0 var(--space-4) var(--space-14) var(--shadow-color);
+ --shadow-dialog: 0 var(--space-6) var(--space-20) var(--shadow-color);
+ --shadow-board: 0 var(--space-2) var(--space-5) var(--shadow-color);
+ --shadow-selection: inset 0 0 0 var(--stroke-2) var(--glass-border);
+}
+
+:root[data-palette='forest'] {
+ --accent: #14744f;
+ --accent-hover: #0c5c3d;
+ --accent-text: #116942;
+ --hero: #e4f2e9;
+}
+:root[data-palette='violet'] {
+ --accent: #7742bf;
+ --accent-hover: #62329f;
+ --accent-text: #6836af;
+ --hero: #f0e9fc;
+}
+:root[data-theme='dark'] {
+ color-scheme: dark;
+ --text: #e8edf7;
+ --background: #101827;
+ --surface: #192438;
+ --soft: #223149;
+ --hero: #1c3052;
+ --accent: #397aee;
+ --accent-hover: #2b69d3;
+ --accent-text: #9bbeff;
+ --secondary: #adbbd0;
+ --border: #34445d;
+ --danger-bg: #442b32;
+ --danger-text: #ffb5a9;
+ --success: #92ddb1;
+ --success-bg: #203d35;
+ --glass-surface: #192438df;
+ --glass-border: #34445daa;
+ --overlay: #0008;
+ --shadow-color: #0006;
+}
+:root[data-theme='dark'][data-palette='forest'] {
+ --accent: #177f5a;
+ --accent-hover: #106e4b;
+ --accent-text: #7adab1;
+ --hero: #183a30;
+}
+:root[data-theme='dark'][data-palette='violet'] {
+ --accent: #8554d1;
+ --accent-hover: #7744c5;
+ --accent-text: #d2b4ff;
+ --hero: #30244c;
+}
+
+/* Player identities remain distinct in either theme. */
+
+.player-color-0 {
+ --player-ink: #775800;
+ --player-soft: #fff3ce;
+ --player-solid: #b88925;
+}
+.player-color-1 {
+ --player-ink: #165ba1;
+ --player-soft: #e6f1ff;
+ --player-solid: #397bba;
+}
+.player-color-2 {
+ --player-ink: #aa3938;
+ --player-soft: #ffecea;
+ --player-solid: #b95f56;
+}
+.player-color-3 {
+ --player-ink: #217146;
+ --player-soft: #e6f5eb;
+ --player-solid: #568264;
+}
+.player-color-4 {
+ --player-ink: #30343b;
+ --player-soft: #e8eaed;
+ --player-solid: #30343b;
+}
+.player-color-5 {
+ --player-ink: #985023;
+ --player-soft: #fff0e1;
+ --player-solid: #ad702f;
+}
+.player-color-6 {
+ --player-ink: #703cad;
+ --player-soft: #f2eaff;
+ --player-solid: #8654b8;
+}
+.player-color-7 {
+ --player-ink: #536070;
+ --player-soft: #eaf0f4;
+ --player-solid: #7d8794;
+}
+[data-theme='dark'] .player-color-0 {
+ --player-ink: #f3cd67;
+ --player-soft: #393326;
+}
+[data-theme='dark'] .player-color-1 {
+ --player-ink: #95c6ff;
+ --player-soft: #243650;
+}
+[data-theme='dark'] .player-color-2 {
+ --player-ink: #ffb4ad;
+ --player-soft: #402d36;
+}
+[data-theme='dark'] .player-color-3 {
+ --player-ink: #92ddb1;
+ --player-soft: #203d35;
+}
+[data-theme='dark'] .player-color-4 {
+ --player-ink: #e1e4e9;
+ --player-soft: #30343b;
+}
+[data-theme='dark'] .player-color-5 {
+ --player-ink: #ffc290;
+ --player-soft: #3b3029;
+}
+[data-theme='dark'] .player-color-6 {
+ --player-ink: #d9b8ff;
+ --player-soft: #352a49;
+}
+[data-theme='dark'] .player-color-7 {
+ --player-ink: #c1ccd6;
+ --player-soft: #303c48;
+}