feat: build initial landing page

This commit introduces the first version of the Opp lens landing page, replacing the default Next.js starter content.

The new page is designed to attract SMEs by showcasing the AI-powered tender management platform. It includes several key sections:
- A hero section with a clear value proposition.
- Sections detailing the problem, how the platform works, its features, and its benefits.
- A call-to-action form for users to sign up for early access.
- A comprehensive footer with contact and social media links.

Key changes include:
- **`app/page.tsx`**: Completely rewritten to build the landing page structure and content.
- **`app/globals.css`**: Updated with a new dark theme, custom brand colors, and a gradient text utility class for headings.
- **Dependencies**: Added `axios` to handle the early access form submission.
- **SEO**: Implemented detailed metadata for improved search engine visibility.
This commit is contained in:
AmirReza Jamali
2025-10-14 16:23:00 +03:30
parent 27efcf7137
commit cd471695b7
12 changed files with 583 additions and 113 deletions
+252 -91
View File
@@ -1,103 +1,264 @@
import { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
export const metadata: Metadata = {
title: "Opp lens - AI-Powered Tender Management for SMEs | Win More Tenders",
description:
"Automated tender management platform for SMEs. Find tailored tenders, automate document handling, and get real-time alerts. Win more opportunities with less effort.",
keywords:
"tender management, AI tender search, SME tenders, public procurement, automated bidding, tender alerts",
authors: [{ name: "PBL Partners AB" }],
openGraph: {
title: "Opp lens - AI-Powered Tender Management",
description: "Win more tenders automatically with our AI-powered platform",
type: "website",
locale: "en_US",
siteName: "Opp lens",
},
icons: {
icon: "/fav-icon.svg",
},
};
export default function Home() {
const centerFrameItems = [
{
header: 200,
text: "Users",
description: `Lacking the time, dedicated staff, and
expertise to constantly monitor the market
and prepare high-quality bids.`,
},
{
header: 520,
text: "Contracts",
description: `Navigating complex requirements and
processes designed for large corporations
with dedicated tender departments.`,
},
{
header: 1500,
text: "Tenders",
description: `Did you know 68% of SMEs in Sweden
rarely or never win public tenders? It's time
to change that.`,
},
];
const contactInfo = [
{
icon: "/Message.svg",
contact: "info@opplens.com",
href: "mailto:info@opplens.com",
ariaLabel: "Email us at info@opplens.com",
},
{
icon: "/Phone.svg",
contact: "(+46) - 761581526",
href: "tel:+46761581526",
ariaLabel: "Call us at +46 761581526",
},
{
icon: "/Location.svg",
contact: "Stockholm, Sweden",
ariaLabel: "Our location in Stockholm, Sweden",
},
];
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
<div className="container px-4 py-4 m-auto">
<header className="flex justify-between items-center border-b md:border-none border-gray-300 pb-4">
<Link
href="/"
aria-label="Opp lens home">
<Image
src="/main-logo.svg"
alt="Opp lens logo"
width={180}
height={30}
priority
/>
</Link>
<div className="flex gap-5 items-center">
<nav
className="hidden md:flex"
aria-label="Main navigation">
<HeaderButtons />
</nav>
<label
htmlFor="language"
className="sr-only">
Select language
</label>
<select
name="language"
id="language"
aria-label="Language selector">
<option value="en">English</option>
</select>
</div>
</header>
<nav
className="block md:hidden"
aria-label="Mobile navigation">
<HeaderButtons />
</nav>
<main className="mt-8 flex flex-col justify-center items-center gap-14">
<section
className="flex"
aria-labelledby="hero-heading">
<div className="flex flex-col">
<h1
className="gradient-text md:!text-left md:!text-8xl md:mb-10"
id="hero-heading">
AI-Powered
</h1>
<Image
className="block md:hidden"
src={"/images/mobile.png"}
alt="Opp lens platform interface on mobile device"
width={520}
height={280}
priority
/>
<div className="w-full flex flex-col gap-6 text-xl">
<h2 className="text-(--primary) font-bold text-5xl md:!mb-10">
Tender Management
</h2>
<h3 className="font-bold">Win more tenders, automatically!</h3>
<p className="text-(--gray-primary)">
Our platform finds tenders tailored to your business, automates
document handling, and sends real-time alerts. Win more
opportunities with less effort and zero manual search.
</p>
</div>
</div>
<Image
className="hidden md:block"
src={"/images/mobile.png"}
alt="Opp lens platform interface on desktop"
width={550}
height={480}
priority
/>
</section>
<section
className="flex flex-col gap-4 text-center md:flex-row"
aria-labelledby="statistics-heading">
<h2
id="statistics-heading"
className="sr-only">
Platform Statistics
</h2>
{centerFrameItems.map((item, index) => (
<CenterFrame
key={index}
header={item.header}
text={item.text}
description={item.description}
/>
))}
</section>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<footer className="mt-14">
<div className="flex justify-center items-center border-t pt-5 border-gray-300">
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
src="/main-logo.svg"
alt="Opp lens logo"
width={180}
height={30}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</div>
<address className="md:flex md:justify-evenly w-full mt-14 px-5 not-italic">
{contactInfo.map((item) => (
<ContactInfo
key={item.contact}
contact={item.contact}
href={item.href}
icon={item.icon}
ariaLabel={item.ariaLabel}
/>
))}
</address>
<div className="flex justify-center items-center">
<p className="text-center text-(--gray-primary) mt-5 leading-10">
Stockholm, Sweden © 2025 Opplens.com by PBL Partners AB. All Rights
Reserved. Democratizing Tender Access for SMEs.
</p>
</div>
</footer>
</div>
);
}
function HeaderButtons() {
return (
<div className="flex justify-between items-center gap-5 mt-4 md:mt-0">
<Link
href={"/contact-us"}
className="text-(--primary)">
Contact us
</Link>
<Link
href={"/login"}
className="bg-(--primary) text-white px-12 py-3 rounded-full">
Login
</Link>
</div>
);
}
function CenterFrame({
header,
text,
description,
}: {
header: number;
text: string;
description: string;
}) {
return (
<article className="flex flex-col justify-center items-center gap-4">
<p
className="gradient-text"
aria-label={`${header} ${text}`}>
<strong>{header}</strong>
</p>
<h3 className="text-2xl font-semibold">{text}</h3>
<p className="text-(--gray-primary)">{description}</p>
</article>
);
}
function ContactInfo({
icon,
contact,
href,
ariaLabel,
}: {
icon: string;
contact: string;
href?: string;
ariaLabel: string;
}) {
return (
<div className="flex gap-3 my-3">
<Image
src={icon}
width={24}
height={24}
alt=""
aria-hidden="true"
/>
{href ? (
<a
href={href}
className="cursor-pointer"
aria-label={ariaLabel}>
{contact}
</a>
) : (
<p>{contact}</p>
)}
</div>
);
}