@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user