The official website for ENTENSY — a software development and tech consulting company. A fully animated, multilingual, dark/light-mode, RTL-aware marketing site built with Next.js 16, React 19, and Tailwind CSS v4.
- Tech Stack
- Getting Started
- Environment Variables
- Project Structure
- Content Editing Guide
- Internationalization & RTL Support
- Sections Overview
- Brand Reference
- Deployment
| Layer | Technology |
|---|---|
| Framework | Next.js 16.2 (App Router) |
| Language | TypeScript 5 |
| Styling | Tailwind CSS v4 |
| Animation | Framer Motion 12, GSAP 3 (SplitText, ScrollTrigger) |
| 3D | Three.js + React Three Fiber |
| i18n | next-intl 4 |
| Forms | React Hook Form + Zod |
| Resend | |
| Carousel | Embla Carousel |
| Icons | react-icons + devicon (via @iconify-json/devicon) |
| Particles | tsParticles Slim |
| Bot protection | Cloudflare Turnstile |
| Package Manager | pnpm |
# Install dependencies
pnpm install
# Run development server
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm startOpen http://localhost:3000 in your browser.
Create a .env.local file in the project root:
# Email — Resend
RESEND_API_KEY=re_your_api_key
# Bot protection — Cloudflare Turnstile
NEXT_PUBLIC_TURNSTILE_SITE_KEY=your_site_key
TURNSTILE_SECRET_KEY=your_secret_keyResend: Get your API key from the Resend dashboard. Verify the entensy.com domain before going live so emails can be sent from contact@entensy.com.
Cloudflare Turnstile: Create a widget at dash.cloudflare.com → Turnstile → Add widget. Choose Managed challenge type and add your domain. The dashboard gives you a Site Key (public, goes in NEXT_PUBLIC_TURNSTILE_SITE_KEY) and a Secret Key (server-only, goes in TURNSTILE_SECRET_KEY). For local development, Cloudflare provides dummy keys that always pass — see their docs.
src/
├── app/
│ ├── layout.tsx # Root layout (minimal pass-through)
│ ├── globals.css # Tailwind theme, CSS variables, keyframes
│ ├── not-found.tsx # Global 404 page
│ └── [locale]/
│ ├── layout.tsx # Locale layout — fonts, providers, metadata
│ ├── page.tsx # Main page (assembles all sections)
│ └── not-found.tsx # Locale-scoped 404 page
│
├── components/
│ ├── layout/
│ │ ├── Navbar.tsx # Top navigation bar
│ │ ├── Footer.tsx # Site footer
│ │ ├── LoadingScreen.tsx # Animated intro loading screen
│ │ ├── Providers.tsx # Client-side provider wrapper
│ │ └── ThemeContext.tsx # Dark/light theme context + ripple transition
│ │
│ ├── sections/
│ │ ├── HomeSection.tsx # Hero / landing section
│ │ ├── ServicesSection.tsx # 10 services cards
│ │ ├── SolutionsSection.tsx # 13 solution cards (bento grid)
│ │ ├── StacksSection.tsx # Tech stack tabs + grid
│ │ ├── PortfolioSection.tsx # Project carousel
│ │ ├── AboutSection.tsx # Company identity, stats, pillars
│ │ ├── SocialsSection.tsx # Social platform tiles
│ │ └── ContactSection.tsx # Contact form + info cards
│ │
│ └── ui/
│ ├── AnimatedButton.tsx # Button — primary/secondary/ghost/outline
│ ├── SectionHeading.tsx # GSAP-animated badge + title + subtitle
│ ├── TiltCard.tsx # 3D tilt wrapper
│ ├── GlassCard.tsx # Glassmorphism card wrapper
│ ├── LanguageSwitcher.tsx # Language dropdown (EN / KU / AR)
│ ├── ThemeToggle.tsx # Dark/light toggle button
│ ├── GlobeBackground.tsx # Three.js wireframe globe
│ └── ParticleBackground.tsx # tsParticles animated background
│
├── hooks/
│ ├── useCardBorder.ts # GSAP border spotlight + cursor glow
│ └── useMousePosition.ts # Normalized mouse position
│
├── i18n/
│ ├── routing.ts # Locale list and default locale
│ ├── request.ts # next-intl server config
│ └── messages/
│ ├── en.json # English translations
│ ├── ar.json # Arabic translations (RTL)
│ └── ckb.json # Kurdish Sorani translations (RTL)
│
└── lib/
├── animations.ts # Shared Framer Motion variants
├── utils.ts # cn(), clamp(), lerp(), mapRange()
├── card-border.ts # Card border effect helpers
├── icon-registry.ts # Icon imports + devicon SVGs for stacks/portfolio
├── services-data.ts # Services content data
├── solutions-data.ts # Solutions content data
├── stacks-data.ts # Technology stacks data
└── portfolio-data.ts # Portfolio projects data
File: src/lib/portfolio-data.ts
Add a new object to the portfolioProjects array:
{
id: "my-new-project",
titleKey: "projects.my_project.title", // points to a translation key
descKey: "projects.my_project.desc", // points to a translation key
image: "/images/projects/my-project.png", // optional — place file in public/images/projects/
gradient: "from-[#FC002A]/20 to-[#7C3AED]/20", // gradient shown when no image
stacks: ["React", "Node.js", "PostgreSQL"],
visitUrl: "https://example.com", // optional — omit to show a disabled button
githubUrl: "https://github.com/...", // optional
},Then add the matching translation strings in all three language files under portfolio.projects:
"projects": {
"my_project": {
"title": "My New Project",
"desc": "Short description of what this project does."
}
}Image naming: drop the image into public/images/projects/ and reference it as /images/projects/my-project.png. If image is omitted the card shows the gradient fallback.
File: src/lib/services-data.ts
Each service entry uses a Lucide icon name, an accent color, and translation keys:
{
id: "my-service",
icon: "Code2", // Lucide icon — must also be in iconMap inside ServicesSection.tsx
color: "#F43F5E",
gradientFrom: "#F43F5E",
gradientTo: "#C9A84C",
size: "normal", // "normal" | "large" (large cards span 2 columns)
titleKey: "services.items.my_service.title",
descKey: "services.items.my_service.desc",
}File: src/lib/solutions-data.ts
Same structure as services but uses react-icons/fi icon names. The id must also appear in solutionTagMap inside SolutionsSection.tsx to display a tag pill on the card.
Translation files: src/i18n/messages/
| File | Language | Direction |
|---|---|---|
en.json |
English | LTR |
ckb.json |
Kurdish Sorani | RTL |
ar.json |
Arabic | RTL |
Every key used in the UI must exist in all three files. The top-level namespaces are:
| Namespace | Covers |
|---|---|
nav |
Navbar links, brand name, CTA label |
home |
Hero section |
services |
Services section |
solutions |
Solutions section |
stacks |
Technology stacks section |
portfolio |
Portfolio section and project cards |
about |
About section (stats, pillars, capabilities) |
contact |
Contact form, info cards, CTA card |
footer |
Footer tagline and copyright |
socials |
Socials section |
loading |
Loading screen tagline |
notFound |
404 page |
lang |
Language switcher labels |
meta |
Browser tab title, description, OG tags |
To add a new language:
- Create
src/i18n/messages/xx.jsonwith all keys copied fromen.json - Add
"xx"to thelocalesarray insrc/i18n/routing.ts - Add
"xx": "Label"underlangin all three existing message files - Add
{ code: "xx", dir: "ltr" }(or"rtl") to thelanguagesarray insrc/components/ui/LanguageSwitcher.tsx
Primary file: src/components/layout/Navbar.tsx
| What to change | Where |
|---|---|
| Navigation links and their anchor IDs | const navLinks array at the top of the file |
| Brand name text | nav.brand key in message files |
| Scroll floating animation (size, radius, padding) | animate={{...}} on the <motion.nav> element |
| Desktop breakpoint (when hamburger appears) | desktopLinksClass / hamburgerHideClass variables |
| "Get Started" button label | nav.get_started in message files |
Related files pulled into the navbar:
| File | Purpose |
|---|---|
src/components/ui/LanguageSwitcher.tsx |
Language dropdown |
src/components/ui/ThemeToggle.tsx |
Dark/light toggle |
File: src/components/layout/Footer.tsx
| What | Where |
|---|---|
| Footer nav links | navLinks array inside the file |
| Tagline | footer.tagline in message files |
| Copyright text | footer.rights in message files |
| Brand name | nav.brand in message files |
File: src/app/globals.css — @theme block at the top
@theme {
--color-brand: #FC002A; /* primary red — use as text-brand, bg-brand, border-brand */
--color-gold: #C9A84C; /* gold accent — use as text-gold, bg-gold, border-gold */
}The site fully supports right-to-left layouts for Arabic (ar) and Kurdish Sorani (ckb). RTL is applied via dir="rtl" on each section element. Conventions used throughout the codebase:
- Diagonal card corner accents mirror to the opposite corner in RTL
- Arrow icons use the
.rtl-arrowCSS class (flips viatransform: scaleX(-1)) - Phone numbers use
.force-ltrto prevent digit reversal in RTL context - Tag pills use
dir="ltr"to keep Latin labels readable - Letter-spacing and
text-transform: uppercaseare suppressed for Arabic/Kurdish text via[dir="rtl"]CSS rules
| Section | Component | Content source |
|---|---|---|
| Hero | HomeSection.tsx |
Translation files |
| Services | ServicesSection.tsx |
lib/services-data.ts + translations |
| Solutions | SolutionsSection.tsx |
lib/solutions-data.ts + translations |
| Tech Stacks | StacksSection.tsx |
lib/stacks-data.ts + translations |
| Portfolio | PortfolioSection.tsx |
lib/portfolio-data.ts + translations |
| About | AboutSection.tsx |
Translation files |
| Socials | SocialsSection.tsx |
Hardcoded links inside the component |
| Contact | ContactSection.tsx |
Translation files + Resend |
| Token | Value |
|---|---|
| Primary red | #FC002A |
| Gold accent | #C9A84C |
| Dark background | #0D0A1E |
| Dark surface | #110C2A |
| Font | Montserrat (300–900) |
| Card border radius | 1rem – 1.5rem |
| Glass card (dark) | rgba(255,255,255,0.03) + backdrop-blur(16px) |
| Glass card (light) | rgba(255,255,255,0.65) + backdrop-blur(16px) |
The site targets Vercel. Push to main and Vercel auto-deploys.
Before deploying:
- Add
RESEND_API_KEYin the Vercel dashboard under Settings → Environment Variables - Add
NEXT_PUBLIC_TURNSTILE_SITE_KEYandTURNSTILE_SECRET_KEYin the same place - Run
pnpm buildlocally to confirm no TypeScript or build errors
Built solely by Andra.