From 57588d460416264ae707ad5f5d68d7d3c1d4bec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4DELTA?= Date: Tue, 7 Apr 2026 16:02:58 +0900 Subject: [PATCH] Fix Color Animation Implementation --- src/app/app.ts | 37 +++++++++++++++-------- src/app/components/dropdown/dropdown.scss | 30 ++++++++++++++++-- src/app/components/popup/popup.scss | 28 ++++++++++++++--- src/app/components/switch/switch.scss | 28 ++++++++++++++--- src/app/features/settings/popup.scss | 2 +- 5 files changed, 98 insertions(+), 27 deletions(-) diff --git a/src/app/app.ts b/src/app/app.ts index 02faf7e..0d95cdc 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -3,32 +3,43 @@ import { RouterOutlet } from '@angular/router'; import { HeaderMenu } from './features/header-menu/header-menu'; import { PopupService } from './services/popup.service'; import { SettingsService } from './services/settings.service'; -import { NgComponentOutlet, isPlatformBrowser } from '@angular/common'; +import { NgComponentOutlet, isPlatformBrowser, isPlatformServer, DOCUMENT } from '@angular/common'; @Component({ selector: 'app-root', imports: [RouterOutlet, HeaderMenu, NgComponentOutlet], templateUrl: './app.html', - styleUrl: './app.scss' + styleUrl: './app.scss', }) export class App { protected popupService = inject(PopupService); protected settingsService = inject(SettingsService); private platformId = inject(PLATFORM_ID); + private document = inject(DOCUMENT); + private rootHtmlElement!: HTMLElement; + constructor() { + if (isPlatformServer(this.platformId)) { + this.rootHtmlElement = this.document.documentElement; + } else if (isPlatformBrowser(this.platformId)) { + this.rootHtmlElement = document.documentElement; + } + effect(() => { - if (isPlatformBrowser(this.platformId)) { - const isDark = this.settingsService.darkMode(); - document.documentElement.classList.toggle('dark-theme', isDark); - document.documentElement.classList.toggle('light-theme', !isDark); - - const lang = this.settingsService.language(); - document.documentElement.lang = lang.toLowerCase(); - - const reduceMotion = this.settingsService.reduceAnimation(); - document.documentElement.classList.toggle('reduce-motion', reduceMotion); - } + this.initializeStatus(); }); } + + initializeStatus() { + const isDark = this.settingsService.darkMode(); + this.rootHtmlElement.classList.toggle('dark-theme', isDark); + this.rootHtmlElement.classList.toggle('light-theme', !isDark); + + const lang = this.settingsService.language(); + this.rootHtmlElement.lang = lang.toLowerCase(); + + const reduceMotion = this.settingsService.reduceAnimation(); + this.rootHtmlElement.classList.toggle('reduce-motion', reduceMotion); + } } diff --git a/src/app/components/dropdown/dropdown.scss b/src/app/components/dropdown/dropdown.scss index 534d83f..01e4133 100644 --- a/src/app/components/dropdown/dropdown.scss +++ b/src/app/components/dropdown/dropdown.scss @@ -13,6 +13,18 @@ $border_radius: 8px; $animation_timing: cubic-bezier(0.4, 0, 0.2, 1); $default_animation_duration: 0.2s; +@property --hover-state { + syntax: ''; + inherits: false; + initial-value: 0; +} + +@property --selected-state { + syntax: ''; + inherits: false; + initial-value: 0; +} + :host { display: inline-block; position: relative; @@ -113,15 +125,27 @@ $default_animation_duration: 0.2s; padding: 0 16px; position: relative; align-content: center; - transition: background-color var(--animation-duration) $animation_timing; + transition: --hover-state var(--animation-duration) $animation_timing, --selected-state var(--animation-duration) $animation_timing; font-size: 16px; + background-color: color-mix( + in oklch, + color-mix( + in oklch, + transparent, + light-dark($hover_bg, $hover_dark_bg) + calc(var(--hover-state) * 100%) + ), + light-dark(list.nth(library.$brand_palette, 1), list.nth(library.$brand_palette, 11)) + calc(var(--selected-state) * 100%) + ); + &:hover, &.focused { - background-color: light-dark($hover_bg, $hover_dark_bg); + --hover-state: 1; } &.selected { - background-color: light-dark(list.nth(library.$brand_palette, 1), list.nth(library.$brand_palette, 11)); + --selected-state: 1; color: list.nth(library.$brand_palette, 5); font-weight: 600; } diff --git a/src/app/components/popup/popup.scss b/src/app/components/popup/popup.scss index 40e9135..0b8206b 100644 --- a/src/app/components/popup/popup.scss +++ b/src/app/components/popup/popup.scss @@ -1,6 +1,12 @@ $animation_timing: cubic-bezier(0.16, 1, 0.3, 1); $default_animation_duration: 0.2s; +@property --close-hover-state { + syntax: ''; + inherits: false; + initial-value: 0; +} + :host { display: block; --animation-duration: calc(#{$default_animation_duration} * var(--motion-duration-multiplier)); @@ -77,17 +83,29 @@ $default_animation_duration: 0.2s; .header-right { .close-btn { - background: none; + --close-hover-state: 0; border: none; padding: 4px; cursor: pointer; border-radius: 50%; - color: oklch(0.4 0 0); - transition: background-color 0.2s, color 0.2s; + + background: color-mix( + in oklch, + transparent, + light-dark(oklch(0 0 0 / 5%), oklch(1 0 0 / 5%)) + calc(var(--close-hover-state) * 100%) + ); + + color: color-mix( + in oklch, + light-dark(oklch(0.4 0 0), oklch(0.6 0 0)), + light-dark(oklch(0 0 0), oklch(1 0 0)) + calc(var(--close-hover-state) * 100%) + ); + transition: --close-hover-state 0.2s; &:hover { - background-color: oklch(0 0 0 / 5%); - color: oklch(0 0 0); + --close-hover-state: 1; } } } diff --git a/src/app/components/switch/switch.scss b/src/app/components/switch/switch.scss index 2b06488..e1f6a0f 100644 --- a/src/app/components/switch/switch.scss +++ b/src/app/components/switch/switch.scss @@ -16,16 +16,35 @@ $border_dark_color: oklch(1 0 0 / 40%); $animation_timing: cubic-bezier(0.4, 0, 0.2, 1); $default_animation_duration: 0.2s; +@property --animation-state { + syntax: ''; + inherits: false; + initial-value: 0; +} + :host { display: inline-block; width: list.nth($dimension, 1); height: list.nth($dimension, 2); border-radius: $border_radius; - background-color: light-dark($off_bg, $off_dark_bg); - box-shadow: 0 0 0 1px light-dark($border_color, $border_dark_color) inset; + background-color: color-mix( + in oklch, + light-dark($off_bg, $off_dark_bg), + light-dark(list.nth(library.$brand_palette, 5), list.nth(library.$brand_palette, 8)) + calc(var(--animation-state) * 100%) + ); + box-shadow: 0 0 0 1px color-mix( + in oklch, + light-dark($border_color, $border_dark_color), + light-dark($border_color, list.nth(library.$brand_palette, 10)) + calc(var(--animation-state) * 100%) + ) inset; + + --animation-state: 0; --animation-duration: calc(#{$default_animation_duration} * var(--motion-duration-multiplier)); - transition: background-color var(--animation-duration) $animation_timing, box-shadow var(--animation-duration) $animation_timing; + // transition: background-color var(--animation-duration) $animation_timing, box-shadow var(--animation-duration) $animation_timing; + transition: --animation-state var(--animation-duration) $animation_timing; cursor: pointer; position: relative; @@ -38,8 +57,7 @@ $default_animation_duration: 0.2s; } &.active { - background-color: light-dark(list.nth(library.$brand_palette, 5), list.nth(library.$brand_palette, 9)); - box-shadow: var(--if-dark, 0 0 0 1px list.nth(library.$brand_palette, 10) inset); + --animation-state: 1; .switch-knob { transform: translate( diff --git a/src/app/features/settings/popup.scss b/src/app/features/settings/popup.scss index 2763c31..83811bf 100644 --- a/src/app/features/settings/popup.scss +++ b/src/app/features/settings/popup.scss @@ -13,7 +13,7 @@ .avatar { border-radius: 50%; - border: 1px solid oklch(0 0 0 / 20%); + outline: 1px solid oklch(0 0 0 / 20%); object-fit: cover; } }