diff --git a/src/app/app.html b/src/app/app.html
index 53afa24..7591de4 100644
--- a/src/app/app.html
+++ b/src/app/app.html
@@ -1,13 +1,12 @@
{{ shell().skipLink }}
-
-
+
diff --git a/src/app/app.scss b/src/app/app.scss
index b1f2811..bffcff9 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -16,7 +16,7 @@
.site-header {
position: relative;
top: 0;
- z-index: 10;
+ z-index: 30;
padding-block: var(--space-3);
}
@@ -199,6 +199,7 @@
.primary-nav > li {
position: relative;
+ z-index: 31;
}
.primary-nav > li > a {
@@ -211,20 +212,24 @@
top: 100%;
left: 0;
min-width: max-content;
- z-index: 20;
- padding: var(--space-2) var(--space-3);
+ z-index: 32;
+ padding: var(--space-3) var(--space-4);
gap: var(--space-1);
border-radius: var(--radius-md);
- background: var(--surface-glass);
- backdrop-filter: blur(var(--blur-glass));
- -webkit-backdrop-filter: blur(var(--blur-glass));
+ background: var(--color-surface-menu);
border: 1px solid var(--surface-glass-border);
- box-shadow: var(--shadow-soft);
+ box-shadow: var(--shadow-raised);
}
- .primary-nav > li:hover > .submenu,
.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 ccebb65..4573d46 100644
--- a/src/app/app.spec.ts
+++ b/src/app/app.spec.ts
@@ -89,16 +89,19 @@ describe('App', () => {
expect(compiled.querySelector('footer')).toBeTruthy();
});
- it('keeps the palette trigger in the header and the dialog outside .site', async () => {
+ it('renders the terminal dock outside .site and keeps the header free of a CV link', async () => {
const fixture = TestBed.createComponent(App);
await fixture.whenStable();
const compiled = fixture.nativeElement as HTMLElement;
const site = compiled.querySelector('.site');
- const trigger = compiled.querySelector('header .command-palette-trigger');
+ const dock = compiled.querySelector('app-terminal-dock');
+ const trigger = compiled.querySelector('.terminal-dock-trigger');
expect(site).toBeTruthy();
- expect(trigger).toBeTruthy();
- expect(site?.contains(trigger)).toBe(true);
+ expect(dock).toBeTruthy();
+ expect(site?.contains(dock)).toBe(false);
+ expect(compiled.querySelector('header a[href="/cv/CV.pdf"]')).toBeNull();
+ expect(compiled.querySelector('header .command-palette-trigger')).toBeNull();
(trigger as HTMLButtonElement).click();
fixture.detectChanges();
@@ -106,10 +109,10 @@ describe('App', () => {
TestBed.inject(ApplicationRef).tick();
fixture.detectChanges();
- const dialog = compiled.querySelector('[role="dialog"]');
- expect(dialog).toBeTruthy();
- expect(site?.contains(dialog)).toBe(false);
- expect(site?.hasAttribute('inert')).toBe(true);
+ const panel = compiled.querySelector('.terminal-dock-panel');
+ expect(panel).toBeTruthy();
+ expect(site?.contains(panel)).toBe(false);
+ expect(site?.hasAttribute('inert')).toBe(false);
});
it('keeps the server-rendered nav expanded and collapses after hydration on a narrow viewport', async () => {
diff --git a/src/app/app.ts b/src/app/app.ts
index f2c039b..98c0eea 100644
--- a/src/app/app.ts
+++ b/src/app/app.ts
@@ -18,9 +18,7 @@ import { LOCALE_HTML_LANG, otherLocale } from './core/i18n/locale';
import { LocaleService } from './core/i18n/locale.service';
import { NavigationService } from './core/navigation/navigation.service';
import { isBrowserPlatform, viewportMatches } from './core/platform/browser';
-import { CommandPalette } from './shared/command-palette/command-palette';
-import { CommandPaletteTrigger } from './shared/command-palette/command-palette-trigger/command-palette-trigger';
-import { CommandPaletteService } from './shared/command-palette/command-palette.service';
+import { TerminalDock } from './shared/terminal/terminal-dock';
/** Matches `md` in `src/_breakpoints.scss` (48rem). */
export const WIDE_NAV_QUERY = '(min-width: 48rem)';
@@ -28,21 +26,13 @@ export const WIDE_NAV_QUERY = '(min-width: 48rem)';
@Component({
selector: 'app-root',
changeDetection: ChangeDetectionStrategy.OnPush,
- imports: [
- RouterOutlet,
- RouterLink,
- RouterLinkActive,
- DotBackground,
- CommandPalette,
- CommandPaletteTrigger,
- ],
+ imports: [RouterOutlet, RouterLink, RouterLinkActive, DotBackground, TerminalDock],
templateUrl: './app.html',
styleUrl: './app.scss',
})
export class App {
protected readonly navigation = inject(NavigationService);
protected readonly localeService = inject(LocaleService);
- protected readonly palette = inject(CommandPaletteService);
private readonly router = inject(Router);
private readonly injector = inject(Injector);
private readonly document = inject(DOCUMENT);
diff --git a/src/app/core/content/shell-copy.ts b/src/app/core/content/shell-copy.ts
index 3bfd4bc..c3054d8 100644
--- a/src/app/core/content/shell-copy.ts
+++ b/src/app/core/content/shell-copy.ts
@@ -8,7 +8,6 @@ export interface ShellCopy {
readonly menuClose: string;
readonly languageSwitch: string;
readonly otherLocaleName: string;
- readonly cvLabel: string;
readonly contactCta: string;
}
@@ -21,7 +20,6 @@ export const SHELL_COPY: Record = {
menuClose: 'Menü schließen',
languageSwitch: 'Zur englischen Version wechseln',
otherLocaleName: 'English',
- cvLabel: 'Lebenslauf als PDF',
contactCta: 'Kontakt',
},
en: {
@@ -32,7 +30,6 @@ export const SHELL_COPY: Record = {
menuClose: 'Close menu',
languageSwitch: 'Switch to the German version',
otherLocaleName: 'Deutsch',
- cvLabel: 'Curriculum vitae as PDF',
contactCta: 'Contact',
},
};
diff --git a/src/app/core/navigation/navigation.service.spec.ts b/src/app/core/navigation/navigation.service.spec.ts
index ad6f997..41bb37a 100644
--- a/src/app/core/navigation/navigation.service.spec.ts
+++ b/src/app/core/navigation/navigation.service.spec.ts
@@ -5,6 +5,7 @@ import { routes } from '../../app.routes';
import { SITE_CONTENT } from '../content/content.token';
import { SITE_CONTENT_DATA } from '../content/site-content';
import { LocaleService } from '../i18n/locale.service';
+import { PRIMARY_NAV } from './navigation';
import { NavigationService } from './navigation.service';
describe('NavigationService', () => {
@@ -40,4 +41,11 @@ describe('NavigationService', () => {
expect(navigation.link('home')).toEqual(['/', 'en']);
expect(navigation.link('contact', 'de')).toEqual(['/', 'kontakt']);
});
+
+ it('keeps primary nav to home, services, projects, about and contact', () => {
+ const ids = PRIMARY_NAV.map((item) => item.routeId);
+ expect(ids).toEqual(['home', 'services', 'projects', 'about', 'contact']);
+ expect(ids).not.toContain('stack');
+ expect(ids).not.toContain('pitch');
+ });
});
diff --git a/src/app/core/navigation/navigation.ts b/src/app/core/navigation/navigation.ts
index 91560c0..0561f03 100644
--- a/src/app/core/navigation/navigation.ts
+++ b/src/app/core/navigation/navigation.ts
@@ -38,10 +38,6 @@ export const PRIMARY_NAV: readonly NavItem[] = [
routeId: 'projects',
label: { de: 'Projekte', en: 'Projects' },
},
- {
- routeId: 'stack',
- label: { de: 'Stack', en: 'Stack' },
- },
{
routeId: 'about',
label: { de: 'Über mich', en: 'About' },
diff --git a/src/app/submenu.styles.spec.ts b/src/app/submenu.styles.spec.ts
new file mode 100644
index 0000000..6ab7768
--- /dev/null
+++ b/src/app/submenu.styles.spec.ts
@@ -0,0 +1,15 @@
+import { readFileSync } from 'node:fs';
+import { join } from 'node:path';
+
+const APP_SCSS = readFileSync(join(process.cwd(), 'src/app/app.scss'), 'utf8');
+
+describe('primary nav submenu surface', () => {
+ it('uses the opaque menu token and not the glass surface', () => {
+ const submenu = APP_SCSS.slice(APP_SCSS.indexOf('.primary-nav > li > .submenu'));
+
+ expect(submenu).toMatch(/background:\s*var\(--color-surface-menu\)/);
+ expect(submenu).not.toMatch(/var\(--surface-glass\)/);
+ expect(submenu).toMatch(/box-shadow:/);
+ expect(submenu).toMatch(/border-radius:/);
+ });
+});
diff --git a/src/styles.scss b/src/styles.scss
index 9b09d24..cb018a0 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -5,6 +5,7 @@
--color-surface-raised: #12121a;
--color-surface-overlay: #1a1a24;
--color-surface-muted: #22222e;
+ --color-surface-menu: #16161f;
/* Color — accent ramp */
--color-accent: #6366f1;