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;
}
}