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:
4
bun.lock
4
bun.lock
@@ -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=="],
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
47
src/components/page-shell.tsx
Normal file
47
src/components/page-shell.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
56
src/routes/about.tsx
Normal 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 />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
42
src/routes/applications.tsx
Normal file
42
src/routes/applications.tsx
Normal 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
41
src/routes/contact.tsx
Normal 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
71
src/routes/enquiry.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
49
src/routes/products.tsx
Normal 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
48
src/routes/projects.tsx
Normal 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
52
src/routes/rental.tsx
Normal 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
48
src/routes/solutions.tsx
Normal 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 />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
160
src/styles.css
160
src/styles.css
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user