From 55d012cc10f0f654f1c507ccae366febb83d5146 Mon Sep 17 00:00:00 2001
From: Antonio Ledebuhr
Date: Thu, 27 Aug 2026 00:41:20 +0200
Subject: [PATCH] Open the systems-map gist dialog and turn the dock into a
default-open TTY.
Plain node activation no longer navigates, case cards stay quiet evidence rows, and the terminal keeps one top-to-bottom scrollport.
Co-authored-by: Cursor
---
src/app/app.html | 18 +-
src/app/app.scss | 64 +---
src/app/app.spec.ts | 7 +-
.../pages/skills/skill-card/skill-card.scss | 10 -
src/app/shared/case-card/case-card.html | 23 +-
src/app/shared/case-card/case-card.scss | 37 +--
src/app/shared/case-card/case-card.ts | 2 +
src/app/shared/case-study/case-study.html | 7 +
src/app/shared/case-study/case-study.scss | 16 +
.../contact-briefing/contact-briefing.html | 3 -
.../contact-briefing/contact-briefing.spec.ts | 5 +-
.../contact-briefing/contact-briefing.ts | 6 -
src/app/shared/systems-map/systems-map.html | 54 +++-
.../shared/systems-map/systems-map.model.ts | 235 ++++++++++-----
src/app/shared/systems-map/systems-map.scss | 34 ++-
.../shared/systems-map/systems-map.spec.ts | 175 ++++++++---
src/app/shared/systems-map/systems-map.ts | 185 +++++++++---
.../shared/terminal/terminal-commands.spec.ts | 23 +-
src/app/shared/terminal/terminal-commands.ts | 47 ++-
src/app/shared/terminal/terminal-dock.html | 82 ++---
src/app/shared/terminal/terminal-dock.scss | 22 +-
.../shared/terminal/terminal-dock.service.ts | 2 +-
src/app/shared/terminal/terminal-dock.spec.ts | 285 ++++++++++--------
src/app/shared/terminal/terminal-dock.ts | 91 ++++--
src/styles.scss | 26 ++
25 files changed, 915 insertions(+), 544 deletions(-)
diff --git a/src/app/app.html b/src/app/app.html
index 7591de4..72aae99 100644
--- a/src/app/app.html
+++ b/src/app/app.html
@@ -25,25 +25,10 @@
{{ item.label }}
- @if (item.children; as children) {
-
- }
}
@@ -57,7 +42,6 @@
>
{{ shell().otherLocaleName }}
- {{ shell().contactCta }}
diff --git a/src/app/app.scss b/src/app/app.scss
index bffcff9..17335bc 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -24,7 +24,7 @@
display: grid;
grid-template-columns: 1fr auto;
gap: var(--space-3);
- align-items: start;
+ align-items: center;
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-lg);
}
@@ -114,17 +114,6 @@
gap: var(--space-2);
}
-.primary-nav ul {
- display: flex;
- flex-direction: column;
- gap: var(--space-1);
- padding-inline-start: var(--space-4);
-}
-
-.contact-cta {
- color: var(--color-text);
-}
-
.is-active {
color: var(--color-accent-cool);
}
@@ -157,7 +146,8 @@
}
@include bp.respond-to(md) {
- .nav-toggle {
+ .nav-toggle,
+ .site-toolbar {
display: none;
}
@@ -166,28 +156,22 @@
}
.site-header-inner {
- grid-template-columns: 1fr auto auto;
+ grid-template-columns: auto 1fr auto;
align-items: center;
- gap: var(--space-2);
+ gap: var(--space-4);
padding-block: var(--space-2);
}
.site-nav,
- .nav-collapsed .site-nav {
- display: flex;
- grid-column: 1 / -1;
- order: 5;
- }
-
+ .nav-collapsed .site-nav,
.site-actions,
.nav-collapsed .site-actions {
display: flex;
grid-column: auto;
- order: 3;
}
- .site-toolbar {
- order: 2;
+ .site-nav {
+ justify-content: center;
}
.primary-nav {
@@ -197,39 +181,7 @@
gap: var(--space-3);
}
- .primary-nav > li {
- position: relative;
- z-index: 31;
- }
-
.primary-nav > li > a {
white-space: nowrap;
}
-
- .primary-nav > li > .submenu {
- display: none;
- position: absolute;
- top: 100%;
- left: 0;
- min-width: max-content;
- z-index: 32;
- padding: var(--space-3) var(--space-4);
- gap: var(--space-1);
- border-radius: var(--radius-md);
- background: var(--color-surface-menu);
- border: 1px solid var(--surface-glass-border);
- box-shadow: var(--shadow-raised);
- }
-
- .primary-nav > li:focus-within > .submenu {
- display: flex;
- flex-direction: column;
- }
-
- @media (hover: hover) and (pointer: fine) {
- .primary-nav > li:hover > .submenu {
- display: flex;
- flex-direction: column;
- }
- }
}
diff --git a/src/app/app.spec.ts b/src/app/app.spec.ts
index 4573d46..22dbfb6 100644
--- a/src/app/app.spec.ts
+++ b/src/app/app.spec.ts
@@ -103,14 +103,9 @@ describe('App', () => {
expect(compiled.querySelector('header a[href="/cv/CV.pdf"]')).toBeNull();
expect(compiled.querySelector('header .command-palette-trigger')).toBeNull();
- (trigger as HTMLButtonElement).click();
- fixture.detectChanges();
- await fixture.whenStable();
- TestBed.inject(ApplicationRef).tick();
- fixture.detectChanges();
-
const panel = compiled.querySelector('.terminal-dock-panel');
expect(panel).toBeTruthy();
+ expect((trigger as HTMLButtonElement).hasAttribute('hidden')).toBe(true);
expect(site?.contains(panel)).toBe(false);
expect(site?.hasAttribute('inert')).toBe(false);
});
diff --git a/src/app/components/pages/skills/skill-card/skill-card.scss b/src/app/components/pages/skills/skill-card/skill-card.scss
index cffb423..29df6bc 100644
--- a/src/app/components/pages/skills/skill-card/skill-card.scss
+++ b/src/app/components/pages/skills/skill-card/skill-card.scss
@@ -4,10 +4,6 @@
height: 100%;
display: flex;
flex-direction: column;
- transition:
- transform var(--duration-base) var(--ease-standard),
- box-shadow var(--duration-base) var(--ease-standard),
- border-color var(--duration-base) var(--ease-standard);
h3 {
margin: 0 0 var(--space-4);
@@ -43,12 +39,6 @@
}
@media (hover: hover) and (pointer: fine) {
- .card:hover {
- transform: translateY(-4px);
- box-shadow: var(--shadow-raised);
- border-color: var(--surface-glass-border-strong);
- }
-
.card .icons a:hover img {
filter: none;
transform: scale(1.1);
diff --git a/src/app/shared/case-card/case-card.html b/src/app/shared/case-card/case-card.html
index 37a2da3..5bd5eb4 100644
--- a/src/app/shared/case-card/case-card.html
+++ b/src/app/shared/case-card/case-card.html
@@ -1,14 +1,11 @@
-
- {{ caseStudy().headline }}
- {{ caseStudy().client }} · {{ caseStudy().role }} · {{ caseStudy().period }}
- {{ caseStudy().summary }}
-
- {{ caseStudy().linkLabel }}
-
+ {{ caseStudy().client }}
+ {{ caseStudy().role }} · {{ caseStudy().period }}
+ {{ caseStudy().teaser }}
+ {{ caseStudy().linkLabel }}
+
diff --git a/src/app/shared/case-card/case-card.scss b/src/app/shared/case-card/case-card.scss
index 780b1d6..cb88802 100644
--- a/src/app/shared/case-card/case-card.scss
+++ b/src/app/shared/case-card/case-card.scss
@@ -1,36 +1,31 @@
.case-card {
- padding: var(--space-5);
- border-radius: var(--radius-md);
- height: 100%;
+ display: grid;
+ gap: var(--space-2);
+ padding: var(--space-4);
+ border-radius: var(--radius-sm);
+ border-inline-start: 3px solid var(--color-accent);
+ background: var(--color-surface-raised);
+ color: inherit;
+ text-decoration: none;
}
h3 {
margin: 0;
- font-size: var(--text-lg);
+ font-size: var(--text-md);
font-weight: 600;
}
.meta,
-p {
+.teaser {
margin: 0;
color: var(--color-text-muted);
}
-.tags {
- list-style: none;
- margin: 0;
- padding: 0;
- color: var(--color-text-subtle);
+.teaser {
+ max-width: 40rem;
+}
+
+.case-card-link {
+ color: var(--color-accent-cool);
font-size: var(--text-sm);
}
-
-a {
- color: var(--color-accent-cool);
- text-decoration: none;
-}
-
-@media (hover: hover) and (pointer: fine) {
- a:hover {
- text-decoration: underline;
- }
-}
diff --git a/src/app/shared/case-card/case-card.ts b/src/app/shared/case-card/case-card.ts
index 525fbeb..7eb109e 100644
--- a/src/app/shared/case-card/case-card.ts
+++ b/src/app/shared/case-card/case-card.ts
@@ -12,8 +12,10 @@ import { NavigationService } from '../../core/navigation/navigation.service';
})
export class CaseCard {
readonly caseStudy = input.required();
+ readonly idPrefix = input('case-card');
private readonly navigation = inject(NavigationService);
protected readonly projectsLink = computed(() => this.navigation.link('projects'));
+ protected readonly headingId = computed(() => `${this.idPrefix()}-${this.caseStudy().id}`);
}
diff --git a/src/app/shared/case-study/case-study.html b/src/app/shared/case-study/case-study.html
index ba0ec35..0db185d 100644
--- a/src/app/shared/case-study/case-study.html
+++ b/src/app/shared/case-study/case-study.html
@@ -10,6 +10,13 @@
@if (caseStudy().transparencyNote; as note) {
{{ note }}
}
+ @if (caseStudy().externalUrl; as url) {
+
+
+ {{ caseStudy().externalLabel }}
+
+
+ }
diff --git a/src/app/shared/case-study/case-study.scss b/src/app/shared/case-study/case-study.scss
index 7786362..1f695df 100644
--- a/src/app/shared/case-study/case-study.scss
+++ b/src/app/shared/case-study/case-study.scss
@@ -48,3 +48,19 @@ ul {
font-size: var(--text-sm);
color: var(--color-text-subtle);
}
+
+.external-link {
+ color: var(--color-accent-cool);
+}
+
+.external-link::after {
+ content: '';
+ display: inline-block;
+ width: 0.65em;
+ height: 0.65em;
+ margin-inline-start: var(--space-1);
+ border-block-end: 1px solid currentColor;
+ border-inline-end: 1px solid currentColor;
+ transform: translateY(-0.15em) rotate(-45deg);
+ vertical-align: middle;
+}
diff --git a/src/app/shared/contact-briefing/contact-briefing.html b/src/app/shared/contact-briefing/contact-briefing.html
index 6668621..c04c94b 100644
--- a/src/app/shared/contact-briefing/contact-briefing.html
+++ b/src/app/shared/contact-briefing/contact-briefing.html
@@ -78,8 +78,5 @@
}
{{ copy().directEmailLabel }}
- @if (showCalendar()) {
- {{ copy().calendarLabel }}
- }
diff --git a/src/app/shared/contact-briefing/contact-briefing.spec.ts b/src/app/shared/contact-briefing/contact-briefing.spec.ts
index 0507669..faede33 100644
--- a/src/app/shared/contact-briefing/contact-briefing.spec.ts
+++ b/src/app/shared/contact-briefing/contact-briefing.spec.ts
@@ -46,12 +46,11 @@ describe('ContactBriefing', () => {
expect(disabledSubmit?.textContent).toContain(copy.submitLabel);
expect(root.querySelector('[aria-invalid="true"]')).toBeNull();
expect(root.querySelector(`a[href="${SITE_CONFIG.calendarUrl}"]`)).toBeNull();
- expect(root.textContent).not.toContain(copy.calendarLabel);
const special = 'Äpfel & Birnen?\nPreis=100';
setControl(root, 'contact-name', special);
setControl(root, 'contact-email', 'team@example.com');
- setControl(root, 'contact-projectType', 'software');
+ setControl(root, 'contact-projectType', 'software-data');
setControl(root, 'contact-situation', special);
setControl(root, 'contact-timeframe', 'Q3');
fixture.detectChanges();
@@ -79,7 +78,7 @@ describe('ContactBriefing', () => {
expect(decodedBody).toContain('team@example.com');
const projectType = copy.fields.find((field) => field.id === 'projectType');
const projectTypeLabel = projectType?.options?.find(
- (option) => option.value === 'software',
+ (option) => option.value === 'software-data',
)?.label;
expect(projectTypeLabel).toBeTruthy();
expect(decodedBody).toContain(`${projectType!.label}: ${projectTypeLabel}`);
diff --git a/src/app/shared/contact-briefing/contact-briefing.ts b/src/app/shared/contact-briefing/contact-briefing.ts
index 3c47087..0c8ee01 100644
--- a/src/app/shared/contact-briefing/contact-briefing.ts
+++ b/src/app/shared/contact-briefing/contact-briefing.ts
@@ -27,7 +27,6 @@ export class ContactBriefing {
protected readonly values = signal>({ ...EMPTY_VALUES });
protected readonly touched = signal>>({});
protected readonly contactEmail = SITE_CONFIG.contactEmail;
- protected readonly calendarUrl = SITE_CONFIG.calendarUrl;
protected readonly incompleteId = 'contact-incomplete';
protected readonly missingLabels = computed(() => {
@@ -56,11 +55,6 @@ export class ContactBriefing {
return `mailto:${SITE_CONFIG.contactEmail}?subject=${encodeURIComponent(copy.mailSubject)}&body=${encodeURIComponent(body)}`;
});
- protected readonly showCalendar = computed(() => {
- const url = SITE_CONFIG.calendarUrl;
- return typeof url === 'string' && url.length > 0;
- });
-
protected onInput(fieldId: ContactFieldId, event: Event): void {
const target = event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
this.values.update((current) => ({ ...current, [fieldId]: target.value }));
diff --git a/src/app/shared/systems-map/systems-map.html b/src/app/shared/systems-map/systems-map.html
index 115a48c..c82f3cd 100644
--- a/src/app/shared/systems-map/systems-map.html
+++ b/src/app/shared/systems-map/systems-map.html
@@ -5,15 +5,13 @@
{{ headingText() }}
}
{{ introText() }}
+ {{ copy().nodeLinkHint }}
-