Add Reduce Animation Setting
This commit is contained in:
1 parent
9043aa0535
commit
4974a699dc
15 files changed
+124
-102
No files matched your search
@@ -25,6 +25,9 @@ export class App {
|
|||||||
|
|
||||||
const lang = this.settingsService.language();
|
const lang = this.settingsService.language();
|
||||||
document.documentElement.lang = lang.toLowerCase();
|
document.documentElement.lang = lang.toLowerCase();
|
||||||
|
|
||||||
|
const reduceMotion = this.settingsService.reduceAnimation();
|
||||||
|
document.documentElement.classList.toggle('reduce-motion', reduceMotion);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,40 +2,36 @@
|
|||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
@use "styles/library" as library;
|
@use "styles/library" as library;
|
||||||
|
|
||||||
$dropdown_bg: oklch(1 0 0); // #FFFFFF
|
$default_bg: oklch(1 0 0); // #FFFFFF
|
||||||
$dropdown_border_color: oklch(0 0 0 / 20%);
|
$border_color: oklch(0 0 0 / 20%);
|
||||||
$dropdown_hover: oklch(0.9702 0 0); // #F5F5F5
|
$hover_bg: oklch(0.9702 0 0); // #F5F5F5
|
||||||
|
|
||||||
$dropdown_height: 40px;
|
$height: 40px;
|
||||||
$dropdown_border_radius: 8px;
|
$border_radius: 8px;
|
||||||
|
|
||||||
$dropdown_animation_timing: cubic-bezier(0.4, 0, 0.2, 1);
|
$animation_timing: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
$dropdown_animation_duration: 0.2s;
|
$default_animation_duration: 0.2s;
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
--animation-duration: calc(#{$default_animation_duration} * var(--motion-duration-multiplier));
|
||||||
padding: 0; margin: 0;
|
padding: 0; margin: 0;
|
||||||
|
|
||||||
background-color: $dropdown_bg;
|
background-color: $default_bg;
|
||||||
border-radius: $dropdown_border_radius;
|
border-radius: $border_radius;
|
||||||
box-shadow: 0 0 0 1px $dropdown_border_color inset;
|
box-shadow: 0 0 0 1px $border_color inset;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
--dropdown-duration: #{$dropdown_animation_duration};
|
|
||||||
|
|
||||||
&.reduce-motion {
|
|
||||||
--dropdown-duration: 0s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-box {
|
.dropdown-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
height: $dropdown_height;
|
height: $height;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
@@ -60,7 +56,7 @@ $dropdown_animation_duration: 0.2s;
|
|||||||
.chevron {
|
.chevron {
|
||||||
width: 24px; height: 24px;
|
width: 24px; height: 24px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transition: transform var(--dropdown-duration) $dropdown_animation_timing;
|
transition: transform var(--dropdown-duration) $animation_timing;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
@@ -77,20 +73,20 @@ $dropdown_animation_duration: 0.2s;
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background-color: $dropdown_bg;
|
background-color: $default_bg;
|
||||||
border-radius: $dropdown_border_radius;
|
border-radius: $border_radius;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-shadow: 0 4px 12px oklch(0 0 0 / 10%); // rgba(0, 0, 0, 0.1)
|
box-shadow: 0 4px 12px oklch(0 0 0 / 10%); // rgba(0, 0, 0, 0.1)
|
||||||
animation: slideIn var(--dropdown-duration) $dropdown_animation_timing;
|
animation: slideIn var(--animation-duration) $animation_timing;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
border-radius: $dropdown_border_radius;
|
border-radius: $border_radius;
|
||||||
box-shadow: 0 0 0 1px $dropdown_border_color inset;
|
box-shadow: 0 0 0 1px $border_color inset;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
}
|
}
|
||||||
@@ -108,10 +104,10 @@ $dropdown_animation_duration: 0.2s;
|
|||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
transition: background-color var(--dropdown-duration) $dropdown_animation_timing;
|
transition: background-color var(--animation-duration) $animation_timing;
|
||||||
|
|
||||||
&:hover, &.focused {
|
&:hover, &.focused {
|
||||||
background-color: $dropdown_hover;
|
background-color: $hover_bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export interface DropdownOption {
|
|||||||
"isOpen() && focusedIndex() >= 0 ? dropdownId + '-option-' + focusedIndex() : null",
|
"isOpen() && focusedIndex() >= 0 ? dropdownId + '-option-' + focusedIndex() : null",
|
||||||
'[attr.aria-label]': 'ariaLabel()',
|
'[attr.aria-label]': 'ariaLabel()',
|
||||||
'[class.open]': 'isOpen()',
|
'[class.open]': 'isOpen()',
|
||||||
'[class.reduce-motion]': 'reduceMotion()',
|
|
||||||
tabindex: '0',
|
tabindex: '0',
|
||||||
'(click)': 'toggle()',
|
'(click)': 'toggle()',
|
||||||
'(keydown.enter)': 'onEnter($event)',
|
'(keydown.enter)': 'onEnter($event)',
|
||||||
@@ -47,12 +46,6 @@ export class Dropdown implements OnInit {
|
|||||||
private static nextId = 0;
|
private static nextId = 0;
|
||||||
dropdownId = `dropdown-${Dropdown.nextId++}`;
|
dropdownId = `dropdown-${Dropdown.nextId++}`;
|
||||||
|
|
||||||
reduceMotion = input<boolean>(
|
|
||||||
typeof window !== 'undefined'
|
|
||||||
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
||||||
: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(private el: ElementRef) {}
|
constructor(private el: ElementRef) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
animation: popup-enter 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
animation: popup-enter 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
|
||||||
border: oklch(0 0 0 / 20%) 2px solid;
|
outline: oklch(0 0 0 / 20%) 2px solid;
|
||||||
gap: 15px;
|
gap: 16px;
|
||||||
|
|
||||||
&.variant-glass {
|
&.variant-glass {
|
||||||
background-color: oklch(1 0 0 / 60%);
|
background-color: oklch(1 0 0 / 60%);
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-bottom: 1px solid oklch(0 0 0 / 5%);
|
box-shadow: 0 1px 0 oklch(0 0 0 / 20%);
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -2,32 +2,32 @@
|
|||||||
@use "sass:list";
|
@use "sass:list";
|
||||||
@use "styles/library" as library;
|
@use "styles/library" as library;
|
||||||
|
|
||||||
$switch_off_bg: oklch(0.683 0 0); // #999999
|
$off_bg: oklch(0.683 0 0); // #999999
|
||||||
|
|
||||||
$switch_dimension: (57px, 30px);
|
$dimension: (57px, 30px);
|
||||||
$switch_slider_dimension: (24px, 24px);
|
$knob_dimension: (24px, 24px);
|
||||||
$switch_padding: (3px, 3px);
|
$internal_padding: (3px, 3px);
|
||||||
$switch_border_radius: 15px;
|
$border_radius: 15px;
|
||||||
$switch_border_color: oklch(0 0 0 / 20%);
|
$border_color: oklch(0 0 0 / 20%);
|
||||||
|
|
||||||
$switch_animation_timing: cubic-bezier(0.4, 0, 0.2, 1);
|
$animation_timing: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
$switch_animation_duration: 0.2s;
|
$default_animation_duration: 0.2s;
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: list.nth($switch_dimension, 1); height: list.nth($switch_dimension, 2);
|
width: list.nth($dimension, 1); height: list.nth($dimension, 2);
|
||||||
border-radius: $switch_border_radius;
|
border-radius: $border_radius;
|
||||||
background-color: $switch_off_bg;
|
background-color: $off_bg;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 0 0 1px $switch_border_color inset;
|
box-shadow: 0 0 0 1px $border_color inset;
|
||||||
|
|
||||||
--switch-duration: #{$switch_animation_duration};
|
--animation-duration: calc(#{$default_animation_duration} * var(--motion-duration-multiplier));
|
||||||
transition: background-color var(--switch-duration) $switch_animation_timing;
|
transition: background-color var(--animation-duration) $animation_timing;
|
||||||
|
|
||||||
&.reduce-motion {
|
&.reduce-motion {
|
||||||
--switch-duration: 0s;
|
--animation-duration: 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &:focus-within {
|
&:hover, &:focus-within {
|
||||||
@@ -41,8 +41,8 @@ $switch_animation_duration: 0.2s;
|
|||||||
|
|
||||||
.switch-knob {
|
.switch-knob {
|
||||||
transform: translate(
|
transform: translate(
|
||||||
list.nth($switch_dimension, 1) - list.nth($switch_slider_dimension, 1) - list.nth($switch_padding, 1) * 2,
|
list.nth($dimension, 1) - list.nth($knob_dimension, 1) - list.nth($internal_padding, 1) * 2,
|
||||||
list.nth($switch_dimension, 2) - list.nth($switch_slider_dimension, 2) - list.nth($switch_padding, 2) * 2
|
list.nth($dimension, 2) - list.nth($knob_dimension, 2) - list.nth($internal_padding, 2) * 2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,10 +50,10 @@ $switch_animation_duration: 0.2s;
|
|||||||
|
|
||||||
.switch-knob {
|
.switch-knob {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: list.nth($switch_slider_dimension, 1); height: list.nth($switch_slider_dimension, 2);
|
width: list.nth($knob_dimension, 1); height: list.nth($knob_dimension, 2);
|
||||||
left: list.nth($switch_padding, 1); top: list.nth($switch_padding, 2);
|
left: list.nth($internal_padding, 1); top: list.nth($internal_padding, 2);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: transform var(--switch-duration) $switch_animation_timing;
|
transition: transform var(--animation-duration) $animation_timing;
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,6 @@ import { Component, input, model, output } from '@angular/core';
|
|||||||
tabindex: '0',
|
tabindex: '0',
|
||||||
'[attr.aria-checked]': 'isActive()',
|
'[attr.aria-checked]': 'isActive()',
|
||||||
'[class.active]': 'isActive()',
|
'[class.active]': 'isActive()',
|
||||||
'[class.reduce-motion]': 'reduceMotion()',
|
|
||||||
'(click)': 'onClick()',
|
'(click)': 'onClick()',
|
||||||
'(keydown.enter)': 'onClick()',
|
'(keydown.enter)': 'onClick()',
|
||||||
'(keydown.space)': 'onSpaceDown($event)',
|
'(keydown.space)': 'onSpaceDown($event)',
|
||||||
@@ -20,12 +19,6 @@ export class Switch {
|
|||||||
isActive = model<boolean>(false);
|
isActive = model<boolean>(false);
|
||||||
toggle = output<boolean>();
|
toggle = output<boolean>();
|
||||||
|
|
||||||
reduceMotion = input<boolean>(
|
|
||||||
typeof window !== 'undefined'
|
|
||||||
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
||||||
: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
onClick(): void {
|
onClick(): void {
|
||||||
this.isActive.update((value) => !value);
|
this.isActive.update((value) => !value);
|
||||||
this.onToggle();
|
this.onToggle();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-bottom: oklch(0 0 0 / 20%) 2px solid;
|
box-shadow: 0 2px 0 oklch(0 0 0 / 20%);
|
||||||
background-color: oklch(1 0 0 / 60%);
|
background-color: oklch(1 0 0 / 60%);
|
||||||
|
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ export class HeaderMenu {
|
|||||||
|
|
||||||
onSettingsClick() {
|
onSettingsClick() {
|
||||||
console.log('HeaderMenu: Settings clicked');
|
console.log('HeaderMenu: Settings clicked');
|
||||||
this.popupService.open(SettingsPopup, { position: { top: '82px', right: '18px' } });
|
this.popupService.open(SettingsPopup, { position: { top: '84px', right: '20px' } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
<dropdown [options]="languages"
|
<dropdown [options]="languages"
|
||||||
[placeholder]="translation.settings.chooseLanguage"
|
|
||||||
[value]="settingsService.language()"
|
[value]="settingsService.language()"
|
||||||
(selectionChange)="onLanguageChange($event)"
|
(selectionChange)="onLanguageChange($event)"
|
||||||
icon="language"></dropdown>
|
icon="language"></dropdown>
|
||||||
@@ -34,6 +33,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<switch [isActive]="settingsService.darkMode()" (toggle)="onDarkModeChange($event)"></switch>
|
<switch [isActive]="settingsService.darkMode()" (toggle)="onDarkModeChange($event)"></switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-container">
|
||||||
|
<div class="settings-name">
|
||||||
|
<span class="material-symbols-outlined icon-24 settings-icon">motion_photos_paused</span>
|
||||||
|
<span>{{ translation.settings.reduceAnimation }}</span>
|
||||||
|
</div>
|
||||||
|
<switch [isActive]="settingsService.reduceAnimation()" (toggle)="onReduceAnimationChange($event)"></switch>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ export class SettingsPopup {
|
|||||||
this.settingsService.setRomanisation(value);
|
this.settingsService.setRomanisation(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onReduceAnimationChange(value: boolean) {
|
||||||
|
this.settingsService.setReduceAnimation(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 팝업을 닫을 때, 자기 자신(클래스)을 명확하게 지정하여 닫습니다.
|
* 팝업을 닫을 때, 자기 자신(클래스)을 명확하게 지정하여 닫습니다.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+4
-5
@@ -1,12 +1,11 @@
|
|||||||
export const EN = {
|
export const EN = {
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Settings',
|
guest: 'Guest',
|
||||||
language: 'Language',
|
language: 'Language',
|
||||||
appearance: 'Appearance',
|
appearance: 'Appearance',
|
||||||
darkMode: 'Dark Mode',
|
darkMode: 'Dark Mode',
|
||||||
romanisation: 'Romanisation',
|
romanisation: 'Romanisation',
|
||||||
guest: 'Guest',
|
reduceAnimation: 'Reduce Animation',
|
||||||
chooseLanguage: 'Choose Language'
|
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
levels: 'Levels',
|
levels: 'Levels',
|
||||||
@@ -19,8 +18,8 @@ export const EN = {
|
|||||||
},
|
},
|
||||||
common: {
|
common: {
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
save: 'Save'
|
save: 'Save',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Translation = typeof EN;
|
export type Translation = typeof EN;
|
||||||
+2
-3
@@ -2,13 +2,12 @@ import { Translation } from './en';
|
|||||||
|
|
||||||
export const JP: Translation = {
|
export const JP: Translation = {
|
||||||
settings: {
|
settings: {
|
||||||
title: '設定',
|
guest: 'ゲスト',
|
||||||
language: '言語',
|
language: '言語',
|
||||||
appearance: '外観',
|
appearance: '外観',
|
||||||
darkMode: 'ダークモード',
|
darkMode: 'ダークモード',
|
||||||
romanisation: 'ローマ字表記',
|
romanisation: 'ローマ字表記',
|
||||||
guest: 'ゲスト',
|
reduceAnimation: 'アニメーションを減らす',
|
||||||
chooseLanguage: '言語を選択',
|
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
levels: 'レベル',
|
levels: 'レベル',
|
||||||
|
|||||||
+2
-3
@@ -2,13 +2,12 @@ import { Translation } from './en';
|
|||||||
|
|
||||||
export const KR: Translation = {
|
export const KR: Translation = {
|
||||||
settings: {
|
settings: {
|
||||||
title: '설정',
|
guest: '게스트',
|
||||||
language: '언어',
|
language: '언어',
|
||||||
appearance: '화면 설정',
|
appearance: '화면 설정',
|
||||||
darkMode: '다크 모드',
|
darkMode: '다크 모드',
|
||||||
romanisation: '로마자 표기',
|
romanisation: '로마자 표기',
|
||||||
guest: '게스트',
|
reduceAnimation: '애니메이션 줄이기',
|
||||||
chooseLanguage: '언어 선택',
|
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
levels: '레벨',
|
levels: '레벨',
|
||||||
|
|||||||
@@ -12,15 +12,17 @@ export class SettingsService {
|
|||||||
|
|
||||||
// Signals for state
|
// Signals for state
|
||||||
private _language = signal<Language>('EN');
|
private _language = signal<Language>('EN');
|
||||||
private _darkMode = signal<boolean>(true);
|
private _darkMode = signal<boolean>(false);
|
||||||
private _romanisation = signal<boolean>(false);
|
private _romanisation = signal<boolean>(true);
|
||||||
private _userName = signal<string>('Guest');
|
private _userName = signal<string>('Guest');
|
||||||
|
private _reduceAnimation = signal<boolean>(false);
|
||||||
|
|
||||||
// Public computed or read-only signals
|
// Public computed or read-only signals
|
||||||
language = computed(() => this._language());
|
language = computed(() => this._language());
|
||||||
darkMode = computed(() => this._darkMode());
|
darkMode = computed(() => this._darkMode());
|
||||||
romanisation = computed(() => this._romanisation());
|
romanisation = computed(() => this._romanisation());
|
||||||
userName = computed(() => this._userName());
|
userName = computed(() => this._userName());
|
||||||
|
reduceAnimation = computed(() => this._reduceAnimation());
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.request = inject(REQUEST);
|
this.request = inject(REQUEST);
|
||||||
@@ -28,30 +30,54 @@ export class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initializeSettings() {
|
private initializeSettings() {
|
||||||
|
let parsedCookie: Record<string, string>;
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
// 클라이언트: 브라우저 API 사용
|
parsedCookie = this.parseCookies(document.cookie);
|
||||||
this._language.set((this.getCookie('lang') as Language) || 'EN');
|
|
||||||
this._darkMode.set(this.getCookie('darkMode') !== 'false');
|
|
||||||
this._romanisation.set(this.getCookie('romanisation') === 'true');
|
|
||||||
this._userName.set(this.getCookie('userName') || 'Guest');
|
|
||||||
} else if (isPlatformServer(this.platformId)) {
|
} else if (isPlatformServer(this.platformId)) {
|
||||||
const cookieHeader = this.request?.headers.get('Cookie');
|
const cookieHeader = this.request?.headers.get('Cookie');
|
||||||
|
|
||||||
if (cookieHeader) {
|
if (!cookieHeader) return;
|
||||||
const cookies = this.parseServerCookies(cookieHeader);
|
parsedCookie = this.parseCookies(cookieHeader);
|
||||||
|
} else return;
|
||||||
|
|
||||||
// console.log(cookies);
|
const settings = {
|
||||||
if (cookies['lang']) this._language.set(cookies['lang'] as Language);
|
lang: parsedCookie['lang'],
|
||||||
if (cookies['darkMode'] !== undefined) this._darkMode.set(cookies['darkMode'] !== 'false');
|
darkMode: parsedCookie['darkMode'],
|
||||||
if (cookies['romanisation'] !== undefined) this._romanisation.set(cookies['romanisation'] === 'true');
|
romanisation: parsedCookie['romanisation'],
|
||||||
if (cookies['userName']) this._userName.set(cookies['userName']);
|
userName: parsedCookie['userName'],
|
||||||
}
|
reduceAnimation: parsedCookie['reduceAnimation'],
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const windowDefined = typeof window !== 'undefined';
|
||||||
|
|
||||||
|
if (!settings.lang) {
|
||||||
|
this.setLanguage('EN');
|
||||||
|
} else this._language.set(settings.lang as Language);
|
||||||
|
|
||||||
|
if (settings.darkMode === undefined) {
|
||||||
|
const isDarkMode = windowDefined ?
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches : false;
|
||||||
|
this.setDarkMode(isDarkMode);
|
||||||
|
} else this._darkMode.set(settings.darkMode === 'true');
|
||||||
|
|
||||||
|
if (settings.romanisation === undefined) {
|
||||||
|
this.setRomanisation(true);
|
||||||
|
} else this._romanisation.set(settings.romanisation === 'true');
|
||||||
|
|
||||||
|
if (!settings.userName) {
|
||||||
|
this.setUserName('Guest');
|
||||||
|
} else this._userName.set(settings.userName);
|
||||||
|
|
||||||
|
if (settings.reduceAnimation === undefined) {
|
||||||
|
const isReduceAnimation = windowDefined ?
|
||||||
|
window.matchMedia('(prefers-reduced-motion: reduce)').matches : false;
|
||||||
|
this.setReduceAnimation(isReduceAnimation);
|
||||||
|
} else this._reduceAnimation.set(settings.reduceAnimation === 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseServerCookies(cookieString: string): Record<string, string> {
|
private parseCookies(cookieString: string): Record<string, string> {
|
||||||
const cookies: Record<string, string> = {};
|
const cookies: Record<string, string> = {};
|
||||||
cookieString.split(';').forEach(c => {
|
cookieString.split(';').forEach((c) => {
|
||||||
const [key, value] = c.trim().split('=');
|
const [key, value] = c.trim().split('=');
|
||||||
if (key && value) cookies[key] = decodeURIComponent(value);
|
if (key && value) cookies[key] = decodeURIComponent(value);
|
||||||
});
|
});
|
||||||
@@ -78,14 +104,9 @@ export class SettingsService {
|
|||||||
this.setCookie('userName', name);
|
this.setCookie('userName', name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCookie(name: string): string | undefined {
|
setReduceAnimation(value: boolean) {
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
this._reduceAnimation.set(value);
|
||||||
const value = `; ${document.cookie}`;
|
this.setCookie('reduceAnimation', value.toString());
|
||||||
const parts = value.split(`; ${name}=`);
|
|
||||||
if (parts.length === 2) return parts.pop()?.split(';').shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCookie(name: string, value: string) {
|
private setCookie(name: string, value: string) {
|
||||||
|
|||||||
+9
-1
@@ -5,6 +5,14 @@
|
|||||||
@import url('https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.1/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css');
|
@import url('https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.1/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css');
|
||||||
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-jp-dynamic-subset.min.css");
|
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-jp-dynamic-subset.min.css");
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--motion-duration-multiplier: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.reduce-motion {
|
||||||
|
--motion-duration-multiplier: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'NanumSquareNeo', 'Wanted Sans Variable', 'Pretendard JP', sans-serif;
|
font-family: 'NanumSquareNeo', 'Wanted Sans Variable', 'Pretendard JP', sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -77,7 +85,7 @@ section.feature-block {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 2px solid oklch(0 0 0 / 20%); // rgba(0, 0, 0, 0.2)
|
outline: 2px solid oklch(0 0 0 / 20%); // rgba(0, 0, 0, 0.2)
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
background-color: oklch(1 0 0 / 60%);
|
background-color: oklch(1 0 0 / 60%);
|
||||||
|
|||||||
Reference in new issue
Block a user