From e13dda79bfd4d96498ec966efb03f919d5e0831e Mon Sep 17 00:00:00 2001 From: Sascha Bach Date: Tue, 26 Aug 2025 14:22:10 +0200 Subject: [PATCH] finished service section and its color schemes --- src/components/ThemeToggle.tsx | 12 +- src/components/sections/ServicesSection.tsx | 80 +++-- src/data/FeatureCard.ts | 4 +- src/data/Service.ts | 8 + src/data/service.ts | 8 - src/scss/services-section.scss | 322 +++++++++++++++++--- src/scss/themes.scss | 99 ++++++ src/scss/topbar.scss | 3 + src/scss/variables.scss | 8 + 9 files changed, 467 insertions(+), 77 deletions(-) create mode 100644 src/data/Service.ts delete mode 100644 src/data/service.ts diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx index 5f4a399..9ba80f5 100644 --- a/src/components/ThemeToggle.tsx +++ b/src/components/ThemeToggle.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + type Props = { theme: 'light' | 'dark'; setTheme: React.Dispatch>; @@ -9,8 +11,14 @@ export default function ThemeToggle({ theme, setTheme }: Props) { }; return ( - ); } diff --git a/src/components/sections/ServicesSection.tsx b/src/components/sections/ServicesSection.tsx index 9b6852b..f8c1471 100644 --- a/src/components/sections/ServicesSection.tsx +++ b/src/components/sections/ServicesSection.tsx @@ -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: , + 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: , - title: "Responsive Web Design", - description: "Creating mobile-first, responsive interfaces that provide optimal viewing experiences across all devices and screen sizes." + 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: , + 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: , + 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: , + 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: , + 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 (

- Services I Offer + {title}

- Solutions tailored to your needs + {subtitle}

- {services.map((service, index) => ( -
-
- {service.icon} -

{service.title}

+ {services.map((service, index) => { + const IconComponent = service.icon; + return ( +
+
+ +

+ {service.title} +

+
+
+

+ {service.description} +

+
-
-

{service.description}

-
-
- ))} + ); + })}
diff --git a/src/data/FeatureCard.ts b/src/data/FeatureCard.ts index a4ec7d5..58a0112 100644 --- a/src/data/FeatureCard.ts +++ b/src/data/FeatureCard.ts @@ -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'; } \ No newline at end of file diff --git a/src/data/Service.ts b/src/data/Service.ts new file mode 100644 index 0000000..01bf785 --- /dev/null +++ b/src/data/Service.ts @@ -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'; +} \ No newline at end of file diff --git a/src/data/service.ts b/src/data/service.ts deleted file mode 100644 index 4f866ac..0000000 --- a/src/data/service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ReactNode } from "react"; - -// Services data -export interface Service { - icon: ReactNode; - title: string; - description: string; -} \ No newline at end of file diff --git a/src/scss/services-section.scss b/src/scss/services-section.scss index 6b1c9ba..3cf2432 100644 --- a/src/scss/services-section.scss +++ b/src/scss/services-section.scss @@ -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; - grid-template-columns: 1fr; - gap: $spacing-xl; + 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: $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; + } } } diff --git a/src/scss/themes.scss b/src/scss/themes.scss index 8096dc1..44f5a39 100644 --- a/src/scss/themes.scss +++ b/src/scss/themes.scss @@ -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)}; diff --git a/src/scss/topbar.scss b/src/scss/topbar.scss index b9da69c..c36b7db 100644 --- a/src/scss/topbar.scss +++ b/src/scss/topbar.scss @@ -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; diff --git a/src/scss/variables.scss b/src/scss/variables.scss index 388e2f6..2dc153d 100644 --- a/src/scss/variables.scss +++ b/src/scss/variables.scss @@ -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