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> <router-outlet></router-outlet>
@if (siteIntroState() !== 'hidden') { @if (siteIntroState() !== "hidden") {
<div <div
class="site-intro" class="site-intro"
[class.site-intro--closing]="siteIntroState() === 'closing'" [class.site-intro--closing]="siteIntroState() === 'closing'"

View File

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

View File

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

View File

@@ -10,5 +10,4 @@ import { FooterComponent } from './footer.component';
templateUrl: './layout.component.html', templateUrl: './layout.component.html',
styleUrl: './layout.component.scss', 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 { import {
afterNextRender, afterNextRender,
Component, Component,
@@ -30,7 +30,13 @@ import {
@Component({ @Component({
selector: 'app-navbar', selector: 'app-navbar',
standalone: true, standalone: true,
imports: [CommonModule, RouterLink, RouterLinkActive, TranslateModule, NgOptimizedImage], imports: [
CommonModule,
RouterLink,
RouterLinkActive,
TranslateModule,
NgOptimizedImage,
],
templateUrl: './navbar.component.html', templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss'], styleUrls: ['./navbar.component.scss'],
}) })

View File

@@ -67,7 +67,10 @@ export class LanguageService {
} }
const currentTree = this.router.parseUrl(this.router.url); const currentTree = this.router.parseUrl(this.router.url);
const localizedRoute = this.resolveLocalizedRouteOverride(currentTree, lang); const localizedRoute = this.resolveLocalizedRouteOverride(
currentTree,
lang,
);
if (localizedRoute) { if (localizedRoute) {
this.navigateToLocalizedRoute(currentTree, localizedRoute); this.navigateToLocalizedRoute(currentTree, localizedRoute);
return; return;
@@ -220,7 +223,9 @@ export class LanguageService {
return null; return null;
} }
const currentPath = this.getCleanPath(this.router.serializeUrl(currentTree)); const currentPath = this.getCleanPath(
this.router.serializeUrl(currentTree),
);
const paths = Object.values(overrides) const paths = Object.values(overrides)
.map((path) => this.normalizeLocalizedRoutePath(path)) .map((path) => this.normalizeLocalizedRoutePath(path))
.filter((path): path is string => !!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) { if (!paths) {
return null; return null;
} }
const normalized = this.supportedLangs.reduce<SeoMap>((accumulator, lang) => { const normalized = this.supportedLangs.reduce<SeoMap>(
(accumulator, lang) => {
const path = this.normalizeSeoPath(paths[lang]); const path = this.normalizeSeoPath(paths[lang]);
if (path) { if (path) {
accumulator[lang] = path; accumulator[lang] = path;
} }
return accumulator; return accumulator;
}, {}); },
{},
);
return Object.keys(normalized).length > 0 ? normalized : null; return Object.keys(normalized).length > 0 ? normalized : null;
} }
@@ -445,7 +450,10 @@ export class SeoService {
if (!path) { if (!path) {
continue; continue;
} }
this.appendAlternateLink(this.seoLocaleByLang[alt], this.toAbsoluteUrl(path)); this.appendAlternateLink(
this.seoLocaleByLang[alt],
this.toAbsoluteUrl(path),
);
} }
if (xDefaultPath) { if (xDefaultPath) {
this.appendAlternateLink('x-default', this.toAbsoluteUrl(xDefaultPath)); this.appendAlternateLink('x-default', this.toAbsoluteUrl(xDefaultPath));

View File

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

View File

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

View File

@@ -268,7 +268,9 @@ describe('ShopService', () => {
}); });
catalogRequest.flush(buildCatalog()); 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); expect(errorResponse?.status).toBe(404);
}); });
@@ -291,7 +293,9 @@ describe('ShopService', () => {
}); });
catalogRequest.flush(buildCatalog()); 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); expect(errorResponse?.status).toBe(404);
}); });
}); });

View File

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

View File

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

View File

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

View File

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