fuzzyworld

word-waltz / files / src / styles / main.css

main.css

17.6 KB · history · blame

1/* ============================================================
2 Word Waltz — global styles
3 ============================================================ */
4
5/* --- Custom properties ------------------------------------ */
6:root {
7 --color-bg: #f9f6f0;
8 --color-surface: #ffffff;
9 --color-border: #d3cfc8;
10 --color-text: #1a1a1a;
11 --color-muted: #6b6b6b;
12
13 --color-correct: #6aaa64;
14 --color-present: #e5a428;
15 --color-absent: #787c7e;
16 --color-tile-bg: #ffffff;
17 --color-tile-text: #1a1a1a;
18
19 --color-key-bg: #d3d6da;
20 --color-key-text: #1a1a1a;
21
22 --color-accent: #538d4e;
23 --color-error: #c0392b;
24 --color-player-a: #c0392b;
25 --color-player-b: #2471a3;
26
27 /* tile size: the min of a width-based cap and a height-based cap.
28 Height formula: (100dvh minus everything except tiles) / 6.
29 ~360px = header(60) + app-pads(22) + scoreboard(67) + app-gap(10)
30 + board-message(22) + board-gaps(16) + keyboard(approx 130).
31 A small safety margin is already included. */
32 --tile-size: min(clamp(30px, 11vw, 62px), max(20px, calc((100dvh - 360px) / 6)));
33 --tile-gap: 6px;
34 --radius: 4px;
35 --font-mono: 'Courier New', Courier, monospace;
36}
37
38/* --- Reset & base ----------------------------------------- */
39*,
40*::before,
41*::after {
42 box-sizing: border-box;
43 margin: 0;
44 padding: 0;
45}
46
47body {
48 font-family: system-ui, -apple-system, sans-serif;
49 background: var(--color-bg);
50 color: var(--color-text);
51 height: 100dvh;
52 overflow: hidden;
53 display: flex;
54 flex-direction: column;
55 touch-action: manipulation;
56}
57
58/* --- Header ----------------------------------------------- */
59header {
60 display: flex;
61 align-items: center;
62 padding: 8px 16px;
63 border-bottom: 1px solid var(--color-border);
64 background: var(--color-surface);
65 gap: 12px;
66}
67
68#home-link {
69 display: flex;
70 align-items: center;
71 gap: 10px;
72 text-decoration: none;
73 color: inherit;
74 flex: 1;
75}
76
77#home-link:hover .header__title {
78 text-decoration: underline;
79}
80
81.header__logo {
82 height: 36px;
83 width: 36px;
84 object-fit: contain;
85 border-radius: 6px;
86 flex-shrink: 0;
87}
88
89.header__title {
90 font-size: 1.2rem;
91 font-weight: 700;
92 letter-spacing: 0.05em;
93 text-transform: uppercase;
94}
95
96.header__links {
97 display: flex;
98 align-items: center;
99 gap: 12px;
100 flex-shrink: 0;
101}
102
103.header__links a {
104 font-size: 0.78rem;
105 color: var(--color-muted);
106 text-decoration: none;
107 white-space: nowrap;
108}
109
110.header__links a:hover {
111 color: var(--color-accent);
112 text-decoration: underline;
113}
114
115.header__notif-btn {
116 background: none;
117 border: none;
118 cursor: pointer;
119 padding: 2px 4px;
120 border-radius: 4px;
121 line-height: 1;
122 opacity: 0.75;
123 transition: opacity 0.15s;
124 display: flex;
125 align-items: center;
126}
127
128.header__notif-btn img {
129 height: 22px;
130 width: 22px;
131 display: block;
132}
133
134.header__notif-btn:hover {
135 opacity: 1;
136}
137
138.header__notif-btn--on {
139 opacity: 1;
140}
141
142.header__notif-btn--blocked {
143 opacity: 0.75;
144 cursor: default;
145}
146
147/* --- App container ---------------------------------------- */
148#app {
149 flex: 1;
150 min-height: 0;
151 display: flex;
152 flex-direction: column;
153 align-items: center;
154 padding: 10px 12px 12px;
155 gap: 10px;
156 max-width: 520px;
157 width: 100%;
158 margin-inline: auto;
159 overflow: hidden;
160}
161
162/* --- Shared utilities ------------------------------------- */
163.status {
164 color: var(--color-muted);
165 text-align: center;
166 font-size: 1rem;
167 padding: 12px 0;
168}
169
170.status--waiting {
171 display: flex;
172 align-items: center;
173 justify-content: center;
174 gap: 8px;
175}
176
177.status__live-dot {
178 display: inline-block;
179 width: 8px;
180 height: 8px;
181 border-radius: 50%;
182 background: var(--color-correct);
183 flex-shrink: 0;
184 animation: live-pulse 1.8s ease-in-out infinite;
185}
186
187@keyframes live-pulse {
188
189 0%,
190 100% {
191 opacity: 1;
192 transform: scale(1);
193 }
194
195 50% {
196 opacity: 0.3;
197 transform: scale(0.6);
198 }
199}
200
201.error {
202 color: var(--color-error);
203 text-align: center;
204 font-size: 1rem;
205 padding: 16px;
206}
207
208/* --- Scoreboard ------------------------------------------- */
209.scoreboard {
210 width: 100%;
211 flex-shrink: 0;
212 background: var(--color-surface);
213 border: 1px solid var(--color-border);
214 border-radius: var(--radius);
215 padding: 8px 12px;
216}
217
218.scoreboard__players {
219 display: flex;
220 align-items: center;
221 justify-content: space-between;
222 gap: 8px;
223}
224
225.scoreboard__player {
226 display: flex;
227 flex-direction: column;
228 align-items: center;
229 gap: 2px;
230 flex: 1;
231 min-width: 0;
232}
233
234.scoreboard__vs {
235 font-size: 0.85rem;
236 color: var(--color-muted);
237 flex-shrink: 0;
238}
239
240.scoreboard__name {
241 font-size: 0.9rem;
242 font-weight: 600;
243 text-align: center;
244 white-space: nowrap;
245 overflow: hidden;
246 text-overflow: ellipsis;
247}
248
249.scoreboard__name--a {
250 color: var(--color-player-a);
251}
252
253.scoreboard__name--b {
254 color: var(--color-player-b);
255}
256
257.scoreboard__score {
258 font-size: 1.6rem;
259 font-weight: 700;
260 line-height: 1;
261}
262
263.scoreboard__score--a {
264 color: var(--color-player-a);
265}
266
267.scoreboard__score--b {
268 color: var(--color-player-b);
269}
270
271.scoreboard__history {
272 list-style: none;
273 padding-left: 0;
274 display: flex;
275 flex-direction: column;
276 gap: 8px;
277}
278
279.scoreboard__round-num {
280 color: var(--color-muted);
281 margin-right: 3px;
282}
283
284.scoreboard__history-wrap {
285 margin-top: 8px;
286 padding-top: 8px;
287 border-top: 1px solid var(--color-border);
288}
289
290.scoreboard__toggle {
291 background: none;
292 border: none;
293 padding: 0;
294 font-size: 0.75rem;
295 color: var(--color-muted);
296 cursor: pointer;
297 margin-bottom: 4px;
298}
299
300.scoreboard__toggle:hover {
301 color: var(--color-text);
302}
303
304.scoreboard__name--editable {
305 cursor: pointer;
306 text-decoration: underline;
307 text-decoration-style: dotted;
308 text-underline-offset: 2px;
309}
310
311.scoreboard__name-input {
312 font-size: 0.9rem;
313 font-weight: 600;
314 border: 1px solid var(--color-accent);
315 border-radius: 2px;
316 padding: 1px 4px;
317 width: 90px;
318 outline: none;
319}
320
321.scoreboard__round {
322 font-size: 0.8rem;
323 color: var(--color-muted);
324 display: flex;
325 flex-direction: column;
326 gap: 3px;
327}
328
329.scoreboard__round--live {
330 font-style: italic;
331}
332
333.scoreboard__emoji {
334 font-size: 0.7rem;
335 line-height: 1.4;
336 letter-spacing: 0.05em;
337 white-space: pre;
338}
339
340.header__share-btn {
341 background: none;
342 border: none;
343 cursor: pointer;
344 padding: 2px 4px;
345 border-radius: 4px;
346 line-height: 1;
347 opacity: 0.75;
348 transition: opacity 0.15s;
349 display: flex;
350 align-items: center;
351}
352
353.header__share-btn img {
354 height: 22px;
355 width: 22px;
356 display: block;
357}
358
359.header__share-btn:hover {
360 opacity: 1;
361}
362
363/* --- Share modal ------------------------------------------ */
364.share-modal {
365 position: fixed;
366 inset: 0;
367 background: rgba(0, 0, 0, 0.4);
368 display: flex;
369 align-items: flex-start;
370 justify-content: center;
371 padding-top: 80px;
372 z-index: 100;
373}
374
375.share-modal__box {
376 background: var(--color-surface);
377 border: 1px solid var(--color-border);
378 border-radius: var(--radius);
379 padding: 20px;
380 max-width: 420px;
381 width: 90%;
382 position: relative;
383 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
384}
385
386.share-modal__close {
387 position: absolute;
388 top: 8px;
389 right: 10px;
390 background: none;
391 border: none;
392 font-size: 1.4rem;
393 line-height: 1;
394 cursor: pointer;
395 color: var(--color-muted);
396 padding: 0 4px;
397}
398
399.share-modal__close:hover {
400 color: var(--color-text);
401}
402
403
404.wordle-board {
405 display: flex;
406 flex-direction: column;
407 align-items: center;
408 gap: 8px;
409 width: 100%;
410 overflow: hidden;
411}
412
413.wordle-board__grid {
414 display: flex;
415 flex-direction: column;
416 gap: var(--tile-gap);
417}
418
419.wordle-board__row {
420 display: flex;
421 gap: var(--tile-gap);
422}
423
424.wordle-board__tile {
425 width: var(--tile-size);
426 height: var(--tile-size);
427 border: 2px solid var(--color-border);
428 border-radius: var(--radius);
429 display: flex;
430 align-items: center;
431 justify-content: center;
432 font-size: calc(var(--tile-size) * 0.55);
433 font-weight: 700;
434 text-transform: uppercase;
435 font-family: var(--font-mono);
436 background: var(--color-tile-bg);
437 color: var(--color-tile-text);
438 user-select: none;
439 /* flip animation */
440 perspective: 250px;
441 transition: border-color 0.1s;
442}
443
444.wordle-board__tile--filled {
445 border-color: #999;
446}
447
448/* Flip keyframe: rotate around X, color applied at 50% */
449@keyframes tile-flip {
450 0% {
451 transform: rotateX(0deg);
452 }
453
454 49% {
455 transform: rotateX(-90deg);
456 }
457
458 50% {
459 transform: rotateX(-90deg);
460 }
461
462 100% {
463 transform: rotateX(0deg);
464 }
465}
466
467.wordle-board__tile--flipping {
468 animation: tile-flip 0.5s ease-in-out forwards;
469}
470
471.wordle-board__tile--correct {
472 background: var(--color-correct);
473 border-color: var(--color-correct);
474 color: #fff;
475}
476
477.wordle-board__tile--present {
478 background: var(--color-present);
479 border-color: var(--color-present);
480 color: #fff;
481}
482
483.wordle-board__tile--absent {
484 background: var(--color-absent);
485 border-color: var(--color-absent);
486 color: #fff;
487}
488
489.wordle-board__message {
490 font-size: 1rem;
491 font-weight: 600;
492 min-height: 1.5em;
493 text-align: center;
494}
495
496.wordle-board__give-up {
497 background: none;
498 border: none;
499 padding: 0;
500 font-size: 0.8rem;
501 color: var(--color-muted);
502 cursor: pointer;
503 text-decoration: underline;
504 text-underline-offset: 2px;
505}
506
507.wordle-board__give-up:hover {
508 color: var(--color-error);
509}
510
511/* --- Keyboard --------------------------------------------- */
512/* 10 keys + 9 gaps per row; #app has 12px padding on each side (24px total).
513 key-w = (100vw - 24px - 9 × 5px) / 10 = 10vw - 6.9px, capped at 43px.
514 Derived as CSS custom properties so wide keys stay proportional. */
515.keyboard {
516 display: flex;
517 flex-direction: column;
518 align-items: center;
519 gap: 8px;
520 width: 100%;
521 padding-bottom: env(safe-area-inset-bottom, 0px);
522 --key-w: clamp(26px, calc(10vw - 6.9px), 43px);
523 --key-w-wide: clamp(40px, calc(15vw - 10px), 65px);
524}
525
526.keyboard__row {
527 display: flex;
528 justify-content: center;
529 gap: 5px;
530}
531
532.keyboard__key {
533 width: var(--key-w);
534 height: clamp(38px, 6dvh, 58px);
535 flex: none;
536 padding: 0;
537 border: none;
538 border-radius: var(--radius);
539 background: var(--color-key-bg);
540 color: var(--color-key-text);
541 font-size: clamp(0.65rem, 2.2vw, 0.85rem);
542 font-weight: 700;
543 cursor: pointer;
544 text-transform: uppercase;
545 transition: background 0.15s, color 0.15s;
546 user-select: none;
547}
548
549.keyboard__key--wide {
550 width: var(--key-w-wide);
551 font-size: clamp(0.55rem, 1.8vw, 0.75rem);
552}
553
554.keyboard__key:active {
555 opacity: 0.8;
556}
557
558.keyboard__key--correct {
559 background: var(--color-correct);
560 color: #fff;
561}
562
563.keyboard__key--present {
564 background: var(--color-present);
565 color: #fff;
566}
567
568.keyboard__key--absent {
569 background: var(--color-absent);
570 color: #fff;
571}
572
573/* --- Word picker ------------------------------------------ */
574.word-picker {
575 display: flex;
576 flex-direction: column;
577 align-items: center;
578 gap: 12px;
579 width: 100%;
580}
581
582.word-picker__prompt {
583 color: var(--color-muted);
584 font-size: 0.95rem;
585 text-align: center;
586}
587
588.word-picker__form {
589 display: flex;
590 gap: 8px;
591 width: 100%;
592 justify-content: center;
593}
594
595.word-picker__input {
596 width: min(100%, 160px);
597 font-size: 1.4rem;
598 font-family: var(--font-mono);
599 font-weight: 700;
600 text-transform: uppercase;
601 text-align: center;
602 letter-spacing: 0.15em;
603 padding: 8px 12px;
604 border: 2px solid var(--color-border);
605 border-radius: var(--radius);
606 background: var(--color-surface);
607 color: var(--color-text);
608 outline: none;
609}
610
611.word-picker__input:focus {
612 border-color: var(--color-accent);
613}
614
615.word-picker__submit {
616 padding: 8px 18px;
617 background: var(--color-accent);
618 color: #fff;
619 border: none;
620 border-radius: var(--radius);
621 font-size: 1rem;
622 font-weight: 600;
623 cursor: pointer;
624}
625
626.word-picker__submit:disabled {
627 opacity: 0.6;
628 cursor: default;
629}
630
631.word-picker__error {
632 color: var(--color-error);
633 font-size: 0.875rem;
634 min-height: 1.2em;
635}
636
637/* --- New game / join screen ------------------------------- */
638.new-game {
639 display: flex;
640 flex-direction: column;
641 align-items: center;
642 gap: 16px;
643 width: 100%;
644 max-width: 360px;
645 background: var(--color-surface);
646 border: 1px solid var(--color-border);
647 border-radius: var(--radius);
648 padding: 24px;
649 text-align: center;
650}
651
652.new-game p {
653 color: var(--color-muted);
654 font-size: 0.95rem;
655}
656
657.new-game form {
658 display: flex;
659 flex-direction: column;
660 gap: 12px;
661 width: 100%;
662}
663
664.new-game label {
665 display: flex;
666 flex-direction: column;
667 gap: 4px;
668 font-size: 0.875rem;
669 font-weight: 600;
670 text-align: left;
671}
672
673.new-game input[type="text"] {
674 padding: 8px 12px;
675 font-size: 1rem;
676 border: 2px solid var(--color-border);
677 border-radius: var(--radius);
678 background: var(--color-surface);
679 color: var(--color-text);
680 outline: none;
681}
682
683.new-game input[type="text"]:focus {
684 border-color: var(--color-accent);
685}
686
687.new-game button[type="submit"] {
688 padding: 10px;
689 background: var(--color-accent);
690 color: #fff;
691 border: none;
692 border-radius: var(--radius);
693 font-size: 1rem;
694 font-weight: 600;
695 cursor: pointer;
696}
697
698.new-game button[type="submit"]:disabled {
699 opacity: 0.6;
700 cursor: default;
701}
702
703/* --- Share dialog ----------------------------------------- */
704.share-dialog {
705 display: flex;
706 flex-direction: column;
707 align-items: center;
708 gap: 12px;
709 width: 100%;
710}
711
712.share-dialog__qr {
713 border-radius: var(--radius);
714 border: 1px solid var(--color-border);
715}
716
717.share-dialog__link {
718 font-size: 0.7rem;
719 word-break: break-all;
720 color: var(--color-muted);
721 text-align: center;
722 max-width: 260px;
723}
724
725.share-dialog__copy {
726 padding: 8px 20px;
727 background: var(--color-surface);
728 color: var(--color-text);
729 border: 2px solid var(--color-border);
730 border-radius: var(--radius);
731 font-size: 0.9rem;
732 font-weight: 600;
733 cursor: pointer;
734}
735
736.share-dialog__copy:hover {
737 border-color: var(--color-accent);
738}
739
740/* --- Main view -------------------------------------------- */
741.main-view {
742 flex: 1;
743 min-height: 0;
744 display: flex;
745 flex-direction: column;
746 align-items: center;
747 justify-content: center;
748 gap: 8px;
749 width: 100%;
750 overflow: hidden;
751}
752
753/* Word picker sits flush below the scoreboard so the OS keyboard
754 can't obscure it — no gap to cover. */
755body:has(.word-picker) .main-view {
756 justify-content: flex-start;
757 padding-top: 12px;
758}
759
760/* Home screen is the only scrollable view. */
761body:has(.home-screen) #app {
762 overflow-y: auto;
763}
764
765/* --- Home screen ------------------------------------------ */
766.home-screen {
767 display: flex;
768 flex-direction: column;
769 gap: 20px;
770 width: 100%;
771 padding-bottom: 24px;
772}
773
774.game-list {
775 display: flex;
776 flex-direction: column;
777 gap: 8px;
778 width: 100%;
779}
780
781.game-list__header {
782 display: flex;
783 justify-content: space-between;
784 align-items: center;
785}
786
787.game-list__header h2 {
788 font-size: 0.78rem;
789 font-weight: 700;
790 text-transform: uppercase;
791 letter-spacing: 0.08em;
792 color: var(--color-muted);
793}
794
795.game-list__delete-all {
796 font-size: 0.8rem;
797 color: var(--color-muted);
798 background: none;
799 border: none;
800 cursor: pointer;
801 padding: 4px 0;
802}
803
804.game-list__delete-all:hover {
805 color: var(--color-error);
806}
807
808.game-list__items {
809 list-style: none;
810 display: flex;
811 flex-direction: column;
812 gap: 6px;
813}
814
815.game-card {
816 display: flex;
817 align-items: stretch;
818 background: var(--color-surface);
819 border: 1px solid var(--color-border);
820 border-radius: var(--radius);
821 overflow: hidden;
822}
823
824.game-card__body {
825 flex: 1;
826 padding: 12px 16px;
827 cursor: pointer;
828 display: flex;
829 flex-direction: column;
830 gap: 3px;
831 min-width: 0;
832 outline: none;
833}
834
835.game-card__body:hover,
836.game-card__body:focus-visible {
837 background: rgba(0, 0, 0, 0.03);
838}
839
840.game-card__opponent {
841 font-weight: 600;
842 font-size: 0.95rem;
843 white-space: nowrap;
844 overflow: hidden;
845 text-overflow: ellipsis;
846}
847
848.game-card__status {
849 font-size: 0.8rem;
850 color: var(--color-muted);
851}
852
853.game-card__status--my-turn {
854 color: var(--color-correct);
855 font-weight: 600;
856}
857
858.game-card__delete {
859 padding: 0 16px;
860 background: none;
861 border: none;
862 border-left: 1px solid var(--color-border);
863 cursor: pointer;
864 color: var(--color-muted);
865 font-size: 1.3rem;
866 line-height: 1;
867 flex-shrink: 0;
868}
869
870.game-card__delete:hover {
871 color: var(--color-error);
872 background: rgba(192, 57, 43, 0.05);
873}
874
875.site-footer a:hover {
876 text-decoration: underline;
877}
878
879/* --- Solo game card accent -------------------------------- */
880.game-card--solo {
881 border-left: 3px solid var(--color-present);
882}
883
884/* --- Solo button in new-game form ------------------------- */
885.new-game__or {
886 font-size: 0.8rem;
887 color: var(--color-muted);
888 margin: 0;
889}
890
891.new-game__solo-btn {
892 padding: 8px 20px;
893 background: none;
894 color: var(--color-present);
895 border: 2px solid var(--color-present);
896 border-radius: var(--radius);
897 font-size: 0.95rem;
898 font-weight: 600;
899 cursor: pointer;
900 width: 100%;
901}
902
903.new-game__solo-btn:hover {
904 background: var(--color-present);
905 color: #fff;
906}