diff --git a/libs/feature/project-list/src/lib/customizable-project-item.component.html b/libs/feature/project-list/src/lib/customizable-project-item.component.html
new file mode 100644
index 0000000..0ce20b1
--- /dev/null
+++ b/libs/feature/project-list/src/lib/customizable-project-item.component.html
@@ -0,0 +1,46 @@
+
+ @if (customizationStateService.isPanelShown()) {
+
+
+
+ Normal
+ Top
+
+
+
+
+
+ Full
+ Compact
+
+
+
+ }
+
+
+
diff --git a/libs/feature/project-list/src/lib/customizable-project-item.component.scss b/libs/feature/project-list/src/lib/customizable-project-item.component.scss
new file mode 100644
index 0000000..4113225
--- /dev/null
+++ b/libs/feature/project-list/src/lib/customizable-project-item.component.scss
@@ -0,0 +1,37 @@
+:host {
+ display: block;
+}
+
+.customizable-project-item-container {
+ position: relative;
+}
+
+.customization-panel {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 10;
+ display: inline-flex;
+ align-items: center;
+ flex-wrap: nowrap;
+ gap: 0.5rem;
+ padding: 0 0.5rem;
+ border-radius: 100vh; // pill shape
+ background-color: white;
+ box-shadow: 0 2px 8px rgb(0 0 0 / 0.2);
+ white-space: nowrap;
+ opacity: 0;
+ pointer-events: none;
+}
+
+:host(:hover) .customization-panel {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.top-project-group,
+.compact-group {
+ display: inline-flex;
+ align-items: center;
+ flex-wrap: nowrap;
+}
diff --git a/libs/feature/project-list/src/lib/customizable-project-item.component.spec.ts b/libs/feature/project-list/src/lib/customizable-project-item.component.spec.ts
new file mode 100644
index 0000000..3217a16
--- /dev/null
+++ b/libs/feature/project-list/src/lib/customizable-project-item.component.spec.ts
@@ -0,0 +1,142 @@
+import { signal } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { provideRouter } from '@angular/router';
+import { CustomizationStateService } from '@portfolio/customization-state';
+import { Project } from '@portfolio/search-engine-domain';
+
+import { CustomizableProjectItemComponent } from './customizable-project-item.component';
+import { ProjectItemComponent } from './project-item.component';
+
+describe('CustomizableProjectItemComponent', () => {
+ let component: CustomizableProjectItemComponent;
+ let fixture: ComponentFixture;
+
+ const mockProject: Project = {
+ id: 'test-project',
+ title: 'Test Project',
+ projectType: 'Web Application',
+ compactDescription: 'A test project for unit testing',
+ keyAchievements: 'Successfully implemented testing',
+ fullDescription: 'This is a full description of the test project',
+ features: ['Feature 1', 'Feature 2'],
+ highlights: ['Highlight 1', 'Highlight 2'],
+ technologies: {
+ fullMatches: ['Angular', 'TypeScript'],
+ partialMatches: [],
+ nonMatches: [],
+ },
+ role: 'Developer',
+ team: 'Development Team',
+ from: new Date(Date.UTC(2024, 0)),
+ fromText: '01/2024',
+ to: new Date(Date.UTC(2024, 11)),
+ toText: '12/2024',
+ duration: { years: 1 },
+ durationText: '1 year',
+ location: 'Remote',
+ workMode: 'Remote',
+ company: 'Test Company',
+ industry: 'Technology',
+ teamSize: 3,
+ engagementType: 'Client',
+ commercialContext: 'Paid',
+ usageScope: 'Public',
+ maturity: 'Production',
+ };
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [CustomizableProjectItemComponent],
+ providers: [
+ provideRouter([]),
+ {
+ provide: CustomizationStateService,
+ useValue: {
+ isPanelShown: signal(true),
+ },
+ },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(CustomizableProjectItemComponent);
+ component = fixture.componentInstance;
+ fixture.componentRef.setInput('project', mockProject);
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should pass default values 1:1 to wrapped project item', () => {
+ const wrapped = fixture.debugElement.query(
+ By.directive(ProjectItemComponent)
+ ).componentInstance as ProjectItemComponent;
+
+ expect(wrapped.project()).toEqual(mockProject);
+ expect(wrapped.isTopProject()).toBe(false);
+ expect(wrapped.compact()).toBe(false);
+ expect(wrapped.printMode()).toBe(false);
+ });
+
+ it('should pass all custom values 1:1 to wrapped project item', () => {
+ fixture.componentRef.setInput('isTopProject', true);
+ fixture.componentRef.setInput('compact', true);
+ fixture.componentRef.setInput('printMode', true);
+ fixture.detectChanges();
+
+ const wrapped = fixture.debugElement.query(
+ By.directive(ProjectItemComponent)
+ ).componentInstance as ProjectItemComponent;
+
+ expect(wrapped.isTopProject()).toBe(true);
+ expect(wrapped.compact()).toBe(true);
+ expect(wrapped.printMode()).toBe(true);
+ });
+
+ it('should override initial isTopProject and compact values through panel actions', () => {
+ fixture.componentRef.setInput('isTopProject', false);
+ fixture.componentRef.setInput('compact', false);
+ fixture.detectChanges();
+
+ clickTopProjectButton(true);
+ fixture.detectChanges();
+
+ clickCompactButton(true);
+ fixture.detectChanges();
+
+ const wrapped = fixture.debugElement.query(
+ By.directive(ProjectItemComponent)
+ ).componentInstance as ProjectItemComponent;
+
+ expect(wrapped.isTopProject()).toBe(true);
+ expect(wrapped.compact()).toBe(true);
+ });
+
+ function getTopProjectButton(value: boolean) {
+ return fixture.debugElement.query(
+ By.css(`mat-button-toggle[data-top-project="${value}"]`)
+ );
+ }
+
+ function clickTopProjectButton(value: boolean) {
+ const toggleButtonElement = getTopProjectButton(
+ value
+ ).nativeElement.querySelector('button') as HTMLButtonElement;
+ toggleButtonElement.click();
+ }
+
+ function getCompactButton(value: boolean) {
+ return fixture.debugElement.query(
+ By.css(`mat-button-toggle[data-compact="${value}"]`)
+ );
+ }
+
+ function clickCompactButton(value: boolean) {
+ const toggleButtonElement = getCompactButton(
+ value
+ ).nativeElement.querySelector('button') as HTMLButtonElement;
+ toggleButtonElement.click();
+ }
+});
diff --git a/libs/feature/project-list/src/lib/customizable-project-item.component.stories.ts b/libs/feature/project-list/src/lib/customizable-project-item.component.stories.ts
new file mode 100644
index 0000000..36a0639
--- /dev/null
+++ b/libs/feature/project-list/src/lib/customizable-project-item.component.stories.ts
@@ -0,0 +1,120 @@
+import { signal } from '@angular/core';
+import { provideRouter } from '@angular/router';
+import { CustomizationStateService } from '@portfolio/customization-state';
+import { Project } from '@portfolio/search-engine-domain';
+import type { Meta, StoryObj } from '@storybook/angular';
+import {
+ applicationConfig,
+ componentWrapperDecorator,
+ moduleMetadata,
+} from '@storybook/angular';
+
+import { CustomizableProjectItemComponent } from './customizable-project-item.component';
+
+const meta: Meta = {
+ title: 'Feature/Customizable Project Item',
+ component: CustomizableProjectItemComponent,
+ tags: ['autodocs'],
+ decorators: [
+ applicationConfig({
+ providers: [provideRouter([])],
+ }),
+ moduleMetadata({
+ providers: [
+ {
+ provide: CustomizationStateService,
+ useValue: {
+ isPanelShown: signal(true),
+ },
+ },
+ ],
+ }),
+ componentWrapperDecorator(
+ story =>
+ ``
+ ),
+ ],
+ argTypes: {
+ project: {
+ control: 'object',
+ table: {
+ type: { summary: 'Project' },
+ },
+ },
+ isTopProject: { control: 'boolean' },
+ compact: { control: 'boolean' },
+ printMode: { control: 'boolean' },
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+const mockProject: Project = {
+ title: 'Portfolio Website Development',
+ projectType: 'Web Application',
+ compactDescription:
+ 'Modern Angular portfolio showcasing professional experience and skills.',
+ keyAchievements:
+ 'Built responsive design with TypeScript and implemented advanced filtering.',
+ fullDescription:
+ 'Developed a comprehensive portfolio website using Angular 18 with a focus on modern web standards, accessibility, and performance. The project features a component-based architecture with reusable UI elements and implements advanced filtering capabilities for project browsing.',
+ features: [
+ 'Responsive design with Angular Material',
+ 'TypeScript implementation',
+ 'Component-based architecture',
+ 'Advanced project filtering',
+ 'SEO optimization',
+ 'Performance monitoring',
+ ],
+ highlights: [
+ 'Achieved 95+ Lighthouse performance score',
+ 'Implemented accessibility standards (WCAG 2.1)',
+ 'Built reusable component library',
+ 'Integrated automated testing suite',
+ ],
+ technologies: {
+ fullMatches: ['Angular', 'TypeScript'],
+ partialMatches: ['Jest', 'SCSS', 'RxJS'],
+ nonMatches: ['Nx', 'Mono Repo', 'Angular Material'],
+ },
+ role: 'Full-Stack Developer',
+ team: 'Solo Project',
+ fromText: '01/2024',
+ toText: 'Present',
+ durationText: '2+ years',
+ location: 'Berlin, Germany',
+ workMode: 'Remote',
+ company: 'Personal Project',
+ industry: 'Technology',
+} as Partial as Project;
+
+export const Default: Story = {
+ args: {
+ project: mockProject,
+ isTopProject: false,
+ compact: false,
+ printMode: false,
+ },
+};
+
+export const AsTopProject: Story = {
+ args: {
+ project: mockProject,
+ isTopProject: true,
+ printMode: false,
+ },
+};
+
+export const Compact: Story = {
+ args: {
+ project: mockProject,
+ isTopProject: false,
+ compact: true,
+ printMode: false,
+ },
+};
diff --git a/libs/feature/project-list/src/lib/customizable-project-item.component.ts b/libs/feature/project-list/src/lib/customizable-project-item.component.ts
new file mode 100644
index 0000000..db97f19
--- /dev/null
+++ b/libs/feature/project-list/src/lib/customizable-project-item.component.ts
@@ -0,0 +1,42 @@
+import { CommonModule } from '@angular/common';
+import { Component, computed, inject, input, signal } from '@angular/core';
+import { MatButtonToggleModule } from '@angular/material/button-toggle';
+import { CustomizationStateService } from '@portfolio/customization-state';
+import { Project } from '@portfolio/search-engine-domain';
+
+import { ProjectItemComponent } from './project-item.component';
+
+@Component({
+ selector: 'lib-customizable-project-item',
+ imports: [CommonModule, MatButtonToggleModule, ProjectItemComponent],
+ templateUrl: './customizable-project-item.component.html',
+ styleUrl: './customizable-project-item.component.scss',
+})
+export class CustomizableProjectItemComponent {
+ protected readonly customizationStateService = inject(
+ CustomizationStateService
+ );
+
+ project = input.required();
+ isTopProject = input(false);
+ compact = input(false);
+ printMode = input(false);
+
+ private readonly customIsTopProject = signal(null);
+ private readonly customCompact = signal(null);
+
+ readonly effectiveIsTopProject = computed(
+ () => this.customIsTopProject() ?? this.isTopProject()
+ );
+ readonly effectiveCompact = computed(
+ () => this.customCompact() ?? this.compact()
+ );
+
+ protected setIsTopProject(isTopProject: boolean): void {
+ this.customIsTopProject.set(isTopProject);
+ }
+
+ protected setCompact(compact: boolean): void {
+ this.customCompact.set(compact);
+ }
+}
diff --git a/libs/feature/project-list/src/lib/project-list.component.html b/libs/feature/project-list/src/lib/project-list.component.html
index e6342c9..3eb9adf 100644
--- a/libs/feature/project-list/src/lib/project-list.component.html
+++ b/libs/feature/project-list/src/lib/project-list.component.html
@@ -15,7 +15,7 @@
/>
} } @else { @for (project of filteredTopProjects(projectsOrder$ | async);
track project.id) {
-
} } @else { @for (project of filteredOtherProjects(projectsOrder$ | async);
track project.id) {
-