Refactor widget container widths for improved layout consistency
- Updated multiple widget components to change the maximum width from `max-w-7xl` to `max-w-6xl`, ensuring a more uniform appearance across the application. - Adjusted layout in various pages to enhance responsiveness and maintain design integrity on different screen sizes.
This commit is contained in:
@@ -1,233 +0,0 @@
|
|||||||
|
|
||||||
# 🛡️ CSP Testing Results - Pre-Deployment Verification
|
|
||||||
|
|
||||||
## Test Date: Thursday, November 6, 2025
|
|
||||||
|
|
||||||
### ✅ CSP Header Verification
|
|
||||||
|
|
||||||
**Status:** CSP is properly configured and active
|
|
||||||
|
|
||||||
**Current CSP Policy:**
|
|
||||||
```
|
|
||||||
Content-Security-Policy:
|
|
||||||
default-src 'self' https://365devnet.eu https://*.365devnet.eu;
|
|
||||||
script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://chat.365devnet.eu;
|
|
||||||
style-src 'self' 'unsafe-inline';
|
|
||||||
img-src 'self' data: https: blob:;
|
|
||||||
font-src 'self' data:;
|
|
||||||
connect-src 'self' https://365devnet.eu https://*.365devnet.eu https://chat.365devnet.eu https://git.365devnet.eu wss://chat.365devnet.eu;
|
|
||||||
frame-src 'self' https://chat.365devnet.eu;
|
|
||||||
frame-ancestors 'none';
|
|
||||||
base-uri 'self';
|
|
||||||
form-action 'self'
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 External Resource Audit
|
|
||||||
|
|
||||||
All external domains have been identified and added to CSP:
|
|
||||||
|
|
||||||
| Resource | Domain | CSP Directive | Status |
|
|
||||||
|----------|--------|---------------|--------|
|
|
||||||
| Latest Commits | `https://git.365devnet.eu` | `connect-src` | ✅ Added |
|
|
||||||
| RocketChat Widget | `https://chat.365devnet.eu` | `script-src`, `connect-src`, `frame-src` | ✅ Included |
|
|
||||||
| RocketChat WebSocket | `wss://chat.365devnet.eu` | `connect-src` | ✅ Included |
|
|
||||||
| Images (any HTTPS) | `https:` | `img-src` | ✅ Wildcard |
|
|
||||||
| Internal APIs | `https://*.365devnet.eu` | `connect-src` | ✅ Wildcard |
|
|
||||||
| Uptime Kuma | Server-side only | N/A | ✅ No CSP needed |
|
|
||||||
|
|
||||||
### 📊 Client-Side vs Server-Side Calls
|
|
||||||
|
|
||||||
**Client-side calls (affected by CSP):**
|
|
||||||
- `/api/commits` → Internal (OK)
|
|
||||||
- `/api/contact` → Internal (OK)
|
|
||||||
- `/api/uptime` → Internal (OK)
|
|
||||||
- RocketChat widget → `chat.365devnet.eu` (Added to CSP)
|
|
||||||
|
|
||||||
**Server-side calls (NOT affected by CSP):**
|
|
||||||
- API routes fetch from `git.365devnet.eu` ✅
|
|
||||||
- API routes fetch from `UPTIME_KUMA_URL` ✅
|
|
||||||
|
|
||||||
### 🧪 Testing Performed
|
|
||||||
|
|
||||||
#### 1. Build Test
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
**Result:** ✅ Build successful (astro-compress warning is harmless)
|
|
||||||
|
|
||||||
#### 2. CSP Enabled Server Test
|
|
||||||
```bash
|
|
||||||
ENABLE_SSR_CSP=1 npm run start
|
|
||||||
```
|
|
||||||
**Result:** ✅ Server started successfully on port 3000
|
|
||||||
|
|
||||||
#### 3. CSP Header Present
|
|
||||||
```bash
|
|
||||||
curl -I http://localhost:3000/en/
|
|
||||||
```
|
|
||||||
**Result:** ✅ CSP header is present and correctly formatted
|
|
||||||
|
|
||||||
### 🎯 Key Fixes Applied
|
|
||||||
|
|
||||||
#### Fix #1: Added `git.365devnet.eu` to `connect-src`
|
|
||||||
**Problem:** Latest Commits widget was calling Gitea API but domain wasn't in CSP
|
|
||||||
**Solution:** Added `https://git.365devnet.eu` to `connect-src`
|
|
||||||
|
|
||||||
#### Fix #2: Made `img-src` more permissive
|
|
||||||
**Problem:** Too restrictive, only allowed specific domains
|
|
||||||
**Solution:** Changed to `https:` to allow all HTTPS images
|
|
||||||
|
|
||||||
#### Fix #3: Added wildcard subdomain support
|
|
||||||
**Problem:** Only specific subdomains were allowed
|
|
||||||
**Solution:** Added `https://*.365devnet.eu` to `connect-src`
|
|
||||||
|
|
||||||
#### Fix #4: Kept `'unsafe-inline'` for scripts
|
|
||||||
**Problem:** Astro's inline scripts require this directive
|
|
||||||
**Solution:** Included `'unsafe-inline'` in `script-src`
|
|
||||||
|
|
||||||
### ⚠️ Manual Testing Required
|
|
||||||
|
|
||||||
Before deploying to production, **manually test these features** with CSP enabled:
|
|
||||||
|
|
||||||
**Test URL:** http://localhost:3000/en/ (with `npm run dev:prod` or `ENABLE_SSR_CSP=1 npm run start`)
|
|
||||||
|
|
||||||
#### Features to Test:
|
|
||||||
|
|
||||||
- [ ] **Mobile Menu Toggle**
|
|
||||||
- Open site on mobile (or use browser DevTools responsive mode)
|
|
||||||
- Click hamburger menu icon
|
|
||||||
- Menu should expand/collapse
|
|
||||||
- Check console for CSP violations
|
|
||||||
|
|
||||||
- [ ] **Language Selector**
|
|
||||||
- Desktop: Click language dropdown
|
|
||||||
- Select different language
|
|
||||||
- Page should reload with new language
|
|
||||||
- Mobile: Use select dropdown
|
|
||||||
|
|
||||||
- [ ] **Theme Switcher**
|
|
||||||
- Click sun/moon icon
|
|
||||||
- Theme should toggle between light/dark
|
|
||||||
- Preference should persist
|
|
||||||
|
|
||||||
- [ ] **Contact Form**
|
|
||||||
- Fill out form
|
|
||||||
- Submit
|
|
||||||
- Check for successful submission
|
|
||||||
- Check console for CSP violations
|
|
||||||
|
|
||||||
- [ ] **Latest Commits Widget** ← Critical test!
|
|
||||||
- Navigate to `/en/development/`
|
|
||||||
- Widget should load commit history
|
|
||||||
- Check browser console for errors
|
|
||||||
- Verify commits are displayed
|
|
||||||
|
|
||||||
- [ ] **RocketChat Widget**
|
|
||||||
- Widget should appear in bottom right
|
|
||||||
- Should be clickable and functional
|
|
||||||
- Check console for CSP violations
|
|
||||||
|
|
||||||
- [ ] **Smooth Scrolling**
|
|
||||||
- Click any anchor link (e.g., "Services" in nav)
|
|
||||||
- Page should scroll smoothly
|
|
||||||
- No console errors
|
|
||||||
|
|
||||||
- [ ] **Images Loading**
|
|
||||||
- All images should load correctly
|
|
||||||
- Check different pages
|
|
||||||
- No broken images
|
|
||||||
|
|
||||||
### 🔧 Debugging Tips
|
|
||||||
|
|
||||||
#### If Something Doesn't Work:
|
|
||||||
|
|
||||||
1. **Open Browser DevTools** (F12)
|
|
||||||
2. **Go to Console tab**
|
|
||||||
3. **Look for red CSP violation errors:**
|
|
||||||
```
|
|
||||||
Refused to load ... because it violates CSP directive...
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Identify the blocked resource:**
|
|
||||||
- What domain?
|
|
||||||
- What type? (script, style, image, fetch, etc.)
|
|
||||||
|
|
||||||
5. **Add to appropriate CSP directive:**
|
|
||||||
- Scripts → `script-src`
|
|
||||||
- API calls → `connect-src`
|
|
||||||
- Images → `img-src`
|
|
||||||
- Styles → `style-src`
|
|
||||||
|
|
||||||
6. **Rebuild and test again**
|
|
||||||
|
|
||||||
### 📝 Deployment Checklist
|
|
||||||
|
|
||||||
Before enabling CSP in production:
|
|
||||||
|
|
||||||
- [ ] Run `npm run dev:prod` locally
|
|
||||||
- [ ] Complete all manual tests above
|
|
||||||
- [ ] Check browser console (no CSP violations)
|
|
||||||
- [ ] Test on Chrome, Firefox, and Safari
|
|
||||||
- [ ] Test on actual mobile device
|
|
||||||
- [ ] Verify Latest Commits widget works
|
|
||||||
- [ ] Verify RocketChat widget works
|
|
||||||
- [ ] Document any new CSP changes
|
|
||||||
|
|
||||||
### 🚀 Deployment Instructions
|
|
||||||
|
|
||||||
#### Option 1: Full Test (Recommended)
|
|
||||||
```bash
|
|
||||||
# Build and test locally with CSP
|
|
||||||
npm run dev:prod
|
|
||||||
|
|
||||||
# Manual testing...
|
|
||||||
|
|
||||||
# If all tests pass, rebuild Docker
|
|
||||||
docker-compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Option 2: Direct Deploy (Use with caution)
|
|
||||||
```bash
|
|
||||||
# Make sure ENABLE_SSR_CSP=1 is in docker-compose.yml
|
|
||||||
docker-compose up -d --build
|
|
||||||
|
|
||||||
# Monitor logs
|
|
||||||
docker-compose logs -f web
|
|
||||||
|
|
||||||
# Check production site immediately
|
|
||||||
# Open browser DevTools and check console
|
|
||||||
```
|
|
||||||
|
|
||||||
### 📊 Confidence Level
|
|
||||||
|
|
||||||
**Current Status:**
|
|
||||||
- ✅ CSP policy created and verified
|
|
||||||
- ✅ All external domains identified
|
|
||||||
- ✅ Server runs with CSP enabled
|
|
||||||
- ✅ CSP header present in responses
|
|
||||||
- ⚠️ Manual browser testing required
|
|
||||||
|
|
||||||
**Confidence for Production:** 85%
|
|
||||||
|
|
||||||
**Why not 100%?** Manual browser testing of interactive features (mobile menu, language selector, Latest Commits widget) has not been performed with CSP enabled. This requires opening a browser and testing each feature while monitoring the console for CSP violations.
|
|
||||||
|
|
||||||
### 🎯 Recommendation
|
|
||||||
|
|
||||||
**Before deploying to production:**
|
|
||||||
|
|
||||||
1. Run `npm run dev:prod`
|
|
||||||
2. Open http://localhost:3000/en/ in your browser
|
|
||||||
3. Open DevTools Console (F12)
|
|
||||||
4. Test each interactive feature
|
|
||||||
5. Verify NO CSP violation errors appear
|
|
||||||
6. **Especially test Latest Commits widget** - this was the main issue!
|
|
||||||
|
|
||||||
If all tests pass → **Deploy with confidence!** ✅
|
|
||||||
If CSP violations appear → **Document them and add domains to CSP** 🔧
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Test performed by:** AI Assistant
|
|
||||||
**Manual verification required by:** User
|
|
||||||
**Next step:** Manual browser testing with `npm run dev:prod`
|
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ const speedClass = {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<section class="relative py-12 md:py-16 overflow-hidden not-prose">
|
<section class="relative py-12 md:py-16 overflow-hidden not-prose">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
||||||
<!-- Section Header -->
|
<!-- Section Header -->
|
||||||
<div class="text-center mb-8 md:mb-10 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade">
|
<div class="text-center mb-8 md:mb-10 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade">
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const {
|
|||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-7xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-6xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
||||||
<Headline title={title} subtitle={subtitle} tagline={tagline} />
|
<Headline title={title} subtitle={subtitle} tagline={tagline} />
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const {
|
|||||||
<WidgetWrapper
|
<WidgetWrapper
|
||||||
id={id}
|
id={id}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
containerClass={`max-w-7xl mx-auto ${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container ?? ''}`}
|
containerClass={`max-w-6xl mx-auto ${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container ?? ''}`}
|
||||||
bg={bg}
|
bg={bg}
|
||||||
>
|
>
|
||||||
<Headline
|
<Headline
|
||||||
@@ -41,7 +41,7 @@ const {
|
|||||||
subtitle: 'max-w-3xl mx-auto sm:text-center text-base md:text-lg lg:text-xl text-muted dark:text-slate-400',
|
subtitle: 'max-w-3xl mx-auto sm:text-center text-base md:text-lg lg:text-xl text-muted dark:text-slate-400',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="mx-auto max-w-7xl p-4 md:px-8">
|
<div class="mx-auto max-w-6xl p-4 md:px-8">
|
||||||
<div class={`md:flex ${isReversed ? 'md:flex-row-reverse' : ''} md:gap-16`}>
|
<div class={`md:flex ${isReversed ? 'md:flex-row-reverse' : ''} md:gap-16`}>
|
||||||
<div class="md:basis-1/2 self-center">
|
<div class="md:basis-1/2 self-center">
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const {
|
|||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-5xl ${classes?.container ?? ''}`} bg={bg}>
|
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-6xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
||||||
<Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
|
<Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
|
||||||
<ItemGrid
|
<ItemGrid
|
||||||
items={items}
|
items={items}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const {
|
|||||||
<footer class:list={[{ dark: theme === 'dark' }, 'relative border-t border-gray-200 dark:border-slate-800 not-prose']}>
|
<footer class:list={[{ dark: theme === 'dark' }, 'relative border-t border-gray-200 dark:border-slate-800 not-prose']}>
|
||||||
<div class="backdrop-blur-sm absolute inset-0 pointer-events-none" aria-hidden="true"></div>
|
<div class="backdrop-blur-sm absolute inset-0 pointer-events-none" aria-hidden="true"></div>
|
||||||
<div
|
<div
|
||||||
class="relative max-w-7xl mx-auto px-4 sm:px-6 pr-12 md:pr-12 dark:text-slate-300 intersect-once intersect-quarter intercept-no-queue motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
|
class="relative max-w-6xl mx-auto px-4 sm:px-6 pr-12 md:pr-12 dark:text-slate-300 intersect-once intersect-quarter intercept-no-queue motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
|
||||||
>
|
>
|
||||||
<!-- ✅ Combined Footer Section -->
|
<!-- ✅ Combined Footer Section -->
|
||||||
<div class="flex flex-col md:flex-row md:justify-between py-6 md:py-8 space-y-6 md:space-y-0">
|
<div class="flex flex-col md:flex-row md:justify-between py-6 md:py-8 space-y-6 md:space-y-0">
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const {
|
|||||||
'md:grid md:grid-cols-3 md:items-center': position === 'center',
|
'md:grid md:grid-cols-3 md:items-center': position === 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'max-w-7xl': !isFullWidth,
|
'max-w-6xl': !isFullWidth,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const {
|
|||||||
|
|
||||||
<section class="relative md:-mt-[76px] not-prose">
|
<section class="relative md:-mt-[76px] not-prose">
|
||||||
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"></div>
|
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"></div>
|
||||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6">
|
<div class="relative max-w-6xl mx-auto px-4 sm:px-6">
|
||||||
<div class="pt-0 md:pt-[76px] pointer-events-none"></div>
|
<div class="pt-0 md:pt-[76px] pointer-events-none"></div>
|
||||||
<div class="py-12 md:py-20 pb-8 md:pb-8">
|
<div class="py-12 md:py-20 pb-8 md:pb-8">
|
||||||
<div class="text-center max-w-5xl mx-auto">
|
<div class="text-center max-w-5xl mx-auto">
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const getSkillGradient = (name: string): string => {
|
|||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-7xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-6xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
||||||
<Headline title={title} subtitle={subtitle} tagline={tagline} classes={{
|
<Headline title={title} subtitle={subtitle} tagline={tagline} classes={{
|
||||||
container: 'max-w-3xl',
|
container: 'max-w-3xl',
|
||||||
title: 'text-3xl lg:text-4xl',
|
title: 'text-3xl lg:text-4xl',
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const getImageSrc = (imagePath: string) => {
|
|||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-7xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-6xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
||||||
<Headline title={title} subtitle={subtitle} tagline={tagline} classes={{
|
<Headline title={title} subtitle={subtitle} tagline={tagline} classes={{
|
||||||
container: 'max-w-3xl',
|
container: 'max-w-3xl',
|
||||||
title: 'text-3xl lg:text-4xl',
|
title: 'text-3xl lg:text-4xl',
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const getEducationStyle = (title: string) => {
|
|||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-7xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-6xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
||||||
<Headline
|
<Headline
|
||||||
title={title}
|
title={title}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ if (uncategorizedSkills.length > 0) {
|
|||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-7xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-6xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
||||||
<Headline
|
<Headline
|
||||||
title={title}
|
title={title}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const getYearRange = (dateStr: string = '') => {
|
|||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-7xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-6xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
|
||||||
<Headline
|
<Headline
|
||||||
title={title}
|
title={title}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const metadata = {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-red-50/80 to-orange-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-red-50/80 to-orange-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
||||||
<div class="text-6xl mb-4">🛡️</div>
|
<div class="text-6xl mb-4">🛡️</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const averagePerDay = contributionDays > 0 ? (totalContributions / contributionD
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-green-50/80 to-blue-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-green-50/80 to-blue-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
||||||
<div class="text-6xl mb-4">👨💻</div>
|
<div class="text-6xl mb-4">👨💻</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const metadata = {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-green-50/80 to-blue-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-green-50/80 to-blue-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
||||||
<div class="text-6xl mb-4">🛡️</div>
|
<div class="text-6xl mb-4">🛡️</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const metadata = {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-red-50/80 to-orange-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-red-50/80 to-orange-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
||||||
<div class="text-6xl mb-4">🎬</div>
|
<div class="text-6xl mb-4">🎬</div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const metadata = {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-blue-50/80 to-purple-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-blue-50/80 to-purple-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
||||||
<div class="text-6xl mb-4">🚗</div>
|
<div class="text-6xl mb-4">🚗</div>
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ function parseAndFormatTime(rawTime) {
|
|||||||
'@type': 'Article',
|
'@type': 'Article',
|
||||||
headline: 'Privacy Policy',
|
headline: 'Privacy Policy',
|
||||||
datePublished: '2025-03-06',
|
datePublished: '2025-03-06',
|
||||||
dateModified: '2025-06-07',
|
dateModified: '2025-11-06',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-blue-50/80 to-green-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-blue-50/80 to-green-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
||||||
<div class="text-6xl mb-4">🔐</div>
|
<div class="text-6xl mb-4">🔐</div>
|
||||||
@@ -80,7 +80,7 @@ function parseAndFormatTime(rawTime) {
|
|||||||
Transparent data practices and your privacy rights
|
Transparent data practices and your privacy rights
|
||||||
</p>
|
</p>
|
||||||
<div class="inline-flex items-center px-4 py-2 bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200 rounded-full text-sm font-medium">
|
<div class="inline-flex items-center px-4 py-2 bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200 rounded-full text-sm font-medium">
|
||||||
📅 Last updated: 07 June 2025
|
📅 Last updated: 06 November 2025
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -374,9 +374,21 @@ function parseAndFormatTime(rawTime) {
|
|||||||
to us. However, please be aware that no method of transmission over the internet or method of electronic storage
|
to us. However, please be aware that no method of transmission over the internet or method of electronic storage
|
||||||
is 100% secure.
|
is 100% secure.
|
||||||
</p>
|
</p>
|
||||||
<div class="bg-green-50/50 dark:bg-green-900/20 rounded-lg p-6 border-l-4 border-green-500 mt-4">
|
<div class="grid md:grid-cols-2 gap-4 mt-4">
|
||||||
|
<div class="bg-green-50/50 dark:bg-green-900/20 rounded-lg p-6 border-l-4 border-green-500">
|
||||||
<p class="text-green-800 dark:text-green-200 mb-0">
|
<p class="text-green-800 dark:text-green-200 mb-0">
|
||||||
<strong>HTTPS Encryption:</strong> Our website uses HTTPS encryption to ensure that any communication between your browser and our website is secure.
|
<strong>🔒 HTTPS Encryption:</strong> All communication between your browser and our website is encrypted using industry-standard HTTPS protocol.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-blue-50/50 dark:bg-blue-900/20 rounded-lg p-6 border-l-4 border-blue-500">
|
||||||
|
<p class="text-blue-800 dark:text-blue-200 mb-0">
|
||||||
|
<strong>🛡️ Content Security Policy:</strong> We implement Content Security Policy (CSP) headers to prevent cross-site scripting (XSS) and other code injection attacks.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-purple-50/50 dark:bg-purple-900/20 rounded-lg p-6 border-l-4 border-purple-500 mt-4">
|
||||||
|
<p class="text-purple-800 dark:text-purple-200 mb-0">
|
||||||
|
<strong>Security Headers:</strong> Our website employs multiple security headers including X-Content-Type-Options, Referrer-Policy, and Cross-Origin policies to protect against common web vulnerabilities.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ const metadata = {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-green-50/80 to-blue-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-green-50/80 to-blue-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
||||||
<div class="text-6xl mb-4">💪</div>
|
<div class="text-6xl mb-4">💪</div>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const tocItems = [
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-purple-50/80 to-blue-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
<div class="text-center mb-12 backdrop-blur-sm bg-gradient-to-br from-purple-50/80 to-blue-50/80 dark:from-slate-800/80 dark:to-slate-900/80 rounded-2xl p-8 shadow-lg">
|
||||||
<div class="text-6xl mb-4">📋</div>
|
<div class="text-6xl mb-4">📋</div>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const metadata = {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<main class="relative max-w-7xl mx-auto px-4 sm:px-6 py-12">
|
<main class="relative max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="text-center mb-5">
|
<div class="text-center mb-5">
|
||||||
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-4">{t.uptime.title}</h1>
|
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-4">{t.uptime.title}</h1>
|
||||||
|
|||||||
Reference in New Issue
Block a user