This commit is contained in:
2026-03-05 22:30:37 +02:00
parent 817bbfb44c
commit 637b5f0167
12 changed files with 1022 additions and 790 deletions

432
static/css/layout.css Normal file
View File

@@ -0,0 +1,432 @@
:root {
--ui-scale: 1.06;
--base-font-size: clamp(16px, 0.35vw + 0.9rem, 20px);
}
#desktop {
flex: 1;
width: 100%;
padding: 4.3rem 1rem 1rem;
display: flex;
align-items: center;
justify-content: center;
}
.mobile-control-strip {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 50;
padding: 0.45rem 0.55rem;
}
.ui-controls {
display: flex;
align-items: center;
gap: 0.4rem;
}
.theme-picker {
min-width: 9rem;
}
.desktop-taskbar {
display: none;
}
.config-window {
width: min(78rem, 100%);
}
.intro-copy {
margin-bottom: 0.7rem;
}
.room-form {
display: flex;
flex-direction: column;
gap: 0.65rem;
}
.config-layout {
display: grid;
gap: 0.75rem;
grid-template-columns: minmax(0, 1fr) 24rem;
}
.config-panel {
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.6rem;
}
.field-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.number-input-wrap {
display: flex;
align-items: center;
gap: 0.35rem;
}
.number-with-unit .input-unit {
min-width: 2rem;
text-align: right;
}
.preview-content {
display: flex;
flex-direction: column;
gap: 0.55rem;
}
.preview-meta {
display: flex;
justify-content: space-between;
}
.preview-board {
min-height: 13rem;
padding: 0.6rem;
}
.preview-cards {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.preview-card {
width: 3.15rem;
height: 4.45rem;
border-radius: 0.32rem;
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
user-select: none;
cursor: grab;
transition: transform 170ms ease;
}
.preview-card.dragging {
opacity: 0.65;
}
.preview-card.wiggle {
animation: wiggle 250ms linear infinite;
}
@keyframes wiggle {
0% { transform: rotate(-2deg); }
50% { transform: rotate(2deg); }
100% { transform: rotate(-2deg); }
}
.preview-card-remove {
position: absolute;
top: -0.35rem;
right: -0.35rem;
width: 1rem;
height: 1rem;
opacity: 0;
transition: opacity 120ms ease, transform 120ms ease;
transform: scale(0.86);
pointer-events: none;
}
.preview-card:hover .preview-card-remove,
.preview-card-remove:focus {
opacity: 1;
transform: scale(1);
pointer-events: auto;
}
.preview-card.is-removing {
animation: card-pop-out 190ms ease forwards;
}
@keyframes card-pop-out {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.58) rotate(-10deg); }
}
.card-editor {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.card-editor-row {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 0.35rem;
}
.deck-tools-row {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 0.35rem;
}
.icon-btn {
width: 2.45rem;
min-width: 2.45rem;
height: 2.15rem;
padding: 0.1rem;
display: inline-flex;
justify-content: center;
align-items: center;
}
.icon-btn img {
width: 1.05rem;
height: 1.05rem;
image-rendering: pixelated;
}
.preset-modal-overlay {
position: fixed;
inset: 0;
z-index: 70;
display: flex;
align-items: center;
justify-content: center;
}
.preset-modal-window {
width: min(40rem, 92vw);
}
.preset-list {
display: flex;
flex-direction: column;
gap: 0.35rem;
max-height: 11rem;
overflow-y: auto;
margin-bottom: 0.5rem;
}
.preset-item {
display: flex;
justify-content: space-between;
gap: 0.5rem;
padding: 0.35rem;
}
.preset-actions {
display: flex;
align-items: center;
gap: 0.25rem;
}
.preset-modal-actions {
display: flex;
justify-content: flex-end;
gap: 0.35rem;
}
.import-pane {
margin-top: 0.5rem;
}
.import-pane textarea {
resize: vertical;
}
.actions-row {
display: flex;
justify-content: flex-end;
gap: 0.4rem;
}
.room-desktop {
align-items: stretch;
}
.room-grid {
width: min(78rem, 100%);
display: grid;
gap: 0.75rem;
grid-template-columns: 2fr 1fr;
}
.room-main-window,
.side-panel-window {
min-height: 40rem;
}
.room-meta {
display: flex;
justify-content: space-between;
margin-bottom: 0.55rem;
}
.voting-board {
min-height: 14rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.6rem;
align-content: flex-start;
}
.vote-card {
width: 4.3rem;
height: 6rem;
border-radius: 0.4rem;
cursor: pointer;
transition: transform 120ms ease;
}
.vote-card:hover {
transform: translateY(-0.2rem);
}
.vote-summary-window {
margin-top: 0.7rem;
}
.vote-summary-content {
display: flex;
flex-direction: column;
gap: 0.45rem;
}
.summary-metrics {
display: flex;
justify-content: space-between;
}
.side-panel-content {
display: flex;
flex-direction: column;
gap: 0.55rem;
height: 100%;
}
.participants-scroll {
flex: 1;
min-height: 13rem;
overflow-y: auto;
}
.participant-item {
display: flex;
justify-content: space-between;
padding: 0.35rem;
}
.side-controls {
margin-top: auto;
display: flex;
flex-direction: column;
gap: 0.45rem;
}
.links-block {
display: grid;
gap: 0.2rem;
}
.admin-controls {
display: flex;
justify-content: flex-end;
}
.join-window {
position: fixed;
z-index: 60;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: min(27rem, 92vw);
}
.skeleton-line,
.skeleton-board,
.skeleton-table,
.skeleton-list,
.skeleton-controls {
height: 1.2rem;
}
.skeleton-board {
height: 15rem;
}
.skeleton-table {
height: 8rem;
}
.skeleton-list {
height: 20rem;
margin-bottom: 0.45rem;
}
.skeleton-controls {
height: 9rem;
}
@media (min-width: 900px) {
.mobile-control-strip {
display: none;
}
.desktop-taskbar {
display: flex;
align-items: center;
justify-content: flex-start;
min-height: 2.5rem;
padding: 0.3rem 0.55rem;
}
#desktop {
padding: 1rem 1rem 3.4rem;
}
}
@media (max-width: 960px) {
.config-layout,
.room-grid {
grid-template-columns: 1fr;
}
.room-main-window,
.side-panel-window {
min-height: unset;
}
}
@media (max-width: 720px) {
#desktop {
align-items: flex-start;
padding-top: 3.65rem;
}
.field-row {
grid-template-columns: 1fr;
}
.summary-metrics {
flex-direction: column;
gap: 0.25rem;
}
}
@media (min-width: 2560px), (min-resolution: 2dppx) {
:root {
--ui-scale: 1.24;
}
}

181
static/css/main.css Normal file
View File

@@ -0,0 +1,181 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: calc(var(--base-font-size) * var(--ui-scale));
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
font-family: var(--font-main);
color: var(--text-primary);
}
.mobile-control-strip,
.taskbar {
background: var(--surface-window);
border-top: var(--window-border-width) solid var(--border-outer);
box-shadow: var(--window-shadow);
}
.taskbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 45;
}
input,
select,
textarea,
button {
font: inherit;
}
.hidden {
display: none !important;
}
.window {
background: var(--surface-window);
border: var(--window-border-width) solid var(--border-outer);
box-shadow: var(--window-shadow);
}
.title-bar {
background: var(--title-bg);
color: var(--title-text);
padding: 0.25rem 0.45rem;
font-size: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.title-bar-controls {
display: inline-flex;
gap: 0.12rem;
}
.title-bar-controls button {
width: 1rem;
height: 1rem;
font-size: 0.72rem;
line-height: 0.6rem;
border: var(--control-border-width) solid var(--border-outer);
background: var(--surface-control);
color: var(--text-primary);
}
.window-content {
padding: 0.75rem;
}
.btn {
border: var(--control-border-width) solid var(--border-outer);
background: var(--surface-control);
color: var(--text-primary);
box-shadow: var(--button-shadow);
padding: 0.3rem 0.65rem;
cursor: pointer;
}
.btn:active {
box-shadow: var(--button-shadow-active);
}
.btn-primary {
font-weight: 700;
}
input[type="text"],
input[type="number"],
input[type="password"],
select,
textarea {
background: var(--surface-input);
color: var(--text-primary);
border: var(--input-border-width) solid var(--border-input);
padding: 0.35rem 0.45rem;
width: 100%;
}
input:focus,
select:focus,
textarea:focus {
outline: none;
box-shadow: var(--focus-ring);
}
input[type="number"] {
appearance: textfield;
-moz-appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.status-line {
background: var(--surface-status);
border: var(--input-border-width) solid var(--border-input);
padding: 0.32rem 0.5rem;
min-height: 1.8rem;
}
.participant-list {
list-style: none;
background: var(--surface-input);
border: var(--input-border-width) solid var(--border-input);
}
.participant-item {
border-bottom: 1px dashed var(--border-muted);
}
.participant-item:last-child {
border-bottom: none;
}
.summary-table {
width: 100%;
border-collapse: collapse;
background: var(--surface-input);
}
.summary-table th,
.summary-table td {
border: 1px solid var(--border-muted);
padding: 0.35rem 0.45rem;
text-align: left;
}
.vote-card,
.preview-card {
border: var(--card-border-width) solid var(--card-border);
background: var(--card-bg);
color: var(--card-text);
}
.vote-card.is-selected {
outline: var(--selected-outline);
outline-offset: -0.35rem;
}
.vote-card.impact {
animation: vote-impact 170ms ease;
}
@keyframes vote-impact {
0% { transform: scale(1); }
40% { transform: scale(0.91); }
100% { transform: scale(1); }
}

View File

@@ -1,721 +0,0 @@
:root {
--desktop-bg: #008080;
--window-bg: #c0c0c0;
--window-text: #000000;
--border-light: #ffffff;
--border-dark: #000000;
--border-mid-light: #dfdfdf;
--border-mid-dark: #808080;
--title-bg: #000080;
--title-text: #ffffff;
--input-bg: #ffffff;
--status-bg: #b3b3b3;
--board-bg: #0f6d3d;
--card-bg: #ffffff;
--card-text: #000000;
}
[data-theme="dark"] {
--desktop-bg: #0a0a0a;
--window-bg: #2b2b2b;
--window-text: #e0e0e0;
--border-light: #555555;
--border-dark: #000000;
--border-mid-light: #3a3a3a;
--border-mid-dark: #1a1a1a;
--title-bg: #000000;
--title-text: #00ff00;
--input-bg: #111111;
--status-bg: #1b1b1b;
--board-bg: #0b2f16;
--card-bg: #171717;
--card-text: #00ff66;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'VT323', monospace;
}
body {
background-color: var(--desktop-bg);
color: var(--window-text);
min-height: 100vh;
display: flex;
flex-direction: column;
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.12) 1px, transparent 1px);
background-size: 4px 4px;
}
#desktop {
flex: 1;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 28px 16px 40px;
}
.top-bar {
position: fixed;
top: 12px;
right: 12px;
z-index: 10;
}
.window {
background-color: var(--window-bg);
border: 2px solid;
border-color: var(--border-light) var(--border-dark) var(--border-dark) var(--border-light);
padding: 2px;
box-shadow: inset 1px 1px var(--border-mid-light), inset -1px -1px var(--border-mid-dark);
}
.title-bar {
background-color: var(--title-bg);
color: var(--title-text);
padding: 2px 4px;
font-size: 1.25rem;
display: flex;
justify-content: space-between;
align-items: center;
letter-spacing: 0.8px;
}
.title-bar-controls {
display: inline-flex;
gap: 2px;
}
.title-bar-controls button {
background: var(--window-bg);
border: 1px solid;
border-color: var(--border-light) var(--border-dark) var(--border-dark) var(--border-light);
width: 16px;
height: 16px;
font-size: 0.8rem;
line-height: 10px;
text-align: center;
color: var(--window-text);
font-weight: bold;
pointer-events: none;
}
.window-content {
padding: 12px;
}
.room-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.field-group {
display: flex;
flex-direction: column;
}
.field-group label,
legend {
font-size: 1.2rem;
margin-bottom: 4px;
}
input[type="text"],
input[type="number"],
input[type="password"],
select {
background: var(--input-bg);
color: var(--window-text);
border: 2px solid;
border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
padding: 5px 6px;
font-size: 1.2rem;
width: 100%;
outline: none;
}
input:focus,
select:focus {
box-shadow: inset 0 0 0 1px var(--title-bg);
}
input[type="number"] {
appearance: textfield;
-moz-appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.number-input-wrap {
display: flex;
align-items: center;
background: var(--input-bg);
border: 2px solid;
border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
padding: 0 6px;
min-height: 38px;
}
.number-input-wrap input[type="number"] {
border: 0;
box-shadow: none;
background: transparent;
padding: 5px 0;
}
.number-with-unit {
gap: 8px;
}
.input-unit {
font-size: 1rem;
opacity: 0.8;
min-width: 26px;
text-align: right;
}
.btn {
background: var(--window-bg);
color: var(--window-text);
border: 2px solid;
border-color: var(--border-light) var(--border-dark) var(--border-dark) var(--border-light);
box-shadow: inset 1px 1px var(--border-mid-light), inset -1px -1px var(--border-mid-dark);
padding: 4px 12px;
font-size: 1.2rem;
cursor: pointer;
margin-left: 6px;
}
.btn:active {
border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
box-shadow: inset 1px 1px var(--border-mid-dark), inset -1px -1px var(--border-mid-light);
padding: 5px 11px 3px 13px;
}
.btn-primary {
font-weight: bold;
}
.actions-row {
text-align: right;
margin-top: 4px;
}
.status-line {
background: var(--status-bg);
border: 2px solid;
border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
padding: 5px 8px;
font-size: 1.1rem;
min-height: 30px;
}
.hidden {
display: none !important;
}
/* Config page */
.config-window {
width: 100%;
max-width: 980px;
}
.intro-copy {
font-size: 1.3rem;
margin-bottom: 12px;
}
.config-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 340px;
gap: 12px;
align-items: start;
}
.config-panel {
display: flex;
flex-direction: column;
gap: 10px;
}
.field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.options-box {
padding: 8px;
}
.options-box legend {
padding: 0 4px;
}
.option-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 1.2rem;
margin: 6px 0;
}
.option-item input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: #000080;
}
[data-theme="dark"] .option-item input[type="checkbox"] {
accent-color: #00aa00;
}
.preview-window {
width: 100%;
}
.preview-content {
display: flex;
flex-direction: column;
gap: 10px;
}
.preview-meta {
display: flex;
justify-content: space-between;
font-size: 1.1rem;
}
.preview-board {
background: var(--board-bg);
border: 2px solid;
border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
border-radius: 2px;
min-height: 190px;
padding: 10px;
}
.preview-cards {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-content: flex-start;
}
.preview-card {
position: relative;
width: 50px;
height: 72px;
background: var(--card-bg);
border: 2px solid #000;
border-radius: 5px;
color: var(--card-text);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: bold;
box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5);
user-select: none;
transition: transform 180ms ease;
cursor: grab;
}
.preview-card.dragging {
opacity: 0.6;
}
.preview-card.wiggle {
animation: wiggle 250ms linear infinite;
}
@keyframes wiggle {
0% { transform: rotate(-2deg); }
50% { transform: rotate(2deg); }
100% { transform: rotate(-2deg); }
}
.preview-card-remove {
position: absolute;
top: -6px;
right: -6px;
width: 18px;
height: 18px;
border: 1px solid;
border-color: var(--border-light) var(--border-dark) var(--border-dark) var(--border-light);
background: #a40000;
color: #fff;
font-size: 0.8rem;
line-height: 14px;
opacity: 0;
transform: scale(0.85);
pointer-events: none;
transition: opacity 130ms ease, transform 130ms ease;
cursor: pointer;
}
.preview-card:hover .preview-card-remove,
.preview-card-remove:focus {
opacity: 1;
transform: scale(1);
pointer-events: auto;
}
.preview-card.is-removing {
animation: card-pop-out 190ms ease forwards;
}
@keyframes card-pop-out {
from {
opacity: 1;
transform: scale(1) rotate(0deg);
}
to {
opacity: 0;
transform: scale(0.58) rotate(-10deg);
}
}
.hint-text {
font-size: 1rem;
}
.card-editor {
display: flex;
flex-direction: column;
gap: 4px;
}
.card-editor label {
font-size: 1.1rem;
}
.card-editor-row {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 6px;
}
.deck-tools-row {
display: flex;
gap: 6px;
}
.icon-btn {
min-width: 36px;
padding-left: 8px;
padding-right: 8px;
}
.preset-picker {
margin-top: 4px;
}
.preset-list {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 150px;
overflow-y: auto;
margin-bottom: 8px;
}
.preset-item {
display: flex;
justify-content: space-between;
gap: 8px;
padding: 6px;
border: 1px dashed var(--border-mid-dark);
}
.preset-meta {
font-size: 1rem;
opacity: 0.8;
}
.preset-actions {
display: flex;
align-items: center;
gap: 4px;
}
.preset-actions .btn {
font-size: 1rem;
padding: 2px 8px;
margin-left: 0;
}
.import-btn {
margin-left: 0;
}
.import-pane {
margin-top: 8px;
}
.import-pane textarea {
width: 100%;
resize: vertical;
background: var(--input-bg);
color: var(--window-text);
border: 2px solid;
border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
padding: 6px;
font-size: 1rem;
}
/* Room page */
.room-desktop {
align-items: stretch;
justify-content: center;
padding-top: 60px;
}
.room-grid {
width: min(1180px, 100%);
display: grid;
gap: 12px;
grid-template-columns: 2fr 1fr;
align-items: stretch;
}
.room-main-window,
.side-panel-window {
min-height: 640px;
}
.room-meta {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 1.15rem;
}
.voting-board {
background: var(--board-bg);
border: 2px solid;
border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
min-height: 230px;
padding: 12px;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-content: flex-start;
}
.vote-card {
width: 72px;
height: 100px;
border-radius: 6px;
border: 2px solid #000;
background: var(--card-bg);
color: var(--card-text);
font-size: 2rem;
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.45);
cursor: pointer;
transition: transform 120ms ease;
}
.vote-card:hover {
transform: translateY(-3px);
}
.vote-card.is-selected {
outline: 2px dotted currentColor;
outline-offset: -6px;
}
.vote-card.impact {
animation: vote-impact 170ms ease;
}
@keyframes vote-impact {
0% { transform: scale(1); }
40% { transform: scale(0.91); }
100% { transform: scale(1); }
}
.vote-summary-window {
margin-top: 12px;
}
.vote-summary-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.summary-table {
width: 100%;
border-collapse: collapse;
background: var(--input-bg);
}
.summary-table th,
.summary-table td {
border: 1px solid var(--border-mid-dark);
padding: 6px 8px;
text-align: left;
}
.summary-metrics {
display: flex;
justify-content: space-between;
font-size: 1.1rem;
}
.side-panel-content {
display: flex;
flex-direction: column;
gap: 10px;
height: calc(100% - 4px);
}
.participants-scroll {
flex: 1;
min-height: 250px;
overflow-y: auto;
}
.participant-list {
list-style: none;
background: var(--input-bg);
border: 2px solid;
border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
padding: 5px;
}
.participant-item {
display: flex;
justify-content: space-between;
padding: 5px;
border-bottom: 1px dashed var(--border-mid-dark);
font-size: 1.15rem;
}
.participant-item:last-child {
border-bottom: 0;
}
.side-controls {
margin-top: auto;
display: flex;
flex-direction: column;
gap: 8px;
}
.links-block {
display: grid;
gap: 4px;
}
.links-block input {
font-size: 1rem;
}
.admin-controls {
display: flex;
justify-content: flex-end;
}
.join-window {
position: fixed;
z-index: 30;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: min(420px, 92vw);
}
#join-error {
margin-top: 8px;
}
.skeleton-line,
.skeleton-board,
.skeleton-table,
.skeleton-list,
.skeleton-controls {
background: linear-gradient(90deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.08));
background-size: 220% 100%;
animation: skeleton-shimmer 1.2s ease infinite;
border: 1px solid var(--border-mid-dark);
}
.skeleton-line {
height: 18px;
margin-bottom: 10px;
}
.skeleton-line.short {
width: 40%;
margin-top: 12px;
}
.skeleton-board {
height: 250px;
}
.skeleton-table {
height: 140px;
}
.skeleton-list {
height: 350px;
margin-bottom: 10px;
}
.skeleton-controls {
height: 180px;
}
@keyframes skeleton-shimmer {
from { background-position: 0 0; }
to { background-position: -200% 0; }
}
@media (max-width: 960px) {
.config-layout,
.room-grid {
grid-template-columns: 1fr;
}
.room-main-window,
.side-panel-window {
min-height: unset;
}
}
@media (max-width: 720px) {
#desktop {
align-items: flex-start;
padding-top: 56px;
}
.field-row {
grid-template-columns: 1fr;
}
.actions-row {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.summary-metrics {
flex-direction: column;
gap: 4px;
}
.btn {
margin-left: 0;
}
}

View File

@@ -0,0 +1,79 @@
:root[data-ui-theme="modern"] {
--font-main: 'Segoe UI', 'Inter', Arial, sans-serif;
--desktop-bg: #e8edf4;
--desktop-pattern: linear-gradient(135deg, #eef3f8 0%, #dde7f2 100%);
--surface-window: #ffffff;
--surface-control: #f1f4f8;
--surface-input: #ffffff;
--surface-status: #f8fafc;
--text-primary: #101828;
--title-bg: #175cd3;
--title-text: #ffffff;
--border-outer: #c4ced9;
--border-input: #b8c3d2;
--border-muted: #d5dde8;
--window-border-width: 1px;
--control-border-width: 1px;
--input-border-width: 1px;
--window-shadow: 0 12px 30px rgba(16, 24, 40, 0.14);
--button-shadow: none;
--button-shadow-active: none;
--focus-ring: 0 0 0 2px rgba(23, 92, 211, 0.22);
--card-bg: #ffffff;
--card-text: #101828;
--card-border: #b8c3d2;
--card-border-width: 1px;
--selected-outline: 2px solid #175cd3;
--modal-overlay: rgba(15, 23, 42, 0.35);
}
:root[data-ui-theme="modern"][data-theme="dark"] {
--desktop-bg: #0b1220;
--desktop-pattern: linear-gradient(140deg, #111b2c 0%, #09101d 100%);
--surface-window: #111827;
--surface-control: #1f2937;
--surface-input: #0f172a;
--surface-status: #172033;
--text-primary: #e5edf7;
--title-bg: #1d4ed8;
--title-text: #ffffff;
--border-outer: #334155;
--border-input: #3f5169;
--border-muted: #334155;
--window-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
--focus-ring: 0 0 0 2px rgba(96, 165, 250, 0.34);
--card-bg: #1e293b;
--card-text: #e5edf7;
--card-border: #334155;
}
:root[data-ui-theme="modern"] body {
background-color: var(--desktop-bg);
background-image: var(--desktop-pattern);
background-size: cover;
}
:root[data-ui-theme="modern"] .window,
:root[data-ui-theme="modern"] .vote-card,
:root[data-ui-theme="modern"] .preview-card,
:root[data-ui-theme="modern"] .btn,
:root[data-ui-theme="modern"] input,
:root[data-ui-theme="modern"] select,
:root[data-ui-theme="modern"] textarea {
border-radius: 0.45rem;
}
:root[data-ui-theme="modern"] .preview-board,
:root[data-ui-theme="modern"] .voting-board {
background: linear-gradient(180deg, #0f6f54 0%, #0a5c45 100%);
border: 1px solid var(--border-input);
}
:root[data-ui-theme="modern"][data-theme="dark"] .preview-board,
:root[data-ui-theme="modern"][data-theme="dark"] .voting-board {
background: linear-gradient(180deg, #0b3e33 0%, #082d25 100%);
}
:root[data-ui-theme="modern"] .preset-modal-overlay {
background: var(--modal-overlay);
}

View File

@@ -0,0 +1,66 @@
:root[data-ui-theme="none"] {
--font-main: ui-monospace, monospace;
--desktop-bg: #ffffff;
--desktop-pattern: none;
--surface-window: transparent;
--surface-control: #f5f5f5;
--surface-input: #ffffff;
--surface-status: #ffffff;
--text-primary: #000000;
--title-bg: transparent;
--title-text: #000000;
--border-outer: #bdbdbd;
--border-input: #bdbdbd;
--border-muted: #d3d3d3;
--window-border-width: 1px;
--control-border-width: 1px;
--input-border-width: 1px;
--window-shadow: none;
--button-shadow: none;
--button-shadow-active: none;
--focus-ring: 0 0 0 1px #777777;
--card-bg: #ffffff;
--card-text: #000000;
--card-border: #bdbdbd;
--card-border-width: 1px;
--selected-outline: 1px dashed #000000;
--modal-overlay: rgba(0, 0, 0, 0.2);
}
:root[data-ui-theme="none"][data-theme="dark"] {
--desktop-bg: #111111;
--surface-window: transparent;
--surface-control: #232323;
--surface-input: #161616;
--surface-status: #161616;
--text-primary: #e8e8e8;
--title-bg: transparent;
--title-text: #e8e8e8;
--border-outer: #4a4a4a;
--border-input: #4a4a4a;
--border-muted: #3a3a3a;
--focus-ring: 0 0 0 1px #9a9a9a;
--card-bg: #161616;
--card-text: #e8e8e8;
--card-border: #4a4a4a;
}
:root[data-ui-theme="none"] body {
background-color: var(--desktop-bg);
background-image: none;
}
:root[data-ui-theme="none"] .title-bar-controls,
:root[data-ui-theme="none"] .icon-btn img {
display: none;
}
:root[data-ui-theme="none"] .window,
:root[data-ui-theme="none"] .preview-board,
:root[data-ui-theme="none"] .voting-board {
border-style: dashed;
}
:root[data-ui-theme="none"] .preset-modal-overlay {
background: var(--modal-overlay);
}

View File

@@ -0,0 +1,88 @@
:root:not([data-ui-theme]),
:root[data-ui-theme="win98"] {
--font-main: 'VT323', monospace;
--desktop-bg: #008080;
--desktop-pattern: radial-gradient(circle, rgba(0, 0, 0, 0.12) 1px, transparent 1px);
--surface-window: #c0c0c0;
--surface-control: #c0c0c0;
--surface-input: #ffffff;
--surface-status: #b3b3b3;
--text-primary: #000000;
--title-bg: #000080;
--title-text: #ffffff;
--border-outer: #000000;
--border-input: #000000;
--border-muted: #808080;
--window-border-width: 2px;
--control-border-width: 1px;
--input-border-width: 2px;
--window-shadow: inset 1px 1px #dfdfdf, inset -1px -1px #808080;
--button-shadow: inset 1px 1px #dfdfdf, inset -1px -1px #808080;
--button-shadow-active: inset 1px 1px #808080, inset -1px -1px #dfdfdf;
--focus-ring: inset 0 0 0 1px #000080;
--card-bg: #ffffff;
--card-text: #000000;
--card-border: #000000;
--card-border-width: 2px;
--selected-outline: 2px dotted currentColor;
--modal-overlay: rgba(0, 0, 0, 0.45);
}
:root[data-ui-theme="win98"][data-theme="dark"] {
--desktop-bg: #0a0a0a;
--desktop-pattern: radial-gradient(circle, rgba(0, 255, 80, 0.08) 1px, transparent 1px);
--surface-window: #2b2b2b;
--surface-control: #2b2b2b;
--surface-input: #111111;
--surface-status: #1b1b1b;
--text-primary: #e0e0e0;
--title-bg: #000000;
--title-text: #00ff66;
--border-outer: #000000;
--border-input: #555555;
--border-muted: #3b3b3b;
--window-shadow: inset 1px 1px #3a3a3a, inset -1px -1px #1a1a1a;
--button-shadow: inset 1px 1px #3a3a3a, inset -1px -1px #1a1a1a;
--button-shadow-active: inset 1px 1px #1a1a1a, inset -1px -1px #3a3a3a;
--focus-ring: inset 0 0 0 1px #00ff66;
--card-bg: #171717;
--card-text: #00ff66;
--card-border: #555555;
}
body {
background-color: var(--desktop-bg);
background-image: var(--desktop-pattern);
background-size: 4px 4px;
}
.preview-board,
.voting-board {
background: #0f6d3d;
border: 2px solid #000;
}
:root[data-ui-theme="win98"][data-theme="dark"] .preview-board,
:root[data-ui-theme="win98"][data-theme="dark"] .voting-board {
background: #0a2c14;
}
.preset-modal-overlay {
background: var(--modal-overlay);
}
.skeleton-line,
.skeleton-board,
.skeleton-table,
.skeleton-list,
.skeleton-controls {
background: linear-gradient(90deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.08));
background-size: 220% 100%;
border: 1px solid var(--border-muted);
animation: skeleton-shimmer 1.2s ease infinite;
}
@keyframes skeleton-shimmer {
from { background-position: 0 0; }
to { background-position: -200% 0; }
}

View File

@@ -13,7 +13,6 @@ const SPECIAL_CARD_ORDER = {
'☕': 3,
};
const themeToggleBtn = document.getElementById('theme-toggle');
const roomConfigForm = document.getElementById('room-config-form');
const statusLine = document.getElementById('config-status');
const scaleSelect = document.getElementById('estimation-scale');
@@ -29,14 +28,14 @@ const usernameInput = document.getElementById('username');
const savePresetButton = document.getElementById('save-preset');
const pickerToggleButton = document.getElementById('preset-picker-toggle');
const shareDeckButton = document.getElementById('share-deck');
const presetPicker = document.getElementById('preset-picker');
const presetModalOverlay = document.getElementById('preset-modal-overlay');
const presetModalCloseButton = document.getElementById('preset-modal-close');
const presetModalDoneButton = document.getElementById('preset-modal-done');
const presetList = document.getElementById('preset-list');
const importToggleButton = document.getElementById('import-toggle');
const importPane = document.getElementById('import-pane');
const importInput = document.getElementById('import-b64');
const importApplyButton = document.getElementById('import-apply');
let isDarkMode = false;
let nextCardID = 1;
let currentCards = [];
let draggingCardID = '';
@@ -48,17 +47,6 @@ if (savedUsername && !usernameInput.value) {
usernameInput.value = savedUsername;
}
themeToggleBtn.addEventListener('click', () => {
isDarkMode = !isDarkMode;
if (isDarkMode) {
document.documentElement.setAttribute('data-theme', 'dark');
themeToggleBtn.textContent = 'Light Mode';
return;
}
document.documentElement.removeAttribute('data-theme');
themeToggleBtn.textContent = 'Dark Mode';
});
function parseNumericCard(value) {
if (!/^-?\d+(\.\d+)?$/.test(value)) {
@@ -341,13 +329,13 @@ function renderPresetList() {
}
function showPresetPicker() {
presetPicker.classList.remove('hidden');
presetModalOverlay.classList.remove('hidden');
pickerToggleButton.setAttribute('aria-expanded', 'true');
renderPresetList();
}
function hidePresetPicker() {
presetPicker.classList.add('hidden');
presetModalOverlay.classList.add('hidden');
pickerToggleButton.setAttribute('aria-expanded', 'false');
}
@@ -414,7 +402,7 @@ savePresetButton.addEventListener('click', () => {
});
pickerToggleButton.addEventListener('click', () => {
if (presetPicker.classList.contains('hidden')) {
if (presetModalOverlay.classList.contains('hidden')) {
showPresetPicker();
return;
}
@@ -464,20 +452,15 @@ importApplyButton.addEventListener('click', () => {
}
});
document.addEventListener('click', (event) => {
const target = event.target;
if (!(target instanceof Element)) {
return;
presetModalOverlay.addEventListener('click', (event) => {
if (event.target === presetModalOverlay) {
hidePresetPicker();
}
if (presetPicker.classList.contains('hidden')) {
return;
}
if (presetPicker.contains(target) || pickerToggleButton.contains(target)) {
return;
}
hidePresetPicker();
});
presetModalCloseButton.addEventListener('click', hidePresetPicker);
presetModalDoneButton.addEventListener('click', hidePresetPicker);
customCardInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();

View File

@@ -3,7 +3,6 @@ const USERNAME_KEY = 'scrumPoker.username';
const roomID = document.body.dataset.roomId;
const params = new URLSearchParams(window.location.search);
const themeToggleBtn = document.getElementById('theme-toggle');
const roomSkeleton = document.getElementById('room-skeleton');
const roomGrid = document.getElementById('room-grid');
const roomTitle = document.getElementById('room-title');
@@ -28,8 +27,6 @@ const joinRoleInput = document.getElementById('join-role');
const joinPasswordInput = document.getElementById('join-password');
const joinAdminTokenInput = document.getElementById('join-admin-token');
const joinError = document.getElementById('join-error');
let isDarkMode = false;
let participantID = params.get('participantId') || '';
let adminToken = params.get('adminToken') || '';
let eventSource = null;
@@ -38,17 +35,6 @@ const savedUsername = localStorage.getItem(USERNAME_KEY) || '';
joinUsernameInput.value = savedUsername;
joinAdminTokenInput.value = adminToken;
themeToggleBtn.addEventListener('click', () => {
isDarkMode = !isDarkMode;
if (isDarkMode) {
document.documentElement.setAttribute('data-theme', 'dark');
themeToggleBtn.textContent = 'Light Mode';
return;
}
document.documentElement.removeAttribute('data-theme');
themeToggleBtn.textContent = 'Dark Mode';
});
function setJoinError(message) {
if (!message) {

75
static/js/ui-controls.js Normal file
View File

@@ -0,0 +1,75 @@
(() => {
const THEME_KEY = 'scrumPoker.ui.theme';
const MODE_KEY = 'scrumPoker.ui.mode';
const DEFAULT_THEME = 'win98';
function applyTheme(theme) {
const normalized = theme || DEFAULT_THEME;
document.documentElement.setAttribute('data-ui-theme', normalized);
}
function applyMode(mode) {
if (mode === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
return;
}
document.documentElement.removeAttribute('data-theme');
}
function getCurrentMode() {
return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
}
function syncControls() {
const theme = document.documentElement.getAttribute('data-ui-theme') || DEFAULT_THEME;
const mode = getCurrentMode();
document.querySelectorAll('[data-role="theme-picker"]').forEach((el) => {
el.value = theme;
});
document.querySelectorAll('[data-role="mode-toggle"]').forEach((el) => {
el.textContent = mode === 'dark' ? 'Light Mode' : 'Dark Mode';
});
}
function initUIControls() {
if (window.__uiControlsInitialized) {
syncControls();
return;
}
window.__uiControlsInitialized = true;
const savedTheme = localStorage.getItem(THEME_KEY) || DEFAULT_THEME;
const savedMode = localStorage.getItem(MODE_KEY) || 'light';
applyTheme(savedTheme);
applyMode(savedMode);
syncControls();
document.querySelectorAll('[data-role="theme-picker"]').forEach((picker) => {
picker.addEventListener('change', (event) => {
const value = event.target.value || DEFAULT_THEME;
localStorage.setItem(THEME_KEY, value);
applyTheme(value);
syncControls();
});
});
document.querySelectorAll('[data-role="mode-toggle"]').forEach((button) => {
button.addEventListener('click', () => {
const next = getCurrentMode() === 'dark' ? 'light' : 'dark';
localStorage.setItem(MODE_KEY, next);
applyMode(next);
syncControls();
});
});
}
window.initUIControls = initUIControls;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initUIControls, { once: true });
} else {
initUIControls();
}
})();