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:
2025-11-06 13:28:20 +01:00
parent 89e07308e2
commit 97f9faa8b2
22 changed files with 39 additions and 260 deletions

View File

@@ -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`

View File

@@ -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 && (

View File

@@ -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} />
{ {

View File

@@ -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">
{ {

View File

@@ -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}

View File

@@ -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">

View File

@@ -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,
}, },
]} ]}
> >

View File

@@ -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">

View File

@@ -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',

View File

@@ -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',

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>