Initial commit: Sudoku game with accounts, history, leaderboard, themes and i18n

Node/Express + SQLite backend, vanilla JS frontend, deployed via Docker Compose.
This commit is contained in:
2026-08-27 21:24:38 +02:00
commit 00e8dcb7d2
16 changed files with 2405 additions and 0 deletions

490
app/styles.css Normal file
View File

@@ -0,0 +1,490 @@
:root{
--bg:#1b1e27;
--panel:#242836;
--cell:#2c3142;
--cell-alt:#323851;
--line-thin:#454c66;
--line-thick:#7d88b8;
--text:#eef0f7;
--muted:#9aa2c0;
--accent:#5b8cff;
--accent-soft:#33395222;
--given:#c9cfe8;
--user:#8fc7ff;
--error:#ff6b6b;
--success:#4ade80;
--select:#3a4a7a;
--peer:#2f3650;
--same:#3d5a8a;
--warn:#f5b942;
}
:root[data-theme="green"]{
--accent:#34d399;
--accent-soft:#1f6f5722;
--user:#7ee3bd;
--select:#1f5b48;
--same:#227a5e;
--peer:#20352f;
--given:#bfe8d4;
}
:root[data-theme="amber"]{
--accent:#fb923c;
--accent-soft:#8a4a1c22;
--user:#ffce9e;
--select:#5a3820;
--same:#7a4d28;
--peer:#332a22;
--given:#f0d2b0;
}
:root[data-theme="mono"]{
--bg:#000000;
--panel:#0a0a0a;
--cell:#141414;
--cell-alt:#1c1c1c;
--line-thin:#3a3a3a;
--line-thick:#6b6b6b;
--text:#ffffff;
--muted:#a0a0a0;
--accent:#e11d2e;
--accent-soft:#4d0a0e22;
--given:#ffffff;
--user:#ff6b6b;
--select:#3a0d10;
--peer:#1a1a1a;
--same:#5c1015;
}
*{box-sizing:border-box;}
html,body{height:100%;}
body{
margin:0;
background:radial-gradient(circle at top, #232838, #14161d 70%);
color:var(--text);
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
display:flex;
align-items:flex-start;
justify-content:center;
min-height:100vh;
padding:16px;
}
.app{
width:100%;
max-width:520px;
display:flex;
flex-direction:column;
gap:14px;
}
h1{
margin:0;
font-size:1.6rem;
letter-spacing:0.04em;
text-align:center;
font-weight:700;
}
[hidden]{ display:none !important; }
select, button, input{
font-family:inherit;
font-size:0.9rem;
border-radius:10px;
border:1px solid var(--line-thin);
background:var(--cell);
color:var(--text);
padding:8px 10px;
cursor:pointer;
}
input{ cursor:text; }
select:hover, button:hover{ border-color:var(--accent); }
button:active{ transform:translateY(1px); }
button:disabled{ opacity:0.4; cursor:not-allowed; }
/* ---------- Settings bar (language + theme) ---------- */
.settings-bar{
display:flex;
justify-content:space-between;
align-items:center;
gap:10px;
}
.lang-switch{
display:flex;
gap:4px;
background:var(--panel);
border-radius:10px;
padding:3px;
}
.lang-btn{
padding:5px 12px;
font-size:0.75rem;
font-weight:600;
border:none;
background:transparent;
border-radius:8px;
}
.lang-btn.active{ background:var(--accent); color:#0c1220; }
.theme-switch{
display:flex;
align-items:center;
gap:8px;
background:var(--panel);
border-radius:10px;
padding:6px 12px;
}
.theme-swatch{
width:20px;
height:20px;
border-radius:50%;
padding:0;
border:2px solid transparent;
cursor:pointer;
}
.theme-swatch[data-theme="blue"]{ background:#5b8cff; }
.theme-swatch[data-theme="green"]{ background:#34d399; }
.theme-swatch[data-theme="amber"]{ background:#fb923c; }
.theme-swatch[data-theme="mono"]{ background:conic-gradient(#000 0% 33.33%, #fff 33.33% 66.66%, #e11d2e 66.66% 100%); }
.theme-swatch.active{ border-color:var(--text); }
/* ---------- Auth bar ---------- */
.authbar{
display:flex;
align-items:center;
justify-content:space-between;
gap:8px;
background:var(--panel);
border-radius:14px;
padding:8px 14px;
font-size:0.85rem;
}
.authbar .who{ color:var(--muted); }
.authbar .who b{ color:var(--text); }
.authbar .pts{ color:var(--warn); font-weight:700; }
.authbar .actions-inline{ display:flex; gap:6px; }
.authbar button{
padding:6px 10px;
font-size:0.8rem;
background:transparent;
}
.authbar button.primary{
background:var(--accent);
border-color:var(--accent);
color:#0c1220;
font-weight:700;
}
.anon-banner{
display:flex;
align-items:center;
gap:10px;
background:linear-gradient(135deg, #2a3350, #262b45);
border:1px solid var(--line-thin);
border-radius:12px;
padding:10px 12px;
font-size:0.82rem;
color:var(--muted);
}
.anon-banner .txt{ flex:1; line-height:1.4; }
.anon-banner .txt b{ color:var(--text); }
.anon-banner .btns{ display:flex; flex-direction:column; gap:6px; }
.anon-banner button{ padding:6px 10px; font-size:0.78rem; white-space:nowrap; }
.anon-banner .close-x{
background:none; border:none; color:var(--muted); font-size:1rem;
padding:2px 6px; align-self:flex-start;
}
.topbar{
display:flex;
justify-content:space-between;
align-items:center;
background:var(--panel);
border-radius:14px;
padding:10px 16px;
font-variant-numeric:tabular-nums;
box-shadow:0 4px 18px rgba(0,0,0,0.35);
}
.topbar .stat{
display:flex;
flex-direction:column;
align-items:center;
font-size:0.75rem;
color:var(--muted);
gap:2px;
}
.topbar .stat span.value{
font-size:1.05rem;
color:var(--text);
font-weight:600;
}
.board-wrap{
position:relative;
background:var(--panel);
border-radius:16px;
padding:14px;
box-shadow:0 6px 24px rgba(0,0,0,0.4);
}
.board{
display:grid;
grid-template-columns:repeat(9, 1fr);
grid-template-rows:repeat(9, 1fr);
width:100%;
aspect-ratio:1/1;
border:2px solid var(--line-thick);
border-radius:8px;
overflow:hidden;
background:var(--line-thin);
gap:1px;
transition:filter 0.2s ease;
}
.board.blurred{ filter:blur(6px); pointer-events:none; }
.cell{
background:var(--cell);
display:flex;
align-items:center;
justify-content:center;
position:relative;
font-size:clamp(16px, 4.2vw, 26px);
font-weight:600;
color:var(--user);
cursor:pointer;
user-select:none;
}
.cell.alt-box{ background:var(--cell-alt); }
.cell.given{ color:var(--given); font-weight:700; cursor:default; }
.cell.peer{ background:var(--peer); }
.cell.alt-box.peer{ background:#37406a; }
.cell.same-value{ background:var(--same); }
.cell.selected{ background:var(--select); box-shadow:inset 0 0 0 2px var(--accent); }
.cell.error{ color:var(--error) !important; }
.cell.hint{ color:var(--success) !important; }
.cell:nth-child(3n){ border-right:2px solid var(--line-thick); }
.cell:nth-child(9n){ border-right:none; }
.notes{
position:absolute;
inset:0;
display:grid;
grid-template-columns:repeat(3,1fr);
grid-template-rows:repeat(3,1fr);
font-size:clamp(7px, 1.6vw, 10px);
font-weight:500;
color:var(--muted);
padding:2px;
}
.notes span{ display:flex; align-items:center; justify-content:center; }
.board-overlay{
position:absolute;
inset:14px;
background:rgba(20,22,30,0.6);
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
gap:12px;
border-radius:8px;
z-index:5;
text-align:center;
padding:16px;
}
.board-overlay[hidden]{ display:none; }
.board-overlay p{ margin:0; font-size:1.1rem; font-weight:700; color:var(--text); }
.board-overlay .big-btn{
padding:14px 28px;
font-size:1.1rem;
font-weight:700;
background:var(--accent);
border-color:var(--accent);
color:#0c1220;
}
.keypad{
display:grid;
grid-template-columns:repeat(9, 1fr);
gap:6px;
}
.keypad button{
padding:12px 0;
font-size:1.1rem;
font-weight:700;
background:var(--cell);
}
.keypad button.count-done{
opacity:0.35;
pointer-events:none;
}
.actions{
display:flex;
gap:8px;
justify-content:center;
flex-wrap:wrap;
}
.actions button{
display:flex;
align-items:center;
gap:6px;
flex:1 1 auto;
justify-content:center;
min-width:80px;
}
.actions button.active{
border-color:var(--accent);
background:var(--accent-soft);
color:var(--accent);
}
.actions[hidden]{ display:none; }
.msg{
text-align:center;
min-height:1.4em;
font-weight:600;
color:var(--success);
}
/* ---------- Modals ---------- */
.modal-overlay{
position:fixed;
inset:0;
background:rgba(10,12,18,0.75);
display:none;
align-items:center;
justify-content:center;
z-index:20;
padding:16px;
}
.modal-overlay.show{ display:flex; }
.modal-card{
background:var(--panel);
padding:24px 26px;
border-radius:16px;
text-align:center;
box-shadow:0 10px 40px rgba(0,0,0,0.5);
width:100%;
max-width:400px;
max-height:90vh;
overflow-y:auto;
position:relative;
}
.modal-card h2{ margin:0 0 6px; }
.modal-card p.sub{ color:var(--muted); margin:4px 0 16px; font-size:0.85rem; }
.modal-close{
position:absolute;
top:10px; right:10px;
background:none; border:none;
color:var(--muted);
font-size:1.1rem;
padding:4px 8px;
}
.modal-card.win h2{ color:var(--success); }
.modal-card .field{
display:flex;
flex-direction:column;
gap:4px;
text-align:left;
margin-bottom:12px;
}
.modal-card .field label{ font-size:0.8rem; color:var(--muted); }
.modal-card .field input{ width:100%; }
.modal-card .error-text{ color:var(--error); font-size:0.82rem; min-height:1.2em; margin-bottom:8px; }
.modal-card .tabs{
display:flex;
gap:6px;
margin-bottom:16px;
background:var(--cell);
border-radius:10px;
padding:4px;
}
.modal-card .tabs button{
flex:1;
border:none;
background:transparent;
padding:8px;
}
.modal-card .tabs button.active{
background:var(--accent);
color:#0c1220;
font-weight:700;
}
.modal-card .full-btn{
width:100%;
background:var(--accent);
border-color:var(--accent);
color:#0c1220;
font-weight:700;
padding:10px;
}
.modal-card .benefits-list{
text-align:left;
color:var(--muted);
font-size:0.85rem;
line-height:1.7;
margin:0 0 16px;
padding-left:0;
list-style:none;
}
.modal-card .benefits-list li{ display:flex; gap:8px; align-items:flex-start; }
.modal-card .stack{ display:flex; flex-direction:column; gap:8px; }
.modal-card .win-stats{
display:flex;
justify-content:center;
gap:18px;
margin:10px 0 18px;
}
.modal-card .win-stats .stat{ display:flex; flex-direction:column; align-items:center; }
.modal-card .win-stats .stat .v{ font-size:1.3rem; font-weight:700; }
.modal-card .win-stats .stat .l{ font-size:0.7rem; color:var(--muted); text-transform:uppercase; }
.data-table{
width:100%;
border-collapse:collapse;
font-size:0.82rem;
text-align:left;
}
.data-table th, .data-table td{
padding:6px 8px;
border-bottom:1px solid var(--line-thin);
}
.data-table th{ color:var(--muted); font-weight:600; }
.data-table tr:first-child td{ font-weight:700; }
.empty-note{ color:var(--muted); font-size:0.85rem; padding:12px 0; }
.diff-tabs{
display:flex;
gap:6px;
margin-bottom:12px;
flex-wrap:wrap;
justify-content:center;
}
.diff-tabs button{
padding:6px 12px;
font-size:0.78rem;
background:var(--cell);
}
.diff-tabs button.active{
background:var(--accent);
border-color:var(--accent);
color:#0c1220;
font-weight:700;
}
.modal-card.rules{ text-align:left; }
.modal-card.rules h2{ text-align:center; }
.modal-card.rules h3{
margin:16px 0 6px;
font-size:0.9rem;
color:var(--accent);
}
.modal-card.rules h3:first-of-type{ margin-top:0; }
.modal-card.rules p, .modal-card.rules li{
font-size:0.85rem;
color:var(--muted);
line-height:1.6;
}
.modal-card.rules ul, .modal-card.rules ol{
margin:4px 0 12px;
padding-left:20px;
}
footer{
text-align:center;
color:var(--muted);
font-size:0.75rem;
opacity:0.7;
}