finished service section and its color schemes

This commit is contained in:
Sascha Bach 2025-08-26 14:22:10 +02:00
parent c22c97b23a
commit e13dda79bf
9 changed files with 467 additions and 77 deletions

View File

@ -1,3 +1,5 @@
import React from 'react';
type Props = {
theme: 'light' | 'dark';
setTheme: React.Dispatch<React.SetStateAction<'light' | 'dark'>>;
@ -9,8 +11,14 @@ export default function ThemeToggle({ theme, setTheme }: Props) {
};
return (
<button className="navbar__container__button toggle-theme" onClick={toggleTheme}>
{theme === 'light' ? '💡' : '💤'}
<button
className="navbar__container__button toggle-theme"
onClick={toggleTheme}
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
>
<span className="toggle-theme__icon">
{theme === 'light' ? '🌙' : '☀️'}
</span>
</button>
);
}

View File

@ -1,62 +1,84 @@
import { Code, Laptop, Lightbulb, Palette, Rocket, ShieldCheck } from "lucide-react";
import type { Service } from '../../data/Service';
export default function ServicesSection() {
const services = [
interface ServicesSectionProps {
title?: string;
subtitle?: string;
services?: Service[];
}
export default function ServicesSection({
title = "Services I Offer",
subtitle = "Solutions tailored to your needs",
services = [
{
icon: <Code className="services-section__icon" />,
icon: Code,
title: "Modern Framework Development",
description: "Building scalable, maintainable web applications using Angular and React frameworks with TypeScript and modern development practices."
description: "Building scalable, maintainable web applications using Angular and React frameworks with TypeScript and modern development practices.",
colorClass: "primary"
},
{
icon: <Palette className="services-section__icon" />,
icon: Palette,
title: "Responsive Web Design",
description: "Creating mobile-first, responsive interfaces that provide optimal viewing experiences across all devices and screen sizes."
description: "Creating mobile-first, responsive interfaces that provide optimal viewing experiences across all devices and screen sizes.",
colorClass: "secondary"
},
{
icon: <ShieldCheck className="services-section__icon" />,
icon: ShieldCheck,
title: "Cross-Browser Testing",
description: "Ensuring consistent functionality and appearance across all major browsers including Chrome, Firefox, Safari, and Edge."
description: "Ensuring consistent functionality and appearance across all major browsers including Chrome, Firefox, Safari, and Edge.",
colorClass: "tertiary"
},
{
icon: <Laptop className="services-section__icon" />,
icon: Laptop,
title: "Frontend Architecture",
description: "Designing and implementing scalable frontend architectures with component-based development and modern tooling."
description: "Designing and implementing scalable frontend architectures with component-based development and modern tooling.",
colorClass: "quaternary"
},
{
icon: <Lightbulb className="services-section__icon" />,
icon: Lightbulb,
title: "Performance Optimization",
description: "Optimizing web applications for speed and efficiency through code splitting, lazy loading, and performance best practices."
description: "Optimizing web applications for speed and efficiency through code splitting, lazy loading, and performance best practices.",
colorClass: "quinary"
},
{
icon: <Rocket className="services-section__icon" />,
icon: Rocket,
title: "Testing & Quality Assurance",
description: "Implementing comprehensive testing strategies using Cypress for end-to-end testing and ensuring code quality."
description: "Implementing comprehensive testing strategies using Cypress for end-to-end testing and ensuring code quality.",
colorClass: "senary"
}
];
]
}: ServicesSectionProps) {
return (
<section id="services" className="services-section">
<div className="services-section__container">
<div className="services-section__header">
<h2 className="services-section__title">
Services I Offer
{title}
</h2>
<p className="services-section__subtitle">
Solutions tailored to your needs
{subtitle}
</p>
</div>
<div className="services-section__grid">
{services.map((service, index) => (
<div key={index} className="services-section__card">
{services.map((service, index) => {
const IconComponent = service.icon;
return (
<div key={index} className={`services-section__card services-section__card--${service.colorClass}`}>
<div className="services-section__card-header">
{service.icon}
<h3 className="services-section__card-title">{service.title}</h3>
<IconComponent className={`services-section__icon services-section__icon--${service.colorClass}`} />
<h3 className={`services-section__card-title services-section__card-title--${service.colorClass}`}>
{service.title}
</h3>
</div>
<div className="services-section__card-content">
<p className="services-section__card-description">{service.description}</p>
<p className={`services-section__card-description services-section__card-description--${service.colorClass}`}>
{service.description}
</p>
</div>
</div>
))}
);
})}
</div>
</div>
</section>

View File

@ -1,8 +1,8 @@
import type { LucideIcon } from 'lucide-react';
export interface FeatureCard {
export interface ServiceCard {
icon: LucideIcon;
title: string;
description: string;
colorClass: 'primary' | 'secondary' | 'tertiary';
colorClass: 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'quinary' | 'senary';
}

8
src/data/Service.ts Normal file
View File

@ -0,0 +1,8 @@
import type { LucideIcon } from 'lucide-react';
export interface Service {
icon: LucideIcon;
title: string;
description: string;
colorClass: 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'quinary' | 'senary';
}

View File

@ -1,8 +0,0 @@
import type { ReactNode } from "react";
// Services data
export interface Service {
icon: ReactNode;
title: string;
description: string;
}

View File

@ -1,85 +1,335 @@
@use 'globals';
@use 'variables' as *;
@use 'mixins' as *;
.services-section {
background: linear-gradient(135deg, #fff7ed 0%, #fee2e2 100%);
min-height: 100vh;
display: flex;
align-items: center;
padding: 2rem 0;
background: var(--services-background);
&__container {
max-width: 1120px;
max-width: 80rem;
margin: 0 auto;
padding: 0 $spacing-md;
padding: 0 1rem;
width: 100%;
@include globals.desktop-only {
padding: 0 1.5rem;
}
}
&__header {
text-align: center;
margin-bottom: $spacing-3xl;
margin-bottom: $services-header-margin;
@include globals.mobile-only {
margin-bottom: 1.5rem;
}
}
&__title {
font-size: 2.25rem;
font-size: 1.875rem;
font-weight: bold;
margin-bottom: $spacing-md;
background: linear-gradient(90deg, #ea580c 0%, #dc2626 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 1rem;
background: var(--gradient-services-title);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
@include globals.desktop-only {
font-size: 2.25rem;
}
@include globals.mobile-only {
font-size: 1.5rem;
}
}
&__subtitle {
font-size: 1.25rem;
color: #6b7280;
color: var(--color-text-muted);
max-width: 32rem;
margin: 0 auto;
@include globals.mobile-only {
font-size: 1rem;
}
}
&__grid {
display: grid;
gap: $services-grid-gap;
max-height: calc(100vh - 12rem);
overflow: hidden;
// Mobile: 1 card per row (320px - 767px)
@media (max-width: 767px) {
grid-template-columns: 1fr;
gap: $spacing-xl;
gap: $services-grid-gap-mobile;
max-height: none;
overflow: visible;
}
// Tablet Portrait: 2 cards per row (768px - 1023px)
@media (min-width: 768px) and (max-width: 1023px) {
grid-template-columns: repeat(2, 1fr);
}
// Desktop Small: 3 cards per row (1024px - 1279px)
@media (min-width: 1024px) and (max-width: 1279px) {
grid-template-columns: repeat(3, 1fr);
}
// Desktop Large: 3 cards per row (1280px+)
@media (min-width: 1280px) {
grid-template-columns: repeat(3, 1fr);
max-width: 1200px; // Limit width for better card proportions
margin: 0 auto;
}
// Ultra-wide: 4 cards per row (1920px+) - Optional
@media (min-width: 1920px) {
grid-template-columns: repeat(4, 1fr);
max-width: 1600px;
}
}
&__card {
background: linear-gradient(135deg, #fff 0%, #f3f4f6 100%);
border-radius: $border-radius-lg;
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.07);
padding: $spacing-xl $spacing-lg;
text-align: center;
transition: box-shadow 0.3s;
border: none;
background: white;
border-radius: $services-card-border-radius;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
overflow: hidden;
display: flex;
flex-direction: column;
height: auto;
min-height: 250px; // Ensures consistent card heights
&:hover {
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
// Responsive card heights
@media (min-width: 768px) {
height: 280px; // Fixed height for consistent rows
}
@media (min-width: 1024px) {
height: 300px;
}
@media (min-width: 1280px) {
height: 320px;
}
&--primary {
background: var(--service-card-primary-bg);
}
&--secondary {
background: var(--service-card-secondary-bg);
}
&--tertiary {
background: var(--service-card-tertiary-bg);
}
&--quaternary {
background: var(--service-card-quaternary-bg);
}
&--quinary {
background: var(--service-card-quinary-bg);
}
&--senary {
background: var(--service-card-senary-bg);
}
}
&__card-header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: $spacing-md;
padding: $services-card-padding;
text-align: center;
flex-shrink: 0;
@media (max-width: 767px) {
padding: 1.25rem;
}
}
&__icon {
width: 2.5rem;
height: 2.5rem;
margin-bottom: $spacing-sm;
color: #ea580c;
width: $services-icon-size;
height: $services-icon-size;
margin: 0 auto 1rem auto;
display: block;
@media (max-width: 767px) {
width: 2rem;
height: 2rem;
margin-bottom: 0.75rem;
}
&--primary {
color: var(--service-icon-primary);
}
&--secondary {
color: var(--service-icon-secondary);
}
&--tertiary {
color: var(--service-icon-tertiary);
}
&--quaternary {
color: var(--service-icon-quaternary);
}
&--quinary {
color: var(--service-icon-quinary);
}
&--senary {
color: var(--service-icon-senary);
}
}
&__card-title {
font-size: 1.25rem;
font-size: 1.125rem;
font-weight: 600;
margin-bottom: $spacing-xs;
margin: 0;
line-height: 1.4;
@media (max-width: 767px) {
font-size: 1rem;
}
@media (min-width: 1280px) {
font-size: 1.25rem;
}
&--primary {
color: var(--service-title-primary);
}
&--secondary {
color: var(--service-title-secondary);
}
&--tertiary {
color: var(--service-title-tertiary);
}
&--quaternary {
color: var(--service-title-quaternary);
}
&--quinary {
color: var(--service-title-quinary);
}
&--senary {
color: var(--service-title-senary);
}
}
&__card-content {
min-height: 60px;
padding: 0 $services-card-padding $services-card-padding $services-card-padding;
flex: 1;
display: flex;
align-items: center;
@media (max-width: 767px) {
padding: 0 1.25rem 1.25rem 1.25rem;
}
}
&__card-description {
font-size: 1rem;
color: #6b7280;
margin: 0;
line-height: 1.5;
font-size: 0.875rem;
text-align: center;
@media (max-width: 767px) {
font-size: 0.8rem;
line-height: 1.4;
}
@media (min-width: 1280px) {
font-size: 0.9rem;
line-height: 1.6;
}
&--primary {
color: var(--service-description-primary);
}
&--secondary {
color: var(--service-description-secondary);
}
&--tertiary {
color: var(--service-description-tertiary);
}
&--quaternary {
color: var(--service-description-quaternary);
}
&--quinary {
color: var(--service-description-quinary);
}
&--senary {
color: var(--service-description-senary);
}
}
}
// Handle 6 cards specifically for better distribution
.services-section__grid:has(.services-section__card:nth-child(6):last-child) {
// For exactly 6 cards
@media (min-width: 768px) and (max-width: 1023px) {
grid-template-columns: repeat(2, 1fr);
// Make last 2 cards center themselves
.services-section__card:nth-child(5),
.services-section__card:nth-child(6) {
// This ensures the last row centers properly
}
}
@media (min-width: 1024px) {
grid-template-columns: repeat(3, 1fr);
}
}
// Ultra-compact mobile layout for very small screens
@media (max-height: 700px) and (max-width: 767px) {
.services-section {
min-height: auto;
padding: 1rem 0;
&__header {
margin-bottom: 1rem;
}
&__card {
min-height: 200px;
height: auto;
}
&__card-header {
padding: 1rem;
}
&__icon {
width: 1.75rem;
height: 1.75rem;
margin-bottom: 0.5rem;
}
}
}

View File

@ -63,6 +63,39 @@ $light-theme: (
feature-description-secondary: #6d28d9,
feature-description-tertiary: #115e59,
// Services section colors - Tailwind inspired
services-background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 25%, #cbd5e1 70%, #94a3b8 100%),
gradient-services-title: linear-gradient(90deg, #0f172a, #1e293b, #334155),
// Service card colors using Tailwind color palette
service-card-primary-bg: linear-gradient(135deg, #dbeafe, #bfdbfe), // blue-100 to blue-200
service-card-secondary-bg: linear-gradient(135deg, #dcfce7, #bbf7d0), // green-100 to green-200
service-card-tertiary-bg: linear-gradient(135deg, #e9d5ff, #ddd6fe), // violet-100 to violet-200
service-card-quaternary-bg: linear-gradient(135deg, #ccfbf1, #99f6e4), // teal-100 to teal-200
service-card-quinary-bg: linear-gradient(135deg, #fed7aa, #fdba74), // orange-200 to orange-300
service-card-senary-bg: linear-gradient(135deg, #fecaca, #fca5a5), // red-200 to red-300
service-icon-primary: #2563eb, // blue-600
service-icon-secondary: #16a34a, // green-600
service-icon-tertiary: #7c3aed, // violet-600
service-icon-quaternary: #0d9488, // teal-600
service-icon-quinary: #ea580c, // orange-600
service-icon-senary: #dc2626, // red-600
service-title-primary: #1e40af, // blue-700
service-title-secondary: #166534, // green-700
service-title-tertiary: #6d28d9, // violet-700
service-title-quaternary: #0f766e, // teal-700
service-title-quinary: #c2410c, // orange-700
service-title-senary: #b91c1c, // red-700
service-description-primary: #1d4ed8, // blue-700
service-description-secondary: #15803d, // green-700
service-description-tertiary: #5b21b6, // violet-800
service-description-quaternary: #115e59, // teal-800
service-description-quinary: #9a3412, // orange-800
service-description-senary: #991b1b, // red-800
// Shadow variables
box-shadow-sm: #{$shadow-sm},
box-shadow-md: #{$shadow-md},
@ -142,6 +175,39 @@ $dark-theme: (
feature-description-secondary: #e9d5ff,
feature-description-tertiary: #ccfbf1,
// Services section colors - Dark mode with Tailwind palette
services-background: linear-gradient(135deg, #0f172a 0%, #1e293b 25%, #334155 70%, #475569 100%),
gradient-services-title: linear-gradient(90deg, #f1f5f9, #e2e8f0, #cbd5e1),
// Service card colors for dark theme - using Tailwind dark colors
service-card-primary-bg: linear-gradient(135deg, rgba(30, 58, 138, 0.3), rgba(37, 99, 235, 0.2)), // blue-800/30 to blue-600/20
service-card-secondary-bg: linear-gradient(135deg, rgba(22, 101, 52, 0.3), rgba(34, 197, 94, 0.2)), // green-800/30 to green-500/20
service-card-tertiary-bg: linear-gradient(135deg, rgba(91, 33, 182, 0.3), rgba(124, 58, 237, 0.2)), // violet-800/30 to violet-600/20
service-card-quaternary-bg: linear-gradient(135deg, rgba(17, 94, 89, 0.3), rgba(20, 184, 166, 0.2)), // teal-800/30 to teal-500/20
service-card-quinary-bg: linear-gradient(135deg, rgba(154, 52, 18, 0.3), rgba(249, 115, 22, 0.2)), // orange-800/30 to orange-500/20
service-card-senary-bg: linear-gradient(135deg, rgba(153, 27, 27, 0.3), rgba(239, 68, 68, 0.2)), // red-800/30 to red-500/20
service-icon-primary: #60a5fa, // blue-400
service-icon-secondary: #4ade80, // green-400
service-icon-tertiary: #a78bfa, // violet-400
service-icon-quaternary: #2dd4bf, // teal-400
service-icon-quinary: #fb923c, // orange-400
service-icon-senary: #f87171, // red-400
service-title-primary: #93c5fd, // blue-300
service-title-secondary: #86efac, // green-300
service-title-tertiary: #c4b5fd, // violet-300
service-title-quaternary: #5eead4, // teal-300
service-title-quinary: #fdba74, // orange-300
service-title-senary: #fca5a5, // red-300
service-description-primary: #bfdbfe, // blue-200
service-description-secondary: #bbf7d0, // green-200
service-description-tertiary: #ddd6fe, // violet-200
service-description-quaternary: #99f6e4, // teal-200
service-description-quinary: #fed7aa, // orange-200
service-description-senary: #fecaca, // red-200
// Shadow variables
box-shadow-sm: #{$shadow-sm},
box-shadow-md: #{$shadow-md},
@ -216,6 +282,39 @@ $dark-theme: (
feature-description-tertiary
)};
// Services section variables
--services-background: #{map.get($theme, services-background)};
--gradient-services-title: #{map.get($theme, gradient-services-title)};
// Service card variables
--service-card-primary-bg: #{map.get($theme, service-card-primary-bg)};
--service-card-secondary-bg: #{map.get($theme, service-card-secondary-bg)};
--service-card-tertiary-bg: #{map.get($theme, service-card-tertiary-bg)};
--service-card-quaternary-bg: #{map.get($theme, service-card-quaternary-bg)};
--service-card-quinary-bg: #{map.get($theme, service-card-quinary-bg)};
--service-card-senary-bg: #{map.get($theme, service-card-senary-bg)};
--service-icon-primary: #{map.get($theme, service-icon-primary)};
--service-icon-secondary: #{map.get($theme, service-icon-secondary)};
--service-icon-tertiary: #{map.get($theme, service-icon-tertiary)};
--service-icon-quaternary: #{map.get($theme, service-icon-quaternary)};
--service-icon-quinary: #{map.get($theme, service-icon-quinary)};
--service-icon-senary: #{map.get($theme, service-icon-senary)};
--service-title-primary: #{map.get($theme, service-title-primary)};
--service-title-secondary: #{map.get($theme, service-title-secondary)};
--service-title-tertiary: #{map.get($theme, service-title-tertiary)};
--service-title-quaternary: #{map.get($theme, service-title-quaternary)};
--service-title-quinary: #{map.get($theme, service-title-quinary)};
--service-title-senary: #{map.get($theme, service-title-senary)};
--service-description-primary: #{map.get($theme, service-description-primary)};
--service-description-secondary: #{map.get($theme, service-description-secondary)};
--service-description-tertiary: #{map.get($theme, service-description-tertiary)};
--service-description-quaternary: #{map.get($theme, service-description-quaternary)};
--service-description-quinary: #{map.get($theme, service-description-quinary)};
--service-description-senary: #{map.get($theme, service-description-senary)};
// Shadow variables
--box-shadow-sm: #{map.get($theme, box-shadow-sm)} var(--box-shadow-hover);
--box-shadow-md: #{map.get($theme, box-shadow-md)};

View File

@ -109,6 +109,9 @@
.theme-toggle {
@extend %button-reset;
display: flex;
align-items: center; // Centers vertically
justify-content: center; // Centers horizontally
font-size: 1rem;
transition: transform 0.2s ease;
width: 20px;

View File

@ -51,4 +51,12 @@ $gradient-hero-dark: linear-gradient(
rgba(88, 28, 135, 0.3) 100%
);
// Services Section Variables
$services-card-padding: 1.5rem;
$services-card-border-radius: 0.75rem;
$services-icon-size: 2.5rem;
$services-grid-gap: 1.5rem;
$services-grid-gap-mobile: 1rem;
$services-header-margin: 2rem;
// Add any other global variables here