Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ccc408a8d | |||
| f9f0970f3f | |||
| f463e854fd | |||
| 81dd2c95ed | |||
| ecebe63084 | |||
| ba4d21195c | |||
| c74c786988 | |||
| 4fd7160967 | |||
| 04a2bb0ebc | |||
| a52c35a224 |
@@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl';
|
||||
import { getAppServices } from '@/lib/services/create-services';
|
||||
import { Button, Heading } from '@/components/ui';
|
||||
import { Terminal, Activity, AlertTriangle, RefreshCw, Home, ShieldAlert } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { m } from 'framer-motion';
|
||||
|
||||
export default function 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">
|
||||
{/* The "Broken" Machine Visual */}
|
||||
<div className="relative mb-12">
|
||||
<motion.div
|
||||
<m.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="relative"
|
||||
@@ -81,7 +81,7 @@ export default function Error({
|
||||
|
||||
{/* Warning Overlay */}
|
||||
<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={{
|
||||
opacity: [0.4, 1, 0.4],
|
||||
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)]"
|
||||
>
|
||||
<ShieldAlert className="w-12 h-12 text-red-500" />
|
||||
</motion.div>
|
||||
</m.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</m.div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-8 w-full items-start">
|
||||
|
||||
@@ -17,9 +17,12 @@ import { mapFileSlugToTranslated } from '@/lib/slugs';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
import { TransitionProvider } from '@/components/providers/TransitionProvider';
|
||||
import { PageTransitionShutter } from '@/components/providers/PageTransitionShutter';
|
||||
import { InitialLoader } from '@/components/providers/InitialLoader';
|
||||
import { CorporateBackground } from '@/components/decorations/CorporateBackground';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const CorporateBackground = dynamic(() => import('@/components/decorations/CorporateBackground').then(mod => mod.CorporateBackground), { ssr: false });
|
||||
const PageTransitionShutter = dynamic(() => import('@/components/providers/PageTransitionShutter').then(mod => mod.PageTransitionShutter), { ssr: false });
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
@@ -32,7 +35,8 @@ export async function generateMetadata(props: {
|
||||
const params = await props.params;
|
||||
const { locale } = params;
|
||||
|
||||
const baseUrl = process.env.CI ? 'http://etib.localhost' : SITE_URL;
|
||||
const baseUrl = SITE_URL;
|
||||
|
||||
return {
|
||||
title: {
|
||||
template: '%s | E-TIB',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Button, Heading } from '@/components/ui';
|
||||
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';
|
||||
|
||||
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">
|
||||
{/* 404 Visual */}
|
||||
<div className="relative mb-12">
|
||||
<motion.div
|
||||
<m.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="relative"
|
||||
@@ -62,7 +62,7 @@ export default function NotFound() {
|
||||
|
||||
{/* 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">
|
||||
<motion.div
|
||||
<m.div
|
||||
animate={{
|
||||
scale: [1, 1.1, 1],
|
||||
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)]"
|
||||
>
|
||||
<Search className="w-10 h-10 text-primary" />
|
||||
</motion.div>
|
||||
</m.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</m.div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 w-full items-start">
|
||||
|
||||
@@ -13,7 +13,7 @@ const HomeCompetenceBentoGrid = nextDynamic(() => import('@/components/blocks/Co
|
||||
const HomeReferencesSlider = nextDynamic(() => import('@/components/blocks/ReferencesSlider').then(mod => mod.ReferencesSlider));
|
||||
const FaqBlock = nextDynamic(() => import('@/components/blocks/FaqBlock').then(mod => mod.FaqBlock));
|
||||
const CertificatesBlock = nextDynamic(() => import('@/components/blocks/CertificatesBlock').then(mod => mod.CertificatesBlock));
|
||||
const HeroSection = nextDynamic(() => import('@/components/blocks/HeroSection').then(mod => mod.HeroSection));
|
||||
import { HeroSection } from '@/components/blocks/HeroSection';
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { m } from 'framer-motion';
|
||||
|
||||
export default function Template({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<motion.div
|
||||
<m.div
|
||||
initial={{ opacity: 0, y: 20, filter: 'blur(10px)' }}
|
||||
animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
|
||||
transition={{
|
||||
@@ -14,6 +14,6 @@ export default function Template({ children }: { children: React.ReactNode }) {
|
||||
className="w-full h-full"
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</m.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
||||
<m.div
|
||||
key={`hero-bg-${pathname}`}
|
||||
className="absolute inset-0 z-0"
|
||||
initial={{ scale: 1.05, opacity: 0 }}
|
||||
initial={{ scale: 1.05, opacity: 1 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 1.5, ease: "easeOut" }}
|
||||
>
|
||||
|
||||
@@ -88,7 +88,9 @@ export function HeroVideo(props: HeroVideoProps) {
|
||||
fill
|
||||
className="object-cover z-[1] pointer-events-none filter contrast-125 saturate-110 brightness-90"
|
||||
sizes="100vw"
|
||||
quality={25}
|
||||
priority
|
||||
fetchPriority="high"
|
||||
/>
|
||||
|
||||
{/* Render video on top if available, but defer src to avoid blocking LCP */}
|
||||
|
||||
@@ -169,6 +169,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
src={imgSrc}
|
||||
alt={ref.title}
|
||||
fill
|
||||
quality={50}
|
||||
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"
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
|
||||
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 {
|
||||
isTransitioning: boolean;
|
||||
@@ -69,7 +71,7 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
|
||||
setTransitionMessage={setTransitionMessage}
|
||||
/>
|
||||
</React.Suspense>
|
||||
<LazyMotion features={domAnimation}>
|
||||
<LazyMotion features={loadFeatures}>
|
||||
{children}
|
||||
</LazyMotion>
|
||||
</TransitionContext.Provider>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { config } from './config';
|
||||
|
||||
const getSiteUrl = () => {
|
||||
if (process.env.CI) return 'http://etib.localhost';
|
||||
return (config.baseUrl as string) || 'https://e-tib.com';
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ const nextConfig = {
|
||||
pagesBufferLength: 2,
|
||||
},
|
||||
experimental: {
|
||||
optimizeCss: true,
|
||||
staleTimes: {
|
||||
dynamic: 0,
|
||||
static: 30,
|
||||
@@ -47,7 +48,16 @@ const nextConfig = {
|
||||
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
|
||||
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) {
|
||||
config.watchOptions = {
|
||||
...config.watchOptions,
|
||||
|
||||
10
package.json
@@ -105,6 +105,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"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",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"test:og": "vitest run tests/og-image.test.ts",
|
||||
@@ -138,7 +139,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.2.46",
|
||||
"version": "2.2.55",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
@@ -153,9 +154,10 @@
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"last 3 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
"last 2 chrome versions",
|
||||
"last 2 firefox versions",
|
||||
"last 2 edge versions",
|
||||
"safari >= 15.4"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"lucide-react": "^0.563.0"
|
||||
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 155 KiB |