tenext redesign
This commit is contained in:
50
components/sections/BenefitsGridSection.tsx
Normal file
50
components/sections/BenefitsGridSection.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { RevealOnView } from "@/components/shared/RevealOnView";
|
||||
|
||||
export function BenefitsGridSection() {
|
||||
return (
|
||||
<section className="relative overflow-hidden bg-white px-[20px] pb-[60px] md:pb-[100px]">
|
||||
<div className="mx-auto max-w-[1260px]">
|
||||
<div className="grid grid-cols-1 gap-[30px] md:grid-cols-3">
|
||||
{/* Card 1 */}
|
||||
<RevealOnView className="flex flex-col gap-[20px] rounded-[25px] border border-tenext-card-border p-[40px] transition-transform duration-300 hover:-translate-y-2">
|
||||
<h4 className="text-[22px] font-bold leading-[1.3] text-tenext-dark font-[var(--font-sora)]">
|
||||
Predictable outcomes from your IT investments
|
||||
</h4>
|
||||
<ul className="flex flex-col gap-[15px] mt-[10px]">
|
||||
<li className="flex items-start gap-[10px] text-[16px] text-tenext-text-muted">
|
||||
<span className="mt-[5px] h-[6px] w-[6px] rounded-full bg-tenext-accent-teal shrink-0"></span>
|
||||
Lowest total cost of ownership
|
||||
</li>
|
||||
<li className="flex items-start gap-[10px] text-[16px] text-tenext-text-muted">
|
||||
<span className="mt-[5px] h-[6px] w-[6px] rounded-full bg-tenext-accent-teal shrink-0"></span>
|
||||
Accelerated time to value
|
||||
</li>
|
||||
</ul>
|
||||
</RevealOnView>
|
||||
|
||||
{/* Card 2 */}
|
||||
<RevealOnView delay={110} className="flex flex-col gap-[20px] rounded-[25px] border border-tenext-card-border p-[40px] transition-transform duration-300 hover:-translate-y-2">
|
||||
<h4 className="text-[22px] font-bold leading-[1.3] text-tenext-dark font-[var(--font-sora)]">
|
||||
Built to fit your existing business processes
|
||||
</h4>
|
||||
<p className="text-[16px] leading-[1.6] text-tenext-text-muted mt-[10px]">
|
||||
Seamless integration with existing systems and platforms, on a
|
||||
scalable and flexible architecture engineered for the long term.
|
||||
</p>
|
||||
</RevealOnView>
|
||||
|
||||
{/* Card 3 */}
|
||||
<RevealOnView delay={220} className="flex flex-col gap-[20px] rounded-[25px] border border-tenext-card-border bg-[#F8F9FA] p-[40px] transition-transform duration-300 hover:-translate-y-2">
|
||||
<h4 className="text-[22px] font-bold leading-[1.3] text-tenext-dark font-[var(--font-sora)]">
|
||||
Security and governance built in
|
||||
</h4>
|
||||
<p className="text-[16px] leading-[1.6] text-tenext-text-muted mt-[10px]">
|
||||
Access control, data governance, and compliance-ready handling are
|
||||
designed in from the first sprint — not retrofitted later.
|
||||
</p>
|
||||
</RevealOnView>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
75
components/sections/BlogSection.tsx
Normal file
75
components/sections/BlogSection.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import { SectionHeading } from "@/components/shared/SectionHeading";
|
||||
import { resources } from "@/data/site-pages";
|
||||
|
||||
/** Cover images, paired to the insights in data/site-pages.ts by order. */
|
||||
const coverImages = [
|
||||
"/images/tenext/portrait-confident-man.jpg",
|
||||
"/images/tenext/Group-18301.jpg",
|
||||
"/images/tenext/3d-woman-shape-glowing-with-bright-holographic-colors-1-min.png",
|
||||
];
|
||||
|
||||
const blogPosts = resources.map((resource, i) => ({
|
||||
title: resource.title,
|
||||
description: resource.description,
|
||||
image: coverImages[i % coverImages.length],
|
||||
category: resource.category,
|
||||
link: `/resources#${resource.id}`,
|
||||
}));
|
||||
|
||||
export function BlogSection() {
|
||||
return (
|
||||
<section className="bg-[#F8F9FA] px-[20px] py-[80px] md:py-[120px]">
|
||||
<div className="mx-auto max-w-[1260px]">
|
||||
<div className="mb-[60px] flex flex-col items-center text-center">
|
||||
<SectionHeading
|
||||
tag="insights"
|
||||
title="Perspectives on cloud, data, AI, and product engineering"
|
||||
titleClassName="max-w-[800px] text-tenext-dark"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-[30px] md:grid-cols-2 lg:grid-cols-3">
|
||||
{blogPosts.map((post, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="group flex flex-col overflow-hidden rounded-[25px] border border-tenext-card-border bg-white transition-shadow duration-300 hover:shadow-xl"
|
||||
>
|
||||
<div className="relative h-[250px] w-full overflow-hidden">
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute left-[20px] top-[20px] rounded-full bg-white px-[15px] py-[5px] text-[12px] font-bold tracking-wide text-tenext-dark uppercase font-[var(--font-sora)]">
|
||||
{post.category}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col p-[30px]">
|
||||
<h4 className="mb-[12px] text-[22px] font-bold leading-[1.3] text-tenext-dark font-[var(--font-sora)] transition-colors group-hover:text-tenext-accent-teal">
|
||||
<Link href={post.link}>{post.title}</Link>
|
||||
</h4>
|
||||
<p className="mb-[20px] text-[15px] leading-[1.6] text-tenext-text-muted">
|
||||
{post.description}
|
||||
</p>
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
href={post.link}
|
||||
className="inline-flex items-center gap-[10px] text-[15px] font-bold text-tenext-dark font-[var(--font-sora)] hover:text-tenext-accent-teal"
|
||||
>
|
||||
Learn more
|
||||
<ArrowUpRight className="h-[18px] w-[18px]" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
115
components/sections/CardsGrid.tsx
Normal file
115
components/sections/CardsGrid.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import Image from "next/image";
|
||||
import { CardHeading, IconButton } from "@/components/shared/CardShared";
|
||||
import { ChartIcon } from "@/components/shared/icons/ChartIcon";
|
||||
import { RevealOnView } from "@/components/shared/RevealOnView";
|
||||
import { valueProps } from "@/data/site-content";
|
||||
|
||||
export function CardsGrid() {
|
||||
return (
|
||||
<section className="px-[20px] pb-[20px]">
|
||||
<div className="grid grid-cols-1 gap-[20px] md:grid-cols-2 xl:grid-cols-[1fr_1fr_0.73fr]">
|
||||
{/* Card 1 */}
|
||||
<RevealOnView delay={0}>
|
||||
<div className="tenext-card group relative flex h-full flex-col justify-between overflow-hidden rounded-[25px] bg-tenext-card-purple p-[40px_35px_45px_35px] lg:p-[50px_45px_50px_45px]">
|
||||
<Image
|
||||
src="/images/tenext/assistant-artificial-intelligence-min.png"
|
||||
alt=""
|
||||
className="pointer-events-none absolute right-[-10px] top-[-10px] h-auto w-[150px] object-contain object-right-top transition-transform duration-500 group-hover:scale-105 lg:w-[190px]"
|
||||
width={220}
|
||||
height={274}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 flex gap-[6px]">
|
||||
<span className="rounded-[15px] border border-white px-[20px] py-[5px] text-[12px] text-white transition-colors hover:border-tenext-teal hover:text-tenext-teal">
|
||||
Engineering
|
||||
</span>
|
||||
<span className="rounded-[15px] border border-white px-[20px] py-[5px] text-[12px] text-white transition-colors hover:border-tenext-teal hover:text-tenext-teal">
|
||||
Cloud
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 mt-[66px] max-w-[460px] lg:mt-[110px]">
|
||||
<CardHeading
|
||||
title="Build and modernize digital products with full-stack engineering expertise"
|
||||
titleClassName="text-white"
|
||||
wrapperClassName="mb-0"
|
||||
/>
|
||||
|
||||
<p className="mt-[24px] max-w-[420px] text-[16px] leading-[1.7] text-white/80">
|
||||
Architecture, development, and modernization of enterprise
|
||||
applications — engineered to scale with your business.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<IconButton />
|
||||
</div>
|
||||
</RevealOnView>
|
||||
|
||||
{/* Card 2 */}
|
||||
<RevealOnView delay={110}>
|
||||
<div className="tenext-card group relative flex h-full flex-col justify-between overflow-hidden rounded-[25px] bg-tenext-card-purple-2 p-[40px_35px_45px_35px] lg:p-[50px_45px_50px_45px]">
|
||||
<div className="absolute inset-0 bg-tenext-card-purple-2" />
|
||||
<div className="absolute inset-0 bg-[url('/images/tenext/c0eb2845-min.png')] bg-[length:60%] bg-right-bottom bg-no-repeat transition-transform duration-500 group-hover:scale-105" />
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-[#454f88]/16" />
|
||||
|
||||
<div className="relative z-10 mb-[54px] max-w-[515px]">
|
||||
<div className="mb-[24px] inline-flex h-[38px] w-[38px] items-center justify-center">
|
||||
<ChartIcon className="h-full w-full text-white" />
|
||||
</div>
|
||||
|
||||
<CardHeading
|
||||
title="Unified cloud and data platforms for smarter enterprise decisions."
|
||||
titleClassName="text-white"
|
||||
wrapperClassName="mb-[18px]"
|
||||
/>
|
||||
|
||||
<p className="max-w-[448px] text-[16px] leading-[1.85] text-white">
|
||||
Consolidate data across systems with secure, scalable platforms
|
||||
that turn day-to-day operations into actionable insight.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<IconButton />
|
||||
</div>
|
||||
</RevealOnView>
|
||||
|
||||
{/* Card 3 (spans 2 columns) */}
|
||||
<RevealOnView className="md:col-span-2 xl:col-span-1" delay={220}>
|
||||
<div className="flex h-full flex-col gap-[20px]">
|
||||
{/* Card 3 - Top half */}
|
||||
<div className="tenext-card relative flex min-h-[176px] flex-col justify-center overflow-hidden rounded-[25px] bg-tenext-card-lavender p-[40px] lg:p-[45px_50px]">
|
||||
<div className="relative z-10 flex flex-col items-start">
|
||||
<h4 className="font-[var(--font-sora)] text-[24px] font-bold leading-[1.25] text-white">
|
||||
{valueProps.reliability.title}
|
||||
</h4>
|
||||
<p className="mt-[10px] text-[15px] leading-[1.6] text-white/85">
|
||||
{valueProps.reliability.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 3 - Bottom half */}
|
||||
<div className="tenext-card group relative flex flex-col justify-between overflow-hidden rounded-[25px] bg-tenext-card-dark p-[40px_35px_45px_35px] lg:p-[50px_45px_45px_45px]">
|
||||
<div className="absolute inset-0 bg-[url('/images/tenext/Layer_2-min.jpg')] bg-cover bg-center transition-all duration-500 group-hover:scale-105" />
|
||||
|
||||
<div className="relative z-10 mb-[26px]">
|
||||
<CardHeading
|
||||
title="Apply AI and data science to automate and augment your operations"
|
||||
titleClassName="text-white max-w-[80%]"
|
||||
wrapperClassName="mb-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10">
|
||||
<p className="max-w-[360px] text-[16px] leading-[1.7] text-white/80">
|
||||
Data platforms and applied AI that turn enterprise data into
|
||||
decision-ready insight.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RevealOnView>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
132
components/sections/FeaturesSection.tsx
Normal file
132
components/sections/FeaturesSection.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import { CardHeading, IconButton } from "@/components/shared/CardShared";
|
||||
|
||||
export function FeaturesSection() {
|
||||
return (
|
||||
<section className="bg-white px-[20px] pb-[100px] pt-[150px]">
|
||||
<div className="mx-auto max-w-[1260px]">
|
||||
{/* Top text and separator */}
|
||||
<div className="mb-[70px] pr-[10%]">
|
||||
<div className="mb-[20px] h-px w-[130px] bg-tenext-card-border" />
|
||||
<h2
|
||||
className="max-w-[700px] tracking-[-0.03em] text-tenext-dark"
|
||||
style={{
|
||||
fontFamily: "var(--font-sora)",
|
||||
fontSize: "clamp(32px, 4vw, 56px)",
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.1,
|
||||
}}
|
||||
>
|
||||
Engineered for outcomes, built for scale
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Feature Cards Grid */}
|
||||
<div className="grid grid-cols-1 gap-[20px] lg:grid-cols-2">
|
||||
{/* Card 1 - Left */}
|
||||
<div className="relative flex flex-col justify-between overflow-hidden rounded-[25px] border border-tenext-card-border bg-tenext-card-dark p-[40px_35px_45px_35px] lg:p-[22%_9%_20%_9%]">
|
||||
<div
|
||||
className="absolute inset-0 bg-[url('/images/tenext/home-9-img.png')] bg-bottom bg-no-repeat transition-all duration-300 hover:scale-105"
|
||||
style={{ backgroundSize: "100% auto" }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#0D0D0D99] to-transparent to-[20%]" />
|
||||
|
||||
<div className="relative z-10 mt-auto">
|
||||
<CardHeading
|
||||
title="Integrate data across your enterprise to uncover deep insight"
|
||||
titleClassName="text-white"
|
||||
/>
|
||||
|
||||
<ul className="mt-[20px] flex flex-col gap-3">
|
||||
<li className="flex items-center gap-3 text-tenext-text-muted">
|
||||
<span className="flex h-1 w-1 rounded-full bg-tenext-text-muted" />
|
||||
Advanced analytics processing
|
||||
</li>
|
||||
<li className="flex items-center gap-3 text-tenext-text-muted">
|
||||
<span className="flex h-1 w-1 rounded-full bg-tenext-text-muted" />
|
||||
Real-time data synchronization
|
||||
</li>
|
||||
<li className="flex items-center gap-3 text-tenext-text-muted">
|
||||
<span className="flex h-1 w-1 rounded-full bg-tenext-text-muted" />
|
||||
Secure API endpoints
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<IconButton />
|
||||
</div>
|
||||
|
||||
{/* Card 2 - Right */}
|
||||
<div className="relative flex flex-col justify-between overflow-hidden rounded-[25px] border border-tenext-card-border bg-tenext-card-dark p-[40px_35px_45px_35px] lg:p-[10%_9%_11%_9%]">
|
||||
<div className="absolute inset-0 bg-[url('/images/tenext/Dots.png')] bg-[length:auto] bg-[50%_20px] bg-no-repeat opacity-20" />
|
||||
|
||||
<div className="relative z-10 flex h-[42px] w-[42px] items-center justify-center text-white">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-full w-full"
|
||||
>
|
||||
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 mt-[60px] lg:mt-[100px]">
|
||||
<CardHeading
|
||||
title="Data privacy and security with robust measures"
|
||||
titleClassName="text-white"
|
||||
/>
|
||||
<p className="text-[16px] text-tenext-text-muted">
|
||||
Enterprise-grade security and governance built into every
|
||||
engagement — from secure architecture and access controls to
|
||||
compliance-ready data handling.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<IconButton />
|
||||
</div>
|
||||
|
||||
{/* Card 3 - Bottom Full Width */}
|
||||
<div className="relative col-span-1 overflow-hidden rounded-[25px] border border-tenext-card-border bg-tenext-card-dark lg:col-span-2">
|
||||
<div
|
||||
className="absolute inset-0 bg-[url('/images/tenext/3d-woman-shape-glowing-with-bright-holographic-colors-1-min.png')] bg-right-bottom bg-no-repeat transition-all duration-300 hover:scale-105"
|
||||
style={{ backgroundSize: "83% auto" }}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 grid grid-cols-1 gap-[40px] p-[40px_35px] lg:grid-cols-2 lg:p-[80px_60px]">
|
||||
<div>
|
||||
<CardHeading
|
||||
subtitle="Technology"
|
||||
title="Tailored solutions specifically to your needs"
|
||||
titleClassName="text-white"
|
||||
subtitleClassName="text-tenext-teal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-[30px] lg:pl-[15%]">
|
||||
<div>
|
||||
<h4 className="text-[20px] font-bold text-white font-[var(--font-sora)]">
|
||||
Engineering ownership
|
||||
</h4>
|
||||
<p className="mt-[8px] text-[15px] leading-[1.7] text-tenext-text-muted">
|
||||
The team that designs a system builds and supports it.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-[20px] font-bold text-white font-[var(--font-sora)]">
|
||||
Support beyond go-live
|
||||
</h4>
|
||||
<p className="mt-[8px] text-[15px] leading-[1.7] text-tenext-text-muted">
|
||||
Monitoring, optimization, and managed support after launch.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
214
components/sections/HeroImageScene.tsx
Normal file
214
components/sections/HeroImageScene.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Mesh, Plane, Program, Renderer, Texture } from "ogl";
|
||||
|
||||
const vertex = `
|
||||
attribute vec2 uv;
|
||||
attribute vec2 position;
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const fragment = `
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D tMap;
|
||||
uniform vec2 uResolution;
|
||||
uniform vec2 uImageResolution;
|
||||
uniform vec2 uPointer;
|
||||
uniform vec2 uVelocity;
|
||||
uniform float uStrength;
|
||||
varying vec2 vUv;
|
||||
|
||||
vec2 coverUv(vec2 uv, vec2 screen, vec2 image) {
|
||||
float screenRatio = screen.x / screen.y;
|
||||
float imageRatio = image.x / image.y;
|
||||
vec2 scale = vec2(1.0);
|
||||
|
||||
if (screenRatio > imageRatio) {
|
||||
scale.y = imageRatio / screenRatio;
|
||||
} else {
|
||||
scale.x = screenRatio / imageRatio;
|
||||
}
|
||||
|
||||
return (uv - 0.5) * scale + 0.5;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = coverUv(vUv, uResolution, uImageResolution);
|
||||
|
||||
vec2 pointerDelta = uv - uPointer;
|
||||
float distanceFromPointer = length(pointerDelta);
|
||||
float cursorMask = smoothstep(0.36, 0.0, distanceFromPointer);
|
||||
float innerMask = smoothstep(0.16, 0.0, distanceFromPointer);
|
||||
vec2 pull = normalize(pointerDelta + 0.0001) * innerMask * 0.0035;
|
||||
vec2 distortion = ((uVelocity * 0.075) - pull) * cursorMask * uStrength;
|
||||
|
||||
uv += distortion;
|
||||
|
||||
float red = texture2D(tMap, uv + distortion * 0.12).r;
|
||||
float green = texture2D(tMap, uv).g;
|
||||
float blue = texture2D(tMap, uv - distortion * 0.1).b;
|
||||
gl_FragColor = vec4(red, green, blue, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
interface HeroImageSceneProps {
|
||||
imageSrc: string;
|
||||
}
|
||||
|
||||
export function HeroImageScene({ imageSrc }: HeroImageSceneProps) {
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const pointerRef = useRef({ x: 0.5, y: 0.5 });
|
||||
const velocityRef = useRef({ x: 0, y: 0 });
|
||||
const strengthRef = useRef(0);
|
||||
const lastMoveTimeRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
const root = rootRef.current;
|
||||
if (!root) return;
|
||||
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
if (reduceMotion.matches) return;
|
||||
|
||||
const renderer = new Renderer({
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
dpr: Math.min(window.devicePixelRatio, 2),
|
||||
});
|
||||
const gl = renderer.gl;
|
||||
root.appendChild(gl.canvas);
|
||||
|
||||
gl.canvas.className = "absolute inset-0 h-full w-full";
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
|
||||
const texture = new Texture(gl, {
|
||||
generateMipmaps: false,
|
||||
minFilter: gl.LINEAR,
|
||||
magFilter: gl.LINEAR,
|
||||
});
|
||||
|
||||
const image = new Image();
|
||||
image.src = imageSrc;
|
||||
image.onload = () => {
|
||||
texture.image = image;
|
||||
texture.needsUpdate = true;
|
||||
};
|
||||
|
||||
const program = new Program(gl, {
|
||||
vertex,
|
||||
fragment,
|
||||
uniforms: {
|
||||
tMap: { value: texture },
|
||||
uResolution: { value: [1, 1] },
|
||||
uImageResolution: { value: [1883, 855] },
|
||||
uPointer: { value: [0.5, 0.5] },
|
||||
uVelocity: { value: [0, 0] },
|
||||
uStrength: { value: 0 },
|
||||
},
|
||||
transparent: true,
|
||||
});
|
||||
|
||||
const mesh = new Mesh(gl, {
|
||||
geometry: new Plane(gl, { width: 2, height: 2 }),
|
||||
program,
|
||||
});
|
||||
|
||||
let animationFrame = 0;
|
||||
let width = 1;
|
||||
let height = 1;
|
||||
|
||||
const resize = () => {
|
||||
const rect = root.getBoundingClientRect();
|
||||
width = Math.max(1, rect.width);
|
||||
height = Math.max(1, rect.height);
|
||||
renderer.setSize(width, height);
|
||||
program.uniforms.uResolution.value = [width, height];
|
||||
};
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
const rect = root.getBoundingClientRect();
|
||||
const isInside =
|
||||
event.clientX >= rect.left &&
|
||||
event.clientX <= rect.right &&
|
||||
event.clientY >= rect.top &&
|
||||
event.clientY <= rect.bottom;
|
||||
|
||||
if (!isInside) {
|
||||
velocityRef.current = { x: 0, y: 0 };
|
||||
return;
|
||||
}
|
||||
|
||||
const nextPointer = {
|
||||
x: (event.clientX - rect.left) / rect.width,
|
||||
y: 1 - (event.clientY - rect.top) / rect.height,
|
||||
};
|
||||
const now = performance.now();
|
||||
const elapsed = Math.max(16, now - lastMoveTimeRef.current);
|
||||
lastMoveTimeRef.current = now;
|
||||
velocityRef.current = {
|
||||
x: ((nextPointer.x - pointerRef.current.x) / elapsed) * 16.67,
|
||||
y: ((nextPointer.y - pointerRef.current.y) / elapsed) * 16.67,
|
||||
};
|
||||
pointerRef.current = nextPointer;
|
||||
strengthRef.current = Math.min(1, strengthRef.current + 0.72);
|
||||
};
|
||||
|
||||
const onPointerLeave = () => {
|
||||
velocityRef.current = { x: 0, y: 0 };
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
strengthRef.current *= 0.86;
|
||||
velocityRef.current = {
|
||||
x: velocityRef.current.x * 0.74,
|
||||
y: velocityRef.current.y * 0.74,
|
||||
};
|
||||
|
||||
program.uniforms.uPointer.value = [
|
||||
pointerRef.current.x,
|
||||
pointerRef.current.y,
|
||||
];
|
||||
program.uniforms.uVelocity.value = [
|
||||
velocityRef.current.x,
|
||||
velocityRef.current.y,
|
||||
];
|
||||
program.uniforms.uStrength.value = strengthRef.current;
|
||||
renderer.render({ scene: mesh });
|
||||
animationFrame = requestAnimationFrame(render);
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver(resize);
|
||||
observer.observe(root);
|
||||
window.addEventListener("pointermove", onPointerMove);
|
||||
root.addEventListener("pointerleave", onPointerLeave);
|
||||
|
||||
resize();
|
||||
animationFrame = requestAnimationFrame(render);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
observer.disconnect();
|
||||
window.removeEventListener("pointermove", onPointerMove);
|
||||
root.removeEventListener("pointerleave", onPointerLeave);
|
||||
gl.canvas.remove();
|
||||
};
|
||||
}, [imageSrc]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={rootRef}
|
||||
aria-hidden="true"
|
||||
className="hero-ogl-scene absolute inset-[-18px]"
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center"
|
||||
style={{ backgroundImage: `url(${imageSrc})` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
81
components/sections/HeroSection.tsx
Normal file
81
components/sections/HeroSection.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { HeroImageScene } from "@/components/sections/HeroImageScene";
|
||||
import { ScrollDown } from "@/components/shared/ScrollDown";
|
||||
|
||||
export function HeroSection() {
|
||||
return (
|
||||
<section className="relative px-5 py-5">
|
||||
{/* Scroll Down button — vertical, left side */}
|
||||
<div className="scroll-widget absolute left-0 top-[40%] z-20 hidden xl:block">
|
||||
<ScrollDown />
|
||||
</div>
|
||||
|
||||
{/* Hero container with background */}
|
||||
<div
|
||||
className="relative overflow-hidden rounded-[25px] bg-tenext-dark"
|
||||
style={{
|
||||
minHeight: "min(858px, calc(100vw * 0.447))",
|
||||
padding: "14.3% 0 8.1% 0",
|
||||
}}
|
||||
>
|
||||
<HeroImageScene imageSrc="/images/tenext/home-9-top-bg-min.jpg" />
|
||||
|
||||
{/* Hero content */}
|
||||
<div className="relative z-10 w-full px-[12.7vw]">
|
||||
<div className="flex flex-col items-start">
|
||||
{/* Heading */}
|
||||
<h1
|
||||
className="hero-reveal mb-[24px] max-w-[1280px] text-left tracking-[-0.03em] text-white"
|
||||
style={{
|
||||
fontFamily: "var(--font-manrope)",
|
||||
fontSize: "clamp(42px, 4.43vw, 85px)",
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.06,
|
||||
}}
|
||||
>
|
||||
Engineering predictable outcomes from your digital investments
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<div
|
||||
className="hero-reveal max-w-[660px]"
|
||||
style={{ animationDelay: "760ms" }}
|
||||
>
|
||||
<p className="text-[16px] font-semibold leading-[1.9] text-white">
|
||||
Tenext helps enterprises transform with cloud, data, AI, and
|
||||
product engineering — built to lower total cost of ownership and
|
||||
accelerate time to value.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<div
|
||||
className="hero-reveal mt-[42px]"
|
||||
style={{ animationDelay: "940ms" }}
|
||||
>
|
||||
<a
|
||||
href="/contacts"
|
||||
className="tenext-button tenext-button-solid inline-flex items-center gap-2 rounded-[25px] border border-tenext-red bg-tenext-red px-[30px] py-[14px] text-[16px] text-white transition-all hover:-translate-y-0.5 hover:border-tenext-red-hover hover:bg-tenext-red-hover hover:shadow-[0_15px_30px_rgba(239,100,100,0.28)]"
|
||||
style={{ minWidth: "216px", fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
Talk to an expert
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" fill="none">
|
||||
<path
|
||||
d="M1 11L11 1M11 1H3M11 1V9"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span className="button-inner" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scroll anchor */}
|
||||
<div id="scroll-down" className="absolute bottom-0" />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
70
components/sections/HowItWorksSection.tsx
Normal file
70
components/sections/HowItWorksSection.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { SectionHeading } from "@/components/shared/SectionHeading";
|
||||
import { RevealOnView } from "@/components/shared/RevealOnView";
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||
|
||||
export function HowItWorksSection() {
|
||||
const steps = [
|
||||
{
|
||||
number: "01",
|
||||
title: "Discover & Diagnose",
|
||||
description:
|
||||
"We assess your systems, processes, and goals to identify where technology can deliver the highest business value.",
|
||||
},
|
||||
{
|
||||
number: "02",
|
||||
title: "Design",
|
||||
description:
|
||||
"We architect a focused solution that fits your existing landscape, balancing cost, scale, and time to value.",
|
||||
},
|
||||
{
|
||||
number: "03",
|
||||
title: "Build & Integrate",
|
||||
description:
|
||||
"Our engineering teams build, integrate, and test — connecting new capabilities to the systems you already run.",
|
||||
},
|
||||
{
|
||||
number: "04",
|
||||
title: "Deploy & Optimize",
|
||||
description:
|
||||
"We deploy with confidence and keep improving through monitoring, support, and continuous optimization.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="bg-white px-[20px] py-[60px] md:py-[100px]">
|
||||
<div className="mx-auto max-w-[1260px]">
|
||||
<div className="flex flex-col md:flex-row md:items-end justify-between gap-[30px] mb-[60px]">
|
||||
<SectionHeading
|
||||
tag="how it works"
|
||||
title="A transparent path from idea to measurable impact"
|
||||
titleClassName="max-w-[700px] text-tenext-dark"
|
||||
/>
|
||||
<div className="flex gap-[15px]">
|
||||
<button className="flex h-[50px] w-[50px] items-center justify-center rounded-full border border-tenext-card-border text-tenext-dark transition-colors hover:bg-tenext-card-border">
|
||||
<ArrowLeft size={20} />
|
||||
</button>
|
||||
<button className="flex h-[50px] w-[50px] items-center justify-center rounded-full border border-tenext-card-border text-tenext-dark transition-colors hover:bg-tenext-card-border">
|
||||
<ArrowRight size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
{steps.map((step, index) => (
|
||||
<RevealOnView key={index} delay={index * 80} className="flex flex-col">
|
||||
<div className="flex flex-wrap items-center gap-[15px] rounded-[15px] bg-gradient-to-r from-tenext-blue to-tenext-purple-start px-[25px] py-[14px] text-white">
|
||||
<span className="text-[14px] font-bold">{step.number}</span>
|
||||
<span className="text-[18px] font-bold font-[var(--font-sora)]">
|
||||
{step.title}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-[20px] mb-[25px] max-w-[720px] text-[16px] leading-[1.6] text-tenext-text-muted">
|
||||
{step.description}
|
||||
</p>
|
||||
</RevealOnView>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
39
components/sections/MarqueeStrip.tsx
Normal file
39
components/sections/MarqueeStrip.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
interface MarqueeStripProps {
|
||||
text?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function MarqueeStrip({
|
||||
text = "Artificial Intelligence",
|
||||
className = "",
|
||||
}: MarqueeStripProps) {
|
||||
const marqueeItems = Array.from({ length: 6 });
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative z-10 mx-[-5px] mb-[-20px] mt-0 overflow-hidden bg-gradient-to-r from-tenext-teal to-tenext-blue py-[42px] ${className}`}
|
||||
style={{ transform: "rotate(-2.07deg)" }}
|
||||
>
|
||||
<div className="marquee-track flex w-max whitespace-nowrap">
|
||||
{[0, 1].map((track) => (
|
||||
<div key={track} className="flex shrink-0 items-center">
|
||||
{marqueeItems.map((_, i) => (
|
||||
<div
|
||||
key={`${track}-${i}`}
|
||||
className="flex items-center gap-8 pr-8"
|
||||
>
|
||||
<span
|
||||
className="marquee-text text-white"
|
||||
style={{ fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
<span className="flex h-3 w-3 rounded-full bg-white opacity-50" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
components/sections/MetricsSection.tsx
Normal file
34
components/sections/MetricsSection.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { RevealOnView } from "@/components/shared/RevealOnView";
|
||||
import { differentiators } from "@/data/site-content";
|
||||
|
||||
export function MetricsSection() {
|
||||
// Show the first four differentiators as a proof grid — real commitments
|
||||
// in place of the template's unverified statistics.
|
||||
const points = differentiators.slice(0, 4);
|
||||
|
||||
return (
|
||||
<section className="bg-white px-[20px] py-[60px] md:py-[100px]">
|
||||
<div className="mx-auto max-w-[1260px]">
|
||||
<div className="grid grid-cols-1 gap-[50px] sm:grid-cols-2 md:gap-[60px]">
|
||||
{points.map((point, index) => (
|
||||
<RevealOnView
|
||||
key={point.id}
|
||||
delay={index * 90}
|
||||
className="flex flex-col gap-[14px]"
|
||||
>
|
||||
<span className="gradient-text-purple text-[22px] font-bold font-[var(--font-sora)]">
|
||||
0{index + 1}
|
||||
</span>
|
||||
<h3 className="text-[24px] font-bold leading-[1.3] text-tenext-dark font-[var(--font-sora)]">
|
||||
{point.title}
|
||||
</h3>
|
||||
<p className="text-[16px] leading-[1.8] text-tenext-text-muted">
|
||||
{point.description}
|
||||
</p>
|
||||
</RevealOnView>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
175
components/sections/Navbar.tsx
Normal file
175
components/sections/Navbar.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { navigationData } from "@/data/navigation";
|
||||
import { SearchIcon } from "@/components/shared/icons/SearchIcon";
|
||||
import { SiteLogo } from "@/components/shared/SiteLogo";
|
||||
|
||||
interface NavbarProps {
|
||||
/**
|
||||
* "overlay" — transparent, sits over a dark hero (home page).
|
||||
* "solid" — dark background, for inner pages that open on a light
|
||||
* surface so the white logo and links stay legible.
|
||||
*/
|
||||
variant?: "overlay" | "solid";
|
||||
}
|
||||
|
||||
export function Navbar({ variant = "solid" }: NavbarProps) {
|
||||
const [isSticky, setIsSticky] = useState(false);
|
||||
const [openDropdown, setOpenDropdown] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setIsSticky(window.scrollY > 120);
|
||||
|
||||
onScroll();
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => window.removeEventListener("scroll", onScroll);
|
||||
}, []);
|
||||
|
||||
const idleBg = variant === "overlay" ? "bg-transparent" : "bg-tenext-dark";
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`tenext-navbar z-30 mx-auto grid h-[100px] max-w-[2520px] grid-cols-[auto_1fr_auto] items-center px-[34px] transition-[background-color,border-radius,box-shadow,transform] duration-500 ${
|
||||
isSticky
|
||||
? "fixed left-5 right-5 top-0 rounded-b-[25px] bg-[#111111] shadow-[0_12px_35px_rgba(0,0,0,0.18)]"
|
||||
: `relative ${idleBg}`
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-[24px] xl:gap-[64px]">
|
||||
<button
|
||||
className="navbar-reveal grid h-6 w-6 grid-cols-2 gap-[5px] text-white transition-colors hover:text-tenext-teal"
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<span className="h-[7px] w-[7px] rounded-[2px] border-2 border-current" />
|
||||
<span className="h-[7px] w-[7px] rounded-[2px] border-2 border-current" />
|
||||
<span className="h-[7px] w-[7px] rounded-[2px] border-2 border-current" />
|
||||
<span className="h-[7px] w-[7px] rounded-[2px] border-2 border-current" />
|
||||
</button>
|
||||
|
||||
{/* Logo */}
|
||||
<a
|
||||
href="/"
|
||||
className="navbar-reveal flex items-center"
|
||||
style={{ animationDelay: "120ms" }}
|
||||
aria-label="Tenext Technologies — Home"
|
||||
>
|
||||
<SiteLogo size={24} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav
|
||||
className="hidden items-center justify-center gap-[4px] lg:flex xl:gap-[11px]"
|
||||
aria-label="Primary navigation"
|
||||
>
|
||||
{navigationData.map((item, index) => (
|
||||
<div
|
||||
key={item.label}
|
||||
className="navbar-reveal relative"
|
||||
style={{ animationDelay: `${140 + index * 70}ms` }}
|
||||
onMouseEnter={() => setOpenDropdown(item.label)}
|
||||
onMouseLeave={() => setOpenDropdown(null)}
|
||||
>
|
||||
<a
|
||||
href={item.href}
|
||||
className={`nav-link-swap flex h-10 items-center whitespace-nowrap rounded-[10px] px-[12px] text-[13px] font-bold uppercase leading-none text-white transition-colors hover:bg-tenext-dark xl:px-[21px] ${
|
||||
item.label === "Home" ? "bg-tenext-dark" : ""
|
||||
}`}
|
||||
style={{ fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
<span className="text-active">{item.label}</span>
|
||||
<span aria-hidden>{item.label}</span>
|
||||
{item.children && (
|
||||
<svg
|
||||
className="ml-[12px] h-[5px] w-[5px] opacity-90"
|
||||
viewBox="0 0 5 5"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M4.35 0.9L3.75 4.05L0.65 3.45"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</a>
|
||||
|
||||
{/* Dropdown */}
|
||||
{item.children && openDropdown === item.label && (
|
||||
<div className="absolute left-0 top-full z-50 min-w-[286px] origin-top rounded-[20px] border border-tenext-card-border bg-[#111111] px-[45px] py-[30px] shadow-2xl">
|
||||
{item.children.map((child, childIndex) => (
|
||||
<a
|
||||
key={child.label}
|
||||
href={child.href}
|
||||
className="dropdown-reveal block py-[10px] pr-5 text-[14px] font-medium text-white/80 transition-all hover:pl-5 hover:text-tenext-teal"
|
||||
style={{
|
||||
animationDelay: `${childIndex * 45}ms`,
|
||||
fontFamily: "var(--font-sora)",
|
||||
}}
|
||||
>
|
||||
{child.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Right Icons */}
|
||||
<div
|
||||
className="navbar-reveal flex items-center justify-end gap-[10px]"
|
||||
style={{ animationDelay: "620ms" }}
|
||||
>
|
||||
<button
|
||||
className="hidden p-[8px] text-white transition-colors hover:text-tenext-teal md:block"
|
||||
aria-label="Search"
|
||||
>
|
||||
<SearchIcon className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
className="hidden items-center gap-[8px] p-[8px] text-[13px] font-bold uppercase leading-none text-white transition-colors hover:text-tenext-teal md:flex"
|
||||
style={{ fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
<svg className="h-4 w-4" viewBox="0 0 16 16" fill="none">
|
||||
<circle
|
||||
cx="8"
|
||||
cy="5"
|
||||
r="3.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.3"
|
||||
/>
|
||||
<path
|
||||
d="M2 14.5C2 11.5 4.5 9.5 8 9.5C11.5 9.5 14 11.5 14 14.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.3"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
Log in
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/contacts"
|
||||
className="tenext-button tenext-button-gradient-border min-w-[188px] justify-center rounded-full px-[24px] py-[21px] text-[16px] font-semibold leading-none text-white"
|
||||
style={{ fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
Talk to an expert
|
||||
<span className="button-inner" />
|
||||
</a>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button className="flex flex-col gap-[5px] lg:hidden" aria-label="Menu">
|
||||
<span className="h-[2px] w-[22px] bg-white" />
|
||||
<span className="h-[2px] w-[22px] bg-white" />
|
||||
<span className="h-[2px] w-[22px] bg-white" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
95
components/sections/PossibilitiesSection.tsx
Normal file
95
components/sections/PossibilitiesSection.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { tenextButton as TenextButton } from "@/components/shared/Button";
|
||||
import { IconBox } from "@/components/shared/IconBox";
|
||||
import { valueProps } from "@/data/site-content";
|
||||
import {
|
||||
OrbitIcon,
|
||||
FanIcon,
|
||||
CirclesIcon,
|
||||
ExpandIcon,
|
||||
} from "@/components/shared/icons/PossibilityIcons";
|
||||
|
||||
export function PossibilitiesSection() {
|
||||
return (
|
||||
<section className="px-[20px] py-[60px] md:py-[100px]">
|
||||
<div className="mx-auto grid max-w-[1260px] grid-cols-1 gap-[50px] lg:grid-cols-[1fr_1fr]">
|
||||
{/* Left Column */}
|
||||
<div className="flex flex-col items-start pr-[10%] pt-[20px]">
|
||||
<span
|
||||
className="tenext-subheading mb-[16px] text-tenext-dark uppercase tracking-[0.1em]"
|
||||
style={{ fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
[ what we do ]
|
||||
</span>
|
||||
|
||||
<h2
|
||||
className="mb-[24px] max-w-[500px] tracking-[-0.03em] text-tenext-dark"
|
||||
style={{
|
||||
fontFamily: "var(--font-dm-sans)",
|
||||
fontSize: "clamp(32px, 4vw, 56px)",
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.1,
|
||||
}}
|
||||
>
|
||||
Services that move the business, not just the technology
|
||||
</h2>
|
||||
|
||||
<p className="mb-[40px] max-w-[480px] text-[16px] leading-[1.8] text-tenext-text-muted">
|
||||
Tenext Technologies partners with enterprises to engineer, migrate,
|
||||
and scale digital platforms. We integrate solutions with your
|
||||
existing systems and processes to deliver predictable outcomes,
|
||||
lower total cost of ownership, and accelerate time to value.
|
||||
</p>
|
||||
|
||||
<TenextButton
|
||||
variant="gradient-border"
|
||||
className="rounded-[25px] px-[35px] py-[15px] text-[15px] font-medium"
|
||||
showArrow={true}
|
||||
>
|
||||
Explore more
|
||||
</TenextButton>
|
||||
|
||||
<div className="mt-[80px] max-w-[420px] border-t border-tenext-card-border pt-[30px]">
|
||||
<h3 className="text-[20px] font-bold text-tenext-dark font-[var(--font-sora)]">
|
||||
{valueProps.engineering.title}
|
||||
</h3>
|
||||
<p className="mt-[10px] text-[16px] leading-[1.7] text-tenext-text-muted">
|
||||
{valueProps.engineering.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column */}
|
||||
<div className="flex flex-col gap-[30px] lg:pl-[10%]">
|
||||
<IconBox
|
||||
icon={<OrbitIcon className="h-full w-full" />}
|
||||
title="Technology Engineering"
|
||||
description="Full-stack product engineering — architecture, development, and modernization of enterprise applications built to scale."
|
||||
hasBorder={true}
|
||||
theme="light"
|
||||
/>
|
||||
<IconBox
|
||||
icon={<FanIcon className="h-full w-full" />}
|
||||
title="Cloud & Enterprise Platforms"
|
||||
description="Cloud migration, platform engineering, and DevOps that lower total cost of ownership and keep systems reliable."
|
||||
hasBorder={true}
|
||||
theme="light"
|
||||
/>
|
||||
<IconBox
|
||||
icon={<CirclesIcon className="h-full w-full" />}
|
||||
title="AI & Data Science"
|
||||
description="Data platforms, analytics, and applied AI that turn enterprise data into automation and decision-ready insight."
|
||||
hasBorder={true}
|
||||
theme="light"
|
||||
/>
|
||||
<IconBox
|
||||
icon={<ExpandIcon className="h-full w-full" />}
|
||||
title="Digital Commerce & Mobility"
|
||||
description="Commerce platforms and enterprise mobile applications that deliver seamless customer and workforce experiences."
|
||||
hasBorder={true}
|
||||
theme="light"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
72
components/sections/TinkerSection.tsx
Normal file
72
components/sections/TinkerSection.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export function TinkerSection() {
|
||||
return (
|
||||
<section className="relative px-[20px] py-[60px] md:py-[100px] overflow-hidden">
|
||||
<div className="mx-auto flex max-w-[1340px] flex-col gap-[50px] lg:flex-row lg:items-center lg:gap-0">
|
||||
{/* Left Column - Images */}
|
||||
<div className="relative flex w-full flex-col lg:mt-[30px] lg:w-[22%]">
|
||||
{/* Background Glow (elementor-absolute) */}
|
||||
<div className="absolute left-0 top-0 z-0 pointer-events-none min-[576px]:left-[-40vw] min-[576px]:top-[-19.252vw]">
|
||||
<Image
|
||||
src="/images/tenext/Group-18433.png"
|
||||
alt=""
|
||||
width={1283}
|
||||
height={1471}
|
||||
className="h-auto w-auto max-w-[100vw] object-contain opacity-80"
|
||||
aria-hidden="true"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Vector Image */}
|
||||
<div className="relative z-10 min-[576px]:ml-[-26vw]">
|
||||
<Image
|
||||
src="/images/tenext/Vector-13.png"
|
||||
alt="Tenext technology stack"
|
||||
width={670}
|
||||
height={694}
|
||||
className="w-full h-auto max-w-[800px] object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Text & Partners */}
|
||||
<div className="relative z-10 flex w-full flex-col lg:w-[78%]">
|
||||
<div className="min-[576px]:mr-[-4%]">
|
||||
<h2
|
||||
className="mb-[60px] text-tenext-dark tracking-[-0.03em]"
|
||||
style={{
|
||||
fontFamily: "var(--font-dm-sans)",
|
||||
fontSize: "clamp(36px, 4.5vw, 58px)",
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.1,
|
||||
}}
|
||||
>
|
||||
Modern technology,{" "}
|
||||
<span className="gradient-text-purple">
|
||||
integrated with the systems
|
||||
</span>{" "}
|
||||
you already run — cloud, data, IoT, DevOps, and blockchain.
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Capability tags */}
|
||||
<div className="flex flex-wrap items-center justify-start gap-[12px]">
|
||||
{["Cloud", "Data & AI", "IoT", "SMAC", "DevOps", "Blockchain"].map(
|
||||
(tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="rounded-full border border-tenext-card-border px-[22px] py-[10px] text-[15px] font-medium text-tenext-dark"
|
||||
style={{ fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
74
components/sections/WOWSection.tsx
Normal file
74
components/sections/WOWSection.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { ArrowIcon } from "@/components/shared/icons/ArrowIcon";
|
||||
import { PageContainer } from "@/components/shared/PageContainer";
|
||||
|
||||
export function WOWSection() {
|
||||
return (
|
||||
<section className="relative z-20 px-[20px]">
|
||||
<div className="relative z-20 mx-auto max-w-[1340px] overflow-hidden rounded-t-[20px] bg-tenext-dark pt-[30px] pb-[60px] md:pt-[40px] md:pb-[80px]">
|
||||
<PageContainer>
|
||||
<div className="grid grid-cols-1 gap-[40px] lg:grid-cols-2 lg:gap-[60px]">
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="mb-[35px] md:mb-[70px] md:pr-[10%]">
|
||||
<span
|
||||
className="mb-[18px] block text-[12px] font-semibold uppercase tracking-[0.1em] text-white"
|
||||
style={{ fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
[ about ]
|
||||
</span>
|
||||
<h2
|
||||
className="tracking-[-0.03em] text-white"
|
||||
style={{
|
||||
fontFamily: "var(--font-dm-sans)",
|
||||
fontSize: "clamp(44px, 5vw, 80px)",
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.1,
|
||||
}}
|
||||
>
|
||||
Why enterprises choose Tenext
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Image
|
||||
src="/images/tenext/home-9-img.png"
|
||||
alt="Tenext"
|
||||
width={632}
|
||||
height={158}
|
||||
className="h-auto w-full max-w-[632px] object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="max-w-[560px] text-white">
|
||||
<p className="text-[16px] font-bold leading-[1.7]">
|
||||
Tenext Technologies helps enterprises turn digital
|
||||
transformation into predictable business outcomes. We combine
|
||||
engineering depth with a diagnostic, outcome-first approach —
|
||||
so every investment maps to measurable value.
|
||||
</p>
|
||||
<p className="mt-[27px] text-[16px] font-medium leading-[1.7]">
|
||||
Our teams integrate solutions with your existing systems and
|
||||
processes, delivering the lowest total cost of ownership and
|
||||
accelerated time to value across cloud, data, AI, product
|
||||
engineering, and digital commerce.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-[40px] md:mt-[60px]">
|
||||
<Link
|
||||
href="/services"
|
||||
className="tenext-button tenext-button-gradient-border group rounded-full px-[35px] py-[15px] font-[var(--font-sora)] text-[15px] font-semibold tracking-wide text-white transition-all"
|
||||
>
|
||||
<span className="button-inner" />
|
||||
Explore more
|
||||
<ArrowIcon className="ml-2 h-[12px] w-[12px] transition-transform duration-300 group-hover:translate-x-1 group-hover:-translate-y-1" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
52
components/sections/about/AboutCTA.tsx
Normal file
52
components/sections/about/AboutCTA.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { tenextButton as TenextButton } from "@/components/shared/Button";
|
||||
import { PageContainer } from "@/components/shared/PageContainer";
|
||||
import { aboutCta } from "@/data/about";
|
||||
|
||||
/** AboutCTA — large enterprise call to action on a premium gradient panel. */
|
||||
export function AboutCTA() {
|
||||
return (
|
||||
<section className="px-5 py-[60px] md:py-[80px]">
|
||||
<div className="relative overflow-hidden rounded-[25px] bg-tenext-dark px-[7vw] py-[80px] text-center md:py-[100px]">
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<div className="absolute left-1/2 top-[-40%] h-[600px] w-[600px] -translate-x-1/2 rounded-full bg-[radial-gradient(circle,rgba(103,102,200,0.35),transparent_60%)]" />
|
||||
<div className="absolute bottom-[-50%] left-[15%] h-[420px] w-[420px] rounded-full bg-[radial-gradient(circle,rgba(69,208,189,0.16),transparent_60%)]" />
|
||||
</div>
|
||||
|
||||
<PageContainer className="relative z-10 flex flex-col items-center">
|
||||
<h2
|
||||
className="max-w-[760px] tracking-[-0.03em] text-white"
|
||||
style={{
|
||||
fontFamily: "var(--font-dm-sans)",
|
||||
fontSize: "clamp(30px, 4vw, 56px)",
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.1,
|
||||
}}
|
||||
>
|
||||
{aboutCta.title}
|
||||
</h2>
|
||||
<p className="mt-[22px] max-w-[560px] text-[17px] leading-[1.75] text-white/80">
|
||||
{aboutCta.paragraph}
|
||||
</p>
|
||||
<div className="mt-[40px] flex flex-wrap items-center justify-center gap-[16px]">
|
||||
<TenextButton
|
||||
href={aboutCta.primaryCta.href}
|
||||
variant="gradient-fill"
|
||||
className="rounded-[25px] px-[36px] py-[16px] text-[15px] font-semibold"
|
||||
showArrow={false}
|
||||
>
|
||||
{aboutCta.primaryCta.label}
|
||||
</TenextButton>
|
||||
<TenextButton
|
||||
href={aboutCta.secondaryCta.href}
|
||||
variant="gradient-border"
|
||||
className="rounded-[25px] px-[36px] py-[16px] text-[15px] font-medium text-white"
|
||||
showArrow={true}
|
||||
>
|
||||
{aboutCta.secondaryCta.label}
|
||||
</TenextButton>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
96
components/sections/about/AboutHero.tsx
Normal file
96
components/sections/about/AboutHero.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import Link from "next/link";
|
||||
import { tenextButton as TenextButton } from "@/components/shared/Button";
|
||||
import { Icon } from "@/components/shared/Icon";
|
||||
import { aboutHero } from "@/data/about";
|
||||
|
||||
const floatIcons = ["cloud", "cpu", "database", "code", "shield", "boxes"];
|
||||
|
||||
/**
|
||||
* AboutHero — flagship enterprise hero.
|
||||
* Left: breadcrumb, headline, supporting paragraph, primary + secondary CTA.
|
||||
* Right: an asset-free glass-tile composition of capability icons (no photos).
|
||||
*/
|
||||
export function AboutHero() {
|
||||
return (
|
||||
<section className="px-5 pt-5">
|
||||
<div className="relative overflow-hidden rounded-[25px] bg-tenext-dark px-[7vw] py-[80px] md:py-[110px]">
|
||||
{/* Ambient gradient wash */}
|
||||
<div className="pointer-events-none absolute inset-0 opacity-70">
|
||||
<div className="absolute -right-[10%] -top-[30%] h-[600px] w-[600px] rounded-full bg-[radial-gradient(circle,rgba(130,88,200,0.35),transparent_60%)]" />
|
||||
<div className="absolute -bottom-[40%] left-[10%] h-[500px] w-[500px] rounded-full bg-[radial-gradient(circle,rgba(69,208,189,0.18),transparent_60%)]" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 grid grid-cols-1 items-center gap-[50px] lg:grid-cols-[1.1fr_0.9fr]">
|
||||
{/* Content */}
|
||||
<div>
|
||||
<nav aria-label="Breadcrumb" className="mb-[26px]">
|
||||
<ol className="flex items-center gap-2 text-[13px] text-white/60">
|
||||
<li>
|
||||
<Link href="/" className="transition-colors hover:text-tenext-teal">
|
||||
Home
|
||||
</Link>
|
||||
</li>
|
||||
<li className="h-1 w-1 rounded-full bg-tenext-teal" />
|
||||
<li className="text-white">About us</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<span className="tenext-subheading mb-[18px] text-tenext-teal" style={{ fontFamily: "var(--font-sora)" }}>
|
||||
{aboutHero.eyebrow}
|
||||
</span>
|
||||
|
||||
<h1
|
||||
className="max-w-[640px] tracking-[-0.03em] text-white"
|
||||
style={{
|
||||
fontFamily: "var(--font-dm-sans)",
|
||||
fontSize: "clamp(38px, 5vw, 72px)",
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.06,
|
||||
}}
|
||||
>
|
||||
{aboutHero.title}
|
||||
</h1>
|
||||
|
||||
<p className="mt-[26px] max-w-[540px] text-[17px] leading-[1.75] text-white/80">
|
||||
{aboutHero.paragraph}
|
||||
</p>
|
||||
|
||||
<div className="mt-[40px] flex flex-wrap items-center gap-[16px]">
|
||||
<TenextButton
|
||||
href={aboutHero.primaryCta.href}
|
||||
variant="gradient-fill"
|
||||
className="rounded-[25px] px-[34px] py-[15px] text-[15px] font-semibold"
|
||||
showArrow={false}
|
||||
>
|
||||
{aboutHero.primaryCta.label}
|
||||
</TenextButton>
|
||||
<TenextButton
|
||||
href={aboutHero.secondaryCta.href}
|
||||
variant="gradient-border"
|
||||
className="rounded-[25px] px-[34px] py-[15px] text-[15px] font-medium text-white"
|
||||
showArrow={true}
|
||||
>
|
||||
{aboutHero.secondaryCta.label}
|
||||
</TenextButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Icon composition */}
|
||||
<div className="relative hidden lg:block" aria-hidden="true">
|
||||
<div className="grid grid-cols-3 gap-[16px]">
|
||||
{floatIcons.map((name, i) => (
|
||||
<div
|
||||
key={name}
|
||||
className="flex aspect-square items-center justify-center rounded-[22px] border border-white/10 bg-white/[0.04] text-tenext-teal backdrop-blur-sm transition-transform duration-500 hover:-translate-y-1"
|
||||
style={{ transform: `translateY(${i % 2 === 0 ? "-12px" : "12px"})` }}
|
||||
>
|
||||
<Icon name={name} className="h-[34px] w-[34px]" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
57
components/sections/about/AboutIndustries.tsx
Normal file
57
components/sections/about/AboutIndustries.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import Link from "next/link";
|
||||
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 { ArrowIcon } from "@/components/shared/icons/ArrowIcon";
|
||||
import { Icon } from "@/components/shared/Icon";
|
||||
import { industries } from "@/data/site-pages";
|
||||
import { industryIcons } from "@/data/about";
|
||||
|
||||
/** AboutIndustries — premium industry cards (replaces the plain text list). */
|
||||
export function AboutIndustries() {
|
||||
return (
|
||||
<SectionContainer paddingY="lg" className="bg-white px-[20px]">
|
||||
<PageContainer>
|
||||
<SectionHeading
|
||||
tag="industries"
|
||||
title="Depth across the industries you operate in"
|
||||
description="We bring domain understanding and engineering rigor to the sectors where Tenext delivers transformation."
|
||||
titleClassName="text-tenext-dark max-w-[720px]"
|
||||
descriptionClassName="max-w-[620px]"
|
||||
className="mb-[50px]"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-[20px] sm:grid-cols-2 lg:grid-cols-3">
|
||||
{industries.map((industry, index) => (
|
||||
<RevealOnView key={industry.id} delay={index * 80}>
|
||||
<Link
|
||||
href={`/industries#${industry.id}`}
|
||||
className="group relative flex h-full flex-col justify-between overflow-hidden rounded-[25px] border border-tenext-card-border bg-tenext-dark p-[35px] transition-all duration-300 hover:-translate-y-2 hover:shadow-[0_22px_55px_rgba(31,31,31,0.22)]"
|
||||
>
|
||||
<div className="pointer-events-none absolute -right-[20%] -top-[30%] h-[240px] w-[240px] rounded-full bg-[radial-gradient(circle,rgba(69,208,189,0.16),transparent_65%)] opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
|
||||
|
||||
<span className="relative flex h-[60px] w-[60px] items-center justify-center rounded-[16px] bg-white/[0.06] text-tenext-teal">
|
||||
<Icon name={industryIcons[industry.id] ?? "building"} className="h-[28px] w-[28px]" />
|
||||
</span>
|
||||
|
||||
<div className="relative mt-[70px]">
|
||||
<h3 className="font-[var(--font-dm-sans)] text-[22px] font-bold text-white">
|
||||
{industry.title}
|
||||
</h3>
|
||||
<p className="mt-[12px] text-[15px] leading-[1.7] text-tenext-text-muted">
|
||||
{industry.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<span className="relative mt-[24px] flex h-[42px] w-[42px] items-center justify-center rounded-full border border-white/15 text-white transition-colors duration-300 group-hover:bg-tenext-teal group-hover:text-tenext-dark">
|
||||
<ArrowIcon className="h-[14px] w-[14px]" />
|
||||
</span>
|
||||
</Link>
|
||||
</RevealOnView>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
66
components/sections/about/AboutPracticeTeams.tsx
Normal file
66
components/sections/about/AboutPracticeTeams.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
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 { tenextButton as TenextButton } from "@/components/shared/Button";
|
||||
import { Icon } from "@/components/shared/Icon";
|
||||
import { practiceTeamCards } from "@/data/about";
|
||||
|
||||
/**
|
||||
* AboutPracticeTeams — the disciplines that staff an engagement.
|
||||
* Each card has a unique gradient header and icon (no repeated artwork,
|
||||
* no invented individuals).
|
||||
*/
|
||||
export function AboutPracticeTeams() {
|
||||
return (
|
||||
<SectionContainer paddingY="lg" className="bg-white px-[20px]">
|
||||
<PageContainer>
|
||||
<div className="mb-[50px] flex flex-col gap-[24px] md:flex-row md:items-end md:justify-between">
|
||||
<SectionHeading
|
||||
tag="practice teams"
|
||||
title="Specialist teams behind every engagement"
|
||||
description="Engagements are staffed from practices that work together — so the people who design a system are the ones who build and support it."
|
||||
titleClassName="text-tenext-dark max-w-[640px]"
|
||||
descriptionClassName="max-w-[560px]"
|
||||
/>
|
||||
<TenextButton
|
||||
href="/careers"
|
||||
variant="simple"
|
||||
className="shrink-0 text-[15px] font-semibold text-tenext-dark"
|
||||
showArrow={true}
|
||||
>
|
||||
See open roles
|
||||
</TenextButton>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-[20px] sm:grid-cols-2 lg:grid-cols-3">
|
||||
{practiceTeamCards.map((team, index) => (
|
||||
<RevealOnView key={team.name} delay={index * 80}>
|
||||
<div className="group flex h-full flex-col overflow-hidden rounded-[25px] border border-tenext-card-border transition-all duration-300 hover:-translate-y-2 hover:shadow-[0_22px_55px_rgba(31,31,31,0.12)]">
|
||||
{/* Unique gradient header */}
|
||||
<div className={`relative h-[130px] bg-gradient-to-br ${team.gradient}`}>
|
||||
<div className="absolute inset-0 opacity-30 [background-image:radial-gradient(rgba(255,255,255,0.5)_1px,transparent_1px)] [background-size:16px_16px]" />
|
||||
<span className="absolute bottom-[-26px] left-[30px] flex h-[54px] w-[54px] items-center justify-center rounded-[16px] border border-white/20 bg-tenext-dark text-tenext-teal transition-transform duration-500 group-hover:-translate-y-1">
|
||||
<Icon name={team.icon} className="h-[26px] w-[26px]" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col p-[30px] pt-[40px]">
|
||||
<h3 className="font-[var(--font-dm-sans)] text-[20px] font-bold text-tenext-dark">
|
||||
{team.name}
|
||||
</h3>
|
||||
<p className="mt-[4px] text-[14px] font-semibold text-tenext-teal">
|
||||
/ {team.focus} /
|
||||
</p>
|
||||
<p className="mt-[14px] text-[15px] leading-[1.7] text-tenext-text-muted">
|
||||
{team.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</RevealOnView>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
48
components/sections/about/DeliveryModel.tsx
Normal file
48
components/sections/about/DeliveryModel.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
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 { Icon } from "@/components/shared/Icon";
|
||||
import { deliverySteps } from "@/data/about";
|
||||
|
||||
/** DeliveryModel — connected timeline: Discover → … → Support. */
|
||||
export function DeliveryModel() {
|
||||
return (
|
||||
<SectionContainer paddingY="lg" className="bg-tenext-dark px-[20px]">
|
||||
<PageContainer>
|
||||
<SectionHeading
|
||||
tag="delivery model"
|
||||
title="A transparent path from idea to lasting impact"
|
||||
description="Every engagement moves through the same disciplined flow, so progress and cost stay visible at each step."
|
||||
titleClassName="text-white max-w-[720px]"
|
||||
descriptionClassName="max-w-[620px] text-tenext-text-muted"
|
||||
className="mb-[60px]"
|
||||
/>
|
||||
|
||||
<div className="relative grid grid-cols-1 gap-[20px] sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
|
||||
{/* Connecting line (desktop) */}
|
||||
<div className="pointer-events-none absolute left-0 right-0 top-[36px] hidden h-px bg-gradient-to-r from-tenext-teal/10 via-tenext-teal/40 to-tenext-teal/10 xl:block" />
|
||||
|
||||
{deliverySteps.map((step, index) => (
|
||||
<RevealOnView key={step.title} delay={index * 90} className="relative">
|
||||
<div className="flex flex-col items-start gap-[16px]">
|
||||
<div className="relative z-10 flex h-[72px] w-[72px] items-center justify-center rounded-[20px] border border-white/10 bg-tenext-dark text-tenext-teal">
|
||||
<Icon name={step.icon} className="h-[30px] w-[30px]" />
|
||||
<span className="absolute -right-2 -top-2 flex h-[26px] w-[26px] items-center justify-center rounded-full bg-tenext-teal text-[12px] font-bold text-tenext-dark">
|
||||
{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="font-[var(--font-sora)] text-[18px] font-bold text-white">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="text-[14px] leading-[1.65] text-tenext-text-muted">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
</RevealOnView>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
85
components/sections/about/IconCardGrid.tsx
Normal file
85
components/sections/about/IconCardGrid.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
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 { Icon } from "@/components/shared/Icon";
|
||||
import { IconCard } from "@/data/about";
|
||||
|
||||
interface IconCardGridProps {
|
||||
id?: string;
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
items: IconCard[];
|
||||
columns?: 2 | 3 | 4;
|
||||
theme?: "light" | "surface" | "dark";
|
||||
}
|
||||
|
||||
const colClass: Record<number, string> = {
|
||||
2: "sm:grid-cols-2",
|
||||
3: "sm:grid-cols-2 lg:grid-cols-3",
|
||||
4: "sm:grid-cols-2 lg:grid-cols-4",
|
||||
};
|
||||
|
||||
/**
|
||||
* IconCardGrid — reusable premium icon-card section for the About page.
|
||||
* Powers Mission/Vision/Values, Why Tenext, Capabilities, Technology
|
||||
* Ecosystem, and Enterprise Trust with a single, consistent card.
|
||||
*/
|
||||
export function IconCardGrid({
|
||||
id,
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
items,
|
||||
columns = 3,
|
||||
theme = "light",
|
||||
}: IconCardGridProps) {
|
||||
const isDark = theme === "dark";
|
||||
const sectionBg =
|
||||
theme === "dark"
|
||||
? "bg-tenext-dark"
|
||||
: theme === "surface"
|
||||
? "bg-tenext-surface-muted"
|
||||
: "bg-white";
|
||||
|
||||
const cardBase = isDark
|
||||
? "border-white/10 bg-white/[0.03] hover:border-tenext-teal/40"
|
||||
: "border-tenext-card-border bg-white hover:border-tenext-teal/50";
|
||||
const titleColor = isDark ? "text-white" : "text-tenext-dark";
|
||||
|
||||
return (
|
||||
<SectionContainer id={id} paddingY="lg" className={`${sectionBg} px-[20px] scroll-mt-[110px]`}>
|
||||
<PageContainer>
|
||||
<SectionHeading
|
||||
tag={eyebrow}
|
||||
title={title}
|
||||
description={description}
|
||||
titleClassName={`${titleColor} max-w-[760px]`}
|
||||
descriptionClassName={`max-w-[640px] ${isDark ? "text-tenext-text-muted" : ""}`}
|
||||
className="mb-[50px]"
|
||||
/>
|
||||
|
||||
<div className={`grid grid-cols-1 gap-[20px] ${colClass[columns]}`}>
|
||||
{items.map((item, index) => (
|
||||
<RevealOnView key={item.title} delay={index * 80}>
|
||||
<div
|
||||
className={`group flex h-full flex-col gap-[16px] rounded-[25px] border p-[35px] transition-all duration-300 hover:-translate-y-2 hover:shadow-[0_22px_55px_rgba(31,31,31,0.12)] ${cardBase}`}
|
||||
>
|
||||
<span className="flex h-[54px] w-[54px] items-center justify-center rounded-[15px] bg-tenext-teal/10 text-tenext-teal transition-colors duration-300 group-hover:bg-tenext-teal group-hover:text-tenext-dark">
|
||||
<Icon name={item.icon} className="h-[26px] w-[26px]" />
|
||||
</span>
|
||||
<h3 className={`font-[var(--font-sora)] text-[20px] font-bold leading-[1.3] ${titleColor}`}>
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-[15px] leading-[1.7] text-tenext-text-muted">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</RevealOnView>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
60
components/sections/about/OurStory.tsx
Normal file
60
components/sections/about/OurStory.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
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 { Icon } from "@/components/shared/Icon";
|
||||
import { ourStory } from "@/data/about";
|
||||
|
||||
const focusAreas = [
|
||||
{ icon: "code", label: "Engineering" },
|
||||
{ icon: "cloud", label: "Cloud" },
|
||||
{ icon: "cpu", label: "AI" },
|
||||
{ icon: "database", label: "Data" },
|
||||
{ icon: "workflow", label: "Digital Transformation" },
|
||||
{ icon: "chart", label: "Business Outcomes" },
|
||||
];
|
||||
|
||||
/** OurStory — company narrative with a focus-area rail. */
|
||||
export function OurStory() {
|
||||
return (
|
||||
<SectionContainer paddingY="lg" className="bg-tenext-surface-muted px-[20px]">
|
||||
<PageContainer>
|
||||
<div className="grid grid-cols-1 gap-[50px] lg:grid-cols-[0.85fr_1.15fr] lg:gap-[80px]">
|
||||
<RevealOnView>
|
||||
<SectionHeading
|
||||
tag={ourStory.eyebrow}
|
||||
title={ourStory.title}
|
||||
titleClassName="text-tenext-dark"
|
||||
/>
|
||||
<div className="mt-[36px] grid grid-cols-2 gap-[14px]">
|
||||
{focusAreas.map((area) => (
|
||||
<div
|
||||
key={area.label}
|
||||
className="flex items-center gap-[12px] rounded-[15px] border border-tenext-card-border bg-white px-[18px] py-[14px]"
|
||||
>
|
||||
<span className="text-tenext-teal">
|
||||
<Icon name={area.icon} className="h-[20px] w-[20px]" />
|
||||
</span>
|
||||
<span className="text-[14px] font-semibold text-tenext-dark font-[var(--font-sora)]">
|
||||
{area.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RevealOnView>
|
||||
|
||||
<RevealOnView delay={120} className="flex flex-col gap-[24px]">
|
||||
{ourStory.paragraphs.map((paragraph, i) => (
|
||||
<p
|
||||
key={i}
|
||||
className={`leading-[1.85] text-tenext-text-muted ${i === 0 ? "text-[19px] text-tenext-dark" : "text-[16px]"}`}
|
||||
>
|
||||
{paragraph}
|
||||
</p>
|
||||
))}
|
||||
</RevealOnView>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
69
components/sections/about/TeamSection.tsx
Normal file
69
components/sections/about/TeamSection.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import { PageContainer } from "@/components/shared/PageContainer";
|
||||
import { SectionContainer } from "@/components/shared/SectionContainer";
|
||||
import { SectionHeading } from "@/components/shared/SectionHeading";
|
||||
import { tenextButton as TenextButton } from "@/components/shared/Button";
|
||||
import { practiceTeams } from "@/data/site-content";
|
||||
|
||||
export function TeamSection() {
|
||||
return (
|
||||
<SectionContainer id="teams" paddingY="lg" className="scroll-mt-[120px]">
|
||||
<PageContainer>
|
||||
<div className="columns-1 gap-6 sm:columns-2 lg:columns-4">
|
||||
<div className="mb-6 flex break-inside-avoid flex-col">
|
||||
<SectionHeading
|
||||
tag="our teams"
|
||||
title="Specialist teams behind every engagement"
|
||||
titleClassName="text-tenext-dark"
|
||||
/>
|
||||
<p className="mt-[24px] text-[16px] leading-[1.8] text-tenext-text-muted">
|
||||
Engagements are staffed from practices that work together —
|
||||
engineering, cloud, data, mobility, and commerce — so the people
|
||||
who design a system are the ones who build and support it.
|
||||
</p>
|
||||
<TenextButton
|
||||
href="/careers"
|
||||
variant="simple"
|
||||
className="mt-[25px] text-[15px] font-medium text-tenext-dark"
|
||||
showArrow={true}
|
||||
>
|
||||
See open roles
|
||||
</TenextButton>
|
||||
</div>
|
||||
|
||||
{practiceTeams.map((team, i) => (
|
||||
<div
|
||||
key={`${team.name}-${i}`}
|
||||
className="group relative mb-6 break-inside-avoid overflow-hidden rounded-[25px] bg-tenext-card-dark"
|
||||
>
|
||||
<div className="relative aspect-[3/4] w-full">
|
||||
<img
|
||||
src={team.image}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src =
|
||||
"/images/tenext/home-9-img.png";
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-80" />
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 w-full p-6 text-white transition-transform duration-300 group-hover:-translate-y-2">
|
||||
<h4
|
||||
className="text-[20px] font-bold"
|
||||
style={{ fontFamily: "var(--font-dm-sans)" }}
|
||||
>
|
||||
{team.name}
|
||||
</h4>
|
||||
<p className="text-[14px] text-tenext-teal">/ {team.focus} /</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
58
components/sections/about/WhoWeAre.tsx
Normal file
58
components/sections/about/WhoWeAre.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { SectionContainer } from "@/components/shared/SectionContainer";
|
||||
import { PageContainer } from "@/components/shared/PageContainer";
|
||||
import { RevealOnView } from "@/components/shared/RevealOnView";
|
||||
import { Icon } from "@/components/shared/Icon";
|
||||
import { whoWeAre } from "@/data/about";
|
||||
|
||||
/** WhoWeAre — balanced two-column: positioning statement + what/who/why. */
|
||||
export function WhoWeAre() {
|
||||
return (
|
||||
<SectionContainer paddingY="lg" className="bg-white px-[20px]">
|
||||
<PageContainer>
|
||||
<div className="grid grid-cols-1 gap-[50px] lg:grid-cols-2 lg:gap-[80px]">
|
||||
{/* Left */}
|
||||
<RevealOnView>
|
||||
<span className="tenext-subheading mb-[16px] text-tenext-teal" style={{ fontFamily: "var(--font-sora)" }}>
|
||||
{whoWeAre.eyebrow}
|
||||
</span>
|
||||
<h2
|
||||
className="max-w-[480px] tracking-[-0.03em] text-tenext-dark"
|
||||
style={{
|
||||
fontFamily: "var(--font-dm-sans)",
|
||||
fontSize: "clamp(30px, 3.6vw, 48px)",
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.12,
|
||||
}}
|
||||
>
|
||||
{whoWeAre.title}
|
||||
</h2>
|
||||
<p className="mt-[24px] max-w-[460px] text-[17px] leading-[1.75] text-tenext-text-muted">
|
||||
{whoWeAre.lead}
|
||||
</p>
|
||||
</RevealOnView>
|
||||
|
||||
{/* Right */}
|
||||
<div className="flex flex-col gap-[24px]">
|
||||
{whoWeAre.points.map((point, index) => (
|
||||
<RevealOnView key={point.title} delay={index * 90}>
|
||||
<div className="flex gap-[22px] rounded-[25px] border border-tenext-card-border p-[30px] transition-transform duration-300 hover:-translate-y-1">
|
||||
<span className="flex h-[54px] w-[54px] shrink-0 items-center justify-center rounded-[15px] bg-tenext-teal/10 text-tenext-teal">
|
||||
<Icon name={point.icon} className="h-[26px] w-[26px]" />
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="mb-[8px] font-[var(--font-sora)] text-[19px] font-bold text-tenext-dark">
|
||||
{point.title}
|
||||
</h3>
|
||||
<p className="text-[15px] leading-[1.7] text-tenext-text-muted">
|
||||
{point.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</RevealOnView>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
23
components/sections/contacts/MapSection.tsx
Normal file
23
components/sections/contacts/MapSection.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { SectionContainer } from "@/components/shared/SectionContainer";
|
||||
import { siteConfig } from "@/data/site-config";
|
||||
|
||||
export function MapSection() {
|
||||
const query = encodeURIComponent(
|
||||
`${siteConfig.address.line1}, ${siteConfig.address.line2}`
|
||||
);
|
||||
|
||||
return (
|
||||
<SectionContainer paddingY="none" className="h-[500px] w-full">
|
||||
<iframe
|
||||
src={`https://www.google.com/maps?q=${query}&output=embed`}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen={false}
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
title={`${siteConfig.legalName} — Coimbatore`}
|
||||
/>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
50
components/sections/services/DeliveryPrinciplesSection.tsx
Normal file
50
components/sections/services/DeliveryPrinciplesSection.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { PageContainer } from "@/components/shared/PageContainer";
|
||||
import { SectionContainer } from "@/components/shared/SectionContainer";
|
||||
import { SectionHeading } from "@/components/shared/SectionHeading";
|
||||
import { deliveryPrinciples } from "@/data/site-content";
|
||||
|
||||
/**
|
||||
* DeliveryPrinciplesSection — masonry grid of the commitments that keep
|
||||
* delivery predictable.
|
||||
*
|
||||
* Shared by: Home and Services.
|
||||
*
|
||||
* This slot held stock client testimonials in the original build. Tenext
|
||||
* does not publish named client quotes, so it presents how the company
|
||||
* works instead. If real, attributable testimonials become available,
|
||||
* this section is the place to reinstate them.
|
||||
*/
|
||||
export function DeliveryPrinciplesSection() {
|
||||
return (
|
||||
<SectionContainer paddingY="lg" className="bg-tenext-surface-muted">
|
||||
<PageContainer>
|
||||
<div className="mb-12 max-w-[650px]">
|
||||
<SectionHeading
|
||||
tag="how we work"
|
||||
title="The principles that keep delivery predictable"
|
||||
titleClassName="text-tenext-dark"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="columns-1 gap-6 sm:columns-2 lg:columns-4">
|
||||
{deliveryPrinciples.map((principle) => (
|
||||
<div
|
||||
key={principle.id}
|
||||
className="mb-6 break-inside-avoid rounded-[20px] bg-white p-8"
|
||||
>
|
||||
<h5
|
||||
className="mb-4 text-[18px] font-bold text-tenext-dark"
|
||||
style={{ fontFamily: "var(--font-dm-sans)" }}
|
||||
>
|
||||
{principle.title}
|
||||
</h5>
|
||||
<p className="text-[15px] leading-[1.7] text-tenext-text-muted">
|
||||
{principle.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
83
components/sections/services/EngagementModelsSection.tsx
Normal file
83
components/sections/services/EngagementModelsSection.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { PageContainer } from "@/components/shared/PageContainer";
|
||||
import { SectionContainer } from "@/components/shared/SectionContainer";
|
||||
import { SectionHeading } from "@/components/shared/SectionHeading";
|
||||
import { engagementModels } from "@/data/site-content";
|
||||
|
||||
/**
|
||||
* EngagementModelsSection — the three ways enterprises work with Tenext.
|
||||
*
|
||||
* This slot held the template's SaaS pricing tiers. Tenext quotes
|
||||
* commercial terms per engagement, so no prices are published; each card
|
||||
* routes to the contact form instead.
|
||||
*/
|
||||
export function EngagementModelsSection() {
|
||||
return (
|
||||
<SectionContainer paddingY="lg">
|
||||
<PageContainer>
|
||||
<div className="mb-16 flex flex-col items-center justify-center text-center">
|
||||
<SectionHeading
|
||||
tag="engagement models"
|
||||
title="Ways to work with us"
|
||||
titleClassName="text-white max-w-[500px] text-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
{engagementModels.map((model) => (
|
||||
<div
|
||||
key={model.name}
|
||||
className="relative flex flex-col overflow-hidden rounded-[25px] border border-tenext-card-border bg-tenext-dark p-10 transition-colors hover:border-tenext-teal"
|
||||
>
|
||||
<h3
|
||||
className="mb-4 text-[24px] font-bold text-white"
|
||||
style={{ fontFamily: "var(--font-dm-sans)" }}
|
||||
>
|
||||
{model.name}
|
||||
</h3>
|
||||
<p className="mb-8 text-[15px] leading-[1.7] text-tenext-text-muted">
|
||||
{model.summary}
|
||||
</p>
|
||||
|
||||
<ul className="mb-10 flex flex-col gap-4">
|
||||
{model.includes.map((item) => (
|
||||
<li
|
||||
key={item}
|
||||
className="flex items-start gap-3 text-[15px] text-white"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
className="mt-[3px] shrink-0 text-tenext-teal"
|
||||
aria-hidden
|
||||
>
|
||||
<path
|
||||
d="M20 6L9 17l-5-5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-auto">
|
||||
<a
|
||||
href="/contacts"
|
||||
className="flex w-full items-center justify-center rounded-[12px] border border-tenext-card-border py-4 text-[16px] font-bold text-white transition-all hover:border-tenext-teal hover:text-tenext-teal"
|
||||
style={{ fontFamily: "var(--font-sora)" }}
|
||||
>
|
||||
Talk to an expert
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
55
components/sections/services/ServiceCardsGrid.tsx
Normal file
55
components/sections/services/ServiceCardsGrid.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { PageContainer } from "@/components/shared/PageContainer";
|
||||
import { SectionContainer } from "@/components/shared/SectionContainer";
|
||||
import { ArrowIcon } from "@/components/shared/icons/ArrowIcon";
|
||||
import { services } from "@/data/site-content";
|
||||
|
||||
export function ServiceCardsGrid() {
|
||||
return (
|
||||
<SectionContainer paddingY="lg">
|
||||
<PageContainer>
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{services.map((svc) => (
|
||||
<div
|
||||
key={svc.id}
|
||||
id={svc.id}
|
||||
className="group relative flex h-full min-h-[340px] scroll-mt-[120px] flex-col justify-between overflow-hidden rounded-[25px] border border-tenext-card-border bg-tenext-card-dark p-10 transition-all hover:bg-tenext-card-purple"
|
||||
>
|
||||
<div className="mb-6 h-[60px] w-[60px] rounded-full bg-tenext-card-purple2 p-4">
|
||||
<Image
|
||||
src="/images/tenext/3x.png"
|
||||
alt=""
|
||||
width={60}
|
||||
height={60}
|
||||
className="h-full w-full object-contain"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src =
|
||||
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23fff'><path d='M12 2L2 22h20L12 2z'/></svg>";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4
|
||||
className="mb-4 text-[22px] font-bold text-white transition-colors group-hover:text-tenext-teal"
|
||||
style={{ fontFamily: "var(--font-dm-sans)" }}
|
||||
>
|
||||
{svc.title}
|
||||
</h4>
|
||||
<p className="text-[16px] text-tenext-text-muted">
|
||||
{svc.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-0 right-0 flex h-[60px] w-[60px] items-center justify-center rounded-tl-[25px] bg-white text-tenext-dark transition-all duration-300 group-hover:bg-tenext-teal group-hover:text-white">
|
||||
<ArrowIcon className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
53
components/sections/services/ServiceFeaturesSection.tsx
Normal file
53
components/sections/services/ServiceFeaturesSection.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { PageContainer } from "@/components/shared/PageContainer";
|
||||
import { SectionContainer } from "@/components/shared/SectionContainer";
|
||||
import { SectionHeading } from "@/components/shared/SectionHeading";
|
||||
import { IconBox } from "@/components/shared/IconBox";
|
||||
import { differentiators } from "@/data/site-content";
|
||||
|
||||
export function ServiceFeaturesSection() {
|
||||
const DefaultIcon = () => (
|
||||
<div className="flex h-full w-full items-center justify-center rounded-[18px] bg-tenext-card-purple">
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
className="text-white"
|
||||
>
|
||||
<path
|
||||
d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<SectionContainer paddingY="lg">
|
||||
<PageContainer>
|
||||
<div className="mb-16">
|
||||
<SectionHeading
|
||||
tag="why tenext"
|
||||
title="What enterprises get from working with us"
|
||||
titleClassName="text-white max-w-[700px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-x-12 gap-y-12 md:grid-cols-2 lg:grid-cols-3">
|
||||
{differentiators.map((item, i) => (
|
||||
<IconBox
|
||||
key={item.id}
|
||||
icon={<DefaultIcon />}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
hasBorder={i < 3} // The original has a border for the first row
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PageContainer>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user