├── website
├── .eslintrc.json
├── public
│ ├── icons
│ │ ├── favicon.ico
│ │ └── apple-touch-icon.png
│ ├── images
│ │ ├── logo.png
│ │ ├── tejal.png
│ │ ├── veljko.jpg
│ │ ├── will.jpg
│ │ ├── landing-page.png
│ │ └── mac-bar.svg
│ ├── videos
│ │ ├── center.mp4
│ │ ├── resize.mp4
│ │ ├── total.mp4
│ │ ├── total.webm
│ │ ├── center.webm
│ │ ├── palette.mp4
│ │ ├── palette.webm
│ │ └── resize.webm
│ └── scripts
│ │ └── darkModeScript.js
├── src
│ ├── images
│ │ └── render-solo.png
│ ├── svg
│ │ ├── RightArrow.tsx
│ │ ├── ScatteredSpheres.tsx
│ │ ├── Quote.tsx
│ │ ├── DarkModeIcons.tsx
│ │ ├── NewsletterSpheres.tsx
│ │ └── FeatureIcons.tsx
│ ├── components
│ │ ├── GradientText.tsx
│ │ ├── Details.tsx
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ ├── Card.tsx
│ │ ├── Title.tsx
│ │ ├── Demo.tsx
│ │ ├── LinkButton.tsx
│ │ ├── Section.tsx
│ │ └── WaitlistForm.tsx
│ └── sections
│ │ ├── Footer.tsx
│ │ ├── Newsletter.tsx
│ │ ├── Hero.tsx
│ │ ├── Header.tsx
│ │ ├── FeatureBlocks.tsx
│ │ ├── Testimonials.tsx
│ │ └── Features.tsx
├── postcss.config.js
├── next.config.js
├── pages
│ ├── api
│ │ └── waitlist.ts
│ ├── _document.tsx
│ ├── index.tsx
│ └── _app.tsx
├── .gitignore
├── tsconfig.json
├── package.json
├── LICENSE
├── tailwind.config.js
├── README.md
└── styles
│ └── globals.css
├── Meshtastic-ESP-RA-BOM.csv
├── Meshtastic-ESP-RA-CPL.csv
├── Meshtastic-ESP-RA-Gerber.zip
├── ProPrj_Icarus-v2_2025-04-28.epro.zip
├── LICENSE
└── README.md
/website/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/Meshtastic-ESP-RA-BOM.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/Meshtastic-ESP-RA-BOM.csv
--------------------------------------------------------------------------------
/Meshtastic-ESP-RA-CPL.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/Meshtastic-ESP-RA-CPL.csv
--------------------------------------------------------------------------------
/Meshtastic-ESP-RA-Gerber.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/Meshtastic-ESP-RA-Gerber.zip
--------------------------------------------------------------------------------
/website/public/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/icons/favicon.ico
--------------------------------------------------------------------------------
/website/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/images/logo.png
--------------------------------------------------------------------------------
/website/public/images/tejal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/images/tejal.png
--------------------------------------------------------------------------------
/website/public/images/veljko.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/images/veljko.jpg
--------------------------------------------------------------------------------
/website/public/images/will.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/images/will.jpg
--------------------------------------------------------------------------------
/website/public/videos/center.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/videos/center.mp4
--------------------------------------------------------------------------------
/website/public/videos/resize.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/videos/resize.mp4
--------------------------------------------------------------------------------
/website/public/videos/total.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/videos/total.mp4
--------------------------------------------------------------------------------
/website/public/videos/total.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/videos/total.webm
--------------------------------------------------------------------------------
/website/public/videos/center.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/videos/center.webm
--------------------------------------------------------------------------------
/website/public/videos/palette.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/videos/palette.mp4
--------------------------------------------------------------------------------
/website/public/videos/palette.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/videos/palette.webm
--------------------------------------------------------------------------------
/website/public/videos/resize.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/videos/resize.webm
--------------------------------------------------------------------------------
/website/src/images/render-solo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/src/images/render-solo.png
--------------------------------------------------------------------------------
/ProPrj_Icarus-v2_2025-04-28.epro.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/ProPrj_Icarus-v2_2025-04-28.epro.zip
--------------------------------------------------------------------------------
/website/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/website/public/images/landing-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/images/landing-page.png
--------------------------------------------------------------------------------
/website/public/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azlan-works/Icarus/HEAD/website/public/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/website/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | i18n: {
6 | locales: ["en"],
7 | defaultLocale: "en",
8 | },
9 | };
10 |
11 | module.exports = nextConfig;
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
2 | To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a
3 | letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
4 |
--------------------------------------------------------------------------------
/website/pages/api/waitlist.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from "next";
2 |
3 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
4 | const { email } = req.body;
5 |
6 | res.status(200).json({ email });
7 | };
8 |
9 | export default handler;
10 |
--------------------------------------------------------------------------------
/website/src/svg/RightArrow.tsx:
--------------------------------------------------------------------------------
1 | export const RightArrow = () => (
2 |
7 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/website/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Head, Html, Main, NextScript } from "next/document";
2 | import Script from "next/script";
3 |
4 | const Document = () => {
5 | return (
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default Document;
18 |
--------------------------------------------------------------------------------
/website/src/components/GradientText.tsx:
--------------------------------------------------------------------------------
1 | import { DetailedHTMLProps, HTMLAttributes } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | export const GradientText = (
7 | props: DetailedHTMLProps, HTMLSpanElement>
8 | ) => {
9 | return (
10 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/website/src/components/Details.tsx:
--------------------------------------------------------------------------------
1 | import { DetailedHTMLProps, HTMLAttributes } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | export const Details = (
7 | props: DetailedHTMLProps<
8 | HTMLAttributes,
9 | HTMLParagraphElement
10 | >
11 | ) => {
12 | return (
13 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/website/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import { ButtonHTMLAttributes, DetailedHTMLProps } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | export const Button = (
7 | props: DetailedHTMLProps<
8 | ButtonHTMLAttributes,
9 | HTMLButtonElement
10 | >
11 | ) => (
12 |
20 | );
21 |
--------------------------------------------------------------------------------
/website/src/svg/ScatteredSpheres.tsx:
--------------------------------------------------------------------------------
1 | export const ScatteredSpheres = () => (
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/website/src/components/Input.tsx:
--------------------------------------------------------------------------------
1 | import { DetailedHTMLProps, InputHTMLAttributes } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | export const Input = (
7 | props: DetailedHTMLProps<
8 | InputHTMLAttributes,
9 | HTMLInputElement
10 | >
11 | ) => {
12 | return (
13 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": [
19 | "next-env.d.ts",
20 | "**/*.ts",
21 | "**/*.tsx",
22 | "public/scripts/darkModeScript.js"
23 | ],
24 | "exclude": ["node_modules"]
25 | }
26 |
--------------------------------------------------------------------------------
/website/src/svg/Quote.tsx:
--------------------------------------------------------------------------------
1 | export const Quote = () => {
2 | return (
3 |
9 |
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/website/src/components/Card.tsx:
--------------------------------------------------------------------------------
1 | import { DetailedHTMLProps, HTMLAttributes } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | export const Card = (
7 | props: DetailedHTMLProps, HTMLDivElement> & {
8 | grayer?: boolean;
9 | }
10 | ) => {
11 | const { grayer = false, ...divProps } = props;
12 |
13 | return (
14 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/website/src/sections/Footer.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { LinkButton } from "../components/LinkButton";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | export const Footer = () => {
7 | return (
8 |
9 |
10 |
11 |
© Muhammad Shah 2024
12 |
13 |
17 | Check out GitHub!
18 |
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/website/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Features } from "../src/sections/Features";
2 | import { FeatureBlocks } from "../src/sections/FeatureBlocks";
3 | import { Footer } from "../src/sections/Footer";
4 | import { Header } from "../src/sections/Header";
5 | import { Hero } from "../src/sections/Hero";
6 | import { Newsletter } from "../src/sections/Newsletter";
7 | import { Testimonials } from "../src/sections/Testimonials";
8 |
9 | const Home = ({
10 | isDarkMode,
11 | toggleDarkMode,
12 | }: {
13 | isDarkMode: boolean;
14 | toggleDarkMode: () => void;
15 | }) => {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default Home;
29 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vivid-landing-template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "axios": "^1.1.3",
13 | "next": "13.0.0",
14 | "react": "18.2.0",
15 | "react-dom": "18.2.0",
16 | "tailwind-merge": "^1.8.0"
17 | },
18 | "devDependencies": {
19 | "@types/aos": "^3.0.4",
20 | "@types/node": "18.11.7",
21 | "@types/react": "18.0.24",
22 | "@types/react-dom": "18.0.8",
23 | "aos": "^2.3.4",
24 | "autoprefixer": "^10.4.13",
25 | "eslint": "8.26.0",
26 | "eslint-config-next": "13.0.0",
27 | "next-seo": "^5.11.1",
28 | "postcss": "^8.4.18",
29 | "tailwindcss": "^3.2.1",
30 | "typescript": "4.8.4",
31 | "usehooks-ts": "^2.9.1",
32 | "vivid-studio": "^0.14.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/website/src/components/Title.tsx:
--------------------------------------------------------------------------------
1 | import { DetailedHTMLProps, HTMLAttributes } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | export const Title = (
7 | props: DetailedHTMLProps<
8 | HTMLAttributes,
9 | HTMLHeadingElement
10 | > & { size: "lg" | "md" }
11 | ) => {
12 | const { size, className = "", ...htmlProps } = props;
13 |
14 | const headingProps = {
15 | ...htmlProps,
16 | "data-aos": "zoom-y-out",
17 | };
18 |
19 | return size === "lg" ? (
20 |
27 | ) : (
28 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/website/src/components/Demo.tsx:
--------------------------------------------------------------------------------
1 | import { DetailedHTMLProps, HTMLAttributes } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | type DemoProps = DetailedHTMLProps<
7 | HTMLAttributes,
8 | HTMLDivElement
9 | > & {
10 | webmSrc: string;
11 | mp4Src: string;
12 | alt: string;
13 | };
14 |
15 | export const Demo = (props: DemoProps) => {
16 | const { webmSrc, mp4Src, alt, ...divProps } = props;
17 |
18 | return (
19 |
26 |
27 | {/* Need both for Safari compatibility */}
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/website/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Vivid Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/website/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const colors = require("tailwindcss/colors");
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | // Control dark pseudo-selector by ancestor's "dark" class
6 | darkMode: "class",
7 | // Scan these files for Tailwind classes
8 | content: ["./pages/**/*.{js,ts,jsx,tsx}", "./src/**/*.{js,ts,jsx,tsx}"],
9 | theme: {
10 | colors: {
11 | // Necessary color utilities
12 | transparent: colors.transparent,
13 | current: colors.current,
14 | // Primary accent color
15 | primary: colors.blue,
16 | // Grayscale
17 | gray: colors.zinc,
18 | // Gradient colors
19 | neon: {
20 | blue: colors.blue[500],
21 | pink: colors.pink[500],
22 | purple: colors.purple[500],
23 | teal: colors.teal[400],
24 | green: colors.green[500],
25 | sky: colors.sky[500],
26 | amber: colors.amber[500],
27 | red: colors.red[500],
28 | },
29 | },
30 | extend: {
31 | fontFamily: {
32 | sans: ["Avenir Next", "Helvetica Neue", "sans-serif"],
33 | },
34 | },
35 | },
36 | plugins: [],
37 | };
38 |
--------------------------------------------------------------------------------
/website/src/svg/DarkModeIcons.tsx:
--------------------------------------------------------------------------------
1 | export const Moon = () => (
2 |
9 | Moon icon
10 |
11 |
12 | );
13 |
14 | export const Sun = () => (
15 |
21 | Sun icon
22 |
27 |
28 | );
29 |
--------------------------------------------------------------------------------
/website/src/sections/Newsletter.tsx:
--------------------------------------------------------------------------------
1 | import { NewsletterSpheres } from "../svg/NewsletterSpheres";
2 | import { Card } from "../components/Card";
3 | import { Section } from "../components/Section";
4 | import { WaitlistForm } from "../components/WaitlistForm";
5 |
6 | // Built with Vivid (https://vivid.lol) ⚡️
7 |
8 | const Background = () => (
9 |
13 |
14 |
15 | );
16 |
17 | export const Newsletter = () => {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | Save your spot today
25 |
26 |
27 | By joining our waitlist, you'll be the first to see our
28 | product. We'd love to learn from you along the way!
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/website/src/components/LinkButton.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | AnchorHTMLAttributes,
3 | ButtonHTMLAttributes,
4 | DetailedHTMLProps,
5 | } from "react";
6 | import { twMerge } from "tailwind-merge";
7 |
8 | // Built with Vivid (https://vivid.lol) ⚡️
9 |
10 | type LinkProps = DetailedHTMLProps<
11 | AnchorHTMLAttributes,
12 | HTMLAnchorElement
13 | >;
14 |
15 | type ButtonProps = DetailedHTMLProps<
16 | ButtonHTMLAttributes,
17 | HTMLButtonElement
18 | >;
19 |
20 | /**
21 | * A link that subtly looks like a button
22 | * @param props HTML props along with `button` if the tag should be a button instead of anchor tag
23 | */
24 | export const LinkButton = (
25 | props: (LinkProps & { button?: undefined }) | (ButtonProps & { button: true })
26 | ) => {
27 | const { button, className = "", ...htmlProps } = props;
28 |
29 | return button ? (
30 |
38 | ) : (
39 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/website/src/svg/NewsletterSpheres.tsx:
--------------------------------------------------------------------------------
1 | export const NewsletterSpheres = () => {
2 | return (
3 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Icarus
2 |
3 | 
4 |
5 | A custom meshtastic PCB based on ESP32-S3 N8R8 and LoRa RA-01SH, designed to be fully assembled by JLCPCB.
6 |
7 | # Documentation:
8 |
9 | Checkout the documentation here: https://docs.azlan.works
10 |
11 | 
12 |
13 |
14 | # OSHWLab:
15 |
16 | Link: https://oshwlab.com/azlan777/icarus-v1
17 |
18 | Note: v1 Works however do not place order as significantly improved v2 is on the way!
19 |
20 | v2 is now available with added LK67 GPS and improved BMS:
21 |
22 | Link: https://oshwlab.com/azlan777/icarus-v2
23 |
24 | 
25 | 
26 | 
27 |
28 | # Sponsored by OSHWLab Stars
29 |
30 | 
31 | 
32 |
33 | Thanks so much to Bob from EasyEDA for his help along the way!
34 |
35 | # Credits
36 |
37 | Thanks so much to [@NomDeTom](https://github.com/NomDeTom) (as well as all the other great people in the Discord) for the help and feedback!
38 |
--------------------------------------------------------------------------------
/website/public/images/mac-bar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 | localhost
27 | :3000
28 |
29 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # Vivid Landing Page Template
2 |
3 | This is a ready to use template for any landing page you need, built with React, NextJS, and Tailwind CSS.
4 |
5 | Modelled after the [Vivid landing page](https://vivid.lol).
6 |
7 | > NOTE: If you are looking for a simpler alternative that is flatter and uses JS instead of TS, check out our [simple template](https://github.com/vivid-labs/vivid-landing-template-simple)!
8 |
9 | ## Preview
10 |
11 | 
12 |
13 |
14 | ## Features
15 |
16 | - Fully modular and easy to extend
17 |
18 | - Perfect lighthouse scores for great SEO
19 |
20 | - Very accessible, passing checks for screen-readers and other browsing aids
21 |
22 | - Easily customizable via Vivid, Tailwind configuration, global style presets, and component props
23 |
24 | - Built-in light and dark modes
25 |
26 | ## Getting Started
27 |
28 | 1. Install dependencies with `yarn install` (or the equivalent for other package managers)
29 |
30 | 2. Run the test app with `yarn dev`
31 |
32 | ## Customizing
33 |
34 | This template is made to be easily customizable! Using Vivid, you can command-click on any part of the page and immediately edit its code. Check out the [Vivid docs](https://docs.vivid.lol) for more information on how to style in-browser!
35 |
36 | The components in the `src/components` directory provide an easy way to extend the page.
37 |
38 | If you want to edit colors or fonts, simply edit `styles/globals.css` or `tailwind.config.js`.
39 |
--------------------------------------------------------------------------------
/website/src/components/Section.tsx:
--------------------------------------------------------------------------------
1 | import { DetailedHTMLProps, HTMLAttributes } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | // Built with Vivid (https://vivid.lol) ⚡️
5 |
6 | export const Section = (
7 | props: DetailedHTMLProps, HTMLDivElement> & {
8 | grayer?: boolean;
9 | fullWidth?: boolean;
10 | gradients?: boolean;
11 | }
12 | ) => {
13 | const { grayer, fullWidth, gradients, ...divProps } = props;
14 |
15 | const NeonCircle = ({ className }: { className: string }) => (
16 |
22 | );
23 |
24 | const Gradients = () => (
25 | <>
26 |
27 |
28 |
29 | >
30 | );
31 |
32 | return (
33 |
38 |
47 | {gradients ? : null}
48 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/website/src/sections/Hero.tsx:
--------------------------------------------------------------------------------
1 | import { ScatteredSpheres } from "../svg/ScatteredSpheres";
2 | import { Title } from "../components/Title";
3 | import { Details } from "../components/Details";
4 | import { Demo } from "../components/Demo";
5 | import { Section } from "../components/Section";
6 | import { GradientText } from "../components/GradientText";
7 | import Image from 'next/image'
8 | import profilePic from '../images/render-solo.png'
9 | // Built with Vivid (https://vivid.lol) ⚡️
10 |
11 | const Background = () => (
12 |
16 |
17 | );
18 |
19 | export const Hero = () => {
20 | return (
21 |
25 |
26 | {/* Text */}
27 |
28 |
29 | Introducing
30 |
31 | Icarus
32 |
33 |
34 | A custom Meshtastic supported PCB based on the ESP32-S3.
35 |
36 |
37 |
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/website/public/scripts/darkModeScript.js:
--------------------------------------------------------------------------------
1 | // Insert this script in your index.html right after the tag.
2 | // This will help to prevent a flash if dark mode is the default.
3 |
4 | (function () {
5 | // Change these if you use something different in your hook.
6 | let storageKey = "usehooks-ts-dark-mode";
7 | let classNameDark = "dark";
8 |
9 | const setClassOnDocumentBody = (darkMode) => {
10 | if (darkMode) {
11 | document.documentElement.classList.add(classNameDark);
12 | document.documentElement.style.setProperty("color-scheme", "dark");
13 | } else {
14 | document.documentElement.style.setProperty("color-scheme", "light");
15 | }
16 | };
17 |
18 | let preferDarkQuery = "(prefers-color-scheme: dark)";
19 | let mql = window.matchMedia(preferDarkQuery);
20 | let supportsColorSchemeQuery = mql.media === preferDarkQuery;
21 | let localStorageTheme = null;
22 | try {
23 | localStorageTheme = localStorage.getItem(storageKey);
24 | } catch (err) {}
25 | let localStorageExists = localStorageTheme !== null;
26 | if (localStorageExists) {
27 | localStorageTheme = JSON.parse(localStorageTheme);
28 | }
29 |
30 | // Determine the source of truth
31 | if (localStorageExists) {
32 | // source of truth from localStorage
33 | setClassOnDocumentBody(localStorageTheme);
34 | } else if (supportsColorSchemeQuery) {
35 | // source of truth from system
36 | setClassOnDocumentBody(mql.matches);
37 | localStorage.setItem(storageKey, mql.matches);
38 | } else {
39 | // source of truth from document.body
40 | let isDarkMode = document.documentElement.classList.contains(classNameDark);
41 | localStorage.setItem(storageKey, JSON.stringify(isDarkMode));
42 | }
43 | })();
44 |
--------------------------------------------------------------------------------
/website/src/components/WaitlistForm.tsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { DetailedHTMLProps, FormEvent, HTMLAttributes, useState } from "react";
3 | import { twMerge } from "tailwind-merge";
4 |
5 | import { Button } from "./Button";
6 | import { Input } from "./Input";
7 |
8 | // Built with Vivid (https://vivid.lol) ⚡️
9 |
10 | export const WaitlistForm = (
11 | props: DetailedHTMLProps, HTMLDivElement> & {
12 | id: string;
13 | }
14 | ) => {
15 | const { id, ...divProps } = props;
16 |
17 | const [email, setEmail] = useState("");
18 | const [emailSubmitted, setEmailSubmitted] = useState(false);
19 |
20 | const handleSubmit = async (e: FormEvent) => {
21 | e.preventDefault();
22 | setEmailSubmitted(true);
23 | const res = await axios.post("/api/waitlist", { email });
24 | setEmail("");
25 | window.alert(`Email "${res.data.email}" submitted!`);
26 | };
27 |
28 | const EmailMessage = () => (
29 |
30 | {emailSubmitted
31 | ? "We'll keep you posted!"
32 | : "Be the first to experience Vivid."}
33 |
34 | );
35 |
36 | return (
37 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/website/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "aos/dist/aos.css";
2 | import "../styles/globals.css";
3 |
4 | import AOS from "aos";
5 | import type { AppProps } from "next/app";
6 | import Head from "next/head";
7 | import { NextSeo } from "next-seo";
8 | import { useEffect } from "react";
9 | import { useDarkMode, useEffectOnce } from "usehooks-ts";
10 |
11 | // Initialize Vivid (https://vivid.lol)
12 | if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
13 | import("vivid-studio").then((v) => v.run());
14 | import("vivid-studio/style.css");
15 | }
16 |
17 | const siteTitle = "Icarus";
18 | const siteDescription =
19 | "Custom Meshtastic PCB based on the ESP32-S3.";
20 |
21 | const App = ({ Component, pageProps }: AppProps) => {
22 | const { isDarkMode, toggle: toggleDarkMode } = useDarkMode();
23 |
24 | useEffect(() => {
25 | if (isDarkMode) {
26 | document.documentElement.classList.add("dark");
27 | document.documentElement.style.setProperty("color-scheme", "dark");
28 | } else {
29 | document.documentElement.classList.add("dark");
30 | document.documentElement.style.setProperty("color-scheme", "dark");
31 | }
32 | }, [isDarkMode]);
33 |
34 | // Initialize animations
35 | useEffectOnce(() => {
36 | AOS.init({
37 | once: true,
38 | // Animations always disabled in dev mode; disabled on phones in prod
39 | disable: process.env.NODE_ENV === "development" ? true : "phone",
40 | duration: 700,
41 | easing: "ease-out-cubic",
42 | });
43 | });
44 |
45 | return (
46 | <>
47 |
48 |
53 |
54 |
58 |
59 |
68 |
73 | >
74 | );
75 | };
76 |
77 | export default App;
78 |
--------------------------------------------------------------------------------
/website/styles/globals.css:
--------------------------------------------------------------------------------
1 | /* Download Avenir */
2 | @import url('http://fonts.cdnfonts.com/css/avenir-next-cyr');
3 |
4 | /* Import Tailwind classes */
5 | @tailwind base;
6 | @tailwind components;
7 | @tailwind utilities;
8 |
9 | /* Make focus ring prettier */
10 | *:focus {
11 | outline: auto;
12 | }
13 |
14 | /* Custom utility classes */
15 | @layer utilities {
16 | /* Use the background as the text fill */
17 | .bg-text {
18 | @apply text-transparent;
19 | @apply bg-clip-text;
20 | }
21 |
22 | /* Gradients */
23 | .pink-blue {
24 | @apply from-neon-pink;
25 | @apply to-neon-blue;
26 | }
27 | .amber-red {
28 | @apply from-neon-amber;
29 | @apply to-neon-red;
30 | }
31 | .green-sky {
32 | @apply from-neon-green;
33 | @apply to-neon-sky;
34 | }
35 | .purple-teal {
36 | @apply from-neon-purple;
37 | @apply to-neon-teal;
38 | }
39 |
40 | /* Responsive colors */
41 | .bg-extra-strong {
42 | @apply bg-gray-50;
43 | @apply dark:bg-gray-900;
44 | }
45 | .bg-strong {
46 | @apply bg-gray-100;
47 | @apply dark:bg-gray-800;
48 | }
49 | .bg-medium {
50 | @apply bg-gray-300;
51 | @apply dark:bg-gray-600;
52 | }
53 | .bg-light {
54 | @apply bg-gray-400;
55 | @apply dark:bg-gray-500;
56 | }
57 | .bg-extra-light {
58 | @apply bg-gray-500;
59 | @apply dark:bg-gray-400;
60 | }
61 | .text-extra-strong {
62 | @apply text-gray-900;
63 | @apply dark:text-gray-50;
64 | }
65 | .text-strong {
66 | @apply text-gray-800;
67 | @apply dark:text-gray-100;
68 | }
69 | .text-medium {
70 | @apply text-gray-700;
71 | @apply dark:text-gray-300;
72 | }
73 | .text-light {
74 | @apply text-gray-600;
75 | @apply dark:text-gray-400;
76 | }
77 | .text-extra-light {
78 | @apply text-gray-500;
79 | @apply dark:text-gray-500;
80 | }
81 |
82 | /* Text sizing */
83 | .title-lg {
84 | @apply text-5xl;
85 | @apply md:text-7xl;
86 | @apply text-strong;
87 | }
88 | .title-md {
89 | @apply text-4xl;
90 | @apply md:text-6xl;
91 | @apply text-strong;
92 | }
93 | .body-lg {
94 | @apply text-lg;
95 | @apply md:text-xl;
96 | }
97 |
98 | /* Rounding */
99 | .round-rect {
100 | @apply rounded-lg;
101 | @apply md:rounded-xl;
102 | }
103 | .round-rect-top {
104 | @apply rounded-t-lg;
105 | @apply md:rounded-t-xl;
106 | }
107 |
108 | /* Flex */
109 | .row {
110 | @apply flex;
111 | @apply flex-row;
112 | }
113 | .col {
114 | @apply flex;
115 | @apply flex-col;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/website/src/sections/Header.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import { useState } from "react";
4 | import { useEffectOnce, useEventListener } from "usehooks-ts";
5 |
6 | import { Button } from "../components/Button";
7 | import { GradientText } from "../components/GradientText";
8 | import { LinkButton } from "../components/LinkButton";
9 | import { Moon, Sun } from "../svg/DarkModeIcons";
10 |
11 | // Built with Vivid (https://vivid.lol) ⚡️
12 |
13 | export const Header = ({
14 | isDarkMode,
15 | toggleDarkMode,
16 | }: {
17 | isDarkMode: boolean;
18 | toggleDarkMode: () => void;
19 | }) => {
20 | const [top, setTop] = useState(true);
21 | const [nextSection, setNextSection] = useState(false);
22 | const [reloaded, setReloaded] = useState(false);
23 |
24 | // Handle scrolling logic
25 | const handleScroll = () => {
26 | setTop(window.pageYOffset <= 10);
27 | setNextSection(window.pageYOffset > window.innerHeight);
28 | };
29 | useEventListener("scroll", handleScroll);
30 |
31 | // Clean up stale dark mode
32 | useEffectOnce(() => setReloaded(true));
33 |
34 | const goToEmail = () => {
35 | window.scrollTo({
36 | top: 0,
37 | behavior: "smooth",
38 | });
39 | };
40 |
41 | const Logo = () => (
42 |
43 |
44 |
45 | Icarus
46 |
47 |
48 |
49 | );
50 |
51 | const Navigation = () => (
52 |
53 |
54 | {reloaded ? ( // Only show after first reload
55 |
56 |
62 | {isDarkMode ? : }
63 |
64 |
65 | ) : null}
66 |
67 | Docs
68 |
69 |
70 |
71 | );
72 |
73 | return (
74 | // Colors must be set explicitly since opacity and blur don't work together
75 |
80 | {/* Header Content */}
81 |
82 |
83 |
84 |
85 |
86 | );
87 | };
88 |
--------------------------------------------------------------------------------
/website/src/sections/FeatureBlocks.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | import {
4 | CardsIcon,
5 | MagicIcon,
6 | DoorIcon,
7 | ShuffleIcon,
8 | SignalIcon,
9 | TalkingIcon,
10 | } from "../svg/FeatureIcons";
11 | import { Card } from "../components/Card";
12 | import { Details } from "../components/Details";
13 | import { GradientText } from "../components/GradientText";
14 | import { Section } from "../components/Section";
15 | import { Title } from "../components/Title";
16 |
17 | // Built with Vivid (https://vivid.lol) ⚡️
18 |
19 | const BlockTitle = ({ children }: { children: ReactNode }) => {
20 | return {children} ;
21 | };
22 |
23 | const BlockText = ({ children }: { children: ReactNode }) => {
24 | return {children}
;
25 | };
26 |
27 | const Block = ({ children }: { children: ReactNode }) => {
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | };
34 |
35 | export const FeatureBlocks = () => {
36 | return (
37 |
38 | {/* Header */}
39 |
40 |
41 | Make styling a{" "}
42 | breeze
43 |
44 |
45 | Stop wasting your time. Vivid makes CSS easier than recursive
46 | depth-first search.
47 |
48 |
49 | {/* Blocks */}
50 |
51 | {/* Block 1 */}
52 |
53 |
54 | In-browser
55 |
56 | Edit your styling from the most natural place — where it renders
57 |
58 |
59 | {/* Block 2 */}
60 |
61 |
62 | Code first
63 |
64 | Use Vivid's command palette and code pane to edit styling using
65 | code
66 |
67 |
68 | {/* Block 3 */}
69 |
70 |
71 | Modern frameworks
72 |
73 | Build components using Vivid in React styled with Tailwind CSS
74 |
75 |
76 | {/* Block 4 */}
77 |
78 |
79 | Production-ready code
80 |
81 | Modify your styling in-browser and update your IDE with code that
82 | looks like your own
83 |
84 |
85 | {/* Block 5 */}
86 |
87 |
88 | Your workflow
89 |
90 | Keep using your browser and IDE without needing an extra window
91 |
92 |
93 | {/* Block 6 */}
94 |
95 |
96 | Smarter inspect element
97 |
98 | Understand and modify the components making up your design with just
99 | one click
100 |
101 |
102 |
103 |
104 | );
105 | };
106 |
--------------------------------------------------------------------------------
/website/src/sections/Testimonials.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { ReactNode } from "react";
3 |
4 | import { Quote } from "../svg/Quote";
5 | import { Card } from "../components/Card";
6 | import { Details } from "../components/Details";
7 | import { GradientText } from "../components/GradientText";
8 | import { Section } from "../components/Section";
9 | import { Title } from "../components/Title";
10 |
11 | // Built with Vivid (https://vivid.lol) ⚡️
12 |
13 | const TestimonialImage = ({ src, alt }: { src: string; alt: string }) => {
14 | return (
15 |
16 |
17 |
24 |
25 | );
26 | };
27 |
28 | const TestimonialText = ({
29 | quote,
30 | name,
31 | title,
32 | handle,
33 | link,
34 | }: {
35 | quote: string;
36 | name: string;
37 | title: string;
38 | handle: string;
39 | link: string;
40 | }) => {
41 | return (
42 | <>
43 | "{quote}"
44 |
56 | >
57 | );
58 | };
59 |
60 | const Testimonial = ({ children }: { children: ReactNode }) => {
61 | return (
62 |
63 | {children}
64 |
65 | );
66 | };
67 |
68 | export const Testimonials = () => {
69 | return (
70 |
71 | {/* Header */}
72 |
73 |
74 | Loved by developers
75 |
76 | Styling your web app has never been easier.
77 |
78 | {/* Testimonials */}
79 |
80 | {/* Testimonial 1 */}
81 |
82 |
83 |
90 |
91 | {/* Testimonial 2 */}
92 |
93 |
94 |
101 |
102 | {/* Testimonial 3 */}
103 |
104 |
105 |
112 |
113 |
114 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/website/src/sections/Features.tsx:
--------------------------------------------------------------------------------
1 | import { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | import { Demo } from "../components/Demo";
5 | import { Details } from "../components/Details";
6 | import { GradientText } from "../components/GradientText";
7 | import { Section } from "../components/Section";
8 | import { Title } from "../components/Title";
9 |
10 | // Built with Vivid (https://vivid.lol) ⚡️
11 |
12 | const FeatureSection = ({
13 | children,
14 | grayer,
15 | right,
16 | center,
17 | }: {
18 | children: ReactNode;
19 | grayer?: boolean;
20 | right?: boolean;
21 | center?: boolean;
22 | }) => (
23 |
32 | );
33 |
34 | const FeatureDemo = (
35 | props: DetailedHTMLProps, HTMLDivElement> & {
36 | webmSrc: string;
37 | mp4Src: string;
38 | bumpLeft?: boolean;
39 | center?: boolean;
40 | className: string;
41 | alt: string;
42 | }
43 | ) => {
44 | const { webmSrc, mp4Src, bumpLeft, center, alt, className, ...divProps } =
45 | props;
46 | return (
47 |
56 |
65 |
66 | );
67 | };
68 |
69 | const Text = ({
70 | children,
71 | center,
72 | }: {
73 | children: ReactNode;
74 | center?: boolean;
75 | }) => (
76 |
81 | {children}
82 |
83 | );
84 |
85 | export const Features = () => {
86 | return (
87 | <>
88 | {/* Feature 1 */}
89 |
90 |
91 |
92 | 1.3"
93 |
94 | OLED
95 | {" "}
96 | display
97 |
98 | Information and messaging at a glance.
99 |
100 |
107 |
108 | {/* Feature 2 */}
109 |
110 |
111 |
112 | Solar
113 | made easy.
114 |
115 | Just attach solar panels to exposed input.
116 |
117 |
124 |
125 | {/* Feature 3 */}
126 |
127 |
128 |
129 | 868 - 915 MHz
130 | support.
131 |
132 |
133 | For worldwide communication, no matter where you are.
134 |
135 |
136 |
142 |
143 | >
144 | );
145 | };
146 |
--------------------------------------------------------------------------------
/website/src/svg/FeatureIcons.tsx:
--------------------------------------------------------------------------------
1 | export const CardsIcon = () => {
2 | return (
3 |
8 | Cards Icon
9 |
10 |
16 |
17 |
21 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export const DoorIcon = () => {
32 | return (
33 |
38 | Door Icon
39 |
40 |
46 |
47 |
52 |
53 |
58 |
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | export const MagicIcon = () => {
72 | return (
73 |
78 | Magic Icon
79 |
80 |
86 |
87 |
91 |
95 |
99 |
104 |
105 |
106 |
107 | );
108 | };
109 |
110 | export const ShuffleIcon = () => {
111 | return (
112 |
117 | Shuffle Icon
118 |
119 |
125 |
126 |
130 |
134 |
135 |
136 |
137 | );
138 | };
139 |
140 | export const SignalIcon = () => {
141 | return (
142 |
147 | Signal Icon
148 |
149 |
155 |
156 |
163 |
167 |
171 |
172 |
173 |
174 | );
175 | };
176 |
177 | export const TalkingIcon = () => {
178 | return (
179 |
184 | Talking Icon
185 |
186 |
192 |
193 |
197 |
201 |
202 |
203 |
204 | );
205 | };
206 |
--------------------------------------------------------------------------------