Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/hero/portrait-colored-352.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/hero/portrait-colored-528.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 6 additions & 7 deletions public/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
"pageTitles": {
"notFound": "Seite nicht gefunden | KamyCoding",
"legalNotice": "Impressum | KamyCoding",
"privacyPolicy": "Datenschutzerklärung | KamyCoding",
"projectDetail": "Projekt | KamyCoding"
"privacyPolicy": "Datenschutzerklärung | KamyCoding"
},
"nav": {
"about": "Über mich",
Expand Down Expand Up @@ -83,19 +82,19 @@
"join": {
"description": "Ein vom Kanban-System inspirierter Task-Manager. Aufgaben per Drag-and-drop erstellen und organisieren sowie Personen und Kategorien zuweisen.",
"imageAlt": "Der Task-Manager Join auf einem Laptop",
"implementation": "Kurzer Text über meine Rolle oder den Workflow in diesem Projekt. Er zeigt, wie ich mein Wissen strukturiert und selbstständig oder im Team eingesetzt habe.",
"implementation": "Ich habe die Aufgabenverwaltung mit Angular, TypeScript und Firebase umgesetzt. Aufgaben lassen sich erstellen, per Drag-and-drop organisieren sowie Personen und Kategorien zuweisen.",
"duration": "5 Wochen"
},
"elPolloLoco": {
"description": "Ein objektorientiertes Jump-and-Run-Spiel. Hilf Pepe, Münzen und Tabasco-Salsa zu sammeln, um gegen das verrückte Huhn anzutreten.",
"imageAlt": "Wüstenszene aus El Pollo Loco mit Pepe",
"implementation": "Kurzer Text über meine Rolle oder den Workflow in diesem Projekt. Er zeigt, wie ich mein Wissen strukturiert und selbstständig oder im Team eingesetzt habe.",
"implementation": "Ich habe das objektorientierte Spiel mit JavaScript, HTML und CSS umgesetzt. Die Spielmechanik umfasst Laufen, Springen, Werfen, das Sammeln von Münzen und Salsa sowie den Kampf gegen das finale Huhn.",
"duration": "3 Wochen"
},
"dabubble": {
"description": "Dieser Slack-Klon erleichtert die Kommunikation und Zusammenarbeit im Team mit einer intuitiven Oberfläche, Echtzeitnachrichten und übersichtlichen Channels.",
"imageAlt": "Team-Messaging-Oberfläche von DABubble",
"implementation": "Kurzer Text über meine Rolle oder den Workflow in diesem Projekt. Er zeigt, wie ich mein Wissen strukturiert und selbstständig oder im Team eingesetzt habe.",
"implementation": "Ich habe die Messaging-Oberfläche mit JavaScript, HTML und CSS umgesetzt, mit Fokus auf Echtzeitunterhaltungen und eine übersichtliche Channel-Struktur.",
"duration": "4 Wochen"
}
}
Expand Down Expand Up @@ -125,8 +124,8 @@
"role": "Frontend Developer"
},
"mariaSchaefer": {
"quote": "Die Zusammenarbeit mit Kamyar war eine große Freude. Er versteht es, Teammitglieder zu motivieren und zu ermutigen, ihre bestmögliche Arbeit zu zeigen, und bringt stets neue Impulse in das Brainstorming ein. Für das Wohlbefinden der Gruppe hatte er immer ein offenes Ohr, war hilfsbereit und brachte außerdem viel Humor mit.",
"role": "Frontend Developer"
"quote": "Kamyar hat meine Portfolio-Website umgesetzt und den gesamten Prozess sehr professionell und sorgfältig begleitet. Er war geduldig, präzise und immer darauf fokussiert, die passende Lösung für meine Wünsche zu finden. Ich kann die Zusammenarbeit mit ihm auf jeden Fall weiterempfehlen.",
"role": "Product Designer"
},
"davidBraun": {
"quote": "Kamyar war bei DA ein hervorragender Teamkollege. Sein positiver Einsatz und seine Bereitschaft, Verantwortung zu übernehmen, haben wesentlich dazu beigetragen, dass wir unsere Ziele erreicht haben.",
Expand Down
13 changes: 6 additions & 7 deletions public/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
"pageTitles": {
"notFound": "Page Not Found | KamyCoding",
"legalNotice": "Legal Notice | KamyCoding",
"privacyPolicy": "Privacy Policy | KamyCoding",
"projectDetail": "Project | KamyCoding"
"privacyPolicy": "Privacy Policy | KamyCoding"
},
"nav": {
"about": "About me",
Expand Down Expand Up @@ -83,19 +82,19 @@
"join": {
"description": "Task manager inspired by the Kanban System. Create and organize tasks using drag and drop functions, assign users and categories.",
"imageAlt": "Join task manager displayed on a laptop",
"implementation": "Short text that describes your role or the workflow for this specific project. Let a recruiter know more about your knowledge and ability to work independently or collaboratively in a structured way.",
"implementation": "I implemented the task-management interface with Angular, TypeScript, and Firebase. Tasks can be created, organized with drag and drop, and assigned to users and categories.",
"duration": "5 weeks"
},
"elPolloLoco": {
"description": "Jump, run and throw game based on an object-oriented approach. Help Pepe find coins and tabasco salsa to fight the crazy hen.",
"imageAlt": "El Pollo Loco desert game scene with Pepe",
"implementation": "Short text that describes your role or the workflow for this specific project. Let a recruiter know more about your knowledge and ability to work independently or collaboratively in a structured way.",
"implementation": "I implemented the object-oriented game with JavaScript, HTML, and CSS. Its mechanics include running, jumping, throwing, collecting coins and salsa, and fighting the final hen.",
"duration": "3 weeks"
},
"dabubble": {
"description": "This Slack clone transforms team communication and collaboration with an intuitive interface, real-time messaging, and clear channel organization.",
"imageAlt": "DABubble team messaging interface",
"implementation": "Short text that describes your role or the workflow for this specific project. Let a recruiter know more about your knowledge and ability to work independently or collaboratively in a structured way.",
"implementation": "I built the messaging interface with JavaScript, HTML, and CSS, focusing on real-time conversations and clear channel organization.",
"duration": "4 weeks"
}
}
Expand Down Expand Up @@ -125,8 +124,8 @@
"role": "Frontend Developer"
},
"mariaSchaefer": {
"quote": "It was a great pleasure to work with Kamyar. He knows how to push and encourage team members to present the best work possible, always adding something to brainstorm. Regarding the well-being of group members, he was always present and available to listen and help others, with a great sense of humor as well.",
"role": "Frontend Developer"
"quote": "Kamyar built my portfolio website and handled the whole process with great care and professionalism. He was patient, precise, and always focused on finding the right solution for what I needed. I’d definitely recommend working with him to anyone who values both a smooth process and a great final result.",
"role": "Product Designer"
},
"davidBraun": {
"quote": "Kamyar was a top team colleague at DA. His positive commitment and willingness to take on responsibility made a significant contribution to us achieving our goals.",
Expand Down
1 change: 0 additions & 1 deletion src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export const routes: Routes = [
path: 'projects/:slug',
component: ProjectDetail,
canActivate: [projectDetailGuard],
data: { titleKey: 'pageTitles.projectDetail' },
},
{
path: '**',
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/projects/projects.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const PORTFOLIO_PROJECTS = [
durationKey: 'projects.items.elPolloLoco.duration',
technologies: [TECHNOLOGIES.javascript, TECHNOLOGIES.html, TECHNOLOGIES.css],
githubUrl: 'https://github.com/kamycoding/El-Pollo-Loco',
liveUrl: 'https://kamycoding.github.io/El-Pollo-Loco/',
liveUrl: 'https://elpolloloco.kamycoding.com',
variant: 'standard',
},
{
Expand Down
6 changes: 3 additions & 3 deletions src/app/features/testimonials/testimonials.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export const TESTIMONIALS = [
paperVariant: 'paper-01',
},
{
id: 'maria-schaefer',
id: 'sogand-asari',
quoteKey: 'testimonials.items.mariaSchaefer.quote',
name: 'Maria Schäfer',
name: 'Sogand Asari',
roleKey: 'testimonials.items.mariaSchaefer.role',
linkedinUrl: 'https://www.linkedin.com/',
linkedinUrl: 'https://www.linkedin.com/in/sogandasari/',
paperVariant: 'paper-02',
},
{
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/testimonials/testimonials.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type TestimonialId = 'tobias-lange' | 'maria-schaefer' | 'david-braun';
export type TestimonialId = 'tobias-lange' | 'sogand-asari' | 'david-braun';

export type TestimonialPaperVariant = 'paper-01' | 'paper-02';

Expand Down
2 changes: 1 addition & 1 deletion src/app/layout/footer/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="site-footer__inner">
<div class="site-footer__brand">
<a routerLink="/" [attr.aria-label]="'footer.homeAria' | translate">
<img src="/logo.svg" alt="" draggable="false" />
<img src="/logo.svg" width="36" height="40" alt="" draggable="false" />
</a>
<p>© Kamyar Zamanfar {{ currentYear }}</p>
</div>
Expand Down
13 changes: 10 additions & 3 deletions src/app/layout/header/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
[attr.aria-label]="'nav.homeAria' | translate"
(click)="onLogoClick()"
>
<img class="size-8" src="/logo.svg" alt="" draggable="false" />
<img class="size-8" src="/logo.svg" width="36" height="40" alt="" draggable="false" />
</a>

<!-- Menu button -->
Expand Down Expand Up @@ -59,7 +59,14 @@
[attr.aria-label]="'nav.homeAria' | translate"
(click)="onLogoClick()"
>
<img class="h-10.5 w-auto" src="/logo.svg" alt="" draggable="false" />
<img
class="h-10.5 w-auto"
src="/logo.svg"
width="36"
height="40"
alt=""
draggable="false"
/>
</a>

<!-- Navigation -->
Expand Down Expand Up @@ -170,7 +177,7 @@
[attr.aria-label]="'nav.homeAria' | translate"
(click)="onLogoClick()"
>
<img class="size-8" src="/logo.svg" alt="" draggable="false" />
<img class="size-8" src="/logo.svg" width="36" height="40" alt="" draggable="false" />
</a>

<button
Expand Down
5 changes: 5 additions & 0 deletions src/app/pages/home/sections/contact/contact.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
>
<label for="contact-message">{{ 'contact.fields.message.label' | translate }}</label>
<textarea
#messageTextarea
id="contact-message"
formControlName="message"
maxlength="2000"
Expand Down Expand Up @@ -124,6 +125,8 @@
src="/assets/contact-footer/contact-arrow.svg"
alt=""
aria-hidden="true"
width="280"
height="44"
draggable="false"
/>
<label class="contact-consent__label" for="contact-privacy">
Expand All @@ -150,6 +153,8 @@
src="/assets/contact-footer/contact-arrow.svg"
alt=""
aria-hidden="true"
width="280"
height="44"
draggable="false"
/>
<button
Expand Down
4 changes: 4 additions & 0 deletions src/app/pages/home/sections/contact/contact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ describe('Contact', () => {

it('submits once and resets only after a confirmed success', () => {
fillValidForm();
const message = getControl<HTMLTextAreaElement>('#contact-message');
message.style.height = '120px';
submitForm();

const requests = httpTesting.match('/api/contact');
Expand All @@ -117,6 +119,8 @@ describe('Contact', () => {
fixture.detectChanges();

expect(getControl<HTMLInputElement>('#contact-name').value).toBe('');
expect(message.value).toBe('');
expect(message.style.height).toBe('');
expect(fixture.nativeElement.textContent).toContain('Thank you! Your message has been sent.');
});

Expand Down
8 changes: 7 additions & 1 deletion src/app/pages/home/sections/contact/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const trimmedMinLength = (minimumLength: number): ValidatorFn => {
})
export class Contact {
@ViewChild('formElement') private formElement?: ElementRef<HTMLFormElement>;
@ViewChild('messageTextarea') private messageTextarea?: ElementRef<HTMLTextAreaElement>;

private readonly formBuilder = inject(NonNullableFormBuilder);
private readonly contactService = inject(ContactService);
Expand Down Expand Up @@ -102,7 +103,7 @@ export class Contact {
const textarea = event.currentTarget as HTMLTextAreaElement;

if (!textarea.value) {
textarea.style.height = '';
this.resetMessageHeight(textarea);
return;
}

Expand Down Expand Up @@ -151,6 +152,7 @@ export class Contact {
this.submissionState.set('success');
this.responseMessage.set('contact.status.success');
this.contactForm.reset();
this.resetMessageHeight();
this.submitAttempted.set(false);
},
error: () => this.handleSubmissionError(),
Expand Down Expand Up @@ -179,6 +181,10 @@ export class Contact {
});
}

private resetMessageHeight(textarea = this.messageTextarea?.nativeElement): void {
textarea?.style.removeProperty('height');
}

private handleSubmissionError(): void {
this.submissionState.set('server-error');
this.responseMessage.set('contact.status.error');
Expand Down
20 changes: 12 additions & 8 deletions src/app/pages/home/sections/hero/hero.css
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,6 @@
opacity: 0;
}

.hero__polaroid:hover .hero__portrait-image--default {
opacity: 0;
}

.hero__polaroid:hover .hero__portrait-image--hover {
opacity: 1;
}

.hero__polaroid figcaption {
display: flex;
flex: 1;
Expand All @@ -180,6 +172,18 @@
}

@media (min-width: 64rem) {
.hero__portrait-image--hover {
background: url('/assets/hero/portrait-hover.webp') center 22% / cover no-repeat;
}

.hero__polaroid:hover .hero__portrait-image--initial {
opacity: 0;
}

.hero__polaroid:hover .hero__portrait-image--hover {
opacity: 1;
}

.hero__stage {
--hero-desktop-content-width: 1440px;
padding: 80px clamp(32px, 5vw, 72px) 48px;
Expand Down
35 changes: 23 additions & 12 deletions src/app/pages/home/sections/hero/hero.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<section id="hero" class="hero" aria-labelledby="hero-title">
<img
class="hero__background"
src="/assets/hero/hero-background.webp"
src="/assets/hero/hero-background-optimized.webp"
width="1440"
height="900"
alt=""
Expand Down Expand Up @@ -58,19 +58,30 @@ <h1 id="hero-title" class="hero__title" [attr.aria-label]="'hero.roleAria' | tra

<figure class="hero__polaroid">
<div class="hero__portrait">
<img
class="hero__portrait-image hero__portrait-image--default"
src="/assets/hero/portrait-default.webp"
[alt]="'hero.portraitAlt' | translate"
draggable="false"
/>
<img
<picture>
<source
media="(max-width: 63.999rem)"
srcset="
/assets/hero/portrait-colored-352.webp 352w,
/assets/hero/portrait-colored-528.webp 528w
"
sizes="176px"
/>
<img
class="hero__portrait-image hero__portrait-image--initial"
src="/assets/hero/portrait-default.webp"
width="1023"
height="1537"
loading="eager"
fetchpriority="high"
[alt]="'hero.portraitAlt' | translate"
draggable="false"
/>
</picture>
<span
class="hero__portrait-image hero__portrait-image--hover"
src="/assets/hero/portrait-hover.webp"
alt=""
aria-hidden="true"
draggable="false"
/>
></span>
</div>
<figcaption>
<span class="hero__caption-default">Kamy :)</span>
Expand Down
8 changes: 8 additions & 0 deletions src/app/pages/home/sections/projects/projects.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ <h2 id="projects-title" class="text-5xl leading-none font-bold lg:text-[4rem]">
class="projects__image block size-full"
[src]="project.imageSrc"
[alt]="project.imageAltKey | translate"
width="560"
height="399"
loading="lazy"
decoding="async"
draggable="false"
/>

Expand All @@ -52,6 +56,10 @@ <h2 id="projects-title" class="text-5xl leading-none font-bold lg:text-[4rem]">
[src]="project.badgeSrc"
alt=""
aria-hidden="true"
width="168"
height="169"
loading="lazy"
decoding="async"
draggable="false"
/>
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/home/sections/testimonials/testimonials.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<header class="ml-auto w-fit max-w-full text-right">
<div class="flex items-end justify-end gap-2">
<img
class="h-9 w-25 shrink-0 object-fill"
class="decorative-reveal h-9 w-25 shrink-0 object-fill"
src="/assets/testimonials/testimonials-title-arrow.png"
alt=""
aria-hidden="true"
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/project-detail/project-detail.css
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@
.project-detail__brand-stamp {
position: absolute;
top: 50%;
left: -60px;
inset-inline-end: -60px;
width: 120px;
height: 120px;
transform: translateY(-50%);
Expand Down
Loading