project setup
@@ -0,0 +1,41 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
import aboutBg from "@/public/assets/images/our-mission-bg.png";
|
||||||
|
import avatar from "@/public/assets/avatars/avatar.png";
|
||||||
|
import avatar1 from "@/public/assets/avatars/avatar_1.png";
|
||||||
|
import avatar2 from "@/public/assets/avatars/avatar_2.png";
|
||||||
|
import avatar3 from "@/public/assets/avatars/avatar_3.png";
|
||||||
|
|
||||||
|
import TeamMemberCard from "@/components/about/TeamMemberCard";
|
||||||
|
|
||||||
|
const AboutPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<section className="relative overflow-hidden w-full min-h-[600px] sm:min-h-[700px] md:aspect-[5/3] md:max-h-[900px] -mt-[75px] pt-[75px]">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 sm:top-[-75px] top-[-35px] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
WebkitMaskImage:
|
||||||
|
"linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 14%, rgba(0,0,0,1) 86%, rgba(0,0,0,0) 100%)",
|
||||||
|
maskImage:
|
||||||
|
"linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 14%, rgba(0,0,0,1) 86%, rgba(0,0,0,0) 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={aboutBg}
|
||||||
|
alt="About background"
|
||||||
|
fill
|
||||||
|
className="object-cover h-full"
|
||||||
|
style={{ objectPosition: "50% 20%" }}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
{/* Mobile darkening overlay */}
|
||||||
|
<div className="absolute inset-0 bg-black/35 md:bg-black/0" />
|
||||||
|
<div className="pointer-events-none absolute left-1/2 top-[-36%] w-[260%] h-[62%] -translate-x-1/2 bg-[radial-gradient(circle_at_50%_-70%,rgba(127,19,236,0.88),rgba(127,19,236,0.52)_58%,rgba(25,16,34,0)_88%)] blur-[180px] opacity-95" />
|
||||||
|
<div className="pointer-events-none absolute left-1/2 bottom-[-18%] w-[240%] h-[36%] -translate-x-1/2 bg-[radial-gradient(circle_at_50%_135%,rgba(127,19,236,0.82),rgba(127,19,236,0.36)_46%,rgba(25,16,34,0)_86%)] blur-[140px] opacity-[0.9]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 w-full h-full flex items-center justify-center px-6 py-12 sm:py-12 md:py-8">
|
||||||
|
<div className="max-w-4xl text-center">
|
||||||
|
<h1 className="text-3xl sm:text-4xl lg:text-6xl font-mono font-extrabold text-white drop-shadow-[0_0_20px_rgba(127,19,236,0.7)]">
|
||||||
|
Our Mission
|
||||||
|
</h1>
|
||||||
|
<p className="mt-4 sm:mt-6 text-sm sm:text-base font-mono leading-relaxed px-4">
|
||||||
|
Us as human, we better work as a collective no matter the language
|
||||||
|
or background we have. However we at LAN Codes believe we can
|
||||||
|
bring one more player to the team, AI. Acceleration of machine
|
||||||
|
learning language (MLL) models. "AI", is scary. In some way "I
|
||||||
|
Robot" it may seem more real to some. However, for players in LAN
|
||||||
|
Codes there is nothing that scares us. We are attempting to use
|
||||||
|
our critical thinking and MLL's efficiency to remove the busy work
|
||||||
|
and reduce human errors. While working on creating these systems
|
||||||
|
our results will produce cost effective security with
|
||||||
|
uncompromising safety. We believe that safety should not be a
|
||||||
|
privilege.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="relative z-10 bg-[var(--color-1)]">
|
||||||
|
<div className="pointer-events-none absolute -top-16 left-0 right-0 h-16 bg-gradient-to-b from-transparent via-[var(--color-1)]/38 to-[var(--color-1)]" />
|
||||||
|
<div className="mx-auto px-4 sm:px-6 lg:px-8 pb-4 pt-4 max-w-[82.5%]">
|
||||||
|
<div className="text-center mb-4">
|
||||||
|
<h2 className="text-2xl sm:text-5xl font-mono">Founder</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center md:justify-center gap-8 md:gap-12 max-w-3xl mx-auto">
|
||||||
|
<TeamMemberCard
|
||||||
|
avatar={avatar2}
|
||||||
|
name="Milan Vucetic"
|
||||||
|
position="Founder"
|
||||||
|
/>
|
||||||
|
<p className="font-mono text-sm sm:text-base leading-relaxed text-white/90 text-center md:max-w-xl">
|
||||||
|
I was always told you cannot reinvent the wheel, but you can make
|
||||||
|
it better. While working in reactive security prevention, then
|
||||||
|
moved towards proactive security prevention and a sprinkle of
|
||||||
|
inspiration, I managed to find a gap in many security doors, and
|
||||||
|
better yet a solution to tighten them up. With that goal I have
|
||||||
|
managed to organize the team of fearless people and with their
|
||||||
|
help LAN Codes became a reality from just an idea on a napkin.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 text-center">
|
||||||
|
<h3 className="text-xl sm:text-4xl font-mono">Meet the team</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-6 sm:gap-12 max-w-3xl mx-auto justify-items-center">
|
||||||
|
<TeamMemberCard
|
||||||
|
avatar={avatar1}
|
||||||
|
name="Tevin Gary-Vincent"
|
||||||
|
position="Head of Cybersecurity"
|
||||||
|
/>
|
||||||
|
<TeamMemberCard
|
||||||
|
avatar={avatar}
|
||||||
|
name="Nenad Zivotic"
|
||||||
|
position="Front-end Developer"
|
||||||
|
/>
|
||||||
|
<TeamMemberCard
|
||||||
|
avatar={avatar3}
|
||||||
|
name="Stefan Djukelic"
|
||||||
|
position="Back-end Developer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AboutPage;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { saveContact } from "@/lib/contactStore";
|
||||||
|
import { contactSchema } from "@/schemas/schemas";
|
||||||
|
|
||||||
|
export async function POST(req: Request) {
|
||||||
|
try {
|
||||||
|
const body = await req.json();
|
||||||
|
const {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
companyName,
|
||||||
|
address,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
industry,
|
||||||
|
notes,
|
||||||
|
subscribe,
|
||||||
|
} = contactSchema.parse(body);
|
||||||
|
|
||||||
|
const saved = await saveContact({
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
companyName,
|
||||||
|
address,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
industry,
|
||||||
|
notes,
|
||||||
|
subscribe,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Response.json({ success: true, id: saved.id });
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err?.issues) {
|
||||||
|
return Response.json(
|
||||||
|
{ success: false, errors: err.issues },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Response.json({ success: false }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
const ContactPage = () => {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8 py-16 md:py-32">
|
||||||
|
<div className="flex flex-col gap-16">
|
||||||
|
<div className="flex flex-col gap-10">
|
||||||
|
<p className="font-mono text-sm sm:text-base">
|
||||||
|
If you have any questions send us an email and we will be happy to
|
||||||
|
answer them.
|
||||||
|
<br />
|
||||||
|
You can expect our team to get back to your with in 24h-48h.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="font-mono text-xl text-[var(--color-7)]">
|
||||||
|
<a href="mailto:service@lancodes.ca">service@lancodes.ca</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-10">
|
||||||
|
<p className="font-mono text-sm sm:text-base">
|
||||||
|
Looking for a job? Send us your resume and we will get back to you
|
||||||
|
when our next round of hiring starts.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="font-mono text-xl text-[var(--color-10)]">
|
||||||
|
<a href="mailto:hireme@lancodes.ca">hireme@lancodes.ca</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContactPage;
|
||||||
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,47 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Fira Code';
|
||||||
|
src: url('/assets/fonts/static/FiraCode-Regular.ttf') format('truetype');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fira Code';
|
||||||
|
src: url('/assets/fonts/static/FiraCode-Medium.ttf') format('truetype');
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fira Code';
|
||||||
|
src: url('/assets/fonts/static/FiraCode-SemiBold.ttf') format('truetype');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fira Code';
|
||||||
|
src: url('/assets/fonts/static/FiraCode-Bold.ttf') format('truetype');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fira Code';
|
||||||
|
src: url('/assets/fonts/static/FiraCode-Light.ttf') format('truetype');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fira Code VF';
|
||||||
|
src: url('/assets/fonts/FiraCode-VariableFont_wght.ttf') format('truetype-variations');
|
||||||
|
font-weight: 300 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { contactSchema } from "@/schemas/schemas";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof contactSchema>;
|
||||||
|
|
||||||
|
const GetStartedPage = () => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors, isSubmitting },
|
||||||
|
reset,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
} = useForm<FormValues>({ resolver: zodResolver(contactSchema) });
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<FormValues> = async (data) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/contact", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error("Request failed");
|
||||||
|
toast.success("Thank you! We'll be in touch.");
|
||||||
|
reset();
|
||||||
|
} catch (e) {
|
||||||
|
toast.error("Failed to save contact. Please try again.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8 py-6 sm:py-12">
|
||||||
|
<div className="text-center mb-6">
|
||||||
|
<h1 className="text-2xl md:text-3xl font-mono font-extrabold">
|
||||||
|
Join Us on This Journey
|
||||||
|
</h1>
|
||||||
|
<p className="mt-4 mb-4 max-w-4xl mx-auto text-sm sm:text-base font-mono">
|
||||||
|
Be a part of something innovative. As our early adapters, you will be
|
||||||
|
one of the key players making a change in how staying safe online is
|
||||||
|
important yet effortless. You will get exclusive early access to our
|
||||||
|
newest solutions and influence the development, while staying
|
||||||
|
protected in the process.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative rounded-3xl border-4 border-[var(--color-3)] px-10 sm:px-14 py-8 shadow-[0_0_20px_rgba(255,255,255,0.5)]">
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
className="relative z-10 space-y-4"
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="sr-only">First name</label>
|
||||||
|
<input
|
||||||
|
placeholder="First name"
|
||||||
|
{...register("firstName")}
|
||||||
|
className="w-full border border-[var(--color-3)] outline-none rounded-sm px-3 py-0.5 text-sm"
|
||||||
|
/>
|
||||||
|
{errors.firstName && (
|
||||||
|
<p className="!text-[var(--color-8)] opacity-100 text-xs mt-1">
|
||||||
|
{errors.firstName.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="sr-only">Last name</label>
|
||||||
|
<input
|
||||||
|
placeholder="Last name"
|
||||||
|
{...register("lastName")}
|
||||||
|
className="w-full border border-[var(--color-3)] outline-none rounded-sm px-3 py-0.5 text-sm"
|
||||||
|
/>
|
||||||
|
{errors.lastName && (
|
||||||
|
<p className="!text-[var(--color-8)] opacity-100 text-xs mt-1">
|
||||||
|
{errors.lastName.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="sr-only">Company name</label>
|
||||||
|
<input
|
||||||
|
placeholder="Company name"
|
||||||
|
{...register("companyName")}
|
||||||
|
className="w-full border border-[var(--color-3)] outline-none rounded-sm px-3 py-0.5 text-sm"
|
||||||
|
/>
|
||||||
|
{errors.companyName && (
|
||||||
|
<p className="!text-[var(--color-8)] opacity-100 text-xs mt-1">
|
||||||
|
{errors.companyName.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="sr-only">Address</label>
|
||||||
|
<input
|
||||||
|
placeholder="Address"
|
||||||
|
{...register("address")}
|
||||||
|
className="w-full border border-[var(--color-3)] outline-none rounded-sm px-3 py-0.5 text-sm"
|
||||||
|
/>
|
||||||
|
{errors.address && (
|
||||||
|
<p className="!text-[var(--color-8)] opacity-100 text-xs mt-1">
|
||||||
|
{errors.address.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="sr-only">Email</label>
|
||||||
|
<input
|
||||||
|
placeholder="Email"
|
||||||
|
type="email"
|
||||||
|
{...register("email")}
|
||||||
|
className="w-full border border-[var(--color-3)] outline-none rounded-sm px-3 py-0.5 text-sm"
|
||||||
|
/>
|
||||||
|
{errors.email && (
|
||||||
|
<p className="!text-[var(--color-8)] opacity-100 text-xs mt-1">
|
||||||
|
{errors.email.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="sr-only">Phone number</label>
|
||||||
|
<input
|
||||||
|
placeholder="Phone number"
|
||||||
|
{...register("phone")}
|
||||||
|
className="w-full border border-[var(--color-3)] outline-none rounded-sm px-3 py-0.5 text-sm"
|
||||||
|
/>
|
||||||
|
{errors.phone && (
|
||||||
|
<p className="!text-[var(--color-8)] opacity-100 text-xs mt-1">
|
||||||
|
{errors.phone.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="sr-only">Industry type</label>
|
||||||
|
<input
|
||||||
|
placeholder="Industry type"
|
||||||
|
{...register("industry")}
|
||||||
|
className="w-full border border-[var(--color-3)] outline-none rounded-sm px-3 py-0.5 text-sm"
|
||||||
|
/>
|
||||||
|
{errors.industry && (
|
||||||
|
<p className="!text-[var(--color-8)] opacity-100 text-xs mt-1">
|
||||||
|
{errors.industry.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="sr-only">Additional notes</label>
|
||||||
|
<textarea
|
||||||
|
placeholder="Additional notes"
|
||||||
|
rows={8}
|
||||||
|
{...register("notes")}
|
||||||
|
className="w-full border border-[var(--color-3)] outline-none rounded-sm px-3 py-1 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 ml-6">
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="subscribe"
|
||||||
|
{...register("subscribe")}
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`w-4 h-4 border-2 border-[var(--color-3)] cursor-pointer flex items-center justify-center transition-colors shadow-[0_0_10px_rgba(255,255,255,0.2)] ${
|
||||||
|
watch("subscribe") ? "bg-[var(--color-3)]" : "bg-transparent"
|
||||||
|
}`}
|
||||||
|
onClick={() => setValue("subscribe", !watch("subscribe"))}
|
||||||
|
>
|
||||||
|
{watch("subscribe") && (
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3 text-white"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
htmlFor="subscribe"
|
||||||
|
className="text-xs sm:text-sm font-mono cursor-pointer"
|
||||||
|
>
|
||||||
|
Sign up for our email list for updates, promotions, and more.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center pt-2">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="px-6 py-3 font-mono text-sm text-white bg-[var(--color-3)] rounded-xl disabled:opacity-50 cursor-pointer"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "Sending..." : "Send"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* White glow */}
|
||||||
|
<div className="pointer-events-none absolute inset-0 rounded-3xl shadow-[0_0_5px_rgba(255,255,255,0.4)] z-0"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GetStartedPage;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
@import "./fonts.css";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Base colors */
|
||||||
|
--white: #ffffff;
|
||||||
|
--black: #000000;
|
||||||
|
|
||||||
|
/* Custom color palette */
|
||||||
|
--color-1: #191022;
|
||||||
|
--color-2: #2d114a;
|
||||||
|
--color-3: #7f13ec;
|
||||||
|
--color-4: #cfa5e0;
|
||||||
|
--color-5: #3cf61a;
|
||||||
|
--color-6: #4ee871;
|
||||||
|
--color-7: #34d399;
|
||||||
|
--color-8: #ff007f;
|
||||||
|
--color-9: #ff31b5;
|
||||||
|
--color-10: #ec4899;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--color-1);
|
||||||
|
--color-foreground: var(--color-2);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: "Fira Code", monospace;
|
||||||
|
|
||||||
|
--color-primary: var(--color-3);
|
||||||
|
--color-secondary: var(--color-7);
|
||||||
|
--color-accent: var(--color-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: "Fira Code", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
background: var(--color-background);
|
||||||
|
color: var(--color-white);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
import favicon from "@/public/assets/Logo_Pin_-_with_no_background.ico";
|
||||||
|
import Navbar from "@/components/navigation/Navbar";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
import ToastProvider from "@/components/providers/ToastProvider";
|
||||||
|
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Lan Codes",
|
||||||
|
description: "Lan Codes",
|
||||||
|
icons: {
|
||||||
|
icon: favicon.src,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className="bg-[var(--color-1)] min-h-screen flex flex-col overflow-x-hidden">
|
||||||
|
<Navbar />
|
||||||
|
<main className="flex-1">{children}</main>
|
||||||
|
<Footer />
|
||||||
|
<ToastProvider />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import { LockKeyhole, RotateCcw, Shield } from "lucide-react";
|
||||||
|
|
||||||
|
import heroBanner from "@/public/assets/images/homescreen_bg.png";
|
||||||
|
import FeatureCard from "@/components/FeatureCard";
|
||||||
|
import GetStartedButton from "@/components/navigation/GetStartedButton";
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<main>
|
||||||
|
<div>
|
||||||
|
<section className="relative overflow-hidden w-full min-h-[500px] sm:min-h-[600px] md:aspect-[16/9] md:max-h-[900px] -mt-[75px] pt-[75px] md:pt-[75px]">
|
||||||
|
{/* Background image */}
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 top-[-75px] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
WebkitMaskImage:
|
||||||
|
"linear-gradient(to bottom, rgba(0,0,0,0) 10%, rgba(0,0,0,0.3) 12%, rgba(0,0,0,1) 16%, rgba(0,0,0,1) 88%, rgba(0,0,0,0) 100%)",
|
||||||
|
maskImage:
|
||||||
|
"linear-gradient(to bottom, rgba(0,0,0,0) 10%, rgba(0,0,0,0.3) 12%, rgba(0,0,0,1) 16%, rgba(0,0,0,1) 88%, rgba(0,0,0,0) 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={heroBanner}
|
||||||
|
alt="Hero background"
|
||||||
|
fill
|
||||||
|
priority
|
||||||
|
className="object-cover h-full"
|
||||||
|
style={{ objectPosition: "50% 42.5%" }}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-black/3 via-black/6 to-black/9" />
|
||||||
|
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,rgba(127,19,236,0.03),rgba(16,0,40,0.06)_58%,rgba(0,0,0,0.12))]" />
|
||||||
|
{/* Mobile darkening overlay */}
|
||||||
|
<div className="absolute inset-0 bg-black/30 md:bg-black/0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 w-full h-full flex items-center px-6 sm:px-10 md:pl-16 lg:pl-24 py-12 sm:py-12 md:py-8">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-6xl">
|
||||||
|
<span className="font-mono font-semibold w-[100px]">
|
||||||
|
Cost-Effective{" "}
|
||||||
|
</span>
|
||||||
|
<span className="text-[var(--color-8)] font-semibold">
|
||||||
|
Security
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span className="font-mono font-semibold w-[100px]">
|
||||||
|
Uncompromising{" "}
|
||||||
|
</span>
|
||||||
|
<span className="text-[var(--color-5)] font-semibold">
|
||||||
|
Safety
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="mt-2 font-bold max-w-4xl text-lg sm:text-xl md:text-2xl">
|
||||||
|
Security that is{" "}
|
||||||
|
<span className="text-[var(--color-9)]">tailored</span>
|
||||||
|
</p>
|
||||||
|
<p className="mb-2 font-bold max-w-4xl text-lg sm:text-xl md:text-2xl">
|
||||||
|
for{" "}
|
||||||
|
<span className="text-[var(--color-5)]">your business</span>{" "}
|
||||||
|
needs
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<GetStartedButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="services" className="relative z-10 bg-[var(--color-1)]">
|
||||||
|
<div className="pointer-events-none absolute -top-16 left-0 right-0 h-16 bg-gradient-to-b from-transparent via-[var(--color-1)]/38 to-[var(--color-1)]" />
|
||||||
|
<div className="mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8 py-6 sm:py-16 md:py-20">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-extrabold font-mono">
|
||||||
|
Comprehensive Cybersecurity Services
|
||||||
|
</h2>
|
||||||
|
<p className="mt-3 max-w-3xl mx-auto text-md sm:text-lg font-mono">
|
||||||
|
Lan Codes offers a range of services in one simple package
|
||||||
|
designed to protect your business from cyber threats, ensuring
|
||||||
|
data security and operational continuity.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 sm:mt-10 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<FeatureCard
|
||||||
|
icon={<Shield color="green" size={30} />}
|
||||||
|
title="Risk Detection"
|
||||||
|
description="Be on top of your end users and ensure there are no weak links in the chain. Have active reminders on your high risk users."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FeatureCard
|
||||||
|
icon={<LockKeyhole color="red" size={30} />}
|
||||||
|
title="Data Encryption & Security"
|
||||||
|
description="Robust data encryption and security measures to protect sensitive information and ensure compliance with industry standard."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FeatureCard
|
||||||
|
icon={<RotateCcw color="purple" size={30} />}
|
||||||
|
title="24/7 Monitoring & Assistance"
|
||||||
|
description="Round-the-clock monitoring and assistance to address and resolve any security incidents, minimizing downtime and potential damage."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import ProjectItems from "@/components/services/ProjectItems";
|
||||||
|
import GetStartedButton from "@/components/navigation/GetStartedButton";
|
||||||
|
import { projectItemsData } from "@/data/StaticData";
|
||||||
|
|
||||||
|
const ServicesPage = () => {
|
||||||
|
const [activeIndex, setActiveIndex] = useState(-1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8 py-8 sm:pt-32 sm:pb-16">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-0">
|
||||||
|
<div className="md:col-span-6">
|
||||||
|
<div className="flex items-center gap-3 mb-3 ml-2">
|
||||||
|
<span className="inline-block w-6 h-6 rotate-45 bg-[var(--color-7)]" />
|
||||||
|
<h1 className="text-2xl sm:text-3xl font-mono ml-2">
|
||||||
|
Bird eye view
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="md:w-3/4 w-full h-[3px] mb-4"
|
||||||
|
style={{ background: "var(--color-2)" }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProjectItems
|
||||||
|
items={projectItemsData}
|
||||||
|
activeIndex={activeIndex}
|
||||||
|
setActiveIndex={setActiveIndex}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-6">
|
||||||
|
<div
|
||||||
|
className={`bg-[var(--color-2)] p-6 md:p-8 md:min-h-[510px] flex items-start relative
|
||||||
|
${
|
||||||
|
activeIndex === projectItemsData.length - 1
|
||||||
|
? "rounded-tl-lg rounded-tr-lg"
|
||||||
|
: "rounded-lg"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{activeIndex === -1 ? (
|
||||||
|
<>
|
||||||
|
<div className="w-full text-center pb-16">
|
||||||
|
<h3 className="text-xl sm:text-2xl font-mono font-extrabold">
|
||||||
|
Boost Your Security.
|
||||||
|
<br />
|
||||||
|
Simplify Your Workflow.
|
||||||
|
</h3>
|
||||||
|
<p className="mt-4 max-w-2xl mx-auto text-sm sm:text-base font-mono leading-relaxed">
|
||||||
|
LAN Codes is designed for organizations that want a smarter,
|
||||||
|
faster way to strengthen their security posture. Our custom
|
||||||
|
LLM automatically detects vulnerabilities and unusual
|
||||||
|
patterns—removing the tedious manual work so your team can
|
||||||
|
focus on what truly matters: investigating threats and
|
||||||
|
finding solutions.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 max-w-2xl mx-auto text-sm sm:text-base font-mono leading-relaxed">
|
||||||
|
Even if a breach occurs, your critical data stays encrypted
|
||||||
|
and protected, giving you the confidence and time to respond
|
||||||
|
effectively and prevent future incidents.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-6 left-0 right-0 flex justify-center">
|
||||||
|
<GetStartedButton />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="w-full text-center pb-16">
|
||||||
|
<p className="font-mono text-sm sm:text-base">
|
||||||
|
{projectItemsData[activeIndex]?.content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-10">
|
||||||
|
<div className="relative rounded-lg bg-[var(--color-2)] ring-1 ring-white/10 px-6 py-8 shadow-[0_0_16px_rgba(255,255,255,0.5)]">
|
||||||
|
<h2 className="text-center text-xl md:text-2xl font-mono mb-4">
|
||||||
|
Affordable Security for Everyone
|
||||||
|
</h2>
|
||||||
|
<p className="text-center mx-auto text-sm sm:text-base font-mono mb-6">
|
||||||
|
We believe cybersecurity should be accessible to all businesses,
|
||||||
|
regardless of size. Our solutions are designed to be cost effective
|
||||||
|
without compromising on security
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<GetStartedButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesPage;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": true,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": true,
|
||||||
|
"includes": ["**", "!node_modules", "!.next", "!dist", "!build"]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"suspicious": {
|
||||||
|
"noUnknownAtRules": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domains": {
|
||||||
|
"next": "recommended",
|
||||||
|
"react": "recommended"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assist": {
|
||||||
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": "on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
interface ButtonProps {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = ({ text }: ButtonProps) => {
|
||||||
|
return (
|
||||||
|
<button className="px-4 py-3 font-mono text-xs sm:text-sm text-white bg-[var(--color-3)] rounded-xl cursor-pointer">
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Button;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
interface FeatureCardProps {
|
||||||
|
icon: ReactNode;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FeatureCard = ({ icon, title, description }: FeatureCardProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-5 border-[3px] border-[var(--color-2)] p-4 rounded-2xl">
|
||||||
|
<div className="mb-2 bg-transparent">{icon}</div>
|
||||||
|
<h3 className="font-mono font-bold leading-none mb-2">{title}</h3>
|
||||||
|
<p className="text-sm sm:text-base font-mono">{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FeatureCard;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="relative h-[125px] flex flex-col justify-center">
|
||||||
|
<div className="absolute top-0 left-0 right-0 h-px">
|
||||||
|
<div
|
||||||
|
className="mx-auto w-[87.5%] h-[3px]"
|
||||||
|
style={{
|
||||||
|
background: "var(--color-2)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-8 flex flex-col md:flex-row justify-between items-center relative">
|
||||||
|
<div className="mb-4 md:mb-0">
|
||||||
|
<Link href="/">
|
||||||
|
<Image
|
||||||
|
src="/assets/images/logo_no_bg.png"
|
||||||
|
alt="Logo"
|
||||||
|
width={125}
|
||||||
|
height={125}
|
||||||
|
className="w-[100px] h-auto sm:w-[120px] lg:w-[125px]"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-white text-sm font-mono absolute top-[70%] md:static left-0 right-0 text-center md:text-right">
|
||||||
|
{currentYear} Lan Codes All Rights Reserved
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import Image, { StaticImageData } from "next/image";
|
||||||
|
|
||||||
|
interface TeamMemberCardProps {
|
||||||
|
avatar: StaticImageData;
|
||||||
|
name: string;
|
||||||
|
position: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TeamMemberCard = ({ avatar, name, position }: TeamMemberCardProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<div className="relative w-40 h-40 sm:w-48 sm:h-48 rounded-full border-2 overflow-hidden">
|
||||||
|
<Image src={avatar} alt={name} className="object-cover" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<p
|
||||||
|
className={`font-mono text-sm ${
|
||||||
|
position === "Founder" ? "text-white" : "text-[var(--color-7)]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</p>
|
||||||
|
{position !== "Founder" && (
|
||||||
|
<p className="font-mono text-sm text-[var(--color-10)]">{position}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeamMemberCard;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import Button from "@/components/Button";
|
||||||
|
|
||||||
|
interface GetStartedButtonProps {
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetStartedButton = ({ onClick }: GetStartedButtonProps) => {
|
||||||
|
return (
|
||||||
|
<Link href="/get-started" onClick={onClick}>
|
||||||
|
<Button text="Get Started" />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GetStartedButton;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
|
interface NavLinksProps {
|
||||||
|
mobile?: boolean;
|
||||||
|
onLinkClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NavLinks = ({ mobile = false, onLinkClick }: NavLinksProps) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const containerClass = mobile
|
||||||
|
? "flex flex-col items-center gap-4 text-lg min-[828px]:flex-row min-[828px]:gap-6"
|
||||||
|
: "hidden md:flex items-center gap-8 text-lg";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className={containerClass}>
|
||||||
|
<Link href="/services" onClick={onLinkClick}>
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
pathname === "/services" && "text-[var(--color-8)]"
|
||||||
|
} font-mono`}
|
||||||
|
>
|
||||||
|
Service
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<Link href="/about" onClick={onLinkClick}>
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
pathname === "/about" && "text-[var(--color-8)]"
|
||||||
|
} font-mono`}
|
||||||
|
>
|
||||||
|
About us
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<Link href="/contact" onClick={onLinkClick}>
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
pathname === "/contact" && "text-[var(--color-8)]"
|
||||||
|
} font-mono`}
|
||||||
|
>
|
||||||
|
Contact
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavLinks;
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { Menu, X } from "lucide-react";
|
||||||
|
|
||||||
|
import NavLinks from "@/components/navigation/NavLinks";
|
||||||
|
import SiteLogo from "@/components/navigation/SiteLogo";
|
||||||
|
import GetStartedButton from "@/components/navigation/GetStartedButton";
|
||||||
|
|
||||||
|
const Navbar = () => {
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const isGetStartedPage = pathname === "/get-started";
|
||||||
|
const isHeroOrAboutPage = pathname === "/" || pathname === "/about";
|
||||||
|
const isAboutPage = pathname === "/about";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={`relative z-50 ${
|
||||||
|
isHeroOrAboutPage ? "bg-transparent" : "bg-[var(--color-1)]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<nav className="flex items-center h-[75px] px-6 md:px-12 relative">
|
||||||
|
<div className="flex-shrink-0" onClick={() => setMenuOpen(false)}>
|
||||||
|
<SiteLogo />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden min-[828px]:flex min-[870px]:hidden items-center gap-6 ml-auto">
|
||||||
|
<NavLinks mobile={true} onLinkClick={() => setMenuOpen(false)} />
|
||||||
|
{!isGetStartedPage && <GetStartedButton />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`hidden min-[870px]:flex ${
|
||||||
|
isGetStartedPage
|
||||||
|
? "absolute left-1/2 transform -translate-x-1/2"
|
||||||
|
: "flex-1 justify-center transform -translate-x-[7.5%]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<NavLinks onLinkClick={() => setMenuOpen(false)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden min-[870px]:flex flex-shrink-0">
|
||||||
|
{!isGetStartedPage && <GetStartedButton />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setMenuOpen(!menuOpen)}
|
||||||
|
className="min-[828px]:hidden absolute right-6 top-1/2 transform -translate-y-1/2 p-2 text-[var(--color-8)]"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
{menuOpen ? <X size={26} /> : <Menu size={26} />}
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{menuOpen && (
|
||||||
|
<div className="min-[828px]:hidden bg-[var(--color-1)] border-t border-[var(--color-2)]">
|
||||||
|
<div className="flex flex-col items-center py-4 space-y-4">
|
||||||
|
<NavLinks mobile={true} onLinkClick={() => setMenuOpen(false)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="absolute left-0 right-0 h-[3px] mx-auto w-[87.5%]"
|
||||||
|
style={{
|
||||||
|
background: `${
|
||||||
|
isAboutPage
|
||||||
|
? "color-mix(in srgb, var(--color-2) 50%, transparent)"
|
||||||
|
: "var(--color-2)"
|
||||||
|
}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const SiteLogo = () => {
|
||||||
|
return (
|
||||||
|
<Link href="/" className="flex items-center gap-1">
|
||||||
|
<div
|
||||||
|
className="relative overflow-hidden w-[100px] h-[100px] sm:w-[120px] sm:h-[120px] lg:w-[150px] lg:h-[150px]"
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="/assets/images/logo_no_bg.png"
|
||||||
|
alt="Logo"
|
||||||
|
width={150}
|
||||||
|
height={150}
|
||||||
|
priority
|
||||||
|
style={{ objectFit: "contain", marginTop: 4 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="font-mono text-xl sm:text-2xl lg:text-3xl z-100">
|
||||||
|
LAN CODES
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SiteLogo;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { ToastContainer } from "react-toastify";
|
||||||
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
|
||||||
|
const ToastProvider = () => {
|
||||||
|
return (
|
||||||
|
<ToastContainer
|
||||||
|
position="bottom-right"
|
||||||
|
autoClose={3000}
|
||||||
|
newestOnTop
|
||||||
|
closeOnClick
|
||||||
|
pauseOnHover={false}
|
||||||
|
theme="colored"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToastProvider;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { ProjectItem } from "@/data/StaticData";
|
||||||
|
|
||||||
|
interface ProjectItemsProps {
|
||||||
|
items: ProjectItem[];
|
||||||
|
activeIndex: number;
|
||||||
|
setActiveIndex: (idx: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectItems = ({
|
||||||
|
items,
|
||||||
|
activeIndex,
|
||||||
|
setActiveIndex,
|
||||||
|
}: ProjectItemsProps) => {
|
||||||
|
return (
|
||||||
|
<ul className="space-y-8">
|
||||||
|
{items.map(({ title }, idx) => {
|
||||||
|
const isActive = idx === activeIndex;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={title}>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveIndex(idx)}
|
||||||
|
className={`ml-1 w-full text-left flex items-center gap-4 rounded-md px-2 py-2 transition cursor-pointer ${
|
||||||
|
isActive && "bg-[var(--color-2)] md:rounded-r-none"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`inline-block w-3.5 h-3.5 rotate-45 ${
|
||||||
|
isActive
|
||||||
|
? "bg-[var(--color-8)] shadow-[0_0_10px_rgba(127,17,224,1)]"
|
||||||
|
: "bg-[var(--color-10)]"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<span className="font-mono text-sm sm:text-base">{title}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectItems;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
export type ProjectItem = {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const projectItemsData: ProjectItem[] = [
|
||||||
|
{
|
||||||
|
title: "Seamless integration",
|
||||||
|
content:
|
||||||
|
"Implementation of this product in to your day to day workflow should not present with any challenges for your core team. Only training and understanding of the of the product will be needed by your administrators and/or your IT department.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Email protection",
|
||||||
|
content:
|
||||||
|
"Identifies and quarantines emails with potential malware or ransomware attachments. Prevents threats from domain impersonation, spoofing, and other misleading phishing attempts. Allows you to create and maintain a list of individual senders or sending domains to allow or block from your business inboxes.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Email encryption",
|
||||||
|
content:
|
||||||
|
"Fully automated process for both inbound and outbound emails with in the company or B2B communications. Removing a request of end user to check boxes by automating the process based on the email constant. Allowing your team to determent what is considered to be a sensitive information and ensuring that system will encrypt your next critical email.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "User risk reporting",
|
||||||
|
content:
|
||||||
|
"Get a full overview of your end user risk status across all the application at one place. Have one stop shop both for user application risk and use best coerce of action to mitigated any form of user risk. ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "User activity tracking",
|
||||||
|
content:
|
||||||
|
"Allows you to create a full time line of user activity from knowing location to time and action that took place. Keeping track of all user app activity in one place allows you to better track compromised user and act on mitigation of any breaches. ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Organization security overview",
|
||||||
|
content:
|
||||||
|
"Connect all the web-app tools that are used with in the company and have a full overview of the best course of action on what security changes or tuning need to be done to ensure maximum level of security.",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Malware protection",
|
||||||
|
// content:
|
||||||
|
// "Continuous malware detection and response powered by up-to-date threat intelligence.",
|
||||||
|
// },
|
||||||
|
];
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export type ContactRecord = {
|
||||||
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
companyName: string;
|
||||||
|
address: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
industry: string;
|
||||||
|
notes?: string;
|
||||||
|
subscribe: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function saveContact({
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
companyName,
|
||||||
|
address,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
industry,
|
||||||
|
notes,
|
||||||
|
subscribe,
|
||||||
|
}: Omit<ContactRecord, "id" | "createdAt">): Promise<ContactRecord> {
|
||||||
|
const { id, createdAt } = await prisma.contact.create({
|
||||||
|
data: {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
companyName,
|
||||||
|
address,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
industry,
|
||||||
|
notes,
|
||||||
|
subscribe,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
companyName,
|
||||||
|
address,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
industry,
|
||||||
|
notes,
|
||||||
|
subscribe,
|
||||||
|
createdAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
|
||||||
|
|
||||||
|
export const prisma =
|
||||||
|
globalForPrisma.prisma ??
|
||||||
|
new PrismaClient({
|
||||||
|
log: ["error", "warn"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||||
|
|
||||||
|
export default prisma;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "project-adonis",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "prisma generate && next build --turbopack",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "biome check",
|
||||||
|
"format": "biome format --write",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:migrate:dev": "prisma migrate dev --name init",
|
||||||
|
"prisma:migrate:deploy": "prisma migrate deploy"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^5.2.2",
|
||||||
|
"@prisma/client": "^5.22.0",
|
||||||
|
"lucide-react": "^0.545.0",
|
||||||
|
"next": "15.5.5",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0",
|
||||||
|
"react-hook-form": "^7.65.0",
|
||||||
|
"react-toastify": "^11.0.5",
|
||||||
|
"zod": "^4.1.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.2.0",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"prisma": "^5.22.0",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: ["@tailwindcss/postcss"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Contact" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"firstName" TEXT NOT NULL,
|
||||||
|
"lastName" TEXT NOT NULL,
|
||||||
|
"companyName" TEXT NOT NULL,
|
||||||
|
"address" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"phone" TEXT NOT NULL,
|
||||||
|
"industry" TEXT NOT NULL,
|
||||||
|
"notes" TEXT,
|
||||||
|
"subscribe" BOOLEAN NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "Contact_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (i.e. Git)
|
||||||
|
provider = "postgresql"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Contact {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
firstName String
|
||||||
|
lastName String
|
||||||
|
companyName String
|
||||||
|
address String
|
||||||
|
email String
|
||||||
|
phone String
|
||||||
|
industry String
|
||||||
|
notes String?
|
||||||
|
subscribe Boolean
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2014-2020 The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
Fira Code Variable Font
|
||||||
|
=======================
|
||||||
|
|
||||||
|
This download contains Fira Code as both a variable font and static fonts.
|
||||||
|
|
||||||
|
Fira Code is a variable font with this axis:
|
||||||
|
wght
|
||||||
|
|
||||||
|
This means all the styles are contained in a single file:
|
||||||
|
FiraCode-VariableFont_wght.ttf
|
||||||
|
|
||||||
|
If your app fully supports variable fonts, you can now pick intermediate styles
|
||||||
|
that aren’t available as static fonts. Not all apps support variable fonts, and
|
||||||
|
in those cases you can use the static font files for Fira Code:
|
||||||
|
static/FiraCode-Light.ttf
|
||||||
|
static/FiraCode-Regular.ttf
|
||||||
|
static/FiraCode-Medium.ttf
|
||||||
|
static/FiraCode-SemiBold.ttf
|
||||||
|
static/FiraCode-Bold.ttf
|
||||||
|
|
||||||
|
Get started
|
||||||
|
-----------
|
||||||
|
|
||||||
|
1. Install the font files you want to use
|
||||||
|
|
||||||
|
2. Use your app's font picker to view the font family and all the
|
||||||
|
available styles
|
||||||
|
|
||||||
|
Learn more about variable fonts
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
|
||||||
|
https://variablefonts.typenetwork.com
|
||||||
|
https://medium.com/variable-fonts
|
||||||
|
|
||||||
|
In desktop apps
|
||||||
|
|
||||||
|
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
|
||||||
|
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
|
||||||
|
|
||||||
|
Online
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/getting_started
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
|
||||||
|
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
|
||||||
|
|
||||||
|
Installing fonts
|
||||||
|
|
||||||
|
MacOS: https://support.apple.com/en-us/HT201749
|
||||||
|
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
|
||||||
|
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
|
||||||
|
|
||||||
|
Android Apps
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/android
|
||||||
|
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
Please read the full license text (OFL.txt) to understand the permissions,
|
||||||
|
restrictions and requirements for usage, redistribution, and modification.
|
||||||
|
|
||||||
|
You can use them in your products & projects – print or digital,
|
||||||
|
commercial or otherwise.
|
||||||
|
|
||||||
|
This isn't legal advice, please consider consulting a lawyer and see the full
|
||||||
|
license for all details.
|
||||||
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 857 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const phoneSchema = z
|
||||||
|
.string()
|
||||||
|
.min(6, "Phone number is too short")
|
||||||
|
.max(20, "Phone number is too long")
|
||||||
|
.regex(/^\+?[1-9]\d{1,14}$/, "Invalid international phone number");
|
||||||
|
|
||||||
|
export const contactSchema = z.object({
|
||||||
|
firstName: z.string().min(3, "First name must be at least 3 characters long"),
|
||||||
|
lastName: z.string().min(3, "Last name must be at least 3 characters long"),
|
||||||
|
companyName: z
|
||||||
|
.string()
|
||||||
|
.min(3, "Company name must be at least 3 characters long"),
|
||||||
|
address: z.string().min(3, "Address must be at least 3 characters long"),
|
||||||
|
email: z.email("Invalid email address"),
|
||||||
|
phone: phoneSchema,
|
||||||
|
industry: z.string().min(1, "Industry is required"),
|
||||||
|
notes: z.string().optional(),
|
||||||
|
subscribe: z.boolean(),
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||