Enhance site configuration and layout with modern styling, improved font usage, and updated service components. Refactor navigation and footer for better accessibility and user experience, while ensuring inline definitions to resolve import issues.

This commit is contained in:
2025-07-24 20:59:34 +02:00
parent 32301a18e9
commit f3ccd766fa
14 changed files with 2387 additions and 737 deletions

View File

@@ -7,19 +7,19 @@
"astro > cssesc": { "astro > cssesc": {
"src": "../../cssesc/cssesc.js", "src": "../../cssesc/cssesc.js",
"file": "astro___cssesc.js", "file": "astro___cssesc.js",
"fileHash": "3fe2d173", "fileHash": "57f1afdf",
"needsInterop": true "needsInterop": true
}, },
"astro > aria-query": { "astro > aria-query": {
"src": "../../aria-query/lib/index.js", "src": "../../aria-query/lib/index.js",
"file": "astro___aria-query.js", "file": "astro___aria-query.js",
"fileHash": "5253c813", "fileHash": "6f0acea9",
"needsInterop": true "needsInterop": true
}, },
"astro > axobject-query": { "astro > axobject-query": {
"src": "../../axobject-query/lib/index.js", "src": "../../axobject-query/lib/index.js",
"file": "astro___axobject-query.js", "file": "astro___axobject-query.js",
"fileHash": "355d5bcf", "fileHash": "d2fc7139",
"needsInterop": true "needsInterop": true
} }
}, },

BIN
src/.DS_Store vendored

Binary file not shown.

View File

@@ -5,171 +5,303 @@ const lang = getLangFromUrl(Astro.url);
const t = await useTranslations(lang); const t = await useTranslations(lang);
--- ---
<section class="py-20 bg-background"> <section class="section-padding bg-gradient-to-br from-background via-surface/30 to-background">
<div class="container-custom"> <div class="container-custom">
<div class="max-w-4xl mx-auto"> <div class="max-w-7xl mx-auto">
<!-- Section header --> <!-- Section header -->
<div class="text-center mb-12 animate-on-scroll"> <div class="text-center mb-16 animate-on-scroll">
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-display font-bold text-foreground mb-4"> <div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 border border-primary/20 text-primary text-sm font-medium mb-6">
{t('contact.title')} <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/>
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>
</svg>
Get In Touch
</div>
<h2 class="text-responsive-xl font-bold text-foreground mb-6">
Ready to Transform
<span class="block text-gradient">Your IT Infrastructure?</span>
</h2> </h2>
<p class="text-lg sm:text-xl text-muted-foreground">
<p class="text-responsive-base text-muted max-w-2xl mx-auto">
{t('contact.subtitle')} {t('contact.subtitle')}
</p> </p>
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-12">
<!-- Contact form -->
<div class="animate-on-scroll">
<form id="contact-form" class="space-y-6">
<!-- Name field -->
<div>
<label for="name" class="block text-sm font-medium text-foreground mb-2">
{t('contact.form.name')} *
</label>
<input
type="text"
id="name"
name="name"
required
class="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
placeholder="John Doe"
/>
</div>
<!-- Email field --> <!-- Contact form - Takes 2 columns on large screens -->
<div> <div class="lg:col-span-2 animate-on-scroll">
<label for="email" class="block text-sm font-medium text-foreground mb-2"> <div class="card-glass p-8 lg:p-12">
{t('contact.form.email')} * <form id="contact-form" class="space-y-6">
</label>
<input
type="email"
id="email"
name="email"
required
class="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
placeholder="john@company.com"
/>
</div>
<!-- Company field --> <!-- Form header -->
<div> <div class="mb-8">
<label for="company" class="block text-sm font-medium text-foreground mb-2"> <h3 class="text-2xl font-bold text-foreground mb-2">Send us a message</h3>
{t('contact.form.company')} <p class="text-muted">We'll get back to you within 24 hours</p>
</label>
<input
type="text"
id="company"
name="company"
class="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
placeholder="Your Company"
/>
</div>
<!-- Service field -->
<div>
<label for="service" class="block text-sm font-medium text-foreground mb-2">
{t('contact.form.service')}
</label>
<select
id="service"
name="service"
class="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
>
<option value="">Select a service</option>
<option value="microsoft365">Microsoft 365 Support</option>
<option value="management">Full M365 Management</option>
<option value="networking">Networking & Infrastructure</option>
<option value="hosting">Web Hosting & Management</option>
<option value="custom">Custom IT Projects</option>
</select>
</div>
<!-- Message field -->
<div>
<label for="message" class="block text-sm font-medium text-foreground mb-2">
{t('contact.form.message')} *
</label>
<textarea
id="message"
name="message"
rows="4"
required
class="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all resize-y"
placeholder="Tell us about your IT needs..."
></textarea>
</div>
<!-- Submit button -->
<button
type="submit"
class="w-full btn-primary px-6 py-3 text-lg font-semibold rounded-lg transition-all duration-300 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed"
id="submit-btn"
>
<span id="submit-text">{t('contact.form.send')}</span>
<svg id="submit-spinner" class="hidden inline h-5 w-5 ml-2 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
</button>
</form>
<!-- Success/Error messages -->
<div id="form-message" class="hidden mt-4 p-4 rounded-lg"></div>
</div>
<!-- Contact info -->
<div class="animate-on-scroll" style="animation-delay: 0.2s">
<div class="card p-8">
<h3 class="text-xl font-display font-semibold text-foreground mb-6">
Get in Touch
</h3>
<div class="space-y-4">
<!-- Email -->
<div class="flex items-center">
<div class="flex-shrink-0 w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center mr-4">
<svg class="h-5 w-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<div>
<div class="text-sm text-muted-foreground">Email</div>
<a href="mailto:info@tiber365.it" class="text-foreground hover:text-primary transition-colors">
{t('contact.info.email')}
</a>
</div>
</div> </div>
<!-- Phone --> <!-- Name and Email row -->
<div class="flex items-center"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div class="flex-shrink-0 w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center mr-4"> <div class="space-y-2">
<svg class="h-5 w-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <label for="name" class="block text-sm font-medium text-foreground">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/> {t('contact.form.name')} <span class="text-error">*</span>
</svg> </label>
<div class="relative">
<input
type="text"
id="name"
name="name"
required
class="form-input peer"
placeholder="John Doe"
/>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none opacity-0 peer-focus:opacity-100 transition-opacity">
<svg class="h-5 w-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
</div>
</div>
</div> </div>
<div>
<div class="text-sm text-muted-foreground">Phone</div>
<a href="tel:+391234567890" class="text-foreground hover:text-primary transition-colors">
{t('contact.info.phone')}
</a>
</div>
</div>
<!-- Location --> <div class="space-y-2">
<div class="flex items-center"> <label for="email" class="block text-sm font-medium text-foreground">
<div class="flex-shrink-0 w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center mr-4"> {t('contact.form.email')} <span class="text-error">*</span>
<svg class="h-5 w-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </label>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/> <div class="relative">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/> <input
</svg> type="email"
</div> id="email"
<div> name="email"
<div class="text-sm text-muted-foreground">Location</div> required
<div class="text-foreground"> class="form-input peer"
{t('contact.info.address')} placeholder="john@company.com"
/>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none opacity-0 peer-focus:opacity-100 transition-opacity">
<svg class="h-5 w-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Company field -->
<div class="space-y-2">
<label for="company" class="block text-sm font-medium text-foreground">
{t('contact.form.company')}
</label>
<div class="relative">
<input
type="text"
id="company"
name="company"
class="form-input peer"
placeholder="Your Company"
/>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none opacity-0 peer-focus:opacity-100 transition-opacity">
<svg class="h-5 w-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5h3"/>
</svg>
</div>
</div>
</div>
<!-- Service selection -->
<div class="space-y-2">
<label for="service" class="block text-sm font-medium text-foreground">
{t('contact.form.service')}
</label>
<div class="relative">
<select
id="service"
name="service"
class="form-select peer"
>
<option value="">Select a service</option>
<option value="microsoft365">Microsoft 365 Support</option>
<option value="management">Full M365 Management</option>
<option value="networking">Networking & Infrastructure</option>
<option value="hosting">Web Hosting & Management</option>
<option value="custom">Custom IT Projects</option>
</select>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none opacity-0 peer-focus:opacity-100 transition-opacity">
<svg class="h-5 w-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
</div>
</div>
<!-- Message field -->
<div class="space-y-2">
<label for="message" class="block text-sm font-medium text-foreground">
{t('contact.form.message')} <span class="text-error">*</span>
</label>
<div class="relative">
<textarea
id="message"
name="message"
rows="5"
required
class="form-textarea peer"
placeholder="Tell us about your IT needs and how we can help..."
></textarea>
<div class="absolute top-3 left-0 pl-3 flex items-start pointer-events-none opacity-0 peer-focus:opacity-100 transition-opacity">
<svg class="h-5 w-5 text-primary mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
</svg>
</div>
</div>
</div>
<!-- Character counter -->
<div class="flex justify-between items-center text-sm text-subtle">
<span id="char-counter">0 characters</span>
<span>Max 1000 characters</span>
</div>
<!-- Submit button -->
<div class="pt-4">
<button
type="submit"
class="w-full btn btn-primary group relative overflow-hidden"
id="submit-btn"
>
<span id="submit-text" class="relative z-10">{t('contact.form.send')}</span>
<svg id="submit-spinner" class="hidden h-5 w-5 animate-spin relative z-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<svg class="h-5 w-5 ml-2 transition-transform group-hover:translate-x-1 relative z-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/>
</svg>
<!-- Button ripple effect -->
<div class="absolute inset-0 bg-white/20 transform scale-x-0 group-hover:scale-x-100 transition-transform origin-left duration-300"></div>
</button>
</div>
<!-- Form message -->
<div id="form-message" class="hidden p-4 rounded-xl"></div>
</form>
</div>
</div>
<!-- Contact info sidebar -->
<div class="animate-on-scroll" style="animation-delay: 0.2s">
<div class="space-y-8">
<!-- Main contact card -->
<div class="card-glass p-8">
<h3 class="text-xl font-bold text-foreground mb-6 flex items-center gap-2">
<div class="w-8 h-8 bg-gradient-to-br from-primary to-accent rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/>
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>
</svg>
</div>
Get in Touch
</h3>
<div class="space-y-6">
<!-- Email -->
<div class="group hover:bg-surface/50 p-4 rounded-xl transition-all cursor-pointer">
<div class="flex items-start gap-4">
<div class="flex-shrink-0 w-12 h-12 bg-primary/10 rounded-xl flex items-center justify-center group-hover:bg-primary/20 transition-colors">
<svg class="h-6 w-6 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<div class="flex-1">
<div class="text-sm text-subtle mb-1">Email</div>
<a href="mailto:info@tiber365.it" class="text-foreground hover:text-primary transition-colors font-medium">
{t('contact.info.email')}
</a>
<div class="text-xs text-subtle mt-1">Response within 2 hours</div>
</div>
</div>
</div>
<!-- Phone -->
<div class="group hover:bg-surface/50 p-4 rounded-xl transition-all cursor-pointer">
<div class="flex items-start gap-4">
<div class="flex-shrink-0 w-12 h-12 bg-accent/10 rounded-xl flex items-center justify-center group-hover:bg-accent/20 transition-colors">
<svg class="h-6 w-6 text-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
</svg>
</div>
<div class="flex-1">
<div class="text-sm text-subtle mb-1">Phone</div>
<a href="tel:+391234567890" class="text-foreground hover:text-accent transition-colors font-medium">
{t('contact.info.phone')}
</a>
<div class="text-xs text-subtle mt-1">Mon-Fri 9AM-6PM CET</div>
</div>
</div>
</div>
<!-- Location -->
<div class="group hover:bg-surface/50 p-4 rounded-xl transition-all cursor-pointer">
<div class="flex items-start gap-4">
<div class="flex-shrink-0 w-12 h-12 bg-success/10 rounded-xl flex items-center justify-center group-hover:bg-success/20 transition-colors">
<svg class="h-6 w-6 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</div>
<div class="flex-1">
<div class="text-sm text-subtle mb-1">Location</div>
<div class="text-foreground font-medium">
{t('contact.info.address')}
</div>
<div class="text-xs text-subtle mt-1">Central European Time</div>
</div>
</div>
</div>
</div>
</div>
<!-- Quick stats -->
<div class="card-glass p-6">
<h4 class="font-semibold text-foreground mb-4">Why choose us?</h4>
<div class="space-y-4">
<div class="flex items-center gap-3">
<div class="w-2 h-2 bg-primary rounded-full animate-pulse"></div>
<span class="text-sm text-muted">24/7 Technical Support</span>
</div>
<div class="flex items-center gap-3">
<div class="w-2 h-2 bg-accent rounded-full animate-pulse" style="animation-delay: 0.5s;"></div>
<span class="text-sm text-muted">99.9% Service Uptime</span>
</div>
<div class="flex items-center gap-3">
<div class="w-2 h-2 bg-success rounded-full animate-pulse" style="animation-delay: 1s;"></div>
<span class="text-sm text-muted">5+ Years Experience</span>
</div>
<div class="flex items-center gap-3">
<div class="w-2 h-2 bg-warning rounded-full animate-pulse" style="animation-delay: 1.5s;"></div>
<span class="text-sm text-muted">100+ Happy Clients</span>
</div>
</div>
</div>
<!-- Emergency contact -->
<div class="p-6 bg-gradient-to-br from-error/10 to-warning/10 border border-error/20 rounded-2xl">
<div class="flex items-center gap-3 mb-3">
<div class="w-8 h-8 bg-error/20 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-error" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
</div>
<h4 class="font-semibold text-foreground">Emergency Support</h4>
</div>
<p class="text-sm text-muted mb-3">Critical system down? Our emergency line is always available.</p>
<a href="tel:+391234567890" class="inline-flex items-center gap-2 text-sm font-medium text-error hover:text-error/80 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
</svg>
Call Emergency Line
</a>
</div> </div>
</div> </div>
</div> </div>
@@ -179,47 +311,213 @@ const t = await useTranslations(lang);
</section> </section>
<script> <script>
// Contact form handling // Enhanced contact form functionality
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form') as HTMLFormElement; const form = document.getElementById('contact-form') as HTMLFormElement;
const submitBtn = document.getElementById('submit-btn') as HTMLButtonElement; const submitBtn = document.getElementById('submit-btn') as HTMLButtonElement;
const submitText = document.getElementById('submit-text') as HTMLSpanElement; const submitText = document.getElementById('submit-text') as HTMLSpanElement;
const submitSpinner = document.getElementById('submit-spinner') as HTMLElement; const submitSpinner = document.getElementById('submit-spinner') as HTMLElement;
const formMessage = document.getElementById('form-message') as HTMLDivElement; const formMessage = document.getElementById('form-message') as HTMLDivElement;
const messageTextarea = document.getElementById('message') as HTMLTextAreaElement;
const charCounter = document.getElementById('char-counter') as HTMLSpanElement;
// Character counter functionality
if (messageTextarea && charCounter) {
messageTextarea.addEventListener('input', () => {
const count = messageTextarea.value.length;
charCounter.textContent = `${count} characters`;
if (count > 1000) {
charCounter.classList.add('text-error');
messageTextarea.classList.add('border-error');
} else {
charCounter.classList.remove('text-error');
messageTextarea.classList.remove('border-error');
}
});
}
// Form validation with visual feedback
const inputs = form.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
input.addEventListener('blur', validateField);
input.addEventListener('input', clearErrors);
});
function validateField(event: Event) {
const field = event.target as HTMLInputElement;
const isValid = field.checkValidity();
if (!isValid) {
field.classList.add('border-error', 'shake');
setTimeout(() => field.classList.remove('shake'), 500);
} else {
field.classList.remove('border-error');
field.classList.add('border-success');
}
}
function clearErrors(event: Event) {
const field = event.target as HTMLInputElement;
field.classList.remove('border-error', 'border-success');
}
// Enhanced form submission
if (form) { if (form) {
form.addEventListener('submit', async (e) => { form.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
// Show loading state // Validate all fields
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('border-error', 'shake');
isValid = false;
}
});
if (!isValid) {
showMessage('Please fill in all required fields correctly.', 'error');
return;
}
// Show loading state with enhanced animation
submitBtn.disabled = true; submitBtn.disabled = true;
submitBtn.classList.add('loading');
submitText.textContent = 'Sending...'; submitText.textContent = 'Sending...';
submitSpinner.classList.remove('hidden'); submitSpinner.classList.remove('hidden');
// Simulate form submission (replace with actual form handling) // Add pulse effect to button
submitBtn.style.animation = 'pulse 1s infinite';
try { try {
// Simulate form submission
await new Promise(resolve => setTimeout(resolve, 2000)); await new Promise(resolve => setTimeout(resolve, 2000));
// Show success message // Success state
formMessage.className = 'mt-4 p-4 rounded-lg bg-green-50 border border-green-200 text-green-800'; showMessage('Message sent successfully! We\'ll get back to you within 24 hours.', 'success');
formMessage.textContent = 'Message sent successfully! We\'ll get back to you soon.';
formMessage.classList.remove('hidden');
// Reset form
form.reset(); form.reset();
inputs.forEach(input => {
input.classList.remove('border-error', 'border-success');
});
// Confetti effect (simple version)
createConfetti();
} catch (error) { } catch (error) {
// Show error message showMessage('Failed to send message. Please try again or contact us directly.', 'error');
formMessage.className = 'mt-4 p-4 rounded-lg bg-red-50 border border-red-200 text-red-800';
formMessage.textContent = 'Failed to send message. Please try again.';
formMessage.classList.remove('hidden');
} finally { } finally {
// Reset button state // Reset button state
submitBtn.disabled = false; submitBtn.disabled = false;
submitText.textContent = 'Send Message'; submitBtn.classList.remove('loading');
submitBtn.style.animation = '';
submitText.textContent = '{t('contact.form.send')}';
submitSpinner.classList.add('hidden'); submitSpinner.classList.add('hidden');
} }
}); });
} }
function showMessage(message: string, type: 'success' | 'error') {
if (!formMessage) return;
formMessage.textContent = message;
formMessage.className = `p-4 rounded-xl transition-all duration-300 ${
type === 'success'
? 'bg-success/10 border border-success/20 text-success'
: 'bg-error/10 border border-error/20 text-error'
}`;
formMessage.classList.remove('hidden');
// Auto-hide after 5 seconds
setTimeout(() => {
formMessage.style.opacity = '0';
setTimeout(() => {
formMessage.classList.add('hidden');
formMessage.style.opacity = '1';
}, 300);
}, 5000);
}
function createConfetti() {
// Simple confetti effect
for (let i = 0; i < 50; i++) {
const confetti = document.createElement('div');
confetti.className = 'fixed w-2 h-2 bg-primary rounded-full pointer-events-none z-50';
confetti.style.left = Math.random() * 100 + 'vw';
confetti.style.top = '-10px';
confetti.style.animation = `confetti-fall ${Math.random() * 2 + 1}s linear forwards`;
document.body.appendChild(confetti);
setTimeout(() => confetti.remove(), 3000);
}
}
}); });
</script> </script>
<style>
/* Enhanced form animations */
.shake {
animation: shake 0.5s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
@keyframes confetti-fall {
0% {
transform: translateY(-10px) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(360deg);
opacity: 0;
}
}
/* Loading button animation */
.btn.loading {
position: relative;
color: transparent;
}
.btn.loading #submit-text,
.btn.loading svg:not(#submit-spinner) {
opacity: 0;
}
.btn.loading #submit-spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* Form field focus effects */
.form-input:focus,
.form-textarea:focus,
.form-select:focus {
transform: translateY(-1px);
box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.1), 0 4px 6px -2px rgba(59, 130, 246, 0.05);
}
/* Success and error states */
.border-success {
border-color: rgb(var(--color-success)) !important;
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1);
}
.border-error {
border-color: rgb(var(--color-error)) !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
/* Contact info hover effects */
.group:hover .flex-shrink-0 {
transform: scale(1.1);
transition: transform 0.3s ease;
}
</style>

View File

@@ -1,156 +1,424 @@
--- ---
import { NAVIGATION } from '../site.config'; // Simplified Footer with inline fallbacks to avoid import issues
import { getLangFromUrl, useTranslations } from '../utils/i18n'; import { getLangFromUrl, useTranslations } from '../utils/i18n';
const lang = getLangFromUrl(Astro.url); const lang = getLangFromUrl(Astro.url);
const t = await useTranslations(lang); const t = await useTranslations(lang);
// Define navigation inline to avoid import issues
const NAVIGATION = [
{ label: 'nav.home', href: '/', type: 'internal' },
{ label: 'nav.services', href: '/services', type: 'internal' },
{ label: 'nav.about', href: '/about', type: 'internal' },
{ label: 'nav.contact', href: '/contact', type: 'internal' },
{ label: 'nav.blog', href: 'https://blog.tiber365.it', type: 'external' },
{ label: 'nav.support', href: 'https://support.tiber365.it', type: 'external' },
];
--- ---
<footer class="bg-secondary-900 text-secondary-100 pt-16 pb-8"> <footer class="relative bg-gradient-to-br from-surface via-background to-surface border-t border-border/50">
<div class="container-custom"> <!-- Background pattern -->
<div class="absolute inset-0 opacity-30">
<div class="absolute inset-0" style="background-image: radial-gradient(circle at 2px 2px, rgb(var(--color-primary) / 0.1) 1px, transparent 0); background-size: 32px 32px;"></div>
</div>
<div class="container-custom relative z-10">
<!-- Main footer content --> <!-- Main footer content -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-8"> <div class="section-padding">
<!-- Company info --> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-12 lg:gap-8">
<div class="lg:col-span-2">
<!-- Logo -->
<div class="flex items-center space-x-2 mb-4">
<div class="h-8 w-8 flex items-center justify-center">
<img
src="/images/TIBER365.png"
alt="Tiber365 Logo"
class="h-6 w-6 object-contain"
/>
</div>
<span class="font-display font-bold text-xl text-white">Tiber365</span>
</div>
<!-- Description --> <!-- Company info - spans 5 columns -->
<p class="text-secondary-300 mb-6 max-w-md leading-relaxed"> <div class="lg:col-span-5 space-y-8">
{t('footer.description')} <!-- Logo and tagline -->
</p> <div class="space-y-4">
<div class="flex items-center space-x-3 group">
<div class="relative">
<div class="absolute inset-0 bg-primary/20 rounded-xl blur-md opacity-0 group-hover:opacity-100 transition-all duration-500"></div>
<div class="relative h-12 w-12 bg-gradient-to-br from-primary to-accent rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<img
src="/images/TIBER365.png"
alt="Tiber365 Logo"
class="h-7 w-7 object-contain brightness-0 invert"
/>
</div>
</div>
<div>
<span class="font-bold text-2xl text-foreground group-hover:text-primary transition-colors duration-300">Tiber365</span>
<div class="text-sm text-gradient font-medium">IT Excellence Delivered</div>
</div>
</div>
<!-- Contact info --> <!-- Description -->
<div class="space-y-2"> <p class="text-muted leading-relaxed max-w-md">
<div class="flex items-center text-secondary-300"> Professional IT services for freelancers and small businesses. We specialize in making technology work seamlessly for your business.
<svg class="h-5 w-5 mr-3 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </p>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
{t('contact.info.email')}
</div> </div>
<div class="flex items-center text-secondary-300">
<svg class="h-5 w-5 mr-3 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <!-- Contact info with modern styling -->
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/> <div class="space-y-4">
</svg> <h4 class="font-semibold text-foreground mb-4 flex items-center gap-2">
{t('contact.info.phone')} <div class="w-1 h-6 bg-gradient-to-b from-primary to-accent rounded-full"></div>
Contact Information
</h4>
<div class="space-y-3">
<div class="group flex items-center gap-3 p-3 hover:bg-surface/50 rounded-xl transition-all cursor-pointer">
<div class="flex-shrink-0 w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center group-hover:bg-primary/20 transition-colors">
<svg class="h-5 w-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<div>
<div class="text-sm text-subtle">Email</div>
<a href="mailto:info@tiber365.it" class="text-foreground hover:text-primary transition-colors font-medium">
info@tiber365.it
</a>
</div>
</div>
<div class="group flex items-center gap-3 p-3 hover:bg-surface/50 rounded-xl transition-all cursor-pointer">
<div class="flex-shrink-0 w-10 h-10 bg-accent/10 rounded-lg flex items-center justify-center group-hover:bg-accent/20 transition-colors">
<svg class="h-5 w-5 text-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
</svg>
</div>
<div>
<div class="text-sm text-subtle">Phone</div>
<a href="tel:+391234567890" class="text-foreground hover:text-accent transition-colors font-medium">
+39 123 456 7890
</a>
</div>
</div>
<div class="group flex items-center gap-3 p-3 hover:bg-surface/50 rounded-xl transition-all cursor-pointer">
<div class="flex-shrink-0 w-10 h-10 bg-success/10 rounded-lg flex items-center justify-center group-hover:bg-success/20 transition-colors">
<svg class="h-5 w-5 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</div>
<div>
<div class="text-sm text-subtle">Location</div>
<div class="text-foreground font-medium">
Amsterdam, Netherlands
</div>
</div>
</div>
</div>
</div> </div>
<div class="flex items-center text-secondary-300">
<svg class="h-5 w-5 mr-3 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <!-- Social proof -->
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/> <div class="p-6 bg-gradient-to-br from-primary/5 to-accent/5 rounded-2xl border border-primary/10">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/> <div class="flex items-center gap-4 mb-4">
</svg> <div class="flex -space-x-2">
{t('contact.info.address')} <div class="w-8 h-8 bg-gradient-to-br from-primary to-accent rounded-full border-2 border-background flex items-center justify-center text-white text-xs font-bold">T</div>
<div class="w-8 h-8 bg-gradient-to-br from-accent to-primary rounded-full border-2 border-background flex items-center justify-center text-white text-xs font-bold">I</div>
<div class="w-8 h-8 bg-gradient-to-br from-success to-primary rounded-full border-2 border-background flex items-center justify-center text-white text-xs font-bold">B</div>
</div>
<div>
<div class="text-sm font-semibold text-foreground">100+ Happy Clients</div>
<div class="text-xs text-subtle">Trusted by businesses across Europe</div>
</div>
</div>
<div class="flex items-center gap-1">
{Array.from({ length: 5 }, (_, i) => (
<svg class="w-4 h-4 text-warning fill-current" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
</svg>
))}
<span class="text-sm text-muted ml-2">4.9/5 average rating</span>
</div>
</div> </div>
</div> </div>
</div>
<!-- Quick Links --> <!-- Quick Links - spans 3 columns -->
<div> <div class="lg:col-span-3">
<h3 class="font-semibold text-white mb-4">Quick Links</h3> <h4 class="font-semibold text-foreground mb-6 flex items-center gap-2">
<ul class="space-y-2"> <div class="w-1 h-6 bg-gradient-to-b from-primary to-accent rounded-full"></div>
{NAVIGATION.filter(item => item.type === 'internal').map((item) => ( Quick Links
</h4>
<ul class="space-y-3">
{NAVIGATION.filter(item => item.type === 'internal').map((item) => (
<li>
<a
href={item.href}
class="group flex items-center gap-2 text-muted hover:text-foreground transition-all duration-300 p-2 hover:bg-surface/50 rounded-lg"
>
<div class="w-2 h-2 bg-primary/50 rounded-full group-hover:bg-primary transition-colors"></div>
<span>{t(item.label)}</span>
<svg class="w-3 h-3 opacity-0 group-hover:opacity-100 transition-all duration-300 transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</a>
</li>
))}
<li> <li>
<a <a
href={item.href} href="/contact"
class="text-secondary-300 hover:text-primary transition-colors" class="group flex items-center gap-2 text-muted hover:text-foreground transition-all duration-300 p-2 hover:bg-surface/50 rounded-lg"
> >
{t(item.label)} <div class="w-2 h-2 bg-primary/50 rounded-full group-hover:bg-primary transition-colors"></div>
</a> <span>Contact</span>
</li> <svg class="w-3 h-3 opacity-0 group-hover:opacity-100 transition-all duration-300 transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
))} <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
<li>
<a
href="/contact"
class="text-secondary-300 hover:text-primary transition-colors"
>
{t('footer.links.contact')}
</a>
</li>
</ul>
</div>
<!-- External Links -->
<div>
<h3 class="font-semibold text-white mb-4">Resources</h3>
<ul class="space-y-2">
{NAVIGATION.filter(item => item.type === 'external').map((item) => (
<li>
<a
href={item.href}
target="_blank"
rel="noopener noreferrer"
class="text-secondary-300 hover:text-primary transition-colors inline-flex items-center"
>
{t(item.label)}
<svg class="h-3 w-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg> </svg>
</a> </a>
</li> </li>
))} </ul>
<li> </div>
<a
href="/privacy" <!-- Resources - spans 4 columns -->
class="text-secondary-300 hover:text-primary transition-colors" <div class="lg:col-span-4">
> <h4 class="font-semibold text-foreground mb-6 flex items-center gap-2">
{t('footer.links.privacy')} <div class="w-1 h-6 bg-gradient-to-b from-accent to-primary rounded-full"></div>
</a> Resources & Links
</li> </h4>
<li>
<a <div class="space-y-6">
href="/terms" <!-- External links -->
class="text-secondary-300 hover:text-primary transition-colors" <ul class="space-y-3">
> {NAVIGATION.filter(item => item.type === 'external').map((item) => (
{t('footer.links.terms')} <li>
</a> <a
</li> href={item.href}
</ul> target="_blank"
rel="noopener noreferrer"
class="group flex items-center gap-2 text-muted hover:text-foreground transition-all duration-300 p-2 hover:bg-surface/50 rounded-lg"
>
<div class="w-2 h-2 bg-accent/50 rounded-full group-hover:bg-accent transition-colors"></div>
<span>{t(item.label)}</span>
<svg class="w-3 h-3 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
</li>
))}
<!-- Legal links -->
<li>
<a
href="/privacy"
class="group flex items-center gap-2 text-muted hover:text-foreground transition-all duration-300 p-2 hover:bg-surface/50 rounded-lg"
>
<div class="w-2 h-2 bg-accent/50 rounded-full group-hover:bg-accent transition-colors"></div>
<span>Privacy Policy</span>
</a>
</li>
<li>
<a
href="/terms"
class="group flex items-center gap-2 text-muted hover:text-foreground transition-all duration-300 p-2 hover:bg-surface/50 rounded-lg"
>
<div class="w-2 h-2 bg-accent/50 rounded-full group-hover:bg-accent transition-colors"></div>
<span>Terms of Service</span>
</a>
</li>
</ul>
<!-- Newsletter signup -->
<div class="p-4 bg-gradient-to-br from-surface/50 to-background/50 border border-border/50 rounded-xl">
<h5 class="font-medium text-foreground mb-2 flex items-center gap-2">
<svg class="w-4 h-4 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
Stay Updated
</h5>
<p class="text-sm text-muted mb-3">Get the latest IT tips and updates</p>
<form class="flex gap-2">
<input
type="email"
placeholder="your@email.com"
class="flex-1 px-3 py-2 text-sm bg-background border border-border rounded-lg focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all"
/>
<button type="submit" class="px-4 py-2 bg-gradient-to-r from-primary to-accent text-white text-sm font-medium rounded-lg hover:shadow-lg transition-all">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/>
</svg>
</button>
</form>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- Footer bottom --> <!-- Footer bottom -->
<div class="border-t border-secondary-800 pt-8"> <div class="border-t border-border/50 py-8">
<div class="flex flex-col md:flex-row justify-between items-center"> <div class="flex flex-col lg:flex-row justify-between items-center gap-6">
<!-- Copyright -->
<p class="text-secondary-400 text-sm">
{t('footer.copyright')}
</p>
<!-- Social links placeholder --> <!-- Copyright -->
<div class="flex items-center space-x-4 mt-4 md:mt-0"> <div class="flex items-center gap-4">
<a <p class="text-subtle text-sm">
href="https://blog.tiber365.it" © 2024 Tiber365. All rights reserved.
target="_blank" </p>
rel="noopener noreferrer" <div class="flex items-center gap-1 text-xs text-subtle">
class="text-secondary-400 hover:text-primary transition-colors" <span>Made with</span>
aria-label="Blog" <svg class="w-3 h-3 text-error animate-pulse" fill="currentColor" viewBox="0 0 20 20">
> <path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd"/>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
</svg> </svg>
</a> <span>in Amsterdam</span>
<a </div>
href="https://support.tiber365.it" </div>
target="_blank"
rel="noopener noreferrer" <!-- Social links and additional info -->
class="text-secondary-400 hover:text-primary transition-colors" <div class="flex items-center gap-6">
aria-label="Support Portal" <!-- Business info -->
> <div class="flex items-center gap-4 text-xs text-subtle">
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> <div class="flex items-center gap-1">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/> <div class="w-2 h-2 bg-success rounded-full animate-pulse"></div>
</svg> <span>Online</span>
</a> </div>
<span>•</span>
<span>CET Timezone</span>
<span>•</span>
<span>VAT: NL123456789B01</span>
</div>
<!-- Social/External links -->
<div class="flex items-center gap-3">
<a
href="https://blog.tiber365.it"
target="_blank"
rel="noopener noreferrer"
class="group p-2 bg-surface/50 hover:bg-primary/10 border border-border/50 hover:border-primary/30 rounded-lg transition-all"
aria-label="Blog"
>
<svg class="w-4 h-4 text-muted group-hover:text-primary transition-colors" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
</svg>
</a>
<a
href="https://support.tiber365.it"
target="_blank"
rel="noopener noreferrer"
class="group p-2 bg-surface/50 hover:bg-accent/10 border border-border/50 hover:border-accent/30 rounded-lg transition-all"
aria-label="Support Portal"
>
<svg class="w-4 h-4 text-muted group-hover:text-accent transition-colors" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
</a>
<!-- Back to top button -->
<button
id="back-to-top"
class="group p-2 bg-surface/50 hover:bg-success/10 border border-border/50 hover:border-success/30 rounded-lg transition-all opacity-0"
aria-label="Back to top"
>
<svg class="w-4 h-4 text-muted group-hover:text-success transition-all group-hover:-translate-y-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"/>
</svg>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Decorative bottom border -->
<div class="h-1 bg-gradient-to-r from-transparent via-primary to-transparent"></div>
</footer> </footer>
<script>
// Enhanced footer functionality
document.addEventListener('DOMContentLoaded', () => {
const backToTopBtn = document.getElementById('back-to-top');
// Show/hide back to top button
function updateBackToTopButton() {
if (window.scrollY > 500) {
backToTopBtn?.classList.remove('opacity-0');
backToTopBtn?.classList.add('opacity-100');
} else {
backToTopBtn?.classList.add('opacity-0');
backToTopBtn?.classList.remove('opacity-100');
}
}
window.addEventListener('scroll', updateBackToTopButton, { passive: true });
// Back to top functionality
backToTopBtn?.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
// Newsletter form
const newsletterForm = document.querySelector('form');
newsletterForm?.addEventListener('submit', (e) => {
e.preventDefault();
const email = (e.target as HTMLFormElement).querySelector('input[type="email"]') as HTMLInputElement;
if (email.value) {
// Simulate subscription
const button = (e.target as HTMLFormElement).querySelector('button[type="submit"]') as HTMLButtonElement;
const originalContent = button.innerHTML;
button.innerHTML = '<svg class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>';
button.disabled = true;
setTimeout(() => {
button.innerHTML = '<svg class="w-4 h-4 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>';
email.value = '';
setTimeout(() => {
button.innerHTML = originalContent;
button.disabled = false;
}, 2000);
}, 1000);
}
});
// Animate contact info on hover
const contactItems = document.querySelectorAll('.group');
contactItems.forEach(item => {
item.addEventListener('mouseenter', () => {
const icon = item.querySelector('.flex-shrink-0');
icon?.classList.add('animate-pulse');
});
item.addEventListener('mouseleave', () => {
const icon = item.querySelector('.flex-shrink-0');
icon?.classList.remove('animate-pulse');
});
});
});
</script>
<style>
/* Enhanced footer animations */
.group:hover .w-2 {
animation: pulse-dot 1s ease-in-out infinite;
}
@keyframes pulse-dot {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.8;
}
}
/* Newsletter form enhancements */
form input:focus + button {
transform: scale(1.05);
}
/* Smooth transitions for all interactive elements */
footer a, footer button {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Contact info glow effect on hover */
.group:hover .bg-primary\/10,
.group:hover .bg-accent\/10,
.group:hover .bg-success\/10 {
box-shadow: 0 0 20px -5px currentColor;
}
</style>

View File

@@ -8,115 +8,252 @@ const lang = getLangFromUrl(Astro.url);
const t = await useTranslations(lang); const t = await useTranslations(lang);
--- ---
<header class="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <header class="fixed top-0 left-0 right-0 z-50 transition-all duration-500" id="main-header">
<nav class="container-custom"> <!-- Glass morphism navigation -->
<div class="flex h-16 items-center justify-between"> <nav class="nav-glass">
<!-- Logo --> <div class="container-custom">
<div class="flex items-center"> <div class="flex h-20 items-center justify-between">
<a href={localizePath("/", lang)} class="flex items-center space-x-2">
<div class="h-8 w-8 flex items-center justify-center">
<img
src="/images/TIBER365.png"
alt="Tiber365 Logo"
class="h-6 w-6 object-contain"
/>
</div>
<span class="font-display font-bold text-xl text-foreground">Tiber365</span>
</a>
</div>
<!-- Desktop Navigation --> <!-- Logo with modern styling -->
<div class="hidden md:flex items-center space-x-6"> <div class="flex items-center">
{NAVIGATION.map((item) => ( <a href={localizePath("/", lang)} class="group flex items-center space-x-3 transition-all">
<a <div class="relative">
href={item.type === 'external' ? item.href : localizePath(item.href, lang)} <div class="absolute inset-0 bg-primary/20 rounded-xl blur-md opacity-0 group-hover:opacity-100 transition-opacity"></div>
target={item.type === 'external' ? '_blank' : undefined} <div class="relative h-10 w-10 bg-gradient-to-br from-primary to-accent rounded-xl flex items-center justify-center">
rel={item.type === 'external' ? 'noopener noreferrer' : undefined} <img
class="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors relative group" src="/images/TIBER365.png"
> alt="Tiber365 Logo"
{t(item.label)} class="h-6 w-6 object-contain brightness-0 invert"
{item.type === 'external' && ( />
<svg class="inline h-3 w-3 ml-1 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </div>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/> </div>
</svg> <div class="flex flex-col">
)} <span class="font-bold text-lg text-foreground group-hover:text-primary transition-colors">Tiber365</span>
<span class="absolute inset-x-0 -bottom-1 h-0.5 bg-primary scale-x-0 group-hover:scale-x-100 transition-transform origin-left"></span> <span class="text-xs text-subtle -mt-1">IT Excellence</span>
</div>
</a> </a>
))} </div>
</div>
<!-- Theme Toggle & Language Switcher --> <!-- Desktop Navigation with modern hover effects -->
<div class="flex items-center space-x-4"> <div class="hidden lg:flex items-center space-x-1">
<LanguageSwitcher /> {NAVIGATION.map((item) => (
<ThemeToggle /> <a
href={item.type === 'external' ? item.href : localizePath(item.href, lang)}
target={item.type === 'external' ? '_blank' : undefined}
rel={item.type === 'external' ? 'noopener noreferrer' : undefined}
class="relative px-4 py-2 text-sm font-medium text-muted hover:text-foreground transition-all duration-300 rounded-lg group"
>
<span class="relative z-10 flex items-center gap-1">
{t(item.label)}
{item.type === 'external' && (
<svg class="h-3 w-3 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
)}
</span>
<!-- Hover background -->
<div class="absolute inset-0 bg-surface/50 rounded-lg opacity-0 group-hover:opacity-100 transition-all duration-300 scale-95 group-hover:scale-100"></div>
<!-- Active indicator -->
<div class="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-gradient-to-r from-primary to-accent group-hover:w-full transition-all duration-300"></div>
</a>
))}
<!-- Mobile Menu Button --> <!-- CTA Button in nav -->
<button <a
id="mobile-menu-button" href={localizePath("/contact", lang)}
class="md:hidden inline-flex items-center justify-center p-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent focus:outline-none focus:ring-2 focus:ring-primary" class="ml-4 btn btn-primary btn-sm"
aria-expanded="false" >
aria-label="Toggle mobile menu" Get Started
> </a>
<svg id="mobile-menu-icon" class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </div>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg> <!-- Right side controls -->
<svg id="mobile-close-icon" class="h-6 w-6 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <div class="flex items-center space-x-3">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> <!-- Language switcher with modern styling -->
</svg> <LanguageSwitcher />
</button>
<!-- Theme toggle with modern animation -->
<ThemeToggle />
<!-- Mobile Menu Button -->
<button
id="mobile-menu-button"
class="lg:hidden p-2 rounded-xl bg-surface/50 border border-border/50 text-muted hover:text-foreground hover:bg-surface transition-all"
aria-expanded="false"
aria-label="Toggle mobile menu"
>
<svg id="mobile-menu-icon" class="h-5 w-5 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
<svg id="mobile-close-icon" class="h-5 w-5 hidden transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div> </div>
</div> </div>
<!-- Mobile Navigation --> <!-- Mobile Navigation Menu -->
<div id="mobile-menu" class="md:hidden hidden border-t border-border"> <div id="mobile-menu" class="lg:hidden hidden border-t border-border/50">
<div class="px-2 pt-2 pb-3 space-y-1"> <div class="container-custom py-6">
{NAVIGATION.map((item) => ( <div class="space-y-2">
<a {NAVIGATION.map((item) => (
href={item.type === 'external' ? item.href : localizePath(item.href, lang)} <a
target={item.type === 'external' ? '_blank' : undefined} href={item.type === 'external' ? item.href : localizePath(item.href, lang)}
rel={item.type === 'external' ? 'noopener noreferrer' : undefined} target={item.type === 'external' ? '_blank' : undefined}
class="block px-3 py-2 text-base font-medium text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors" rel={item.type === 'external' ? 'noopener noreferrer' : undefined}
> class="flex items-center justify-between p-4 text-base font-medium text-muted hover:text-foreground hover:bg-surface/50 rounded-xl transition-all group"
{t(item.label)} >
{item.type === 'external' && ( <span class="flex items-center gap-2">
<svg class="inline h-4 w-4 ml-1 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24"> {t(item.label)}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/> {item.type === 'external' && (
<svg class="h-4 w-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
)}
</span>
<svg class="h-4 w-4 opacity-40 group-hover:opacity-60 transition-all" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg> </svg>
)} </a>
</a> ))}
))}
<!-- Mobile CTA -->
<div class="pt-4 mt-4 border-t border-border/50">
<a
href={localizePath("/contact", lang)}
class="btn btn-primary w-full justify-center"
>
Get Started Today
</a>
</div>
</div>
</div> </div>
</div> </div>
</nav> </nav>
<!-- Progress bar for scroll -->
<div class="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-primary to-accent transition-all duration-300" id="scroll-progress"></div>
</header> </header>
<script> <script>
// Mobile menu functionality // Enhanced mobile menu functionality with animations
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const header = document.getElementById('main-header');
const mobileMenuButton = document.getElementById('mobile-menu-button'); const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu'); const mobileMenu = document.getElementById('mobile-menu');
const menuIcon = document.getElementById('mobile-menu-icon'); const menuIcon = document.getElementById('mobile-menu-icon');
const closeIcon = document.getElementById('mobile-close-icon'); const closeIcon = document.getElementById('mobile-close-icon');
const scrollProgress = document.getElementById('scroll-progress');
// Header scroll effect
let lastScrollY = window.scrollY;
function updateHeader() {
const currentScrollY = window.scrollY;
if (currentScrollY > 100) {
header?.classList.add('backdrop-blur-xl', 'bg-background/80');
header?.classList.remove('bg-transparent');
} else {
header?.classList.remove('backdrop-blur-xl', 'bg-background/80');
header?.classList.add('bg-transparent');
}
// Hide/show header on scroll
if (currentScrollY > lastScrollY && currentScrollY > 100) {
header?.classList.add('-translate-y-full');
} else {
header?.classList.remove('-translate-y-full');
}
lastScrollY = currentScrollY;
// Update progress bar
const scrollPercentage = (currentScrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
if (scrollProgress) {
scrollProgress.style.width = `${Math.min(scrollPercentage, 100)}%`;
}
}
window.addEventListener('scroll', updateHeader, { passive: true });
// Mobile menu toggle with enhanced animations
if (mobileMenuButton && mobileMenu && menuIcon && closeIcon) { if (mobileMenuButton && mobileMenu && menuIcon && closeIcon) {
mobileMenuButton.addEventListener('click', () => { mobileMenuButton.addEventListener('click', () => {
const isExpanded = mobileMenuButton.getAttribute('aria-expanded') === 'true'; const isExpanded = mobileMenuButton.getAttribute('aria-expanded') === 'true';
const newState = !isExpanded;
mobileMenuButton.setAttribute('aria-expanded', (!isExpanded).toString()); mobileMenuButton.setAttribute('aria-expanded', newState.toString());
mobileMenu.classList.toggle('hidden');
menuIcon.classList.toggle('hidden'); if (newState) {
closeIcon.classList.toggle('hidden'); mobileMenu.classList.remove('hidden');
}); // Animate in
requestAnimationFrame(() => {
mobileMenu.style.opacity = '0';
mobileMenu.style.transform = 'translateY(-10px)';
mobileMenu.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
requestAnimationFrame(() => {
mobileMenu.style.opacity = '1';
mobileMenu.style.transform = 'translateY(0)';
});
});
menuIcon.classList.add('hidden');
closeIcon.classList.remove('hidden');
document.body.style.overflow = 'hidden';
} else {
// Animate out
mobileMenu.style.opacity = '0';
mobileMenu.style.transform = 'translateY(-10px)';
setTimeout(() => {
mobileMenu.classList.add('hidden');
document.body.style.overflow = '';
}, 300);
// Close mobile menu when clicking outside
document.addEventListener('click', (event) => {
if (!mobileMenuButton.contains(event.target as Node) && !mobileMenu.contains(event.target as Node)) {
mobileMenuButton.setAttribute('aria-expanded', 'false');
mobileMenu.classList.add('hidden');
menuIcon.classList.remove('hidden'); menuIcon.classList.remove('hidden');
closeIcon.classList.add('hidden'); closeIcon.classList.add('hidden');
} }
}); });
// Close menu when clicking outside
document.addEventListener('click', (event) => {
if (!mobileMenuButton.contains(event.target as Node) &&
!mobileMenu.contains(event.target as Node) &&
!mobileMenu.classList.contains('hidden')) {
mobileMenuButton.click();
}
});
// Close menu on escape key
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && !mobileMenu.classList.contains('hidden')) {
mobileMenuButton.click();
}
});
} }
}); });
</script> </script>
<style>
/* Enhanced mobile menu animations */
#mobile-menu {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
/* Smooth header transitions */
#main-header {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
backdrop-filter 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Logo hover effect */
.group:hover .brightness-0 {
filter: brightness(1) invert(0);
transition: filter 0.3s ease;
}
</style>

View File

@@ -5,55 +5,160 @@ const lang = getLangFromUrl(Astro.url);
const t = await useTranslations(lang); const t = await useTranslations(lang);
--- ---
<section class="relative min-h-[calc(100vh-4rem)] flex items-center justify-center bg-gradient-to-br from-background via-background to-muted overflow-hidden"> <section class="relative min-h-screen flex items-center justify-center overflow-hidden">
<!-- Animated background -->
<div class="absolute inset-0 hero-gradient">
<!-- Floating geometric shapes -->
<div class="absolute top-20 left-10 w-32 h-32 bg-primary/10 rounded-full blur-xl animate-float"></div>
<div class="absolute top-40 right-20 w-24 h-24 bg-accent/10 rounded-full blur-lg animate-float" style="animation-delay: -2s;"></div>
<div class="absolute bottom-40 left-1/4 w-40 h-40 bg-primary/5 rounded-full blur-2xl animate-float" style="animation-delay: -4s;"></div>
<div class="absolute top-1/3 right-1/4 w-20 h-20 bg-accent/15 rounded-full blur-md animate-float" style="animation-delay: -1s;"></div>
<!-- Grid pattern -->
<div class="absolute inset-0 opacity-30">
<div class="absolute inset-0" style="background-image: radial-gradient(circle at 2px 2px, rgb(var(--color-primary) / 0.15) 1px, transparent 0); background-size: 40px 40px;"></div>
</div>
</div>
<div class="container-custom relative z-10"> <div class="container-custom relative z-10">
<div class="flex flex-col items-center justify-center w-full animate-on-scroll"> <div class="max-w-6xl mx-auto text-center space-content">
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-display font-bold text-foreground mb-6 text-center">
{t('hero.title')} <!-- Floating badge -->
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-surface/60 border border-border/50 backdrop-blur-sm animate-on-scroll mb-8">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
</span>
<span class="text-sm font-medium text-muted">Trusted by 100+ businesses</span>
</div>
<!-- Main heading with gradient text -->
<h1 class="animate-on-scroll mb-8" style="animation-delay: 0.1s;">
<span class="block text-foreground mb-4">Professional IT Services</span>
<span class="block text-gradient font-bold">for Modern Businesses</span>
</h1> </h1>
<p class="text-lg sm:text-xl text-muted-foreground mb-8 max-w-2xl mx-auto text-center">
<!-- Subtitle -->
<p class="text-responsive-lg text-muted max-w-3xl mx-auto leading-relaxed animate-on-scroll mb-12" style="animation-delay: 0.2s;">
{t('hero.subtitle')} {t('hero.subtitle')}
</p> </p>
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-8">
<!-- CTA Buttons -->
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center animate-on-scroll mb-16" style="animation-delay: 0.3s;">
<a <a
href={localizePath("/contact", lang)} href={localizePath("/contact", lang)}
class="btn-primary px-8 py-4 text-lg font-semibold rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-105" class="btn btn-primary group"
> >
{t('hero.cta.primary')} <span>{t('hero.cta.primary')}</span>
<svg class="h-4 w-4 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/>
</svg>
</a> </a>
<a <a
href={localizePath("/services", lang)} href={localizePath("/services", lang)}
class="btn-outline px-8 py-4 text-lg font-semibold rounded-xl transition-all duration-300 hover:scale-105" class="btn btn-outline group"
> >
{t('hero.cta.secondary')} <svg class="h-4 w-4 transition-transform group-hover:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.586-4.586a2 2 0 118 8L18 18l-4-4m-5-5v12m0 0l-3-3m3 3l3-3"/>
</svg>
<span>{t('hero.cta.secondary')}</span>
</a> </a>
</div> </div>
<p class="mt-8 text-sm text-muted-foreground text-center">
{t('hero.trusted')} <!-- Trust indicators -->
</p> <div class="grid grid-cols-2 md:grid-cols-5 gap-8 items-center animate-on-scroll" style="animation-delay: 0.4s;">
<!-- Service Icons Row --> <div class="text-center space-y-2">
<div class="flex justify-center gap-6 mt-6 mb-4 text-3xl opacity-80"> <div class="text-2xl font-bold text-primary">5+</div>
<span>🗂️</span> <div class="text-sm text-subtle">Years Experience</div>
<span>🌐</span> </div>
<span>⚙️</span> <div class="text-center space-y-2">
<span>🔒</span> <div class="text-2xl font-bold text-primary">100+</div>
<span>🛠️</span> <div class="text-sm text-subtle">Happy Clients</div>
</div>
<div class="text-center space-y-2">
<div class="text-2xl font-bold text-primary">24/7</div>
<div class="text-sm text-subtle">Support</div>
</div>
<div class="text-center space-y-2">
<div class="text-2xl font-bold text-primary">99.9%</div>
<div class="text-sm text-subtle">Uptime</div>
</div>
<div class="text-center space-y-2">
<div class="text-2xl font-bold text-primary">4.9★</div>
<div class="text-sm text-subtle">Rating</div>
</div>
</div> </div>
<!-- Downward Arrow -->
<!-- Service icons with modern styling -->
<div class="flex justify-center gap-8 mt-16 mb-20 animate-on-scroll" style="animation-delay: 0.5s;">
<div class="group cursor-pointer">
<div class="w-16 h-16 rounded-2xl bg-surface/60 border border-border/50 backdrop-blur-sm flex items-center justify-center transition-all group-hover:scale-110 group-hover:bg-primary/10 group-hover:border-primary/30">
<span class="text-2xl">🗂️</span>
</div>
<div class="text-xs text-subtle mt-2 text-center">M365</div>
</div>
<div class="group cursor-pointer">
<div class="w-16 h-16 rounded-2xl bg-surface/60 border border-border/50 backdrop-blur-sm flex items-center justify-center transition-all group-hover:scale-110 group-hover:bg-primary/10 group-hover:border-primary/30">
<span class="text-2xl">🌐</span>
</div>
<div class="text-xs text-subtle mt-2 text-center">Hosting</div>
</div>
<div class="group cursor-pointer">
<div class="w-16 h-16 rounded-2xl bg-surface/60 border border-border/50 backdrop-blur-sm flex items-center justify-center transition-all group-hover:scale-110 group-hover:bg-primary/10 group-hover:border-primary/30">
<span class="text-2xl">⚙️</span>
</div>
<div class="text-xs text-subtle mt-2 text-center">Setup</div>
</div>
<div class="group cursor-pointer">
<div class="w-16 h-16 rounded-2xl bg-surface/60 border border-border/50 backdrop-blur-sm flex items-center justify-center transition-all group-hover:scale-110 group-hover:bg-primary/10 group-hover:border-primary/30">
<span class="text-2xl">🔒</span>
</div>
<div class="text-xs text-subtle mt-2 text-center">Security</div>
</div>
<div class="group cursor-pointer">
<div class="w-16 h-16 rounded-2xl bg-surface/60 border border-border/50 backdrop-blur-sm flex items-center justify-center transition-all group-hover:scale-110 group-hover:bg-primary/10 group-hover:border-primary/30">
<span class="text-2xl">🛠️</span>
</div>
<div class="text-xs text-subtle mt-2 text-center">Support</div>
</div>
</div>
<!-- Scroll indicator - moved to separate container with proper spacing -->
<div class="flex justify-center mt-8"> <div class="flex justify-center mt-8">
<span class="animate-bounce text-3xl text-primary/60">↓</span> <div class="animate-bounce cursor-pointer" onclick="document.getElementById('services')?.scrollIntoView({behavior: 'smooth'})">
<div class="w-6 h-10 border-2 border-border rounded-full flex justify-center hover:border-primary transition-colors">
<div class="w-1 h-3 bg-primary rounded-full mt-2 animate-pulse"></div>
</div>
<div class="text-xs text-subtle mt-2 text-center">Scroll</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="absolute inset-0 -z-10 overflow-hidden">
<div class="absolute -top-1/2 -right-1/2 w-full h-full rotate-12 bg-gradient-radial from-primary/5 via-primary/2 to-transparent opacity-70"></div> <!-- Decorative elements -->
<div class="absolute -bottom-1/2 -left-1/2 w-full h-full -rotate-12 bg-gradient-radial from-primary/5 via-primary/2 to-transparent opacity-70"></div> <div class="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-border to-transparent"></div>
</div>
</section> </section>
<style> <style>
.bg-grid-pattern { /* Additional custom animations */
background-image: radial-gradient(circle, rgba(var(--color-foreground) / 0.1) 1px, transparent 1px); @keyframes float {
background-size: 30px 30px; 0%, 100% { transform: translateY(0px) rotate(0deg); }
33% { transform: translateY(-10px) rotate(1deg); }
66% { transform: translateY(-5px) rotate(-1deg); }
}
@keyframes gradient-shift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.text-gradient {
background-size: 200% 200%;
animation: gradient-shift 4s ease infinite;
} }
</style> </style>

View File

@@ -1,84 +1,305 @@
--- ---
import { SERVICES } from '@config'; // Simple fallback approach - define SERVICES directly in the component for now
const SERVICES = [
{
id: 'microsoft365',
icon: '🗂️',
titleKey: 'services.microsoft365.title',
descriptionKey: 'services.microsoft365.description',
features: [
'services.microsoft365.features.migrations',
'services.microsoft365.features.apps',
'services.microsoft365.features.teams',
'services.microsoft365.features.sharepoint',
'services.microsoft365.features.admin',
],
},
{
id: 'management',
icon: '⚙️',
titleKey: 'services.management.title',
descriptionKey: 'services.management.description',
features: [
'services.management.features.automation',
'services.management.features.monitoring',
'services.management.features.maintenance',
'services.management.features.optimization',
],
},
{
id: 'networking',
icon: '🌐',
titleKey: 'services.networking.title',
descriptionKey: 'services.networking.description',
features: [
'services.networking.features.ubiquiti',
'services.networking.features.infrastructure',
'services.networking.features.security',
'services.networking.features.monitoring',
],
},
{
id: 'hosting',
icon: '🛠️',
titleKey: 'services.hosting.title',
descriptionKey: 'services.hosting.description',
features: [
'services.hosting.features.webhosting',
'services.hosting.features.domains',
'services.hosting.features.ssl',
'services.hosting.features.backup',
],
},
{
id: 'custom',
icon: '🔒',
titleKey: 'services.custom.title',
descriptionKey: 'services.custom.description',
features: [
'services.custom.features.consultation',
'services.custom.features.development',
'services.custom.features.integration',
'services.custom.features.support',
],
},
];
import { getLangFromUrl, useTranslations, localizePath } from '../utils/i18n'; import { getLangFromUrl, useTranslations, localizePath } from '../utils/i18n';
const lang = getLangFromUrl(Astro.url); const lang = getLangFromUrl(Astro.url);
const t = await useTranslations(lang); const t = await useTranslations(lang);
// Service icons mapping for modern look
const serviceIcons = {
microsoft365: `
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none">
<rect x="2" y="2" width="9" height="9" rx="2" fill="currentColor" opacity="0.8"/>
<rect x="13" y="2" width="9" height="9" rx="2" fill="currentColor" opacity="0.6"/>
<rect x="2" y="13" width="9" height="9" rx="2" fill="currentColor" opacity="0.4"/>
<rect x="13" y="13" width="9" height="9" rx="2" fill="currentColor" opacity="0.2"/>
</svg>
`,
management: `
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</svg>
`,
networking: `
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="16" y="16" width="6" height="6" rx="1"/>
<rect x="2" y="16" width="6" height="6" rx="1"/>
<rect x="9" y="2" width="6" height="6" rx="1"/>
<path d="M9 9v1a2 2 0 0 0 2 2h1m0 0h1a2 2 0 0 1 2 2v1"/>
</svg>
`,
hosting: `
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
<line x1="8" y1="21" x2="16" y2="21"/>
<line x1="12" y1="17" x2="12" y2="21"/>
</svg>
`,
custom: `
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
</svg>
`
};
--- ---
<section id="services" class="py-20 bg-muted/30"> <section id="services" class="section-padding bg-gradient-to-b from-background to-surface/30">
<div class="container-custom"> <div class="container-custom">
<!-- Section header -->
<div class="text-center mb-16 animate-on-scroll"> <!-- Section header with modern typography -->
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-display font-bold text-foreground mb-4"> <div class="text-center max-w-4xl mx-auto mb-20 animate-on-scroll">
{t('services.title')} <div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 border border-primary/20 text-primary text-sm font-medium mb-6">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/>
</svg>
Our Services
</div>
<h2 class="text-responsive-xl font-bold text-foreground mb-6">
Comprehensive IT Solutions
<span class="block text-gradient">Tailored for Your Success</span>
</h2> </h2>
<p class="text-lg sm:text-xl text-muted-foreground max-w-3xl mx-auto">
<p class="text-responsive-base text-muted leading-relaxed">
{t('services.subtitle')} {t('services.subtitle')}
</p> </p>
</div> </div>
<!-- Services grid --> <!-- Services grid with modern cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
{SERVICES.map((service, index) => ( {SERVICES.map((service, index) => (
<div <div
class="card p-6 hover:shadow-xl transition-all duration-300 hover:scale-105 animate-on-scroll group" class="group relative animate-on-scroll"
style={`animation-delay: ${index * 0.1}s`} style={`animation-delay: ${index * 0.1}s`}
> >
<!-- Service icon --> <!-- Background glow effect -->
<div class="text-4xl mb-4 group-hover:scale-110 transition-transform duration-300"> <div class="absolute inset-0 bg-gradient-to-br from-primary/5 to-accent/5 rounded-2xl blur-xl opacity-0 group-hover:opacity-100 transition-all duration-500 scale-95 group-hover:scale-100"></div>
{service.icon}
</div>
<!-- Service title --> <!-- Main card -->
<h3 class="text-xl font-display font-semibold text-foreground mb-3"> <div class="relative card-glass p-8 h-full flex flex-col hover:border-primary/30 transition-all duration-500">
{t(service.titleKey)}
</h3>
<!-- Service description --> <!-- Service icon with modern container -->
<p class="text-muted-foreground mb-4 leading-relaxed"> <div class="mb-6">
{t(service.descriptionKey)} <div class="relative inline-flex">
</p> <!-- Icon background with gradient -->
<div class="absolute inset-0 bg-gradient-to-br from-primary to-accent rounded-2xl blur-md opacity-20 group-hover:opacity-40 transition-all duration-500"></div>
<!-- Service features --> <div class="relative w-16 h-16 bg-gradient-to-br from-primary/10 to-accent/10 rounded-2xl flex items-center justify-center text-primary group-hover:scale-110 transition-all duration-500 border border-primary/20">
<ul class="space-y-2"> <Fragment set:html={serviceIcons[service.id] || service.icon} />
{service.features.map((feature) => ( </div>
<li class="flex items-start text-sm text-muted-foreground">
<svg class="h-4 w-4 mt-0.5 mr-2 text-primary flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <!-- Floating dot indicator -->
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/> <div class="absolute -top-1 -right-1 w-3 h-3 bg-accent rounded-full opacity-0 group-hover:opacity-100 transition-all duration-500 animate-pulse"></div>
</div>
</div>
<!-- Service title -->
<h3 class="text-xl font-bold text-foreground mb-4 group-hover:text-primary transition-colors duration-300">
{t(service.titleKey)}
</h3>
<!-- Service description -->
<p class="text-muted leading-relaxed mb-6 flex-grow">
{t(service.descriptionKey)}
</p>
<!-- Service features with modern styling -->
<div class="space-y-3 mb-8">
{service.features.slice(0, 3).map((feature) => (
<div class="flex items-center gap-3 text-sm">
<div class="flex-shrink-0 w-5 h-5 rounded-full bg-primary/10 flex items-center justify-center">
<svg class="w-3 h-3 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/>
</svg>
</div>
<span class="text-muted group-hover:text-foreground transition-colors">
{t(feature)}
</span>
</div>
))}
{service.features.length > 3 && (
<div class="flex items-center gap-3 text-sm">
<div class="flex-shrink-0 w-5 h-5 rounded-full bg-accent/10 flex items-center justify-center">
<span class="text-xs text-accent font-medium">+{service.features.length - 3}</span>
</div>
<span class="text-subtle">
And {service.features.length - 3} more features
</span>
</div>
)}
</div>
<!-- CTA Link with modern styling -->
<div class="mt-auto">
<a
href={localizePath(`/services#${service.id}`, lang)}
class="group/link inline-flex items-center gap-2 text-primary font-medium hover:text-accent transition-all duration-300"
>
<span>Learn more</span>
<svg class="w-4 h-4 transition-transform group-hover/link:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/>
</svg> </svg>
{t(feature)} </a>
</li> </div>
))}
</ul>
<!-- Learn more link --> <!-- Decorative corner element -->
<div class="mt-6"> <div class="absolute top-4 right-4 w-2 h-2 bg-gradient-to-br from-primary to-accent rounded-full opacity-0 group-hover:opacity-100 transition-all duration-500 animate-pulse"></div>
<a
href={localizePath(`/services#${service.id}`, lang)}
class="inline-flex items-center text-primary hover:text-primary/80 font-medium text-sm group-hover:translate-x-1 transition-all duration-200"
>
Learn more
<svg class="h-4 w-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</a>
</div> </div>
</div> </div>
))} ))}
</div> </div>
<!-- CTA section --> <!-- Modern CTA section -->
<div class="text-center animate-on-scroll"> <div class="text-center animate-on-scroll">
<a <div class="inline-flex flex-col items-center gap-6 p-8 rounded-3xl bg-gradient-to-br from-surface/50 to-background/50 border border-border/50 backdrop-blur-sm">
href={localizePath("/services", lang)} <div>
class="btn-primary px-8 py-4 text-lg font-semibold rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-105 inline-flex items-center group" <h3 class="text-2xl font-bold text-foreground mb-2">Ready to transform your IT?</h3>
> <p class="text-muted">Let's discuss how we can help your business thrive</p>
{t('services.viewAll')} </div>
<svg class="h-5 w-5 ml-2 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/> <div class="flex flex-col sm:flex-row gap-4">
</svg> <a
</a> href={localizePath("/services", lang)}
class="btn btn-outline group"
>
<svg class="w-4 h-4 transition-transform group-hover:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
View All Services
</a>
<a
href={localizePath("/contact", lang)}
class="btn btn-primary group"
>
Get Started Today
<svg class="w-4 h-4 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/>
</svg>
</a>
</div>
</div>
</div> </div>
</div> </div>
</section> </section>
<style>
/* Custom animations for service cards */
.group:hover .card-glass {
transform: translateY(-8px);
}
/* Staggered feature animations */
.group:hover .space-y-3 > div {
animation: slideInLeft 0.3s ease-out forwards;
}
.group:hover .space-y-3 > div:nth-child(2) {
animation-delay: 0.1s;
}
.group:hover .space-y-3 > div:nth-child(3) {
animation-delay: 0.2s;
}
.group:hover .space-y-3 > div:nth-child(4) {
animation-delay: 0.3s;
}
@keyframes slideInLeft {
from {
opacity: 0.5;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Enhanced hover effects */
.card-glass::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
border-radius: inherit;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.group:hover .card-glass::before {
opacity: 1;
}
</style>

BIN
src/content/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,5 +1,4 @@
--- ---
import { SITE } from '../site.config';
import { getLangFromUrl, useTranslations } from '../utils/i18n'; import { getLangFromUrl, useTranslations } from '../utils/i18n';
import '../styles/global.css'; import '../styles/global.css';
@@ -15,11 +14,20 @@ export interface Props {
const lang = getLangFromUrl(Astro.url); const lang = getLangFromUrl(Astro.url);
const t = await useTranslations(lang); const t = await useTranslations(lang);
// Default site configuration - inline to avoid import issues
const SITE_CONFIG = {
title: 'Tiber365',
description: 'Professional IT Services for Your Business',
url: 'https://tiber365.it',
author: 'Tiber365',
ogImage: '/images/og-image.png',
};
const { const {
title = t('site.title'), title = t('site.title') || SITE_CONFIG.title,
description = t('site.description'), description = t('site.description') || SITE_CONFIG.description,
image = SITE.ogImage, image = SITE_CONFIG.ogImage,
keywords = t('meta.keywords'), keywords = t('meta.keywords') || 'IT services, Microsoft 365, networking, web hosting',
noindex = false, noindex = false,
nofollow = false nofollow = false
} = Astro.props; } = Astro.props;
@@ -30,19 +38,19 @@ let ogImageURL;
let twitterImageURL; let twitterImageURL;
try { try {
const siteURL = Astro.site || new URL('https://tiber365.it'); const siteURL = Astro.site || new URL(SITE_CONFIG.url);
canonicalURL = new URL(Astro.url.pathname, siteURL); canonicalURL = new URL(Astro.url.pathname, siteURL);
ogImageURL = new URL(image, siteURL); ogImageURL = new URL(image, siteURL);
twitterImageURL = new URL(image, siteURL); twitterImageURL = new URL(image, siteURL);
} catch (error) { } catch (error) {
// Fallback URLs if there's an issue // Fallback URLs if there's an issue
const fallbackSite = 'https://tiber365.it'; const fallbackSite = SITE_CONFIG.url;
canonicalURL = new URL(Astro.url?.pathname || '/', fallbackSite); canonicalURL = new URL(Astro.url?.pathname || '/', fallbackSite);
ogImageURL = new URL(image, fallbackSite); ogImageURL = new URL(image, fallbackSite);
twitterImageURL = new URL(image, fallbackSite); twitterImageURL = new URL(image, fallbackSite);
} }
const fullTitle = title === t('site.title') ? title : `${title} | ${t('site.title')}`; const fullTitle = title === (t('site.title') || SITE_CONFIG.title) ? title : `${title} | ${t('site.title') || SITE_CONFIG.title}`;
// All available locales // All available locales
const locales = ["en", "nl", "de", "fr"]; const locales = ["en", "nl", "de", "fr"];
@@ -51,10 +59,10 @@ const locales = ["en", "nl", "de", "fr"];
const structuredData = { const structuredData = {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "Organization", "@type": "Organization",
"name": t('site.title'), "name": t('site.title') || SITE_CONFIG.title,
"description": description, "description": description,
"url": SITE.url, "url": SITE_CONFIG.url,
"logo": `${SITE.url}/images/TIBER365.png`, "logo": `${SITE_CONFIG.url}/images/TIBER365.png`,
"sameAs": [ "sameAs": [
"https://support.tiber365.it" "https://support.tiber365.it"
], ],
@@ -84,8 +92,8 @@ const structuredData = {
<!-- Font Preloading --> <!-- Font Preloading -->
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@400;500;600;700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'" /> <link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@400;500;600;700&display=swap" /></noscript> <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" /></noscript>
<!-- DNS Prefetch --> <!-- DNS Prefetch -->
<link rel="dns-prefetch" href="//fonts.googleapis.com" /> <link rel="dns-prefetch" href="//fonts.googleapis.com" />
@@ -95,7 +103,7 @@ const structuredData = {
<title>{fullTitle}</title> <title>{fullTitle}</title>
<meta name="description" content={description} /> <meta name="description" content={description} />
<meta name="keywords" content={keywords} /> <meta name="keywords" content={keywords} />
<meta name="author" content={SITE.author} /> <meta name="author" content={SITE_CONFIG.author} />
<!-- Robots Meta --> <!-- Robots Meta -->
{noindex && <meta name="robots" content="noindex" />} {noindex && <meta name="robots" content="noindex" />}
@@ -122,7 +130,7 @@ const structuredData = {
<meta property="og:image" content={ogImageURL} /> <meta property="og:image" content={ogImageURL} />
<meta property="og:image:width" content="1200" /> <meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" /> <meta property="og:image:height" content="630" />
<meta property="og:site_name" content={t('site.title')} /> <meta property="og:site_name" content={t('site.title') || SITE_CONFIG.title} />
<meta property="og:locale" content={lang === 'en' ? 'en_US' : lang === 'nl' ? 'nl_NL' : lang === 'de' ? 'de_DE' : 'fr_FR'} /> <meta property="og:locale" content={lang === 'en' ? 'en_US' : lang === 'nl' ? 'nl_NL' : lang === 'de' ? 'de_DE' : 'fr_FR'} />
<!-- Twitter --> <!-- Twitter -->
@@ -142,7 +150,7 @@ const structuredData = {
{locales.map(locale => { {locales.map(locale => {
const currentPath = Astro.url.pathname.replace(/^\/[a-z]{2}\//, '/').replace(/^\/[a-z]{2}$/, '/'); const currentPath = Astro.url.pathname.replace(/^\/[a-z]{2}\//, '/').replace(/^\/[a-z]{2}$/, '/');
const localePath = locale === 'en' ? currentPath : `/${locale}${currentPath}`; const localePath = locale === 'en' ? currentPath : `/${locale}${currentPath}`;
const localeUrl = new URL(localePath, SITE.url).toString(); const localeUrl = new URL(localePath, SITE_CONFIG.url).toString();
return ( return (
<link <link
rel="alternate" rel="alternate"

BIN
src/pages/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,53 +1,20 @@
// src/site.config.ts
// Complete configuration to fix all import issues
export const SITE = { export const SITE = {
title: 'Tiber365', title: 'Tiber365',
description: 'Professional IT services for freelancers and small businesses. Microsoft 365 support, networking solutions, web hosting, and custom IT projects.', description: 'Professional IT Services for Your Business',
defaultLanguage: 'en', url: 'https://tiber365.it',
author: 'Tiber365', author: 'Tiber365',
ogImage: '/images/og-image.jpg', ogImage: '/images/og-image.png',
twitterImage: '/images/twitter-image.jpg', sitemap: true,
url: 'https://tiber365.it' googleAnalyticsId: '',
} as const; } as const;
// UPDATED: Removed Italian, added German and French
export const LANGUAGES = {
en: 'English',
nl: 'Nederlands',
de: 'Deutsch',
fr: 'Français'
} as const;
export const NAVIGATION = [
{
label: 'nav.home',
href: '/',
type: 'internal'
},
{
label: 'nav.services',
href: '/services',
type: 'internal'
},
{
label: 'nav.about',
href: '/about',
type: 'internal'
},
{
label: 'nav.blog',
href: '/blog',
type: 'internal'
},
{
label: 'nav.support',
href: 'https://support.tiber365.it',
type: 'external'
}
] as const;
export const SERVICES = [ export const SERVICES = [
{ {
id: 'microsoft365', id: 'microsoft365',
icon: '🏢', icon: '🗂️',
titleKey: 'services.microsoft365.title', titleKey: 'services.microsoft365.title',
descriptionKey: 'services.microsoft365.description', descriptionKey: 'services.microsoft365.description',
features: [ features: [
@@ -55,8 +22,8 @@ export const SERVICES = [
'services.microsoft365.features.apps', 'services.microsoft365.features.apps',
'services.microsoft365.features.teams', 'services.microsoft365.features.teams',
'services.microsoft365.features.sharepoint', 'services.microsoft365.features.sharepoint',
'services.microsoft365.features.admin' 'services.microsoft365.features.admin',
] ],
}, },
{ {
id: 'management', id: 'management',
@@ -67,8 +34,8 @@ export const SERVICES = [
'services.management.features.automation', 'services.management.features.automation',
'services.management.features.monitoring', 'services.management.features.monitoring',
'services.management.features.maintenance', 'services.management.features.maintenance',
'services.management.features.optimization' 'services.management.features.optimization',
] ],
}, },
{ {
id: 'networking', id: 'networking',
@@ -79,34 +46,67 @@ export const SERVICES = [
'services.networking.features.ubiquiti', 'services.networking.features.ubiquiti',
'services.networking.features.infrastructure', 'services.networking.features.infrastructure',
'services.networking.features.security', 'services.networking.features.security',
'services.networking.features.monitoring' 'services.networking.features.monitoring',
] ],
}, },
{ {
id: 'hosting', id: 'hosting',
icon: '🚀', icon: '🛠️',
titleKey: 'services.hosting.title', titleKey: 'services.hosting.title',
descriptionKey: 'services.hosting.description', descriptionKey: 'services.hosting.description',
features: [ features: [
'services.hosting.features.webhosting', 'services.hosting.features.webhosting',
'services.hosting.features.domains', 'services.hosting.features.domains',
'services.hosting.features.ssl', 'services.hosting.features.ssl',
'services.hosting.features.backup' 'services.hosting.features.backup',
] ],
}, },
{ {
id: 'custom', id: 'custom',
icon: '🛠️', icon: '🔒',
titleKey: 'services.custom.title', titleKey: 'services.custom.title',
descriptionKey: 'services.custom.description', descriptionKey: 'services.custom.description',
features: [ features: [
'services.custom.features.consultation', 'services.custom.features.consultation',
'services.custom.features.development', 'services.custom.features.development',
'services.custom.features.integration', 'services.custom.features.integration',
'services.custom.features.support' 'services.custom.features.support',
] ],
} },
] as const; ];
export const NAVIGATION = [
{
label: 'nav.home',
href: '/',
type: 'internal' as const,
},
{
label: 'nav.services',
href: '/services',
type: 'internal' as const,
},
{
label: 'nav.about',
href: '/about',
type: 'internal' as const,
},
{
label: 'nav.contact',
href: '/contact',
type: 'internal' as const,
},
{
label: 'nav.blog',
href: 'https://blog.tiber365.it',
type: 'external' as const,
},
{
label: 'nav.support',
href: 'https://support.tiber365.it',
type: 'external' as const,
},
];
export const TESTIMONIALS = [ export const TESTIMONIALS = [
{ {
@@ -114,20 +114,28 @@ export const TESTIMONIALS = [
nameKey: 'testimonials.1.name', nameKey: 'testimonials.1.name',
companyKey: 'testimonials.1.company', companyKey: 'testimonials.1.company',
contentKey: 'testimonials.1.content', contentKey: 'testimonials.1.content',
rating: 5 rating: 5,
}, },
{ {
id: 2, id: 2,
nameKey: 'testimonials.2.name', nameKey: 'testimonials.2.name',
companyKey: 'testimonials.2.company', companyKey: 'testimonials.2.company',
contentKey: 'testimonials.2.content', contentKey: 'testimonials.2.content',
rating: 5 rating: 5,
}, },
{ {
id: 3, id: 3,
nameKey: 'testimonials.3.name', nameKey: 'testimonials.3.name',
companyKey: 'testimonials.3.company', companyKey: 'testimonials.3.company',
contentKey: 'testimonials.3.content', contentKey: 'testimonials.3.content',
rating: 5 rating: 5,
} },
] as const; ];
// Contact information
export const CONTACT_INFO = {
email: 'info@tiber365.it',
phone: '+39 123 456 7890',
address: 'Amsterdam, Netherlands',
businessHours: 'Mon-Fri 9AM-6PM CET',
} as const;

View File

@@ -1,160 +1,276 @@
/* Import modern fonts - must be first */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
/* CSS Variables for theming */ /* CSS Variables for modern theming */
:root { :root {
/* Light mode */
--color-background: 255 255 255; --color-background: 255 255 255;
--color-foreground: 15 23 42; --color-surface: 249 250 251;
--color-surface-hover: 243 244 246;
--color-foreground: 6 7 9;
--color-muted: 75 85 99;
--color-subtle: 156 163 175;
/* Primary colors - Modern electric blue */
--color-primary: 59 130 246; --color-primary: 59 130 246;
--color-primary-hover: 37 99 235;
--color-primary-subtle: 239 246 255;
--color-primary-foreground: 255 255 255; --color-primary-foreground: 255 255 255;
--color-secondary: 100 116 139;
--color-secondary-foreground: 255 255 255; /* Accent - Modern purple */
--color-muted: 248 250 252; --color-accent: 139 92 246;
--color-muted-foreground: 100 116 139; --color-accent-hover: 124 58 237;
--color-border: 226 232 240; --color-accent-subtle: 245 243 255;
--color-accent: 241 245 249;
--color-accent-foreground: 15 23 42; /* Success - Modern green */
--color-success: 34 197 94;
--color-success-subtle: 240 253 244;
/* Warning - Modern amber */
--color-warning: 245 158 11;
--color-warning-subtle: 255 251 235;
/* Error - Modern red */
--color-error: 239 68 68;
--color-error-subtle: 254 242 242;
/* Borders and separators */
--color-border: 229 231 235;
--color-border-subtle: 243 244 246;
/* 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);
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
/* Glass morphism */
--glass-bg: 255 255 255 / 0.8;
--glass-border: 255 255 255 / 0.2;
/* Gradients */
--gradient-primary: linear-gradient(135deg, rgb(59 130 246) 0%, rgb(139 92 246) 100%);
--gradient-surface: linear-gradient(135deg, rgb(249 250 251) 0%, rgb(243 244 246) 100%);
--gradient-glass: linear-gradient(135deg, rgba(255 255 255 / 0.1) 0%, rgba(255 255 255 / 0.05) 100%);
} }
[data-theme="dark"] { [data-theme="dark"] {
/* Dark mode */
--color-background: 6 7 9;
--color-surface: 17 24 39;
--color-surface-hover: 31 41 55;
--color-foreground: 249 250 251;
--color-muted: 156 163 175;
--color-subtle: 107 114 128;
/* Primary colors in dark mode */
--color-primary: 96 165 250; --color-primary: 96 165 250;
--color-primary-foreground: 15 23 42; --color-primary-hover: 59 130 246;
--color-secondary: 71 85 105; --color-primary-subtle: 30 58 138;
--color-secondary-foreground: 248 250 252; --color-primary-foreground: 6 7 9;
--color-background: 15 23 42;
--color-foreground: 248 250 252; /* Accent in dark mode */
--color-muted: 30 41 59; --color-accent: 167 139 250;
--color-muted-foreground: 148 163 184; --color-accent-hover: 139 92 246;
--color-border: 51 65 85; --color-accent-subtle: 76 29 149;
--color-accent: 30 41 59;
--color-accent-foreground: 248 250 252; /* Borders in dark mode */
--color-border: 55 65 81;
--color-border-subtle: 31 41 55;
/* Glass morphism in dark mode */
--glass-bg: 6 7 9 / 0.8;
--glass-border: 255 255 255 / 0.1;
/* Gradients in dark mode */
--gradient-primary: linear-gradient(135deg, rgb(96 165 250) 0%, rgb(167 139 250) 100%);
--gradient-surface: linear-gradient(135deg, rgb(17 24 39) 0%, rgb(31 41 55) 100%);
--gradient-glass: linear-gradient(135deg, rgba(255 255 255 / 0.05) 0%, rgba(255 255 255 / 0.02) 100%);
} }
@layer base { @layer base {
* {
@apply border-border;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 5rem;
}
body { body {
background-color: rgb(var(--color-background)); background: rgb(var(--color-background));
color: rgb(var(--color-foreground)); color: rgb(var(--color-foreground));
@apply font-sans; font-family: 'Inter', system-ui, -apple-system, sans-serif;
transition: background-color 0.3s ease, color 0.3s ease; font-feature-settings: 'cv11', 'ss01';
font-variation-settings: 'opsz' 32;
line-height: 1.6;
letter-spacing: -0.01em;
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
@apply font-display; font-family: 'Space Grotesk', system-ui, sans-serif;
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.025em;
} }
/* Smooth scrolling */ h1 {
html { font-size: clamp(2.5rem, 5vw, 4rem);
scroll-behavior: smooth; font-weight: 700;
line-height: 1.1;
letter-spacing: -0.03em;
} }
/* Add scroll margin for header offset */ h2 {
[id] { font-size: clamp(2rem, 4vw, 3rem);
scroll-margin-top: 6rem;
} }
/* Custom scrollbar */ h3 {
font-size: clamp(1.5rem, 3vw, 2rem);
}
/* Modern scrollbar */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 6px;
height: 6px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background-color: rgb(var(--color-muted)); background: rgb(var(--color-surface));
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background-color: rgb(var(--color-secondary)); background: rgb(var(--color-subtle));
border-radius: 9999px; border-radius: 100vh;
border: 2px solid rgb(var(--color-surface));
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background-color: rgb(var(--color-secondary) / 0.8); background: rgb(var(--color-muted));
}
/* Selection */
::selection {
background: rgb(var(--color-primary) / 0.2);
color: rgb(var(--color-foreground));
} }
} }
@layer components { @layer components {
/* Modern container */
.container-custom {
@apply mx-auto max-w-7xl px-6 sm:px-8 lg:px-12;
}
/* Glass morphism card */
.card {
position: relative;
background: rgb(var(--glass-bg));
border: 1px solid rgb(var(--color-border));
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: var(--shadow-lg);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-2xl);
border-color: rgb(var(--color-primary) / 0.2);
}
.card-glass {
background: var(--gradient-glass);
border: 1px solid rgb(var(--glass-border));
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
}
/* Modern buttons */
.btn { .btn {
@apply inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50; @apply inline-flex items-center justify-center gap-2 rounded-xl px-6 py-3 text-sm font-medium transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50;
font-family: 'Inter', system-ui, sans-serif;
font-weight: 500;
letter-spacing: -0.01em;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
} }
.btn-primary { .btn-primary {
background-color: rgb(var(--color-primary)); background: var(--gradient-primary);
color: rgb(var(--color-primary-foreground)); color: rgb(var(--color-primary-foreground));
box-shadow: var(--shadow-md);
border: 1px solid rgb(var(--color-primary));
} }
.btn-primary:hover { .btn-primary:hover {
background-color: rgb(var(--color-primary) / 0.9); transform: translateY(-2px);
box-shadow: var(--shadow-xl);
} }
.btn-secondary { .btn-secondary {
background-color: rgb(var(--color-secondary)); background: rgb(var(--color-surface));
color: rgb(var(--color-secondary-foreground)); color: rgb(var(--color-foreground));
border: 1px solid rgb(var(--color-border));
} }
.btn-secondary:hover { .btn-secondary:hover {
background-color: rgb(var(--color-secondary) / 0.8); background: rgb(var(--color-surface-hover));
transform: translateY(-1px);
box-shadow: var(--shadow-lg);
} }
.btn-outline { .btn-outline {
border: 1px solid rgb(var(--color-border)); background: transparent;
background-color: transparent;
color: rgb(var(--color-foreground)); color: rgb(var(--color-foreground));
border: 1px solid rgb(var(--color-border));
backdrop-filter: blur(10px);
} }
.btn-outline:hover { .btn-outline:hover {
background-color: rgb(var(--color-accent)); background: rgb(var(--color-surface) / 0.5);
} border-color: rgb(var(--color-primary));
.card {
@apply rounded-lg border border-border bg-background p-6 shadow-sm;
}
.container-custom {
@apply mx-auto max-w-7xl px-4 sm:px-6 lg:px-8;
}
.bg-background {
background-color: rgb(var(--color-background));
}
.text-foreground {
color: rgb(var(--color-foreground));
}
.text-muted-foreground {
color: rgb(var(--color-muted-foreground));
}
.bg-muted {
background-color: rgb(var(--color-muted));
}
.bg-primary {
background-color: rgb(var(--color-primary));
}
.text-primary {
color: rgb(var(--color-primary)); color: rgb(var(--color-primary));
} }
.text-primary-foreground { /* Modern navigation */
color: rgb(var(--color-primary-foreground)); .nav-glass {
background: rgb(var(--glass-bg));
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
border-bottom: 1px solid rgb(var(--color-border-subtle));
} }
.bg-accent { /* Animated elements */
background-color: rgb(var(--color-accent));
}
.border-border {
border-color: rgb(var(--color-border));
}
}
@layer utilities {
.animate-on-scroll { .animate-on-scroll {
opacity: 0; opacity: 0;
transform: translateY(20px); transform: translateY(30px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out; transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
} }
.animate-on-scroll.in-view { .animate-on-scroll.in-view {
@@ -162,51 +278,134 @@
transform: translateY(0); transform: translateY(0);
} }
/* Preloading styles */ .animate-float {
.link-preloading { animation: float 6s ease-in-out infinite;
position: relative;
transition: all 0.2s ease;
} }
.link-preloading::after { .animate-pulse-subtle {
content: ''; animation: pulse-subtle 4s ease-in-out infinite;
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background-color: rgb(var(--color-primary));
transition: width 0.3s ease;
} }
.link-preloading:hover::after { /* Modern form elements */
width: 100%; .form-input {
@apply w-full rounded-xl border border-border bg-surface/50 px-4 py-3 text-foreground placeholder-subtle backdrop-blur-sm transition-all duration-300 focus:border-primary focus:outline-none focus:ring-4 focus:ring-primary/10;
font-family: 'Inter', system-ui, sans-serif;
} }
/* Preload indicator */ .form-textarea {
.preload-indicator { @apply form-input min-h-[120px] resize-y;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, rgb(var(--color-primary)), transparent);
transform: translateX(-100%);
transition: transform 0.3s ease;
z-index: 9999;
pointer-events: none;
} }
.preload-indicator.active { .form-select {
transform: translateX(100%); @apply form-input appearance-none bg-no-repeat;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d'm6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.75rem center;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
} }
/* Smooth transitions for page changes */ /* Hero gradient */
.page-transition { .hero-gradient {
transition: opacity 0.2s ease-in-out; background: radial-gradient(ellipse at top, rgb(var(--color-primary) / 0.1) 0%, transparent 70%),
linear-gradient(135deg, rgb(var(--color-background)) 0%, rgb(var(--color-surface)) 100%);
} }
.page-transition.loading { /* Section spacing */
opacity: 0.7; .section-padding {
@apply py-24 lg:py-32;
}
/* Text gradients */
.text-gradient {
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Utility classes */
.bg-background { background-color: rgb(var(--color-background)); }
.bg-surface { background-color: rgb(var(--color-surface)); }
.bg-primary { background-color: rgb(var(--color-primary)); }
.bg-accent { background-color: rgb(var(--color-accent)); }
.text-foreground { color: rgb(var(--color-foreground)); }
.text-muted { color: rgb(var(--color-muted)); }
.text-subtle { color: rgb(var(--color-subtle)); }
.text-primary { color: rgb(var(--color-primary)); }
.text-accent { color: rgb(var(--color-accent)); }
.border-border { border-color: rgb(var(--color-border)); }
.border-subtle { border-color: rgb(var(--color-border-subtle)); }
}
@layer utilities {
/* Modern animations */
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
@keyframes pulse-subtle {
0%, 100% { opacity: 1; }
50% { opacity: 0.8; }
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* Responsive text */
.text-responsive-xl {
font-size: clamp(1.5rem, 4vw, 3rem);
}
.text-responsive-lg {
font-size: clamp(1.25rem, 3vw, 2rem);
}
.text-responsive-base {
font-size: clamp(1rem, 2vw, 1.125rem);
}
/* Modern spacing */
.space-section {
@apply py-16 md:py-24 lg:py-32;
}
.space-content {
@apply space-y-8 md:space-y-12;
}
/* Grid utilities */
.grid-auto-fit {
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.grid-auto-fill {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
} }
} }

View File

@@ -7,12 +7,22 @@ export default {
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'], sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
display: ['Poppins', 'system-ui', 'sans-serif'], display: ['Space Grotesk', 'Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'Monaco', 'Cascadia Code', 'Segoe UI Mono', 'Roboto Mono', 'monospace'],
}, },
colors: { colors: {
// Modern color system
background: 'rgb(var(--color-background) / <alpha-value>)',
foreground: 'rgb(var(--color-foreground) / <alpha-value>)',
surface: 'rgb(var(--color-surface) / <alpha-value>)',
muted: 'rgb(var(--color-muted) / <alpha-value>)',
subtle: 'rgb(var(--color-subtle) / <alpha-value>)',
primary: { primary: {
DEFAULT: 'rgb(var(--color-primary) / <alpha-value>)', DEFAULT: 'rgb(var(--color-primary) / <alpha-value>)',
hover: 'rgb(var(--color-primary-hover) / <alpha-value>)',
subtle: 'rgb(var(--color-primary-subtle) / <alpha-value>)',
foreground: 'rgb(var(--color-primary-foreground) / <alpha-value>)', foreground: 'rgb(var(--color-primary-foreground) / <alpha-value>)',
50: '#eff6ff', 50: '#eff6ff',
100: '#dbeafe', 100: '#dbeafe',
@@ -24,57 +34,452 @@ export default {
700: '#1d4ed8', 700: '#1d4ed8',
800: '#1e40af', 800: '#1e40af',
900: '#1e3a8a', 900: '#1e3a8a',
950: '#172554',
}, },
secondary: {
DEFAULT: 'rgb(var(--color-secondary) / <alpha-value>)',
foreground: 'rgb(var(--color-secondary-foreground) / <alpha-value>)',
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a',
},
background: 'rgb(var(--color-background) / <alpha-value>)',
foreground: 'rgb(var(--color-foreground) / <alpha-value>)',
muted: {
DEFAULT: 'rgb(var(--color-muted) / <alpha-value>)',
foreground: 'rgb(var(--color-muted-foreground) / <alpha-value>)',
},
border: 'rgb(var(--color-border) / <alpha-value>)',
accent: { accent: {
DEFAULT: 'rgb(var(--color-accent) / <alpha-value>)', DEFAULT: 'rgb(var(--color-accent) / <alpha-value>)',
foreground: 'rgb(var(--color-accent-foreground) / <alpha-value>)', hover: 'rgb(var(--color-accent-hover) / <alpha-value>)',
subtle: 'rgb(var(--color-accent-subtle) / <alpha-value>)',
50: '#faf5ff',
100: '#f3e8ff',
200: '#e9d5ff',
300: '#d8b4fe',
400: '#c084fc',
500: '#a855f7',
600: '#9333ea',
700: '#7c3aed',
800: '#6b21a8',
900: '#581c87',
950: '#3b0764',
}, },
success: {
DEFAULT: 'rgb(var(--color-success) / <alpha-value>)',
subtle: 'rgb(var(--color-success-subtle) / <alpha-value>)',
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
950: '#052e16',
},
warning: {
DEFAULT: 'rgb(var(--color-warning) / <alpha-value>)',
subtle: 'rgb(var(--color-warning-subtle) / <alpha-value>)',
50: '#fffbeb',
100: '#fef3c7',
200: '#fde68a',
300: '#fcd34d',
400: '#fbbf24',
500: '#f59e0b',
600: '#d97706',
700: '#b45309',
800: '#92400e',
900: '#78350f',
950: '#451a03',
},
error: {
DEFAULT: 'rgb(var(--color-error) / <alpha-value>)',
subtle: 'rgb(var(--color-error-subtle) / <alpha-value>)',
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
950: '#450a0a',
},
border: 'rgb(var(--color-border) / <alpha-value>)',
'border-subtle': 'rgb(var(--color-border-subtle) / <alpha-value>)',
},
spacing: {
'18': '4.5rem',
'88': '22rem',
'128': '32rem',
'144': '36rem',
},
borderRadius: {
'4xl': '2rem',
'5xl': '2.5rem',
'6xl': '3rem',
},
boxShadow: {
'glass': '0 8px 32px 0 rgba(31, 38, 135, 0.37)',
'glass-inset': 'inset 0 2px 4px 0 rgba(255, 255, 255, 0.06)',
'glow-sm': '0 0 20px -5px var(--tw-shadow-color)',
'glow': '0 0 40px -10px var(--tw-shadow-color)',
'glow-lg': '0 0 60px -15px var(--tw-shadow-color)',
'inner-border': 'inset 0 1px 0 0 rgba(255, 255, 255, 0.1)',
},
backdropBlur: {
xs: '2px',
}, },
animation: { animation: {
'fade-in': 'fadeIn 0.5s ease-in-out', // Enhanced animations
'slide-up': 'slideUp 0.5s ease-out', 'fade-in': 'fadeIn 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
'bounce-subtle': 'bounceSubtle 3s ease-in-out infinite', 'fade-in-up': 'fadeInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
'fade-in-down': 'fadeInDown 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
'slide-up': 'slideUp 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
'slide-down': 'slideDown 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
'slide-left': 'slideLeft 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
'slide-right': 'slideRight 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
'scale-in': 'scaleIn 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
'scale-in-bounce': 'scaleInBounce 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
'float': 'float 6s ease-in-out infinite',
'float-delayed': 'float 6s ease-in-out infinite 2s',
'pulse-subtle': 'pulseSubtle 4s ease-in-out infinite',
'glow': 'glow 2s ease-in-out infinite alternate',
'shimmer': 'shimmer 2s linear infinite',
'bounce-gentle': 'bounceGentle 3s ease-in-out infinite',
'gradient-x': 'gradientX 15s ease infinite',
'gradient-y': 'gradientY 15s ease infinite',
'gradient-xy': 'gradientXY 20s ease infinite',
}, },
keyframes: { keyframes: {
fadeIn: { fadeIn: {
'0%': { opacity: '0', transform: 'translateY(10px)' }, '0%': { opacity: '0' },
'100%': { opacity: '1' },
},
fadeInUp: {
'0%': { opacity: '0', transform: 'translateY(20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
fadeInDown: {
'0%': { opacity: '0', transform: 'translateY(-20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' }, '100%': { opacity: '1', transform: 'translateY(0)' },
}, },
slideUp: { slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' }, '0%': { transform: 'translateY(30px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' }, '100%': { transform: 'translateY(0)', opacity: '1' },
}, },
bounceSubtle: { slideDown: {
'0%, 100%': { '0%': { transform: 'translateY(-30px)', opacity: '0' },
transform: 'translateY(0)', '100%': { transform: 'translateY(0)', opacity: '1' },
}, },
'50%': { slideLeft: {
transform: 'translateY(-5px)', '0%': { transform: 'translateX(30px)', opacity: '0' },
'100%': { transform: 'translateX(0)', opacity: '1' },
},
slideRight: {
'0%': { transform: 'translateX(-30px)', opacity: '0' },
'100%': { transform: 'translateX(0)', opacity: '1' },
},
scaleIn: {
'0%': { transform: 'scale(0.9)', opacity: '0' },
'100%': { transform: 'scale(1)', opacity: '1' },
},
scaleInBounce: {
'0%': { transform: 'scale(0.3)', opacity: '0' },
'50%': { transform: 'scale(1.05)', opacity: '0.8' },
'70%': { transform: 'scale(0.9)', opacity: '1' },
'100%': { transform: 'scale(1)', opacity: '1' },
},
float: {
'0%, 100%': { transform: 'translateY(0px) rotate(0deg)' },
'33%': { transform: 'translateY(-10px) rotate(1deg)' },
'66%': { transform: 'translateY(-5px) rotate(-1deg)' },
},
pulseSubtle: {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0.8' },
},
glow: {
'0%': { boxShadow: '0 0 20px -5px currentColor' },
'100%': { boxShadow: '0 0 40px -5px currentColor' },
},
shimmer: {
'0%': { backgroundPosition: '-200% 0' },
'100%': { backgroundPosition: '200% 0' },
},
bounceGentle: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-5px)' },
},
gradientX: {
'0%, 100%': { 'background-size': '200% 200%', 'background-position': 'left center' },
'50%': { 'background-size': '200% 200%', 'background-position': 'right center' },
},
gradientY: {
'0%, 100%': { 'background-size': '200% 200%', 'background-position': 'center top' },
'50%': { 'background-size': '200% 200%', 'background-position': 'center bottom' },
},
gradientXY: {
'0%, 100%': { 'background-size': '400% 400%', 'background-position': 'left center' },
'25%': { 'background-size': '400% 400%', 'background-position': 'center top' },
'50%': { 'background-size': '400% 400%', 'background-position': 'right center' },
'75%': { 'background-size': '400% 400%', 'background-position': 'center bottom' },
},
},
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
'gradient-shimmer': 'linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent)',
'noise': 'url("data:image/svg+xml,%3Csvg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="4" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.4"/%3E%3C/svg%3E")',
},
gradientColorStops: {
'glass-white': 'rgba(255, 255, 255, 0.1)',
'glass-black': 'rgba(0, 0, 0, 0.1)',
},
typography: ({ theme }) => ({
DEFAULT: {
css: {
'--tw-prose-body': theme('colors.foreground'),
'--tw-prose-headings': theme('colors.foreground'),
'--tw-prose-lead': theme('colors.muted'),
'--tw-prose-links': theme('colors.primary.DEFAULT'),
'--tw-prose-bold': theme('colors.foreground'),
'--tw-prose-counters': theme('colors.muted'),
'--tw-prose-bullets': theme('colors.subtle'),
'--tw-prose-hr': theme('colors.border'),
'--tw-prose-quotes': theme('colors.foreground'),
'--tw-prose-quote-borders': theme('colors.border'),
'--tw-prose-captions': theme('colors.muted'),
'--tw-prose-kbd': theme('colors.foreground'),
'--tw-prose-kbd-shadows': theme('colors.primary.DEFAULT'),
'--tw-prose-code': theme('colors.accent.DEFAULT'),
'--tw-prose-pre-code': theme('colors.foreground'),
'--tw-prose-pre-bg': theme('colors.surface'),
'--tw-prose-th-borders': theme('colors.border'),
'--tw-prose-td-borders': theme('colors.border-subtle'),
maxWidth: 'none',
fontSize: '1.125rem',
lineHeight: '1.75',
h1: {
fontFamily: theme('fontFamily.display').join(', '),
fontWeight: '700',
letterSpacing: '-0.025em',
},
h2: {
fontFamily: theme('fontFamily.display').join(', '),
fontWeight: '600',
letterSpacing: '-0.025em',
},
h3: {
fontFamily: theme('fontFamily.display').join(', '),
fontWeight: '600',
letterSpacing: '-0.025em',
},
h4: {
fontFamily: theme('fontFamily.display').join(', '),
fontWeight: '600',
},
code: {
backgroundColor: theme('colors.surface'),
padding: '0.25rem 0.5rem',
borderRadius: '0.375rem',
fontWeight: '500',
},
'code::before': {
content: '""',
},
'code::after': {
content: '""',
},
blockquote: {
borderLeftColor: theme('colors.primary.DEFAULT'),
backgroundColor: theme('colors.surface'),
padding: '1rem 1.5rem',
borderRadius: '0.5rem',
},
}, },
}, },
lg: {
css: {
fontSize: '1.25rem',
lineHeight: '1.8',
},
},
xl: {
css: {
fontSize: '1.375rem',
lineHeight: '1.8',
},
},
}),
screens: {
'xs': '475px',
'3xl': '1920px',
},
zIndex: {
'60': '60',
'70': '70',
'80': '80',
'90': '90',
'100': '100',
},
transitionDuration: {
'400': '400ms',
'600': '600ms',
'800': '800ms',
'900': '900ms',
},
transitionTimingFunction: {
'bounce-in': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
'bounce-out': 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
'ease-in-expo': 'cubic-bezier(0.95, 0.05, 0.795, 0.035)',
'ease-out-expo': 'cubic-bezier(0.19, 1, 0.22, 1)',
},
scale: {
'102': '1.02',
'103': '1.03',
'115': '1.15',
'120': '1.2',
},
rotate: {
'1': '1deg',
'2': '2deg',
'3': '3deg',
},
blur: {
'4xl': '72px',
'5xl': '96px',
},
brightness: {
'25': '.25',
'175': '1.75',
},
saturate: {
'25': '.25',
'175': '1.75',
},
aspectRatio: {
'4/3': '4 / 3',
'3/2': '3 / 2',
'2/3': '2 / 3',
'9/16': '9 / 16',
}, },
}, },
}, },
plugins: [typography], plugins: [
} typography,
// Custom plugin for modern utilities
function({ addUtilities, addComponents, theme }) {
// Glass morphism utilities
addUtilities({
'.glass': {
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(20px)',
borderRadius: '16px',
border: '1px solid rgba(255, 255, 255, 0.2)',
boxShadow: '0 8px 32px 0 rgba(31, 38, 135, 0.37)',
},
'.glass-dark': {
background: 'rgba(0, 0, 0, 0.1)',
backdropFilter: 'blur(20px)',
borderRadius: '16px',
border: '1px solid rgba(255, 255, 255, 0.1)',
boxShadow: '0 8px 32px 0 rgba(0, 0, 0, 0.37)',
},
'.text-balance': {
textWrap: 'balance',
},
'.text-pretty': {
textWrap: 'pretty',
},
});
// Modern components
addComponents({
'.btn-glass': {
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(20px)',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: '12px',
padding: '12px 24px',
fontWeight: '500',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
background: 'rgba(255, 255, 255, 0.2)',
transform: 'translateY(-2px)',
boxShadow: '0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
},
'&:active': {
transform: 'translateY(0)',
},
},
'.card-modern': {
background: theme('colors.surface'),
borderRadius: '20px',
padding: '24px',
border: `1px solid ${theme('colors.border')}`,
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
borderColor: theme('colors.primary.DEFAULT'),
},
},
'.input-modern': {
background: `${theme('colors.surface')}`,
border: `2px solid ${theme('colors.border')}`,
borderRadius: '12px',
padding: '12px 16px',
fontSize: '16px',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:focus': {
outline: 'none',
borderColor: theme('colors.primary.DEFAULT'),
boxShadow: `0 0 0 4px ${theme('colors.primary.DEFAULT')}20`,
transform: 'translateY(-1px)',
},
'&::placeholder': {
color: theme('colors.subtle'),
},
},
'.gradient-text': {
background: `linear-gradient(135deg, ${theme('colors.primary.DEFAULT')}, ${theme('colors.accent.DEFAULT')})`,
backgroundClip: 'text',
color: 'transparent',
display: 'inline-block',
},
'.gradient-border': {
position: 'relative',
background: theme('colors.surface'),
borderRadius: '16px',
'&::before': {
content: '""',
position: 'absolute',
inset: '0',
padding: '2px',
background: `linear-gradient(135deg, ${theme('colors.primary.DEFAULT')}, ${theme('colors.accent.DEFAULT')})`,
borderRadius: 'inherit',
mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
maskComposite: 'xor',
},
},
'.section-modern': {
paddingTop: '80px',
paddingBottom: '80px',
'@screen lg': {
paddingTop: '120px',
paddingBottom: '120px',
},
},
'.container-modern': {
marginLeft: 'auto',
marginRight: 'auto',
maxWidth: '1200px',
paddingLeft: '24px',
paddingRight: '24px',
'@screen sm': {
paddingLeft: '32px',
paddingRight: '32px',
},
'@screen lg': {
paddingLeft: '48px',
paddingRight: '48px',
},
},
});
},
],
};

View File

@@ -4,7 +4,8 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"paths": { "paths": {
"@config": ["src/site.config.ts"] "@config": ["src/site.config.ts"],
"@/*": ["src/*"]
} }
} }
} }