updated new fonts

This commit is contained in:
sankalp committed 2026-09-25 21:48:48 +05:45
1 parent f078b1f3fa
commit 0ddcb53af0
30 files changed
+530 -223

No files matched your search

+10 -10
View File
@@ -76,16 +76,16 @@ Content models: `Organization` (singleton with social links/map/hours), `AboutUs
Read-only JSON endpoints (paginated, page size 20):
| Endpoint | Description |
|---|---|
| `/api/homepage/hero/`, `/api/homepage/stats/`, `/api/homepage/marquee/` | Homepage sections |
| `/api/org/about/`, `vision/`, `mision/`, `values/`, `what-we-do/`, `partners/`, `sister-companies/`, `services/`, `organization/` | Organization content |
| `/api/products/` | Products (with features) |
| `/api/projects/`, `/api/events/` | Projects & events |
| `/api/team/` | Team members |
| `/api/testimonials/` | Testimonials |
| `/api/career/` | Job listings (filters: `job_time`, `location`; search) |
| `/api/contact/` | `POST` create (public); list requires admin |
| Endpoint | Description |
| --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `/api/homepage/hero/`, `/api/homepage/stats/`, `/api/homepage/marquee/` | Homepage sections |
| `/api/org/about/`, `vision/`, `mision/`, `values/`, `what-we-do/`, `partners/`, `sister-companies/`, `services/`, `organization/` | Organization content |
| `/api/products/` | Products (with features) |
| `/api/projects/`, `/api/events/` | Projects & events |
| `/api/team/` | Team members |
| `/api/testimonials/` | Testimonials |
| `/api/career/` | Job listings (filters: `job_time`, `location`; search) |
| `/api/contact/` | `POST` create (public); list requires admin |
### Website pages (frontend)
+8 -4
View File
@@ -57,8 +57,10 @@ class OrganizationAdmin(ImagePreviewMixin, SingletonAdmin):
{
"fields": (
"address",
("phone_number", "telephone_number"),
("primary_email", "secondary_email"),
"phone_number",
"telephone_number",
"primary_email",
"secondary_email",
"whatsapp_no",
),
},
@@ -67,8 +69,10 @@ class OrganizationAdmin(ImagePreviewMixin, SingletonAdmin):
"Social Media",
{
"fields": (
("facebook", "tiktok"),
("instagram", "youtube"),
"facebook",
"tiktok",
"instagram",
"youtube",
"linkedin",
),
},
+1
View File
@@ -105,6 +105,7 @@ USE_TZ = True
# Static files and media files
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "static"]
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
+5
View File
@@ -15,6 +15,11 @@
<meta name="apple-mobile-web-app-title" content="Udhamshil Nepal" />
<meta name="keywords" content="machinery nepal, agro processing, food production machinery, cold storage nepal, construction machinery, small scale machinery nepal, commercial machinery nepal, udhamsil nepal, banke machinery" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" href="{% static 'frontend/logo.png' %}" />
<title>{{ seo.title|default:"Udhamshil Nepal | Machinery built for Nepal" }}</title>
<meta name="description" content="{{ seo.description|default:'Reliable, innovative machinery solutions — exporting, manufacturing, distributing and supplying small, medium and commercial scale equipment across the nation.' }}" />
Binary file not shown.
View File
Whitespace-only changes.
+1
View File
@@ -50,6 +50,7 @@
"embla-carousel-react": "^8.6.0",
"framer-motion": "^13.4.1",
"input-otp": "^1.4.2",
"lenis": "^1.3.26",
"lucide-react": "^0.575.0",
"react": "^19.2.0",
"react-day-picker": "^9.14.0",
+21
View File
@@ -113,6 +113,9 @@ importers:
input-otp:
specifier: ^1.4.2
version: 1.5.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
lenis:
specifier: ^1.3.26
version: 1.3.26(react@19.3.0)
lucide-react:
specifier: ^0.575.0
version: 0.575.0(react@19.3.0)
@@ -2285,6 +2288,20 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
lenis@1.3.26:
resolution: {integrity: sha512-s/xTCZCxTFvHbAN1OzuhNaN5YPJH2ail0XAkctKW1b+RUAG4nUL5UHLXwNko1h8aEeT2jspBXegMgPJd8zcuag==}
peerDependencies:
'@nuxt/kit': '>=3.0.0'
react: '>=17.0.0'
vue: '>=3.0.0'
peerDependenciesMeta:
'@nuxt/kit':
optional: true
react:
optional: true
vue:
optional: true
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -4716,6 +4733,10 @@ snapshots:
dependencies:
json-buffer: 3.0.1
lenis@1.3.26(react@19.3.0):
optionalDependencies:
react: 19.3.0
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

+35 -1
View File
@@ -1,5 +1,6 @@
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
import { useEffect } from "react";
import Lenis from "lenis";
import Layout from "./pages/Layout";
import About from "./pages/About";
import CareerDetail from "./pages/CareerDetail";
@@ -10,23 +11,54 @@ import Index from "./pages/Home";
import Products from "./pages/Products";
import ProjectsEvents from "./pages/Projects-events";
import Team from "./pages/Team";
import Privacy from "./pages/Privacy";
import Terms from "./pages/Terms";
function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
// If Lenis is active, use its scrollTo method to prevent conflicts
if ((window as any).lenis) {
(window as any).lenis.scrollTo(0, { immediate: true });
} else {
window.scrollTo(0, 0);
}
}, [pathname]);
return null;
}
function SmoothScroll() {
useEffect(() => {
const lenis = new Lenis();
(window as any).lenis = lenis;
function raf(time: number) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
return () => {
lenis.destroy();
delete (window as any).lenis;
};
}, []);
return null;
}
import { AnimatePresence } from "framer-motion";
export default function App() {
const basename =
import.meta.env.VITE_ROUTER_BASENAME ||
(window.location.pathname.startsWith("/static/frontend") ? "/static/frontend" : "/");
return (
<BrowserRouter basename={basename}>
<SmoothScroll />
<ScrollToTop />
<Routes>
<Route path="/" element={<Layout />}>
@@ -40,6 +72,8 @@ export default function App() {
<Route path="products" element={<Products />} />
<Route path="projects-events" element={<ProjectsEvents />} />
<Route path="team" element={<Team />} />
<Route path="privacy" element={<Privacy />} />
<Route path="terms" element={<Terms />} />
</Route>
</Routes>
</BrowserRouter>
@@ -0,0 +1,152 @@
import { useState } from "react";
export function ContactForm({
title = "Send an enquiry",
subtitle = "We generally respond within 24 hours.",
className = "",
}: {
title?: string;
subtitle?: string;
className?: string;
}) {
const [sent, setSent] = useState(false);
const [error, setError] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSent(false);
setError(false);
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData.entries());
try {
const baseUrl = import.meta.env.VITE_API_BASE_URL || "/api";
const res = await fetch(`${baseUrl}/contact/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: data.name,
phone_no: data.phone_no,
email: data.email,
subject: data.subject || "Website Enquiry",
message: data.message,
}),
});
if (res.ok) {
setSent(true);
(e.target as HTMLFormElement).reset();
} else {
setError(true);
}
} catch (err) {
setError(true);
}
};
return (
<form
onSubmit={handleSubmit}
className={`flex flex-col gap-8 ${className}`}
>
<div className="mb-4">
<h3 className="text-4xl font-display font-black tracking-tighter uppercase text-foreground">
{title}
</h3>
{subtitle && (
<p className="mt-4 text-xs font-bold uppercase tracking-widest text-muted-foreground">
{subtitle}
</p>
)}
</div>
<div className="grid gap-8 sm:grid-cols-2">
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
Full Name
</label>
<input
required
name="name"
placeholder="John Doe"
className="w-full border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
Phone Number
</label>
<input
required
name="phone_no"
type="tel"
placeholder="+977 98..."
className="w-full border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
</div>
<div className="grid gap-8 sm:grid-cols-2">
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
Email Address
</label>
<input
required
name="email"
type="email"
placeholder="john@example.com"
className="w-full border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
Subject
</label>
<input
required
name="subject"
placeholder="Machinery Quote"
className="w-full border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
</div>
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
What are you looking for?
</label>
<textarea
required
name="message"
rows={4}
placeholder="Tell us about your requirements..."
className="w-full resize-none border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
<button
type="submit"
className="mt-6 inline-flex self-start items-center gap-2 text-sm font-bold uppercase tracking-widest text-background bg-foreground px-8 py-4 hover:bg-foreground/80 transition-colors"
>
Send Enquiry
</button>
{sent && (
<div className="mt-4 border border-border p-6 text-center bg-muted/30">
<p className="text-sm font-bold uppercase tracking-widest text-foreground">
Thanks — your enquiry has been sent successfully. We will get back to you shortly.
</p>
</div>
)}
{error && (
<div className="mt-4 border border-border p-6 text-center bg-red-500/10">
<p className="text-sm font-bold uppercase tracking-widest text-red-600">
There was an error sending your message. Please try again later.
</p>
</div>
)}
</form>
);
}
+8 -3
View File
@@ -28,7 +28,12 @@ export function Footer() {
<div className="flex flex-col lg:flex-row gap-16 lg:gap-24 mb-32">
{/* Logo and Copyright */}
<div className="flex flex-col lg:w-[35%] shrink-0">
<div className="flex items-center gap-3">
<div className="flex items-center gap-4">
<img
src={org?.logo || `${import.meta.env.BASE_URL}logo.png`}
alt={org?.name || "UDHAMSIL"}
className="w-48 lg:w-64 h-auto object-contain"
/>
<span className="font-display text-2xl font-bold tracking-tight">
{org?.name || "UDHAMSIL"}
</span>
@@ -152,10 +157,10 @@ export function Footer() {
Legal
</h4>
<nav className="flex flex-col gap-4 text-sm font-medium text-background/70">
<Link to="#" className="hover:text-background transition-colors">
<Link to="/privacy" className="hover:text-background transition-colors">
Privacy
</Link>
<Link to="#" className="hover:text-background transition-colors">
<Link to="/terms" className="hover:text-background transition-colors">
Terms
</Link>
</nav>
+8 -3
View File
@@ -2,6 +2,7 @@ import { Link, NavLink } from "react-router-dom";
import { ArrowUpRight, Menu } from "lucide-react";
import { useState } from "react";
import { useApi } from "@/hooks/useApi";
import { Button } from "@/components/ui/button";
import {
Sheet,
@@ -24,15 +25,19 @@ const links = [
export function Header() {
const [open, setOpen] = useState(false);
const { data: org } = useApi<any>("org/organization");
const logoUrl = org?.logo || `${import.meta.env.BASE_URL}logo.png`;
return (
<header className="fixed top-0 inset-x-0 w-full z-50 bg-background border-b border-transparent transition-all duration-300 hover:border-border">
<div className="mx-auto max-w-7xl px-4 sm:px-6">
<nav className="flex h-24 items-center justify-between gap-4">
<Link to="/" className="flex items-center gap-4">
<span className="leading-tight">
<span className="block font-display text-lg tracking-wide">UDHAMSIL</span>
</span>
<img
src={logoUrl}
alt={org?.name || "Udhamshil Nepal"}
className="h-20 md:h-24 w-auto object-contain scale-110 md:scale-125 origin-left"
/>
</Link>
<div className="hidden h-full items-center gap-8 text-xs font-medium uppercase tracking-widest text-muted-foreground lg:flex">
+10 -28
View File
@@ -1,6 +1,5 @@
import { useEffect, useRef, useState, type ReactNode } from "react";
/** Translates its children vertically as the page scrolls. */
import { motion } from "framer-motion"; /** Translates its children vertically as the page scrolls. */
export function Parallax({
speed = 0.15,
className,
@@ -71,36 +70,19 @@ export function Reveal({
className?: string;
children: ReactNode;
}) {
const ref = useRef<HTMLDivElement>(null);
const [shown, setShown] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => {
if (entries[0]?.isIntersecting) {
setShown(true);
io.disconnect();
}
},
{ threshold: 0.15 },
);
io.observe(el);
return () => io.disconnect();
}, []);
return (
<div
ref={ref}
<motion.div
className={className}
style={{
opacity: shown ? 1 : 0,
transform: shown ? "none" : "translateY(28px)",
transition: `opacity .7s ease ${delay}ms, transform .7s ease ${delay}ms`,
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-40px" }}
transition={{
duration: 0.8,
delay: delay / 1000,
ease: [0.22, 1, 0.36, 1],
}}
>
{children}
</div>
</motion.div>
);
}
+5 -1
View File
@@ -49,7 +49,11 @@ function About() {
const mision = misionList?.[0];
const values = valuesList || [];
if (!isLoading && !about && !vision && !mision && values.length === 0) {
if (isLoading) {
return <div className="min-h-[150vh] bg-background" />;
}
if (!about && !vision && !mision && values.length === 0) {
return null;
}
+1 -1
View File
@@ -29,7 +29,7 @@ function CareerDetail() {
const { slug } = useParams();
const { data: role, loading } = useApi<CareerRole>(`career/${slug}`);
if (loading) return null;
if (loading) return <div className="min-h-[150vh] bg-background" />;
if (!role) {
return <RoleNotFound />;
+5 -1
View File
@@ -18,7 +18,11 @@ export interface CareerRole {
function Career() {
const { data: roles, loading } = useApi<CareerRole[]>("career");
if (!loading && (!roles || roles.length === 0)) {
if (loading) {
return <div className="min-h-[150vh] bg-background" />;
}
if (!roles || roles.length === 0) {
return null;
}
+3 -136
View File
@@ -15,44 +15,11 @@ interface Organization {
google_map_link: string;
}
import { ContactForm } from "@/components/site/ContactForm";
export default function Contact() {
const [sent, setSent] = useState(false);
const [error, setError] = useState(false);
const { data: org } = useApi<Organization>("org/organization");
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSent(false);
setError(false);
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData.entries());
try {
const baseUrl = import.meta.env.VITE_API_BASE_URL || "/api";
const res = await fetch(`${baseUrl}/contact/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: data.name,
phone_no: data.phone_no,
email: data.email,
subject: data.subject || "Website Enquiry",
message: data.message,
}),
});
if (res.ok) {
setSent(true);
(e.target as HTMLFormElement).reset();
} else {
setError(true);
}
} catch (err) {
setError(true);
}
};
return (
<div className="relative min-h-screen">
<LightField />
@@ -67,107 +34,7 @@ export default function Contact() {
<div className="mx-auto grid max-w-7xl gap-8 px-6 pb-24 lg:grid-cols-12 lg:gap-16">
{/* Form */}
<Reveal className="order-2 lg:order-1 lg:col-span-7">
<form
onSubmit={handleSubmit}
className="flex flex-col gap-8 border-t-2 border-foreground pt-12"
>
<div className="mb-4">
<h3 className="text-4xl font-display font-black tracking-tighter uppercase text-foreground">
Send an enquiry
</h3>
<p className="mt-4 text-xs font-bold uppercase tracking-widest text-muted-foreground">
We generally respond within 24 hours.
</p>
</div>
<div className="grid gap-8 sm:grid-cols-2">
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
Full Name
</label>
<input
required
name="name"
placeholder="John Doe"
className="w-full border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
Phone Number
</label>
<input
required
name="phone_no"
type="tel"
placeholder="+977 98..."
className="w-full border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
</div>
<div className="grid gap-8 sm:grid-cols-2">
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
Email Address
</label>
<input
required
name="email"
type="email"
placeholder="john@example.com"
className="w-full border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
Subject
</label>
<input
required
name="subject"
placeholder="Machinery Quote"
className="w-full border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
</div>
<div className="space-y-4">
<label className="text-xs font-bold uppercase tracking-widest text-foreground">
What are you looking for?
</label>
<textarea
required
name="message"
rows={4}
placeholder="Tell us about your requirements..."
className="w-full resize-none border-b border-border bg-transparent px-0 py-3 text-base text-foreground outline-none transition placeholder:text-muted-foreground/30 focus:border-foreground"
/>
</div>
<button
type="submit"
className="mt-6 inline-flex self-start items-center gap-2 text-sm font-bold uppercase tracking-widest text-background bg-foreground px-8 py-4 hover:bg-foreground/80 transition-colors"
>
Send Enquiry
</button>
{sent && (
<div className="mt-4 border border-border p-6 text-center">
<p className="text-sm font-bold uppercase tracking-widest text-foreground">
Thanks — your enquiry has been sent successfully. We will get back to you
shortly.
</p>
</div>
)}
{error && (
<div className="mt-4 border border-border p-6 text-center">
<p className="text-sm font-bold uppercase tracking-widest text-red-600">
There was an error sending your message. Please try again later.
</p>
</div>
)}
</form>
<ContactForm className="border-t-2 border-foreground pt-12" />
</Reveal>
{/* Contact Info Sidebar */}
+29 -28
View File
@@ -7,6 +7,7 @@ import { OrbitalVisualization } from "@/components/site/OrbitalVisualization";
import { ClientsMarquee } from "@/components/site/ClientsMarquee";
import { TestimonialsMarquee } from "@/components/site/TestimonialsMarquee";
import { useApi } from "@/hooks/useApi";
import { ContactForm } from "@/components/site/ContactForm";
function AnimatedStat({ value }: { value: number }) {
const ref = useRef<HTMLSpanElement>(null);
@@ -40,8 +41,8 @@ function AnimatedStat({ value }: { value: number }) {
export default Index;
function Index() {
const { data: heroList } = useApi("homepage/hero");
const { data: org } = useApi("org/organization");
const { data: heroList, loading: heroLoading } = useApi("homepage/hero");
const { data: org, loading: orgLoading } = useApi("org/organization");
const { data: statsList } = useApi("homepage/stats");
const { data: marqueeList } = useApi("homepage/marquee");
const { data: whatWeDoList } = useApi("org/what-we-do");
@@ -59,6 +60,10 @@ function Index() {
const services = servicesList || [];
const testimonials = testimonialsList || [];
if (heroLoading || orgLoading) {
return <div className="min-h-[150vh] bg-background" />;
}
return (
<div className="relative bg-background">
<LightField />
@@ -320,33 +325,29 @@ function Index() {
{testimonials.length > 0 && <TestimonialsMarquee testimonials={testimonials} />}
<section className="relative z-10 bg-brand text-white overflow-hidden">
<div className="mx-auto max-w-7xl px-6 py-32 md:py-48 text-center">
<section className="relative z-10 bg-background overflow-hidden border-t border-border">
<div className="mx-auto max-w-7xl px-6 py-32 md:py-48">
<Reveal>
<div className="mx-auto max-w-4xl">
<p className="text-xs font-bold uppercase tracking-widest text-white/70 mb-8">
Ready to scale
</p>
<h2 className="text-5xl font-display font-black tracking-tighter uppercase md:text-7xl lg:text-[7rem] leading-[0.9]">
Need a machine
<br />
spec'd for your
<br />
output?
</h2>
<div className="mt-16 flex flex-col items-center justify-center gap-8 sm:flex-row">
<Link
to="/contact"
className="inline-flex items-center gap-2 text-sm font-bold uppercase tracking-widest text-brand bg-white px-8 py-4 hover:bg-white/90 transition-colors"
>
Get a Quote <ArrowRight className="h-4 w-4" />
</Link>
<Link
to="/products"
className="inline-flex items-center gap-2 text-sm font-bold uppercase tracking-widest text-white hover:text-white/70 transition-colors border-b border-white/30 pb-1 hover:border-white"
>
Browse Catalogue
</Link>
<div className="grid lg:grid-cols-2 gap-16 lg:gap-24 items-start">
<div>
<p className="text-xs font-bold uppercase tracking-widest text-muted-foreground mb-8">
Ready to scale
</p>
<h2 className="text-5xl font-display font-black tracking-tighter uppercase md:text-7xl leading-[0.9]">
Need a machine
<br />
spec'd for your
<br />
output?
</h2>
<div className="mt-12 hidden lg:block">
<p className="text-sm font-medium text-muted-foreground max-w-sm leading-relaxed">
Share your scale, target output, and location — we'll come back with a recommendation and price tailored to your exact needs.
</p>
</div>
</div>
<div className="bg-muted p-8 md:p-12 border border-border">
<ContactForm title="Get a Quote" subtitle="" />
</div>
</div>
</Reveal>
+14 -2
View File
@@ -1,14 +1,26 @@
import { Outlet } from "react-router-dom";
import { useLocation, useOutlet } from "react-router-dom";
import { motion } from "framer-motion";
import { Header } from "../components/site/Header";
import { Footer } from "../components/site/Footer";
import { FloatingActionButtons } from "../components/site/FloatingActionButtons";
export default function Layout() {
const location = useLocation();
const outlet = useOutlet();
return (
<div className="relative flex min-h-screen flex-col overflow-x-hidden bg-background">
<Header />
<main className="flex-1 pt-20">
<Outlet />
<motion.div
key={location.pathname}
initial={{ opacity: 0, y: 15, filter: "blur(2px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
className="flex flex-col min-h-full"
>
{outlet}
</motion.div>
</main>
<Footer />
<FloatingActionButtons />
+66
View File
@@ -0,0 +1,66 @@
import { LightField } from "@/components/site/Parallax";
import { PageHero } from "@/components/site/PageHero";
export default function Privacy() {
return (
<div className="relative min-h-screen bg-background">
<LightField />
<PageHero
eyebrow="Legal"
title="Privacy Policy"
description="How we handle and protect your data."
/>
<section className="relative z-10 border-t border-border mt-16">
<div className="mx-auto max-w-4xl px-6 py-32">
<div className="prose prose-invert prose-lg max-w-none">
<h2 className="font-display font-black tracking-tight uppercase">
1. Information We Collect
</h2>
<p>
We collect information that you provide directly to us, including when you fill out a
form, request a quote, or communicate with us. This may include your name, email
address, phone number, and any other information you choose to provide.
</p>
<h2 className="font-display font-black tracking-tight uppercase mt-12">
2. How We Use Your Information
</h2>
<p>
We use the information we collect to provide, maintain, and improve our services,
communicate with you, respond to your inquiries, and fulfill your requests for
machinery and equipment quotes.
</p>
<h2 className="font-display font-black tracking-tight uppercase mt-12">
3. Information Sharing
</h2>
<p>
We do not sell, trade, or otherwise transfer your personally identifiable information
to outside parties unless we provide users with advance notice. This does not include
website hosting partners and other parties who assist us in operating our website or
conducting our business.
</p>
<h2 className="font-display font-black tracking-tight uppercase mt-12">
4. Data Security
</h2>
<p>
We implement a variety of security measures to maintain the safety of your personal
information. However, no method of transmission over the Internet or electronic
storage is 100% secure, and we cannot guarantee absolute security.
</p>
<h2 className="font-display font-black tracking-tight uppercase mt-12">
5. Contact Us
</h2>
<p>
If there are any questions regarding this privacy policy, you may contact us using the
information on our Contact page.
</p>
</div>
</div>
</section>
</div>
);
}
+5 -1
View File
@@ -20,7 +20,11 @@ interface Product {
function Products() {
const { data: products, loading } = useApi<Product[]>("products");
if (!loading && (!products || products.length === 0)) {
if (loading) {
return <div className="min-h-[150vh] bg-background" />;
}
if (!products || products.length === 0) {
return null;
}
+5 -1
View File
@@ -70,7 +70,11 @@ function ProjectsEvents() {
entries.sort((a, b) => b.timestamp - a.timestamp);
if (!isLoading && entries.length === 0) {
if (isLoading) {
return <div className="min-h-[150vh] bg-background" />;
}
if (entries.length === 0) {
return null;
}
+5 -1
View File
@@ -19,7 +19,11 @@ interface TeamMember {
function Team() {
const { data: people, loading } = useApi<TeamMember[]>("team");
if (!loading && (!people || people.length === 0)) {
if (loading) {
return <div className="min-h-[150vh] bg-background" />;
}
if (!people || people.length === 0) {
return null;
}
+67
View File
@@ -0,0 +1,67 @@
import { LightField } from "@/components/site/Parallax";
import { PageHero } from "@/components/site/PageHero";
export default function Terms() {
return (
<div className="relative min-h-screen bg-background">
<LightField />
<PageHero
eyebrow="Legal"
title="Terms of Service"
description="The rules that govern our relationship."
/>
<section className="relative z-10 border-t border-border mt-16">
<div className="mx-auto max-w-4xl px-6 py-32">
<div className="prose prose-invert prose-lg max-w-none">
<h2 className="font-display font-black tracking-tight uppercase">
1. Acceptance of Terms
</h2>
<p>
By accessing and using this website, you accept and agree to be bound by the terms and
provision of this agreement.
</p>
<h2 className="font-display font-black tracking-tight uppercase mt-12">
2. Use License
</h2>
<p>
Permission is granted to temporarily download one copy of the materials on our website
for personal, non-commercial transitory viewing only. This is the grant of a license,
not a transfer of title.
</p>
<h2 className="font-display font-black tracking-tight uppercase mt-12">
3. Disclaimer
</h2>
<p>
The materials on our website are provided on an 'as is' basis. We make no warranties,
expressed or implied, and hereby disclaim and negate all other warranties including,
without limitation, implied warranties or conditions of merchantability, fitness for a
particular purpose, or non-infringement of intellectual property or other violation of
rights.
</p>
<h2 className="font-display font-black tracking-tight uppercase mt-12">
4. Limitations
</h2>
<p>
In no event shall we or our suppliers be liable for any damages (including, without
limitation, damages for loss of data or profit, or due to business interruption)
arising out of the use or inability to use the materials on our website.
</p>
<h2 className="font-display font-black tracking-tight uppercase mt-12">
5. Governing Law
</h2>
<p>
These terms and conditions are governed by and construed in accordance with the laws
of Nepal and you irrevocably submit to the exclusive jurisdiction of the courts in
that location.
</p>
</div>
</div>
</section>
</div>
);
}
+66 -2
View File
@@ -4,6 +4,54 @@
@custom-variant dark (&:is(.dark *));
@font-face {
font-family: 'Inter Display';
src: url('/static/frontend/fonts/InterDisplay-Medium.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter Display';
src: url('/static/frontend/fonts/InterDisplay-Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter Display';
src: url('/static/frontend/fonts/InterDisplay-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter Display';
src: url('/static/frontend/fonts/InterDisplay-SemiBold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter Display';
src: url('/static/frontend/fonts/InterDisplay-SemiBold.woff2') format('woff2');
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter Display';
src: url('/static/frontend/fonts/InterDisplay-SemiBold.woff2') format('woff2');
font-weight: 900;
font-style: normal;
font-display: swap;
}
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
@@ -35,8 +83,8 @@
--color-brand: var(--brand);
--color-ink: var(--ink);
--color-mist: var(--mist);
--font-display: "Helvetica Neue", Helvetica, Arial, sans-serif;
--font-sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
--font-display: "Bricolage Grotesque", sans-serif;
--font-sans: "Inter Display", sans-serif;
}
:root {
@@ -283,3 +331,19 @@
transition-duration: 0.01ms !important;
}
}
html.lenis,
html.lenis body {
height: auto;
}
.lenis.lenis-smooth {
scroll-behavior: auto !important;
}
.lenis.lenis-smooth [data-lenis-prevent] {
overscroll-behavior: contain;
}
.lenis.lenis-stopped {
overflow: hidden;
}
.lenis.lenis-smooth iframe {
pointer-events: none;
}