last update
CI / quality (push) Waiting to run

This commit is contained in:
MOH
2026-07-14 20:10:38 +02:00
parent cc6db94977
commit 1a7b3397f6
14 changed files with 847 additions and 146 deletions
+19 -6
View File
@@ -6,7 +6,8 @@ import { redirect } from "next/navigation";
import { SiteAmbientBackdrop } from "@/components/layout/site-ambient-backdrop";
import { SiteFooter } from "@/components/layout/site-footer";
import { SiteHeader } from "@/components/layout/site-header";
import { isAdminAuthenticated } from "@/lib/admin-auth";
import { ScrollSmootherProvider } from "@/components/scroll-smoother-provider";
import { isSuperAdmin } from "@/lib/admin-auth";
import { getMaintenanceMode, getSiteSettings, getSiteSettingsMediaBindings } from "@/lib/app-config";
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
@@ -29,22 +30,34 @@ export default async function SiteLayout({ children, params }: SiteLayoutProps)
getSiteSettings(),
]);
const localeKey = resolveLocale(await getLocale().catch(() => siteSettings.defaultLocale), siteSettings.defaultLocale);
const authenticated = await isAdminAuthenticated();
// Server-side decision only. The header receives just this boolean and uses
// it purely to show/hide the Admin shortcut link — it grants no access by
// itself. Every admin page/action re-checks the session independently, so
// this value is not a security boundary, only a UI convenience.
const authenticated = await isSuperAdmin();
if (maintenanceEnabled && !authenticated) {
redirect(getLocalizedPath(localeKey, "/coming-soon", siteSettings.defaultLocale));
}
return (
<div className="relative flex min-h-screen flex-col overflow-hidden">
<>
<ScrollSmootherProvider />
<SiteAmbientBackdrop />
<SiteHeader
lightLogoUrl={mediaBindings.siteLogoLight?.url}
darkLogoUrl={mediaBindings.siteLogoDark?.url}
defaultLocale={siteSettings.defaultLocale}
isSuperAdmin={authenticated}
/>
<main className="flex-1">{children}</main>
<SiteFooter defaultLocale={siteSettings.defaultLocale} />
</div>
<div id="smooth-wrapper">
<div id="smooth-content">
<div className="site-content-frame flex min-h-screen flex-col">
<main className="flex-1">{children}</main>
<SiteFooter defaultLocale={siteSettings.defaultLocale} />
</div>
</div>
</div>
</>
);
}
+26 -27
View File
@@ -4,14 +4,13 @@ import { getLocale, getTranslations } from "next-intl/server";
import { Container } from "@/components/layout/container";
import { HomeHero } from "@/components/layout/home-hero";
import { MotionFade } from "@/components/motion-fade";
import { BentoGridSection } from "@/components/home/bento-grid-section";
import { MagicBentoSection } from "@/components/home/magic-bento-section";
import { CapabilitiesSection } from "@/components/home/capabilities-section";
import { ContactCtaSection } from "@/components/home/contact-cta-section";
import { MarqueeSection } from "@/components/home/marquee-section";
import { ProcessSection } from "@/components/home/process-section";
import { ProjectsSection } from "@/components/home/projects-section";
import type {
BentoSectionCopy,
CapabilitiesSectionCopy,
ContactCtaSectionCopy,
MarqueeSectionCopy,
@@ -72,45 +71,40 @@ export default async function HomePage({ params }: HomePageProps) {
getPublishedPortfolioProjects(),
]);
const bentoCopy: BentoSectionCopy = {
eyebrow: t("bento.eyebrow"),
title: t("bento.title"),
description: t("bento.description"),
about: {
eyebrow: t("bento.about.eyebrow"),
const bentoCards = [
{
label: t("bento.about.eyebrow"),
title: t("bento.about.title"),
description: t("bento.about.description"),
points: t.raw("bento.about.points") as string[],
},
coreStack: {
eyebrow: t("bento.coreStack.eyebrow"),
{
label: t("bento.coreStack.eyebrow"),
title: t("bento.coreStack.title"),
items: t.raw("bento.coreStack.items") as string[],
description: (t.raw("bento.coreStack.items") as string[]).join(" · "),
},
availability: {
eyebrow: t("bento.availability.eyebrow"),
{
label: t("bento.availability.eyebrow"),
title: t("bento.availability.title"),
description: t("bento.availability.description"),
status: t("bento.availability.status"),
},
currentFocus: {
eyebrow: t("bento.currentFocus.eyebrow"),
{
label: t("bento.currentFocus.eyebrow"),
title: t("bento.currentFocus.title"),
items: t.raw("bento.currentFocus.items") as string[],
description: (t.raw("bento.currentFocus.items") as string[]).join(" · "),
},
contact: {
eyebrow: t("bento.contact.eyebrow"),
{
label: t("bento.contact.eyebrow"),
title: t("bento.contact.title"),
description: t("bento.contact.description"),
emailLabel: t("bento.contact.emailLabel"),
emailValue: t("bento.contact.emailValue"),
},
highlights: {
eyebrow: t("bento.highlights.eyebrow"),
{
label: t("bento.highlights.eyebrow"),
title: t("bento.highlights.title"),
items: t.raw("bento.highlights.items") as Array<{ value: string; label: string }>,
description: (t.raw("bento.highlights.items") as Array<{ value: string; label: string }>)
.map((i) => `${i.value} ${i.label}`)
.join(" · "),
},
};
];
const marqueeCopy: MarqueeSectionCopy = {
eyebrow: t("marquee.eyebrow"),
@@ -216,7 +210,12 @@ export default async function HomePage({ params }: HomePageProps) {
className="relative z-10 flex max-w-7xl flex-col gap-16 pb-14 pt-6 sm:gap-20 sm:pb-16 sm:pt-8 lg:gap-24 lg:pb-20 lg:pt-10"
>
<MotionFade>
<BentoGridSection copy={bentoCopy} />
<MagicBentoSection
eyebrow={t("bento.eyebrow")}
heading={t("bento.title")}
description={t("bento.description")}
cards={bentoCards}
/>
</MotionFade>
<MotionFade delay={0.06}>
+196 -43
View File
@@ -246,8 +246,15 @@ html[lang="ar"] {
);
}
.site-content-frame {
position: relative;
isolation: isolate;
min-height: 100%;
overflow-x: clip;
}
.hero-surface {
background: linear-gradient(180deg, hsl(var(--hero-base)), hsl(var(--background)));
background: hsl(var(--background));
}
.hero-home {
@@ -255,7 +262,7 @@ html[lang="ar"] {
}
.hero-page {
background: linear-gradient(180deg, hsl(var(--hero-base) / 0.96), hsl(var(--background)));
background: hsl(var(--background));
}
.hero-sheet {
@@ -265,29 +272,15 @@ html[lang="ar"] {
.hero-sheet-base {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 90% 55% at 50% -5%, hsl(var(--hero-left) / 0.38), transparent),
radial-gradient(ellipse 55% 40% at 92% 10%, hsl(var(--hero-right) / 0.22), transparent),
radial-gradient(ellipse 60% 35% at 8% 15%, hsl(var(--hero-left) / 0.18), transparent),
linear-gradient(180deg, hsl(var(--hero-base)), hsl(var(--background)) 90%);
background: hsl(var(--background));
}
.hero-sheet-veil {
position: absolute;
inset: 0;
background: linear-gradient(
160deg,
rgba(255, 255, 255, 0.06) 0%,
rgba(255, 255, 255, 0.02) 40%,
rgba(255, 255, 255, 0) 100%
);
display: none;
}
.hero-sheet-fade {
position: absolute;
inset: auto 0 0 0;
height: 7rem;
background: linear-gradient(180deg, transparent, hsl(var(--background)));
display: none;
}
.hero-title-line {
@@ -381,33 +374,11 @@ html[lang="ar"] {
}
.hero-header-bridge {
position: absolute;
left: 50%;
top: 3.5rem;
height: 11rem;
width: min(34rem, 72vw);
transform: translateX(-50%);
background:
radial-gradient(circle at 50% 0%, hsl(var(--hero-left) / 0.18), transparent 56%),
linear-gradient(180deg, hsl(var(--foreground) / 0.06), transparent 72%);
filter: blur(24px);
opacity: 0.9;
pointer-events: none;
display: none;
}
.hero-content-bridge {
position: absolute;
left: 50%;
bottom: 2.5rem;
height: 9rem;
width: min(44rem, 82vw);
transform: translateX(-50%);
background:
radial-gradient(circle at 50% 50%, hsl(var(--hero-bottom) / 0.2), transparent 58%),
linear-gradient(180deg, transparent, hsl(var(--background) / 0.86));
filter: blur(20px);
opacity: 0.92;
pointer-events: none;
display: none;
}
.retro-led-panel {
@@ -543,3 +514,185 @@ html[lang="ar"] {
}
}
/* ── Magic Bento ── */
.mb-grid {
display: grid;
gap: 0.5em;
font-size: clamp(1rem, 0.9rem + 0.5vw, 1.5rem);
}
.mb-card {
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
aspect-ratio: 4 / 3;
min-height: 200px;
width: 100%;
padding: 1.25em;
border-radius: var(--radius-nested, 16px);
border: 1px solid hsl(var(--border));
background: hsl(var(--surface-2));
font-weight: 300;
overflow: hidden;
transition: all 0.3s ease;
--glow-x: 50%;
--glow-y: 50%;
--glow-intensity: 0;
--glow-radius: 200px;
}
.mb-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px hsl(var(--foreground) / 0.08);
}
.mb-card__header,
.mb-card__body {
display: flex;
position: relative;
color: hsl(var(--foreground));
}
.mb-card__header {
gap: 0.75em;
justify-content: space-between;
}
.mb-card__body {
flex-direction: column;
gap: 0.25em;
}
.mb-card__label {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.14em;
color: hsl(var(--muted-foreground));
}
.mb-card__title {
font-weight: 500;
font-size: 1rem;
margin: 0;
color: hsl(var(--foreground));
}
.mb-card__desc {
font-size: 0.8rem;
line-height: 1.4;
color: hsl(var(--muted-foreground));
}
.mb-card__extra {
margin-top: 0.5em;
}
.mb-card--autohide .mb-card__title,
.mb-card--autohide .mb-card__desc {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.mb-card--autohide .mb-card__title {
-webkit-line-clamp: 2;
line-clamp: 2;
}
.mb-card--autohide .mb-card__desc {
-webkit-line-clamp: 3;
line-clamp: 3;
}
/* Responsive grid */
@media (max-width: 599px) {
.mb-grid {
grid-template-columns: 1fr;
}
.mb-card {
min-height: 180px;
}
}
@media (min-width: 600px) {
.mb-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.mb-grid {
grid-template-columns: repeat(4, 1fr);
}
.mb-card:nth-child(3) {
grid-column: span 2;
grid-row: span 2;
}
.mb-card:nth-child(4) {
grid-column: 1 / span 2;
grid-row: 2 / span 2;
}
.mb-card:nth-child(6) {
grid-column: 4;
grid-row: 3;
}
}
/* Border glow */
.mb-card--glow::after {
content: "";
position: absolute;
inset: 0;
padding: 6px;
background: radial-gradient(
var(--glow-radius) circle at var(--glow-x) var(--glow-y),
rgba(var(--glow-color, 220, 68, 22), calc(var(--glow-intensity) * 0.8)) 0%,
rgba(var(--glow-color, 220, 68, 22), calc(var(--glow-intensity) * 0.4)) 30%,
transparent 60%
);
border-radius: inherit;
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
transition: opacity 0.3s ease;
z-index: 1;
}
.mb-card--glow:hover {
box-shadow:
0 4px 20px rgba(220, 68, 22, 0.15),
0 0 30px rgba(220, 68, 22, 0.08);
}
/* ── ScrollSmoother wrapper ── */
#smooth-wrapper {
overflow: hidden;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#smooth-content {
overflow: visible;
width: 100%;
}
@media (prefers-reduced-motion: reduce) {
#smooth-wrapper {
overflow: visible;
position: relative;
}
}