diff --git a/src/app/app.component.html b/src/app/app.component.html index 5aab10b..bb42443 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -2,21 +2,19 @@ -@if (isLanguageConfirmed()) { - -} + @if (isLanguageSelectorOpen()) {
{{ dialogContent().app.selectorTitle }}
- @if (isLanguageConfirmed()) { - - } +
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 3d471b8..0721331 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -25,9 +25,9 @@ describe('AppComponent', (): void => { expect(component).toBeTruthy(); }); - it('should render the language selector on first visit', (): void => { + it('should keep the language selector closed on first visit', (): void => { const el = fixture.nativeElement as HTMLElement; - expect(el.querySelector('.language-overlay')).not.toBeNull(); + expect(el.querySelector('.language-overlay')).toBeNull(); }); it('should preselect English by default', (): void => { @@ -35,6 +35,11 @@ describe('AppComponent', (): void => { expect(comp.selectedLanguage()).toBe('en'); }); + it('should render the language switcher on first visit', (): void => { + const el = fixture.nativeElement as HTMLElement; + expect(el.querySelector('.language-switcher')).not.toBeNull(); + }); + it('should always render a router-outlet element', (): void => { const el = fixture.nativeElement as HTMLElement; expect(el.querySelector('router-outlet')).not.toBeNull(); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5ce8fc5..10d1994 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,7 +12,6 @@ import { RouterOutlet } from '@angular/router'; import type { LanguageCode } from '../languages/language.types'; import type { LanguageOption } from './app.types'; -import { KofiWidgetService } from './services/kofi-widget.service'; import { LanguageService } from './services/language.service'; @Component({ @@ -24,13 +23,12 @@ import { LanguageService } from './services/language.service'; }) export class AppComponent implements OnInit, OnDestroy { private readonly document = inject(DOCUMENT); - private readonly kofiWidgetService = inject(KofiWidgetService); private readonly languageService = inject(LanguageService); protected readonly content = this.languageService.content; protected readonly currentLanguageCode = this.languageService.languageCode; protected readonly isLanguageConfirmed = this.languageService.isLanguageConfirmed; - protected readonly isLanguageSelectorOpen = signal(true); + protected readonly isLanguageSelectorOpen = signal(false); protected readonly selectedLanguage = signal('en'); protected readonly languageOptions: readonly LanguageOption[] = [ { code: 'en', accent: 'EN' }, @@ -54,8 +52,6 @@ export class AppComponent implements OnInit, OnDestroy { ngOnInit(): void { const initialLanguage = this.languageService.initializeFromStorage(); this.selectedLanguage.set(initialLanguage); - this.isLanguageSelectorOpen.set(!this.isLanguageConfirmed()); - this.kofiWidgetService.scheduleLoad(); } protected chooseLanguage(language: LanguageCode): void { @@ -66,7 +62,6 @@ export class AppComponent implements OnInit, OnDestroy { protected confirmLanguage(): void { this.languageService.confirmLanguage(this.selectedLanguage()); this.isLanguageSelectorOpen.set(false); - this.kofiWidgetService.prioritizeLoad(); } protected reopenLanguageSelector(): void { @@ -75,13 +70,8 @@ export class AppComponent implements OnInit, OnDestroy { } protected closeLanguageSelector(): void { - if (!this.isLanguageConfirmed()) { - return; - } - this.selectedLanguage.set(this.currentLanguageCode()); this.isLanguageSelectorOpen.set(false); - this.kofiWidgetService.prioritizeLoad(); } protected closeLanguageSelectorOnBackdrop(event: MouseEvent): void { @@ -95,7 +85,6 @@ export class AppComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.kofiWidgetService.teardown(); this.document.body.style.overflow = ''; this.document.documentElement.style.overflow = ''; } diff --git a/src/app/services/kofi-widget.service.ts b/src/app/services/kofi-widget.service.ts deleted file mode 100644 index a98a8c1..0000000 --- a/src/app/services/kofi-widget.service.ts +++ /dev/null @@ -1,350 +0,0 @@ -import { DOCUMENT, isPlatformBrowser } from '@angular/common'; -import { inject, Injectable, PLATFORM_ID } from '@angular/core'; - -@Injectable({ providedIn: 'root' }) -export class KofiWidgetService { - private readonly document = inject(DOCUMENT); - private readonly platformId = inject(PLATFORM_ID); - - private readonly widgetScriptUrl = 'https://storage.ko-fi.com/cdn/scripts/overlay-widget.js'; - private readonly widgetIframeTitle = 'Ko-fi support chat'; - private readonly fallbackDelayMs = 2500; - private readonly mobileBreakpointPx = 700; - private readonly mobileViewportOffsetPx = 24; - - private loadTimeoutId: number | null = null; - private loadIdleCallbackId: number | null = null; - private loadListenerAbortController: AbortController | null = null; - private resizeListenerAbortController: AbortController | null = null; - private iframeObserver: MutationObserver | null = null; - private isWidgetScheduled = false; - private isWidgetLoaded = false; - private isWidgetDrawn = false; - - scheduleLoad(): void { - if (!isPlatformBrowser(this.platformId) || this.isWidgetScheduled) { - return; - } - - this.isWidgetScheduled = true; - - const windowRef = this.document.defaultView; - if (windowRef === null) { - return; - } - - const queueWidgetLoad = (): void => { - if (this.isWidgetLoaded || this.isWidgetDrawn) { - return; - } - - const loadWidget = (): void => { - this.loadIdleCallbackId = null; - void this.loadWidget(); - }; - - if (typeof windowRef.requestIdleCallback === 'function') { - this.loadIdleCallbackId = windowRef.requestIdleCallback(loadWidget, { timeout: 2500 }); - return; - } - - this.loadTimeoutId = windowRef.setTimeout(loadWidget, 1800); - }; - - const triggerWidgetLoad = (): void => { - this.clearStartListeners(); - queueWidgetLoad(); - }; - - const scheduleFallbackWidgetLoad = (): void => { - if (this.isWidgetLoaded || this.isWidgetDrawn) { - return; - } - - this.loadTimeoutId = windowRef.setTimeout((): void => { - this.loadTimeoutId = null; - triggerWidgetLoad(); - }, this.fallbackDelayMs); - }; - - this.loadListenerAbortController = new AbortController(); - const listenerOptions: AddEventListenerOptions = { - passive: true, - signal: this.loadListenerAbortController.signal, - }; - - windowRef.addEventListener('pointerdown', triggerWidgetLoad, listenerOptions); - windowRef.addEventListener('keydown', triggerWidgetLoad, listenerOptions); - windowRef.addEventListener('touchstart', triggerWidgetLoad, listenerOptions); - windowRef.addEventListener('scroll', triggerWidgetLoad, listenerOptions); - - if (this.document.readyState === 'complete') { - scheduleFallbackWidgetLoad(); - } else { - windowRef.addEventListener('load', scheduleFallbackWidgetLoad, { - ...listenerOptions, - once: true, - }); - } - } - - prioritizeLoad(): void { - if (!isPlatformBrowser(this.platformId) || this.isWidgetLoaded || this.isWidgetDrawn) { - return; - } - - const windowRef = this.document.defaultView; - if (windowRef === null) { - return; - } - - this.clearStartListeners(); - this.clearPendingLoadTimers(); - - const loadWidget = (): void => { - this.loadIdleCallbackId = null; - this.loadTimeoutId = null; - void this.loadWidget(); - }; - - if (typeof windowRef.requestIdleCallback === 'function') { - this.loadIdleCallbackId = windowRef.requestIdleCallback(loadWidget, { timeout: 1200 }); - return; - } - - this.loadTimeoutId = windowRef.setTimeout(loadWidget, 200); - } - - teardown(): void { - this.clearStartListeners(); - this.resizeListenerAbortController?.abort(); - this.resizeListenerAbortController = null; - this.clearPendingLoadTimers(); - - this.iframeObserver?.disconnect(); - this.iframeObserver = null; - } - - private clearStartListeners(): void { - this.loadListenerAbortController?.abort(); - this.loadListenerAbortController = null; - } - - private clearPendingLoadTimers(): void { - const windowRef = this.document.defaultView; - - if (windowRef !== null && this.loadTimeoutId !== null) { - windowRef.clearTimeout(this.loadTimeoutId); - this.loadTimeoutId = null; - } - - if ( - windowRef !== null && - this.loadIdleCallbackId !== null && - typeof windowRef.cancelIdleCallback === 'function' - ) { - windowRef.cancelIdleCallback(this.loadIdleCallbackId); - this.loadIdleCallbackId = null; - } - } - - private async loadWidget(): Promise { - if (this.isWidgetLoaded || this.isWidgetDrawn) { - return; - } - - const windowRef = this.document.defaultView; - if (windowRef === null) { - return; - } - - if (windowRef.kofiWidgetOverlay !== undefined) { - this.isWidgetLoaded = true; - this.drawWidget(); - return; - } - - const existingScript = this.document.querySelector( - `script[src="${this.widgetScriptUrl}"]`, - ); - - if (existingScript !== null) { - existingScript.addEventListener( - 'load', - (): void => { - this.isWidgetLoaded = true; - this.drawWidget(); - }, - { once: true }, - ); - return; - } - - await new Promise((resolve, reject): void => { - const scriptElement = this.document.createElement('script'); - scriptElement.src = this.widgetScriptUrl; - scriptElement.async = true; - scriptElement.crossOrigin = 'anonymous'; - scriptElement.addEventListener( - 'load', - (): void => { - this.isWidgetLoaded = true; - resolve(); - }, - { once: true }, - ); - scriptElement.addEventListener( - 'error', - (): void => reject(new Error('Failed to load the Ko-fi widget script.')), - { once: true }, - ); - this.document.body.appendChild(scriptElement); - }).catch((): void => { - this.isWidgetLoaded = false; - }); - - this.drawWidget(); - } - - private drawWidget(): void { - if (this.isWidgetDrawn) { - return; - } - - const windowRef = this.document.defaultView; - if (windowRef?.kofiWidgetOverlay === undefined) { - return; - } - - windowRef.kofiWidgetOverlay.draw('fraujulian', { - type: 'floating-chat', - 'floating-chat.donateButton.text': 'Coffee', - 'floating-chat.donateButton.background-color': '#ff5f5f', - 'floating-chat.donateButton.text-color': '#fff', - }); - - this.isWidgetDrawn = true; - this.ensureAccessibleIframeTitles(); - this.applyResponsiveWidgetLayout(); - this.observeInjectedIframes(); - this.observeViewportChanges(); - } - - private ensureAccessibleIframeTitles(): void { - this.document - .querySelectorAll('iframe[id^="kofi-wo-container"]') - .forEach((iframe: HTMLIFrameElement): void => { - iframe.title = this.widgetIframeTitle; - }); - } - - private applyResponsiveWidgetLayout(): void { - const windowRef = this.document.defaultView; - if (windowRef === null) { - return; - } - - const isMobileViewport: boolean = windowRef.innerWidth <= this.mobileBreakpointPx; - const mobileWidthPx: number = Math.max(windowRef.innerWidth - this.mobileViewportOffsetPx, 280); - const mobileHeightPx: number = Math.max( - Math.min(Math.round(windowRef.innerHeight * 0.68), 520), - 360, - ); - - this.document - .querySelectorAll('[id*="kofi-widget-overlay"]') - .forEach((overlay: HTMLElement): void => { - if (isMobileViewport) { - overlay.style.left = '12px'; - overlay.style.right = '12px'; - overlay.style.bottom = '12px'; - overlay.style.width = 'auto'; - overlay.style.maxWidth = `${mobileWidthPx}px`; - } else { - overlay.style.removeProperty('left'); - overlay.style.removeProperty('right'); - overlay.style.removeProperty('width'); - overlay.style.removeProperty('max-width'); - } - }); - - this.document - .querySelectorAll( - '.floatingchat-container-wrap, .floatingchat-container-wrap-mobi', - ) - .forEach((container: HTMLElement): void => { - if (isMobileViewport) { - container.style.width = `${mobileWidthPx}px`; - container.style.maxWidth = 'calc(100vw - 24px)'; - container.style.left = '0'; - container.style.right = '0'; - } else { - container.style.removeProperty('width'); - container.style.removeProperty('max-width'); - container.style.removeProperty('left'); - container.style.removeProperty('right'); - } - }); - - this.document - .querySelectorAll('iframe[id^="kofi-wo-container"]') - .forEach((iframe: HTMLIFrameElement): void => { - iframe.title = this.widgetIframeTitle; - - if (isMobileViewport) { - iframe.style.width = `${mobileWidthPx}px`; - iframe.style.maxWidth = 'calc(100vw - 24px)'; - iframe.style.height = `${mobileHeightPx}px`; - iframe.style.maxHeight = '68vh'; - iframe.style.borderRadius = '18px'; - } else { - iframe.style.removeProperty('width'); - iframe.style.removeProperty('max-width'); - iframe.style.removeProperty('height'); - iframe.style.removeProperty('max-height'); - iframe.style.removeProperty('border-radius'); - } - }); - } - - private observeInjectedIframes(): void { - if (this.iframeObserver !== null) { - return; - } - - this.iframeObserver = new MutationObserver((): void => { - this.ensureAccessibleIframeTitles(); - this.applyResponsiveWidgetLayout(); - }); - - this.iframeObserver.observe(this.document.body, { - childList: true, - subtree: true, - }); - } - - private observeViewportChanges(): void { - if (this.resizeListenerAbortController !== null) { - return; - } - - const windowRef = this.document.defaultView; - if (windowRef === null) { - return; - } - - this.resizeListenerAbortController = new AbortController(); - const listenerOptions: AddEventListenerOptions = { - passive: true, - signal: this.resizeListenerAbortController.signal, - }; - - windowRef.addEventListener( - 'resize', - (): void => { - this.applyResponsiveWidgetLayout(); - }, - listenerOptions, - ); - } -} diff --git a/src/app/services/language.service.ts b/src/app/services/language.service.ts index 7e38b4d..d39ba0a 100644 --- a/src/app/services/language.service.ts +++ b/src/app/services/language.service.ts @@ -36,7 +36,7 @@ const deShellLanguage: LanguageShellPack = { export class LanguageService { private static readonly storageKey = 'portfolio-language'; private readonly currentLanguageCode = signal('en'); - private readonly languageConfirmed = signal(false); + private readonly languageConfirmed = signal(true); readonly languageCode = this.currentLanguageCode.asReadonly(); readonly isLanguageConfirmed = this.languageConfirmed.asReadonly(); @@ -52,7 +52,6 @@ export class LanguageService { const storedLanguage = window.localStorage.getItem(LanguageService.storageKey); if (storedLanguage === 'de' || storedLanguage === 'en') { this.currentLanguageCode.set(storedLanguage); - this.languageConfirmed.set(true); } return this.currentLanguageCode(); diff --git a/src/kofi-widget.d.ts b/src/kofi-widget.d.ts deleted file mode 100644 index 3a2cb49..0000000 --- a/src/kofi-widget.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -interface KofiWidgetOverlay { - draw(userName: string, options: Record): void; -} - -interface Window { - kofiWidgetOverlay?: KofiWidgetOverlay; - requestIdleCallback?: (callback: IdleRequestCallback, options?: IdleRequestOptions) => number; - cancelIdleCallback?: (handle: number) => void; -} diff --git a/src/styles.scss b/src/styles.scss index c1fcc0f..510b7b9 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -4,6 +4,7 @@ --surface: rgba(20, 18, 22, 0.62); --surface-strong: rgba(15, 14, 18, 0.78); --surface-soft: rgba(255, 255, 255, 0.05); + --panel-background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.03)); --border-soft: rgba(255, 255, 255, 0.1); --text-primary: #f7f1ea; --text-muted: rgba(247, 241, 234, 0.74); @@ -95,7 +96,7 @@ hr { } .panel { - background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.03)); + background: var(--panel-background); border: 1px solid var(--border-soft); border-radius: var(--radius-xl); box-shadow: var(--shadow); @@ -194,6 +195,14 @@ hr { margin-right: 0; } +.hero-section > *, +.story-grid > *, +.project-entry > *, +.project-header > *, +.footer-inner > * { + min-width: 0; +} + .hero-copy { grid-column: 1; align-self: start; @@ -201,6 +210,25 @@ hr { text-align: left; } +.hero-copy, +.hero-copy-main, +.hero-actions, +.hero-action-links, +.hero-meta, +.hero-visual, +.portrait-media, +.story-copy, +.story-detail, +.project-copy, +.project-title-wrap, +.project-languages, +.legal-contact-box, +.language-copy, +.language-switcher-copy { + min-width: 0; + max-width: 100%; +} + .hero-copy-main { display: block; } @@ -209,11 +237,29 @@ hr { .legal-intro, .project-description, .story-copy p, -.detail-panel p { +.story-detail p, +.detail-panel p, +.legal-contact-box p { color: var(--text-muted); line-height: 1.75; } +.hero-intro, +.legal-intro, +.project-description, +.story-copy p, +.story-detail p, +.detail-panel p, +.legal-contact-box p, +.legal-provider-name, +.project-link, +.project-language-chip, +.portrait-caption, +.portrait-click-hint, +.inline-link { + overflow-wrap: anywhere; +} + .hero-intro { max-width: 34rem; margin: var(--space-4) 0 0; @@ -239,6 +285,7 @@ hr { display: grid; justify-items: center; gap: 0.45rem; + width: min(100%, 22rem); } .hero-contact-title { @@ -252,7 +299,12 @@ hr { } .hero-contact-area { + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(0, 1fr); gap: 0.6rem; + width: 100%; + justify-items: center; } #contact-links { @@ -260,20 +312,25 @@ hr { } .hero-contact-area a { - width: 2.8rem; - height: 2.8rem; + width: min(100%, 2.8rem); + height: auto; + aspect-ratio: 1; } .action-link { display: inline-flex; align-items: center; justify-content: center; + max-width: 100%; min-height: 3rem; padding: 0 1.25rem; border-radius: 999px; border: 1px solid var(--border-soft); font: inherit; color: inherit; + line-height: 1.35; + overflow-wrap: anywhere; + white-space: normal; background: rgba(255, 255, 255, 0.04); cursor: pointer; transition: @@ -444,6 +501,7 @@ hr { position: absolute; right: 1.1rem; bottom: 1.1rem; + max-width: calc(100% - 2.2rem); padding: 0.5rem 0.9rem; border-radius: 0.65rem; background: #ffffff; @@ -457,6 +515,7 @@ hr { position: absolute; left: 1.1rem; top: 1.1rem; + max-width: calc(100% - 2.2rem); padding: 0.5rem 0.9rem; border-radius: 0.65rem; background: #ffffff; @@ -543,11 +602,15 @@ hr { align-items: center; gap: var(--space-2); width: fit-content; + max-width: 100%; border: 0; border-radius: 999px; padding: 0.9rem 1.1rem; font: inherit; color: var(--text-primary); + line-height: 1.35; + overflow-wrap: anywhere; + white-space: normal; background: rgba(255, 255, 255, 0.06); cursor: pointer; transition: @@ -624,17 +687,22 @@ hr { } .contact-area { - display: flex; - flex-wrap: wrap; + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(0, 1fr); gap: var(--space-3); + width: 100%; + justify-items: center; } .contact-area a { display: inline-flex; align-items: center; justify-content: center; - width: 3.3rem; - height: 3.3rem; + width: min(100%, 3.3rem); + height: auto; + aspect-ratio: 1; + min-width: 0; border-radius: 50%; background: rgba(255, 255, 255, 0.06); border: 1px solid var(--border-soft); @@ -1094,10 +1162,6 @@ hr { backdrop-filter: none; } - .panel { - background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.025)), rgba(12, 12, 16, 0.88); - } - .project-entry, .about-scroll-anim-enabled .story-copy, .about-scroll-anim-enabled .story-detail { @@ -1192,20 +1256,24 @@ hr { place-items: center; padding: 1.25rem; background: - radial-gradient(circle at top, rgba(239, 191, 157, 0.16), transparent 28%), - linear-gradient(180deg, rgba(4, 4, 6, 0.7), rgba(7, 7, 10, 0.9)); - backdrop-filter: blur(14px); + radial-gradient(circle at 50% 18%, rgba(217, 162, 140, 0.2), transparent 24%), + linear-gradient(180deg, rgba(3, 3, 5, 0.78), rgba(7, 7, 10, 0.94)); + backdrop-filter: blur(18px); } .language-card { position: relative; display: grid; - gap: 1rem; - width: min(100%, 520px); - padding: clamp(1rem, 4vw, 1.35rem); + gap: 1.15rem; + width: min(100%, 500px); + padding: clamp(1.1rem, 4vw, 1.45rem); background: - radial-gradient(circle at top left, rgba(239, 191, 157, 0.16), transparent 26%), - linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02)), rgba(10, 10, 14, 0.62); + radial-gradient(circle at top left, rgba(217, 162, 140, 0.18), transparent 28%), + linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02)), rgba(11, 10, 14, 0.82); + border: 1px solid rgba(239, 191, 157, 0.16); + box-shadow: + 0 24px 60px rgba(0, 0, 0, 0.32), + inset 0 1px 0 rgba(255, 255, 255, 0.04); } .language-card-header { @@ -1217,7 +1285,7 @@ hr { .language-copy { display: grid; - gap: 0; + gap: 0.18rem; padding-right: 3.4rem; } @@ -1228,29 +1296,30 @@ hr { .language-copy h2 { font-size: clamp(1.85rem, 4vw, 2.5rem); - line-height: 1; + line-height: 0.98; letter-spacing: -0.04em; + text-wrap: balance; } .language-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0.75rem; + gap: 0.85rem; } .language-option { position: relative; display: grid; - gap: 0.45rem; - justify-items: center; + gap: 0.55rem; + justify-items: start; align-content: center; - min-height: 8.5rem; - padding: 1rem 0.9rem; - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 20px; - background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.03)), rgba(10, 10, 14, 0.34); + min-height: 7.4rem; + padding: 1rem 1rem 1.05rem; + border: 1px solid rgba(255, 255, 255, 0.09); + border-radius: 22px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0.018)), rgba(14, 13, 18, 0.72); color: inherit; - text-align: center; + text-align: left; cursor: pointer; transition: transform var(--transition-fast), @@ -1261,23 +1330,23 @@ hr { .language-option:hover { transform: translateY(-2px); - border-color: rgba(255, 255, 255, 0.28); + border-color: rgba(239, 191, 157, 0.24); box-shadow: - 0 0 0 1px rgba(255, 255, 255, 0.1), - 0 14px 28px rgba(0, 0, 0, 0.16); + 0 0 0 1px rgba(239, 191, 157, 0.08), + 0 16px 30px rgba(0, 0, 0, 0.2); } .language-option-active { - border-color: rgba(239, 191, 157, 0.64); - background: linear-gradient(180deg, rgba(239, 191, 157, 0.14), rgba(255, 255, 255, 0.05)), rgba(10, 10, 14, 0.48); + border-color: rgba(239, 191, 157, 0.5); + background: linear-gradient(180deg, rgba(217, 162, 140, 0.16), rgba(255, 255, 255, 0.03)), rgba(17, 14, 18, 0.84); box-shadow: - 0 0 0 1px rgba(239, 191, 157, 0.22), - 0 18px 34px rgba(0, 0, 0, 0.2); + 0 0 0 1px rgba(239, 191, 157, 0.18), + 0 18px 34px rgba(0, 0, 0, 0.22); } .language-option strong { - font-size: 1.32rem; - line-height: 1.08; + font-size: 1.18rem; + line-height: 1.04; letter-spacing: -0.03em; } @@ -1297,8 +1366,8 @@ hr { .language-option-accent { color: var(--accent-strong); - background: rgba(239, 191, 157, 0.12); - border: 1px solid rgba(239, 191, 157, 0.24); + background: rgba(239, 191, 157, 0.1); + border: 1px solid rgba(239, 191, 157, 0.18); } .language-close { @@ -1306,14 +1375,14 @@ hr { top: 1rem; right: 1rem; z-index: 1; - width: 2.8rem; - height: 2.8rem; - border: 1px solid var(--border-soft); + width: 2.5rem; + height: 2.5rem; + border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 999px; - background: rgba(255, 255, 255, 0.04); + background: rgba(255, 255, 255, 0.03); color: var(--text-primary); font: inherit; - font-size: 1.4rem; + font-size: 1.25rem; cursor: pointer; transition: transform var(--transition-fast), @@ -1323,8 +1392,8 @@ hr { .language-close:hover { transform: translateY(-2px); - border-color: rgba(255, 255, 255, 0.42); - background: rgba(255, 255, 255, 0.08); + border-color: rgba(239, 191, 157, 0.28); + background: rgba(239, 191, 157, 0.08); } .language-switcher { @@ -1334,32 +1403,43 @@ hr { z-index: 2000; display: inline-flex; align-items: center; - gap: 0.7rem; - min-height: 3.5rem; - padding: 0.55rem 0.8rem 0.55rem 0.55rem; - border: 1px solid rgba(255, 255, 255, 0.22); + gap: 0.6rem; + min-height: 3.15rem; + padding: 0.42rem 0.78rem 0.42rem 0.42rem; + border: 1px solid rgba(239, 191, 157, 0.18); border-radius: 999px; - background: linear-gradient(180deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.04)), rgba(12, 12, 16, 0.82); + background: + radial-gradient(circle at top left, rgba(217, 162, 140, 0.15), transparent 58%), + linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.018)), rgba(12, 12, 16, 0.86); color: var(--text-primary); - box-shadow: var(--shadow-soft); - backdrop-filter: blur(18px); + box-shadow: + 0 14px 32px rgba(5, 5, 8, 0.24), + inset 0 1px 0 rgba(255, 255, 255, 0.04); + backdrop-filter: blur(14px); cursor: pointer; transition: transform var(--transition-fast), border-color var(--transition-fast), - background var(--transition-fast); + background var(--transition-fast), + box-shadow var(--transition-fast); } .language-switcher:hover { transform: translateY(-2px); - border-color: rgba(255, 255, 255, 0.52); - background: linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.05)), rgba(18, 18, 24, 0.9); + border-color: rgba(239, 191, 157, 0.34); + background: + radial-gradient(circle at top left, rgba(217, 162, 140, 0.2), transparent 58%), + linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.022)), rgba(16, 15, 20, 0.92); + box-shadow: + 0 18px 38px rgba(5, 5, 8, 0.28), + inset 0 1px 0 rgba(255, 255, 255, 0.05); } .language-switcher-tag { - min-width: 2.3rem; - color: #111214; - background: #f7f1ea; + min-width: 2.15rem; + color: #1a1514; + background: linear-gradient(180deg, #f0c2a7, #d9a28c); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3); } .language-switcher-copy { @@ -1369,14 +1449,16 @@ hr { } .language-switcher-copy strong { - font-size: 0.78rem; + font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase; + color: rgba(247, 241, 234, 0.78); } .language-switcher-copy span { - color: var(--text-muted); - font-size: 0.9rem; + color: var(--text-primary); + font-size: 0.86rem; + font-weight: 600; } .app-content[aria-hidden='true'] { @@ -1386,7 +1468,7 @@ hr { @media (max-width: 700px) { .language-card { - width: min(100%, 460px); + width: min(100%, 440px); gap: 0.9rem; padding: 0.95rem; padding-top: 1.15rem; @@ -1405,30 +1487,30 @@ hr { } .language-option { - min-height: auto; + min-height: 5.5rem; } .language-switcher { right: 0.75rem; top: 0.75rem; gap: 0.45rem; - min-height: 2.75rem; - padding: 0.4rem 0.65rem 0.4rem 0.4rem; + min-height: 2.6rem; + padding: 0.34rem 0.58rem 0.34rem 0.34rem; } .language-switcher-tag { - min-width: 1.95rem; - min-height: 1.65rem; - padding: 0.14rem 0.55rem; + min-width: 1.85rem; + min-height: 1.58rem; + padding: 0.12rem 0.5rem; font-size: 0.68rem; } .language-switcher-copy strong { - font-size: 0.68rem; + font-size: 0.62rem; letter-spacing: 0.1em; } .language-switcher-copy span { - font-size: 0.78rem; + font-size: 0.74rem; } }