Refactor website (#2243)

## Description

Switches to Vue for easier maintenance (pre-built in CI). Changes UI to
look a little nicer
<img width="1920" height="1080" alt="image"
src="https://github.com/user-attachments/assets/c5933326-c391-4d5c-8b3c-d3eeaa11a2f9"
/>
<img width="1920" height="1080" alt="image"
src="https://github.com/user-attachments/assets/d933c8d0-e5a0-40c0-bc80-0c4c8a4cc4f0"
/>
<img width="1920" height="1080" alt="image"
src="https://github.com/user-attachments/assets/3d8a652b-bd49-4147-a269-140e79fd4164"
/>
<img width="1021" height="1352" alt="image"
src="https://github.com/user-attachments/assets/545a9c02-541c-4a34-b81f-587f21bc682f"
/>
<img width="957" height="1196" alt="image"
src="https://github.com/user-attachments/assets/0dfd8080-0ffe-48c5-8fe0-11177f95dfc9"
/>


## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
This commit is contained in:
Graham
2025-12-30 01:44:08 -05:00
committed by GitHub
parent 80d3efe00e
commit d30ae6cc27
11 changed files with 1351 additions and 320 deletions

View File

@@ -1,15 +1,19 @@
<!doctype html>
<html lang="en-US">
<!-- TODO: We need more images and videos to improve quality of the site- if you can see this and have PhotonVision, take a screenshot of yourself using it and send it to the #media channel in Discord!-->
<head>
<title>PhotonVision</title>
<link rel="icon" href="favicon.svg" type="image/svg+xml" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="src/css/main.css" />
<link rel="stylesheet" href="src/css/fontawesome.min.css" />
<link rel="stylesheet" href="src/css/brands.min.css" />
<link rel="stylesheet" href="src/css/solid.min.css" />
<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=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&family=Prompt:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
rel="stylesheet"
/>
<meta name="title" content="PhotonVision" />
<meta
@@ -31,308 +35,7 @@
/>
</head>
<body class="home bg-zinc-900 font-light text-white w-screen">
<div id="page-wrapper" class="h-screen overflow-y-auto scroll-smooth">
<!-- Header -->
<header
id="header"
class="bg-zinc-800 flex md:justify-end sticky top-0 w-full z-10"
>
<button
class="header-btn hover:bg-zinc-700 cursor-pointer flex md:!hidden"
popovertarget="mobile-nav"
aria-label="Open mobile navigation menu"
>
<i class="fa-solid fa-bars !leading-[24px]"></i>
</button>
<div class="relative">
<div
popover
id="mobile-nav"
class="h-screen starting:open:-translate-x-50 open:translate-0 transition-transform -translate-x-50 relative duration-400 transition-discrete bg-zinc-700 open:flex open:flex-col md:open:hidden"
>
<a
href="https://docs.photonvision.org/en/latest/"
class="header-btn"
>Documentation</a
>
<a href="https://discord.gg/wYxTwym" class="header-btn">Discord</a>
<a
href="https://github.com/PhotonVision/photonvision/"
class="header-btn"
>GitHub</a
>
<a
href="https://www.redbubble.com/people/PhotonVision/shop?asc=u"
class="header-btn"
>Merch</a
>
<a
href="https://demo.photonvision.org"
class="header-btn bg-primary"
>Demo</a
>
</div>
</div>
<nav id="nav" class="hidden md:flex">
<a href="https://docs.photonvision.org/en/latest/" class="header-btn"
>Documentation</a
>
<a href="https://discord.gg/wYxTwym" class="header-btn">Discord</a>
<a
href="https://github.com/PhotonVision/photonvision/"
class="header-btn"
>GitHub</a
>
<a
href="https://www.redbubble.com/people/PhotonVision/shop?asc=u"
class="header-btn"
>Merch</a
>
<a href="https://demo.photonvision.org" class="header-btn bg-primary"
>Demo</a
>
</nav>
</header>
<!-- One -->
<section
class="flex flex-col h-[calc(100vh_-_56px)] justify-center items-center p-8 md:p-0 relative"
>
<div class="flex justify-center md:justify-end flex-col md:flex-row">
<header
class="flex flex-col gap-4 items-center md:items-end justify-center text-center md:text-end"
>
<h2 class="text-4xl">PhotonVision</h2>
<div class="text-xl max-w-128">
PhotonVision is the free, fast, and easy-to-use computer vision
solution for the FIRST® Robotics Competition. Teams can download
a PhotonVision image for select coprocessors and start tracking
targets in minutes.
</div>
<a
href="https://docs.photonvision.org/en/latest/docs/quick-start/quick-install.html"
class="bg-primary py-4 px-8 rounded grow-0 self-center md:self-end"
>Get Started</a
>
</header>
<span class="self-center md:self-initial mt-8 md:ms-8 shrink"
><img
src="images/PhotonVision-Icon-BG.png"
alt="PhotonVision Logo"
class="max-w-64"
/></span>
</div>
<a href="#two" class="absolute bottom-0 p-4">
<i class="fa-solid fa-chevron-down"></i>
</a>
</section>
<!-- Two -->
<section
id="two"
class="h-[calc(100vh_-_56px)] relative justify-center flex items-center"
>
<span class="image fit"
><img
src="images/demo.png"
alt="Demo of PhotonVision UI"
loading="lazy"
/></span>
<a href="#three" class="absolute bottom-0 p-4"
><i class="fa-solid fa-chevron-down"></i
></a>
</section>
<!-- Three -->
<section
id="three"
class="min-h-[calc(100vh_-_56px)] flex flex-col gap-4 items-center justify-center px-4 md:px-14 lg:px-28 relative"
>
<header
class="flex flex-col gap-4 justify-center items-center text-center"
>
<h2 class="text-3xl">The Future is in Sight</h2>
<p class="text-2xl">
PhotonVision is a powerful, open-source vision system for FRC. It's
designed to be fast and easy to use, regardless of your team's
financial or technical resources.
</p>
<hr class="border-brand-yellow border-2 w-64 mb-4" />
</header>
<div class="mb-8 lg:mb-0">
<div
class="grid max-w-xl grid-cols-1 gap-x-8 gap-y-10 lg:max-w-none lg:grid-cols-2 lg:gap-y-16"
>
<section class="relative pl-16">
<div class="features-icon">
<i class="fa-solid fa-location-arrow"></i>
</div>
<h3 class="text-lg mb-2">First-Class AprilTag Support</h3>
<p>FRC Target tracking, out of the box.</p>
</section>
<section class="relative pl-16">
<div class="features-icon">
<i class="icon alt major fa-solid fa-chess-board"></i>
</div>
<h3 class="text-lg mb-2">Built-In Camera Calibration</h3>
<p>
Per-camera intrinsics calibration maximizes accuracy of
homography
</p>
</section>
<section class="relative pl-16">
<div class="features-icon">
<i class="icon alt major fa-solid fa-brain"></i>
</div>
<h3 class="text-lg mb-2">Machine Learning</h3>
<p>Hardware-accelerated inferencing for gamepiece detection</p>
</section>
<section class="relative pl-16">
<div class="features-icon">
<i class="icon alt major fa-solid fa-eye"></i>
</div>
<h3 class="text-lg mb-2">Driver Mode Integration</h3>
<p>You can use the same camera for driving and robot vision</p>
</section>
<section class="relative pl-16">
<div class="features-icon">
<i class="icon alt major fa-solid fa-camera"></i>
</div>
<h3 class="text-lg mb-2">Simultaneous Multi-Camera Operation</h3>
<p>
PhotonVision can run as many cameras as your hardware can handle
</p>
</section>
<section class="relative pl-16">
<div class="features-icon">
<i class="icon alt major fa-solid fa-layer-group"></i>
</div>
<h3 class="text-lg mb-2">Multi-Tag Pose Estimation</h3>
<p>Fuse all your available data for peak robot performance</p>
</section>
</div>
</div>
<a href="#four" class="absolute bottom-0 p-4"
><i class="fa-solid fa-chevron-down"></i
></a>
</section>
<!-- Four -->
<section id="four">
<div class="">
<video
src="images/in-action.mp4"
playsinline
autoplay
loop
muted
loading="lazy"
class="w-full h-[calc(100vh_-_56px)] object-cover bottom-0 relative"
></video>
</div>
</section>
<section
id="five"
class="flex flex-col md:flex-row items-center justify-between relative mt-8 py-8 px-28 gap-8 bg-primary"
>
<header
class="flex flex-col gap-4 justify-center items-center text-center"
>
<h2 class="text-3xl">Champs 2024 Talk</h2>
<div class="flex gap-2 mb-4">
<a
href="https://docs.google.com/presentation/d/1Gh5InslM5p7aDxjzK8DHoEorpATOl-MQWWixY5GjGgs/edit#slide=id.p"
class="bg-brand-blue py-4 px-8 rounded grow-0 text-nowrap"
>Slide Deck</a
>
<a
href="https://github.com/PhotonVision/champs_2024"
class="bg-brand-blue py-4 px-8 rounded grow-0 text-nowrap"
>Code</a
>
</div>
</header>
<div class="self-stretch contents">
<iframe
src="https://www.youtube-nocookie.com/embed/iV2v7F_9GwE?si=4wgaT1IrZBpA71dF"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
class="max-w-256 aspect-video"
></iframe>
</div>
</section>
<!-- Eight -->
<section
id="six"
class="min-h-[calc(100vh_-_56px)] flex flex-col gap-4 items-center justify-center px-14 lg:px-28"
>
<header
class="flex flex-col gap-4 justify-center items-center text-center"
>
<h2 class="text-3xl">FOSS</h2>
<p class="text-2xl">
PhotonVision is an open-source, community based vision system
designed for use within the FIRST® Robotics Competition that aims
to provide easy and inexpensive vision tracking to teams.
</p>
<hr class="border-brand-yellow border-2 w-64 mb-4" />
</header>
<div>
<div
class="grid max-w-xl grid-cols-1 gap-x-8 gap-y-10 lg:max-w-none lg:grid-cols-2 lg:gap-y-16"
>
<section class="relative pl-16">
<div class="features-icon">
<i class="icon alt major fa-solid fa-check"></i>
</div>
<h3 class="text-lg mb-2">Open Source</h3>
<p>
PhotonVision is the largest FOSS FRC Vision project to date,
constantly being updated with new features and bug fixes.
</p>
</section>
<section class="relative pl-16">
<div class="features-icon">
<i class="icon alt major fa-solid fa-balance-scale"></i>
</div>
<h3 class="text-lg mb-2">GNU GPL v3</h3>
<p>
The GNU GPL v3 license allows you to download, modify and share
source code.
</p>
</section>
<section class="relative pl-16">
<div class="features-icon">
<i class="icon alt major fab fa-github"></i>
</div>
<h3 class="text-lg mb-2">We're on GitHub</h3>
<p>
We do all of our development openly on GitHub. Transparency is
key for every contributor of PhotonVision.
</p>
</section>
</div>
</div>
</section>
<!-- Footer -->
<footer
id="footer"
class="p-14 bg-zinc-700 flex flex-col items-center gap-8"
>
<img
src="images/PhotonVision-Icon-BG.png"
style="width: 50px; height: 50px"
alt="PhotonVision logo"
/>
<ul class="text-sm">
<li>&copy;2025 PhotonVision. All rights reserved.</li>
</ul>
</footer>
</div>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -5,16 +5,20 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "tsc && vite-ssg build",
"preview": "vite preview"
},
"devDependencies": {
"@toolwind/anchors": "^1.0.10",
"prettier": "^3.5.3",
"typescript": "~5.7.2",
"vite": "^7.1.2"
"vite": "^7.1.2",
"vite-ssg": "^28.2.2"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.12",
"tailwindcss": "^4.1.12"
"@vitejs/plugin-vue": "^6.0.2",
"tailwindcss": "^4.1.12",
"vue": "^3.5.25"
}
}

813
website/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

365
website/src/App.vue Normal file
View File

@@ -0,0 +1,365 @@
<script setup lang="ts">
import Button from "./components/Button.vue";
import GridSection from "./components/GridSection.vue";
const navLinks = [
{
href: "https://docs.photonvision.org/en/latest/",
label: "Documentation",
icon: "fa-solid fa-book",
},
{
href: "https://discord.gg/wYxTwym",
label: "Discord",
icon: "fab fa-discord",
},
{
href: "https://github.com/PhotonVision/photonvision/",
label: "GitHub",
icon: "fab fa-github",
},
{
href: "https://www.redbubble.com/people/PhotonVision/shop?asc=u",
label: "Merch",
icon: "fa-solid fa-shirt",
},
];
const features = [
{
icon: "fa-solid fa-location-arrow",
title: "First-Class AprilTag Support",
description: "FRC Target tracking, out of the box.",
},
{
icon: "fa-solid fa-chess-board",
title: "Built-In Camera Calibration",
description:
"Per-camera intrinsics calibration maximizes accuracy of homography",
},
{
icon: "fa-solid fa-brain",
title: "Machine Learning",
description: "Hardware-accelerated inferencing for gamepiece detection",
},
{
icon: "fa-solid fa-eye",
title: "Driver Mode Integration",
description: "Use the same camera for driving and robot vision",
},
{
icon: "fa-solid fa-camera",
title: "Simultaneous Multi-Camera Operation",
description:
"PhotonVision can run as many cameras as your hardware can handle",
},
{
icon: "fa-solid fa-layer-group",
title: "Multi-Tag Pose Estimation",
description: "Fuse all your available data for peak robot performance",
},
];
const fossFeatures = [
{
icon: "fa-solid fa-check",
title: "Open Source",
description:
"PhotonVision is the largest FOSS FRC Vision project to date, constantly being updated with new features and bug fixes.",
},
{
icon: "fa-solid fa-balance-scale",
title: "GNU GPL v3",
description:
"The GNU GPL v3 license allows you to download, modify and share source code.",
},
{
icon: "fab fa-github",
title: "We're on GitHub",
description:
"We do all of our development openly on GitHub. Transparency is key for every contributor of PhotonVision.",
},
];
const socialLinks = [
{
href: "https://github.com/PhotonVision/photonvision/",
icon: "fab fa-github",
label: "GitHub",
},
{
href: "https://discord.gg/wYxTwym",
icon: "fab fa-discord",
label: "Discord",
},
];
</script>
<template>
<div id="page-wrapper" class="h-screen overflow-y-auto scroll-smooth">
<header
id="header"
class="bg-zinc-900/80 backdrop-blur-md flex items-center justify-between sticky top-0 w-full z-50 border-b border-zinc-700/50 px-4 md:px-8"
>
<a href="#" class="flex items-center gap-3 py-2 group">
<img
src="/images/PhotonVision-Icon-BG.png"
alt="PhotonVision"
class="w-10 h-10 group-hover:scale-110 transition-transform duration-300"
/>
<span class="font-bold text-xl hidden sm:block text-white">
PhotonVision
</span>
</a>
<div>
<!-- Mobile menu button -->
<button
class="p-3 hover:bg-zinc-700/50 rounded-lg cursor-pointer flex md:!hidden transition-colors anchor/my-anchor"
popovertarget="mobile-nav"
aria-label="Open mobile navigation menu"
>
<i class="fa-solid fa-bars text-xl"></i>
</button>
<div class="relative">
<div
popover
id="mobile-nav"
class="bg-zinc-900/95 backdrop-blur-lg transition-opacity starting:open:opacity-0 open:opacity-100 opacity-0 duration-300 transition-discrete open:flex open:flex-col md:open:hidden items-center justify-center gap-2 anchored/my-anchor anchored-bottom-span-left overflow-clip p-2 rounded-b-2xl"
>
<a
v-for="link in navLinks"
:key="link.href"
:href="link.href"
class="flex items-center gap-3 px-6 py-4 text-xl text-white hover:text-brand-yellow transition-colors"
>
<i :class="link.icon"></i>
{{ link.label }}
</a>
<Button
href="https://demo.photonvision.org"
variant="primary"
size="sm"
>
Demo
</Button>
</div>
</div>
</div>
<!-- Desktop nav -->
<nav id="nav" class="hidden md:flex items-center gap-1">
<a
v-for="link in navLinks"
:key="link.href"
:href="link.href"
class="flex items-center gap-2 px-4 py-2 rounded-lg hover:bg-zinc-700/50 hover:text-brand-yellow transition-all duration-200"
>
<i :class="[link.icon, 'text-sm opacity-70']"></i>
{{ link.label }}
</a>
<Button
href="https://demo.photonvision.org"
variant="primary"
size="sm"
class="ml-2"
>
Demo
</Button>
</nav>
</header>
<section
class="flex flex-col min-h-[calc(100vh_-_56px)] md:justify-center items-center p-8 md:p-16 relative overflow-hidden"
>
<div
class="flex justify-center md:justify-start flex-col md:flex-row items-start"
>
<span class="self-center md:self-initial mb-8 md:mb-0 md:me-12 shrink">
<img
src="/images/PhotonVision-Icon-BG.png"
alt="PhotonVision Logo"
class="max-w-20 md:max-w-80 drop-shadow-2xl md:p-0 transition-transform duration-500"
/>
</span>
<header
class="flex flex-col gap-6 items-center md:items-start justify-center text-center md:text-start"
>
<h2
class="text-5xl md:text-6xl font-bold drop-shadow-lg font-heading"
>
PhotonVision
</h2>
<div
class="text-xl md:text-2xl max-w-128 text-zinc-300 leading-relaxed"
>
PhotonVision is the free, fast, and easy-to-use computer vision
solution for the FIRST® Robotics Competition. Teams can download a
PhotonVision image for select coprocessors and start tracking
targets in minutes.
</div>
<Button
href="https://docs.photonvision.org/en/latest/docs/quick-start/quick-install.html"
variant="primary"
size="lg"
class="self-center md:self-start rounded-xl"
>
Get Started
</Button>
</header>
</div>
<a
href="#demo"
class="absolute bottom-8 p-4 text-brand-yellow/70 hover:text-brand-yellow hover:translate-y-1 transition-all duration-300 animate-bounce"
>
<i class="fa-solid fa-chevron-down text-2xl"></i>
</a>
</section>
<section
id="demo"
class="min-h-[calc(100vh_-_56px)] relative justify-center flex items-center py-16 bg-zinc-950"
>
<div class="relative mx-4 md:mx-16 lg:mx-32">
<div
class="absolute -inset-4 bg-gradient-to-r from-primary via-brand-blue to-brand-yellow rounded-2xl blur-xl opacity-30"
></div>
<img
src="/images/demo.png"
alt="Demo of PhotonVision UI"
loading="lazy"
class="relative rounded-xl shadow-2xl border border-zinc-700/50"
/>
</div>
<a
href="#features"
class="absolute bottom-8 p-4 text-brand-yellow/70 hover:text-brand-yellow hover:translate-y-1 transition-all duration-300"
>
<i class="fa-solid fa-chevron-down text-2xl"></i>
</a>
</section>
<GridSection
id="features"
description="PhotonVision is a powerful, open-source vision system for FRC. It's designed to be fast and easy to use, regardless of your team's financial or technical resources."
:features="features"
:columns="2"
show-scroll-indicator
scroll-target="#video"
>
<template #title>
The Future is in <span class="text-brand-yellow">Sight</span>
</template>
</GridSection>
<section id="video" class="relative">
<div class="relative">
<video
src="/images/in-action.mp4"
playsinline
autoplay
loop
muted
class="w-full h-[calc(100vh_-_56px)] object-cover"
></video>
<div
class="absolute inset-0 bg-gradient-to-t from-zinc-900/60 via-transparent to-zinc-900/30 pointer-events-none"
></div>
</div>
</section>
<section
id="champs"
class="flex flex-col lg:flex-row items-center justify-center relative py-16 px-8 md:px-16 lg:px-28 gap-12 bg-primary"
>
<header
class="flex flex-col gap-6 justify-center items-center lg:items-start text-center lg:text-left"
>
<h2 class="text-4xl font-bold font-heading">Champs 2024 Talk</h2>
<p class="text-zinc-200 text-lg max-w-md">
Watch our presentation from the 2024 FIRST Championship and learn how
to get the most out of PhotonVision.
</p>
<div class="flex gap-4">
<Button
href="https://docs.google.com/presentation/d/1Gh5InslM5p7aDxjzK8DHoEorpATOl-MQWWixY5GjGgs/edit#slide=id.p"
variant="secondary"
>
Slide Deck
</Button>
<Button
href="https://github.com/PhotonVision/champs_2024"
variant="outline"
>
View Code
</Button>
</div>
</header>
<div class="flex-1 max-w-3xl w-full">
<div
class="relative rounded-xl overflow-hidden shadow-2xl border border-white/10"
>
<iframe
src="https://www.youtube-nocookie.com/embed/iV2v7F_9GwE?si=4wgaT1IrZBpA71dF"
title="YouTube video player"
frameborder="0"
allow="
accelerometer;
autoplay;
clipboard-write;
encrypted-media;
gyroscope;
picture-in-picture;
web-share;
"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
class="w-full aspect-video"
></iframe>
</div>
</div>
</section>
<GridSection
id="foss"
description="PhotonVision is an open-source, community based vision system designed for use within the FIRST® Robotics Competition that aims to provide easy and inexpensive vision tracking to teams."
:features="fossFeatures"
:columns="3"
reverse-cards
>
<template #title>
Built by the <span class="text-brand-yellow">Community</span>
</template>
</GridSection>
<footer id="footer" class="py-12 px-8 bg-zinc-800 border-t border-zinc-700">
<div
class="max-w-6xl mx-auto flex flex-col md:flex-row items-center justify-between gap-8"
>
<div class="flex items-center gap-4">
<img
src="/images/PhotonVision-Icon-BG.png"
class="w-12 h-12 hover:scale-110 transition-transform duration-300"
alt="PhotonVision logo"
/>
<span class="font-semibold text-lg">PhotonVision</span>
</div>
<div class="flex gap-6">
<a
v-for="link in socialLinks"
:key="link.href"
:href="link.href"
class="text-zinc-400 hover:text-brand-yellow transition-colors"
:aria-label="link.label"
>
<i :class="[link.icon, 'text-2xl']"></i>
</a>
</div>
<p class="text-sm text-zinc-400">
&copy;{{ new Date().getFullYear() }} PhotonVision. All rights
reserved.
</p>
</div>
</footer>
</div>
</template>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
defineProps<{
href: string;
variant?: "primary" | "secondary" | "outline";
size?: "sm" | "md" | "lg";
}>();
const variantClasses = {
primary:
"bg-primary shadow-lg shadow-primary/30 hover:shadow-xl hover:shadow-primary/40 hover:scale-105",
secondary: "bg-brand-blue hover:bg-brand-blue/80 shadow-lg",
outline: "bg-zinc-800 hover:bg-zinc-700 shadow-lg border border-zinc-600",
};
const sizeClasses = {
sm: "py-2 px-4 text-sm",
md: "py-3 px-6",
lg: "py-4 px-10 text-lg",
};
</script>
<template>
<a
:href="href"
:class="[
'rounded-lg font-medium text-nowrap transition-all duration-300 inline-block text-center',
variantClasses[variant ?? 'primary'],
sizeClasses[size ?? 'md'],
]"
>
<slot />
</a>
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
defineProps<{
icon: string;
title: string;
description: string;
reverse?: boolean;
}>();
</script>
<template>
<section
:class="[
'relative rounded-xl bg-zinc-800/50 border border-zinc-700/50 flex flex-col',
reverse ? 'p-8' : 'p-6',
]"
>
<div
:class="[
'flex items-center justify-center rounded-xl bg-linear-to-br from-brand-blue to-primary shadow-lg',
'size-12 rounded-2xl mb-4',
]"
>
<i :class="[icon, reverse ? 'text-2xl' : '']"></i>
</div>
<h3
:class="[
'font-semibold text-brand-yellow',
reverse ? 'text-xl mb-3' : 'text-xl mb-2',
]"
>
{{ title }}
</h3>
<p class="text-zinc-300">{{ description }}</p>
</section>
</template>

View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import FeatureCard from "./FeatureCard.vue";
withDefaults(
defineProps<{
id: string;
label?: string;
title?: string;
description: string;
features: {
icon: string;
title: string;
description: string;
}[];
columns?: 2 | 3;
reverseCards?: boolean;
showScrollIndicator?: boolean;
scrollTarget?: string;
}>(),
{
columns: 2,
reverseCards: false,
showScrollIndicator: false,
},
);
</script>
<template>
<section
:id="id"
class="min-h-[calc(100vh_-_56px)] flex flex-col gap-8 items-center justify-center px-4 md:px-14 lg:px-28 py-20 relative overflow-hidden"
>
<header
class="flex flex-col gap-6 justify-center items-center text-center max-w-4xl relative z-10"
>
<span
v-if="label"
class="text-brand-blue font-semibold tracking-wider uppercase text-sm"
>
{{ label }}
</span>
<h2 class="text-4xl md:text-5xl font-bold font-heading">
<slot name="title">
{{ title }}
</slot>
</h2>
<p class="text-xl md:text-2xl text-zinc-300 leading-relaxed">
{{ description }}
</p>
<div class="w-24 h-1 bg-brand-blue rounded-full"></div>
</header>
<div :class="label ? 'relative z-10' : 'mb-8 lg:mb-0'">
<div
class="grid max-w-5xl grid-cols-1 gap-6"
:class="columns === 3 ? 'lg:grid-cols-3' : 'lg:grid-cols-2'"
>
<FeatureCard
v-for="feature in features"
:key="feature.title"
:icon="feature.icon"
:title="feature.title"
:description="feature.description"
:reverse="reverseCards"
/>
</div>
</div>
<a
v-if="showScrollIndicator && scrollTarget"
:href="scrollTarget"
class="absolute bottom-8 p-4 text-brand-yellow/70 hover:text-brand-yellow hover:translate-y-1 transition-all duration-300"
>
<i class="fa-solid fa-chevron-down text-2xl"></i>
</a>
</section>
</template>

View File

@@ -1,15 +1,12 @@
@import "tailwindcss";
@import "@toolwind/anchors";
@theme {
--color-primary: #006492;
--color-brand-yellow: #ffd842;
--color-brand-blue: #39a4d6;
}
.header-btn {
@apply hover:text-brand-yellow text-white flex p-4 transition-colors;
}
.features-icon {
@apply absolute top-0 left-0 flex size-10 items-center justify-center rounded-lg bg-brand-blue;
--font-sans: "Lato", sans-serif;
--font-heading: "Prompt", sans-serif;
}
.fas::before,

7
website/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@@ -0,0 +1,5 @@
import { ViteSSG } from "vite-ssg/single-page";
import App from "./App.vue";
import "./css/main.css";
export const createApp = ViteSSG(App);

View File

@@ -1,5 +1,7 @@
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [tailwindcss()],
plugins: [vue(), tailwindcss()],
});