A modern single-page redesign of the Zila Seba app landing page — React 19, Vite 8, Tailwind CSS 4, zero UI libraries.

Zila Seba is a Bangladeshi local digital services platform — think utility bill payments, government form submissions, and civic services through a single app. The original landing page was functional but visually dated. This redesign reimagines it as a dark, modern product marketing page with a bento grid layout, scroll-triggered count-up statistics, and motion built entirely in pure CSS. No animation libraries. No UI component kits.
Overview
The redesign had a clear brief: make Zila Seba feel like a product worth downloading. The original used a light background, generic stock illustrations, and static text blocks. The new direction is a dark luxury aesthetic — near-black backgrounds, a blue-green accent that matches Bangladeshi state branding, and a bento grid that communicates the platform's breadth of services at a glance.
Every animated element — the count-up stats, the floating phone mockup, the infinite logo marquee — is implemented with Tailwind v4 CSS utilities and @keyframes declarations. React 19 handles component composition and scroll event binding, but does no heavy lifting here. The constraint was intentional: prove the design system does not need Framer Motion, GSAP, or any third-party animation package.
No Headless UI, no Radix, no shadcn/ui, no Framer Motion. Every component — including the count-up stat blocks, the marquee, the mobile nav — is hand-written from scratch. The only runtime dependencies are React and React DOM.
Design Decisions
The original landing page used a white background with light blue accents — readable, but forgettable. The redesign flips to a dark foundation for several deliberate reasons:
- Dark backgrounds make brand-colored UI elements (teal buttons, icon glows) pop without additional decoration
- Government and civic apps rarely use dark mode, so the contrast positions Zila Seba as modern and tech-forward
- A dark theme simplifies the illustration layer — phone mockups float without needing dropped shadows or cutout masks
Typography pairs Hind Siliguri (for Bengali script) with Inter (for Latin UI text). This is the first time a typeface decision actually matters for this project — Zila Seba's core audience reads Bengali, and most western-facing landing page templates give zero consideration to Indic script rendering.
The bento grid replaces a standard feature list. Instead of five bullet points under a heading, each service category gets its own card with an icon, a label, and a hover state. The grid is asymmetric: larger cards anchor corner slots, smaller ones fill the interior. This creates visual weight without needing a separate hero illustration.
Features
| Feature | Details |
|---|---|
| Bento grid layout | Asymmetric service card grid with hover lift animations |
| Count-up statistics | Numbers animate from zero on first scroll into view, triggered by IntersectionObserver |
| Infinite logo marquee | Continuous horizontal scroll of partner/service logos via pure CSS @keyframes |
| Floating phone mockup | Phone image with subtle CSS float animation; no JS animation library |
| Bengali typography | Hind Siliguri renders the Bengali script portions of the UI correctly |
| Mobile-first responsive | Single-column on small screens; bento grid activates at md breakpoint |
| Pure CSS animations | All motion implemented with Tailwind v4 utilities and @keyframes — zero JS animation |
| Zero UI libraries | Every component hand-written; only runtime deps are React and React DOM |
Tech Stack
| Tool | Role |
|---|---|
| React 19 | Component composition, scroll event binding for count-up trigger |
| Vite 8 | Build tooling with first-class React and Tailwind support |
| Tailwind CSS 4 | CSS-first config, design tokens, all utility styling |
| Google Fonts — Hind Siliguri + Inter | Bengali script and Latin UI typefaces |
Pure CSS @keyframes | Marquee, float animation, hover transitions |
Component Architecture
src/
├── components/
│ ├── Hero.tsx # Headline, CTA, floating phone
│ ├── BentoGrid.tsx # Service category cards
│ ├── Stats.tsx # Count-up stat blocks
│ ├── Marquee.tsx # Infinite logo scroll
│ ├── Features.tsx # Feature highlight section
│ ├── Download.tsx # App store CTA
│ └── Nav.tsx # Sticky nav with mobile drawer
├── hooks/
│ └── useCountUp.ts # IntersectionObserver + rAF counter
└── styles/
└── globals.css # @theme tokens, @keyframes declarationsCount-Up Implementation
The stat counters animate on first viewport entry using a custom hook that combines IntersectionObserver with requestAnimationFrame. The hook is pure — it takes a target number and duration, returns the current display value, and cleans up automatically.
src/hooks/useCountUp.ts
import { useState, useEffect, useRef } from 'react'
export function useCountUp(target: number, duration = 1800) {
const [value, setValue] = useState(0)
const ref = useRef<HTMLElement>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) return
observer.disconnect()
const start = performance.now()
const tick = (now: number) => {
const progress = Math.min((now - start) / duration, 1)
const eased = 1 - Math.pow(1 - progress, 3) // ease-out cubic
setValue(Math.round(eased * target))
if (progress < 1) requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
},
{ threshold: 0.5 }
)
observer.observe(el)
return () => observer.disconnect()
}, [target, duration])
return { ref, value }
}The easing curve (1 - (1 - t)^3) gives the count-up a fast start and smooth deceleration — it reads as confident rather than mechanical.
Before and After
The redesign addresses specific weaknesses in the original page:
| Aspect | Original | Redesign |
|---|---|---|
| Visual tone | Light, generic | Dark, product-forward |
| Layout | Static feature list | Asymmetric bento grid |
| Typography | Single Latin font | Hind Siliguri + Inter pairing |
| Motion | None | Count-up, marquee, float — pure CSS |
| Perceived quality | Functional | Premium |
| UI library usage | Untracked | Zero — all hand-written |