feat/brand-logo #50

Merged
JoeKung merged 7 commits from feat/brand-logo into dev 2026-03-20 13:17:04 +01:00
15 changed files with 84 additions and 59 deletions
Showing only changes of commit 997e770256 - Show all commits

View File

@@ -1,6 +1,6 @@
<router-outlet></router-outlet>
@if (siteIntroState() !== 'hidden') {
@if (siteIntroState() !== "hidden") {
<div
class="site-intro"
[class.site-intro--closing]="siteIntroState() === 'closing'"

View File

@@ -54,7 +54,9 @@ export function parseAcceptLanguage(
}
const qualityParam = params.find((param) => param.startsWith('q='));
const quality = qualityParam ? Number.parseFloat(qualityParam.slice(2)) : 1;
const quality = qualityParam
? Number.parseFloat(qualityParam.slice(2))
: 1;
return {
tag: rawTag,
quality: Number.isFinite(quality) ? quality : 0,
@@ -70,7 +72,9 @@ export function parseAcceptLanguage(
index: number;
} => entry !== null && entry.quality > 0,
)
.sort((left, right) => right.quality - left.quality || left.index - right.index)
.sort(
(left, right) => right.quality - left.quality || left.index - right.index,
)
.map((entry) => entry.tag);
}
@@ -102,9 +106,7 @@ function resolveExplicitLanguageFromUrl(
const normalizedUrl = String(url ?? '/');
const [pathAndQuery] = normalizedUrl.split('#', 1);
const [rawPath, rawQuery] = pathAndQuery.split('?', 2);
const firstSegment = rawPath
.split('/')
.filter(Boolean)[0];
const firstSegment = rawPath.split('/').filter(Boolean)[0];
const pathLanguage = normalizeSupportedLanguage(firstSegment);
if (pathLanguage) {
return pathLanguage;

View File

@@ -14,7 +14,10 @@ type SupportedLang = 'it' | 'en' | 'de' | 'fr';
const FALLBACK_LANG: SupportedLang = 'it';
const translationCache = new Map<SupportedLang, Promise<TranslationObject>>();
const translationLoaders: Record<SupportedLang, () => Promise<TranslationObject>> = {
const translationLoaders: Record<
SupportedLang,
() => Promise<TranslationObject>
> = {
it: () =>
import('../../../assets/i18n/it.json').then(
(module) => module.default as TranslationObject,
@@ -51,8 +54,9 @@ export class StaticTranslateLoader implements TranslateLoader {
}
private loadTranslation(lang: SupportedLang): Promise<TranslationObject> {
const transferStateKey =
makeStateKey<TranslationObject>(`i18n:${lang.toLowerCase()}`);
const transferStateKey = makeStateKey<TranslationObject>(
`i18n:${lang.toLowerCase()}`,
);
if (
isPlatformBrowser(this.platformId) &&
this.transferState.hasKey(transferStateKey)

View File

@@ -10,5 +10,4 @@ import { FooterComponent } from './footer.component';
templateUrl: './layout.component.html',
styleUrl: './layout.component.scss',
})
export class LayoutComponent {
}
export class LayoutComponent {}

View File

@@ -1,4 +1,4 @@
import {CommonModule, NgOptimizedImage} from '@angular/common';
import { CommonModule, NgOptimizedImage } from '@angular/common';
import {
afterNextRender,
Component,
@@ -30,7 +30,13 @@ import {
@Component({
selector: 'app-navbar',
standalone: true,
imports: [CommonModule, RouterLink, RouterLinkActive, TranslateModule, NgOptimizedImage],
imports: [
CommonModule,
RouterLink,
RouterLinkActive,
TranslateModule,
NgOptimizedImage,
],
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss'],
})

View File

@@ -67,7 +67,10 @@ export class LanguageService {
}
const currentTree = this.router.parseUrl(this.router.url);
const localizedRoute = this.resolveLocalizedRouteOverride(currentTree, lang);
const localizedRoute = this.resolveLocalizedRouteOverride(
currentTree,
lang,
);
if (localizedRoute) {
this.navigateToLocalizedRoute(currentTree, localizedRoute);
return;
@@ -220,7 +223,9 @@ export class LanguageService {
return null;
}
const currentPath = this.getCleanPath(this.router.serializeUrl(currentTree));
const currentPath = this.getCleanPath(
this.router.serializeUrl(currentTree),
);
const paths = Object.values(overrides)
.map((path) => this.normalizeLocalizedRoutePath(path))
.filter((path): path is string => !!path);

View File

@@ -360,18 +360,23 @@ export class SeoService {
}, {});
}
private normalizeAlternatePaths(paths: SeoMap | null | undefined): SeoMap | null {
private normalizeAlternatePaths(
paths: SeoMap | null | undefined,
): SeoMap | null {
if (!paths) {
return null;
}
const normalized = this.supportedLangs.reduce<SeoMap>((accumulator, lang) => {
const path = this.normalizeSeoPath(paths[lang]);
if (path) {
accumulator[lang] = path;
}
return accumulator;
}, {});
const normalized = this.supportedLangs.reduce<SeoMap>(
(accumulator, lang) => {
const path = this.normalizeSeoPath(paths[lang]);
if (path) {
accumulator[lang] = path;
}
return accumulator;
},
{},
);
return Object.keys(normalized).length > 0 ? normalized : null;
}
@@ -445,7 +450,10 @@ export class SeoService {
if (!path) {
continue;
}
this.appendAlternateLink(this.seoLocaleByLang[alt], this.toAbsoluteUrl(path));
this.appendAlternateLink(
this.seoLocaleByLang[alt],
this.toAbsoluteUrl(path),
);
}
if (xDefaultPath) {
this.appendAlternateLink('x-default', this.toAbsoluteUrl(xDefaultPath));

View File

@@ -18,10 +18,7 @@
</button>
</div>
<div
class="animation-stage"
[attr.data-variant]="variant()"
>
<div class="animation-stage" [attr.data-variant]="variant()">
<app-brand-animation-logo
[variant]="variant()"
[decorative]="false"

View File

@@ -287,7 +287,7 @@ export class ProductDetailComponent {
this.shopService.resolveMediaUrl(image.hero) ??
this.shopService.resolveMediaUrl(image.card) ??
this.shopService.resolveMediaUrl(image.thumb)
);
);
}
private scheduleCartWarmup(): void {
@@ -774,9 +774,12 @@ export class ProductDetailComponent {
const targetPath =
product.localizedPaths?.[lang] ??
`/${lang}/shop/p/${this.shopRouteService.productPathSegment(product)}`;
const normalizedTargetPath =
targetPath.startsWith('/') ? targetPath : `/${targetPath}`;
const currentPath = this.router.serializeUrl(currentTree).split(/[?#]/, 1)[0];
const normalizedTargetPath = targetPath.startsWith('/')
? targetPath
: `/${targetPath}`;
const currentPath = this.router
.serializeUrl(currentTree)
.split(/[?#]/, 1)[0];
if (currentPath === normalizedTargetPath) {
return;
}

View File

@@ -268,7 +268,9 @@ describe('ShopService', () => {
});
catalogRequest.flush(buildCatalog());
httpMock.expectNone('http://localhost:8000/api/shop/products/desk-cable-clip');
httpMock.expectNone(
'http://localhost:8000/api/shop/products/desk-cable-clip',
);
expect(errorResponse?.status).toBe(404);
});
@@ -291,7 +293,9 @@ describe('ShopService', () => {
});
catalogRequest.flush(buildCatalog());
httpMock.expectNone('http://localhost:8000/api/shop/products/desk-cable-clip');
httpMock.expectNone(
'http://localhost:8000/api/shop/products/desk-cable-clip',
);
expect(errorResponse?.status).toBe(404);
});
});

View File

@@ -292,8 +292,9 @@ export class ShopService {
return this.getProductCatalog().pipe(
map((catalog) =>
catalog.products.find((product) =>
this.normalizePublicPath(product.publicPath) === normalizedPath,
catalog.products.find(
(product) =>
this.normalizePublicPath(product.publicPath) === normalizedPath,
),
),
switchMap((product) => {

View File

@@ -29,14 +29,14 @@
will-change: transform;
}
.brand-animation[data-variant='site-intro'] .brand-animation__letter {
animation: site-intro-preview var(--brand-animation-site-intro-duration, 1s) linear 1 forwards;
.brand-animation[data-variant="site-intro"] .brand-animation__letter {
animation: site-intro-preview var(--brand-animation-site-intro-duration, 1s)
linear 1 forwards;
}
.brand-animation[data-variant='calculator-loader'] .brand-animation__letter {
.brand-animation[data-variant="calculator-loader"] .brand-animation__letter {
animation: calculator-loader-loop
var(--brand-animation-loader-loop-duration, 2.65s)
infinite;
var(--brand-animation-loader-loop-duration, 2.65s) infinite;
}
@keyframes site-intro-preview {
@@ -75,8 +75,7 @@
var(--three-anchor-x) * var(--word-scale) * var(--word-spacing-factor)
)
)
scaleX(var(--loader-group-scale-x))
scaleY(var(--loader-group-scale-y));
scaleX(var(--loader-group-scale-x)) scaleY(var(--loader-group-scale-y));
}
12% {
@@ -87,8 +86,7 @@
var(--three-anchor-x) * var(--word-scale) * var(--word-spacing-factor)
)
)
scaleX(var(--loader-group-scale-x))
scaleY(var(--loader-group-scale-y));
scaleX(var(--loader-group-scale-x)) scaleY(var(--loader-group-scale-y));
}
12% {
@@ -118,8 +116,7 @@
var(--bee-anchor-x) * var(--word-scale) * var(--word-spacing-factor)
)
)
scaleX(var(--loader-group-scale-x))
scaleY(var(--loader-group-scale-y));
scaleX(var(--loader-group-scale-x)) scaleY(var(--loader-group-scale-y));
}
88% {
@@ -131,12 +128,11 @@
transform: translate(-50%, -50%)
translateX(
calc(
(var(--bee-anchor-x) + var(--loader-exit-shift)) *
var(--word-scale) * var(--word-spacing-factor)
(var(--bee-anchor-x) + var(--loader-exit-shift)) * var(--word-scale) *
var(--word-spacing-factor)
)
)
scaleX(0.98)
scaleY(1.02);
scaleX(0.98) scaleY(1.02);
}
94.01%,
@@ -148,8 +144,7 @@
var(--three-anchor-x) * var(--word-scale) * var(--word-spacing-factor)
)
)
scaleX(var(--loader-group-scale-x))
scaleY(var(--loader-group-scale-y));
scaleX(var(--loader-group-scale-x)) scaleY(var(--loader-group-scale-y));
}
}

View File

@@ -62,8 +62,7 @@ export class BrandAnimationLogoComponent {
readonly resolvedLetters = computed<ResolvedAnimationLetter[]>(() =>
LETTERS.map((letter) => ({
key: letter.key,
src:
this.variant() === 'site-intro' ? letter.yellowSrc : letter.darkSrc,
src: this.variant() === 'site-intro' ? letter.yellowSrc : letter.darkSrc,
wordX: letter.wordX,
})),
);

View File

@@ -20,9 +20,9 @@ describe('server routing redirects', () => {
});
it('redirects legacy shop product aliases to the canonical product route', () => {
expect(resolvePublicRedirectTarget('/shop/accessories/desk-cable-clip')).toBe(
'/it/shop/p/desk-cable-clip',
);
expect(
resolvePublicRedirectTarget('/shop/accessories/desk-cable-clip'),
).toBe('/it/shop/p/desk-cable-clip');
expect(
resolvePublicRedirectTarget('/de/shop/zubehor/schreibtisch-kabelhalter'),
).toBe('/de/shop/p/schreibtisch-kabelhalter');

View File

@@ -99,5 +99,7 @@ function splitSegments(pathname: string): string[] {
}
function looksLikeLangToken(segment: string | null | undefined): boolean {
return typeof segment === 'string' && /^[a-z]{2}(?:-[a-z]{2})?$/i.test(segment);
return (
typeof segment === 'string' && /^[a-z]{2}(?:-[a-z]{2})?$/i.test(segment)
);
}