feat(ZEE-29): implement design system, dark mode, and JSON content management

- Add comprehensive design system with CSS variables, animations, and utility classes
- Implement dark mode with ThemeProvider (system preference + manual toggle)
- Create JSON-based content management system in src/content/
- Update all pages to use structured JSON content
- Add ThemeProvider component with theme toggle button
- Update Header with glass effect and animated mobile menu
- Update Footer with dark mode support
- Update documentation (README.md, CLAUDE.md, knowledge.md, proto/README.md)
- Sync with Linear (ZEE-29 completed)
This commit is contained in:
Do Siki
2026-01-24 02:30:27 +01:00
parent db464d1c48
commit d1213f9b3f
27 changed files with 3294 additions and 450 deletions
+543 -13
View File
@@ -1,26 +1,556 @@
@import "tailwindcss";
/* ========================================
mozdIT Bt. Design System
======================================== */
/* Design Tokens - Light Theme */
:root {
--background: #ffffff;
--foreground: #171717;
/* Brand Colors */
--color-primary-50: #eff6ff;
--color-primary-100: #dbeafe;
--color-primary-200: #bfdbfe;
--color-primary-300: #93c5fd;
--color-primary-400: #60a5fa;
--color-primary-500: #3b82f6;
--color-primary-600: #2563eb;
--color-primary-700: #1d4ed8;
--color-primary-800: #1e40af;
--color-primary-900: #1e3a8a;
--color-primary-950: #172554;
/* Accent Colors */
--color-accent-50: #eef2ff;
--color-accent-100: #e0e7ff;
--color-accent-200: #c7d2fe;
--color-accent-500: #6366f1;
--color-accent-600: #4f46e5;
/* Success Colors */
--color-success-50: #f0fdf4;
--color-success-500: #22c55e;
--color-success-600: #16a34a;
/* Warning Colors */
--color-warning-50: #fffbeb;
--color-warning-500: #f59e0b;
/* Error Colors */
--color-error-50: #fef2f2;
--color-error-500: #ef4444;
--color-error-600: #dc2626;
/* Neutral Colors - Light Mode */
--color-background: #ffffff;
--color-background-secondary: #f9fafb;
--color-background-tertiary: #f3f4f6;
--color-foreground: #111827;
--color-foreground-secondary: #374151;
--color-foreground-muted: #6b7280;
--color-border: #e5e7eb;
--color-border-light: #f3f4f6;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
/* Transitions */
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-normal: 200ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-bounce: 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
/* Spacing */
--container-padding: 1rem;
--section-spacing: 4rem;
/* Border Radius */
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-full: 9999px;
/* Fonts */
--font-sans: var(--font-geist-sans), system-ui, -apple-system, sans-serif;
--font-mono: var(--font-geist-mono), 'Fira Code', monospace;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
/* Dark Mode */
[data-theme="dark"] {
--color-background: #0f172a;
--color-background-secondary: #1e293b;
--color-background-tertiary: #334155;
--color-foreground: #f8fafc;
--color-foreground-secondary: #e2e8f0;
--color-foreground-muted: #94a3b8;
--color-border: #334155;
--color-border-light: #1e293b;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.3);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.4);
}
/* System preference fallback */
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
:root:not([data-theme="light"]) {
--color-background: #0f172a;
--color-background-secondary: #1e293b;
--color-background-tertiary: #334155;
--color-foreground: #f8fafc;
--color-foreground-secondary: #e2e8f0;
--color-foreground-muted: #94a3b8;
--color-border: #334155;
--color-border-light: #1e293b;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.3);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.4);
}
}
/* Tailwind Theme Extension */
@theme inline {
--color-background: var(--color-background);
--color-foreground: var(--color-foreground);
--font-sans: var(--font-sans);
--font-mono: var(--font-mono);
}
/* Base Styles */
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
background: var(--color-background);
color: var(--color-foreground);
font-family: var(--font-sans);
transition: background-color var(--transition-normal), color var(--transition-normal);
}
/* Selection */
::selection {
background: var(--color-primary-500);
color: white;
}
/* Focus Styles */
:focus-visible {
outline: 2px solid var(--color-primary-500);
outline-offset: 2px;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--color-background-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: var(--radius-full);
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-foreground-muted);
}
/* ========================================
Utility Classes
======================================== */
/* Gradient Backgrounds */
.bg-gradient-hero {
background: linear-gradient(
135deg,
var(--color-primary-50) 0%,
var(--color-accent-50) 50%,
var(--color-primary-100) 100%
);
}
[data-theme="dark"] .bg-gradient-hero {
background: linear-gradient(
135deg,
var(--color-background-secondary) 0%,
var(--color-background-tertiary) 50%,
var(--color-background-secondary) 100%
);
}
/* Glass Effect */
.glass {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
[data-theme="dark"] .glass {
background: rgba(15, 23, 42, 0.8);
}
/* ========================================
Animation Keyframes
======================================== */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
}
/* Animation Classes */
.animate-fade-in {
animation: fadeIn var(--transition-slow) ease-out forwards;
}
.animate-fade-in-up {
animation: fadeInUp 0.5s ease-out forwards;
}
.animate-fade-in-down {
animation: fadeInDown 0.5s ease-out forwards;
}
.animate-slide-in-left {
animation: slideInLeft 0.5s ease-out forwards;
}
.animate-slide-in-right {
animation: slideInRight 0.5s ease-out forwards;
}
.animate-scale-in {
animation: scaleIn 0.3s ease-out forwards;
}
.animate-bounce-slow {
animation: bounce 2s ease-in-out infinite;
}
.animate-pulse-slow {
animation: pulse 2s ease-in-out infinite;
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
.animate-spin-slow {
animation: spin 3s linear infinite;
}
/* Staggered animations for children */
.stagger-children > *:nth-child(1) { animation-delay: 0ms; }
.stagger-children > *:nth-child(2) { animation-delay: 100ms; }
.stagger-children > *:nth-child(3) { animation-delay: 200ms; }
.stagger-children > *:nth-child(4) { animation-delay: 300ms; }
.stagger-children > *:nth-child(5) { animation-delay: 400ms; }
.stagger-children > *:nth-child(6) { animation-delay: 500ms; }
/* Hover Animations */
.hover-lift {
transition: transform var(--transition-normal), box-shadow var(--transition-normal);
}
.hover-lift:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.hover-scale {
transition: transform var(--transition-fast);
}
.hover-scale:hover {
transform: scale(1.02);
}
.hover-glow {
transition: box-shadow var(--transition-normal);
}
.hover-glow:hover {
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
}
/* Button Styles */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
font-weight: 500;
border-radius: var(--radius-md);
transition: all var(--transition-normal);
cursor: pointer;
border: none;
text-decoration: none;
}
.btn:active {
transform: scale(0.98);
}
.btn-primary {
background: var(--color-primary-600);
color: white;
}
.btn-primary:hover {
background: var(--color-primary-700);
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
}
.btn-secondary {
background: transparent;
color: var(--color-primary-600);
border: 2px solid var(--color-primary-600);
}
.btn-secondary:hover {
background: var(--color-primary-50);
}
[data-theme="dark"] .btn-secondary:hover {
background: var(--color-primary-900);
}
/* Card Styles */
.card {
background: var(--color-background);
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
padding: 2rem;
transition: all var(--transition-normal);
}
.card:hover {
border-color: var(--color-primary-200);
box-shadow: var(--shadow-md);
}
[data-theme="dark"] .card:hover {
border-color: var(--color-primary-800);
}
/* Icon Container */
.icon-container {
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
background: var(--color-primary-100);
border-radius: var(--radius-lg);
transition: all var(--transition-normal);
}
.icon-container-lg {
width: 4rem;
height: 4rem;
}
[data-theme="dark"] .icon-container {
background: var(--color-primary-900);
}
.group:hover .icon-container {
background: var(--color-primary-200);
transform: scale(1.1);
}
[data-theme="dark"] .group:hover .icon-container {
background: var(--color-primary-800);
}
/* Section Styles */
.section {
padding: var(--section-spacing) 0;
}
.container {
max-width: 80rem;
margin: 0 auto;
padding: 0 var(--container-padding);
}
@media (min-width: 640px) {
:root {
--container-padding: 1.5rem;
}
}
@media (min-width: 1024px) {
:root {
--container-padding: 2rem;
}
}
/* Link Styles */
.link {
color: var(--color-primary-600);
text-decoration: none;
transition: color var(--transition-fast);
position: relative;
}
.link:hover {
color: var(--color-primary-700);
}
.link-underline::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background: var(--color-primary-600);
transition: width var(--transition-normal);
}
.link-underline:hover::after {
width: 100%;
}
/* Prose adjustments for dark mode */
[data-theme="dark"] .prose {
--tw-prose-body: var(--color-foreground-secondary);
--tw-prose-headings: var(--color-foreground);
--tw-prose-links: var(--color-primary-400);
--tw-prose-bold: var(--color-foreground);
}
/* Reduce motion for accessibility */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
html {
scroll-behavior: auto;
}
}
+7 -4
View File
@@ -1,12 +1,15 @@
import { siteConfig } from '@/config/site'
import { content } from '@/content'
import type { Metadata } from 'next'
const { contact: pageContent } = content.pages
export const metadata: Metadata = {
title: `Kapcsolat | ${siteConfig.general.name}`,
description: 'Vegye fel velünk a kapcsolatot! Segítünk minden IT kérdésében. Email, telefon és online űrlap is rendelkezésére áll.',
title: `${pageContent.meta.title} | ${siteConfig.general.name}`,
description: pageContent.meta.description,
openGraph: {
title: `Kapcsolat | ${siteConfig.general.name}`,
description: 'Vegye fel velünk a kapcsolatot! Segítünk minden IT kérdésében.',
title: `${pageContent.meta.title} | ${siteConfig.general.name}`,
description: pageContent.meta.description,
url: `${siteConfig.general.url}/kapcsolat`,
},
}
+42 -53
View File
@@ -1,8 +1,11 @@
'use client'
import { siteConfig } from '@/config/site'
import { content } from '@/content'
import { useState } from 'react'
const { contact: pageContent } = content.pages
export default function ContactPage() {
const [formData, setFormData] = useState({
name: '',
@@ -20,27 +23,27 @@ export default function ContactPage() {
const newErrors: Record<string, string> = {}
if (!formData.name.trim()) {
newErrors.name = 'A név megadása kötelező'
newErrors.name = pageContent.form.fields.name.error
}
if (!formData.email.trim()) {
newErrors.email = 'Az email cím megadása kötelező'
newErrors.email = pageContent.form.fields.email.errorRequired
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Érvénytelen email cím formátum'
newErrors.email = pageContent.form.fields.email.errorInvalid
}
if (!formData.subject.trim()) {
newErrors.subject = 'A tárgy megadása kötelező'
newErrors.subject = pageContent.form.fields.subject.error
}
if (!formData.message.trim()) {
newErrors.message = 'Az üzenet megadása kötelező'
newErrors.message = pageContent.form.fields.message.errorRequired
} else if (formData.message.trim().length < 10) {
newErrors.message = 'Az üzenet legalább 10 karakter hosszú legyen'
newErrors.message = pageContent.form.fields.message.errorMinLength
}
if (!formData.gdprConsent) {
newErrors.gdprConsent = 'Az adatkezelési tájékoztató elfogadása kötelező'
newErrors.gdprConsent = pageContent.form.fields.gdpr.error
}
setErrors(newErrors)
@@ -93,7 +96,6 @@ export default function ContactPage() {
[name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked : value
}))
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }))
}
@@ -105,10 +107,10 @@ export default function ContactPage() {
<section className="bg-gradient-to-r from-blue-50 to-indigo-50 py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6">
Kapcsolat
{pageContent.hero.title}
</h1>
<p className="text-xl text-gray-600 leading-relaxed">
Vegye fel velünk a kapcsolatot! Szívesen segítünk minden IT kérdésében.
{pageContent.hero.subtitle}
</p>
</div>
</section>
@@ -119,13 +121,13 @@ export default function ContactPage() {
<div>
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
Küldjön üzenetet
{pageContent.form.title}
</h2>
{submitStatus === 'success' && (
<div className="mb-6 p-4 bg-green-50 border border-green-200 rounded-md">
<p className="text-green-800">
Köszönjük üzenetét! Hamarosan felvesszük Önnel a kapcsolatot.
{pageContent.form.successMessage}
</p>
</div>
)}
@@ -133,7 +135,7 @@ export default function ContactPage() {
{submitStatus === 'error' && (
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-800">
Hiba történt az üzenet küldése során. Kérjük, próbálja újra vagy írjon közvetlenül a {siteConfig.contact.email} címre.
{pageContent.form.errorMessage.replace('{email}', siteConfig.contact.email)}
</p>
</div>
)}
@@ -141,7 +143,7 @@ export default function ContactPage() {
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
Név *
{pageContent.form.fields.name.label} *
</label>
<input
type="text"
@@ -152,14 +154,14 @@ export default function ContactPage() {
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.name ? 'border-red-300' : 'border-gray-300'
}`}
placeholder="Az Ön neve"
placeholder={pageContent.form.fields.name.placeholder}
/>
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
Email cím *
{pageContent.form.fields.email.label} *
</label>
<input
type="email"
@@ -170,14 +172,14 @@ export default function ContactPage() {
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.email ? 'border-red-300' : 'border-gray-300'
}`}
placeholder="pelda@email.hu"
placeholder={pageContent.form.fields.email.placeholder}
/>
{errors.email && <p className="mt-1 text-sm text-red-600">{errors.email}</p>}
</div>
<div>
<label htmlFor="subject" className="block text-sm font-medium text-gray-700 mb-1">
Tárgy *
{pageContent.form.fields.subject.label} *
</label>
<input
type="text"
@@ -188,14 +190,14 @@ export default function ContactPage() {
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.subject ? 'border-red-300' : 'border-gray-300'
}`}
placeholder="Miben segíthetünk?"
placeholder={pageContent.form.fields.subject.placeholder}
/>
{errors.subject && <p className="mt-1 text-sm text-red-600">{errors.subject}</p>}
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-1">
Üzenet *
{pageContent.form.fields.message.label} *
</label>
<textarea
id="message"
@@ -206,7 +208,7 @@ export default function ContactPage() {
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.message ? 'border-red-300' : 'border-gray-300'
}`}
placeholder="Írja le részletesen kérését vagy kérdését..."
placeholder={pageContent.form.fields.message.placeholder}
/>
{errors.message && <p className="mt-1 text-sm text-red-600">{errors.message}</p>}
</div>
@@ -220,9 +222,10 @@ export default function ContactPage() {
onChange={handleInputChange}
className="mt-1 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<span className="text-sm text-gray-700">
Elfogadom az <a href="/adatkezelesi-tajekoztato" className="text-blue-600 hover:text-blue-700 underline">adatkezelési tájékoztatót</a> és hozzájárulok személyes adataim kezeléséhez a kapcsolatfelvétel céljából. *
</span>
<span
className="text-sm text-gray-700"
dangerouslySetInnerHTML={{ __html: pageContent.form.fields.gdpr.label + ' *' }}
/>
</label>
{errors.gdprConsent && <p className="mt-1 text-sm text-red-600">{errors.gdprConsent}</p>}
</div>
@@ -232,7 +235,7 @@ export default function ContactPage() {
disabled={isSubmitting}
className="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium py-3 px-4 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
{isSubmitting ? 'Küldés...' : 'Üzenet küldése'}
{isSubmitting ? pageContent.form.submittingButton : pageContent.form.submitButton}
</button>
</form>
</div>
@@ -242,7 +245,7 @@ export default function ContactPage() {
<div className="space-y-8">
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
Elérhetőségek
{pageContent.info.title}
</h2>
<div className="space-y-6">
@@ -251,7 +254,7 @@ export default function ContactPage() {
<span className="text-lg"></span>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-1">Email</h3>
<h3 className="font-semibold text-gray-900 mb-1">{pageContent.info.email.title}</h3>
<a
href={`mailto:${siteConfig.contact.email}`}
className="text-blue-600 hover:text-blue-700"
@@ -259,7 +262,7 @@ export default function ContactPage() {
{siteConfig.contact.email}
</a>
<p className="text-sm text-gray-600 mt-1">
24 órán belül válaszolunk
{pageContent.info.email.responseTime}
</p>
</div>
</div>
@@ -269,7 +272,7 @@ export default function ContactPage() {
<span className="text-lg">🏢</span>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-1">Cég</h3>
<h3 className="font-semibold text-gray-900 mb-1">{pageContent.info.company.title}</h3>
<p className="text-gray-700">{siteConfig.general.name}</p>
<p className="text-sm text-gray-600">{siteConfig.contact.address}</p>
</div>
@@ -280,17 +283,17 @@ export default function ContactPage() {
<span className="text-lg">🌐</span>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-1">Webmail hozzáférés</h3>
<h3 className="font-semibold text-gray-900 mb-1">{pageContent.info.webmail.title}</h3>
<a
href={siteConfig.hero.cta.primary.href}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-700"
>
Webmail belépés
{pageContent.info.webmail.linkText}
</a>
<p className="text-sm text-gray-600 mt-1">
Ügyfeleink számára
{pageContent.info.webmail.subtitle}
</p>
</div>
</div>
@@ -300,30 +303,16 @@ export default function ContactPage() {
{/* FAQ */}
<div className="bg-gray-50 rounded-xl p-8">
<h2 className="text-xl font-bold text-gray-900 mb-6">
Gyakori kérdések
{pageContent.faq.title}
</h2>
<div className="space-y-4">
<div>
<h3 className="font-semibold text-gray-900 mb-2">Milyen gyorsan válaszolnak?</h3>
<p className="text-gray-600 text-sm">
Email üzenetekre 24 órán belül, sürgős esetekben telefonon is elérhetők vagyunk.
</p>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-2">Van ingyenes konzultáció?</h3>
<p className="text-gray-600 text-sm">
Igen! Az első konzultáció mindig ingyenes, hogy megismerjük az Ön igényeit.
</p>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-2">Milyen fizetési módokat fogadnak el?</h3>
<p className="text-gray-600 text-sm">
Banki átutalás, PayPal és kártyás fizetés is lehetséges.
</p>
</div>
{pageContent.faq.items.map((item, index) => (
<div key={index}>
<h3 className="font-semibold text-gray-900 mb-2">{item.question}</h3>
<p className="text-gray-600 text-sm">{item.answer}</p>
</div>
))}
</div>
</div>
</div>
+26 -6
View File
@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Header from "../components/Header";
import Footer from "../components/Footer";
import { ThemeProvider } from "../components/ThemeProvider";
import { siteConfig } from "../config/site";
const geistSans = Geist({
@@ -61,15 +62,34 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="hu">
<html lang="hu" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else if (savedTheme === 'light') {
document.documentElement.setAttribute('data-theme', 'light');
}
})();
`,
}}
/>
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen flex flex-col`}
style={{ background: 'var(--color-background)', color: 'var(--color-foreground)' }}
>
<Header />
<main className="flex-1">
{children}
</main>
<Footer />
<ThemeProvider>
<Header />
<main className="flex-1">
{children}
</main>
<Footer />
</ThemeProvider>
</body>
</html>
);
+198 -66
View File
@@ -1,25 +1,54 @@
import { siteConfig } from '@/config/site'
import { content } from '@/content'
const { home: pageContent } = content.pages
export default function Home() {
return (
<div className="space-y-16">
<div className="space-y-0">
{/* Hero Section */}
<section className="bg-gradient-to-r from-blue-50 to-indigo-50 py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 mb-6 leading-tight">
<section className="bg-gradient-hero py-20 lg:py-28 relative overflow-hidden">
{/* Background decoration */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div
className="absolute -top-40 -right-40 w-80 h-80 rounded-full opacity-30 animate-pulse-slow"
style={{ background: 'var(--color-primary-200)' }}
/>
<div
className="absolute -bottom-40 -left-40 w-96 h-96 rounded-full opacity-20 animate-pulse-slow"
style={{ background: 'var(--color-accent-200)', animationDelay: '1s' }}
/>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center relative z-10">
<h1
className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 leading-tight animate-fade-in-up"
style={{ color: 'var(--color-foreground)' }}
>
{siteConfig.hero.title}
</h1>
<p className="text-lg md:text-xl text-gray-600 max-w-4xl mx-auto mb-8 leading-relaxed">
<p
className="text-lg md:text-xl max-w-4xl mx-auto mb-10 leading-relaxed animate-fade-in-up"
style={{ color: 'var(--color-foreground-muted)', animationDelay: '100ms' }}
>
{siteConfig.hero.description}
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
<div
className="flex flex-col sm:flex-row gap-4 justify-center items-center animate-fade-in-up"
style={{ animationDelay: '200ms' }}
>
<a
href={siteConfig.hero.cta.primary.href}
target={siteConfig.hero.cta.primary.external ? '_blank' : undefined}
rel={siteConfig.hero.cta.primary.external ? 'noopener noreferrer' : undefined}
className="bg-blue-600 hover:bg-blue-700 text-white font-medium px-8 py-4 rounded-md transition-colors text-lg inline-flex items-center gap-2"
className="btn btn-primary text-lg px-8 py-4 group"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg
className="w-5 h-5 transition-transform duration-200 group-hover:scale-110"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10" />
</svg>
{siteConfig.hero.cta.primary.text}
@@ -27,7 +56,7 @@ export default function Home() {
{siteConfig.hero.cta.secondary && (
<a
href={siteConfig.hero.cta.secondary.href}
className="border-2 border-blue-600 text-blue-600 hover:bg-blue-50 font-medium px-8 py-4 rounded-md transition-colors text-lg"
className="btn btn-secondary text-lg px-8 py-4"
>
{siteConfig.hero.cta.secondary.text}
</a>
@@ -37,19 +66,44 @@ export default function Home() {
</section>
{/* USP Section */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 bg-gray-50">
<div className="text-center">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-8">
{siteConfig.about.title}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
{siteConfig.about.usps.map((usp) => (
<div key={usp.id} className="text-center">
<div className="w-16 h-16 bg-blue-100 group-hover:bg-blue-200 rounded-full flex items-center justify-center mx-auto mb-4 transition-colors">
<section
className="py-20"
style={{ background: 'var(--color-background-secondary)' }}
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2
className="text-3xl md:text-4xl font-bold mb-4"
style={{ color: 'var(--color-foreground)' }}
>
{siteConfig.about.title}
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 stagger-children">
{siteConfig.about.usps.map((usp, index) => (
<div
key={usp.id}
className="group text-center p-6 rounded-xl transition-all duration-300 hover-lift animate-fade-in-up"
style={{
background: 'var(--color-background)',
border: '1px solid var(--color-border)',
animationDelay: `${index * 100}ms`
}}
>
<div
className="icon-container icon-container-lg mx-auto mb-4"
>
<span className="text-2xl">{usp.icon}</span>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">{usp.title}</h3>
<p className="text-gray-600">{usp.description}</p>
<h3
className="text-lg font-semibold mb-2 transition-colors duration-200"
style={{ color: 'var(--color-foreground)' }}
>
{usp.title}
</h3>
<p style={{ color: 'var(--color-foreground-muted)' }}>
{usp.description}
</p>
</div>
))}
</div>
@@ -57,64 +111,142 @@ export default function Home() {
</section>
{/* Services Section */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
{siteConfig.services.title}
</h2>
<p className="text-lg text-gray-600 max-w-3xl mx-auto">
{siteConfig.services.subtitle}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
{siteConfig.services.services.map((service) => (
<div key={service.id} className="group bg-white p-8 rounded-xl shadow-sm border border-gray-200 hover:shadow-md transition-all duration-300 hover:border-blue-200">
<div className="w-12 h-12 bg-blue-100 group-hover:bg-blue-200 rounded-lg flex items-center justify-center mb-4 transition-colors">
<span className="text-xl">{service.icon}</span>
<section
className="py-20"
style={{ background: 'var(--color-background)' }}
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2
className="text-3xl md:text-4xl font-bold mb-4"
style={{ color: 'var(--color-foreground)' }}
>
{siteConfig.services.title}
</h2>
<p
className="text-lg max-w-3xl mx-auto"
style={{ color: 'var(--color-foreground-muted)' }}
>
{siteConfig.services.subtitle}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
{siteConfig.services.services.map((service, index) => (
<div
key={service.id}
className="group card hover-lift"
style={{ animationDelay: `${index * 100}ms` }}
>
<div className="icon-container mb-4">
<span className="text-xl">{service.icon}</span>
</div>
<h3
className="text-xl font-semibold mb-3 transition-colors duration-200 group-hover:text-blue-600"
style={{ color: 'var(--color-foreground)' }}
>
{service.title}
</h3>
<p
className="leading-relaxed mb-4"
style={{ color: 'var(--color-foreground-muted)' }}
>
{service.description}
</p>
<div className="mb-4">
<h4
className="text-sm font-semibold mb-2"
style={{ color: 'var(--color-foreground-secondary)' }}
>
{pageContent.serviceFeatures.title}
</h4>
<ul
className="text-sm space-y-1.5"
style={{ color: 'var(--color-foreground-muted)' }}
>
{service.features.map((feature, idx) => (
<li key={idx} className="flex items-start group/item">
<span
className="mr-2 transition-transform duration-200 group-hover/item:scale-125"
style={{ color: 'var(--color-success-500)' }}
>
</span>
{feature}
</li>
))}
</ul>
</div>
<a
href="/kapcsolat"
className="inline-flex items-center font-medium transition-all duration-200 group/link"
style={{ color: 'var(--color-primary-600)' }}
>
{service.ctaText}
<svg
className="w-4 h-4 ml-1 transition-transform duration-200 group-hover/link:translate-x-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</a>
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-3 group-hover:text-blue-600 transition-colors">{service.title}</h3>
<p className="text-gray-600 leading-relaxed">
{service.description}
</p>
<div className="mt-4">
<h4 className="text-sm font-semibold text-gray-700 mb-2">Szolgáltatás jellemzők:</h4>
<ul className="text-sm text-gray-600 space-y-1">
{service.features.map((feature, index) => (
<li key={index} className="flex items-start">
<span className="text-blue-500 mr-2"></span>
{feature}
</li>
))}
</ul>
</div>
<a href="/kapcsolat" className="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium mt-4 transition-colors">
{service.ctaText}
<svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</a>
</div>
))}
))}
</div>
</div>
</section>
{/* CTA Section */}
<section className="bg-gray-900 text-white py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl font-bold mb-4">
Kapcsolatfelvétel az első lépés
<section
className="py-20 relative overflow-hidden"
style={{ background: 'var(--color-foreground)' }}
>
{/* Animated background elements */}
<div className="absolute inset-0 overflow-hidden pointer-events-none opacity-10">
<div
className="absolute top-10 left-10 w-32 h-32 rounded-full animate-float"
style={{ background: 'var(--color-primary-500)' }}
/>
<div
className="absolute bottom-10 right-20 w-24 h-24 rounded-full animate-float"
style={{ background: 'var(--color-accent-500)', animationDelay: '0.5s' }}
/>
<div
className="absolute top-1/2 right-1/4 w-16 h-16 rounded-full animate-float"
style={{ background: 'var(--color-primary-400)', animationDelay: '1s' }}
/>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center relative z-10">
<h2
className="text-3xl md:text-4xl font-bold mb-4"
style={{ color: 'var(--color-background)' }}
>
{pageContent.cta.title}
</h2>
<p className="text-xl text-gray-300 mb-8">
Mutassuk meg, hogyan segíthetünk Önnek megvalósítani címeit!
<p
className="text-xl mb-8 max-w-2xl mx-auto"
style={{ color: 'var(--color-background)', opacity: 0.8 }}
>
{pageContent.cta.subtitle}
</p>
<a
href="/kapcsolat"
className="inline-block bg-blue-600 hover:bg-blue-700 text-white font-medium px-8 py-3 rounded-md transition-colors"
className="btn btn-primary text-lg px-8 py-4 hover-glow"
>
Kapcsolatfelvétel
{pageContent.cta.button}
<svg
className="w-5 h-5 ml-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</a>
</div>
</section>
</div>
);
)
}
+141 -103
View File
@@ -1,153 +1,191 @@
import { siteConfig } from '@/config/site'
import { content } from '@/content'
import type { Metadata } from 'next'
const { about: pageContent } = content.pages
export const metadata: Metadata = {
title: `Rólunk | ${siteConfig.general.name}`,
description: 'Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit. Több mint 10 éve nyújtunk megbízható IT szolgáltatásokat.',
title: `${pageContent.meta.title} | ${siteConfig.general.name}`,
description: pageContent.meta.description,
openGraph: {
title: `Rólunk | ${siteConfig.general.name}`,
description: 'Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit.',
title: `${pageContent.meta.title} | ${siteConfig.general.name}`,
description: pageContent.meta.ogDescription,
url: `${siteConfig.general.url}/rolunk`,
},
}
export default function AboutPage() {
return (
<div className="space-y-16 py-8">
<div className="space-y-0">
{/* Hero Section */}
<section className="bg-gradient-to-r from-blue-50 to-indigo-50 py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6">
Rólunk
<section className="bg-gradient-hero py-16 lg:py-24 relative overflow-hidden">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div
className="absolute -top-20 -right-20 w-60 h-60 rounded-full opacity-20 animate-pulse-slow"
style={{ background: 'var(--color-primary-300)' }}
/>
</div>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center relative z-10">
<h1
className="text-4xl md:text-5xl font-bold mb-6 animate-fade-in-up"
style={{ color: 'var(--color-foreground)' }}
>
{pageContent.hero.title}
</h1>
<p className="text-xl text-gray-600 leading-relaxed">
Több mint 10 éve biztosítunk megbízható IT infrastruktúrát és személyes ügyfélszolgálatot
<p
className="text-xl leading-relaxed animate-fade-in-up"
style={{ color: 'var(--color-foreground-muted)', animationDelay: '100ms' }}
>
{pageContent.hero.subtitle}
</p>
</div>
</section>
{/* Story Section */}
<section className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="prose prose-lg mx-auto">
<h2 className="text-3xl font-bold text-gray-900 mb-6">Történetünk</h2>
<div className="space-y-6 text-gray-700 leading-relaxed">
<p>
A <strong>mozdIT Bt.</strong> 2010-ben alakult azzal a céllal, hogy kisvállalkozások és
magánszemélyek számára nyújtson megbízható, személyes IT szolgáltatásokat.
Alapítóink több évtizedes tapasztalattal rendelkeznek a rendszeradminisztráció
és webfejlesztés területén.
</p>
<section
className="py-16"
style={{ background: 'var(--color-background)' }}
>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="prose prose-lg mx-auto">
<h2
className="text-3xl font-bold mb-6"
style={{ color: 'var(--color-foreground)' }}
>
{pageContent.story.title}
</h2>
<p>
Kezdetben néhány ügyfél weboldalának üzemeltetésével indultunk, ma pedig
több száz domain és email fiók működését biztosítjuk. Növekedésünk során
mindig szem előtt tartottuk az alapelveinket: <em>megbízhatóság, személyes
kapcsolat és műszaki kiválóság</em>.
</p>
<p>
Csapatunk folyamatosan képezi magát a legújabb technológiák terén, hogy
ügyfeleink mindig korszerű és biztonságos megoldásokat kapjanak. Büszkék
vagyunk arra, hogy sok ügyfelünkkel évek óta tartjuk a kapcsolatot, és
számos projektet vittünk sikerre közösen.
</p>
<div
className="space-y-6 leading-relaxed"
style={{ color: 'var(--color-foreground-secondary)' }}
>
{pageContent.story.paragraphs.map((paragraph, index) => (
<p
key={index}
dangerouslySetInnerHTML={{ __html: paragraph }}
/>
))}
</div>
</div>
</div>
</section>
{/* Mission & Values */}
<section className="bg-gray-50 py-16">
<section
className="py-16"
style={{ background: 'var(--color-background-secondary)' }}
>
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900 mb-4">
Küldetésünk és értékeink
<h2
className="text-3xl font-bold mb-4"
style={{ color: 'var(--color-foreground)' }}
>
{pageContent.mission.title}
</h2>
<p className="text-lg text-gray-600 max-w-3xl mx-auto">
Minden nap azért dolgozunk, hogy ügyfeleink digitális jelenléte biztonságos,
stabil és hatékony legyen.
<p
className="text-lg max-w-3xl mx-auto"
style={{ color: 'var(--color-foreground-muted)' }}
>
{pageContent.mission.subtitle}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="text-center">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl">🛡</span>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 stagger-children">
{pageContent.mission.values.map((item, index) => (
<div
key={item.title}
className="group text-center p-8 rounded-xl hover-lift animate-fade-in-up"
style={{
background: 'var(--color-background)',
border: '1px solid var(--color-border)',
animationDelay: `${index * 100}ms`
}}
>
<div className="icon-container icon-container-lg mx-auto mb-4">
<span className="text-2xl">{item.icon}</span>
</div>
<h3
className="text-xl font-semibold mb-3"
style={{ color: 'var(--color-foreground)' }}
>
{item.title}
</h3>
<p style={{ color: 'var(--color-foreground-muted)' }}>
{item.description}
</p>
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-3">Megbízhatóság</h3>
<p className="text-gray-600">
99.9% uptime és 24/7 monitoring biztosítja, hogy szolgáltatásaink mindig
elérhetők legyenek.
</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl">👥</span>
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-3">Személyes kapcsolat</h3>
<p className="text-gray-600">
Minden ügyfél számít számunkra. Személyre szabott megoldásokat kínálunk
és mindig elérhetők vagyunk.
</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl"></span>
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-3">Műszaki kiválóság</h3>
<p className="text-gray-600">
Korszerű technológiák és bevált gyakorlatok alkalmazásával biztosítjuk
a legmagasabb színvonalú szolgáltatást.
</p>
</div>
))}
</div>
</div>
</section>
{/* Team Section */}
<section className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900 mb-4">
Szakértő csapat
</h2>
<p className="text-lg text-gray-600">
Tapasztalt IT szakemberek, akik szenvedélyesen dolgoznak az ügyfeleink sikeréért
</p>
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8">
<div className="prose prose-lg mx-auto">
<p className="text-gray-700 leading-relaxed">
Csapatunk rendszeradminisztrátorokból, webfejlesztőkből és ügyfélszolgálati
szakértőkből áll. Mindannyian több mint 10 éves tapasztalattal rendelkeznek
a maguk területén, és folyamatosan követik a technológiai újdonságokat.
</p>
<p className="text-gray-700 leading-relaxed">
Hiszünk abban, hogy a kommunikáció és a műszaki tudás együtt teremti meg
a tökéletes ügyfélélményt. Ezért minden munkatársunk nemcsak technikai
szakértő, hanem kiváló kommunikátor is.
<section
className="py-16"
style={{ background: 'var(--color-background)' }}
>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2
className="text-3xl font-bold mb-4"
style={{ color: 'var(--color-foreground)' }}
>
{pageContent.team.title}
</h2>
<p
className="text-lg"
style={{ color: 'var(--color-foreground-muted)' }}
>
{pageContent.team.subtitle}
</p>
</div>
<div className="card">
<div className="prose prose-lg mx-auto">
{pageContent.team.paragraphs.map((paragraph, index) => (
<p
key={index}
className="leading-relaxed"
style={{ color: 'var(--color-foreground-secondary)' }}
>
{paragraph}
</p>
))}
</div>
</div>
</div>
</section>
{/* CTA Section */}
<section className="bg-gray-900 text-white py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl font-bold mb-4">
Legyen Ön is elégedett ügyfelünk!
<section
className="py-16 relative overflow-hidden"
style={{ background: 'var(--color-foreground)' }}
>
<div className="absolute inset-0 overflow-hidden pointer-events-none opacity-10">
<div
className="absolute top-10 right-20 w-24 h-24 rounded-full animate-float"
style={{ background: 'var(--color-primary-500)' }}
/>
</div>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center relative z-10">
<h2
className="text-3xl font-bold mb-4"
style={{ color: 'var(--color-background)' }}
>
{pageContent.cta.title}
</h2>
<p className="text-xl text-gray-300 mb-8">
Vegye fel velünk a kapcsolatot, és beszéljük meg, hogyan segíthetünk Önnek.
<p
className="text-xl mb-8"
style={{ color: 'var(--color-background)', opacity: 0.8 }}
>
{pageContent.cta.subtitle}
</p>
<a
href="/kapcsolat"
className="inline-block bg-blue-600 hover:bg-blue-700 text-white font-medium px-8 py-3 rounded-md transition-colors"
className="btn btn-primary text-lg px-8 py-4 hover-glow"
>
Kapcsolatfelvétel
{pageContent.cta.button}
</a>
</div>
</section>
+42 -107
View File
@@ -1,12 +1,15 @@
import { siteConfig } from '@/config/site'
import { content } from '@/content'
import type { Metadata } from 'next'
const { services: pageContent } = content.pages
export const metadata: Metadata = {
title: `Szolgáltatások | ${siteConfig.general.name}`,
description: 'Webhosting, email szolgáltatás és DNS adminisztráció professzionális szinten. Ismerje meg részletes szolgáltatásainkat.',
title: `${pageContent.meta.title} | ${siteConfig.general.name}`,
description: pageContent.meta.description,
openGraph: {
title: `Szolgáltatások | ${siteConfig.general.name}`,
description: 'Webhosting, email szolgáltatás és DNS adminisztráció professzionális szinten.',
title: `${pageContent.meta.title} | ${siteConfig.general.name}`,
description: pageContent.meta.ogDescription,
url: `${siteConfig.general.url}/szolgaltatasok`,
},
}
@@ -18,10 +21,10 @@ export default function ServicesPage() {
<section className="bg-gradient-to-r from-blue-50 to-indigo-50 py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6">
Szolgáltatásaink
{pageContent.hero.title}
</h1>
<p className="text-xl text-gray-600 leading-relaxed">
Teljes körű IT megoldások kisvállalkozások és magánszemélyek számára
{pageContent.hero.subtitle}
</p>
</div>
</section>
@@ -39,7 +42,7 @@ export default function ServicesPage() {
<p className="text-gray-600 leading-relaxed mb-6">{service.description}</p>
<div className="mb-6">
<h3 className="text-lg font-semibold text-gray-900 mb-3">Szolgáltatás jellemzők:</h3>
<h3 className="text-lg font-semibold text-gray-900 mb-3">{content.common.labels.features}</h3>
<ul className="space-y-2">
{service.features.map((feature, index) => (
<li key={index} className="flex items-start">
@@ -69,97 +72,35 @@ export default function ServicesPage() {
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900 mb-4">
Részletes szolgáltatásleírás
{pageContent.details.title}
</h2>
<p className="text-lg text-gray-600">
Minden szolgáltatásunk mögött évtizedes tapasztalat és modern technológia áll
{pageContent.details.subtitle}
</p>
</div>
<div className="space-y-12">
{/* Web Hosting Details */}
<div className="bg-white rounded-xl p-8 shadow-sm">
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0">
<span className="text-xl">🌐</span>
</div>
<div className="flex-1">
<h3 className="text-2xl font-bold text-gray-900 mb-4">Web Hosting részletesen</h3>
<div className="prose prose-lg text-gray-700">
<p>
Weboldalak biztonságos és gyors üzemeltetése SSD tárolással, automatikus biztonsági mentéssel
és 24/7 monitoringgal. Támogatjuk a PHP, Python, Node.js technológiákat és MySQL/PostgreSQL
adatbázisokat.
</p>
<h4 className="text-lg font-semibold text-gray-900 mt-6 mb-3">Technikai specifikációk:</h4>
<ul className="space-y-1">
<li>SSD tárhely 10GB-tól 500GB-ig</li>
<li>Havi adatforgalom: korlátlan</li>
<li>SSL tanúsítványok (Let's Encrypt vagy prémium)</li>
<li>CDN integráció a gyorsabb betöltésért</li>
<li>Automatikus napi biztonsági mentés</li>
<li>cPanel vagy egyedi admin felület</li>
</ul>
{pageContent.details.services.map((service) => (
<div key={service.title} className="bg-white rounded-xl p-8 shadow-sm">
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0">
<span className="text-xl">{service.icon}</span>
</div>
<div className="flex-1">
<h3 className="text-2xl font-bold text-gray-900 mb-4">{service.title}</h3>
<div className="prose prose-lg text-gray-700">
<p>{service.description}</p>
<h4 className="text-lg font-semibold text-gray-900 mt-6 mb-3">{service.specs.title}</h4>
<ul className="space-y-1">
{service.specs.items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
</div>
</div>
</div>
</div>
{/* Email Service Details */}
<div className="bg-white rounded-xl p-8 shadow-sm">
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0">
<span className="text-xl"></span>
</div>
<div className="flex-1">
<h3 className="text-2xl font-bold text-gray-900 mb-4">Email szolgáltatás részletesen</h3>
<div className="prose prose-lg text-gray-700">
<p>
Professzionális email fiókok saját domain névvel, spam szűréssel és vírusvédelemmel.
Webmail felület és IMAP/POP3/SMTP támogatás minden népszerű email klienssel.
</p>
<h4 className="text-lg font-semibold text-gray-900 mt-6 mb-3">Email funkciók:</h4>
<ul className="space-y-1">
<li>Korlátlan email fiókok létrehozása</li>
<li>5GB-50GB tárhelyet fiókként</li>
<li>Webmail hozzáférés (Roundcube/SOGo)</li>
<li>Mobilalkalmazás szinkronizáció</li>
<li>Spam és vírusszűrés</li>
<li>Email továbbítás és automatikus válaszok</li>
<li>Backup és archiválás</li>
</ul>
</div>
</div>
</div>
</div>
{/* DNS Administration Details */}
<div className="bg-white rounded-xl p-8 shadow-sm">
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0">
<span className="text-xl"></span>
</div>
<div className="flex-1">
<h3 className="text-2xl font-bold text-gray-900 mb-4">DNS adminisztráció részletesen</h3>
<div className="prose prose-lg text-gray-700">
<p>
Teljes DNS kezelés domain regisztrációval, átvitellel és professzionális beállításokkal.
Gyors propagáció és megbízható névszerverek világszerte.
</p>
<h4 className="text-lg font-semibold text-gray-900 mt-6 mb-3">DNS szolgáltatások:</h4>
<ul className="space-y-1">
<li>Domain regisztráció (.hu, .com, .eu, stb.)</li>
<li>Domain átvitel más szolgáltatótól</li>
<li>DNS rekord kezelés (A, CNAME, MX, TXT)</li>
<li>Subdomain beállítások</li>
<li>Redirect és forwarding szolgáltatások</li>
<li>DNSSEC támogatás</li>
<li>API hozzáférés fejlesztőknek</li>
</ul>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</section>
@@ -168,24 +109,18 @@ export default function ServicesPage() {
<section className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="bg-blue-50 rounded-xl p-8 text-center">
<h2 className="text-2xl font-bold text-gray-900 mb-4">
Műszaki támogatás
{pageContent.support.title}
</h2>
<p className="text-lg text-gray-700 mb-6">
Minden szolgáltatásunkhoz teljes körű műszaki támogatást biztosítunk
{pageContent.support.subtitle}
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 text-sm">
<div>
<h3 className="font-semibold text-gray-900 mb-2">Email támogatás</h3>
<p className="text-gray-600">24 órán belüli válasz</p>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-2">Telefonos segítség</h3>
<p className="text-gray-600">Munkaidőben elérhető</p>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-2">Sürgős esetek</h3>
<p className="text-gray-600">Azonnali beavatkozás</p>
</div>
{pageContent.support.channels.map((channel) => (
<div key={channel.title}>
<h3 className="font-semibold text-gray-900 mb-2">{channel.title}</h3>
<p className="text-gray-600">{channel.description}</p>
</div>
))}
</div>
</div>
</section>
@@ -194,17 +129,17 @@ export default function ServicesPage() {
<section className="bg-gray-900 text-white py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl font-bold mb-4">
Kezdjük el a közös munkát!
{pageContent.cta.title}
</h2>
<p className="text-xl text-gray-300 mb-8">
Vegye fel velünk a kapcsolatot ingyenes konzultációért és egyedi ajánlatért.
{pageContent.cta.subtitle}
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="/kapcsolat"
className="inline-block bg-blue-600 hover:bg-blue-700 text-white font-medium px-8 py-3 rounded-md transition-colors"
>
Kapcsolatfelvétel
{pageContent.cta.primaryButton}
</a>
<a
href={siteConfig.hero.cta.primary.href}
@@ -212,7 +147,7 @@ export default function ServicesPage() {
rel="noopener noreferrer"
className="inline-block border-2 border-white text-white hover:bg-white hover:text-gray-900 font-medium px-8 py-3 rounded-md transition-colors"
>
Webmail belépés
{pageContent.cta.secondaryButton}
</a>
</div>
</div>
+124 -25
View File
@@ -1,36 +1,92 @@
'use client'
import { siteConfig } from '@/config/site'
export default function Footer() {
return (
<footer className="bg-gray-50 border-t border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<footer
className="border-t"
style={{
background: 'var(--color-background-secondary)',
borderColor: 'var(--color-border)'
}}
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 lg:gap-12">
{/* Company Info */}
<div className="md:col-span-2">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
<a
href="/"
className="inline-flex items-center gap-2 text-xl font-bold mb-4 transition-colors duration-200"
style={{ color: 'var(--color-primary-600)' }}
>
{siteConfig.general.name}
</h3>
<p className="text-gray-600 mb-4 max-w-md">
<span
className="inline-block w-2 h-2 rounded-full animate-pulse-slow"
style={{ background: 'var(--color-success-500)' }}
/>
</a>
<p
className="mb-6 max-w-md leading-relaxed"
style={{ color: 'var(--color-foreground-muted)' }}
>
{siteConfig.general.description}
</p>
<div className="flex space-x-4">
<div className="text-sm text-gray-500">
<p>Email: {siteConfig.contact.email}</p>
<p>Cég: {siteConfig.general.name}</p>
<p>Székhely: {siteConfig.contact.address}</p>
<div className="space-y-2 text-sm" style={{ color: 'var(--color-foreground-muted)' }}>
<a
href={`mailto:${siteConfig.contact.email}`}
className="flex items-center gap-2 group transition-colors duration-200 hover:text-blue-600"
style={{ color: 'var(--color-foreground-secondary)' }}
>
<span
className="flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center transition-all duration-200 group-hover:scale-110"
style={{ background: 'var(--color-primary-100)' }}
>
</span>
<span>{siteConfig.contact.email}</span>
</a>
<div
className="flex items-center gap-2"
style={{ color: 'var(--color-foreground-secondary)' }}
>
<span
className="flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center"
style={{ background: 'var(--color-primary-100)' }}
>
🏢
</span>
<span>{siteConfig.contact.address}</span>
</div>
</div>
</div>
{/* Navigation Links */}
<div>
<h3 className="text-sm font-semibold text-gray-900 uppercase tracking-wide mb-4">
<h3
className="text-sm font-semibold uppercase tracking-wider mb-4"
style={{ color: 'var(--color-foreground)' }}
>
Navigáció
</h3>
<ul className="space-y-2">
<ul className="space-y-3">
{siteConfig.navigation.footer.map((item) => (
<li key={item.href}>
<a href={item.href} className="text-gray-600 hover:text-blue-600 transition-colors">
<a
href={item.href}
className="group flex items-center gap-2 text-sm transition-all duration-200"
style={{ color: 'var(--color-foreground-muted)' }}
onMouseEnter={(e) => {
e.currentTarget.style.color = 'var(--color-primary-600)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'var(--color-foreground-muted)'
}}
>
<span
className="w-1.5 h-1.5 rounded-full transition-all duration-200 group-hover:scale-150"
style={{ background: 'var(--color-primary-500)' }}
/>
{item.label}
</a>
</li>
@@ -40,32 +96,59 @@ export default function Footer() {
{/* Services */}
<div>
<h3 className="text-sm font-semibold text-gray-900 uppercase tracking-wide mb-4">
<h3
className="text-sm font-semibold uppercase tracking-wider mb-4"
style={{ color: 'var(--color-foreground)' }}
>
Szolgáltatások
</h3>
<ul className="space-y-2">
<ul className="space-y-3">
{siteConfig.services.services.map((service) => (
<li key={service.id} className="text-gray-600">
<li
key={service.id}
className="flex items-center gap-2 text-sm"
style={{ color: 'var(--color-foreground-muted)' }}
>
<span className="text-base">{service.icon}</span>
{service.title}
</li>
))}
<li className="text-gray-600">Műszaki támogatás</li>
<li
className="flex items-center gap-2 text-sm"
style={{ color: 'var(--color-foreground-muted)' }}
>
<span className="text-base">🛠</span>
Műszaki támogatás
</li>
</ul>
</div>
</div>
{/* Bottom section */}
<div className="border-t border-gray-200 pt-8 mt-8">
<div className="flex flex-col sm:flex-row justify-between items-center">
<p className="text-gray-500 text-sm">
<div
className="border-t pt-8 mt-8"
style={{ borderColor: 'var(--color-border)' }}
>
<div className="flex flex-col sm:flex-row justify-between items-center gap-4">
<p
className="text-sm"
style={{ color: 'var(--color-foreground-muted)' }}
>
{siteConfig.footer.copyright}
</p>
<div className="flex space-x-4 mt-4 sm:mt-0">
<div className="flex items-center gap-6">
{siteConfig.footer.links.map((link) => (
<a
key={link.href}
href={link.href}
className="text-gray-500 hover:text-blue-600 text-sm transition-colors"
className="text-sm transition-all duration-200 link-underline relative"
style={{ color: 'var(--color-foreground-muted)' }}
onMouseEnter={(e) => {
e.currentTarget.style.color = 'var(--color-primary-600)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'var(--color-foreground-muted)'
}}
>
{link.label}
</a>
@@ -73,7 +156,23 @@ export default function Footer() {
</div>
</div>
</div>
{/* Tech badge */}
<div className="mt-8 flex justify-center">
<div
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-xs"
style={{
background: 'var(--color-background-tertiary)',
color: 'var(--color-foreground-muted)'
}}
>
<span>Powered by</span>
<span className="font-medium" style={{ color: 'var(--color-foreground)' }}>Next.js</span>
<span></span>
<span className="font-medium" style={{ color: 'var(--color-foreground)' }}>Tailwind CSS</span>
</div>
</div>
</div>
</footer>
);
}
)
}
+158 -45
View File
@@ -1,93 +1,206 @@
'use client'
import { siteConfig } from '@/config/site'
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { ThemeToggle } from './ThemeProvider'
export default function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false)
const [isScrolled, setIsScrolled] = useState(false)
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 10)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
const toggleMenu = () => {
setIsMenuOpen(!isMenuOpen)
}
// Close menu on escape key
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') setIsMenuOpen(false)
}
document.addEventListener('keydown', handleEscape)
return () => document.removeEventListener('keydown', handleEscape)
}, [])
return (
<header className="bg-white border-b border-gray-200 sticky top-0 z-50">
<header
className={`sticky top-0 z-50 transition-all duration-300 ${
isScrolled
? 'glass shadow-md'
: ''
}`}
style={{
background: isScrolled ? undefined : 'var(--color-background)',
borderBottom: `1px solid var(--color-border)`,
}}
>
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<div className="flex-shrink-0">
<a href="/" className="text-xl font-bold text-blue-600 hover:text-blue-700">
{siteConfig.general.name}
<a
href="/"
className="group flex items-center gap-2 text-xl font-bold transition-all duration-200"
style={{ color: 'var(--color-primary-600)' }}
>
<span className="relative">
<span className="group-hover:opacity-0 transition-opacity duration-200">
{siteConfig.general.name}
</span>
<span
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
style={{ color: 'var(--color-primary-700)' }}
>
{siteConfig.general.name}
</span>
</span>
<span
className="inline-block w-2 h-2 rounded-full animate-pulse-slow"
style={{ background: 'var(--color-success-500)' }}
title="Online"
/>
</a>
</div>
{/* Desktop Navigation */}
<div className="hidden md:block">
<div className="flex items-center space-x-8">
{siteConfig.navigation.main.map((item) => (
<a
key={item.href}
href={item.href}
target={item.external ? '_blank' : undefined}
rel={item.external ? 'noopener noreferrer' : undefined}
className={item.label === 'Kapcsolat'
? "bg-blue-600 text-white px-4 py-2 rounded-md text-sm font-medium hover:bg-blue-700 transition-colors"
: "text-gray-900 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors"
}
>
<div className="hidden md:flex items-center space-x-1">
{siteConfig.navigation.main.map((item, index) => (
<a
key={item.href}
href={item.href}
target={item.external ? '_blank' : undefined}
rel={item.external ? 'noopener noreferrer' : undefined}
className={`relative px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${
item.label === 'Kapcsolat'
? 'btn btn-primary text-white'
: 'hover-scale'
}`}
style={item.label !== 'Kapcsolat' ? {
color: 'var(--color-foreground)',
} : undefined}
>
{item.label !== 'Kapcsolat' && (
<span
className="absolute inset-0 rounded-lg opacity-0 hover:opacity-100 transition-opacity duration-200"
style={{ background: 'var(--color-primary-50)' }}
/>
)}
<span className="relative z-10">
{item.label}
</a>
))}
{item.external && (
<svg className="inline-block w-3 h-3 ml-1 -mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
)}
</span>
</a>
))}
{/* Theme Toggle */}
<div className="ml-2 pl-2" style={{ borderLeft: `1px solid var(--color-border)` }}>
<ThemeToggle />
</div>
</div>
{/* Mobile menu button */}
<div className="md:hidden">
<div className="md:hidden flex items-center gap-2">
<ThemeToggle />
<button
type="button"
onClick={toggleMenu}
className="text-gray-500 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
className="p-2 rounded-lg transition-all duration-200"
style={{
color: 'var(--color-foreground-muted)',
background: isMenuOpen ? 'var(--color-background-tertiary)' : 'transparent'
}}
aria-expanded={isMenuOpen}
>
<span className="sr-only">{isMenuOpen ? 'Close main menu' : 'Open main menu'}</span>
{isMenuOpen ? (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
) : (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
)}
<div className="relative w-6 h-6">
{/* Hamburger to X animation */}
<span
className={`absolute h-0.5 w-6 rounded-full transform transition-all duration-300 ${
isMenuOpen ? 'rotate-45 top-3' : 'rotate-0 top-1'
}`}
style={{ background: 'currentColor' }}
/>
<span
className={`absolute h-0.5 w-6 rounded-full top-3 transition-all duration-200 ${
isMenuOpen ? 'opacity-0 translate-x-2' : 'opacity-100'
}`}
style={{ background: 'currentColor' }}
/>
<span
className={`absolute h-0.5 w-6 rounded-full transform transition-all duration-300 ${
isMenuOpen ? '-rotate-45 top-3' : 'rotate-0 top-5'
}`}
style={{ background: 'currentColor' }}
/>
</div>
</button>
</div>
</div>
{/* Mobile Navigation - Dynamic visibility */}
<div className={`md:hidden absolute top-full left-0 right-0 bg-white border-b border-gray-200 shadow-lg transition-all duration-300 ease-in-out ${
isMenuOpen
? 'opacity-100 visible'
: 'opacity-0 invisible'
}`}>
<div className="px-2 pt-2 pb-3 space-y-1">
{siteConfig.navigation.main.map((item) => (
{/* Mobile Navigation */}
<div
className={`md:hidden overflow-hidden transition-all duration-300 ease-in-out ${
isMenuOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div
className="py-3 space-y-1 border-t"
style={{ borderColor: 'var(--color-border)' }}
>
{siteConfig.navigation.main.map((item, index) => (
<a
key={item.href}
href={item.href}
target={item.external ? '_blank' : undefined}
rel={item.external ? 'noopener noreferrer' : undefined}
onClick={() => setIsMenuOpen(false)}
className={item.label === 'Kapcsolat'
? "block px-3 py-2 rounded-md text-base font-medium bg-blue-600 text-white"
: "block px-3 py-2 rounded-md text-base font-medium text-gray-900 hover:text-blue-600 hover:bg-blue-50"
}
className={`block px-4 py-3 rounded-lg text-base font-medium transition-all duration-200 ${
item.label === 'Kapcsolat' ? 'btn btn-primary text-white mt-2' : ''
}`}
style={{
animationDelay: `${index * 50}ms`,
...(item.label !== 'Kapcsolat' ? {
color: 'var(--color-foreground)',
background: 'transparent',
} : {})
}}
onMouseEnter={(e) => {
if (item.label !== 'Kapcsolat') {
e.currentTarget.style.background = 'var(--color-primary-50)'
e.currentTarget.style.color = 'var(--color-primary-600)'
}
}}
onMouseLeave={(e) => {
if (item.label !== 'Kapcsolat') {
e.currentTarget.style.background = 'transparent'
e.currentTarget.style.color = 'var(--color-foreground)'
}
}}
>
{item.label}
<span className="flex items-center justify-between">
{item.label}
{item.external && (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
)}
</span>
</a>
))}
</div>
</div>
</nav>
</header>
);
}
)
}
+154
View File
@@ -0,0 +1,154 @@
'use client'
import { createContext, useContext, useEffect, useState, ReactNode } from 'react'
type Theme = 'light' | 'dark' | 'system'
interface ThemeContextType {
theme: Theme
resolvedTheme: 'light' | 'dark'
setTheme: (theme: Theme) => void
toggleTheme: () => void
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
// Return default values when not in provider (SSR/prerendering)
return {
theme: 'system' as const,
resolvedTheme: 'light' as const,
setTheme: () => {},
toggleTheme: () => {},
}
}
return context
}
interface ThemeProviderProps {
children: ReactNode
defaultTheme?: Theme
}
export function ThemeProvider({ children, defaultTheme = 'system' }: ThemeProviderProps) {
const [theme, setThemeState] = useState<Theme>(defaultTheme)
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light')
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
const savedTheme = localStorage.getItem('theme') as Theme | null
if (savedTheme) {
setThemeState(savedTheme)
}
}, [])
useEffect(() => {
if (!mounted) return
const root = document.documentElement
let resolved: 'light' | 'dark'
if (theme === 'system') {
resolved = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
root.removeAttribute('data-theme')
} else {
resolved = theme
root.setAttribute('data-theme', theme)
}
setResolvedTheme(resolved)
}, [theme, mounted])
useEffect(() => {
if (!mounted) return
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = (e: MediaQueryListEvent) => {
if (theme === 'system') {
setResolvedTheme(e.matches ? 'dark' : 'light')
}
}
mediaQuery.addEventListener('change', handleChange)
return () => mediaQuery.removeEventListener('change', handleChange)
}, [theme, mounted])
const setTheme = (newTheme: Theme) => {
setThemeState(newTheme)
localStorage.setItem('theme', newTheme)
}
const toggleTheme = () => {
const newTheme = resolvedTheme === 'light' ? 'dark' : 'light'
setTheme(newTheme)
}
// Prevent hydration mismatch - render children without context until mounted
if (!mounted) {
return <>{children}</>
}
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
// Theme toggle button component
export function ThemeToggle() {
const { resolvedTheme, toggleTheme } = useTheme()
return (
<button
onClick={toggleTheme}
className="relative p-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all duration-200 group"
style={{
background: resolvedTheme === 'dark' ? 'var(--color-background-tertiary)' : 'var(--color-background-tertiary)',
}}
aria-label={resolvedTheme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
>
{/* Sun icon */}
<svg
className={`w-5 h-5 transition-all duration-300 ${
resolvedTheme === 'dark'
? 'opacity-0 rotate-90 scale-0'
: 'opacity-100 rotate-0 scale-100'
}`}
style={{ color: 'var(--color-foreground)', position: resolvedTheme === 'dark' ? 'absolute' : 'relative' }}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
{/* Moon icon */}
<svg
className={`w-5 h-5 transition-all duration-300 ${
resolvedTheme === 'dark'
? 'opacity-100 rotate-0 scale-100'
: 'opacity-0 -rotate-90 scale-0'
}`}
style={{ color: 'var(--color-foreground)', position: resolvedTheme === 'light' ? 'absolute' : 'relative' }}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
</button>
)
}
+17
View File
@@ -0,0 +1,17 @@
{
"buttons": {
"contact": "Kapcsolatfelvétel",
"learnMore": "További információk",
"webmail": "Webmail belépés",
"sendMessage": "Üzenet küldése"
},
"labels": {
"required": "*",
"features": "Szolgáltatás jellemzők:"
},
"validation": {
"required": "Ez a mező kötelező",
"invalidEmail": "Érvénytelen email cím formátum",
"minLength": "Legalább {min} karakter szükséges"
}
}
+62
View File
@@ -0,0 +1,62 @@
/**
* Content Loader - Centralized content management
*
* This module provides type-safe access to all JSON content files.
* Content can be easily modified by editing the JSON files without code changes.
*
* Usage:
* import { content, getPageContent } from '@/content'
*
* // Access specific page content
* const aboutContent = content.pages.about
*
* // Or use the helper function
* const servicesContent = getPageContent('services')
*/
import type {
SiteContent,
CommonContent,
HomePageContent,
AboutPageContent,
ServicesPageContent,
ContactPageContent
} from './types'
// Import JSON files
import commonJson from './common.json'
import homeJson from './pages/home.json'
import aboutJson from './pages/about.json'
import servicesJson from './pages/services.json'
import contactJson from './pages/contact.json'
// Type assertions for JSON imports
const common: CommonContent = commonJson as CommonContent
const home: HomePageContent = homeJson as HomePageContent
const about: AboutPageContent = aboutJson as AboutPageContent
const services: ServicesPageContent = servicesJson as ServicesPageContent
const contact: ContactPageContent = contactJson as ContactPageContent
// Combined content object
export const content: SiteContent = {
common,
pages: {
home,
about,
services,
contact
}
}
// Helper function to get page content with type safety
export function getPageContent<T extends keyof SiteContent['pages']>(
page: T
): SiteContent['pages'][T] {
return content.pages[page]
}
// Export individual content for direct imports
export { common, home, about, services, contact }
// Re-export types
export * from './types'
+53
View File
@@ -0,0 +1,53 @@
{
"meta": {
"title": "Rólunk",
"description": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit. Több mint 10 éve nyújtunk megbízható IT szolgáltatásokat.",
"ogDescription": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit."
},
"hero": {
"title": "Rólunk",
"subtitle": "Több mint 10 éve biztosítunk megbízható IT infrastruktúrát és személyes ügyfélszolgálatot"
},
"story": {
"title": "Történetünk",
"paragraphs": [
"A <strong>mozdIT Bt.</strong> 2010-ben alakult azzal a céllal, hogy kisvállalkozások és magánszemélyek számára nyújtson megbízható, személyes IT szolgáltatásokat. Alapítóink több évtizedes tapasztalattal rendelkeznek a rendszeradminisztráció és webfejlesztés területén.",
"Kezdetben néhány ügyfél weboldalának üzemeltetésével indultunk, ma pedig több száz domain és email fiók működését biztosítjuk. Növekedésünk során mindig szem előtt tartottuk az alapelveinket: <em>megbízhatóság, személyes kapcsolat és műszaki kiválóság</em>.",
"Csapatunk folyamatosan képezi magát a legújabb technológiák terén, hogy ügyfeleink mindig korszerű és biztonságos megoldásokat kapjanak. Büszkék vagyunk arra, hogy sok ügyfelünkkel évek óta tartjuk a kapcsolatot, és számos projektet vittünk sikerre közösen."
]
},
"mission": {
"title": "Küldetésünk és értékeink",
"subtitle": "Minden nap azért dolgozunk, hogy ügyfeleink digitális jelenléte biztonságos, stabil és hatékony legyen.",
"values": [
{
"icon": "🛡️",
"title": "Megbízhatóság",
"description": "99.9% uptime és 24/7 monitoring biztosítja, hogy szolgáltatásaink mindig elérhetők legyenek."
},
{
"icon": "👥",
"title": "Személyes kapcsolat",
"description": "Minden ügyfél számít számunkra. Személyre szabott megoldásokat kínálunk és mindig elérhetők vagyunk."
},
{
"icon": "⚡",
"title": "Műszaki kiválóság",
"description": "Korszerű technológiák és bevált gyakorlatok alkalmazásával biztosítjuk a legmagasabb színvonalú szolgáltatást."
}
]
},
"team": {
"title": "Szakértő csapat",
"subtitle": "Tapasztalt IT szakemberek, akik szenvedélyesen dolgoznak az ügyfeleink sikeréért",
"paragraphs": [
"Csapatunk rendszeradminisztrátorokból, webfejlesztőkből és ügyfélszolgálati szakértőkből áll. Mindannyian több mint 10 éves tapasztalattal rendelkeznek a maguk területén, és folyamatosan követik a technológiai újdonságokat.",
"Hiszünk abban, hogy a jó kommunikáció és a műszaki tudás együtt teremti meg a tökéletes ügyfélélményt. Ezért minden munkatársunk nemcsak technikai szakértő, hanem kiváló kommunikátor is."
]
},
"cta": {
"title": "Legyen Ön is elégedett ügyfelünk!",
"subtitle": "Vegye fel velünk a kapcsolatot, és beszéljük meg, hogyan segíthetünk Önnek.",
"button": "Kapcsolatfelvétel"
}
}
+77
View File
@@ -0,0 +1,77 @@
{
"meta": {
"title": "Kapcsolat",
"description": "Vegye fel velünk a kapcsolatot! Szívesen segítünk minden IT kérdésében."
},
"hero": {
"title": "Kapcsolat",
"subtitle": "Vegye fel velünk a kapcsolatot! Szívesen segítünk minden IT kérdésében."
},
"form": {
"title": "Küldjön üzenetet",
"successMessage": "Köszönjük üzenetét! Hamarosan felvesszük Önnel a kapcsolatot.",
"errorMessage": "Hiba történt az üzenet küldése során. Kérjük, próbálja újra vagy írjon közvetlenül a {email} címre.",
"fields": {
"name": {
"label": "Név",
"placeholder": "Az Ön neve",
"error": "A név megadása kötelező"
},
"email": {
"label": "Email cím",
"placeholder": "pelda@email.hu",
"errorRequired": "Az email cím megadása kötelező",
"errorInvalid": "Érvénytelen email cím formátum"
},
"subject": {
"label": "Tárgy",
"placeholder": "Miben segíthetünk?",
"error": "A tárgy megadása kötelező"
},
"message": {
"label": "Üzenet",
"placeholder": "Írja le részletesen kérését vagy kérdését...",
"errorRequired": "Az üzenet megadása kötelező",
"errorMinLength": "Az üzenet legalább 10 karakter hosszú legyen"
},
"gdpr": {
"label": "Elfogadom az <a href=\"/adatkezelesi-tajekoztato\" class=\"text-blue-600 hover:text-blue-700 underline\">adatkezelési tájékoztatót</a> és hozzájárulok személyes adataim kezeléséhez a kapcsolatfelvétel céljából.",
"error": "Az adatkezelési tájékoztató elfogadása kötelező"
}
},
"submitButton": "Üzenet küldése",
"submittingButton": "Küldés..."
},
"info": {
"title": "Elérhetőségek",
"email": {
"title": "Email",
"responseTime": "24 órán belül válaszolunk"
},
"company": {
"title": "Cég"
},
"webmail": {
"title": "Webmail hozzáférés",
"linkText": "Webmail belépés →",
"subtitle": "Ügyfeleink számára"
}
},
"faq": {
"title": "Gyakori kérdések",
"items": [
{
"question": "Milyen gyorsan válaszolnak?",
"answer": "Email üzenetekre 24 órán belül, sürgős esetekben telefonon is elérhetők vagyunk."
},
{
"question": "Van ingyenes konzultáció?",
"answer": "Igen! Az első konzultáció mindig ingyenes, hogy megismerjük az Ön igényeit."
},
{
"question": "Milyen fizetési módokat fogadnak el?",
"answer": "Banki átutalás, PayPal és kártyás fizetés is lehetséges."
}
]
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"cta": {
"title": "Kapcsolatfelvétel az első lépés",
"subtitle": "Mutassuk meg, hogyan segíthetünk Önnek megvalósítani céljait!",
"button": "Kapcsolatfelvétel"
},
"serviceFeatures": {
"title": "Szolgáltatás jellemzők:"
}
}
+94
View File
@@ -0,0 +1,94 @@
{
"meta": {
"title": "Szolgáltatások",
"description": "Webhosting, email szolgáltatás és DNS adminisztráció professzionális szinten. Ismerje meg részletes szolgáltatásainkat.",
"ogDescription": "Webhosting, email szolgáltatás és DNS adminisztráció professzionális szinten."
},
"hero": {
"title": "Szolgáltatásaink",
"subtitle": "Teljes körű IT megoldások kisvállalkozások és magánszemélyek számára"
},
"details": {
"title": "Részletes szolgáltatásleírás",
"subtitle": "Minden szolgáltatásunk mögött évtizedes tapasztalat és modern technológia áll",
"services": [
{
"icon": "🌐",
"title": "Web Hosting részletesen",
"description": "Weboldalak biztonságos és gyors üzemeltetése SSD tárolással, automatikus biztonsági mentéssel és 24/7 monitoringgal. Támogatjuk a PHP, Python, Node.js technológiákat és MySQL/PostgreSQL adatbázisokat.",
"specs": {
"title": "Technikai specifikációk:",
"items": [
"SSD tárhely 10GB-tól 500GB-ig",
"Havi adatforgalom: korlátlan",
"SSL tanúsítványok (Let's Encrypt vagy prémium)",
"CDN integráció a gyorsabb betöltésért",
"Automatikus napi biztonsági mentés",
"cPanel vagy egyedi admin felület"
]
}
},
{
"icon": "✉️",
"title": "Email szolgáltatás részletesen",
"description": "Professzionális email fiókok saját domain névvel, spam szűréssel és vírusvédelemmel. Webmail felület és IMAP/POP3/SMTP támogatás minden népszerű email klienssel.",
"specs": {
"title": "Email funkciók:",
"items": [
"Korlátlan email fiókok létrehozása",
"5GB-50GB tárhelyet fiókként",
"Webmail hozzáférés (Roundcube/SOGo)",
"Mobilalkalmazás szinkronizáció",
"Spam és vírusszűrés",
"Email továbbítás és automatikus válaszok",
"Backup és archiválás"
]
}
},
{
"icon": "⚙️",
"title": "DNS adminisztráció részletesen",
"description": "Teljes DNS kezelés domain regisztrációval, átvitellel és professzionális beállításokkal. Gyors propagáció és megbízható névszerverek világszerte.",
"specs": {
"title": "DNS szolgáltatások:",
"items": [
"Domain regisztráció (.hu, .com, .eu, stb.)",
"Domain átvitel más szolgáltatótól",
"DNS rekord kezelés (A, CNAME, MX, TXT)",
"Subdomain beállítások",
"Redirect és forwarding szolgáltatások",
"DNSSEC támogatás",
"API hozzáférés fejlesztőknek"
]
}
}
]
},
"support": {
"title": "Műszaki támogatás",
"subtitle": "Minden szolgáltatásunkhoz teljes körű műszaki támogatást biztosítunk",
"channels": [
{
"icon": "✉️",
"title": "Email támogatás",
"description": "24 órán belüli válasz"
},
{
"icon": "📞",
"title": "Telefonos segítség",
"description": "Munkaidőben elérhető"
},
{
"icon": "🚨",
"title": "Sürgős esetek",
"description": "Azonnali beavatkozás"
}
]
},
"cta": {
"title": "Kezdjük el a közös munkát!",
"subtitle": "Vegye fel velünk a kapcsolatot ingyenes konzultációért és egyedi ajánlatért.",
"primaryButton": "Kapcsolatfelvétel",
"secondaryButton": "Webmail belépés"
}
}
+178
View File
@@ -0,0 +1,178 @@
/**
* Content Types - TypeScript definitions for JSON content files
* These types ensure type safety when working with page content
*/
// Common content types
export interface CTAContent {
text: string
href: string
external?: boolean
}
export interface IconTextItem {
icon: string
title: string
description: string
}
export interface FAQItem {
question: string
answer: string
}
export interface TechSpec {
title: string
items: string[]
}
// Page-specific content types
export interface HomePageContent {
cta: {
title: string
subtitle: string
button: string
}
serviceFeatures: {
title: string
}
}
export interface AboutPageContent {
meta: {
title: string
description: string
ogDescription: string
}
hero: {
title: string
subtitle: string
}
story: {
title: string
paragraphs: string[]
}
mission: {
title: string
subtitle: string
values: IconTextItem[]
}
team: {
title: string
subtitle: string
paragraphs: string[]
}
cta: {
title: string
subtitle: string
button: string
}
}
export interface ServiceDetailContent {
icon: string
title: string
description: string
specs: TechSpec
}
export interface ServicesPageContent {
meta: {
title: string
description: string
ogDescription: string
}
hero: {
title: string
subtitle: string
}
details: {
title: string
subtitle: string
services: ServiceDetailContent[]
}
support: {
title: string
subtitle: string
channels: IconTextItem[]
}
cta: {
title: string
subtitle: string
primaryButton: string
secondaryButton: string
}
}
export interface ContactPageContent {
meta: {
title: string
description: string
}
hero: {
title: string
subtitle: string
}
form: {
title: string
successMessage: string
errorMessage: string
fields: {
name: { label: string; placeholder: string; error: string }
email: { label: string; placeholder: string; errorRequired: string; errorInvalid: string }
subject: { label: string; placeholder: string; error: string }
message: { label: string; placeholder: string; errorRequired: string; errorMinLength: string }
gdpr: { label: string; error: string }
}
submitButton: string
submittingButton: string
}
info: {
title: string
email: {
title: string
responseTime: string
}
company: {
title: string
}
webmail: {
title: string
linkText: string
subtitle: string
}
}
faq: {
title: string
items: FAQItem[]
}
}
export interface CommonContent {
buttons: {
contact: string
learnMore: string
webmail: string
sendMessage: string
}
labels: {
required: string
features: string
}
validation: {
required: string
invalidEmail: string
minLength: string
}
}
// Main content structure
export interface SiteContent {
common: CommonContent
pages: {
home: HomePageContent
about: AboutPageContent
services: ServicesPageContent
contact: ContactPageContent
}
}