text centralization

This commit is contained in:
Sascha 2025-11-03 16:58:40 +01:00
parent c83b6e07cf
commit 34b02f69be
8 changed files with 344 additions and 203 deletions

View File

@ -14,6 +14,7 @@ interface AboutSectionProps {
export default function AboutSection(props: AboutSectionProps = {}) { export default function AboutSection(props: AboutSectionProps = {}) {
const texts = getTexts().about; const texts = getTexts().about;
const accessibilityTexts = getTexts().accessibility;
// Use centralized texts as defaults, allow props to override // Use centralized texts as defaults, allow props to override
const { const {
@ -22,32 +23,12 @@ export default function AboutSection(props: AboutSectionProps = {}) {
subtitle = texts.subtitle, subtitle = texts.subtitle,
bio = texts.bio, bio = texts.bio,
profileImage = saschaImage, profileImage = saschaImage,
featureCards = [ featureCards = texts.featureCards.map((card, index) => ({
{ icon: [Code, Palette, ShieldCheck, Eye][index] || Code,
icon: Code, title: card.title,
title: "Web Development", description: card.description,
description: "Building modern web applications with Angular and React frameworks, leveraging TypeScript and JavaScript for robust, scalable solutions.", colorClass: ["primary", "secondary", "tertiary", "quaternary"][index] || "primary"
colorClass: "primary" }))
},
{
icon: Palette,
title: "Responsive Design",
description: "Creating pixel-perfect, responsive interfaces that work seamlessly across all devices and screen sizes.",
colorClass: "secondary"
},
{
icon: ShieldCheck,
title: "Cross-Browser Compatibility",
description: "Ensuring consistent user experiences across all major browsers with thorough testing and optimization.",
colorClass: "tertiary"
},
{
icon: Eye,
title: "Web Accessibility",
description: "Implementing accessibility best practices to deliver websites that support screen readers, keyboard navigation, and assistive technologies.",
colorClass: "quaternary"
}
]
} = props; } = props;
return ( return (
@ -65,8 +46,8 @@ export default function AboutSection(props: AboutSectionProps = {}) {
{/* Skip link for keyboard navigation */} {/* Skip link for keyboard navigation */}
<div className="about-section__skip"> <div className="about-section__skip">
<a href="#features" className="sr-only sr-only-focusable"> <a href="#services" className="sr-only sr-only-focusable">
Skip to features section {accessibilityTexts.skipLinks.skipToServices}
</a> </a>
</div> </div>

View File

@ -10,43 +10,18 @@ interface CertificationsSectionProps {
export default function CertificationsSection(props: CertificationsSectionProps = {}) { export default function CertificationsSection(props: CertificationsSectionProps = {}) {
const texts = getTexts().certifications; const texts = getTexts().certifications;
const accessibilityTexts = getTexts().accessibility;
// Use centralized texts as defaults, allow props to override // Use centralized texts as defaults, allow props to override
const { const {
title = texts.title, title = texts.title,
subtitle = texts.subtitle, subtitle = texts.subtitle,
certifications = [ certifications = texts.certificationItems.map((cert, index) => ({
{ name: cert.name,
name: "Practical Prompt Engineering Masterclass: Hands-On Learning", issuer: cert.issuer,
issuer: "Udemy Lecture - Asif Farooqi, Abdullah Dar", year: ["2025", "2025", "2020", "2020", "2015"][index] || "2025",
year: "2025",
icon: "" icon: ""
}, }))
{
name: "Understanding Typescript",
issuer: "Udemy Lecture - Maximilian Schwarzmüller",
year: "2025",
icon: ""
},
{
name: "Angular Step by Step",
issuer: "Udemy Lecture - Shivprasad Koirala",
year: "2020",
icon: ""
},
{
name: "Angular and Typescript",
issuer: "LinkedIn TestDome",
year: "2020",
icon: ""
},
{
name: "Bachelor of Science in Computer Science",
issuer: "TU Darmstadt",
year: "2015",
icon: ""
}
]
} = props; } = props;
// Sort certifications by year in descending order (newest first) // Sort certifications by year in descending order (newest first)
@ -70,7 +45,7 @@ export default function CertificationsSection(props: CertificationsSectionProps
{/* Skip link for keyboard navigation */} {/* Skip link for keyboard navigation */}
<div className="certifications-section__skip"> <div className="certifications-section__skip">
<a href="#contact" className="sr-only sr-only-focusable"> <a href="#contact" className="sr-only sr-only-focusable">
Skip to contact section {accessibilityTexts.skipLinks.skipToContact}
</a> </a>
</div> </div>

View File

@ -40,14 +40,14 @@ export default function ContactSection(props: ContactSectionProps = {}) {
// Enhanced email handler with better accessibility // Enhanced email handler with better accessibility
const handleEmailClick = () => { const handleEmailClick = () => {
const email = getEmailAddress(); const email = getEmailAddress();
const subject = encodeURIComponent("Portfolio Inquiry - Let's discuss your project"); const subject = encodeURIComponent(texts.emailSubject);
const body = encodeURIComponent("Hello Sascha,\n\nI visited your portfolio and would like to discuss a potential project or collaboration.\n\nBest regards,"); const body = encodeURIComponent(texts.emailBody);
// Announce action to screen readers // Announce action to screen readers
const announcement = document.createElement('div'); const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite'); announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('class', 'sr-only'); announcement.setAttribute('class', 'sr-only');
announcement.textContent = 'Opening email client with pre-filled message'; announcement.textContent = texts.emailAnnouncement;
document.body.appendChild(announcement); document.body.appendChild(announcement);
globalThis.location.href = `mailto:${email}?subject=${subject}&body=${body}`; globalThis.location.href = `mailto:${email}?subject=${subject}&body=${body}`;
@ -67,7 +67,7 @@ export default function ContactSection(props: ContactSectionProps = {}) {
const announcement = document.createElement('div'); const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite'); announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('class', 'sr-only'); announcement.setAttribute('class', 'sr-only');
announcement.textContent = `Opening ${platform} profile in new tab`; announcement.textContent = texts.socialAnnouncement.replace('{platform}', platform);
document.body.appendChild(announcement); document.body.appendChild(announcement);
globalThis.open(url, '_blank', 'noopener,noreferrer'); globalThis.open(url, '_blank', 'noopener,noreferrer');

View File

@ -16,6 +16,7 @@ interface HeroSectionProps {
export default function HeroSection(props: HeroSectionProps = {}) { export default function HeroSection(props: HeroSectionProps = {}) {
const texts = getTexts().hero; const texts = getTexts().hero;
const accessibilityTexts = getTexts().accessibility;
// Use centralized texts as defaults, allow props to override // Use centralized texts as defaults, allow props to override
const { const {
@ -98,7 +99,7 @@ export default function HeroSection(props: HeroSectionProps = {}) {
{/* Skip link for keyboard navigation */} {/* Skip link for keyboard navigation */}
<div className="hero-section__skip"> <div className="hero-section__skip">
<a href="#about" className="sr-only sr-only-focusable"> <a href="#about" className="sr-only sr-only-focusable">
Skip to about section {accessibilityTexts.skipLinks.skipToAbout}
</a> </a>
</div> </div>
@ -140,11 +141,11 @@ export default function HeroSection(props: HeroSectionProps = {}) {
{/* Hidden descriptions for screen readers */} {/* Hidden descriptions for screen readers */}
<div id="resume-description" className="sr-only"> <div id="resume-description" className="sr-only">
This will download my current resume in PDF format {accessibilityTexts.screenReader.downloadResumeDescription}
</div> </div>
<aside className="hero-section__stats" aria-labelledby="stats-heading"> <aside className="hero-section__stats" aria-labelledby="stats-heading">
<h2 id="stats-heading" className="sr-only">Professional Statistics</h2> <h2 id="stats-heading" className="sr-only">{accessibilityTexts.screenReader.professionalStatistics}</h2>
{statItems.map((item, index) => ( {statItems.map((item, index) => (
<div <div
key={item.label} key={item.label}

View File

@ -16,6 +16,7 @@ interface ProjectsSectionProps {
export default function ProjectsSection(props: ProjectsSectionProps = {}) { export default function ProjectsSection(props: ProjectsSectionProps = {}) {
const texts = getTexts().projects; const texts = getTexts().projects;
const accessibilityTexts = getTexts().accessibility;
const [activeCardIndex, setActiveCardIndex] = useState<number | null>(null); const [activeCardIndex, setActiveCardIndex] = useState<number | null>(null);
const [focusedActionIndex, setFocusedActionIndex] = useState<number>(0); const [focusedActionIndex, setFocusedActionIndex] = useState<number>(0);
const cardRefs = useRef<(HTMLElement | null)[]>([]); const cardRefs = useRef<(HTMLElement | null)[]>([]);
@ -25,36 +26,24 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
const { const {
title = texts.title, title = texts.title,
subtitle = texts.subtitle, subtitle = texts.subtitle,
projects = [ projects = texts.projectItems.map((project, index) => ({
{ id: index + 1,
id: 1, title: project.title,
title: "Portfolio Website", description: project.description,
description: "A personal portfolio website to showcase my skills, projects, and experience.", image: [portfolioImage, ergoVRImage, icaraceImage][index] || portfolioImage,
image: portfolioImage, technologies: [
technologies: ["React", "TypeScript", "SCSS", "Vercel", "Github Copilot"], ["React", "TypeScript", "SCSS", "Github Copilot"],
year: "2025", ["Unity3D", "C#", "Oculus SDK"],
github: personalConfig.gitProjects.portfolio, ["Angular 4", "Typescript", "HTML", "CSS"]
live: personalConfig.projectsUrls.portfolio ][index] || [],
}, year: ["2025", "2015", "2018"][index] || "2025",
{ github: [
id: 2, personalConfig.gitProjects.portfolio,
title: "ErgoVR", personalConfig.gitProjects.ergoVR,
description: "A virtual reality application for analysis of motion sickness in VR environments from 2015.", personalConfig.gitProjects.ergoVR
image: ergoVRImage, ][index],
technologies: ["Unity3D", "C#", "Oculus SDK"], live: index === 0 ? personalConfig.projectsUrls.portfolio : undefined
year: "2015", }))
github: personalConfig.gitProjects.ergoVR
},
{
id: 3,
title: "Icarace",
description: "Participated in the development of a web-platform for a fitness racing game for Icaros GmbH until 2018.",
image: icaraceImage,
technologies: ["Angular 4", "Typescript", "HTML", "CSS"],
year: "2018",
github: personalConfig.gitProjects.ergoVR
}
]
} = props; } = props;
// Get available actions for a project // Get available actions for a project
@ -73,7 +62,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
// Announce to screen readers // Announce to screen readers
const project = projects[index]; const project = projects[index];
const actions = getProjectActions(project); const actions = getProjectActions(project);
const announcement = `${project.title} card selected. ${actions.length} actions available. Use Tab to navigate actions, Escape to close.`; const announcement = `${project.title} card selected. ${actions.length} actions available. ${accessibilityTexts.navigation.useTabToNavigate}, Escape to close.`;
announceToScreenReader(announcement); announceToScreenReader(announcement);
}; };
@ -196,7 +185,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
{/* Keyboard navigation instructions */} {/* Keyboard navigation instructions */}
<div className="projects-section__instructions sr-only" aria-live="polite"> <div className="projects-section__instructions sr-only" aria-live="polite">
<p>Use Tab to navigate between project cards. Press Enter or Space to select a card and view available actions. Use Tab to navigate between actions within a selected card. Press Escape to close the selected card. Use Arrow keys to move between cards when one is selected.</p> <p>{accessibilityTexts.navigation.useTabToNavigateCards}</p>
</div> </div>
{/* Global keyboard shortcuts info - MOVED BEFORE CARDS */} {/* Global keyboard shortcuts info - MOVED BEFORE CARDS */}
@ -217,7 +206,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
{/* Skip link for keyboard navigation help */} {/* Skip link for keyboard navigation help */}
<div className="projects-section__skip"> <div className="projects-section__skip">
<a href="#contact" className="sr-only sr-only-focusable"> <a href="#contact" className="sr-only sr-only-focusable">
Skip to contact section {accessibilityTexts.skipLinks.skipToContact}
</a> </a>
</div> </div>
@ -233,7 +222,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
className={`projects-section__card ${isActive ? 'projects-section__card--active' : ''}`} className={`projects-section__card ${isActive ? 'projects-section__card--active' : ''}`}
ref={(el) => { cardRefs.current[cardIndex] = el; }} ref={(el) => { cardRefs.current[cardIndex] = el; }}
aria-describedby={`project-${project.id}-description project-${project.id}-metadata`} aria-describedby={`project-${project.id}-description project-${project.id}-metadata`}
aria-label={`Project card: ${project.title}. ${cardIndex + 1} of ${projects.length}`} aria-label={`${accessibilityTexts.screenReader.projectCard}: ${project.title}. ${cardIndex + 1} of ${projects.length}`}
onKeyDown={(e) => handleCardKeyDown(e, cardIndex)} onKeyDown={(e) => handleCardKeyDown(e, cardIndex)}
onClick={() => handleCardSelect(cardIndex)} onClick={() => handleCardSelect(cardIndex)}
> >
@ -260,7 +249,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="projects-section__action-button projects-section__action-button--secondary" className="projects-section__action-button projects-section__action-button--secondary"
aria-label={`View ${project.title} source code on GitHub (opens in new tab)`} aria-label={`${accessibilityTexts.screenReader.viewSourceCode} ${project.title} source code on GitHub (opens in new tab)`}
tabIndex={isActive ? 0 : -1} tabIndex={isActive ? 0 : -1}
onKeyDown={(e) => handleActionKeyDown(e, cardIndex, 0)} onKeyDown={(e) => handleActionKeyDown(e, cardIndex, 0)}
> >
@ -275,7 +264,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="projects-section__action-button projects-section__action-button--primary" className="projects-section__action-button projects-section__action-button--primary"
aria-label={`View ${project.title} live demo (opens in new tab)`} aria-label={`${accessibilityTexts.screenReader.viewLiveDemo} ${project.title} live demo (opens in new tab)`}
tabIndex={isActive ? 0 : -1} tabIndex={isActive ? 0 : -1}
onKeyDown={(e) => handleActionKeyDown(e, cardIndex, project.github ? 1 : 0)} onKeyDown={(e) => handleActionKeyDown(e, cardIndex, project.github ? 1 : 0)}
> >
@ -321,7 +310,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
{isActive && ( {isActive && (
<div className="projects-section__status sr-only" aria-live="polite" aria-atomic="true"> <div className="projects-section__status sr-only" aria-live="polite" aria-atomic="true">
{project.title} card selected. {actions.length} actions available. {project.title} card selected. {actions.length} actions available.
Use Tab to navigate actions, Escape to close, Arrow keys to move between cards. {accessibilityTexts.navigation.useTabToNavigate}, Escape to close, Arrow keys to move between cards.
</div> </div>
)} )}
</div> </div>

View File

@ -10,49 +10,18 @@ interface ServicesSectionProps {
export default function ServicesSection(props: ServicesSectionProps = {}) { export default function ServicesSection(props: ServicesSectionProps = {}) {
const texts = getTexts().services; const texts = getTexts().services;
const accessibilityTexts = getTexts().accessibility;
// Use centralized texts as defaults, allow props to override // Use centralized texts as defaults, allow props to override
const { const {
title = texts.title, title = texts.title,
subtitle = texts.subtitle, subtitle = texts.subtitle,
services = [ services = texts.serviceItems.map((service, index) => ({
{ icon: [Eye, Palette, ShieldCheck, Laptop, Lightbulb, Rocket][index] || Eye,
icon: Eye, title: service.title,
title: "Web Accessibility", description: service.description,
description: "Building inclusive and accessible web applications by implementing Web Content Accessibility Guidelines, semantic HTML, and support for assistive technologies.", colorClass: ["primary", "secondary", "tertiary", "quaternary", "quinary", "senary"][index] || "primary"
colorClass: "primary" }))
},
{
icon: Palette,
title: "Responsive Web Design",
description: "Creating mobile-first, responsive interfaces that provide optimal viewing experiences across all devices and screen sizes.",
colorClass: "secondary"
},
{
icon: ShieldCheck,
title: "Cross-Browser Testing",
description: "Ensuring consistent functionality and appearance across all major browsers including Chrome, Firefox, Safari, and Edge.",
colorClass: "tertiary"
},
{
icon: Laptop,
title: "Frontend Architecture",
description: "Designing and implementing scalable frontend architectures with component-based development and modern tooling.",
colorClass: "quaternary"
},
{
icon: Lightbulb,
title: "Performance Optimization",
description: "Optimizing web applications for speed and efficiency through code splitting, lazy loading, and performance best practices.",
colorClass: "quinary"
},
{
icon: Rocket,
title: "Testing & Quality Assurance",
description: "Implementing comprehensive testing strategies using Cypress for end-to-end testing and ensuring code quality.",
colorClass: "senary"
}
]
} = props; } = props;
return ( return (
@ -70,8 +39,8 @@ export default function ServicesSection(props: ServicesSectionProps = {}) {
{/* Skip link for keyboard navigation */} {/* Skip link for keyboard navigation */}
<div className="services-section__skip"> <div className="services-section__skip">
<a href="#certifications" className="sr-only sr-only-focusable"> <a href="#skills" className="sr-only sr-only-focusable">
Skip to certifications section {accessibilityTexts.skipLinks.skipToSkills}
</a> </a>
</div> </div>

View File

@ -28,67 +28,13 @@ const getColorByPosition = (index: number): string => {
export default function SkillsSection(props: SkillsSectionProps = {}) { export default function SkillsSection(props: SkillsSectionProps = {}) {
const texts = getTexts().skills; const texts = getTexts().skills;
const accessibilityTexts = getTexts().accessibility;
// Use centralized texts as defaults, allow props to override // Use centralized texts as defaults, allow props to override
const { const {
title = texts.title, title = texts.title,
subtitle = texts.subtitle, subtitle = texts.subtitle,
skillCategories = [ skillCategories = texts.skillCategories
{
title: "Web Frameworks",
skills: [
{ skill: "Angular", value: 90 },
{ skill: "React", value: 70 },
{ skill: "Next.js", value: 70 },
{ skill: "TypeScript", value: 95 }
]
},
{
title: "Styling & Design",
skills: [
{ skill: "Accessibility (a11y) Standards", value: 90 },
{ skill: "SCSS/Sass", value: 95 },
{ skill: "CSS3", value: 95 },
{ skill: "Bootstrap", value: 80 }
]
},
{
title: "Backend Development",
skills: [
{ skill: "Node.js", value: 75 },
{ skill: "Express.js", value: 70 },
{ skill: "REST APIs", value: 85 },
{ skill: "Database Design", value: 70 }
]
},
{
title: "Development Tools",
skills: [
{ skill: "Git & GitHub", value: 90 },
{ skill: "VS Code", value: 95 },
{ skill: "npm/yarn", value: 85 },
{ skill: "Webpack/Vite", value: 75 }
]
},
{
title: "Testing & Quality",
skills: [
{ skill: "Unit Testing", value: 80 },
{ skill: "E2E Testing", value: 70 },
{ skill: "Code Review", value: 85 },
{ skill: "Performance Optimization", value: 75 }
]
},
{
title: "AI-Tools",
skills: [
{ skill: "GitHub Copilot", value: 95 },
{ skill: "ChatGPT", value: 90 },
{ skill: "Claude", value: 85 },
{ skill: "AI-Assisted Development", value: 90 }
]
}
]
} = props; } = props;
return ( return (
@ -106,8 +52,8 @@ export default function SkillsSection(props: SkillsSectionProps = {}) {
{/* Skip link for keyboard navigation */} {/* Skip link for keyboard navigation */}
<div className="skills-section__skip"> <div className="skills-section__skip">
<a href="#services" className="sr-only sr-only-focusable"> <a href="#certifications" className="sr-only sr-only-focusable">
Skip to services section {accessibilityTexts.skipLinks.skipToCertifications}
</a> </a>
</div> </div>

View File

@ -41,18 +41,33 @@ export interface TextConfig {
subtitle: string; subtitle: string;
name: string; name: string;
bio: string[]; bio: string[];
featureCards: Array<{
title: string;
description: string;
}>;
}; };
// Services Section // Services Section
services: { services: {
title: string; title: string;
subtitle: string; subtitle: string;
serviceItems: Array<{
title: string;
description: string;
}>;
}; };
// Skills Section // Skills Section
skills: { skills: {
title: string; title: string;
subtitle: string; subtitle: string;
skillCategories: Array<{
title: string;
skills: Array<{
skill: string;
value: number;
}>;
}>;
}; };
// Projects Section // Projects Section
@ -61,12 +76,20 @@ export interface TextConfig {
subtitle: string; subtitle: string;
codeButtonText: string; codeButtonText: string;
liveButtonText: string; liveButtonText: string;
projectItems: Array<{
title: string;
description: string;
}>;
}; };
// Certifications Section // Certifications Section
certifications: { certifications: {
title: string; title: string;
subtitle: string; subtitle: string;
certificationItems: Array<{
name: string;
issuer: string;
}>;
}; };
// Contact Section // Contact Section
@ -84,6 +107,10 @@ export interface TextConfig {
preferredContactValue: string; preferredContactValue: string;
locationLabel: string; locationLabel: string;
locationValue: string; locationValue: string;
emailSubject: string;
emailBody: string;
emailAnnouncement: string;
socialAnnouncement: string;
}; };
// Imprint Page // Imprint Page
@ -184,6 +211,52 @@ export interface TextConfig {
legalBasisTitle: string; legalBasisTitle: string;
legalBasisText: string; legalBasisText: string;
}; };
// Accessibility and Navigation
accessibility: {
skipLinks: {
skipToHero: string;
skipToAbout: string;
skipToServices: string;
skipToSkills: string;
skipToProjects: string;
skipToCertifications: string;
skipToContact: string;
};
navigation: {
keyboardHelp: string;
keyboardHelpExpanded: string[];
useTabToNavigate: string;
useTabToNavigateCards: string;
useTabToNavigateOptions: string;
closeKeyboardHelp: string;
};
screenReader: {
professionalStatistics: string;
projectCard: string;
viewSourceCode: string;
viewLiveDemo: string;
downloadResume: string;
downloadResumeDescription: string;
keyboardNavigationHelp: string;
themeSwitchButton: string;
switchToTheme: string;
certificationCard: string;
serviceCard: string;
skillBadge: string;
};
ariaLabels: {
mainNavigation: string;
socialLinks: string;
themeToggle: string;
mobileMenuToggle: string;
contactForm: string;
projectsGrid: string;
skillsGrid: string;
certificationsGrid: string;
servicesGrid: string;
};
};
} }
// Default text configuration (English/German mix as currently used) // Default text configuration (English/German mix as currently used)
@ -242,16 +315,126 @@ export const defaultTexts: TextConfig = {
'If you are wondering whether your website already meets accessibility standards, or if you feel it is time to make your online presence more inclusive and legally compliant, I would be glad to support you. Often just a few clear steps can make a big difference, helping your website reach more people and stand out from the competition.', 'If you are wondering whether your website already meets accessibility standards, or if you feel it is time to make your online presence more inclusive and legally compliant, I would be glad to support you. Often just a few clear steps can make a big difference, helping your website reach more people and stand out from the competition.',
"Let's connect and see how we can make your website barrier free, modern and ready for the future.", "Let's connect and see how we can make your website barrier free, modern and ready for the future.",
], ],
featureCards: [
{
title: 'Web Development',
description:
'Building modern web applications with Angular and React frameworks, leveraging TypeScript and JavaScript for robust, scalable solutions.',
},
{
title: 'Responsive Design',
description:
'Creating pixel-perfect, responsive interfaces that work seamlessly across all devices and screen sizes.',
},
{
title: 'Cross-Browser Compatibility',
description:
'Ensuring consistent user experiences across all major browsers with thorough testing and optimization.',
},
{
title: 'Web Accessibility',
description:
'Implementing accessibility best practices to deliver websites that support screen readers, keyboard navigation, and assistive technologies.',
},
],
}, },
services: { services: {
title: 'Services', title: 'Services',
subtitle: 'Comprehensive web development solutions tailored to your needs', subtitle: 'Comprehensive web development solutions tailored to your needs',
serviceItems: [
{
title: 'Web Accessibility',
description:
'Building inclusive and accessible web applications by implementing Web Content Accessibility Guidelines, semantic HTML, and support for assistive technologies.',
},
{
title: 'Responsive Web Design',
description:
'Creating mobile-first, responsive interfaces that provide optimal viewing experiences across all devices and screen sizes.',
},
{
title: 'Cross-Browser Testing',
description:
'Ensuring consistent functionality and appearance across all major browsers including Chrome, Firefox, Safari, and Edge.',
},
{
title: 'Frontend Architecture',
description:
'Designing and implementing scalable frontend architectures with component-based development and modern tooling.',
},
{
title: 'Performance Optimization',
description:
'Optimizing web applications for speed and efficiency through code splitting, lazy loading, and performance best practices.',
},
{
title: 'Testing & Quality Assurance',
description:
'Implementing comprehensive testing strategies using Cypress for end-to-end testing and ensuring code quality.',
},
],
}, },
skills: { skills: {
title: 'Skills & Technologies', title: 'Skills & Technologies',
subtitle: 'Technical expertise across the full development stack', subtitle: 'Technical expertise across the full development stack',
skillCategories: [
{
title: 'Web Frameworks',
skills: [
{ skill: 'Angular', value: 90 },
{ skill: 'React', value: 70 },
{ skill: 'Next.js', value: 70 },
{ skill: 'TypeScript', value: 95 },
],
},
{
title: 'Styling & Design',
skills: [
{ skill: 'Accessibility (a11y) Standards', value: 90 },
{ skill: 'SCSS/Sass', value: 95 },
{ skill: 'CSS3', value: 95 },
{ skill: 'Bootstrap', value: 80 },
],
},
{
title: 'Backend Development',
skills: [
{ skill: 'Node.js', value: 75 },
{ skill: 'Express.js', value: 70 },
{ skill: 'REST APIs', value: 85 },
{ skill: 'Database Design', value: 70 },
],
},
{
title: 'Development Tools',
skills: [
{ skill: 'Git & GitHub', value: 90 },
{ skill: 'VS Code', value: 95 },
{ skill: 'npm/yarn', value: 85 },
{ skill: 'Webpack/Vite', value: 75 },
],
},
{
title: 'Testing & Quality',
skills: [
{ skill: 'Unit Testing', value: 80 },
{ skill: 'E2E Testing', value: 70 },
{ skill: 'Code Review', value: 85 },
{ skill: 'Performance Optimization', value: 75 },
],
},
{
title: 'AI-Tools',
skills: [
{ skill: 'GitHub Copilot', value: 95 },
{ skill: 'ChatGPT', value: 90 },
{ skill: 'Claude', value: 85 },
{ skill: 'AI-Assisted Development', value: 90 },
],
},
],
}, },
projects: { projects: {
@ -259,11 +442,50 @@ export const defaultTexts: TextConfig = {
subtitle: 'A showcase of my work and personal projects', subtitle: 'A showcase of my work and personal projects',
codeButtonText: 'Code', codeButtonText: 'Code',
liveButtonText: 'Live', liveButtonText: 'Live',
projectItems: [
{
title: 'Portfolio Website',
description:
'A personal portfolio website to showcase my skills, projects, and experience.',
},
{
title: 'ErgoVR',
description:
'A virtual reality application for analysis of motion sickness in VR environments from 2015.',
},
{
title: 'Icarace',
description:
'Participated in the development of a web-platform for a fitness racing game for Icaros GmbH until 2018.',
},
],
}, },
certifications: { certifications: {
title: 'Certifications & Achievements', title: 'Certifications & Achievements',
subtitle: 'My professional qualifications and continuous learning', subtitle: 'My professional qualifications and continuous learning',
certificationItems: [
{
name: 'Practical Prompt Engineering Masterclass: Hands-On Learning',
issuer: 'Udemy Lecture - Asif Farooqi, Abdullah Dar',
},
{
name: 'Understanding Typescript',
issuer: 'Udemy Lecture - Maximilian Schwarzmüller',
},
{
name: 'Angular Step by Step',
issuer: 'Udemy Lecture - Shivprasad Koirala',
},
{
name: 'Angular and Typescript',
issuer: 'LinkedIn TestDome',
},
{
name: 'Bachelor of Science in Computer Science',
issuer: 'TU Darmstadt',
},
],
}, },
contact: { contact: {
@ -281,6 +503,11 @@ export const defaultTexts: TextConfig = {
preferredContactValue: 'Email', preferredContactValue: 'Email',
locationLabel: 'Location:', locationLabel: 'Location:',
locationValue: 'Germany', locationValue: 'Germany',
emailSubject: "Portfolio Inquiry - Let's discuss your project",
emailBody:
'Hello Sascha,\n\nI visited your portfolio and would like to discuss a potential project or collaboration.\n\nBest regards,',
emailAnnouncement: 'Opening email client with pre-filled message',
socialAnnouncement: 'Opening {platform} profile in new tab',
}, },
imprint: { imprint: {
@ -475,6 +702,59 @@ export const defaultTexts: TextConfig = {
legalBasisText: legalBasisText:
'Processing is based on your explicit consent (Art. 6(1)(a) GDPR).', 'Processing is based on your explicit consent (Art. 6(1)(a) GDPR).',
}, },
accessibility: {
skipLinks: {
skipToHero: 'Skip to main content',
skipToAbout: 'Skip to about section',
skipToServices: 'Skip to services section',
skipToSkills: 'Skip to skills section',
skipToProjects: 'Skip to projects section',
skipToCertifications: 'Skip to certifications section',
skipToContact: 'Skip to contact section',
},
navigation: {
keyboardHelp: 'Keyboard Navigation Help',
keyboardHelpExpanded: [
'Use Tab to navigate between elements',
'Use Enter or Space to activate buttons and links',
'Use Arrow keys to navigate between project cards',
'Use Escape to close this help dialog',
'Use Ctrl+F to search the page',
],
useTabToNavigate: 'Use Tab to navigate between elements',
useTabToNavigateCards:
'Use Tab to navigate between project cards, or Arrow keys to move within the grid',
useTabToNavigateOptions: 'Use Tab to navigate between options',
closeKeyboardHelp: 'Close keyboard help',
},
screenReader: {
professionalStatistics: 'Professional Statistics',
projectCard: 'Project card',
viewSourceCode: 'View source code',
viewLiveDemo: 'View live demo',
downloadResume: 'Download Resume',
downloadResumeDescription:
'This will download my current resume as a PDF file',
keyboardNavigationHelp: 'Keyboard Navigation Help',
themeSwitchButton: 'Theme switch button',
switchToTheme: 'Switch to theme',
certificationCard: 'Certification card',
serviceCard: 'Service card',
skillBadge: 'Skill badge',
},
ariaLabels: {
mainNavigation: 'Main navigation',
socialLinks: 'Social media links',
themeToggle: 'Toggle theme',
mobileMenuToggle: 'Toggle mobile menu',
contactForm: 'Contact form',
projectsGrid: 'Projects grid',
skillsGrid: 'Skills grid',
certificationsGrid: 'Certifications grid',
servicesGrid: 'Services grid',
},
},
}; };
// Export a function to get texts (can be extended for internationalization) // Export a function to get texts (can be extended for internationalization)