// chibre — feuille de score Jass · style sanguinolent
// Logique :
// atouts : Trèfle (×1), Carreau (×1), Cœur (×1), Pique (×2)
// par manche : (points + annonces + match) × multiplicateur
// bonus Match = 100 (les 9 plis)
// total points par manche = 157 → saisir dans une équipe remplit l'autre
// manches éditables ; objectif personnalisable
const { useState, useEffect, useMemo } = React;
// --- palette sanguinolente ------------------------------------------------
const C = {
bg: '#0a0807',
bgDeep: '#050403',
bgVoid: '#020100',
panel: '#14100e',
panelLight:'#1f1815',
paper: '#2a221d',
paperLight:'#3a2e26',
line: '#3a2823',
lineDim: '#251915',
bone: '#d8c9a8',
boneDim: '#9c8d70',
bonePale: '#e8dfca',
blood: '#7a0c0c',
bloodBri: '#b81818',
bloodDeep: '#3d0606',
bloodInk: '#510808',
rust: '#6b3a1a',
amber: '#c9a142',
rot: '#5a6b32',
ash: '#6a605a',
};
const TRUMPS = [
{ id: 'trefle', label: 'Trèfle', mult: 1, glyph: '♣' },
{ id: 'carreau', label: 'Carreau', mult: 1, glyph: '♦' },
{ id: 'coeur', label: 'Cœur', mult: 1, glyph: '♥' },
{ id: 'pique', label: 'Pique', mult: 2, glyph: '♠' },
];
// --- racine ---------------------------------------------------------------
function App() {
useEffect(() => {
const linkEl = document.createElement('link');
linkEl.rel = 'stylesheet';
linkEl.href = 'https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,700;0,900;1,700;1,900&family=Special+Elite&family=IM+Fell+English+SC&family=JetBrains+Mono:wght@400;500;700&display=swap';
document.head.appendChild(linkEl);
const styleEl = document.createElement('style');
styleEl.textContent = `
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
background: ${C.bgVoid};
font-family: 'Special Elite', 'JetBrains Mono', monospace;
color: ${C.bone};
overflow-x: hidden;
}
::selection { background: ${C.blood}; color: ${C.bone}; }
input { font-family: inherit; color: inherit; }
input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; }
button { font-family: inherit; }
@keyframes ch_flash {
0% { filter: brightness(1) saturate(1); }
25% { filter: brightness(1.4) saturate(1.5) drop-shadow(0 0 24px ${C.bloodBri}); }
100% { filter: brightness(1) saturate(1); }
}
@keyframes ch_pop {
0% { transform: translateY(0) scale(1); }
35% { transform: translateY(-3px) scale(1.04); }
100% { transform: translateY(0) scale(1); }
}
@keyframes ch_fadeup {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes ch_blink { 0%,100% { opacity: 1; } 50% { opacity: 0.2; } }
@keyframes ch_drip {
0% { transform: translateY(-100%) scaleY(0.5); opacity: 0; }
20% { opacity: 1; }
100% { transform: translateY(0) scaleY(1); opacity: 0.85; }
}
@keyframes ch_flicker {
0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% { opacity: 1; text-shadow: 0 0 4px ${C.bloodBri}88, 0 0 12px ${C.blood}; }
20%, 24%, 55% { opacity: 0.55; text-shadow: none; }
}
.ch-page { position: relative; min-height: 100vh; }
/* film grain + vignette */
.ch-page::before {
content: '';
position: fixed; inset: 0;
background-image:
radial-gradient(circle at 30% 30%, ${C.blood}11, transparent 50%),
radial-gradient(circle at 80% 80%, ${C.bloodInk}15, transparent 55%),
radial-gradient(ellipse at center, transparent 30%, ${C.bgVoid}d0 100%);
pointer-events: none;
z-index: 1;
}
.ch-page::after {
content: '';
position: fixed; inset: 0;
background-image:
repeating-radial-gradient(circle at 13% 27%, ${C.bone}06 0 1px, transparent 1px 3px),
repeating-radial-gradient(circle at 71% 84%, ${C.bone}05 0 1px, transparent 1px 4px);
pointer-events: none;
opacity: 0.7;
z-index: 2;
mix-blend-mode: overlay;
}
.ch-inner { max-width: 1200px; margin: 0 auto; position: relative; z-index: 3; padding: 28px 20px 56px; }
.ch-header {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 14px;
padding-bottom: 26px;
margin-bottom: 26px;
border-bottom: 2px solid ${C.bloodDeep};
position: relative;
}
.ch-header::after {
content: '';
position: absolute; left: 50%; bottom: -2px; transform: translateX(-50%);
width: 60%; height: 2px;
background: linear-gradient(90deg, transparent, ${C.bloodBri} 40%, ${C.bloodBri} 60%, transparent);
box-shadow: 0 0 12px ${C.bloodBri};
}
.ch-header-side { display: flex; align-items: center; gap: 10px; }
.ch-header-side-r { justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
.ch-header-center { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.ch-board {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 14px;
align-items: stretch;
margin-bottom: 24px;
}
.ch-trump-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.ch-inputs-row {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 18px;
align-items: flex-start;
}
.ch-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding-top: 18px;
border-top: 1px dashed ${C.line};
}
.ch-history::-webkit-scrollbar { width: 6px; height: 6px; }
.ch-history::-webkit-scrollbar-thumb { background: ${C.bloodDeep}; }
.ch-history::-webkit-scrollbar-track { background: ${C.bgDeep}; }
.ch-input::placeholder { color: ${C.boneDim}; opacity: 0.5; font-style: italic; }
.ch-mobile-only { display: none; }
/* ============= MOBILE ============= */
@media (max-width: 760px) {
.ch-inner { padding: 14px 10px 36px; }
.ch-header {
grid-template-columns: 1fr;
gap: 10px;
padding-bottom: 18px;
margin-bottom: 16px;
text-align: center;
}
.ch-header-side { justify-content: center !important; flex-wrap: wrap; }
.ch-header-side-r { order: 3; }
.ch-wordmark { font-size: 56px !important; }
.ch-board {
grid-template-columns: 1fr;
gap: 12px;
margin-bottom: 18px;
}
.ch-divider { display: none !important; }
.ch-card { padding: 22px 18px 18px !important; }
.ch-score-number { font-size: 96px !important; }
.ch-trump-row { grid-template-columns: repeat(4, 1fr); gap: 8px; }
.ch-token-disc { width: 56px !important; height: 56px !important; }
.ch-inputs-row { grid-template-columns: 1fr; gap: 14px; }
.ch-inputs-center { padding-top: 0 !important; order: 2; }
.ch-input-col-r { order: 3; align-items: flex-start !important; }
.ch-input-col-r .ch-col-head,
.ch-input-col-r .ch-input-row,
.ch-input-col-r .ch-bottom-row,
.ch-input-col-r .ch-preview-row { flex-direction: row !important; }
.ch-actions { flex-direction: column-reverse; align-items: stretch; gap: 10px; }
.ch-actions button { width: 100%; justify-content: center; }
.ch-entry, .ch-history-card { padding: 16px 14px !important; }
.ch-section-title { font-size: 16px !important; }
.ch-mult-label { display: none; }
.ch-thead, .ch-trow { gap: 6px !important; padding: 10px 2px !important; }
.ch-th-cumul, .ch-td-cumul { display: none; }
.ch-th-trump, .ch-td-trump { flex: 0 0 64px !important; }
.ch-trump-pill-text { display: none; }
.ch-mobile-only { display: inline; }
.ch-target-wrap { flex-wrap: wrap; }
.ch-target-custom { width: 70px !important; }
.ch-verdict-inner { min-width: 0 !important; padding: 32px 24px 26px !important; width: calc(100vw - 28px); }
.ch-verdict-name { font-size: 56px !important; }
.ch-edit-bar { flex-wrap: wrap; }
}
@media (max-width: 380px) {
.ch-trump-row { grid-template-columns: repeat(2, 1fr); }
.ch-score-number { font-size: 84px !important; }
.ch-wordmark { font-size: 44px !important; }
}
`;
document.head.appendChild(styleEl);
return () => { linkEl.remove(); styleEl.remove(); };
}, []);
return ;
}
// --- composant principal --------------------------------------------------
function Chibre() {
const [target, setTarget] = useState(1000);
const [customTarget, setCustomTarget] = useState('');
const [targetMode, setTargetMode] = useState('1000'); // '1000' | '2500' | 'custom'
const [names, setNames] = useState(['Nous', 'Eux']);
const [editingName, setEditingName] = useState(null);
const [rounds, setRounds] = useState([]);
const [editingIdx, setEditingIdx] = useState(null);
const [trumpId, setTrumpId] = useState('coeur');
const [p1, setP1] = useState('');
const [p2, setP2] = useState('');
const [w1, setW1] = useState('');
const [w2, setW2] = useState('');
const [match, setMatch] = useState('none');
const [flash, setFlash] = useState(null);
const trump = TRUMPS.find(t => t.id === trumpId);
const scores = useMemo(() => {
const s = [0, 0];
rounds.forEach(r => { s[0] += r.t1; s[1] += r.t2; });
return s;
}, [rounds]);
const winner = scores[0] >= target ? 0 : scores[1] >= target ? 1 : null;
// Saisie auto-complémentaire : 157 = total des points par manche
const setPoints1 = (val) => {
const cleaned = val.replace(/[^0-9]/g, '');
setP1(cleaned);
if (cleaned === '') { setP2(''); return; }
const n = Math.min(157, parseInt(cleaned) || 0);
setP2(String(Math.max(0, 157 - n)));
};
const setPoints2 = (val) => {
const cleaned = val.replace(/[^0-9]/g, '');
setP2(cleaned);
if (cleaned === '') { setP1(''); return; }
const n = Math.min(157, parseInt(cleaned) || 0);
setP1(String(Math.max(0, 157 - n)));
};
const previewT = useMemo(() => {
const pp1 = parseInt(p1) || 0;
const pp2 = parseInt(p2) || 0;
const ww1 = parseInt(w1) || 0;
const ww2 = parseInt(w2) || 0;
const m1 = match === 'team1' ? 100 : 0;
const m2 = match === 'team2' ? 100 : 0;
return [(pp1 + ww1 + m1) * trump.mult, (pp2 + ww2 + m2) * trump.mult];
}, [p1, p2, w1, w2, match, trump]);
function applyTargetMode(mode, custom) {
setTargetMode(mode);
if (mode === '1000') setTarget(1000);
else if (mode === '2500') setTarget(2500);
else {
const n = parseInt(custom) || 0;
if (n > 0) setTarget(n);
}
}
function validate() {
if (winner !== null) return;
const pp1 = parseInt(p1) || 0;
const pp2 = parseInt(p2) || 0;
const ww1 = parseInt(w1) || 0;
const ww2 = parseInt(w2) || 0;
if (pp1 + pp2 + ww1 + ww2 === 0 && match === 'none') return;
setRounds(rs => [...rs, {
trump: trumpId, mult: trump.mult,
p1: pp1, p2: pp2, w1: ww1, w2: ww2,
match,
t1: previewT[0], t2: previewT[1],
}]);
if (previewT[0] > previewT[1]) setFlash(0);
else if (previewT[1] > previewT[0]) setFlash(1);
setTimeout(() => setFlash(null), 1100);
setP1(''); setP2(''); setW1(''); setW2(''); setMatch('none');
}
function undo() { setRounds(rs => rs.slice(0, -1)); }
function reset() {
if (rounds.length && !confirm('Effacer la partie en cours ?')) return;
setRounds([]); setP1(''); setP2(''); setW1(''); setW2(''); setMatch('none');
}
function saveEdit(idx, edited) {
const t = TRUMPS.find(x => x.id === edited.trump) || trump;
const m1 = edited.match === 'team1' ? 100 : 0;
const m2 = edited.match === 'team2' ? 100 : 0;
const t1 = (edited.p1 + edited.w1 + m1) * t.mult;
const t2 = (edited.p2 + edited.w2 + m2) * t.mult;
setRounds(rs => rs.map((r, i) => i === idx ? { ...edited, mult: t.mult, t1, t2 } : r));
setEditingIdx(null);
}
function deleteRound(idx) {
if (!confirm('Supprimer cette manche ?')) return;
setRounds(rs => rs.filter((_, i) => i !== idx));
setEditingIdx(null);
}
return (
{/* éclaboussures de fond */}
0}
/>
setNames([n, names[1]])}
score={scores[0]}
target={target}
preview={previewT[0]}
isWinner={winner === 0}
flashing={flash === 0}
editing={editingName === 0}
setEditing={(b) => setEditingName(b ? 0 : null)}
/>
setNames([names[0], n])}
score={scores[1]}
target={target}
preview={previewT[1]}
isWinner={winner === 1}
flashing={flash === 1}
editing={editingName === 1}
setEditing={(b) => setEditingName(b ? 1 : null)}
/>
0}
locked={winner !== null}
/>
{winner !== null && }
);
}
// --- éclaboussures de sang en fond ---------------------------------------
function BloodSplats() {
return (
{/* gouttes/coulures */}
{[
[12, 5, 24], [28, 3, 80], [54, 4, 30], [73, 6, 60], [88, 4, 40],
].map(([x, w, h], i) => (
))}
);
}
// --- en-tête --------------------------------------------------------------
function Header({ targetMode, target, customTarget, setCustomTarget, applyTargetMode, onReset, canReset }) {
return (
✚
PRENEZ · GARDE
chibre
— feuille de pointage —
Effacer
);
}
function Ornament() {
return (
);
}
function TargetToggle({ mode, target, custom, setCustom, apply }) {
return (
{[
['1000', '1000'],
['2500', '2500'],
].map(([m, l]) => (
apply(m, custom)}
style={{
...S.targetBtn,
...(mode === m ? S.targetBtnOn : null),
}}
>{l}
))}
apply('custom', custom)}
onChange={(e) => {
const v = e.target.value.replace(/[^0-9]/g, '');
setCustom(v);
apply('custom', v);
}}
style={S.targetCustomInput}
/>
);
}
// --- carte d'équipe -------------------------------------------------------
function TeamCard({ side, accent, name, onRename, score, target, preview, isWinner, flashing, editing, setEditing }) {
const pct = Math.min(1, score / Math.max(1, target));
const previewPct = preview > 0 ? Math.min(1, (score + preview) / Math.max(1, target)) : pct;
const [shown, setShown] = useState(score);
const [bumping, setBumping] = useState(false);
useEffect(() => {
if (score === shown) return;
setBumping(true);
const t = setTimeout(() => { setShown(score); setBumping(false); }, 180);
return () => clearTimeout(t);
}, [score]); // eslint-disable-line
return (
{/* corner stains */}
{/* edge strip */}
{side === 'left' ? 'I' : 'II'}
{editing ? (
{ onRename(e.target.value.trim() || name); setEditing(false); }}
onKeyDown={(e) => { if (e.key === 'Enter') e.currentTarget.blur(); if (e.key === 'Escape') setEditing(false); }}
style={{ ...S.teamNameInput, color: accent, borderColor: accent }}
/>
) : (
setEditing(true)} title="Renommer (cliquer)">
{name}
✎
)}
{side === 'left' ? 'I' : 'II'}
{shown}
de {target}
{preview > 0 && (
+ {preview}
)}
{isWinner
? · VAINQUEUR ·
:
il reste {' '}
{Math.max(0, target - score)} {' '}
points
}
);
}
function CornerStain({ pos }) {
const map = {
tl: { top: -10, left: -10, rot: 0 },
br: { bottom: -10, right: -10, rot: 180 },
};
const p = map[pos];
return (
);
}
// --- jauge ----------------------------------------------------------------
function Gauge({ pct, previewPct, accent }) {
const ticks = [];
for (let i = 0; i <= 10; i++) ticks.push({ pct: i * 10, major: true });
for (let i = 0; i < 10; i++) ticks.push({ pct: i * 10 + 5, major: false });
return (
{previewPct > pct && (
)}
{ticks.map((t, i) => (
))}
);
}
// --- séparateur -----------------------------------------------------------
function Divider() {
return (
);
}
// --- saisie ---------------------------------------------------------------
function RoundEntry({ trump, trumpId, setTrumpId, names,
p1, setP1, p2, setP2, w1, setW1, w2, setW2,
match, setMatch, previewT, onValidate, onUndo, canUndo, locked }) {
return (
· MANCHE ·
Inscrire la donne
1 ? C.bloodBri : C.line,
color: trump.mult > 1 ? C.bloodBri : C.bone,
boxShadow: trump.mult > 1 ? `0 0 12px ${C.bloodBri}55` : 'none',
}}>
×{trump.mult}
multiplicateur
{TRUMPS.map(t => (
setTrumpId(t.id)}
/>
))}
setMatch(match === 'team1' ? 'none' : 'team1')}
preview={previewT[0]}
mult={trump.mult}
/>
setMatch(match === 'team2' ? 'none' : 'team2')}
preview={previewT[1]}
mult={trump.mult}
mirror
/>
↶ Annuler la dernière
✚
Valider la manche
✚
);
}
function TrumpToken({ trump, active, onClick }) {
const isRed = trump.id === 'coeur' || trump.id === 'carreau';
const ink = isRed ? C.bloodBri : C.bone;
return (
{trump.glyph}
{trump.mult > 1 && (
×{trump.mult}
)}
{trump.label}
);
}
function ColumnInputs({ label, sideTag, accent, points, setPoints, weis, setWeis, match, onMatch, preview, mult, mirror }) {
return (
{sideTag}
{label}
setWeis(v.replace(/[^0-9]/g, ''))} accent={accent} />
{match ? '✚' : '+'}
MATCH +100
cette manche
0 ? accent : C.boneDim,
}}>
{preview > 0 ? `+${preview}` : '—'}
);
}
function Field({ label, hint, value, onChange, accent, big }) {
return (
{label} · {hint}
onChange(e.target.value)}
placeholder="0"
style={{
...S.fieldInput,
...(big ? S.fieldInputBig : null),
color: accent,
borderColor: value ? accent : C.line,
}}
/>
);
}
function SumIndicator({ a, b }) {
const sum = a + b;
const ok = sum === 0 || sum === 157;
const color = sum === 0 ? C.boneDim : (ok ? C.amber : C.bloodBri);
return (
somme
{sum || '—'}
= 157
);
}
// --- historique éditable -------------------------------------------------
function History({ rounds, names, editingIdx, setEditingIdx, onSave, onDelete }) {
if (!rounds.length) {
return (
· REGISTRE ·
Historique des manches
Aucune manche jouée. Distribuez les cartes.
);
}
let r1 = 0, r2 = 0;
return (
· REGISTRE ·
Historique · {rounds.length} manche{rounds.length > 1 ? 's' : ''}
n°
Atout
{names[0]}
cumul
{names[1]}
cumul
{rounds.map((r, i) => {
r1 += r.t1; r2 += r.t2;
const t = TRUMPS.find(x => x.id === r.trump) || TRUMPS[0];
const isEditing = editingIdx === i;
if (isEditing) {
return (
onSave(i, edited)}
onCancel={() => setEditingIdx(null)}
onDelete={() => onDelete(i)}
/>
);
}
return (
{String(i + 1).padStart(2, '0')}
1 ? C.bloodBri : C.line,
color: t.id === 'coeur' || t.id === 'carreau' ? C.bloodBri : C.bone,
}}>
{t.glyph}
{t.label}{t.mult > 1 ? ×{t.mult} : null}
{t.mult > 1 ? '×2' : ''}
{r1}
{r2}
setEditingIdx(i)} title="Modifier" style={S.editBtn}>✎
);
})}
);
}
function EditRow({ round, onSave, onCancel, onDelete }) {
const [trumpId, setTrumpId] = useState(round.trump);
const [p1, setP1] = useState(String(round.p1));
const [p2, setP2] = useState(String(round.p2));
const [w1, setW1] = useState(String(round.w1 || ''));
const [w2, setW2] = useState(String(round.w2 || ''));
const [match, setMatch] = useState(round.match || 'none');
const t = TRUMPS.find(x => x.id === trumpId) || TRUMPS[0];
// auto-complete points
const onP1 = (v) => {
const c = v.replace(/[^0-9]/g, '');
setP1(c);
if (c === '') { setP2(''); return; }
setP2(String(Math.max(0, 157 - Math.min(157, parseInt(c) || 0))));
};
const onP2 = (v) => {
const c = v.replace(/[^0-9]/g, '');
setP2(c);
if (c === '') { setP1(''); return; }
setP1(String(Math.max(0, 157 - Math.min(157, parseInt(c) || 0))));
};
const previewT1 = ((parseInt(p1)||0) + (parseInt(w1)||0) + (match === 'team1' ? 100 : 0)) * t.mult;
const previewT2 = ((parseInt(p2)||0) + (parseInt(w2)||0) + (match === 'team2' ? 100 : 0)) * t.mult;
return (
Modifier
{TRUMPS.map(tt => (
setTrumpId(tt.id)}
style={{
...S.editTrumpBtn,
...(trumpId === tt.id ? {
borderColor: C.bloodBri,
color: C.bonePale,
background: C.bloodDeep,
} : null),
}}
title={tt.label}
>
{tt.glyph}
×{tt.mult}
))}
🗑 Supprimer
Annuler
onSave({
trump: trumpId,
p1: parseInt(p1)||0, p2: parseInt(p2)||0,
w1: parseInt(w1)||0, w2: parseInt(w2)||0,
match,
})} style={S.btnPrimary}>
✚ Enregistrer ✚
);
}
function RoundCell({ points, weis, match, total, mult, accent, align }) {
const parts = [`${points}`];
if (weis) parts.push(`+${weis}a`);
if (match) parts.push('+100m');
return (
{total}
({parts.join(' ')}){mult > 1 ? `×${mult}` : ''}
);
}
// --- verdict --------------------------------------------------------------
function Verdict({ name, onReset }) {
return (
· la partie est gagnée ·
{name}
✚
Nouvelle partie
✚
);
}
function Footer() {
const items = [
[C.bone, 'Trèfle', '♣'],
[C.bloodBri,'Carreau', '♦'],
[C.bloodBri,'Cœur', '♥'],
[C.bone, 'Pique ×2','♠'],
];
return (
{items.map(([col, name, gl], i) => (
{gl}
{name}
{i < items.length - 1 && · }
))}
);
}
// --- styles ---------------------------------------------------------------
const S = {
page: {
minHeight: '100vh',
background: `
radial-gradient(ellipse at 25% 0%, ${C.bloodInk}88 0%, transparent 55%),
radial-gradient(ellipse at 80% 100%, ${C.bloodInk}55 0%, transparent 55%),
linear-gradient(180deg, ${C.bg} 0%, ${C.bgDeep} 50%, ${C.bgVoid} 100%),
${C.bgVoid}
`,
backgroundAttachment: 'fixed',
},
eyebrow: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 12,
letterSpacing: '0.34em',
textTransform: 'uppercase',
color: C.bloodBri,
fontWeight: 700,
},
wordmark: {
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 78,
margin: 0,
color: C.bonePale,
letterSpacing: '0.005em',
lineHeight: 1,
textShadow: `
2px 2px 0 ${C.bloodDeep},
4px 4px 0 ${C.bgVoid},
0 0 22px ${C.blood}88
`,
},
subtitle: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 12,
letterSpacing: '0.32em',
color: C.boneDim,
textTransform: 'lowercase',
},
targetWrap: {
display: 'inline-flex',
border: `1px solid ${C.line}`,
background: C.panel,
boxShadow: `inset 0 0 8px ${C.bgVoid}, 0 2px 4px ${C.bgVoid}`,
},
targetBtn: {
background: 'transparent',
border: 'none',
borderRight: `1px solid ${C.lineDim}`,
color: C.boneDim,
padding: '8px 14px',
fontFamily: "'IM Fell English SC', serif",
fontSize: 13,
letterSpacing: '0.12em',
cursor: 'pointer',
fontWeight: 700,
},
targetBtnOn: {
background: `linear-gradient(180deg, ${C.bloodBri}, ${C.blood})`,
color: C.bonePale,
boxShadow: `inset 0 1px 0 ${C.bonePale}33, inset 0 -2px 4px ${C.bloodDeep}`,
textShadow: `0 1px 0 ${C.bloodDeep}`,
},
targetCustomWrap: {
display: 'flex',
alignItems: 'center',
padding: '0 4px',
background: C.bgDeep,
transition: 'all 200ms ease',
},
targetCustomWrapOn: {
background: `linear-gradient(180deg, ${C.bloodBri}22, ${C.bloodDeep})`,
boxShadow: `inset 0 0 12px ${C.blood}55`,
},
targetCustomInput: {
width: 60,
background: 'transparent',
border: 'none',
color: C.bonePale,
padding: '8px 6px',
fontFamily: "'IM Fell English SC', serif",
fontSize: 13,
fontWeight: 700,
letterSpacing: '0.06em',
outline: 'none',
textAlign: 'center',
},
divider: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
minWidth: 36,
},
dividerLabel: {
position: 'absolute',
top: '50%',
transform: 'translateY(-50%)',
fontFamily: "'IM Fell English SC', serif",
fontWeight: 700,
fontSize: 16,
color: C.bonePale,
background: C.bg,
padding: '6px 8px',
letterSpacing: '0.16em',
border: `1px solid ${C.bloodDeep}`,
boxShadow: `0 0 10px ${C.blood}66`,
},
card: {
position: 'relative',
padding: '34px 30px 24px',
background: `
radial-gradient(ellipse at 18% 8%, ${C.paperLight}, ${C.paper} 60%, ${C.panel} 100%)
`,
backgroundColor: C.paper,
color: C.bone,
overflow: 'hidden',
transition: 'box-shadow 350ms ease',
border: 'none',
},
teamHeader: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 14,
marginBottom: 12,
},
teamSideTag: {
fontFamily: "'IM Fell English SC', serif",
fontWeight: 700,
fontSize: 18,
letterSpacing: '0.12em',
opacity: 0.85,
},
teamName: {
background: 'transparent',
border: 'none',
cursor: 'pointer',
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 36,
letterSpacing: '0.01em',
padding: '2px 10px',
textShadow: `1px 1px 0 ${C.bgVoid}`,
},
teamNamePencil: {
fontSize: 14,
marginLeft: 6,
fontStyle: 'normal',
opacity: 0.6,
},
teamNameInput: {
background: C.bgVoid,
border: '1px solid',
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 32,
textAlign: 'center',
padding: '4px 10px',
outline: 'none',
width: 220,
},
scoreWrap: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
margin: '4px 0 14px',
},
scoreNumber: {
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 'clamp(110px, 14vw, 168px)',
lineHeight: 0.92,
letterSpacing: '-0.02em',
fontVariantNumeric: 'lining-nums tabular-nums',
},
scoreFooting: {
display: 'flex',
alignItems: 'center',
gap: 12,
marginTop: 2,
},
scoreOf: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 13,
letterSpacing: '0.18em',
color: C.boneDim,
textTransform: 'lowercase',
},
previewChip: {
display: 'inline-flex',
alignItems: 'baseline',
gap: 2,
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 18,
border: `1px solid`,
padding: '2px 10px',
},
previewPlus: { fontSize: 14, marginRight: 1 },
gaugeWrap: { padding: '14px 0 6px' },
gaugeTrack: {
position: 'relative',
height: 20,
background: `linear-gradient(180deg, ${C.bgVoid}, ${C.bgDeep})`,
border: `1px solid ${C.line}`,
},
gaugeFill: {
position: 'absolute',
left: 0, top: 0, bottom: 0,
transition: 'width 480ms cubic-bezier(.2,.7,.2,1)',
},
gaugeFillPreview: {
position: 'absolute',
left: 0, top: 0, bottom: 0,
transition: 'width 220ms ease',
},
gaugeNeedle: {
position: 'absolute',
top: -8, bottom: -8,
transform: 'translateX(-50%)',
transition: 'left 480ms cubic-bezier(.2,.7,.2,1)',
pointerEvents: 'none',
},
gaugeNeedleHead: {
width: 10, height: 10,
border: `1px solid`,
borderRadius: '50%',
margin: '0 auto',
},
gaugeNeedleStem: {
width: 2,
height: 'calc(100% - 10px)',
margin: '0 auto',
},
cardSeal: {
marginTop: 14,
textAlign: 'center',
fontFamily: "'IM Fell English SC', serif",
fontSize: 13,
letterSpacing: '0.16em',
paddingTop: 12,
borderTop: `1px dashed ${C.line}`,
textTransform: 'lowercase',
},
entry: {
position: 'relative',
background: `linear-gradient(180deg, ${C.panel}, ${C.bgDeep})`,
border: `1px solid ${C.line}`,
padding: '20px 22px',
marginBottom: 22,
boxShadow: `0 0 24px ${C.bloodInk}88, inset 0 0 30px ${C.bgVoid}`,
},
entryHeader: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 12,
marginBottom: 16,
paddingBottom: 12,
borderBottom: `1px dashed ${C.line}`,
},
entryHeaderLeft: { display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' },
entryHeaderRight: { display: 'flex', alignItems: 'center', gap: 12 },
sectionEyebrow: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 11,
letterSpacing: '0.32em',
textTransform: 'uppercase',
color: C.bloodBri,
fontWeight: 700,
},
sectionTitle: {
fontFamily: "'Cormorant Garamond', serif",
fontStyle: 'italic',
fontWeight: 700,
fontSize: 22,
color: C.bonePale,
},
multBadge: {
display: 'inline-flex',
alignItems: 'baseline',
gap: 8,
border: '1px solid',
padding: '5px 14px',
background: C.bgVoid,
},
multBadgeNum: {
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 22,
},
multBadgeLabel: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 11,
letterSpacing: '0.18em',
textTransform: 'lowercase',
color: C.boneDim,
},
entryBody: { display: 'flex', flexDirection: 'column', gap: 22 },
tokenWrap: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 7,
padding: '12px 4px 8px',
background: 'transparent',
border: 'none',
cursor: 'pointer',
transition: 'transform 180ms ease',
position: 'relative',
},
tokenWrapOn: { transform: 'translateY(-3px)' },
tokenDisc: {
position: 'relative',
width: 64, height: 64,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid',
transition: 'all 220ms ease',
},
tokenGlyph: {
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontSize: 32,
lineHeight: 1,
transition: 'all 220ms ease',
textShadow: `1px 1px 0 ${C.bgVoid}`,
},
tokenLabel: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 13,
letterSpacing: '0.1em',
textTransform: 'lowercase',
fontWeight: 700,
transition: 'color 220ms ease',
},
inputsCenter: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
paddingTop: 32,
},
inputCol: {
display: 'flex',
flexDirection: 'column',
gap: 12,
width: '100%',
},
inputColHead: {
display: 'flex',
alignItems: 'baseline',
gap: 10,
},
colSide: {
fontFamily: "'IM Fell English SC', serif",
fontWeight: 700,
fontSize: 14,
letterSpacing: '0.12em',
opacity: 0.8,
},
colName: {
fontFamily: "'Cormorant Garamond', serif",
fontStyle: 'italic',
fontWeight: 700,
fontSize: 22,
},
inputRow: { display: 'flex', gap: 10, width: '100%' },
inputBottomRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, width: '100%' },
field: {
display: 'flex',
flexDirection: 'column',
gap: 6,
flex: '1 1 0',
},
fieldLabel: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 11,
letterSpacing: '0.2em',
textTransform: 'uppercase',
color: C.boneDim,
fontWeight: 700,
},
fieldHint: { letterSpacing: '0.14em', opacity: 0.7, fontStyle: 'italic', textTransform: 'lowercase' },
fieldInput: {
width: '100%',
background: C.bgVoid,
border: `1px solid ${C.line}`,
padding: '10px 14px',
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 700,
fontStyle: 'italic',
fontSize: 24,
fontVariantNumeric: 'lining-nums tabular-nums',
outline: 'none',
transition: 'border-color 220ms ease',
boxShadow: `inset 0 2px 4px ${C.bgVoid}`,
},
fieldInputBig: { fontSize: 32, padding: '8px 14px', fontWeight: 900 },
sumWrap: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 2,
minWidth: 60,
},
sumLabel: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 10,
letterSpacing: '0.28em',
textTransform: 'uppercase',
color: C.boneDim,
fontWeight: 700,
},
sumValue: {
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 30,
lineHeight: 1,
},
sumHint: {
fontFamily: "'Special Elite', monospace",
fontSize: 10,
color: C.boneDim,
letterSpacing: '0.06em',
},
matchBtn: {
display: 'inline-flex',
alignItems: 'center',
gap: 8,
padding: '8px 14px',
background: C.bgVoid,
border: `1px solid ${C.line}`,
color: C.boneDim,
fontFamily: "'IM Fell English SC', serif",
fontSize: 12,
letterSpacing: '0.18em',
textTransform: 'uppercase',
fontWeight: 700,
cursor: 'pointer',
transition: 'all 200ms ease',
},
matchBtnDot: { fontSize: 13, color: C.bloodBri },
matchBtnPlus: { color: C.amber, marginLeft: 2, fontFamily: "'Cormorant Garamond', serif", fontStyle: 'italic', fontWeight: 900 },
previewRow: { display: 'flex', alignItems: 'baseline', gap: 8 },
previewLabel: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 10,
letterSpacing: '0.24em',
textTransform: 'uppercase',
color: C.boneDim,
fontWeight: 700,
},
previewValue: {
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 24,
},
btnGhost: {
background: 'transparent',
border: `1px solid ${C.line}`,
color: C.bonePale,
padding: '10px 18px',
fontFamily: "'IM Fell English SC', serif",
fontSize: 13,
letterSpacing: '0.16em',
textTransform: 'uppercase',
fontWeight: 700,
cursor: 'pointer',
transition: 'all 200ms ease',
},
btnPrimary: {
display: 'inline-flex',
alignItems: 'center',
gap: 12,
padding: '14px 30px',
background: `linear-gradient(180deg, ${C.bloodBri} 0%, ${C.blood} 60%, ${C.bloodDeep} 100%)`,
border: `1px solid ${C.bonePale}`,
color: C.bonePale,
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 20,
letterSpacing: '0.04em',
cursor: 'pointer',
boxShadow: `
inset 0 1px 0 ${C.bonePale}44,
inset 0 -3px 6px ${C.bloodDeep},
0 4px 0 ${C.bloodDeep},
0 0 20px ${C.blood}aa
`,
textShadow: `0 1px 0 ${C.bloodDeep}, 0 0 8px ${C.bonePale}55`,
},
btnPrimaryGlyph: { color: C.bonePale, fontSize: 14, opacity: 0.85 },
history: {
background: `linear-gradient(180deg, ${C.panel}, ${C.bgDeep})`,
border: `1px solid ${C.line}`,
padding: '20px 22px',
marginBottom: 22,
boxShadow: `0 0 18px ${C.bloodInk}66, inset 0 0 20px ${C.bgVoid}`,
},
historyEmpty: {
fontFamily: "'IM Fell English SC', serif",
fontStyle: 'normal',
fontSize: 14,
color: C.boneDim,
textAlign: 'center',
padding: '28px 0',
border: `1px dashed ${C.line}`,
background: C.bgVoid,
letterSpacing: '0.12em',
},
historyScroll: {
maxHeight: 360,
overflowY: 'auto',
},
tableHead: {
display: 'flex',
gap: 12,
padding: '8px 4px 10px',
borderBottom: `1px solid ${C.bloodDeep}`,
marginBottom: 4,
},
thCell: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 11,
letterSpacing: '0.22em',
textTransform: 'uppercase',
color: C.bloodBri,
fontWeight: 700,
},
tableRow: {
display: 'flex',
gap: 12,
padding: '12px 4px',
borderBottom: `1px dotted ${C.lineDim}`,
alignItems: 'center',
},
tdCell: { fontFamily: "'Special Elite', monospace", fontSize: 12 },
trumpPill: {
display: 'inline-flex',
alignItems: 'center',
background: C.bgVoid,
padding: '4px 10px',
border: '1px solid',
fontFamily: "'IM Fell English SC', serif",
fontSize: 12,
fontWeight: 700,
letterSpacing: '0.04em',
},
editBtn: {
background: 'transparent',
border: `1px solid ${C.line}`,
color: C.boneDim,
width: 28, height: 28,
cursor: 'pointer',
fontSize: 13,
transition: 'all 200ms ease',
},
// edit row
editRow: {
padding: '16px 12px',
background: `linear-gradient(180deg, ${C.bloodInk}33, ${C.bgVoid})`,
border: `1px solid ${C.bloodBri}`,
boxShadow: `0 0 18px ${C.bloodBri}55, inset 0 0 20px ${C.bgVoid}`,
margin: '6px 0',
},
editBar: {
display: 'flex',
alignItems: 'center',
gap: 6,
paddingBottom: 12,
borderBottom: `1px dashed ${C.line}`,
marginBottom: 12,
flexWrap: 'wrap',
},
editTrumpBtn: {
background: C.bgVoid,
border: `1px solid ${C.line}`,
color: C.bone,
padding: '6px 10px',
fontFamily: "'IM Fell English SC', serif",
cursor: 'pointer',
transition: 'all 180ms ease',
display: 'inline-flex',
alignItems: 'center',
},
editGrid: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 16,
},
editCol: {
display: 'flex',
flexDirection: 'column',
gap: 8,
},
editColLabel: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 12,
letterSpacing: '0.18em',
textTransform: 'uppercase',
color: C.boneDim,
fontWeight: 700,
},
editFieldRow: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 6,
},
editInput: {
background: C.bgVoid,
border: `1px solid ${C.line}`,
padding: '6px 10px',
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 700,
fontSize: 18,
outline: 'none',
},
editMatchBtn: {
background: C.bgVoid,
border: `1px solid ${C.line}`,
color: C.boneDim,
padding: '6px 12px',
fontFamily: "'IM Fell English SC', serif",
fontSize: 11,
letterSpacing: '0.16em',
textTransform: 'uppercase',
fontWeight: 700,
cursor: 'pointer',
width: 'fit-content',
transition: 'all 180ms ease',
},
editActions: {
display: 'flex',
alignItems: 'center',
gap: 8,
paddingTop: 14,
marginTop: 12,
borderTop: `1px dashed ${C.line}`,
flexWrap: 'wrap',
},
verdict: {
position: 'fixed',
inset: 0,
background: `radial-gradient(ellipse at center, ${C.bloodInk}cc, ${C.bgVoid}f5)`,
backdropFilter: 'blur(6px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 200,
animation: 'ch_fadeup 320ms ease-out',
padding: 16,
},
verdictInner: {
position: 'relative',
background: `
radial-gradient(ellipse at 18% 8%, ${C.paperLight}, ${C.paper} 60%, ${C.panel} 100%)
`,
padding: '44px 64px 36px',
border: `1px solid ${C.bloodBri}`,
boxShadow: `
0 40px 100px ${C.bgVoid},
inset 0 0 60px ${C.bloodInk}55,
0 0 60px ${C.bloodBri}88,
0 0 120px ${C.blood}55
`,
textAlign: 'center',
color: C.bone,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 12,
minWidth: 420,
},
verdictKicker: {
fontFamily: "'IM Fell English SC', serif",
fontSize: 12,
letterSpacing: '0.32em',
textTransform: 'uppercase',
color: C.bloodBri,
marginTop: 6,
fontWeight: 700,
},
verdictName: {
fontFamily: "'Cormorant Garamond', serif",
fontWeight: 900,
fontStyle: 'italic',
fontSize: 76,
color: C.bonePale,
lineHeight: 1,
textShadow: `
2px 2px 0 ${C.bloodDeep},
4px 4px 0 ${C.bgVoid},
0 0 40px ${C.bloodBri}88,
0 0 80px ${C.blood}55
`,
},
verdictRule: {
width: 90, height: 1,
background: `linear-gradient(90deg, transparent, ${C.bloodBri}, transparent)`,
margin: '4px 0 8px',
boxShadow: `0 0 8px ${C.bloodBri}`,
},
footer: {
textAlign: 'center',
padding: '20px 0 0',
fontFamily: "'IM Fell English SC', serif",
fontSize: 12,
letterSpacing: '0.1em',
},
};
ReactDOM.createRoot(document.getElementById('root')).render( );