Redesigned home plus pages

X-Lovable-Edit-ID: edt-7621b530-7ad0-4275-87d0-3a060c9b3f4e
Co-authored-by: yusuf-intern <248833874+yusuf-intern@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-07-21 17:16:13 +00:00
15 changed files with 1104 additions and 205 deletions

View File

@@ -60,7 +60,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.32.0", "@eslint/js": "^9.32.0",
"@lovable.dev/vite-tanstack-config": "^2.7.6", "@lovable.dev/vite-tanstack-config": "2.7.7",
"@types/node": "^22.16.5", "@types/node": "^22.16.5",
"@types/react": "^19.2.0", "@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0", "@types/react-dom": "^19.2.0",
@@ -232,7 +232,7 @@
"@lovable.dev/vite-plugin-hmr-gate": ["@lovable.dev/vite-plugin-hmr-gate@1.1.3", "", { "peerDependencies": { "vite": ">=5.0.0 <9.0.0" } }, "sha512-edEZzJloDj2IO7lf380So7k1SiNeYDbFLqeLzuqsP8J1qSetKWqTBTR2gAfGxBlLXHZrlayb/dU8Y9S9AlqW0Q=="], "@lovable.dev/vite-plugin-hmr-gate": ["@lovable.dev/vite-plugin-hmr-gate@1.1.3", "", { "peerDependencies": { "vite": ">=5.0.0 <9.0.0" } }, "sha512-edEZzJloDj2IO7lf380So7k1SiNeYDbFLqeLzuqsP8J1qSetKWqTBTR2gAfGxBlLXHZrlayb/dU8Y9S9AlqW0Q=="],
"@lovable.dev/vite-tanstack-config": ["@lovable.dev/vite-tanstack-config@2.7.6", "", { "dependencies": { "@lovable.dev/vite-plugin-dev-server-bridge": "^1.1.1", "@lovable.dev/vite-plugin-hmr-gate": "^1.1.3", "@tanstack/devtools-vite": "^0.8.1", "lightningcss": "^1.30.0" }, "peerDependencies": { "@tailwindcss/vite": ">=4.0.0", "@tanstack/react-start": ">=1.100.0", "@vitejs/plugin-react": ">=4.0.0", "nitro": ">=3.0.260603-beta", "vite": ">=5.0.0 <9.0.0", "vite-tsconfig-paths": ">=6.0.0" }, "optionalPeers": ["nitro"] }, "sha512-OxmPom+MWV54Itezoapqa/T6zN8oSHap4kHG7mse4iiCMGGD+E4qa+q1SctacU6wEDkJiqJHhYPG+QEsSY3IUw=="], "@lovable.dev/vite-tanstack-config": ["@lovable.dev/vite-tanstack-config@2.7.7", "https://europe-west1-npm.pkg.dev/lovable-core-prod/sandbox-npm-cache/@lovable.dev/vite-tanstack-config/-/vite-tanstack-config-2.7.7.tgz", { "dependencies": { "@lovable.dev/vite-plugin-dev-server-bridge": "^1.2.1", "@lovable.dev/vite-plugin-hmr-gate": "^1.1.3", "@tanstack/devtools-vite": "^0.8.1", "lightningcss": "^1.30.0" }, "peerDependencies": { "@tailwindcss/vite": ">=4.0.0", "@tanstack/react-start": ">=1.100.0", "@vitejs/plugin-react": ">=4.0.0", "nitro": ">=3.0.260603-beta", "vite": ">=5.0.0 <9.0.0", "vite-tsconfig-paths": ">=6.0.0" }, "optionalPeers": ["nitro"] }, "sha512-J7IXGzGaMfsFiEpNUaG3utmA1d/tXv4a7XPJb9ONlG7xyfq7LjPebJ10J087sou4lRl08ndJkJ/reE5paAJH2w=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],

View File

@@ -67,7 +67,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.32.0", "@eslint/js": "^9.32.0",
"@lovable.dev/vite-tanstack-config": "^2.7.6", "@lovable.dev/vite-tanstack-config": "2.7.7",
"@types/node": "^22.16.5", "@types/node": "^22.16.5",
"@types/react": "^19.2.0", "@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0", "@types/react-dom": "^19.2.0",

View File

@@ -0,0 +1,47 @@
import { Link } from "@tanstack/react-router";
import { ArrowUpRight, type LucideIcon } from "lucide-react";
export function PageHero({ eyebrow, title, lede }: { eyebrow: string; title: string; lede?: string }) {
return (
<section className="border-b">
<div className="container-page py-20">
<div className="text-xs uppercase tracking-[0.25em] text-brand-blue font-semibold">{eyebrow}</div>
<h1 className="mt-4 font-display text-5xl md:text-6xl font-black leading-[1]">{title}</h1>
{lede && <p className="mt-6 max-w-2xl text-lg text-muted-foreground">{lede}</p>}
</div>
</section>
);
}
export function Section({ children, className = "" }: { children: React.ReactNode; className?: string }) {
return <section className={`py-20 ${className}`}><div className="container-page">{children}</div></section>;
}
export function FeatureRow({ img, title, body, reverse, Icon }: { img: string; title: string; body: string; reverse?: boolean; Icon?: LucideIcon }) {
return (
<div className={`grid lg:grid-cols-2 gap-10 items-center ${reverse ? "lg:[&>*:first-child]:order-2" : ""}`}>
<img src={img} alt={title} className="w-full h-[380px] object-cover" />
<div>
{Icon && <Icon className="h-6 w-6 text-brand-red" />}
<h3 className="mt-4 font-display text-3xl font-black">{title}</h3>
<p className="mt-4 text-muted-foreground whitespace-pre-line">{body}</p>
</div>
</div>
);
}
export function CTA() {
return (
<section className="bg-foreground text-background">
<div className="container-page py-16 flex flex-wrap items-center justify-between gap-6">
<div>
<div className="text-xs uppercase tracking-widest text-brand-red">Ready when you are</div>
<h3 className="mt-2 font-display text-3xl font-black">Let's design your misting system.</h3>
</div>
<Link to="/enquiry" className="inline-flex items-center gap-2 bg-brand-red text-white px-6 py-3.5 rounded-md text-sm font-semibold">
Request a quote <ArrowUpRight className="h-4 w-4" />
</Link>
</div>
</section>
);
}

View File

@@ -9,8 +9,56 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root' import { Route as rootRouteImport } from './routes/__root'
import { Route as SolutionsRouteImport } from './routes/solutions'
import { Route as RentalRouteImport } from './routes/rental'
import { Route as ProjectsRouteImport } from './routes/projects'
import { Route as ProductsRouteImport } from './routes/products'
import { Route as EnquiryRouteImport } from './routes/enquiry'
import { Route as ContactRouteImport } from './routes/contact'
import { Route as ApplicationsRouteImport } from './routes/applications'
import { Route as AboutRouteImport } from './routes/about'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
const SolutionsRoute = SolutionsRouteImport.update({
id: '/solutions',
path: '/solutions',
getParentRoute: () => rootRouteImport,
} as any)
const RentalRoute = RentalRouteImport.update({
id: '/rental',
path: '/rental',
getParentRoute: () => rootRouteImport,
} as any)
const ProjectsRoute = ProjectsRouteImport.update({
id: '/projects',
path: '/projects',
getParentRoute: () => rootRouteImport,
} as any)
const ProductsRoute = ProductsRouteImport.update({
id: '/products',
path: '/products',
getParentRoute: () => rootRouteImport,
} as any)
const EnquiryRoute = EnquiryRouteImport.update({
id: '/enquiry',
path: '/enquiry',
getParentRoute: () => rootRouteImport,
} as any)
const ContactRoute = ContactRouteImport.update({
id: '/contact',
path: '/contact',
getParentRoute: () => rootRouteImport,
} as any)
const ApplicationsRoute = ApplicationsRouteImport.update({
id: '/applications',
path: '/applications',
getParentRoute: () => rootRouteImport,
} as any)
const AboutRoute = AboutRouteImport.update({
id: '/about',
path: '/about',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({ const IndexRoute = IndexRouteImport.update({
id: '/', id: '/',
path: '/', path: '/',
@@ -19,28 +67,144 @@ const IndexRoute = IndexRouteImport.update({
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/about': typeof AboutRoute
'/applications': typeof ApplicationsRoute
'/contact': typeof ContactRoute
'/enquiry': typeof EnquiryRoute
'/products': typeof ProductsRoute
'/projects': typeof ProjectsRoute
'/rental': typeof RentalRoute
'/solutions': typeof SolutionsRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/about': typeof AboutRoute
'/applications': typeof ApplicationsRoute
'/contact': typeof ContactRoute
'/enquiry': typeof EnquiryRoute
'/products': typeof ProductsRoute
'/projects': typeof ProjectsRoute
'/rental': typeof RentalRoute
'/solutions': typeof SolutionsRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
'/': typeof IndexRoute '/': typeof IndexRoute
'/about': typeof AboutRoute
'/applications': typeof ApplicationsRoute
'/contact': typeof ContactRoute
'/enquiry': typeof EnquiryRoute
'/products': typeof ProductsRoute
'/projects': typeof ProjectsRoute
'/rental': typeof RentalRoute
'/solutions': typeof SolutionsRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' fullPaths:
| '/'
| '/about'
| '/applications'
| '/contact'
| '/enquiry'
| '/products'
| '/projects'
| '/rental'
| '/solutions'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' to:
id: '__root__' | '/' | '/'
| '/about'
| '/applications'
| '/contact'
| '/enquiry'
| '/products'
| '/projects'
| '/rental'
| '/solutions'
id:
| '__root__'
| '/'
| '/about'
| '/applications'
| '/contact'
| '/enquiry'
| '/products'
| '/projects'
| '/rental'
| '/solutions'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
AboutRoute: typeof AboutRoute
ApplicationsRoute: typeof ApplicationsRoute
ContactRoute: typeof ContactRoute
EnquiryRoute: typeof EnquiryRoute
ProductsRoute: typeof ProductsRoute
ProjectsRoute: typeof ProjectsRoute
RentalRoute: typeof RentalRoute
SolutionsRoute: typeof SolutionsRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
interface FileRoutesByPath { interface FileRoutesByPath {
'/solutions': {
id: '/solutions'
path: '/solutions'
fullPath: '/solutions'
preLoaderRoute: typeof SolutionsRouteImport
parentRoute: typeof rootRouteImport
}
'/rental': {
id: '/rental'
path: '/rental'
fullPath: '/rental'
preLoaderRoute: typeof RentalRouteImport
parentRoute: typeof rootRouteImport
}
'/projects': {
id: '/projects'
path: '/projects'
fullPath: '/projects'
preLoaderRoute: typeof ProjectsRouteImport
parentRoute: typeof rootRouteImport
}
'/products': {
id: '/products'
path: '/products'
fullPath: '/products'
preLoaderRoute: typeof ProductsRouteImport
parentRoute: typeof rootRouteImport
}
'/enquiry': {
id: '/enquiry'
path: '/enquiry'
fullPath: '/enquiry'
preLoaderRoute: typeof EnquiryRouteImport
parentRoute: typeof rootRouteImport
}
'/contact': {
id: '/contact'
path: '/contact'
fullPath: '/contact'
preLoaderRoute: typeof ContactRouteImport
parentRoute: typeof rootRouteImport
}
'/applications': {
id: '/applications'
path: '/applications'
fullPath: '/applications'
preLoaderRoute: typeof ApplicationsRouteImport
parentRoute: typeof rootRouteImport
}
'/about': {
id: '/about'
path: '/about'
fullPath: '/about'
preLoaderRoute: typeof AboutRouteImport
parentRoute: typeof rootRouteImport
}
'/': { '/': {
id: '/' id: '/'
path: '/' path: '/'
@@ -53,17 +217,15 @@ declare module '@tanstack/react-router' {
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
AboutRoute: AboutRoute,
ApplicationsRoute: ApplicationsRoute,
ContactRoute: ContactRoute,
EnquiryRoute: EnquiryRoute,
ProductsRoute: ProductsRoute,
ProjectsRoute: ProjectsRoute,
RentalRoute: RentalRoute,
SolutionsRoute: SolutionsRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>() ._addFileTypes<FileRouteTypes>()
import type { getRouter } from './router.tsx'
import type { startInstance } from './start.ts'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
config: Awaited<ReturnType<typeof startInstance.getOptions>>
}
}

View File

@@ -7,28 +7,158 @@ import {
HeadContent, HeadContent,
Scripts, Scripts,
} from "@tanstack/react-router"; } from "@tanstack/react-router";
import { useEffect, type ReactNode } from "react"; import { useEffect, useState, type ReactNode } from "react";
import { Menu, X, Phone } from "lucide-react";
import appCss from "../styles.css?url"; import appCss from "../styles.css?url";
import { reportLovableError } from "../lib/lovable-error-reporting"; import { reportLovableError } from "../lib/lovable-error-reporting";
const NAV = [
{ to: "/", label: "Home" },
{ to: "/about", label: "About" },
{ to: "/applications", label: "Applications" },
{ to: "/products", label: "Products" },
{ to: "/solutions", label: "Solutions" },
{ to: "/projects", label: "Projects" },
{ to: "/enquiry", label: "Enquiry" },
{ to: "/contact", label: "Contact" },
{ to: "/rental", label: "Rental" },
] as const;
function TopBar() {
return (
<div className="w-full bg-brand-red text-white text-sm">
<div className="container-page flex flex-wrap items-center justify-end gap-6 py-2">
<a href="tel:+97142515596" className="inline-flex items-center gap-2 hover:opacity-80">
<Phone className="h-3.5 w-3.5" /> +971 4 251 5596
</a>
<a href="tel:+971552098628" className="inline-flex items-center gap-2 hover:opacity-80">
<Phone className="h-3.5 w-3.5" /> +971 55 209 8628
</a>
</div>
</div>
);
}
function Header() {
const [open, setOpen] = useState(false);
return (
<header className="sticky top-0 z-50 bg-background/95 backdrop-blur border-b">
<div className="container-page flex items-center justify-between h-20">
<Link to="/" className="flex items-baseline gap-1 font-display">
<span className="text-3xl font-black tracking-tight">
<span className="text-brand-blue">Pro</span>
<span className="text-brand-red">mist</span>
</span>
<span className="text-xs uppercase tracking-[0.2em] text-muted-foreground hidden sm:inline">
Misting Systems
</span>
</Link>
<nav className="hidden lg:flex items-center gap-7 text-sm font-medium">
{NAV.map((n) => (
<Link
key={n.to}
to={n.to}
className="text-foreground/80 hover:text-brand-red transition-colors"
activeProps={{ className: "text-brand-red" }}
activeOptions={{ exact: n.to === "/" }}
>
{n.label}
</Link>
))}
</nav>
<button
onClick={() => setOpen((v) => !v)}
className="lg:hidden inline-flex items-center justify-center h-10 w-10 rounded-md border"
aria-label="Toggle menu"
>
{open ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</button>
</div>
{open && (
<div className="lg:hidden border-t">
<div className="container-page py-4 flex flex-col gap-3">
{NAV.map((n) => (
<Link
key={n.to}
to={n.to}
onClick={() => setOpen(false)}
className="text-foreground/80 hover:text-brand-red"
activeProps={{ className: "text-brand-red" }}
activeOptions={{ exact: n.to === "/" }}
>
{n.label}
</Link>
))}
</div>
</div>
)}
</header>
);
}
function Footer() {
return (
<footer className="mt-24 border-t bg-muted/40">
<div className="container-page py-14 grid gap-10 md:grid-cols-4">
<div>
<div className="font-display text-2xl font-black">
<span className="text-brand-blue">Pro</span>
<span className="text-brand-red">mist</span>
</div>
<p className="mt-3 text-sm text-muted-foreground max-w-xs">
High-pressure misting systems for cooling, humidification and dust suppression since 1979.
</p>
</div>
<div>
<h4 className="text-xs uppercase tracking-widest text-brand-red font-semibold">Explore</h4>
<ul className="mt-4 space-y-2 text-sm">
{NAV.slice(1, 6).map((n) => (
<li key={n.to}>
<Link to={n.to} className="hover:text-brand-red">{n.label}</Link>
</li>
))}
</ul>
</div>
<div>
<h4 className="text-xs uppercase tracking-widest text-brand-red font-semibold">Company</h4>
<ul className="mt-4 space-y-2 text-sm">
<li><Link to="/enquiry" className="hover:text-brand-red">Enquiry</Link></li>
<li><Link to="/contact" className="hover:text-brand-red">Contact</Link></li>
<li><Link to="/rental" className="hover:text-brand-red">Rental</Link></li>
</ul>
</div>
<div>
<h4 className="text-xs uppercase tracking-widest text-brand-red font-semibold">Contact</h4>
<ul className="mt-4 space-y-2 text-sm text-muted-foreground">
<li>+971 4 251 5596</li>
<li>+971 55 209 8628</li>
<li>Dubai, United Arab Emirates</li>
</ul>
</div>
</div>
<div className="border-t">
<div className="container-page py-5 text-xs text-muted-foreground flex flex-wrap justify-between gap-2">
<span>© {new Date().getFullYear()} Promist Misting Systems. All rights reserved.</span>
<span>Humidity and Control</span>
</div>
</div>
</footer>
);
}
function NotFoundComponent() { function NotFoundComponent() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background px-4"> <div className="flex min-h-[70vh] items-center justify-center px-4">
<div className="max-w-md text-center"> <div className="max-w-md text-center">
<h1 className="text-7xl font-bold text-foreground">404</h1> <h1 className="text-7xl font-black text-brand-red">404</h1>
<h2 className="mt-4 text-xl font-semibold text-foreground">Page not found</h2> <h2 className="mt-4 text-xl font-semibold">Page not found</h2>
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">
The page you're looking for doesn't exist or has been moved. The page you're looking for doesn't exist or has been moved.
</p> </p>
<div className="mt-6"> <Link to="/" className="mt-6 inline-flex rounded-md bg-brand-red text-white px-5 py-2.5 text-sm font-medium hover:opacity-90">
<Link Go home
to="/" </Link>
className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
>
Go home
</Link>
</div>
</div> </div>
</div> </div>
); );
@@ -40,32 +170,17 @@ function ErrorComponent({ error, reset }: { error: Error; reset: () => void }) {
useEffect(() => { useEffect(() => {
reportLovableError(error, { boundary: "tanstack_root_error_component" }); reportLovableError(error, { boundary: "tanstack_root_error_component" });
}, [error]); }, [error]);
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background px-4"> <div className="flex min-h-[70vh] items-center justify-center px-4">
<div className="max-w-md text-center"> <div className="max-w-md text-center">
<h1 className="text-xl font-semibold tracking-tight text-foreground"> <h1 className="text-xl font-semibold">This page didn't load</h1>
This page didn't load <p className="mt-2 text-sm text-muted-foreground">Something went wrong. Try again or head home.</p>
</h1> <div className="mt-6 flex justify-center gap-2">
<p className="mt-2 text-sm text-muted-foreground">
Something went wrong on our end. You can try refreshing or head back home.
</p>
<div className="mt-6 flex flex-wrap justify-center gap-2">
<button <button
onClick={() => { onClick={() => { router.invalidate(); reset(); }}
router.invalidate(); className="rounded-md bg-brand-red text-white px-4 py-2 text-sm font-medium"
reset(); >Try again</button>
}} <a href="/" className="rounded-md border px-4 py-2 text-sm font-medium">Go home</a>
className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
>
Try again
</button>
<a
href="/"
className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent"
>
Go home
</a>
</div> </div>
</div> </div>
</div> </div>
@@ -77,21 +192,19 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
meta: [ meta: [
{ charSet: "utf-8" }, { charSet: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" }, { name: "viewport", content: "width=device-width, initial-scale=1" },
{ title: "Lovable App" }, { title: "Promist Misting Systems — Humidity and Control" },
{ name: "description", content: "Lovable Generated Project" }, { name: "description", content: "High-pressure misting systems for outdoor cooling, humidification, dust suppression and sanitization in the UAE since 1979." },
{ name: "author", content: "Lovable" }, { property: "og:title", content: "Promist Misting Systems" },
{ property: "og:title", content: "Lovable App" }, { property: "og:description", content: "High-pressure misting for cooling, humidification and dust suppression." },
{ property: "og:description", content: "Lovable Generated Project" },
{ property: "og:type", content: "website" }, { property: "og:type", content: "website" },
{ name: "twitter:card", content: "summary_large_image" }, { name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:site", content: "@Lovable" },
], ],
links: [ links: [
{ { rel: "stylesheet", href: appCss },
rel: "stylesheet",
href: appCss,
},
{ rel: "icon", href: "/favicon.ico", type: "image/x-icon" }, { rel: "icon", href: "/favicon.ico", type: "image/x-icon" },
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
{ rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
{ rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@300;400;500;600;700;800;900&display=swap" },
], ],
}), }),
shellComponent: RootShell, shellComponent: RootShell,
@@ -103,24 +216,22 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
function RootShell({ children }: { children: ReactNode }) { function RootShell({ children }: { children: ReactNode }) {
return ( return (
<html lang="en"> <html lang="en">
<head> <head><HeadContent /></head>
<HeadContent /> <body>{children}<Scripts /></body>
</head>
<body>
{children}
<Scripts />
</body>
</html> </html>
); );
} }
function RootComponent() { function RootComponent() {
const { queryClient } = Route.useRouteContext(); const { queryClient } = Route.useRouteContext();
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
{/* Required: nested routes render here. Removing <Outlet /> breaks all child routes. */} <TopBar />
<Outlet /> <Header />
<main>
<Outlet />
</main>
<Footer />
</QueryClientProvider> </QueryClientProvider>
); );
} }

56
src/routes/about.tsx Normal file
View File

@@ -0,0 +1,56 @@
import { createFileRoute } from "@tanstack/react-router";
import { PageHero, Section, CTA } from "@/components/page-shell";
export const Route = createFileRoute("/about")({
head: () => ({
meta: [
{ title: "About Promist — Misting Systems Since 1979" },
{ name: "description", content: "Promist manufactures and sells high-quality misting systems and misting fans at factory-direct prices since 1979." },
{ property: "og:title", content: "About Promist" },
{ property: "og:description", content: "Misting systems and misting fans manufactured since 1979." },
],
}),
component: About,
});
function About() {
return (
<>
<PageHero
eyebrow="About us"
title="Manufacturers of high-pressure misting since 1979."
lede="We design, build and install misting systems for cooling, humidification, dust suppression and sanitization — from residential patios to industrial factories."
/>
<Section>
<div className="grid lg:grid-cols-3 gap-12">
<div className="lg:col-span-2 space-y-6 text-lg text-muted-foreground">
<p>
For more than four decades, Promist has been engineering high-pressure misting systems for the harshest climates in
the region. Our systems force water through specially designed nozzles, creating an ultra-fine mist that evaporates
instantly to cool the surrounding air.
</p>
<p>
We manufacture and sell at factory-direct prices, giving our customers full control over quality, delivery and cost.
From the smallest residential patio to full industrial deployments, every project is engineered from first principles.
</p>
</div>
<div className="border-l pl-8 space-y-6">
<div>
<div className="text-xs uppercase tracking-widest text-brand-red font-semibold">Mission</div>
<p className="mt-2">Deliver reliable humidity and cooling control, everywhere it's needed.</p>
</div>
<div>
<div className="text-xs uppercase tracking-widest text-brand-red font-semibold">Vision</div>
<p className="mt-2">To be the region's most trusted misting technology partner.</p>
</div>
<div>
<div className="text-xs uppercase tracking-widest text-brand-red font-semibold">Values</div>
<p className="mt-2">Craft, quality, and unwavering customer service.</p>
</div>
</div>
</div>
</Section>
<CTA />
</>
);
}

View File

@@ -0,0 +1,42 @@
import { createFileRoute } from "@tanstack/react-router";
import { PageHero, Section, FeatureRow, CTA } from "@/components/page-shell";
import { Droplets, Wind, Factory, Sparkles, TreePine, Warehouse } from "lucide-react";
export const Route = createFileRoute("/applications")({
head: () => ({
meta: [
{ title: "Applications — Promist Misting Systems" },
{ name: "description", content: "Cost-effective misting solutions for industrial, commercial, residential, warehouses, greenhouses, gardens, patios and more." },
{ property: "og:title", content: "Applications" },
{ property: "og:description", content: "Misting applications across industrial, commercial and residential settings." },
],
}),
component: Applications,
});
const APPS = [
{ img: "https://promistcool.com/wp-content/uploads/2016/03/humidification-misting.jpg", title: "Humidification", body: "Commercial and industrial environments where controlling humidity or temperature provides enhanced profitability, product quality and process reliability.", Icon: Droplets },
{ img: "https://promistcool.com/wp-content/uploads/2016/03/commercial-misting.jpg", title: "Commercial Outdoor Cooling", body: "When temperatures rise into the 45s and above, patio dining and outdoor bar areas become uninhabitable money losers. Misting brings them back to life.", Icon: Wind, reverse: true },
{ img: "https://promistcool.com/wp-content/uploads/2016/03/industrial-misting.jpg", title: "Industrial Cooling", body: "Create a safer and more productive working environment. Cooling employees, equipment and processes across factories and warehouses.", Icon: Factory },
{ img: "https://promistcool.com/wp-content/uploads/2020/04/banner-sanitz.jpg", title: "Special Effects", body: "Showcase incredible landscapes, swimming pools, events, expos, amusement parks and sports events with an atmospheric mist.", Icon: Sparkles, reverse: true },
{ img: "https://promistcool.com/wp-content/uploads/2017/04/new-banner.jpg", title: "Greenhouses & Gardens", body: "Precise humidity control keeps plants healthy, improves yields and reduces water consumption compared to traditional irrigation.", Icon: TreePine },
{ img: "https://promistcool.com/wp-content/uploads/2017/04/banner4.jpg", title: "Warehouses & Factories", body: "Suppress airborne dust, cool loading bays and keep goods in ideal conditions — even during the hottest months.", Icon: Warehouse, reverse: true },
];
function Applications() {
return (
<>
<PageHero
eyebrow="Applications"
title="A cost-effective solution for cooling and humidification."
lede="Promist misting systems adapt to industrial, commercial, residential, warehouses, greenhouses, gardens, zoos, patios, parks and amusement areas."
/>
<Section>
<div className="space-y-24">
{APPS.map((a) => <FeatureRow key={a.title} {...a} />)}
</div>
</Section>
<CTA />
</>
);
}

41
src/routes/contact.tsx Normal file
View File

@@ -0,0 +1,41 @@
import { createFileRoute } from "@tanstack/react-router";
import { PageHero, Section, CTA } from "@/components/page-shell";
import { Phone, Mail, MapPin } from "lucide-react";
export const Route = createFileRoute("/contact")({
head: () => ({
meta: [
{ title: "Contact Promist — Dubai, UAE" },
{ name: "description", content: "Contact Promist for misting system enquiries, service and support in the UAE." },
{ property: "og:title", content: "Contact Promist" },
{ property: "og:description", content: "Reach the Promist team in Dubai, UAE." },
],
}),
component: Contact,
});
function Contact() {
return (
<>
<PageHero eyebrow="Contact" title="Get in touch." lede="Our team is available across office hours to answer questions and provide quotes." />
<Section>
<div className="grid md:grid-cols-3 gap-10">
{[
{ I: Phone, t: "Call", l: ["+971 4 251 5596", "+971 55 209 8628"] },
{ I: Mail, t: "Email", l: ["info@promistcool.com", "sales@promistcool.com"] },
{ I: MapPin, t: "Visit", l: ["Dubai, United Arab Emirates"] },
].map((c) => (
<div key={c.t} className="border-t pt-6">
<c.I className="h-6 w-6 text-brand-red" />
<h3 className="mt-4 font-display text-2xl font-black">{c.t}</h3>
<div className="mt-3 space-y-1 text-muted-foreground">
{c.l.map((line) => <p key={line}>{line}</p>)}
</div>
</div>
))}
</div>
</Section>
<CTA />
</>
);
}

71
src/routes/enquiry.tsx Normal file
View File

@@ -0,0 +1,71 @@
import { createFileRoute } from "@tanstack/react-router";
import { PageHero, Section } from "@/components/page-shell";
import { useState } from "react";
import { ArrowUpRight } from "lucide-react";
export const Route = createFileRoute("/enquiry")({
head: () => ({
meta: [
{ title: "Enquiry — Request a Misting System Quote" },
{ name: "description", content: "Send a quick enquiry to Promist for a misting system quote or consultation." },
{ property: "og:title", content: "Enquiry" },
{ property: "og:description", content: "Request a Promist misting system quote." },
],
}),
component: Enquiry,
});
function Enquiry() {
const [sent, setSent] = useState(false);
return (
<>
<PageHero
eyebrow="Enquiry"
title="Tell us about your project."
lede="A Promist engineer will get back to you with sizing, pricing and delivery details."
/>
<Section>
<div className="grid lg:grid-cols-5 gap-16">
<form
className="lg:col-span-3 space-y-6"
onSubmit={(e) => { e.preventDefault(); setSent(true); }}
>
{[
{ l: "Full name", n: "name", t: "text" },
{ l: "Email address", n: "email", t: "email" },
{ l: "Phone", n: "phone", t: "tel" },
{ l: "Company (optional)", n: "company", t: "text" },
].map((f) => (
<label key={f.n} className="block">
<span className="text-xs uppercase tracking-widest text-muted-foreground">{f.l}</span>
<input required={f.n !== "company"} type={f.t} name={f.n} className="mt-2 w-full border-b bg-transparent py-3 outline-none focus:border-brand-red" />
</label>
))}
<label className="block">
<span className="text-xs uppercase tracking-widest text-muted-foreground">How can we help?</span>
<textarea required name="message" rows={5} className="mt-2 w-full border-b bg-transparent py-3 outline-none focus:border-brand-red resize-none" />
</label>
<button type="submit" className="inline-flex items-center gap-2 bg-brand-red text-white px-6 py-3.5 rounded-md text-sm font-semibold hover:opacity-90">
{sent ? "Thanks — we'll be in touch" : "Submit enquiry"} <ArrowUpRight className="h-4 w-4" />
</button>
</form>
<aside className="lg:col-span-2 border-l pl-10 space-y-8">
<div>
<div className="text-xs uppercase tracking-widest text-brand-red font-semibold">Call</div>
<p className="mt-2 font-display text-2xl font-bold">+971 4 251 5596</p>
<p className="font-display text-2xl font-bold">+971 55 209 8628</p>
</div>
<div>
<div className="text-xs uppercase tracking-widest text-brand-red font-semibold">Email</div>
<p className="mt-2">info@promistcool.com</p>
</div>
<div>
<div className="text-xs uppercase tracking-widest text-brand-red font-semibold">Location</div>
<p className="mt-2">Dubai, United Arab Emirates</p>
</div>
</aside>
</div>
</Section>
</>
);
}

View File

@@ -1,24 +1,270 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, Link } from "@tanstack/react-router";
import { ArrowUpRight, Droplets, Wind, Factory, Sparkles, Gauge, ShieldCheck } from "lucide-react";
// No head() here: the home route inherits title/description/og/twitter from
// __root.tsx, and ships no og:image so serve-time hosting can inject the
// project's social preview (explicit og:image or latest screenshot).
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: Index, head: () => ({
meta: [
{ title: "Promist — High-Pressure Misting for Cooling & Humidity" },
{ name: "description", content: "Turnkey misting systems for outdoor cooling, humidification, dust suppression, sanitization and special effects. Manufactured in the UAE since 1979." },
{ property: "og:title", content: "Promist — Humidity and Control" },
{ property: "og:description", content: "Misting systems for cooling, humidification and dust suppression." },
],
}),
component: Home,
}); });
// IMPORTANT: Replace this placeholder. See ./README.md for routing conventions. const IMG = {
function Index() { nozzle: "https://promistcool.com/wp-content/uploads/2020/04/banner-sanitz.jpg",
booth: "https://promistcool.com/wp-content/uploads/2020/04/image-002.png",
humid: "https://promistcool.com/wp-content/uploads/2016/03/humidification-misting.jpg",
commercial: "https://promistcool.com/wp-content/uploads/2016/03/commercial-misting.jpg",
industrial: "https://promistcool.com/wp-content/uploads/2016/03/industrial-misting.jpg",
project1: "https://promistcool.com/wp-content/uploads/2017/04/banner4.jpg",
project2: "https://promistcool.com/wp-content/uploads/2017/04/banner2.jpg",
project3: "https://promistcool.com/wp-content/uploads/2017/04/banner3.jpg",
banner: "https://promistcool.com/wp-content/uploads/2017/04/new-banner.jpg",
};
function Home() {
return ( return (
<div <>
className="flex min-h-screen items-center justify-center" {/* HERO */}
style={{ backgroundColor: "#fcfbf8" }} <section className="relative overflow-hidden border-b">
> <div className="container-page grid lg:grid-cols-12 gap-10 py-16 lg:py-24 items-center">
<img <div className="lg:col-span-6">
data-lovable-blank-page-placeholder="REMOVE_THIS" <div className="inline-flex items-center gap-2 text-xs uppercase tracking-[0.25em] text-brand-blue font-semibold">
src="https://cdn.gpteng.co/blank-app-v1.svg" <span className="rule-blue" /> Since 1979
alt="Your app will live here!" </div>
/> <h1 className="mt-6 font-display text-5xl md:text-7xl font-black leading-[0.95]">
</div> Humidity
<br />
<span className="text-brand-blue">and</span>{" "}
<span className="text-brand-red italic">Control.</span>
</h1>
<p className="mt-6 text-lg text-muted-foreground max-w-xl">
We engineer high-pressure misting systems that cool, humidify, suppress dust and sanitize
from residential patios to industrial factories across the region.
</p>
<div className="mt-8 flex flex-wrap gap-3">
<Link to="/enquiry" className="inline-flex items-center gap-2 bg-brand-red text-white px-6 py-3.5 rounded-md text-sm font-semibold hover:opacity-90">
Request a quote <ArrowUpRight className="h-4 w-4" />
</Link>
<Link to="/products" className="inline-flex items-center gap-2 border border-foreground/20 px-6 py-3.5 rounded-md text-sm font-semibold hover:border-brand-blue hover:text-brand-blue">
Explore products
</Link>
</div>
<dl className="mt-12 grid grid-cols-3 gap-6 max-w-lg">
{[
["45+", "Years of expertise"],
["1000+", "Projects delivered"],
["70 bar", "High-pressure precision"],
].map(([k, v]) => (
<div key={k}>
<dt className="font-display text-3xl font-black text-brand-red">{k}</dt>
<dd className="text-xs uppercase tracking-wider text-muted-foreground mt-1">{v}</dd>
</div>
))}
</dl>
</div>
<div className="lg:col-span-6">
<div className="relative">
<div className="absolute -inset-4 bg-gradient-to-br from-brand-blue/10 to-brand-red/10 blur-2xl" />
<img src={IMG.nozzle} alt="Promist misting nozzle" className="relative w-full h-[520px] object-cover" />
<div className="absolute bottom-0 left-0 right-0 bg-background/90 backdrop-blur border-t border-l border-r px-6 py-4 flex items-center justify-between">
<div>
<div className="text-xs uppercase tracking-widest text-muted-foreground">Featured</div>
<div className="font-display text-xl font-bold">Sanitization Booth</div>
</div>
<Link to="/products" className="text-sm font-semibold text-brand-red inline-flex items-center gap-1">
View <ArrowUpRight className="h-4 w-4" />
</Link>
</div>
</div>
</div>
</div>
</section>
{/* APPLICATIONS */}
<section className="py-24">
<div className="container-page">
<div className="flex flex-wrap items-end justify-between gap-6 mb-14">
<div>
<span className="rule-red" />
<h2 className="mt-4 font-display text-4xl md:text-5xl font-black">Applications</h2>
<p className="mt-4 max-w-2xl text-muted-foreground">
A cost-effective solution for outdoor cooling and humidification across industrial, commercial,
residential, warehouses, greenhouses, gardens, patios, parks and amusement areas.
</p>
</div>
<Link to="/applications" className="text-sm font-semibold text-brand-blue inline-flex items-center gap-1">
All applications <ArrowUpRight className="h-4 w-4" />
</Link>
</div>
<div className="grid md:grid-cols-3 divide-y md:divide-y-0 md:divide-x border-y">
{[
{ img: IMG.humid, title: "Humidification", desc: "Commercial and industrial environments where controlled humidity or temperature drive profitability.", icon: Droplets },
{ img: IMG.commercial, title: "Commercial Outdoor Cooling", desc: "Reclaim patio dining and outdoor bars once temperatures push past 45°C.", icon: Wind },
{ img: IMG.industrial, title: "Industrial Cooling", desc: "A safer, more productive floor — cooling employees, equipment and processes.", icon: Factory },
].map((a) => (
<article key={a.title} className="p-8 group">
<div className="aspect-[4/3] overflow-hidden">
<img src={a.img} alt={a.title} className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" />
</div>
<div className="mt-6 flex items-center gap-3">
<a.icon className="h-5 w-5 text-brand-red" />
<h3 className="font-display text-xl font-bold">{a.title}</h3>
</div>
<p className="mt-3 text-sm text-muted-foreground">{a.desc}</p>
<Link to="/applications" className="mt-5 inline-flex items-center gap-1 text-sm font-semibold text-brand-red">
Details <ArrowUpRight className="h-4 w-4" />
</Link>
</article>
))}
</div>
</div>
</section>
{/* SANITIZATION BOOTH FEATURE */}
<section className="bg-muted/40 py-24 border-y">
<div className="container-page grid lg:grid-cols-2 gap-14 items-center">
<div>
<span className="rule-blue" />
<h2 className="mt-4 font-display text-4xl md:text-5xl font-black">Sanitization Booth</h2>
<p className="mt-4 text-muted-foreground max-w-lg">
A turnkey solution for your disinfectant needs engineered for continuous public use.
</p>
<ul className="mt-8 grid sm:grid-cols-2 gap-x-8 gap-y-4 text-sm">
{[
"Transparent plastic cover with aluminium structure",
"Electric control panel",
"Optimised water consumption",
"Reflective sensor",
"12-month warranty from installation",
"234 × 110 × 238 cm",
].map((f) => (
<li key={f} className="flex gap-3 border-b border-dashed pb-3">
<span className="text-brand-red font-black">/</span>
<span>{f}</span>
</li>
))}
</ul>
<Link to="/products" className="mt-8 inline-flex items-center gap-2 bg-brand-blue text-white px-6 py-3 rounded-md text-sm font-semibold hover:opacity-90">
View details <ArrowUpRight className="h-4 w-4" />
</Link>
</div>
<div>
<img src={IMG.booth} alt="Sanitization Booth" className="w-full max-h-[560px] object-contain" />
</div>
</div>
</section>
{/* 5 REASONS */}
<section className="py-24">
<div className="container-page">
<div className="max-w-2xl">
<span className="rule-red" />
<h2 className="mt-4 font-display text-4xl md:text-5xl font-black">
Five great reasons <span className="text-brand-blue">to choose us</span>
</h2>
</div>
<ol className="mt-14 grid md:grid-cols-5 gap-0 border-t">
{[
"Best product quality",
"100% customer satisfaction",
"Comprehensive range of services",
"Always available for you",
"Services guarantee",
].map((r, i) => (
<li key={r} className="border-b md:border-b-0 md:border-r last:md:border-r-0 p-8">
<div className="font-display text-5xl font-black text-brand-red/20">
0{i + 1}
</div>
<div className="mt-4 font-display text-lg font-bold leading-snug">{r}</div>
</li>
))}
</ol>
</div>
</section>
{/* FULL SERVICES */}
<section className="bg-foreground text-background py-24">
<div className="container-page">
<div className="grid lg:grid-cols-12 gap-10 items-start">
<div className="lg:col-span-4">
<span className="rule-blue" />
<h2 className="mt-4 font-display text-4xl md:text-5xl font-black">
Promist is a<br />full-service<br />
<span className="text-brand-red italic">misting technology</span>
</h2>
<p className="mt-6 text-background/70 max-w-sm">
By forcing water through specially designed nozzles at high pressure, we create an ultra-fine
mist the science behind every one of our systems.
</p>
</div>
<div className="lg:col-span-8 grid sm:grid-cols-2 gap-x-10 gap-y-10">
{[
{ i: Droplets, t: "Humidification", d: "The most efficient way to keep the right humidity levels for food, materials and processes." },
{ i: Sparkles, t: "Special effects", d: "Elevate landscapes, pools, events, expos, amusement parks and sports events." },
{ i: Gauge, t: "Cooling principle", d: "Billions of tiny droplets evaporate into vapor — the phase change cools the surrounding air." },
{ i: ShieldCheck, t: "Dust suppression", d: "Extremely effective solutions for airborne dust suppression and air filtration." },
].map((s) => (
<div key={s.t} className="border-t border-background/20 pt-6">
<s.i className="h-6 w-6 text-brand-red" />
<h3 className="mt-4 font-display text-2xl font-bold">{s.t}</h3>
<p className="mt-2 text-background/70 text-sm">{s.d}</p>
</div>
))}
</div>
</div>
</div>
</section>
{/* PROJECTS */}
<section className="py-24">
<div className="container-page">
<div className="flex flex-wrap items-end justify-between gap-6 mb-12">
<div>
<span className="rule-red" />
<h2 className="mt-4 font-display text-4xl md:text-5xl font-black">Recent projects</h2>
</div>
<Link to="/projects" className="text-sm font-semibold text-brand-blue inline-flex items-center gap-1">
View more <ArrowUpRight className="h-4 w-4" />
</Link>
</div>
<div className="grid md:grid-cols-3 gap-1">
{[IMG.project1, IMG.project2, IMG.project3].map((src, i) => (
<a key={src} href={src} className="group relative overflow-hidden aspect-[4/5]">
<img src={src} alt={`Project ${i + 1}`} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />
<div className="absolute inset-0 bg-gradient-to-t from-brand-red/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="absolute bottom-0 left-0 p-6 text-white translate-y-4 group-hover:translate-y-0 opacity-0 group-hover:opacity-100 transition-all">
<div className="text-xs uppercase tracking-widest">Case study</div>
<div className="font-display text-2xl font-bold">Project 0{i + 1}</div>
</div>
</a>
))}
</div>
</div>
</section>
{/* CONTACT BAND */}
<section className="relative overflow-hidden">
<img src={IMG.banner} alt="" className="absolute inset-0 w-full h-full object-cover" />
<div className="absolute inset-0 bg-brand-blue-dark/80" />
<div className="container-page relative py-20 grid md:grid-cols-2 gap-8 items-center text-white">
<div>
<span className="rule-red" />
<h2 className="mt-4 font-display text-4xl md:text-5xl font-black">
Questions? <br />Call now: <span className="text-white/90">+971 4 251 5596</span>
</h2>
</div>
<div className="flex md:justify-end">
<Link to="/enquiry" className="inline-flex items-center gap-2 bg-brand-red text-white px-8 py-4 rounded-md text-sm font-semibold hover:opacity-90">
Request a quote <ArrowUpRight className="h-4 w-4" />
</Link>
</div>
</div>
</section>
</>
); );
} }

49
src/routes/products.tsx Normal file
View File

@@ -0,0 +1,49 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { PageHero, Section, CTA } from "@/components/page-shell";
import { ArrowUpRight } from "lucide-react";
export const Route = createFileRoute("/products")({
head: () => ({
meta: [
{ title: "Products — Nozzles, Pumps & Sanitization Booth" },
{ name: "description", content: "High-pressure pumps, misting nozzles, control panels and the Promist Sanitization Booth." },
{ property: "og:title", content: "Promist Products" },
{ property: "og:description", content: "Pumps, nozzles, panels and sanitization systems." },
],
}),
component: Products,
});
const PRODUCTS = [
{ img: "https://promistcool.com/wp-content/uploads/2020/04/banner-sanitz.jpg", title: "Misting Nozzles", desc: "Precision brass and stainless-steel nozzles engineered for ultra-fine mist and long service life." },
{ img: "https://promistcool.com/wp-content/uploads/2017/04/new-banner.jpg", title: "High-Pressure Pumps", desc: "Reliable pumps up to 70 bar for residential, commercial and industrial deployments." },
{ img: "https://promistcool.com/wp-content/uploads/2020/04/image-002.png", title: "Sanitization Booth", desc: "Turnkey walk-through booth with electric control panel, reflective sensor and 12-month warranty." },
{ img: "https://promistcool.com/wp-content/uploads/2017/04/banner4.jpg", title: "Misting Fans", desc: "Portable and fixed misting fans for events, patios, factories and open spaces." },
];
function Products() {
return (
<>
<PageHero
eyebrow="Products"
title="Engineered components. Complete systems."
lede="Every part of a Promist system — from nozzle to pump to control panel — is built to work as one."
/>
<Section>
<div className="grid md:grid-cols-2 gap-x-14 gap-y-20">
{PRODUCTS.map((p) => (
<article key={p.title}>
<img src={p.img} alt={p.title} className="w-full h-[320px] object-cover" />
<h3 className="mt-6 font-display text-2xl font-black">{p.title}</h3>
<p className="mt-3 text-muted-foreground">{p.desc}</p>
<Link to="/enquiry" className="mt-4 inline-flex items-center gap-1 text-sm font-semibold text-brand-red">
Enquire <ArrowUpRight className="h-4 w-4" />
</Link>
</article>
))}
</div>
</Section>
<CTA />
</>
);
}

48
src/routes/projects.tsx Normal file
View File

@@ -0,0 +1,48 @@
import { createFileRoute } from "@tanstack/react-router";
import { PageHero, Section, CTA } from "@/components/page-shell";
export const Route = createFileRoute("/projects")({
head: () => ({
meta: [
{ title: "Projects — Promist Misting Installations" },
{ name: "description", content: "A selection of misting system installations delivered by Promist across the region." },
{ property: "og:title", content: "Projects" },
{ property: "og:description", content: "Recent Promist misting installations." },
],
}),
component: Projects,
});
const IMGS = [
"https://promistcool.com/wp-content/uploads/2017/04/banner4.jpg",
"https://promistcool.com/wp-content/uploads/2017/04/banner2.jpg",
"https://promistcool.com/wp-content/uploads/2017/04/banner3.jpg",
"https://promistcool.com/wp-content/uploads/2020/04/banner-sanitz.jpg",
"https://promistcool.com/wp-content/uploads/2017/04/new-banner.jpg",
"https://promistcool.com/wp-content/uploads/2016/03/commercial-misting.jpg",
"https://promistcool.com/wp-content/uploads/2016/03/industrial-misting.jpg",
"https://promistcool.com/wp-content/uploads/2016/03/humidification-misting.jpg",
];
function Projects() {
return (
<>
<PageHero
eyebrow="Projects"
title="Selected installations."
lede="A cross-section of Promist deployments — residential, commercial and industrial."
/>
<Section>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-1">
{IMGS.map((src, i) => (
<a key={src} href={src} className="group relative overflow-hidden aspect-square">
<img src={src} alt={`Promist project ${i + 1}`} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />
<div className="absolute inset-0 bg-brand-red/0 group-hover:bg-brand-red/30 transition-colors" />
</a>
))}
</div>
</Section>
<CTA />
</>
);
}

52
src/routes/rental.tsx Normal file
View File

@@ -0,0 +1,52 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { PageHero, Section } from "@/components/page-shell";
import { ArrowUpRight } from "lucide-react";
export const Route = createFileRoute("/rental")({
head: () => ({
meta: [
{ title: "Rental — Misting Systems for Events & Short-Term Use" },
{ name: "description", content: "Rent Promist misting fans and systems for events, expos, weddings and short-term outdoor cooling needs." },
{ property: "og:title", content: "Promist Rental" },
{ property: "og:description", content: "Short-term misting rentals for events and outdoor spaces." },
],
}),
component: Rental,
});
function Rental() {
return (
<>
<PageHero
eyebrow="Rental"
title="Cool on demand — for a day, a week or a season."
lede="Rent complete Promist misting systems and fans for events, expos, weddings, film sets and temporary outdoor installations."
/>
<Section>
<div className="grid lg:grid-cols-2 gap-14">
<div>
<img src="https://promistcool.com/wp-content/uploads/2017/04/new-banner.jpg" alt="Rental misting" className="w-full h-[440px] object-cover" />
</div>
<div>
<ul className="space-y-4">
{[
"Delivery, installation and pickup included",
"Flexible terms — daily, weekly, monthly",
"Portable fans and full pump-driven systems",
"24/7 technical support during rental period",
"Custom sizing based on your site plan",
].map((f) => (
<li key={f} className="flex gap-4 border-b pb-4">
<span className="text-brand-red font-black">/</span><span>{f}</span>
</li>
))}
</ul>
<Link to="/enquiry" className="mt-8 inline-flex items-center gap-2 bg-brand-red text-white px-6 py-3.5 rounded-md text-sm font-semibold">
Request rental pricing <ArrowUpRight className="h-4 w-4" />
</Link>
</div>
</div>
</Section>
</>
);
}

48
src/routes/solutions.tsx Normal file
View File

@@ -0,0 +1,48 @@
import { createFileRoute } from "@tanstack/react-router";
import { PageHero, Section, CTA } from "@/components/page-shell";
import { Droplets, Sparkles, Gauge, ShieldCheck, Wind, Factory } from "lucide-react";
export const Route = createFileRoute("/solutions")({
head: () => ({
meta: [
{ title: "Solutions — Misting Technology by Promist" },
{ name: "description", content: "Humidification, cooling, dust suppression and special effects — engineered by Promist." },
{ property: "og:title", content: "Solutions" },
{ property: "og:description", content: "Full-service misting technology solutions." },
],
}),
component: Solutions,
});
const S = [
{ i: Droplets, t: "Misting Technology", d: "By forcing water at high pressure through specially designed misting nozzles, we create an ultra-fine mist that evaporates instantly." },
{ i: Gauge, t: "Cooling Principle", d: "Billions of tiny water droplets evaporate, becoming vapor. The energy used to change water to gas cools the surrounding air." },
{ i: ShieldCheck, t: "Humidification", d: "The most efficient way to keep the right humidity levels for food, materials and industrial processes." },
{ i: Wind, t: "Dust Suppression", d: "Extremely effective airborne dust suppression and air filtration for factories, warehouses and outdoor sites." },
{ i: Sparkles, t: "Special Effects", d: "Elevate landscapes, pools, events, expos, amusement parks and sports events with atmospheric mist." },
{ i: Factory, t: "Industrial Systems", d: "Custom engineered high-pressure systems designed to run continuously in demanding industrial environments." },
];
function Solutions() {
return (
<>
<PageHero
eyebrow="Solutions"
title="Promist is a full-service misting technology partner."
lede="From concept to commissioning, we deliver misting systems that just work — every day, in every climate."
/>
<Section>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-x-12 gap-y-14 border-t pt-14">
{S.map((x) => (
<div key={x.t} className="border-b pb-8">
<x.i className="h-7 w-7 text-brand-red" />
<h3 className="mt-5 font-display text-2xl font-black">{x.t}</h3>
<p className="mt-3 text-muted-foreground">{x.d}</p>
</div>
))}
</div>
</Section>
<CTA />
</>
);
}

View File

@@ -4,141 +4,67 @@
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
/*
* Design system definition.
*
* The @theme inline block maps CSS custom properties to Tailwind utility
* classes (e.g. --color-primary -> bg-primary, text-primary).
*
* The :root and .dark blocks define the actual color values using oklch.
* All colors MUST use oklch format.
*
* To add a new semantic color:
* 1. Add the variable to :root (light value) and .dark (dark value)
* 2. Register it in @theme inline as --color-<name>: var(--<name>)
*/
@theme inline { @theme inline {
--radius-sm: calc(var(--radius) - 4px); --font-sans: "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif;
--radius-md: calc(var(--radius) - 2px); --font-display: "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif;
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted); --color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground); --color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent); --color-brand-red: var(--brand-red);
--color-accent-foreground: var(--accent-foreground); --color-brand-blue: var(--brand-blue);
--color-destructive: var(--destructive); --color-brand-blue-dark: var(--brand-blue-dark);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border); --color-border: var(--border);
--color-input: var(--input); --color-primary: var(--brand-red);
--color-ring: var(--ring); --color-primary-foreground: oklch(1 0 0);
--color-ring-offset-background: var(--background); --color-accent: var(--brand-blue);
--color-chart-1: var(--chart-1); --color-accent-foreground: oklch(1 0 0);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
} }
:root { :root {
--radius: 0.625rem;
--background: oklch(1 0 0); --background: oklch(1 0 0);
--foreground: oklch(0.129 0.042 264.695); --foreground: oklch(0.18 0.02 250);
--card: oklch(1 0 0); --muted: oklch(0.97 0.005 250);
--card-foreground: oklch(0.129 0.042 264.695); --muted-foreground: oklch(0.45 0.02 250);
--popover: oklch(1 0 0); --border: oklch(0.9 0.01 250);
--popover-foreground: oklch(0.129 0.042 264.695);
--primary: oklch(0.208 0.042 265.755);
--primary-foreground: oklch(0.984 0.003 247.858);
--secondary: oklch(0.968 0.007 247.896);
--secondary-foreground: oklch(0.208 0.042 265.755);
--muted: oklch(0.968 0.007 247.896);
--muted-foreground: oklch(0.554 0.046 257.417);
--accent: oklch(0.968 0.007 247.896);
--accent-foreground: oklch(0.208 0.042 265.755);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.984 0.003 247.858);
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.984 0.003 247.858);
--sidebar-foreground: oklch(0.129 0.042 264.695);
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-ring: oklch(0.704 0.04 256.788);
}
.dark { --brand-red: oklch(0.58 0.22 27); /* Promist red */
--background: oklch(0.129 0.042 264.695); --brand-blue: oklch(0.62 0.16 240); /* Promist blue */
--foreground: oklch(0.984 0.003 247.858); --brand-blue-dark: oklch(0.42 0.14 245);
--card: oklch(0.208 0.042 265.755);
--card-foreground: oklch(0.984 0.003 247.858);
--popover: oklch(0.208 0.042 265.755);
--popover-foreground: oklch(0.984 0.003 247.858);
--primary: oklch(0.929 0.013 255.508);
--primary-foreground: oklch(0.208 0.042 265.755);
--secondary: oklch(0.279 0.041 260.031);
--secondary-foreground: oklch(0.984 0.003 247.858);
--muted: oklch(0.279 0.041 260.031);
--muted-foreground: oklch(0.704 0.04 256.788);
--accent: oklch(0.279 0.041 260.031);
--accent-foreground: oklch(0.984 0.003 247.858);
--destructive: oklch(0.704 0.191 22.216);
--destructive-foreground: oklch(0.984 0.003 247.858);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.208 0.042 265.755);
--sidebar-foreground: oklch(0.984 0.003 247.858);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.551 0.027 264.364);
} }
@layer base { @layer base {
* { * {
border-color: var(--color-border); border-color: var(--color-border);
} }
html, body {
body {
background-color: var(--color-background); background-color: var(--color-background);
color: var(--color-foreground); color: var(--color-foreground);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 {
font-family: var(--font-display);
letter-spacing: -0.02em;
} }
} }
@utility container-page {
max-width: 1240px;
margin-inline: auto;
padding-inline: 1.5rem;
}
@utility rule-red {
display: block;
width: 64px;
height: 3px;
background: var(--brand-red);
}
@utility rule-blue {
display: block;
width: 64px;
height: 3px;
background: var(--brand-blue);
}