fix(site): remove canvas frame, invert logos, new footer text

The canvas had a 1px frame. The company logos are opaque white, so
invert them to match the dark tiles. Core fetches the Auth public key
(JWKS), so the To Be Played diagram now shows that arrow.
This commit is contained in:
lobo committed 2026-10-14 08:49:00 +02:00
1 parent 93e9c59436
commit 07a9b8bc11
9 files changed
+84 -19

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@
</main> </main>
<footer class="border-t border-black/8 p-fluid-md text-center opacity-80 bg-app-bg"> <footer class="border-t border-black/8 p-fluid-md text-center opacity-80 bg-app-bg">
<small>© {{ currentYear }} Andreas Dahm - {{ `APP.COPYRIGHT` | translate }}</small> <small>© {{ currentYear }} Andreas Dahm. {{ 'APP.COPYRIGHT' | translate }}</small>
<br /> <br />
<small> <small>
{{ 'APP.AI_NOTICE' | translate }} {{ 'APP.AI_NOTICE' | translate }}
+38
View File
@@ -0,0 +1,38 @@
import { TestBed } from '@angular/core/testing';
import { Component, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideTranslateService } from '@ngx-translate/core';
import { AppComponent } from './app.component';
import { TopbarComponent } from '../topbar/topbar.component';
import { ParticleBackgroundComponent } from '../../shared/components/particles-background/particles-background.component';
import { SeoService } from '../../service/seo.service';
@Component({ selector: 'app-topbar', template: '' })
class TopbarStub {}
@Component({ selector: 'app-particles-background', template: '' })
class ParticlesStub {}
describe('AppComponent', () => {
it('renders the copyright line with year, name and a closing dot', async () => {
TestBed.overrideComponent(AppComponent, {
remove: { imports: [TopbarComponent, ParticleBackgroundComponent] },
add: { imports: [TopbarStub, ParticlesStub] },
});
await TestBed.configureTestingModule({
imports: [AppComponent],
providers: [
provideZonelessChangeDetection(),
provideRouter([]),
provideTranslateService({ lang: 'en', fallbackLang: 'en' }),
{ provide: SeoService, useValue: { init: vi.fn() } },
],
}).compileComponents();
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const text = fixture.nativeElement.querySelector('footer small').textContent.trim();
expect(text).toBe(`© ${new Date().getFullYear()} Andreas Dahm. APP.COPYRIGHT`);
});
});
+2 -1
View File
@@ -73,7 +73,8 @@
<div class="relative pl-14 pb-fluid-sm"> <div class="relative pl-14 pb-fluid-sm">
@if (isFirstRoleAtCompany(i)) { @if (isFirstRoleAtCompany(i)) {
<img src="{{entry.logo}}" alt="" aria-hidden="true" <img src="{{entry.logo}}" alt="" aria-hidden="true"
class="absolute left-0 top-0 w-10 h-10 object-contain opacity-90 rounded-[10%] bg-logo-bg" /> class="absolute left-0 top-0 w-10 h-10 object-contain opacity-90 rounded-[10%] bg-logo-bg"
[class.invert]="entry.invertLogo" />
<div class="font-semibold opacity-90 text-[clamp(0.9rem,2.2vw,1.05rem)] leading-10" data-testid="company"> <div class="font-semibold opacity-90 text-[clamp(0.9rem,2.2vw,1.05rem)] leading-10" data-testid="company">
{{ (entry.key + '.COMPANY') | translate }} {{ (entry.key + '.COMPANY') | translate }}
</div> </div>
+15 -1
View File
@@ -5,6 +5,7 @@ import { MatIconRegistry } from '@angular/material/icon';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { provideTranslateService } from '@ngx-translate/core'; import { provideTranslateService } from '@ngx-translate/core';
import { AboutComponent } from './about.component'; import { AboutComponent } from './about.component';
import { AssetsConstants } from '../../constants/AssetsConstants';
/** /**
* No translations are loaded, so the translate pipe echoes the key back. That is * No translations are loaded, so the translate pipe echoes the key back. That is
@@ -110,6 +111,16 @@ describe('AboutComponent', () => {
expect(textOf('[data-testid="role"]')).not.toContain('ABOUT.XP.COMPANY0.ROLE'); expect(textOf('[data-testid="role"]')).not.toContain('ABOUT.XP.COMPANY0.ROLE');
}); });
it('inverts the opaque white logos so they read as dark tiles', () => {
const inverted = fixture.debugElement
.queryAll(By.css('img.invert'))
.map(img => new URL(img.nativeElement.src, 'http://localhost').pathname);
const expected = [AssetsConstants.COLORDIGITAL_LOGO, AssetsConstants.TERAPORT_LOGO]
.map(src => new URL(src, 'http://localhost/').pathname);
expect(new Set(inverted)).toEqual(new Set(expected));
});
it('reveals the earlier positions when the toggle button is clicked', () => { it('reveals the earlier positions when the toggle button is clicked', () => {
click('toggle-experience'); click('toggle-experience');
@@ -166,11 +177,14 @@ describe('AboutComponent', () => {
describe('education', () => { describe('education', () => {
it('shows only the post-school entries until the toggle button is clicked', () => { it('shows only the post-school entries until the toggle button is clicked', () => {
expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E6.WHERE'); expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E6.WHERE');
expect(textOf('[data-testid="education-where"]')).not.toContain('ABOUT.EDUCATION.E0.WHERE'); expect(textOf('[data-testid="education-where"]')).not.toContain('ABOUT.EDUCATION.E2.WHERE');
expect(textOf('[data-testid="education-where"]').length).toBe(3);
click('toggle-education'); click('toggle-education');
expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E2.WHERE');
expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E0.WHERE'); expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E0.WHERE');
expect(textOf('[data-testid="education-where"]').length).toBe(7);
}); });
}); });
+10 -4
View File
@@ -17,6 +17,8 @@ interface ExperienceEntry {
company: string; company: string;
logo: string; logo: string;
highlightCount: number; highlightCount: number;
/** The source file is opaque white; inverting gives the same dark tile as the other logos. */
invertLogo?: boolean;
} }
interface SkillGroup { interface SkillGroup {
@@ -50,25 +52,29 @@ export class AboutComponent {
key: 'ABOUT.XP.COMPANY9', key: 'ABOUT.XP.COMPANY9',
company: 'colordigital', company: 'colordigital',
logo: AssetsConstants.COLORDIGITAL_LOGO, logo: AssetsConstants.COLORDIGITAL_LOGO,
highlightCount: 3 highlightCount: 3,
invertLogo: true
}, },
{ {
key: 'ABOUT.XP.COMPANY8', key: 'ABOUT.XP.COMPANY8',
company: 'teraport', company: 'teraport',
logo: AssetsConstants.TERAPORT_LOGO, logo: AssetsConstants.TERAPORT_LOGO,
highlightCount: 3 highlightCount: 3,
invertLogo: true
}, },
{ {
key: 'ABOUT.XP.COMPANY7', key: 'ABOUT.XP.COMPANY7',
company: 'colordigital-earlier', company: 'colordigital-earlier',
logo: AssetsConstants.COLORDIGITAL_LOGO, logo: AssetsConstants.COLORDIGITAL_LOGO,
highlightCount: 3 highlightCount: 3,
invertLogo: true
}, },
{ {
key: 'ABOUT.XP.COMPANY6', key: 'ABOUT.XP.COMPANY6',
company: 'colordigital-earlier', company: 'colordigital-earlier',
logo: AssetsConstants.COLORDIGITAL_LOGO, logo: AssetsConstants.COLORDIGITAL_LOGO,
highlightCount: 3 highlightCount: 3,
invertLogo: true
}, },
{ {
key: 'ABOUT.XP.COMPANY5', key: 'ABOUT.XP.COMPANY5',
@@ -7,3 +7,7 @@
z-index: -1; z-index: -1;
pointer-events: none; pointer-events: none;
} }
canvas {
box-shadow: none;
}
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"APP": { "APP": {
"TITLE": "Playground", "TITLE": "Playground",
"COPYRIGHT": "Bilder und Sourcecode sind urheberrechtlich geschützt, keine Nutzung ohne Zustimmung!", "COPYRIGHT": "Alle Rechte vorbehalten.",
"AI_NOTICE": "Entstanden mit einem KI-gestützten Engineering-Workflow.", "AI_NOTICE": "Entstanden mit einem KI-gestützten Engineering-Workflow.",
"AI_NOTICE_LINK": "Wie ich mit KI arbeite" "AI_NOTICE_LINK": "Wie ich mit KI arbeite"
}, },
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"APP": { "APP": {
"TITLE": "Playground", "TITLE": "Playground",
"COPYRIGHT": "Images and code protected by copyright, no use without permission!", "COPYRIGHT": "All rights reserved.",
"AI_NOTICE": "Built with an AI-assisted engineering workflow.", "AI_NOTICE": "Built with an AI-assisted engineering workflow.",
"AI_NOTICE_LINK": "How I build with AI" "AI_NOTICE_LINK": "How I build with AI"
}, },
@@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630" role="img" aria-labelledby="title desc"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630" role="img" aria-labelledby="title desc">
<title id="title">To Be Played</title> <title id="title">To Be Played</title>
<desc id="desc">Architecture of To Be Played: a React frontend talks to an Auth service that signs tokens and a Core service that verifies them. Both use PostgreSQL, Core also calls the Steam API and IGDB.</desc> <desc id="desc">Architecture of To Be Played: a React frontend talks to an Auth service that signs tokens and a Core service that verifies them. Both use PostgreSQL, Core also calls the Steam API and IGDB. Core fetches the Auth public key (JWKS) at runtime.</desc>
<defs> <defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
@@ -42,16 +42,16 @@
<text x="370" y="184" class="sans name">Auth service</text> <text x="370" y="184" class="sans name">Auth service</text>
<text x="370" y="210" class="sans sub">signs tokens, Ed25519 private key</text> <text x="370" y="210" class="sans sub">signs tokens, Ed25519 private key</text>
<text x="370" y="232" class="sans sub">HttpOnly cookies</text> <text x="370" y="232" class="sans sub">HttpOnly cookies</text>
<rect x="350" y="290" width="310" height="220" rx="10" fill="#212a35" stroke="#2f3a47"/> <rect x="350" y="320" width="310" height="220" rx="10" fill="#212a35" stroke="#2f3a47"/>
<text x="370" y="324" class="sans name">Core service</text> <text x="370" y="354" class="sans name">Core service</text>
<text x="370" y="350" class="sans sub">library, stats, suggestions</text> <text x="370" y="380" class="sans sub">library, stats, suggestions</text>
<text x="370" y="372" class="sans sub">verifies tokens, public key only</text> <text x="370" y="402" class="sans sub">verifies tokens, public key only</text>
<rect x="740" y="150" width="180" height="230" rx="14" fill="#1a222c" stroke="#2f3a47"/> <rect x="740" y="150" width="180" height="230" rx="14" fill="#1a222c" stroke="#2f3a47"/>
<text x="760" y="178" class="sans cap" text-anchor="start">POSTGRESQL</text> <text x="760" y="178" class="sans cap" text-anchor="start">POSTGRESQL</text>
<rect x="760" y="192" width="140" height="56" rx="10" fill="#243040" stroke="#33475c"/> <rect x="760" y="192" width="140" height="56" rx="10" fill="#243040" stroke="#33475c"/>
<text x="830" y="227" class="mono chip" text-anchor="middle">auth db</text> <text x="830" y="227" class="mono chip" text-anchor="middle">auth db</text>
<rect x="760" y="302" width="140" height="56" rx="10" fill="#243040" stroke="#33475c"/> <rect x="760" y="312" width="140" height="56" rx="10" fill="#243040" stroke="#33475c"/>
<text x="830" y="337" class="mono chip" text-anchor="middle">core db</text> <text x="830" y="347" class="mono chip" text-anchor="middle">core db</text>
<text x="1000" y="372" class="sans cap" text-anchor="start">EXTERNAL</text> <text x="1000" y="372" class="sans cap" text-anchor="start">EXTERNAL</text>
<rect x="1000" y="395" width="152" height="50" rx="10" fill="#212a35" stroke="#2f3a47" stroke-dasharray="4 4"/> <rect x="1000" y="395" width="152" height="50" rx="10" fill="#212a35" stroke="#2f3a47" stroke-dasharray="4 4"/>
<text x="1076" y="427" class="sans chip" text-anchor="middle">Steam API</text> <text x="1076" y="427" class="sans chip" text-anchor="middle">Steam API</text>
@@ -59,10 +59,12 @@
<text x="1076" y="492" class="sans chip" text-anchor="middle">IGDB</text> <text x="1076" y="492" class="sans chip" text-anchor="middle">IGDB</text>
<path d="M 252 258 H 342" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/> <path d="M 252 258 H 342" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<text x="297" y="248" class="sans arr" text-anchor="middle">login</text> <text x="297" y="248" class="sans arr" text-anchor="middle">login</text>
<path d="M 252 330 H 342" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/> <path d="M 252 340 H 342" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<text x="297" y="320" class="sans arr" text-anchor="middle">API, cookie</text> <text x="297" y="330" class="sans arr" text-anchor="middle">API, cookie</text>
<path d="M 668 220 H 752" stroke="#26c6da" stroke-width="2" fill="none" marker-end="url(#arrowCyan)"/> <path d="M 668 220 H 752" stroke="#26c6da" stroke-width="2" fill="none" marker-end="url(#arrowCyan)"/>
<path d="M 668 330 H 752" stroke="#26c6da" stroke-width="2" fill="none" marker-end="url(#arrowCyan)"/> <path d="M 668 340 H 752" stroke="#26c6da" stroke-width="2" fill="none" marker-end="url(#arrowCyan)"/>
<path d="M 600 312 V 278" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<text x="588" y="300" class="sans arr" text-anchor="end">public key (JWKS)</text>
<path d="M 668 420 H 992" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/> <path d="M 668 420 H 992" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<text x="830" y="410" class="sans arr" text-anchor="middle">sync</text> <text x="830" y="410" class="sans arr" text-anchor="middle">sync</text>
<path d="M 668 485 H 992" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/> <path d="M 668 485 H 992" stroke="#4a5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB