tenext redesign

This commit is contained in:
2026-08-03 14:42:01 +05:30
parent 1e3f225bed
commit 6625bb9018
78 changed files with 2798 additions and 859 deletions

View File

@@ -0,0 +1,64 @@
import { SectionContainer } from "@/components/shared/SectionContainer";
import { PageContainer } from "@/components/shared/PageContainer";
import { SectionHeading } from "@/components/shared/SectionHeading";
import { RevealOnView } from "@/components/shared/RevealOnView";
import { CardItem } from "@/data/site-pages";
interface AnchoredCardGridProps {
tag?: string;
title: string;
description?: string;
items: CardItem[];
/** Number of columns at lg breakpoint */
columns?: 2 | 3;
}
/**
* AnchoredCardGrid — data-driven grid of bordered cards.
*
* Each card carries an `id` so navbar hash links (e.g. /solutions#devops)
* scroll to it. Shared by the Solutions and Industries pages; composes the
* existing SectionContainer / PageContainer / SectionHeading primitives.
*/
export function AnchoredCardGrid({
tag,
title,
description,
items,
columns = 3,
}: AnchoredCardGridProps) {
const colClass = columns === 2 ? "md:grid-cols-2" : "md:grid-cols-2 lg:grid-cols-3";
return (
<SectionContainer paddingY="lg" className="bg-white px-[20px]">
<PageContainer>
<SectionHeading
tag={tag}
title={title}
description={description}
titleClassName="text-tenext-dark max-w-[720px]"
descriptionClassName="max-w-[620px]"
className="mb-[50px]"
/>
<div className={`grid grid-cols-1 gap-[20px] ${colClass}`}>
{items.map((item, index) => (
<RevealOnView key={item.id} delay={index * 90}>
<div
id={item.id}
className="flex h-full scroll-mt-[120px] flex-col gap-[15px] rounded-[25px] border border-tenext-card-border p-[40px] transition-transform duration-300 hover:-translate-y-2"
>
<h3 className="font-[var(--font-sora)] text-[22px] font-bold leading-[1.3] text-tenext-dark">
{item.title}
</h3>
<p className="text-[16px] leading-[1.7] text-tenext-text-muted">
{item.description}
</p>
</div>
</RevealOnView>
))}
</div>
</PageContainer>
</SectionContainer>
);
}

View File

@@ -2,6 +2,7 @@
import { type FormEvent, useState } from "react";
import { siteConfig } from "@/data/site-config";
import { ctaContent } from "@/data/site-content";
import { SectionHeading } from "@/components/shared/SectionHeading";
import { SocialIcons } from "@/components/shared/SocialIcons";
import { PageContainer } from "@/components/shared/PageContainer";
@@ -42,10 +43,10 @@ export function ContactFormSection() {
{/* ── Left: Info column ── */}
<div className="flex flex-col">
<SectionHeading
tag="get in touch"
title="We are always ready to help you and answer your questions"
tag={ctaContent.contact.tag}
title={ctaContent.contact.title}
titleClassName="text-white"
description="Pacific hake false trevally queen parrotfish black prickleback mosshead warbonnet sweeper! Greenling sleeper."
description={ctaContent.contact.description}
/>
{/* Contact details grid */}
@@ -62,12 +63,6 @@ export function ContactFormSection() {
>
{siteConfig.phone}
</a>
<a
href="tel:+11800234567"
className="text-[15px] text-tenext-text-muted transition-colors hover:text-tenext-teal"
>
+ (123) 1800-234-5678
</a>
</div>
</div>

View File

@@ -1,8 +1,8 @@
import Image from "next/image";
import Link from "next/link";
import { siteConfig } from "@/data/site-config";
import { SocialIcons } from "@/components/shared/SocialIcons";
import { PageContainer } from "@/components/shared/PageContainer";
import { SiteLogo } from "@/components/shared/SiteLogo";
/**
* Footer — shared site-wide footer.
@@ -23,14 +23,8 @@ export function Footer() {
<div className="grid grid-cols-1 gap-12 md:grid-cols-2 lg:grid-cols-[1.5fr_1fr_1fr]">
{/* Brand column */}
<div className="flex flex-col items-start gap-8">
<Link href="/" aria-label="Home">
<Image
src="/images/tenext/logo-dark.png"
alt={siteConfig.name}
width={146}
height={26}
className="h-auto w-auto brightness-0 invert"
/>
<Link href="/" aria-label={`${siteConfig.legalName} — Home`}>
<SiteLogo size={28} />
</Link>
<SocialIcons links={siteConfig.socials} />

View File

@@ -0,0 +1,79 @@
import {
Cloud,
Cpu,
Database,
Code2,
Smartphone,
Boxes,
GitBranch,
ShieldCheck,
LineChart,
Rocket,
Target,
Eye,
Compass,
Building2,
Landmark,
HeartPulse,
Factory,
GraduationCap,
ShoppingBag,
Layers,
Workflow,
Users,
Sparkles,
Handshake,
BadgeCheck,
Search,
PenTool,
Hammer,
RefreshCw,
TrendingUp,
Headset,
type LucideIcon,
} from "lucide-react";
const map: Record<string, LucideIcon> = {
cloud: Cloud,
cpu: Cpu,
database: Database,
code: Code2,
mobile: Smartphone,
boxes: Boxes,
devops: GitBranch,
shield: ShieldCheck,
chart: LineChart,
rocket: Rocket,
target: Target,
eye: Eye,
compass: Compass,
building: Building2,
landmark: Landmark,
health: HeartPulse,
factory: Factory,
education: GraduationCap,
retail: ShoppingBag,
layers: Layers,
workflow: Workflow,
users: Users,
sparkles: Sparkles,
handshake: Handshake,
badge: BadgeCheck,
discover: Search,
design: PenTool,
build: Hammer,
modernize: RefreshCw,
scale: TrendingUp,
support: Headset,
};
interface IconProps {
name: string;
className?: string;
}
/** Renders a named lucide icon. Keeps content data serializable (string keys). */
export function Icon({ name, className = "h-6 w-6" }: IconProps) {
const Cmp = map[name] ?? Sparkles;
return <Cmp className={className} strokeWidth={1.6} />;
}

View File

@@ -0,0 +1,42 @@
import { SectionContainer } from "@/components/shared/SectionContainer";
import { PageContainer } from "@/components/shared/PageContainer";
import { RevealOnView } from "@/components/shared/RevealOnView";
import { LegalSection, legalEffectiveDate } from "@/data/legal";
interface LegalContentProps {
sections: LegalSection[];
}
/**
* LegalContent — shared renderer for Privacy and Terms pages.
* Uses the site's spacing, container, typography, and reveal animation.
*/
export function LegalContent({ sections }: LegalContentProps) {
return (
<SectionContainer paddingY="lg" className="bg-white px-[20px]">
<PageContainer className="max-w-[820px]">
<p className="mb-[40px] text-[14px] font-semibold uppercase tracking-[0.1em] text-tenext-teal">
Effective {legalEffectiveDate}
</p>
<div className="flex flex-col gap-[40px]">
{sections.map((section, index) => (
<RevealOnView key={section.heading} delay={index * 60}>
<h2 className="mb-[16px] font-[var(--font-sora)] text-[24px] font-bold leading-[1.25] text-tenext-dark">
{section.heading}
</h2>
{section.body.map((paragraph, i) => (
<p
key={i}
className="mb-[14px] text-[16px] leading-[1.8] text-tenext-text-muted"
>
{paragraph}
</p>
))}
</RevealOnView>
))}
</div>
</PageContainer>
</SectionContainer>
);
}

View File

@@ -1,3 +1,5 @@
import { siteConfig } from "@/data/site-config";
interface BreadcrumbItem {
label: string;
href?: string;
@@ -23,8 +25,23 @@ interface PageTitleBannerProps {
* - Top-left and bottom-right rounded corner decorations
*/
export function PageTitleBanner({ title, breadcrumbs }: PageTitleBannerProps) {
const breadcrumbJsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: breadcrumbs.map((crumb, index) => ({
"@type": "ListItem",
position: index + 1,
name: crumb.label,
...(crumb.href ? { item: `${siteConfig.url}${crumb.href}` } : {}),
})),
};
return (
<div className="relative mx-5 overflow-hidden rounded-[25px] bg-tenext-dark">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
{/* Background image */}
<div
className="absolute inset-0 bg-cover bg-center opacity-80"

View File

@@ -1,18 +1,15 @@
"use client";
import { PageContainer } from "@/components/shared/PageContainer";
import { SectionContainer } from "@/components/shared/SectionContainer";
import { industries } from "@/data/site-pages";
/**
* Industries trust strip.
*
* Replaces the template's fabricated partner-logo carousel. Tenext does
* not publish client logos here, so this presents the real industries it
* serves. Swap in a genuine client-logo row once approved logos exist.
*/
export function PartnerLogosSection() {
const logos = [
"/images/tenext/partners-1.png",
"/images/tenext/partners-2.png",
"/images/tenext/partners-1.png",
"/images/tenext/partners-2.png",
"/images/tenext/partners-1.png",
"/images/tenext/partners-2.png",
];
return (
<SectionContainer
paddingY="lg"
@@ -28,25 +25,18 @@ export function PartnerLogosSection() {
lineHeight: 1.2,
}}
>
Our trusted collaborators in progress and success
Trusted to deliver across the industries we serve
</h3>
<div className="flex flex-wrap justify-center gap-8 md:gap-16 lg:gap-24 opacity-60">
{logos.map((src, index) => (
<div
key={index}
className="flex h-12 items-center justify-center grayscale transition-all hover:grayscale-0"
<div className="flex flex-wrap items-center justify-center gap-x-10 gap-y-6 md:gap-x-16">
{industries.map((industry) => (
<span
key={industry.id}
className="text-[18px] font-semibold text-white/70 transition-colors hover:text-tenext-teal md:text-[22px]"
style={{ fontFamily: "var(--font-sora)" }}
>
<img
src={src}
alt={`Partner ${index + 1}`}
className="h-full w-auto object-contain"
onError={(e) => {
(e.target as HTMLImageElement).src =
"/images/tenext/logo-dark.png";
}}
/>
</div>
{industry.title}
</span>
))}
</div>
</PageContainer>

View File

@@ -1,8 +1,9 @@
import { tenextButton as TenextButton } from "@/components/shared/AieroButton";
import { tenextButton as TenextButton } from "@/components/shared/Button";
import { PageContainer } from "@/components/shared/PageContainer";
import { ctaContent } from "@/data/site-content";
/**
* PreFooterCTA — "Elevate your business with our innovative solutions" banner.
* PreFooterCTA — closing conversion banner.
*
* Shared across: Home, About, Services, Contacts.
*
@@ -25,7 +26,7 @@ export function PreFooterCTA() {
lineHeight: 1.15,
}}
>
Elevate your business with our innovative solutions
{ctaContent.preFooter.heading}
</h2>
{/* CTA */}
@@ -34,7 +35,7 @@ export function PreFooterCTA() {
variant="simple"
className="shrink-0 text-[15px] font-semibold text-tenext-teal"
>
Get in touch
{ctaContent.preFooter.linkLabel}
</TenextButton>
</PageContainer>
</section>

View File

@@ -0,0 +1,24 @@
interface SiteLogoProps {
/** Extra classes (e.g. color overrides) */
className?: string;
/** Wordmark font-size in px — matches the slot it replaces */
size?: number;
}
/**
* SiteLogo — Tenext Technologies wordmark.
*
* Temporary text logo in the project's Sora typography, used until an
* official logo asset is supplied. Rendered on dark surfaces (navbar,
* footer), so it defaults to white with a teal accent dot.
*/
export function SiteLogo({ className = "", size = 22 }: SiteLogoProps) {
return (
<span
className={`font-bold leading-none tracking-[-0.02em] text-white ${className}`}
style={{ fontFamily: "var(--font-sora)", fontSize: `${size}px` }}
>
Tenext<span className="text-tenext-teal">.</span>
</span>
);
}