diff --git a/src/app/app.ts b/src/app/app.ts index 957de88..6aba061 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { RouterOutlet } from '@angular/router'; @Component({ @@ -7,5 +7,15 @@ import { RouterOutlet } from '@angular/router'; templateUrl: './app.html', styleUrl: './app.css' }) -export class App { +export class App implements OnInit { + ngOnInit(): void { + this.initTheme(); + } + + private initTheme(): void { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme === 'dark') { + document.documentElement.setAttribute('data-theme', 'dark'); + } + } } diff --git a/src/app/pages/welcome/welcome.component.css b/src/app/pages/welcome/welcome.component.css index 9c86a94..edc7ffb 100644 --- a/src/app/pages/welcome/welcome.component.css +++ b/src/app/pages/welcome/welcome.component.css @@ -8,6 +8,180 @@ position: relative; } +.theme-toggle-container { + position: absolute; + top: 20px; + right: 20px; + z-index: 100; +} + +.switch { + display: inline-block; + position: relative; +} + +.switch__input { + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + width: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; +} + +.switch__label { + position: relative; + display: inline-block; + width: 120px; + height: 60px; + background-color: #2B2B2B; + border: 5px solid #5B5B5B; + border-radius: 9999px; + cursor: pointer; + transition: all 0.4s cubic-bezier(.46,.03,.52,.96); +} + +.switch__indicator { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) translateX(-72%); + display: block; + width: 40px; + height: 40px; + background-color: #7B7B7B; + border-radius: 9999px; + box-shadow: 10px 0px 0 0 rgba(0, 0, 0, 0.2) inset; + transition: all 0.4s cubic-bezier(.46,.03,.52,.96); +} + +.switch__indicator::before, +.switch__indicator::after { + position: absolute; + content: ''; + display: block; + background-color: #FFFFFF; + border-radius: 9999px; + transition: all 0.4s cubic-bezier(.46,.03,.52,.96); +} + +.switch__indicator::before { + top: 7px; + left: 7px; + width: 9px; + height: 9px; + opacity: 0.6; +} + +.switch__indicator::after { + bottom: 8px; + right: 6px; + width: 14px; + height: 14px; + opacity: 0.8; +} + +.switch__decoration { + position: absolute; + top: 65%; + left: 50%; + display: block; + width: 5px; + height: 5px; + background-color: #FFFFFF; + border-radius: 9999px; + animation: twinkle-stars 0.8s infinite -0.6s; + transition: all 0.4s cubic-bezier(.46,.03,.52,.96); +} + +.switch__decoration::before, +.switch__decoration::after { + position: absolute; + display: block; + content: ''; + width: 5px; + height: 5px; + background-color: #FFFFFF; + border-radius: 9999px; +} + +.switch__decoration::before { + top: -20px; + left: 10px; + opacity: 1; + animation: twinkle-stars 0.6s infinite; +} + +.switch__decoration::after { + top: -7px; + left: 30px; + animation: twinkle-stars 0.6s infinite -0.2s; +} + +@keyframes twinkle-stars { + 50% { opacity: 0.2; } +} + +.switch__input:checked + .switch__label { + background-color: #8FB5F5; + border-color: #347CF8; +} + +.switch__input:checked + .switch__label .switch__indicator { + background-color: #ECD21F; + box-shadow: none; + transform: translate(-50%, -50%) translateX(72%); +} + +.switch__input:checked + .switch__label .switch__indicator::before, +.switch__input:checked + .switch__label .switch__indicator::after { + display: none; +} + +.switch__input:checked + .switch__label .switch__decoration { + top: 50%; + transform: translate(0%, -50%); + animation: cloud 8s linear infinite; + width: 20px; + height: 20px; +} + +.switch__input:checked + .switch__label .switch__decoration::before { + width: 10px; + height: 10px; + top: auto; + bottom: 0; + left: -8px; + animation: none; +} + +.switch__input:checked + .switch__label .switch__decoration::after { + width: 15px; + height: 15px; + top: auto; + bottom: 0; + left: 16px; + animation: none; +} + +.switch__input:checked + .switch__label .switch__decoration, +.switch__input:checked + .switch__label .switch__decoration::before, +.switch__input:checked + .switch__label .switch__decoration::after { + border-radius: 9999px 9999px 0 0; +} + +.switch__input:checked + .switch__label .switch__decoration::after { + border-bottom-right-radius: 9999px; +} + +@keyframes cloud { + 0% { transform: translate(0%, -50%); } + 50% { transform: translate(-50%, -50%); } + 100% { transform: translate(0%, -50%); } +} + .clouds-container { display: flex; justify-content: center; @@ -100,7 +274,7 @@ top: -10px; left: 50%; transform: translateX(-50%); - width: 100px; + width: 75px; height: 15px; border: 2px solid rgba(255, 215, 0, 0.7); border-radius: 50%; @@ -196,6 +370,21 @@ p { } @media (max-width: 768px) { + .theme-toggle-container { + top: 10px; + right: 10px; + } + + .switch__label { + width: 100px; + height: 50px; + } + + .switch__indicator { + width: 33px; + height: 33px; + } + .welcome-shell { padding: var(--size-lg); } diff --git a/src/app/pages/welcome/welcome.component.html b/src/app/pages/welcome/welcome.component.html index d4f84c9..d5b67d8 100644 --- a/src/app/pages/welcome/welcome.component.html +++ b/src/app/pages/welcome/welcome.component.html @@ -1,4 +1,13 @@
+
+
+ + +
+
Player One diff --git a/src/app/pages/welcome/welcome.component.ts b/src/app/pages/welcome/welcome.component.ts index e13a0da..3f3c149 100644 --- a/src/app/pages/welcome/welcome.component.ts +++ b/src/app/pages/welcome/welcome.component.ts @@ -24,7 +24,16 @@ export class WelcomeComponent { constructor( private readonly router: Router, private readonly gameApi: GameApiService - ) {} + ) { + this.initTheme(); + } + + private initTheme(): void { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme === 'dark') { + document.documentElement.setAttribute('data-theme', 'dark'); + } + } startOneVsOne(): void { if (this.creating) { @@ -108,4 +117,21 @@ export class WelcomeComponent { this.gameIdInput = ''; this.errorMessage = ''; } + + toggleDarkMode(): void { + const htmlElement = document.documentElement; + const isDarkMode = htmlElement.getAttribute('data-theme') === 'dark'; + + if (isDarkMode) { + htmlElement.removeAttribute('data-theme'); + localStorage.removeItem('theme'); + } else { + htmlElement.setAttribute('data-theme', 'dark'); + localStorage.setItem('theme', 'dark'); + } + } + + isDarkMode(): boolean { + return document.documentElement.getAttribute('data-theme') === 'dark'; + } } diff --git a/src/styles-variables.css b/src/styles-variables.css index 0d782a0..32b9dc3 100644 --- a/src/styles-variables.css +++ b/src/styles-variables.css @@ -2,19 +2,20 @@ COLOR VARIABLES - Semantic Naming ======================================== */ -:root { - /* Primary Colors */ +/* Light Mode Colors (Default) */ +:root:not([data-theme='dark']) { + /* Primary Colors - Light Mode */ --color-primary: #BA6D4B; --color-primary-dark: #5A2C28; --color-primary-light: #F3C8A0; - /* Secondary Colors */ + /* Secondary Colors - Light Mode */ --color-secondary-mint: #B9DAD1; --color-secondary-blue: #B9C2DA; --color-secondary-purple: #C19EF5; --color-secondary-lime: #E1EAA9; - /* Functional Colors */ + /* Functional Colors - Light Mode */ --color-bg-main: #F3C8A0; --color-bg-board: #B9DAD1; --color-bg-card: #E1EAA9; @@ -26,6 +27,36 @@ --color-text-primary: #5A2C28; --color-text-button-hover: #F3C8A0; --color-border: #5A2C28; +} + +/* Dark Mode Colors */ +:root[data-theme='dark'] { + /* Primary Colors - Dark Mode */ + --color-primary: #1a3a52; + --color-primary-dark: #0f1f2e; + --color-primary-light: #2d5a7b; + + /* Secondary Colors - Dark Mode */ + --color-secondary-mint: #4a7c7c; + --color-secondary-blue: #5a6fa5; + --color-secondary-purple: #7d5fa8; + --color-secondary-lime: #6b8e23; + + /* Functional Colors - Dark Mode */ + --color-bg-main: #1a2f47; + --color-bg-board: #2d5a7b; + --color-bg-card: #3d4e63; + --color-bg-input: #2d4a6f; + --color-bg-input-focus: #3d5a8f; + --color-bg-button: #5a7da5; + --color-bg-button-hover: #7a9dc5; + + --color-text-primary: #e8f0f8; + --color-text-button-hover: #ffffff; + --color-border: #4a7c9c; +} + +:root { /* ======================================== TYPOGRAPHY SIZES diff --git a/src/styles.css b/src/styles.css index 7354bb7..fa5a81b 100644 --- a/src/styles.css +++ b/src/styles.css @@ -5,13 +5,57 @@ box-sizing: border-box; } +/* Light Mode (Default) */ +html:not([data-theme='dark']), +html:not([data-theme='dark']) body { + background: linear-gradient(160deg, var(--color-primary-light), var(--color-secondary-mint)); + color: var(--color-text-primary); +} + +html:not([data-theme='dark']) body::before { + display: none; +} + +/* Dark Mode */ +html[data-theme='dark'], +html[data-theme='dark'] body { + background: #0f1f2e; + background-image: + radial-gradient(2px 2px at 20px 30px, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)), + radial-gradient(2px 2px at 60px 70px, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0)), + radial-gradient(1px 1px at 50px 50px, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0)), + radial-gradient(1px 1px at 130px 80px, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)), + radial-gradient(2px 2px at 90px 10px, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0)), + radial-gradient(1px 1px at 130px 120px, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0)), + radial-gradient(2px 2px at 10px 90px, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0)), + radial-gradient(1px 1px at 40px 120px, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); + background-repeat: repeat; + background-size: 150px 150px; + background-attachment: fixed; + color: var(--color-text-primary); +} + +html[data-theme='dark'] body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: + radial-gradient(circle at 20% 80%, rgba(45, 90, 123, 0.1) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba(90, 111, 165, 0.1) 0%, transparent 50%), + radial-gradient(circle at 50% 50%, rgba(29, 53, 82, 0.05) 0%, transparent 50%); + pointer-events: none; + z-index: -1; +} + html, body { margin: 0; min-height: 100%; font-family: "Comic Sans MS", "Comic Sans", cursive; - background: linear-gradient(160deg, var(--color-primary-light), var(--color-secondary-mint)); - color: var(--color-text-primary); + position: relative; } button,