Compare commits

..

46 Commits

Author SHA1 Message Date
eaf40dec7c chore: release 2.2.72
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 28s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m17s
Build & Deploy / 🧪 QA (push) Successful in 1m52s
Build & Deploy / 🏗️ Build (push) Successful in 3m34s
Build & Deploy / 🚀 Deploy (push) Successful in 34s
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-06-26 19:22:17 +02:00
d56ce045c3 fix(perf): replace framer-motion with pure css animations in HeroSection to eliminate JS-induced 4.5s LCP delay 2026-06-26 19:22:17 +02:00
586b6cdee8 chore: release 2.2.71
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 31s
Build & Deploy / 🧪 QA (push) Successful in 1m50s
Build & Deploy / 🏗️ Build (push) Successful in 3m40s
Build & Deploy / 🚀 Deploy (push) Successful in 35s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m17s
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-06-26 13:00:33 +02:00
f1b8ee1188 fix(perf): completely disable Sentry client-side auto-instrumentation to save 556KB of JS
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 1m18s
Build & Deploy / 🧪 QA (push) Successful in 1m46s
Build & Deploy / 🏗️ Build (push) Successful in 3m41s
Build & Deploy / 🚀 Deploy (push) Successful in 35s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-26 12:24:07 +02:00
88cc214ddb fix(ci): use correct auth cookie name for e-tib gatekeeper bypass
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 1m17s
Build & Deploy / 🧪 QA (push) Successful in 1m47s
Build & Deploy / 🏗️ Build (push) Successful in 3m29s
Build & Deploy / 🚀 Deploy (push) Successful in 35s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-26 11:21:24 +02:00
25facb995f chore: bump version for fix
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m12s
Build & Deploy / 🧪 QA (push) Successful in 1m44s
Build & Deploy / 🏗️ Build (push) Successful in 3m32s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-26 10:22:03 +02:00
7b01924055 fix: revert optimizeCss to fix LCP/FCP regression caused by critters with Tailwind v4 2026-06-26 10:21:57 +02:00
474672b2a1 chore: release 2.2.69 (PageSpeed optimizations)
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🚀 Deploy (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m46s
Build & Deploy / 🏗️ Build (push) Successful in 3m38s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m19s
Build & Deploy / 🔔 Notify (push) Successful in 5s
2026-06-26 09:53:12 +02:00
7e552bbea5 chore: release 2.2.68
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🧪 QA (push) Successful in 1m43s
Build & Deploy / 🏗️ Build (push) Successful in 3m26s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m11s
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-06-25 22:40:40 +02:00
21886a693c fix: resolve yaml syntax error in deploy.yml and invalid next.config 2026-06-25 22:40:38 +02:00
45944cbfa7 chore: release 2.2.67
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🏗️ Build (push) Successful in 3m24s
Build & Deploy / 🧪 QA (push) Successful in 1m42s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m12s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-25 22:32:19 +02:00
761c9c956f chore: release 2.2.65 2026-06-25 22:32:14 +02:00
e18559b83c fix: enforce deployment priorities and completely strip client sentry bundle 2026-06-25 22:32:14 +02:00
818ccc8fae chore: release 2.2.64
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 29s
Build & Deploy / 🧪 QA (push) Successful in 1m39s
Build & Deploy / 🏗️ Build (push) Successful in 3m17s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m6s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-25 22:08:40 +02:00
b64b2ccf25 fix: pipeline execution, deployment priority, and bundle optimizations
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 1m33s
Build & Deploy / 🧪 QA (push) Successful in 1m40s
Build & Deploy / 🏗️ Build (push) Successful in 3m19s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-25 22:08:26 +02:00
e219b3b818 fix: apply GIT_HTTP_VERSION workaround globally for all jobs
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🧪 QA (push) Successful in 1m37s
Build & Deploy / 🏗️ Build (push) Successful in 3m10s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m6s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-25 18:25:28 +02:00
88628ee663 fix: remove git trace variables
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 26s
Build & Deploy / 🧪 QA (push) Successful in 1m37s
Build & Deploy / 🏗️ Build (push) Successful in 3m7s
Build & Deploy / 🚀 Deploy (push) Successful in 31s
Build & Deploy / 🔔 Notify (push) Successful in 4s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m6s
2026-06-25 17:31:17 +02:00
734dd19725 2.2.63
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 28s
Build & Deploy / 🧪 QA (push) Successful in 1m35s
Build & Deploy / 🏗️ Build (push) Successful in 2m47s
Build & Deploy / 🚀 Deploy (push) Successful in 32s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m6s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-25 17:24:19 +02:00
3175b99177 chore: force http1.1 for git checkout 2026-06-25 17:24:19 +02:00
1986280520 2.2.62
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 28s
Build & Deploy / 🧪 QA (push) Successful in 1m42s
Build & Deploy / 🏗️ Build (push) Successful in 2m48s
Build & Deploy / 🚀 Deploy (push) Successful in 36s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m5s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-25 17:23:09 +02:00
d4437f5274 chore: debug checkout action 2026-06-25 17:23:09 +02:00
f09c338d0d 2.2.61
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Failing after 13m8s
2026-06-25 17:19:59 +02:00
91d6073a58 2.2.60
Some checks failed
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Failing after 13m49s
Build & Deploy / 🧪 QA (push) Has been cancelled
2026-06-25 17:16:36 +02:00
c5f2642d05 fix(ci): prevent hostedtoolcache purge to fix checkout action failure 2026-06-25 17:16:36 +02:00
c2bfdc7e7c 2.2.59
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 28s
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Failing after 14m39s
2026-06-25 17:13:36 +02:00
5625428113 fix(ci): remove aggressive docker prune steps causing deadlock in runner 2026-06-25 17:13:36 +02:00
8f9a37466c 2.2.58
Some checks failed
Build & Deploy / 🔍 Prepare (push) Failing after 0s
Build & Deploy / 🧪 QA (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Failing after 3s
2026-06-25 17:09:39 +02:00
803ba997df 2.2.57
Some checks failed
Build & Deploy / 🔍 Prepare (push) Failing after 2m12s
Build & Deploy / 🧪 QA (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-25 17:07:15 +02:00
4985ae0315 chore: trigger pipeline to check for transient failure
Some checks failed
Build & Deploy / 🔍 Prepare (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-06-25 17:06:28 +02:00
5c93a51db9 chore: bump version to 2.2.56
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Failing after 20m22s
2026-06-25 15:04:00 +02:00
70a312052c fix: resolve ssr:false build error for dynamic imports in Server Components 2026-06-25 15:03:51 +02:00
0ccc408a8d chore: bump version to 2.2.55
Some checks failed
Build & Deploy / 🔍 Prepare (push) Failing after 3h10m20s
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-06-25 13:13:21 +02:00
f9f0970f3f perf: resolve Lighthouse issues, optimize CSS and JS payload 2026-06-25 13:13:15 +02:00
f463e854fd perf(images): set fetchPriority high and aggressive quality 25 for LCP poster
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Has been cancelled
2026-06-24 21:08:47 +02:00
81dd2c95ed perf(images): aggressively compress reference slider images for lighthouse
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 33s
Build & Deploy / 🧪 QA (push) Successful in 1m36s
Build & Deploy / 🏗️ Build (push) Successful in 3m5s
Build & Deploy / 🚀 Deploy (push) Successful in 31s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m9s
2026-06-24 18:31:33 +02:00
ecebe63084 perf(build): completely strip polyfills via postinstall script and aggressively compress LCP poster image
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 30s
Build & Deploy / 🧪 QA (push) Successful in 1m35s
Build & Deploy / 🏗️ Build (push) Successful in 2m58s
Build & Deploy / 🚀 Deploy (push) Successful in 31s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m6s
2026-06-24 15:57:14 +02:00
ba4d21195c perf(build): brutally remove next.js internal legacy polyfills via webpack alias
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 32s
Build & Deploy / 🧪 QA (push) Successful in 1m35s
Build & Deploy / 🏗️ Build (push) Successful in 2m57s
Build & Deploy / 🚀 Deploy (push) Successful in 42s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m4s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-24 14:45:06 +02:00
c74c786988 fix(ui): regenerate video posters at 2 second mark to prevent black initial frames
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 32s
Build & Deploy / 🧪 QA (push) Successful in 1m34s
Build & Deploy / 🏗️ Build (push) Successful in 2m59s
Build & Deploy / 🚀 Deploy (push) Successful in 34s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m3s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-24 14:38:46 +02:00
4fd7160967 perf: remove legacy JS polyfills by targeting modern browserslist and fix LCP penalty by rendering hero background immediately visible instead of opacity 0
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m34s
Build & Deploy / 🏗️ Build (push) Successful in 2m58s
Build & Deploy / 🚀 Deploy (push) Successful in 31s
Build & Deploy / 🔔 Notify (push) Successful in 4s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m5s
2026-06-24 13:49:03 +02:00
04a2bb0ebc perf: fix massive unused JS payload by replacing synchronous framer-motion imports with LazyMotion (m) in template/error boundaries, revert HeroSection lazy load to save LCP
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 32s
Build & Deploy / 🧪 QA (push) Successful in 1m36s
Build & Deploy / 🏗️ Build (push) Successful in 3m7s
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🔔 Notify (push) Successful in 3s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m3s
2026-06-24 12:15:59 +02:00
a52c35a224 fix(seo): remove process.env.CI override for baseUrl to prevent etib.localhost leaking into prod canonicals
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 30s
Build & Deploy / 🧪 QA (push) Successful in 1m32s
Build & Deploy / 🏗️ Build (push) Successful in 2m57s
Build & Deploy / 🚀 Deploy (push) Successful in 35s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m4s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-24 12:13:28 +02:00
343b484c6e chore(release): v2.2.46
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Has been cancelled
2026-06-24 10:48:56 +02:00
a6a8dbd134 perf: fix canonical origin mismatch and reduce unused js
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m23s
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
2026-06-24 10:46:29 +02:00
8f2509c3fc chore(release): v2.2.45
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 32s
Build & Deploy / 🧪 QA (push) Successful in 1m34s
Build & Deploy / 🏗️ Build (push) Successful in 3m6s
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m2s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-23 21:24:23 +02:00
41a3c10cf8 fix(ui): center carousel cards with peek on both sides
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-06-23 21:00:18 +02:00
103e4e63ed fix(ui): center references slider title and description
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 31s
Build & Deploy / 🧪 QA (push) Successful in 1m32s
Build & Deploy / 🏗️ Build (push) Successful in 3m0s
Build & Deploy / 🚀 Deploy (push) Successful in 34s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m2s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-23 18:54:47 +02:00
63 changed files with 6926 additions and 40167 deletions

View File

@@ -20,6 +20,7 @@ concurrency:
env: env:
PUPPETEER_SKIP_DOWNLOAD: "true" PUPPETEER_SKIP_DOWNLOAD: "true"
COREPACK_NPM_REGISTRY: "https://registry.npmmirror.com" COREPACK_NPM_REGISTRY: "https://registry.npmmirror.com"
GIT_HTTP_VERSION: HTTP/1.1
jobs: jobs:
@@ -49,9 +50,7 @@ jobs:
echo "=== Tool Cache Usage ===" echo "=== Tool Cache Usage ==="
du -sh /opt/hostedtoolcache/* || true du -sh /opt/hostedtoolcache/* || true
du -sh /opt/hostedtoolcache/.[!.]* || true du -sh /opt/hostedtoolcache/.[!.]* || true
echo "Purging old tool caches..." echo "Purging old tool caches skipped to prevent runner corruption."
rm -rf /opt/hostedtoolcache/* || true
rm -rf /opt/hostedtoolcache/.[!.]* || true
echo "=== Host Disk Usage ===" echo "=== Host Disk Usage ==="
docker run --rm -v /:/host-root alpine df -h || true docker run --rm -v /:/host-root alpine df -h || true
echo "=== Host Inode Usage ===" echo "=== Host Inode Usage ==="
@@ -60,23 +59,7 @@ jobs:
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || true docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || true
echo "=== Host Docker Info ===" echo "=== Host Docker Info ==="
docker info || true docker info || true
echo "Pruning host Buildkit builder caches aggressively..." echo "=== System Disk Usage After Check ==="
for container in $(docker ps --filter "name=buildx_buildkit_builder" --format "{{.Names}}"); do
echo "Pruning Buildkit inside container: $container"
docker exec -i "$container" buildctl prune --all || true
done
echo "Removing buildkit builder containers to clear overlay storage..."
containers=$(docker ps -a --filter "name=buildx_buildkit_builder" -q)
if [ -n "$containers" ]; then
docker rm -f $containers || true
fi
echo "=== Truncating host Docker container log files ==="
docker run --rm -v /:/host-root alpine sh -c "truncate -s 0 /host-root/mnt/HC_Volume_104796416/docker/containers/*/*.log" || true
echo "Purging old build layers, unused volumes and images..."
docker builder prune -a -f || true
docker volume prune -f || true
docker system prune -a -f --volumes || true
echo "=== System Disk Usage After Prune ==="
df -h || true df -h || true
- name: Checkout repository - name: Checkout repository
@@ -112,6 +95,15 @@ jobs:
fi fi
else else
TARGET="branch" TARGET="branch"
# Check if this exact commit has a tag. If so, skip the branch deploy
# because the tag pipeline will handle the production/staging deploy.
git fetch --tags --quiet || true
if git describe --tags --exact-match HEAD >/dev/null 2>&1; then
echo "Commit has a tag. Skipping branch deployment to favor tag deployment."
TARGET="skip"
fi
SLUG=$(echo "$REF" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//') SLUG=$(echo "$REF" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
IMAGE_TAG="branch-${SLUG}-${SHORT_SHA}" IMAGE_TAG="branch-${SLUG}-${SHORT_SHA}"
ENV_FILE=".env.branch-${SLUG}" ENV_FILE=".env.branch-${SLUG}"
@@ -218,6 +210,13 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: 🚦 Priority Gate Check
env:
GITEA_PAT: ${{ secrets.GITEA_PAT }}
CURRENT_RUN_ID: ${{ github.run_id }}
CURRENT_TARGET: ${{ needs.prepare.outputs.target }}
run: bash scripts/priority-gate.sh
- name: 🔐 Registry Auth - name: 🔐 Registry Auth
id: auth id: auth
env: env:
@@ -297,6 +296,13 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: 🚦 Priority Gate Check
env:
GITEA_PAT: ${{ secrets.GITEA_PAT }}
CURRENT_RUN_ID: ${{ github.run_id }}
CURRENT_TARGET: ${{ needs.prepare.outputs.target }}
run: bash scripts/priority-gate.sh
- name: 📝 Generate Environment - name: 📝 Generate Environment
shell: bash shell: bash
env: env:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
.lighthouseci/links.json Normal file
View File

@@ -0,0 +1,3 @@
{
"http://localhost:3000/de": "https://storage.googleapis.com/lighthouse-infrastructure.appspot.com/reports/1782417688223-28941.report.html"
}

View File

@@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl';
import { getAppServices } from '@/lib/services/create-services'; import { getAppServices } from '@/lib/services/create-services';
import { Button, Heading } from '@/components/ui'; import { Button, Heading } from '@/components/ui';
import { Terminal, Activity, AlertTriangle, RefreshCw, Home, ShieldAlert } from 'lucide-react'; import { Terminal, Activity, AlertTriangle, RefreshCw, Home, ShieldAlert } from 'lucide-react';
import { motion } from 'framer-motion'; import { m } from 'framer-motion';
export default function Error({ export default function Error({
error, error,
@@ -65,7 +65,7 @@ export default function Error({
<div className="relative z-10 w-full max-w-4xl mx-auto flex flex-col items-center"> <div className="relative z-10 w-full max-w-4xl mx-auto flex flex-col items-center">
{/* The "Broken" Machine Visual */} {/* The "Broken" Machine Visual */}
<div className="relative mb-12"> <div className="relative mb-12">
<motion.div <m.div
initial={{ opacity: 0, scale: 0.8 }} initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
className="relative" className="relative"
@@ -81,7 +81,7 @@ export default function Error({
{/* Warning Overlay */} {/* Warning Overlay */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center"> <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center">
<motion.div <m.div
animate={{ animate={{
opacity: [0.4, 1, 0.4], opacity: [0.4, 1, 0.4],
scale: [0.95, 1.05, 0.95] scale: [0.95, 1.05, 0.95]
@@ -90,9 +90,9 @@ export default function Error({
className="p-4 rounded-2xl bg-red-500/20 border border-red-500/40 backdrop-blur-md shadow-[0_0_30px_rgba(239,68,68,0.3)]" className="p-4 rounded-2xl bg-red-500/20 border border-red-500/40 backdrop-blur-md shadow-[0_0_30px_rgba(239,68,68,0.3)]"
> >
<ShieldAlert className="w-12 h-12 text-red-500" /> <ShieldAlert className="w-12 h-12 text-red-500" />
</motion.div> </m.div>
</div> </div>
</motion.div> </m.div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-12 gap-8 w-full items-start"> <div className="grid grid-cols-1 md:grid-cols-12 gap-8 w-full items-start">

View File

@@ -17,9 +17,10 @@ import { mapFileSlugToTranslated } from '@/lib/slugs';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import { TransitionProvider } from '@/components/providers/TransitionProvider'; import { TransitionProvider } from '@/components/providers/TransitionProvider';
import { PageTransitionShutter } from '@/components/providers/PageTransitionShutter';
import { InitialLoader } from '@/components/providers/InitialLoader'; import { InitialLoader } from '@/components/providers/InitialLoader';
import { CorporateBackground } from '@/components/decorations/CorporateBackground'; import { DynamicCorporateBackground as CorporateBackground } from '@/components/decorations/DynamicCorporateBackground';
import { DynamicPageTransitionShutter as PageTransitionShutter } from '@/components/providers/DynamicPageTransitionShutter';
const inter = Inter({ const inter = Inter({
subsets: ['latin'], subsets: ['latin'],
display: 'swap', display: 'swap',
@@ -32,7 +33,8 @@ export async function generateMetadata(props: {
const params = await props.params; const params = await props.params;
const { locale } = params; const { locale } = params;
const baseUrl = process.env.CI ? 'http://etib.localhost' : SITE_URL; const baseUrl = SITE_URL;
return { return {
title: { title: {
template: '%s | E-TIB', template: '%s | E-TIB',
@@ -42,11 +44,11 @@ export async function generateMetadata(props: {
metadataBase: new URL(baseUrl), metadataBase: new URL(baseUrl),
manifest: '/manifest.webmanifest', manifest: '/manifest.webmanifest',
alternates: { alternates: {
canonical: `${baseUrl}/${locale}`, canonical: `/${locale}`,
languages: { languages: {
'de': `${baseUrl}/de`, 'de': `/de`,
'en': `${baseUrl}/en`, 'en': `/en`,
'x-default': `${baseUrl}/de`, 'x-default': `/de`,
}, },
}, },
}; };

View File

@@ -4,7 +4,7 @@ import { useEffect } from 'react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Button, Heading } from '@/components/ui'; import { Button, Heading } from '@/components/ui';
import { Terminal, Search, Home, Mail, Activity, Cpu } from 'lucide-react'; import { Terminal, Search, Home, Mail, Activity, Cpu } from 'lucide-react';
import { motion } from 'framer-motion'; import { m } from 'framer-motion';
import ClientNotFoundTracker from '@/components/analytics/ClientNotFoundTracker'; import ClientNotFoundTracker from '@/components/analytics/ClientNotFoundTracker';
export default function NotFound() { export default function NotFound() {
@@ -46,7 +46,7 @@ export default function NotFound() {
<div className="relative z-10 w-full max-w-4xl mx-auto flex flex-col items-center"> <div className="relative z-10 w-full max-w-4xl mx-auto flex flex-col items-center">
{/* 404 Visual */} {/* 404 Visual */}
<div className="relative mb-12"> <div className="relative mb-12">
<motion.div <m.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="relative" className="relative"
@@ -62,7 +62,7 @@ export default function NotFound() {
{/* Search Scan Overlay */} {/* Search Scan Overlay */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center"> <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center">
<motion.div <m.div
animate={{ animate={{
scale: [1, 1.1, 1], scale: [1, 1.1, 1],
opacity: [0.5, 1, 0.5] opacity: [0.5, 1, 0.5]
@@ -71,9 +71,9 @@ export default function NotFound() {
className="p-5 rounded-full bg-primary/20 border border-primary/40 backdrop-blur-md shadow-[0_0_40px_rgba(var(--color-primary),0.3)]" className="p-5 rounded-full bg-primary/20 border border-primary/40 backdrop-blur-md shadow-[0_0_40px_rgba(var(--color-primary),0.3)]"
> >
<Search className="w-10 h-10 text-primary" /> <Search className="w-10 h-10 text-primary" />
</motion.div> </m.div>
</div> </div>
</motion.div> </m.div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 w-full items-start"> <div className="grid grid-cols-1 md:grid-cols-12 gap-12 w-full items-start">

View File

@@ -1,10 +1,10 @@
'use client'; 'use client';
import { motion } from 'framer-motion'; import { m } from 'framer-motion';
export default function Template({ children }: { children: React.ReactNode }) { export default function Template({ children }: { children: React.ReactNode }) {
return ( return (
<motion.div <m.div
initial={{ opacity: 0, y: 20, filter: 'blur(10px)' }} initial={{ opacity: 0, y: 20, filter: 'blur(10px)' }}
animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }} animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
transition={{ transition={{
@@ -14,6 +14,6 @@ export default function Template({ children }: { children: React.ReactNode }) {
className="w-full h-full" className="w-full h-full"
> >
{children} {children}
</motion.div> </m.div>
); );
} }

View File

@@ -1,6 +1,5 @@
'use client'; 'use client';
import * as Sentry from '@sentry/nextjs';
import Error from 'next/error'; import Error from 'next/error';
import { useEffect } from 'react'; import { useEffect } from 'react';
@@ -10,7 +9,9 @@ export default function GlobalError({
error: Error & { digest?: string }; error: Error & { digest?: string };
}) { }) {
useEffect(() => { useEffect(() => {
Sentry.captureException(error); import('@sentry/nextjs').then((Sentry) => {
Sentry.captureException(error);
});
}, [error]); }, [error]);
return ( return (

View File

@@ -87,3 +87,35 @@
} }
} }
/* trigger rebuild */ /* trigger rebuild */
@layer utilities {
@keyframes hero-fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-hero-fade-in-up {
animation: hero-fade-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
}
@layer utilities {
@keyframes hero-zoom-out {
from {
transform: scale(1.05);
}
to {
transform: scale(1);
}
}
.animate-hero-zoom-out {
animation: hero-zoom-out 1.5s ease-out forwards;
}
}

48
bundle-stats.json Normal file
View File

@@ -0,0 +1,48 @@
.next/static/chunks/0b521531212fbb38.js
.next/static/chunks/0c4bd4931d57e66a.js
.next/static/chunks/114976d6e8233fa6.js
.next/static/chunks/163f657a16afbe5e.js
.next/static/chunks/1c2f1bc547a6b6db.js
.next/static/chunks/2583fa7802927c7b.js
.next/static/chunks/30da1bf4e9770388.js
.next/static/chunks/38b815a6d50481c3.js
.next/static/chunks/3dc2f63532eb2144.js
.next/static/chunks/3e327b4e2347bc0f.js
.next/static/chunks/3ef3cefd2dae1060.js
.next/static/chunks/45499dd88f8aa0be.js
.next/static/chunks/4b9828b23c1decfd.js
.next/static/chunks/4c801febc70daa53.js
.next/static/chunks/4d5ea13dba62fe42.js
.next/static/chunks/4dffe404993f353e.js
.next/static/chunks/4f185d0c413fdb9e.js
.next/static/chunks/4f69e8e09eeae4d6.js
.next/static/chunks/4fa8be7f170d31f3.js
.next/static/chunks/583b451485662887.js
.next/static/chunks/5e1e2d5c357c1b57.js
.next/static/chunks/6216b234a7a88a60.js
.next/static/chunks/651fbbae0062342b.js
.next/static/chunks/66a1b90666fc2830.js
.next/static/chunks/6c0ddf2fd63ab912.js
.next/static/chunks/72cf73de45fde6cf.js
.next/static/chunks/75ea3a278c540e05.js
.next/static/chunks/77a84d4dc662b7cf.js
.next/static/chunks/7c603cfd5a61abd1.js
.next/static/chunks/84ddf763fbc98e13.js
.next/static/chunks/885c97d656f95ebd.js
.next/static/chunks/8d6ae249fcc5e38f.js
.next/static/chunks/920f860e909b2eb8.js
.next/static/chunks/951d03271d663d8a.js
.next/static/chunks/a094276392fca24a.js
.next/static/chunks/a2e7fbe3f294caac.js
.next/static/chunks/a6c9d757034eb86a.js
.next/static/chunks/a6dc76eec9924929.js
.next/static/chunks/c3f21c4df538784a.js
.next/static/chunks/c641ee8514fa0552.js
.next/static/chunks/d8c0ef20e0ad9790.js
.next/static/chunks/e60e6d615bf7d2f5.js
.next/static/chunks/e714b6ccaf8d5f12.js
.next/static/chunks/eeebcf8c8de1a29a.js
.next/static/chunks/f0b47c1f19d25fd2.js
.next/static/chunks/f8d5b1a2accefb4c.js
.next/static/chunks/fa18a72fd9fb11d5.js
.next/static/chunks/turbopack-2aac2a40101e9988.js

View File

@@ -3,7 +3,7 @@
import React, { useEffect, useState, useCallback, useRef } from 'react'; import React, { useEffect, useState, useCallback, useRef } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { m, LazyMotion, AnimatePresence } from 'framer-motion'; import { m, AnimatePresence, LazyMotion } from 'framer-motion';
import { useRouter, useSearchParams, usePathname } from 'next/navigation'; import { useRouter, useSearchParams, usePathname } from 'next/navigation';
interface LightboxProps { interface LightboxProps {

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect, useMemo } from "react";
import { AnimatePresence, m, LazyMotion, domAnimation } from 'framer-motion'; import { AnimatePresence, m } from 'framer-motion';
import { MessageSquare, X, Check, Image as ImageIcon, Type, StickyNote, Download, Trash2, List } from "lucide-react"; import { MessageSquare, X, Check, Image as ImageIcon, Type, StickyNote, Download, Trash2, List } from "lucide-react";
import { clsx, type ClassValue } from "clsx"; import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { m, LazyMotion, domAnimation, Variants } from 'framer-motion'; import { m, Variants } from 'framer-motion';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay'; import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
import { LogoArcs } from '@/components/ui/LogoArcs'; import { LogoArcs } from '@/components/ui/LogoArcs';

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import { Container } from '@/components/ui/Container'; import { Container } from '@/components/ui/Container';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay'; import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { useScroll, useTransform, m, LazyMotion, domAnimation } from 'framer-motion'; import { useScroll, useTransform, m } from 'framer-motion';
import { Building2, Compass, Layers, ArrowDownToLine, Wrench, Factory, Zap, MapPin, CheckCircle2 } from 'lucide-react'; import { Building2, Compass, Layers, ArrowDownToLine, Wrench, Factory, Zap, MapPin, CheckCircle2 } from 'lucide-react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';

View File

@@ -2,7 +2,7 @@
import Link from 'next/link'; import Link from 'next/link';
import Image from 'next/image'; import Image from 'next/image';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay'; import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';

View File

@@ -4,7 +4,7 @@ import React, { Suspense } from 'react';
import { Section, Container } from '@/components/ui'; import { Section, Container } from '@/components/ui';
import { ContactForm } from '@/components/forms/ContactForm'; import { ContactForm } from '@/components/forms/ContactForm';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
const ContactMap = dynamic(() => import('@/components/ContactMap'), { const ContactMap = dynamic(() => import('@/components/ContactMap'), {
ssr: false, ssr: false,

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import { useLocale } from 'next-intl'; import { useLocale } from 'next-intl';
export function DataGridPulse() { export function DataGridPulse() {

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { useScroll, useTransform, m, LazyMotion, domAnimation } from 'framer-motion'; import { useScroll, useTransform, m } from 'framer-motion';
import { useLocale } from 'next-intl'; import { useLocale } from 'next-intl';
export function DeepDrillAnimation() { export function DeepDrillAnimation() {

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
export function GrowthChart() { export function GrowthChart() {

View File

@@ -3,7 +3,6 @@
import React from 'react'; import React from 'react';
import Image from 'next/image'; import Image from 'next/image';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { m, LazyMotion, domAnimation } from 'framer-motion';
import { Badge, Container, Heading } from '@/components/ui'; import { Badge, Container, Heading } from '@/components/ui';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -17,25 +16,7 @@ export interface HeroSectionProps {
ctaHref?: string; ctaHref?: string;
} }
const containerVariants = { // CSS animations replace Framer Motion for better LCP
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }
},
};
export const HeroSection: React.FC<HeroSectionProps> = (props) => { export const HeroSection: React.FC<HeroSectionProps> = (props) => {
const { title, badge, subtitle, backgroundImage, alignment, ctaLabel, ctaHref } = props; const { title, badge, subtitle, backgroundImage, alignment, ctaLabel, ctaHref } = props;
@@ -47,12 +28,9 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
className={`relative min-h-[35vh] md:min-h-[50vh] flex items-center pt-28 pb-10 md:pt-36 md:pb-24 overflow-hidden bg-neutral-dark ${alignment === 'center' ? 'justify-center text-center' : ''}`} className={`relative min-h-[35vh] md:min-h-[50vh] flex items-center pt-28 pb-10 md:pt-36 md:pb-24 overflow-hidden bg-neutral-dark ${alignment === 'center' ? 'justify-center text-center' : ''}`}
> >
{bgSrc && ( {bgSrc && (
<m.div <div
key={`hero-bg-${pathname}`} key={`hero-bg-${pathname}`}
className="absolute inset-0 z-0" className="absolute inset-0 z-0 animate-hero-zoom-out"
initial={{ scale: 1.05, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 1.5, ease: "easeOut" }}
> >
<Image <Image
src={bgSrc} src={bgSrc}
@@ -74,37 +52,34 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
{/* Top Fade for Header Navigation Readability */} {/* Top Fade for Header Navigation Readability */}
<div className="absolute top-0 left-0 w-full h-48 bg-gradient-to-b from-black/70 to-transparent z-[3] pointer-events-none" /> <div className="absolute top-0 left-0 w-full h-48 bg-gradient-to-b from-black/70 to-transparent z-[3] pointer-events-none" />
</m.div> </div>
)} )}
<Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}> <Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}>
<m.div <div
key={`hero-content-${pathname}`} key={`hero-content-${pathname}`}
className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`} className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}
variants={containerVariants}
initial="hidden"
animate="visible"
> >
{badge && ( {badge && (
<m.div variants={itemVariants}> <div className="animate-hero-fade-in-up" style={{ animationDelay: '0.1s', opacity: 0, animationFillMode: 'forwards' }}>
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg"> <Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">
{badge} {badge}
</Badge> </Badge>
</m.div> </div>
)} )}
<m.div variants={itemVariants}> <div className="animate-hero-fade-in-up" style={{ animationDelay: badge ? '0.25s' : '0.1s', opacity: 0, animationFillMode: 'forwards' }}>
<Heading level={1} size="section" variant="white" align={alignment || 'left'} className="mb-4 md:mb-8"> <Heading level={1} size="section" variant="white" align={alignment || 'left'} className="mb-4 md:mb-8">
{title} {title}
</Heading> </Heading>
</m.div> </div>
{subtitle && ( {subtitle && (
<m.div variants={itemVariants}> <div className="animate-hero-fade-in-up" style={{ animationDelay: badge ? '0.4s' : '0.25s', opacity: 0, animationFillMode: 'forwards' }}>
<p className={`text-lg md:text-2xl text-white/70 font-medium leading-relaxed max-w-2xl ${alignment === 'center' ? 'mx-auto' : ''}`}> <p className={`text-lg md:text-2xl text-white/70 font-medium leading-relaxed max-w-2xl ${alignment === 'center' ? 'mx-auto' : ''}`}>
{subtitle} {subtitle}
</p> </p>
</m.div> </div>
)} )}
{ctaLabel && ctaHref && ( {ctaLabel && ctaHref && (
<m.div variants={itemVariants} className={`mt-8 ${alignment === 'center' ? 'flex flex-wrap justify-center gap-4' : 'flex flex-wrap gap-4'}`}> <div className={`mt-8 animate-hero-fade-in-up ${alignment === 'center' ? 'flex flex-wrap justify-center gap-4' : 'flex flex-wrap gap-4'}`} style={{ animationDelay: badge ? '0.55s' : '0.4s', opacity: 0, animationFillMode: 'forwards' }}>
<Button <Button
href={ctaHref} href={ctaHref}
variant="accent" variant="accent"
@@ -115,9 +90,9 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
&rarr; &rarr;
</span> </span>
</Button> </Button>
</m.div> </div>
)} )}
</m.div> </div>
</Container> </Container>
</section> </section>
); );

View File

@@ -88,7 +88,9 @@ export function HeroVideo(props: HeroVideoProps) {
fill fill
className="object-cover z-[1] pointer-events-none filter contrast-125 saturate-110 brightness-90" className="object-cover z-[1] pointer-events-none filter contrast-125 saturate-110 brightness-90"
sizes="100vw" sizes="100vw"
quality={25}
priority priority
fetchPriority="high"
/> />
{/* Render video on top if available, but defer src to avoid blocking LCP */} {/* Render video on top if available, but defer src to avoid blocking LCP */}

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React, { useState, useRef, useCallback, useMemo } from 'react'; import React, { useState, useRef, useCallback, useMemo } from 'react';
import { m, LazyMotion, domAnimation, AnimatePresence } from 'framer-motion'; import { m, AnimatePresence } from 'framer-motion';
import { MapPin, CheckCircle2, ArrowUpRight } from 'lucide-react'; import { MapPin, CheckCircle2, ArrowUpRight } from 'lucide-react';
import Image from 'next/image'; import Image from 'next/image';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';

View File

@@ -4,7 +4,7 @@ import React from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay'; import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
export interface JobListingBlockProps { export interface JobListingBlockProps {

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import Link from 'next/link'; import Link from 'next/link';
import Image from 'next/image'; import Image from 'next/image';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay'; import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
@@ -46,7 +46,21 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
const scrollLeftRef = React.useRef(0); const scrollLeftRef = React.useRef(0);
const isDraggingRef = React.useRef(false); const isDraggingRef = React.useRef(false);
const [isDragging, setIsDragging] = React.useState(false); const [isDragging, setIsDragging] = React.useState(false);
// Center the first card on mount so it appears in the middle with peek on both sides
React.useEffect(() => {
const el = containerRef.current;
if (!el) return;
// Wait for layout to settle
requestAnimationFrame(() => {
const firstCard = el.querySelector('[data-card]') as HTMLElement | null;
if (!firstCard) return;
const cardCenter = firstCard.offsetLeft + firstCard.offsetWidth / 2;
const containerCenter = el.offsetWidth / 2;
el.scrollLeft = cardCenter - containerCenter;
});
}, [references.length]);
const badge = props.badge || data?.badge || t('badge'); const badge = props.badge || data?.badge || t('badge');
const title = props.title || data?.title || t('title'); const title = props.title || data?.title || t('title');
const description = props.description || data?.description || t('description'); const description = props.description || data?.description || t('description');
@@ -97,28 +111,30 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
<section id="referenzen" className="pt-16 pb-8 md:py-24 lg:py-32 bg-neutral-dark text-white relative overflow-hidden"> <section id="referenzen" className="pt-16 pb-8 md:py-24 lg:py-32 bg-neutral-dark text-white relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-b from-neutral-dark via-neutral-900 to-neutral-dark z-0" /> <div className="absolute inset-0 bg-gradient-to-b from-neutral-dark via-neutral-900 to-neutral-dark z-0" />
<div className="container relative z-10 mb-12 flex flex-col md:flex-row justify-between items-start md:items-end gap-6"> <div className="container relative z-10 mb-12 flex flex-col items-center justify-center text-center gap-6">
<div className="text-left"> <div>
<h2 className="text-primary-light font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2> <h2 className="text-primary-light font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>
<h3 className="font-heading text-3xl md:text-5xl font-extrabold">{title}</h3> <h3 className="font-heading text-3xl md:text-5xl font-extrabold">{title}</h3>
</div> </div>
<div className="text-left max-w-3xl mx-auto md:mx-0 mb-8 md:mb-16"> <div className="max-w-3xl mx-auto mb-4 md:mb-8 text-neutral-300">
{description} {description}
</div> </div>
</div> </div>
<div className="relative z-10"> <div className="relative z-10">
<div className="absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 md:hidden pointer-events-none" /> {/* Fade edges for peek effect */}
<div className="absolute left-0 top-0 bottom-0 w-16 bg-gradient-to-r from-neutral-dark to-transparent z-20 pointer-events-none" />
<div className="absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 pointer-events-none" />
{/* Carousel: breaks viewport edge on both sides but initial padding aligns with container */} {/* Carousel: centered first card with peek on both sides */}
<LazyMotion features={domAnimation}>
<div <div
ref={containerRef} ref={containerRef}
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseUp={onMouseUp} onMouseUp={onMouseUp}
onMouseMove={onMouseMove} onMouseMove={onMouseMove}
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-6 md:px-[max(2rem,calc((100vw-1280px)/2))] lg:px-[max(2.5rem,calc((100vw-1280px)/2))] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`} className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-[calc(50vw-160px)] md:px-[calc(50vw-240px)] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
> >
{references.map((ref, i) => { {references.map((ref, i) => {
const imgSrc = ref.image const imgSrc = ref.image
@@ -128,11 +144,12 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
return ( return (
<m.div <m.div
key={ref.id} key={ref.id}
data-card
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-50px" }} viewport={{ once: true, margin: "-50px" }}
transition={{ delay: i * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }} transition={{ delay: i * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }}
className="flex-shrink-0 w-[320px] md:w-[480px] snap-start group pointer-events-auto" className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto"
> >
<Link <Link
href={`/${locale}/referenzen#${ref.slug}`} href={`/${locale}/referenzen#${ref.slug}`}
@@ -152,6 +169,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
src={imgSrc} src={imgSrc}
alt={ref.title} alt={ref.title}
fill fill
quality={50}
sizes="(max-width: 768px) 320px, 480px" sizes="(max-width: 768px) 320px, 480px"
className="object-cover saturate-50 opacity-70 group-hover:scale-105 group-hover:opacity-100 group-hover:saturate-100 transition-all duration-700 ease-in-out pointer-events-none" className="object-cover saturate-50 opacity-70 group-hover:scale-105 group-hover:opacity-100 group-hover:saturate-100 transition-all duration-700 ease-in-out pointer-events-none"
/> />
@@ -169,10 +187,9 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
</m.div> </m.div>
); );
})} })}
{/* Right padding spacer so last card doesn't clip at viewport edge */}
<div className="flex-shrink-0 w-6" aria-hidden="true" />
</div> </div>
</LazyMotion>
</div> </div>
<div className="container relative z-10 mt-4 flex justify-center"> <div className="container relative z-10 mt-4 flex justify-center">

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { useScroll, useTransform, m, LazyMotion, domAnimation } from 'framer-motion'; import { useScroll, useTransform, m } from 'framer-motion';
import { useLocale } from 'next-intl'; import { useLocale } from 'next-intl';
export function ScaleOfImpact() { export function ScaleOfImpact() {

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { m, LazyMotion, domAnimation, Variants } from 'framer-motion'; import { m, Variants } from 'framer-motion';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay'; import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
export interface ServicePanelData { export interface ServicePanelData {

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { m, LazyMotion, domAnimation, Variants } from 'framer-motion'; import { m, Variants } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay'; import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';

View File

@@ -3,7 +3,7 @@
import React from 'react'; import React from 'react';
import TrackedLink from '@/components/analytics/TrackedLink'; import TrackedLink from '@/components/analytics/TrackedLink';
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button'; import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
export interface SupportCTAProps { export interface SupportCTAProps {
title: string; title: string;

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect } from 'react';
import { useScroll, useTransform, useSpring, m, LazyMotion, domAnimation } from 'framer-motion'; import { useScroll, useTransform, useSpring, m } from 'framer-motion';
import { LogoArcs } from '@/components/ui/LogoArcs'; import { LogoArcs } from '@/components/ui/LogoArcs';
function MagneticRing({ function MagneticRing({

View File

@@ -0,0 +1,7 @@
'use client';
import dynamic from 'next/dynamic';
export const DynamicCorporateBackground = dynamic(
() => import('./CorporateBackground').then(mod => mod.CorporateBackground),
{ ssr: false }
);

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
export function EUFundingBadge() { export function EUFundingBadge() {

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { m, LazyMotion, domAnimation } from 'framer-motion'; import { m } from 'framer-motion';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { sendContactFormAction } from '@/app/actions/contact'; import { sendContactFormAction } from '@/app/actions/contact';

View File

@@ -4,7 +4,7 @@ import * as React from 'react';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { TransitionLink } from '@/components/ui/TransitionLink'; import { TransitionLink } from '@/components/ui/TransitionLink';
import { Home, Layers, Star, Mail, Menu } from 'lucide-react'; import { Home, Layers, Star, Mail, Menu } from 'lucide-react';
import { m, LazyMotion, domAnimation, AnimatePresence } from 'framer-motion'; import { m, AnimatePresence } from 'framer-motion';
export interface NavLink { export interface NavLink {
label: string; label: string;

View File

@@ -0,0 +1,7 @@
'use client';
import dynamic from 'next/dynamic';
export const DynamicPageTransitionShutter = dynamic(
() => import('./PageTransitionShutter').then(mod => mod.PageTransitionShutter),
{ ssr: false }
);

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { m, LazyMotion, domAnimation, AnimatePresence } from 'framer-motion'; import { m, AnimatePresence } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { m, LazyMotion, domAnimation, AnimatePresence } from 'framer-motion'; import { m, AnimatePresence } from 'framer-motion';
import { useTransition } from './TransitionProvider'; import { useTransition } from './TransitionProvider';
import Image from 'next/image'; import Image from 'next/image';

View File

@@ -2,7 +2,9 @@
import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react'; import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { useRouter, usePathname, useSearchParams } from 'next/navigation';
import { LazyMotion, domAnimation } from 'framer-motion'; import { LazyMotion } from 'framer-motion';
const loadFeatures = () => import('@/lib/framer-features').then(res => res.default);
interface TransitionContextType { interface TransitionContextType {
isTransitioning: boolean; isTransitioning: boolean;
@@ -69,7 +71,7 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
setTransitionMessage={setTransitionMessage} setTransitionMessage={setTransitionMessage}
/> />
</React.Suspense> </React.Suspense>
<LazyMotion features={domAnimation}> <LazyMotion features={loadFeatures}>
{children} {children}
</LazyMotion> </LazyMotion>
</TransitionContext.Provider> </TransitionContext.Provider>

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useMotionValue, useTransform, animate, useInView, m, LazyMotion, domAnimation } from 'framer-motion'; import { useMotionValue, useTransform, animate, useInView, m } from 'framer-motion';
interface AnimatedCounterProps { interface AnimatedCounterProps {
value: number; value: number;

View File

@@ -37,7 +37,7 @@ export function TransitionLink({ children, href, onClick, transitionMessage, ...
}; };
return ( return (
<Link href={href} onClick={handleClick} className={cn('cursor-pointer', props.className)} {...props}> <Link href={href} onClick={handleClick} prefetch={props.prefetch ?? false} className={cn('cursor-pointer', props.className)} {...props}>
{children} {children}
</Link> </Link>
); );

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useInView, m, LazyMotion, domAnimation } from 'framer-motion'; import { useInView, m } from 'framer-motion';
import { useRef } from 'react'; import { useRef } from 'react';
/** /**

1
dummy-polyfill.js Normal file
View File

@@ -0,0 +1 @@
export default {};

View File

@@ -1,5 +1,5 @@
// Sentry initialization move to GlitchtipErrorReportingService to allow lazy-loading // Sentry initialization move to GlitchtipErrorReportingService to allow lazy-loading
// for PageSpeed 100 optimizations. This file is now empty to prevent the SDK // for PageSpeed 100 optimizations. This file is now empty to prevent the SDK
// from being included in the initial JS bundle. // from being included in the initial JS bundle.
import * as Sentry from '@sentry/nextjs';
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; export {};

2
lib/empty-polyfill.js Normal file
View File

@@ -0,0 +1,2 @@
// Empty polyfill to trick Next.js into not loading legacy polyfills
export default {};

View File

@@ -1,7 +1,6 @@
import { config } from './config'; import { config } from './config';
const getSiteUrl = () => { const getSiteUrl = () => {
if (process.env.CI) return 'http://etib.localhost';
return (config.baseUrl as string) || 'https://e-tib.com'; return (config.baseUrl as string) || 'https://e-tib.com';
}; };

View File

@@ -1,4 +1,4 @@
import withMintelConfig from '@mintel/next-config'; import { baseNextConfig } from '@mintel/next-config';
import withBundleAnalyzer from '@next/bundle-analyzer'; import withBundleAnalyzer from '@next/bundle-analyzer';
import { withSentryConfig } from '@sentry/nextjs'; import { withSentryConfig } from '@sentry/nextjs';
@@ -47,7 +47,16 @@ const nextConfig = {
NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION || process.env.npm_package_version || 'dev', NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION || process.env.npm_package_version || 'dev',
}, },
// Prevent webpack from restarting when .env files are touched via Docker volume mount // Prevent webpack from restarting when .env files are touched via Docker volume mount
webpack: (config, { dev }) => { webpack: (config, { dev, isServer }) => {
// Completely strip Next.js forced legacy polyfills for modern browser targets
if (!isServer) {
config.resolve.alias = {
...config.resolve.alias,
'next/dist/build/polyfills/polyfill-module': false,
'next/dist/build/polyfills/polyfill-nomodule': false,
};
}
if (dev) { if (dev) {
config.watchOptions = { config.watchOptions = {
...config.watchOptions, ...config.watchOptions,
@@ -538,14 +547,69 @@ const withAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true', enabled: process.env.ANALYZE === 'true',
}); });
// withMintelConfig internally sets output:'standalone' and wraps with withSentryConfig.
// In dev mode, standalone triggers @vercel/nft file tracing on every compile through // In dev mode, standalone triggers @vercel/nft file tracing on every compile through
// VirtioFS, which hammers CPU. Strip it for dev. // VirtioFS, which hammers CPU. Strip it for dev.
const withMDX = createMDX({}); const withMDX = createMDX({});
let resolvedConfig = withMDX(withAnalyzer(withMintelConfig(nextConfig))); let resolvedConfig = withMDX(withAnalyzer(nextConfig));
// Apply next-intl manually (bypassing withMintelConfig to control Sentry)
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
resolvedConfig = withNextIntl(resolvedConfig);
resolvedConfig = withSentryConfig(
resolvedConfig,
{
silent: !process.env.CI,
treeshake: { removeDebugLogging: true },
},
{
disableClientWebpackPlugin: true,
autoInstrumentServerFunctions: false,
autoInstrumentMiddleware: false,
autoInstrumentAppDirectory: false,
}
);
// Manually merge baseNextConfig properties
const finalConfig = {
...baseNextConfig,
...resolvedConfig,
images: {
...baseNextConfig.images,
...(resolvedConfig.images || {}),
remotePatterns: [
...(baseNextConfig.images?.remotePatterns || []),
...(resolvedConfig.images?.remotePatterns || [])
]
},
rewrites: async () => {
let baseRewrites = [];
if (typeof baseNextConfig.rewrites === 'function') {
baseRewrites = await baseNextConfig.rewrites();
}
let configRewrites = { beforeFiles: [], fallback: [], afterFiles: [] };
if (typeof resolvedConfig.rewrites === 'function') {
const res = await resolvedConfig.rewrites();
if (Array.isArray(res)) {
configRewrites.fallback = res;
} else {
configRewrites = res;
}
}
return {
beforeFiles: configRewrites.beforeFiles || [],
fallback: [
...(configRewrites.fallback || []),
...baseRewrites
],
afterFiles: configRewrites.afterFiles || []
};
}
};
if (!isProd) { if (!isProd) {
delete resolvedConfig.output; delete finalConfig.output;
} }
export default resolvedConfig; export default finalConfig;

View File

@@ -105,6 +105,7 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint .", "lint": "eslint .",
"postinstall": "echo '' > node_modules/next/dist/build/polyfills/polyfill-module.js && echo '' > node_modules/next/dist/build/polyfills/polyfill-nomodule.js",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests", "test": "vitest run --passWithNoTests",
"test:og": "vitest run tests/og-image.test.ts", "test:og": "vitest run tests/og-image.test.ts",
@@ -138,7 +139,7 @@
"prepare": "husky", "prepare": "husky",
"preinstall": "npx only-allow pnpm" "preinstall": "npx only-allow pnpm"
}, },
"version": "2.2.44", "version": "2.2.72",
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@parcel/watcher", "@parcel/watcher",
@@ -149,13 +150,15 @@
], ],
"overrides": { "overrides": {
"next": "16.1.6", "next": "16.1.6",
"minimatch": ">=10.2.2" "minimatch": ">=10.2.2",
"framer-motion": "^12.38.0"
} }
}, },
"browserslist": [ "browserslist": [
"last 3 versions", "last 2 chrome versions",
"not dead", "last 2 firefox versions",
"not ie 11" "last 2 edge versions",
"safari >= 15.4"
], ],
"peerDependencies": { "peerDependencies": {
"lucide-react": "^0.563.0" "lucide-react": "^0.563.0"

40
pnpm-lock.yaml generated
View File

@@ -7,6 +7,7 @@ settings:
overrides: overrides:
next: 16.1.6 next: 16.1.6
minimatch: '>=10.2.2' minimatch: '>=10.2.2'
framer-motion: ^12.38.0
importers: importers:
@@ -52,7 +53,7 @@ importers:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
framer-motion: framer-motion:
specifier: ^12.34.0 specifier: ^12.38.0
version: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
graphql: graphql:
specifier: ^16.12.0 specifier: ^16.12.0
@@ -4843,20 +4844,6 @@ packages:
fraction.js@5.3.4: fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
framer-motion@11.18.2:
resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
framer-motion@12.38.0: framer-motion@12.38.0:
resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==}
peerDependencies: peerDependencies:
@@ -6142,15 +6129,9 @@ packages:
module-details-from-path@1.0.4: module-details-from-path@1.0.4:
resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==}
motion-dom@11.18.1:
resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==}
motion-dom@12.38.0: motion-dom@12.38.0:
resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
motion-utils@11.18.1:
resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==}
motion-utils@12.36.0: motion-utils@12.36.0:
resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==} resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
@@ -9246,7 +9227,7 @@ snapshots:
dependencies: dependencies:
'@medv/finder': 4.0.2 '@medv/finder': 4.0.2
clsx: 2.1.1 clsx: 2.1.1
framer-motion: 11.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) framer-motion: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
html2canvas: 1.4.1 html2canvas: 1.4.1
lucide-react: 0.441.0(react@19.2.4) lucide-react: 0.441.0(react@19.2.4)
next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0)
@@ -12941,15 +12922,6 @@ snapshots:
fraction.js@5.3.4: {} fraction.js@5.3.4: {}
framer-motion@11.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
motion-dom: 11.18.1
motion-utils: 11.18.1
tslib: 2.8.1
optionalDependencies:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
framer-motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): framer-motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
motion-dom: 12.38.0 motion-dom: 12.38.0
@@ -14577,16 +14549,10 @@ snapshots:
module-details-from-path@1.0.4: {} module-details-from-path@1.0.4: {}
motion-dom@11.18.1:
dependencies:
motion-utils: 11.18.1
motion-dom@12.38.0: motion-dom@12.38.0:
dependencies: dependencies:
motion-utils: 12.36.0 motion-utils: 12.36.0
motion-utils@11.18.1: {}
motion-utils@12.36.0: {} motion-utils@12.36.0: {}
mri@1.2.0: {} mri@1.2.0: {}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

@@ -6,13 +6,14 @@ module.exports = async (browser, context) => {
const page = await browser.newPage(); const page = await browser.newPage();
// Using LHCI_URL or TARGET_URL if available // Using LHCI_URL or TARGET_URL if available
const targetUrl = const targetUrl =
process.env.LHCI_URL || process.env.TARGET_URL || 'https://testing.klz-cables.com'; process.env.LHCI_URL || process.env.TARGET_URL || 'https://testing.e-tib.com';
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026'; const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
const authCookieName = process.env.AUTH_COOKIE_NAME || 'etib_gatekeeper_session';
console.log(`🔑 LHCI Auth: Setting gatekeeper cookie for ${new URL(targetUrl).hostname}...`); console.log(`🔑 LHCI Auth: Setting gatekeeper cookie (${authCookieName}) for ${new URL(targetUrl).hostname}...`);
await page.setCookie({ await page.setCookie({
name: 'klz_gatekeeper_session', name: authCookieName,
value: gatekeeperPassword, value: gatekeeperPassword,
domain: new URL(targetUrl).hostname, domain: new URL(targetUrl).hostname,
path: '/', path: '/',

View File

@@ -19,6 +19,7 @@ const targetUrl =
'http://localhost:3000'; 'http://localhost:3000';
const limit = process.env.PAGESPEED_LIMIT ? parseInt(process.env.PAGESPEED_LIMIT) : 20; // Default limit to avoid infinite runs const limit = process.env.PAGESPEED_LIMIT ? parseInt(process.env.PAGESPEED_LIMIT) : 20; // Default limit to avoid infinite runs
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026'; const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
const authCookieName = process.env.AUTH_COOKIE_NAME || 'etib_gatekeeper_session';
async function main() { async function main() {
console.log(`\n🚀 Starting PageSpeed test for: ${targetUrl}`); console.log(`\n🚀 Starting PageSpeed test for: ${targetUrl}`);
@@ -32,7 +33,7 @@ async function main() {
// We might need to bypass gatekeeper for the sitemap fetch too // We might need to bypass gatekeeper for the sitemap fetch too
const response = await axios.get(sitemapUrl, { const response = await axios.get(sitemapUrl, {
headers: { headers: {
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`, Cookie: `${authCookieName}=${gatekeeperPassword}`,
}, },
validateStatus: (status) => status < 400, validateStatus: (status) => status < 400,
}); });
@@ -77,7 +78,7 @@ async function main() {
// Handle authentication for staging/testing // Handle authentication for staging/testing
// Lighthouse can set cookies via --collect.settings.extraHeaders // Lighthouse can set cookies via --collect.settings.extraHeaders
const extraHeaders = JSON.stringify({ const extraHeaders = JSON.stringify({
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`, Cookie: `${authCookieName}=${gatekeeperPassword}`,
}); });
// Detect Chrome path from Puppeteer installation if not provided // Detect Chrome path from Puppeteer installation if not provided

View File

@@ -70,7 +70,9 @@ while IFS= read -r run; do
elif [ "$CURRENT_PRIO" -lt "$RUN_PRIO" ]; then elif [ "$CURRENT_PRIO" -lt "$RUN_PRIO" ]; then
echo "Current workflow has LOWER priority ($CURRENT_PRIO < $RUN_PRIO). Aborting self." echo "Current workflow has LOWER priority ($CURRENT_PRIO < $RUN_PRIO). Aborting self."
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$CURRENT_RUN_ID/cancel" curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$CURRENT_RUN_ID/cancel"
exit 1 echo "Waiting for Gitea to process cancellation..."
sleep 15
exit 0
else else
# Equal priority: Newer wins # Equal priority: Newer wins
if [ "$CURRENT_RUN_ID" -gt "$RUN_ID" ]; then if [ "$CURRENT_RUN_ID" -gt "$RUN_ID" ]; then
@@ -79,7 +81,9 @@ while IFS= read -r run; do
else else
echo "Equal priority, but current is older. Aborting self." echo "Equal priority, but current is older. Aborting self."
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$CURRENT_RUN_ID/cancel" curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$CURRENT_RUN_ID/cancel"
exit 1 echo "Waiting for Gitea to process cancellation..."
sleep 15
exit 0
fi fi
fi fi
done <<< "$RUNS" done <<< "$RUNS"

9
skip_branch_script.sh Normal file
View File

@@ -0,0 +1,9 @@
if [[ "${{ github.ref_type }}" == "branch" && "${{ github.ref_name }}" != "main" && "${{ github.ref_name }}" != "master" ]]; then
# Fetch tags from remote for this specific commit to check if it's being tagged simultaneously
TAGS=$(git ls-remote --tags origin | grep "${{ github.sha }}" || true)
if [[ -n "$TAGS" ]]; then
echo "Dieser Commit hat einen Tag! Überspringe Preview-Deployment, damit der Tag-Deploy Vorrang hat."
echo "target=skip" >> $GITHUB_OUTPUT
exit 0
fi
fi