Merge branch 'main' of https://github.com/zen-browser/www
This commit is contained in:
@@ -1,36 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { addDownload } from "@/lib/db";
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
import { ny } from "@/lib/utils";
|
||||
import { Checkbox } from "./ui/checkbox";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import Particles from "./ui/particles";
|
||||
import confetti from 'canvas-confetti';
|
||||
import confetti from "canvas-confetti";
|
||||
import { releases, releaseTree } from "@/lib/releases";
|
||||
import { InfoCircledIcon } from "@radix-ui/react-icons";
|
||||
import Link from "next/link";
|
||||
const BASE_URL =
|
||||
"https://github.com/zen-browser/desktop/releases/latest/download";
|
||||
|
||||
import SparklesText from "./ui/sparkles-text";
|
||||
const BASE_URL = "https://github.com/zen-browser/desktop/releases/latest/download";
|
||||
|
||||
function getDefaultPlatformBasedOnUserAgent() {
|
||||
let userAgent = "";
|
||||
if (typeof window !== "undefined") {
|
||||
userAgent = window.navigator.userAgent;
|
||||
}
|
||||
if (userAgent.includes("Win")) {
|
||||
return "Windows";
|
||||
}
|
||||
if (userAgent.includes("Mac")) {
|
||||
return "MacOS";
|
||||
}
|
||||
if (userAgent.includes("Linux")) {
|
||||
return "Linux";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const field_enter = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
@@ -64,14 +48,15 @@ const field_exit = keyframes`
|
||||
}
|
||||
`;
|
||||
|
||||
const FormField = styled.div<{ enter: boolean, out: boolean }>`
|
||||
const FormField = styled.div<{ enter: boolean; out: boolean }>`
|
||||
max-height: 0;
|
||||
flex-direction: column;
|
||||
margin-top: 3rem;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
animation: 0.2s ease-in-out forwards ${({ enter, out }) => enter ? field_enter : out ? field_exit : ""} !important;
|
||||
animation-delay: ${({ enter }) => enter ? "0.4s" : "0s"};
|
||||
animation: 0.2s ease-in-out forwards
|
||||
${({ enter, out }) => (enter ? field_enter : out ? field_exit : "")} !important;
|
||||
animation-delay: ${({ enter }) => (enter ? "0.4s" : "0s")};
|
||||
`;
|
||||
|
||||
const FieldTitle = styled.div`
|
||||
@@ -88,24 +73,43 @@ const FieldDescription = styled.div`
|
||||
export default function DownloadPage() {
|
||||
const [platform, setPlatform] = useState<string | null>(null);
|
||||
const [architecture, setArchitecture] = useState<string | null>(null);
|
||||
const [windowsDownloadType, setWindowsDownloadType] = useState<string | null>(null);
|
||||
const [linuxDownloadType, setLinuxDownloadType] = useState<string | null>(null);
|
||||
const [windowsDownloadType, setWindowsDownloadType] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
const [linuxDownloadType, setLinuxDownloadType] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const [selectedPlatform, setSelectedPlatform] = useState(getDefaultPlatformBasedOnUserAgent());
|
||||
const [selectedPlatform, setSelectedPlatform] = useState("");
|
||||
const [selectedArchitecture, setSelectedArchitecture] = useState("specific");
|
||||
const [selectedWindowsDownloadType, setSelectedWindowsDownloadType] = useState("installer");
|
||||
const [selectedLinuxDownloadType, setSelectedLinuxDownloadType] = useState("portable");
|
||||
const [selectedWindowsDownloadType, setSelectedWindowsDownloadType] =
|
||||
useState("installer");
|
||||
const [selectedLinuxDownloadType, setSelectedLinuxDownloadType] =
|
||||
useState("portable");
|
||||
|
||||
const [hasDownloaded, setHasDownloaded] = useState(false);
|
||||
|
||||
const [flowIndex, setFlowIndex] = useState(0);
|
||||
|
||||
const [flowIndex, setFlowIndex] = useState(0);
|
||||
useEffect(() => {
|
||||
let userAgent: string = "";
|
||||
if (typeof window !== "undefined") {
|
||||
userAgent = window.navigator.userAgent;
|
||||
}
|
||||
if (userAgent.includes("Win")) {
|
||||
setSelectedPlatform("Windows");
|
||||
}
|
||||
if (userAgent.includes("Mac")) {
|
||||
setSelectedPlatform("MacOS");
|
||||
}
|
||||
if (userAgent.includes("Linux")) {
|
||||
setSelectedPlatform("Linux");
|
||||
}
|
||||
}, []);
|
||||
const throwConfetti = () => {
|
||||
const end = Date.now() + 3 * 1000 // 3 seconds
|
||||
const colors = ['#a786ff', '#fd8bbc', '#eca184', '#f8deb1']
|
||||
const end = Date.now() + 3 * 1000; // 3 seconds
|
||||
const colors = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"];
|
||||
const frame = () => {
|
||||
if (Date.now() > end)
|
||||
return
|
||||
if (Date.now() > end) return;
|
||||
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
@@ -114,7 +118,7 @@ export default function DownloadPage() {
|
||||
startVelocity: 60,
|
||||
origin: { x: 0, y: 0.5 },
|
||||
colors,
|
||||
})
|
||||
});
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 120,
|
||||
@@ -122,16 +126,18 @@ export default function DownloadPage() {
|
||||
startVelocity: 60,
|
||||
origin: { x: 1, y: 0.5 },
|
||||
colors,
|
||||
})
|
||||
requestAnimationFrame(frame)
|
||||
}
|
||||
frame()
|
||||
}
|
||||
});
|
||||
requestAnimationFrame(frame);
|
||||
};
|
||||
frame();
|
||||
};
|
||||
|
||||
const startDownload = () => {
|
||||
let releaseTarget: string;
|
||||
if (selectedLinuxDownloadType === "flatpak") {
|
||||
window.open("https://dl.flathub.org/repo/appstream/io.github.zen_browser.zen.flatpakref");
|
||||
window.open(
|
||||
"https://dl.flathub.org/repo/appstream/io.github.zen_browser.zen.flatpakref"
|
||||
);
|
||||
releaseTarget = "flatpak";
|
||||
} else {
|
||||
const platform = releaseTree[selectedPlatform.toLowerCase()];
|
||||
@@ -139,9 +145,12 @@ export default function DownloadPage() {
|
||||
if (selectedPlatform === "MacOS") {
|
||||
releaseTarget = platform[arch];
|
||||
} else {
|
||||
releaseTarget = platform[arch][selectedPlatform === "Windows"
|
||||
? selectedWindowsDownloadType as string
|
||||
: selectedLinuxDownloadType as string];
|
||||
releaseTarget =
|
||||
platform[arch][
|
||||
selectedPlatform === "Windows"
|
||||
? (selectedWindowsDownloadType as string)
|
||||
: (selectedLinuxDownloadType as string)
|
||||
];
|
||||
}
|
||||
console.log("Downloading: ");
|
||||
console.log("platform: ", selectedPlatform);
|
||||
@@ -154,10 +163,8 @@ export default function DownloadPage() {
|
||||
};
|
||||
|
||||
const continueFlow = () => {
|
||||
if (flowIndex === 0)
|
||||
setPlatform(selectedPlatform);
|
||||
if (flowIndex === 1)
|
||||
setArchitecture(selectedArchitecture);
|
||||
if (flowIndex === 0) setPlatform(selectedPlatform);
|
||||
if (flowIndex === 1) setArchitecture(selectedArchitecture);
|
||||
if (flowIndex === 2 || (flowIndex === 1 && platform === "MacOS")) {
|
||||
setWindowsDownloadType(selectedWindowsDownloadType);
|
||||
setLinuxDownloadType(selectedLinuxDownloadType);
|
||||
@@ -168,7 +175,7 @@ export default function DownloadPage() {
|
||||
|
||||
const goBackFlow = () => {
|
||||
if (flowIndex === 1) {
|
||||
setPlatform(null);
|
||||
setPlatform(null);
|
||||
} else if (flowIndex === 2) {
|
||||
setArchitecture(null);
|
||||
} else if (flowIndex === 3) {
|
||||
@@ -176,43 +183,77 @@ export default function DownloadPage() {
|
||||
setSelectedWindowsDownloadType("installer");
|
||||
setLinuxDownloadType(null);
|
||||
setSelectedLinuxDownloadType("portable");
|
||||
}
|
||||
if (flowIndex > 0)
|
||||
setFlowIndex(flowIndex - 1);
|
||||
}
|
||||
}
|
||||
if (flowIndex > 0) setFlowIndex(flowIndex - 1);
|
||||
};
|
||||
|
||||
const changeToFlatpak = () => {
|
||||
if (selectedArchitecture === "specific") {
|
||||
setSelectedLinuxDownloadType("flatpak");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<link rel="stylesheet" type='text/css' href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css" />
|
||||
<link rel="stylesheet" type='text/css' href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css" />
|
||||
<link rel="stylesheet" type='text/css' href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css" />
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css"
|
||||
/>
|
||||
|
||||
<div className="w-full overflow-hidden relative h-screen flex items-center justify-center flex-col lg:flex-row">
|
||||
<div className="flex flex-col justify-center w-full p-10 md:p-0 md:p-20 lg:p-0 lg:w-1/2 2xl:w-1/3 mx-auto">
|
||||
{hasDownloaded && (
|
||||
<div className="flex flex-col justify-center w-full p-10 md:p-20 lg:p-0 lg:w-1/2 2xl:w-1/3 mx-auto">
|
||||
{(hasDownloaded && (
|
||||
<div className="flex items-center justify-center flex-col">
|
||||
<h1 className="text-6xl font-bold">Downloaded! ❤️</h1>
|
||||
<p className="text-muted-foreground mt-3">Zen Browser has been downloaded successfully. Enjoy browsing the web with Zen!</p>
|
||||
<p className="text-muted-foreground mt-3">
|
||||
Zen Browser has been downloaded successfully. Enjoy browsing the
|
||||
web with Zen!
|
||||
</p>
|
||||
<div className="flex font-bold mt-5 items-center justify-between mx-auto">
|
||||
<a href="https://github.com/zen-browser">Source Code</a>
|
||||
<a className="ml-5" href="https://patreon.com/zen_browser?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink">Donate</a>
|
||||
<a className="ml-5" href="/release-notes/latest">Release Notes</a>
|
||||
<Link href="https://github.com/zen-browser">Source Code</Link>
|
||||
<Link
|
||||
className="ml-5"
|
||||
href="https://patreon.com/zen_browser?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink"
|
||||
>
|
||||
Donate
|
||||
</Link>
|
||||
<Link className="ml-5" href="/release-notes/latest">
|
||||
Release Notes
|
||||
</Link>
|
||||
</div>
|
||||
{selectedPlatform === "MacOS" && (
|
||||
<div className="mt-12 flex flex-col items-start border justify-between rounded-md bg-background p-5">
|
||||
<h3 className="text-xl font-semibold">Installation Instructions</h3>
|
||||
<p className="text-muted-foreground text-sm ">To install Zen on MacOS, the process is a bit different. Please follow the instructions below:</p>
|
||||
<Button className="mt-5" onClick={() => window.location.href = "https://github.com/zen-browser/desktop/issues/53"}>Download Zen for MacOS</Button>
|
||||
<h3 className="text-xl font-semibold">
|
||||
Installation Instructions
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm ">
|
||||
To install Zen on MacOS, the process is a bit different.
|
||||
Please follow the instructions below:
|
||||
</p>
|
||||
<Button
|
||||
className="mt-5"
|
||||
onClick={() =>
|
||||
(window.location.href =
|
||||
"https://github.com/zen-browser/desktop/issues/53")
|
||||
}
|
||||
>
|
||||
Download Zen for MacOS
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) || (
|
||||
)) || (
|
||||
<>
|
||||
<h1 className="text-6xl font-bold">Download <SparklesText className="mx-2" text="Zen " /></h1>
|
||||
<p className="text-muted-foreground mt-3">
|
||||
@@ -222,23 +263,40 @@ export default function DownloadPage() {
|
||||
)}
|
||||
<div className="relative w-full">
|
||||
{platform === null && (
|
||||
<FormField
|
||||
enter={platform === null}
|
||||
out={platform !== null}
|
||||
>
|
||||
<FormField enter={platform === null} out={platform !== null}>
|
||||
<FieldTitle>Platform</FieldTitle>
|
||||
<FieldDescription>Choose the platform you want to download Zen for.</FieldDescription>
|
||||
<div onClick={() => setSelectedPlatform("Windows")} className={ny("select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border", selectedPlatform === "Windows" ? "border-blue-400" : "")}>
|
||||
<FieldDescription>
|
||||
Choose the platform you want to download Zen for.
|
||||
</FieldDescription>
|
||||
<div
|
||||
onClick={() => setSelectedPlatform("Windows")}
|
||||
className={ny(
|
||||
"select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedPlatform === "Windows" ? "border-blue-400" : ""
|
||||
)}
|
||||
>
|
||||
<Checkbox checked={selectedPlatform === "Windows"} />
|
||||
<i className="devicon-windows8-original ml-3 p-2 border border-blue-400 rounded-lg"></i>
|
||||
<div className="ml-2">Windows</div>
|
||||
</div>
|
||||
<div onClick={() => setSelectedPlatform("Linux")} className={ny("select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border", selectedPlatform === "Linux" ? "border-yellow-400" : "")}>
|
||||
<Checkbox checked={selectedPlatform === "Linux"} />
|
||||
<div
|
||||
onClick={() => setSelectedPlatform("Linux")}
|
||||
className={ny(
|
||||
"select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedPlatform === "Linux" ? "border-yellow-400" : ""
|
||||
)}
|
||||
>
|
||||
<Checkbox checked={selectedPlatform === "Linux"} />
|
||||
<i className="devicon-linux-plain ml-3 p-2 border border-yellow-400 rounded-lg"></i>
|
||||
<div className="ml-2">Linux</div>
|
||||
</div>
|
||||
<div onClick={() => setSelectedPlatform("MacOS")} className={ny("select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border", selectedPlatform === "MacOS" ? "border-purple-400" : "")}>
|
||||
<div
|
||||
onClick={() => setSelectedPlatform("MacOS")}
|
||||
className={ny(
|
||||
"select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedPlatform === "MacOS" ? "border-purple-400" : ""
|
||||
)}
|
||||
>
|
||||
<Checkbox checked={selectedPlatform === "MacOS"} />
|
||||
<i className="devicon-apple-original p-2 border border-purple-400 ml-3 rounded-lg"></i>
|
||||
<div className="ml-2 font-bold">MacOS</div>
|
||||
@@ -246,94 +304,217 @@ export default function DownloadPage() {
|
||||
</FormField>
|
||||
)}
|
||||
{/* Architecture */}
|
||||
{((platform === "Windows" || platform === "Linux") && flowIndex === 1) && (
|
||||
<FormField
|
||||
enter={platform === "Windows" || platform === "Linux" && flowIndex === 1}
|
||||
out={platform !== "Windows" && platform !== "Linux" && flowIndex >= 1}
|
||||
>
|
||||
<FieldTitle>Select Architecture</FieldTitle>
|
||||
<FieldDescription>Choose the architecture of your device, either optimized or generic.</FieldDescription>
|
||||
<div className="flex items-center justify-center">
|
||||
<div onClick={() => setSelectedArchitecture("specific")} className={ny("select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedArchitecture === "specific" ? "border-blue-400" : "")}>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">🚀</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Optimized</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">Blazing fast and compatible with modern devices</p>
|
||||
{(platform === "Windows" || platform === "Linux") &&
|
||||
flowIndex === 1 && (
|
||||
<FormField
|
||||
enter={
|
||||
platform === "Windows" ||
|
||||
(platform === "Linux" && flowIndex === 1)
|
||||
}
|
||||
out={
|
||||
platform !== "Windows" &&
|
||||
platform !== "Linux" &&
|
||||
flowIndex >= 1
|
||||
}
|
||||
>
|
||||
<FieldTitle>Select Architecture</FieldTitle>
|
||||
<FieldDescription>
|
||||
Choose the architecture of your device, either optimized or
|
||||
generic.
|
||||
</FieldDescription>
|
||||
<div className="flex items-center justify-center">
|
||||
<div
|
||||
onClick={() => setSelectedArchitecture("specific")}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedArchitecture === "specific"
|
||||
? "border-blue-400"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||
🚀
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Optimized</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">
|
||||
Blazing fast and compatible with modern devices
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setSelectedArchitecture("generic")}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedArchitecture === "generic"
|
||||
? "border-blue-400"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||
👴
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Generic</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">
|
||||
Slow but compatible with older devices.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={() => setSelectedArchitecture("generic")} className={ny("select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedArchitecture === "generic" ? "border-blue-400" : "")}>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">👴</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Generic</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">Slow but compatible with older devices.</p>
|
||||
</div>
|
||||
</div>
|
||||
</FormField>
|
||||
)}
|
||||
{(platform === "MacOS" && flowIndex === 1) && (
|
||||
</FormField>
|
||||
)}
|
||||
{platform === "MacOS" && flowIndex === 1 && (
|
||||
<FormField
|
||||
enter={platform === "MacOS"}
|
||||
out={platform !== "MacOS"}
|
||||
>
|
||||
<FieldTitle>Download Zen for MacOS</FieldTitle>
|
||||
<FieldDescription>Click the button below to download Zen for MacOS.</FieldDescription>
|
||||
<FieldDescription>
|
||||
Click the button below to download Zen for MacOS.
|
||||
</FieldDescription>
|
||||
<div className="flex items-center justify-center">
|
||||
<div onClick={() => setSelectedArchitecture("specific")} className={ny("select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedArchitecture === "specific" ? "border-blue-400" : "")}>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">🍏</h1>
|
||||
<div
|
||||
onClick={() => setSelectedArchitecture("specific")}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedArchitecture === "specific"
|
||||
? "border-blue-400"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||
🍏
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">aarch64</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">64-bit ARM architecture, for Apple's M Series Chips</p>
|
||||
</div>
|
||||
<div onClick={() => setSelectedArchitecture("generic")} className={ny("select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedArchitecture === "generic" ? "border-blue-400" : "")}>
|
||||
<h1 className="text-5xl font-bold my-2 opacity-40 dark:opacity-20">x64</h1>
|
||||
<div
|
||||
onClick={() => setSelectedArchitecture("generic")}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedArchitecture === "generic"
|
||||
? "border-blue-400"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl font-bold my-2 opacity-40 dark:opacity-20">
|
||||
x64
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Intel</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">64-bit Intel architecture, for older Macs</p>
|
||||
<p className="text-muted-foreground mx-auto text-center">
|
||||
64-bit Intel architecture, for older Macs
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</FormField>
|
||||
)}
|
||||
{flowIndex === 2 && (platform === "Windows") && (
|
||||
{flowIndex === 2 && platform === "Windows" && (
|
||||
<FormField
|
||||
enter={platform === "Windows" && flowIndex === 2}
|
||||
out={platform !== "Windows" && flowIndex >= 2}
|
||||
>
|
||||
<FieldTitle
|
||||
className="text-2xl"
|
||||
>Download Zen for Windows {selectedArchitecture}</FieldTitle>
|
||||
<FieldDescription>Choose the type of download you want for Zen for Windows.</FieldDescription>
|
||||
<FieldTitle className="text-2xl">
|
||||
Download Zen for Windows {selectedArchitecture}
|
||||
</FieldTitle>
|
||||
<FieldDescription>
|
||||
Choose the type of download you want for Zen for Windows.
|
||||
</FieldDescription>
|
||||
<div className="flex items-center justify-center">
|
||||
<div onClick={() => setSelectedWindowsDownloadType("installer")} className={ny("select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedWindowsDownloadType === "installer" ? "border-blue-400" : "")}>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">🚀</h1>
|
||||
<div
|
||||
onClick={() => setSelectedWindowsDownloadType("installer")}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedWindowsDownloadType === "installer"
|
||||
? "border-blue-400"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||
🚀
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Installer</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">Install Zen with a setup wizard</p>
|
||||
<p className="text-muted-foreground mx-auto text-center">
|
||||
Install Zen with a setup wizard
|
||||
</p>
|
||||
</div>
|
||||
<div onClick={() => setSelectedWindowsDownloadType("portable")} className={ny("select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedWindowsDownloadType === "portable" ? "border-blue-400" : "")}>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">📦</h1>
|
||||
<div
|
||||
onClick={() => setSelectedWindowsDownloadType("portable")}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedWindowsDownloadType === "portable"
|
||||
? "border-blue-400"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||
📦
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Portable</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">Download Zen as a ZIP file</p>
|
||||
<p className="text-muted-foreground mx-auto text-center">
|
||||
Download Zen as a ZIP file
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</FormField>
|
||||
)}
|
||||
{flowIndex === 2 && (platform === "Linux") && (
|
||||
{flowIndex === 2 && platform === "Linux" && (
|
||||
<FormField
|
||||
enter={platform === "Linux" && flowIndex === 2}
|
||||
out={platform !== "Linux" && flowIndex >= 2}
|
||||
>
|
||||
<FieldTitle
|
||||
className="text-2xl"
|
||||
>Download Zen for Linux {selectedArchitecture}</FieldTitle>
|
||||
<FieldDescription>Choose the type of download you want for Zen for Linux.</FieldDescription>
|
||||
<FieldTitle className="text-2xl">
|
||||
Download Zen for Linux {selectedArchitecture}
|
||||
</FieldTitle>
|
||||
<FieldDescription>
|
||||
Choose the type of download you want for Zen for Linux.
|
||||
</FieldDescription>
|
||||
<div className="flex items-center justify-center">
|
||||
<div onClick={() => setSelectedLinuxDownloadType("appimage")} className={ny("select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedLinuxDownloadType === "appimage" ? "border-blue-400" : "")}>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">🚀</h1>
|
||||
<div
|
||||
onClick={() => setSelectedLinuxDownloadType("appimage")}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedLinuxDownloadType === "appimage"
|
||||
? "border-blue-400"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||
🚀
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">AppImage</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">Install Zen with a setup wizard</p>
|
||||
<p className="text-muted-foreground mx-auto text-center">
|
||||
Install Zen with a setup wizard
|
||||
</p>
|
||||
</div>
|
||||
<div onClick={() => setSelectedLinuxDownloadType("portable")} className={ny("select-none w-full h-full mb-2 ml-5 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedLinuxDownloadType === "portable" ? "border-blue-400" : "")}>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">📦</h1>
|
||||
<div
|
||||
onClick={() => setSelectedLinuxDownloadType("portable")}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 ml-5 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedLinuxDownloadType === "portable"
|
||||
? "border-blue-400"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||
📦
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Portable</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">Download Zen as a ZIP file</p>
|
||||
<p className="text-muted-foreground mx-auto text-center">
|
||||
Download Zen as a ZIP file
|
||||
</p>
|
||||
</div>
|
||||
<div onClick={() => changeToFlatpak()} className={ny("select-none w-full h-full mb-2 ml-5 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", selectedLinuxDownloadType === "flatpak" ? "border-blue-400" : "",
|
||||
selectedArchitecture === "generic" ? "opacity-50 cursor-not-allowed" : "")}>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">🧑💻</h1>
|
||||
<div
|
||||
onClick={() => changeToFlatpak()}
|
||||
className={ny(
|
||||
"select-none w-full h-full mb-2 ml-5 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
|
||||
selectedLinuxDownloadType === "flatpak"
|
||||
? "border-blue-400"
|
||||
: "",
|
||||
selectedArchitecture === "generic"
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||
🧑💻
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold my-2">Flatpak</h1>
|
||||
<p className="text-muted-foreground mx-auto text-center">
|
||||
Install Zen from the Flatpak repository.
|
||||
@@ -345,23 +526,44 @@ export default function DownloadPage() {
|
||||
</div>
|
||||
{!hasDownloaded && (
|
||||
<div className="mt-5 flex items-center justify-between">
|
||||
<Button variant="ghost" onClick={() => goBackFlow()} className={ny("opacity-70", platform === null ? "invisible" : "")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => goBackFlow()}
|
||||
className={ny(
|
||||
"opacity-70",
|
||||
platform === null ? "invisible" : ""
|
||||
)}
|
||||
>
|
||||
<ChevronLeft className="size-4" />
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={() => continueFlow()} disabled={
|
||||
(selectedPlatform === null)
|
||||
}>
|
||||
{((flowIndex === 1 && platform === "MacOS") || flowIndex === 2) ? "Download 🥳" : "Continue"}
|
||||
<Button
|
||||
onClick={() => continueFlow()}
|
||||
disabled={selectedPlatform === null}
|
||||
>
|
||||
{(flowIndex === 1 && platform === "MacOS") || flowIndex === 2
|
||||
? "Download 🥳"
|
||||
: "Continue"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{((platform === "Linux" || platform === "Windows") && flowIndex === 1) && (
|
||||
<div className="mt-5 flex items-center">
|
||||
<InfoCircledIcon className="size-4 mr-2" />
|
||||
<p className="text-muted-foreground">Confused about which build to choose? <a href="https://github.com/zen-browser/desktop/blob/main/docs/requirements.md#supported-cpus-for-optimized-builds-windows-and-linux" target="_blank" className="text-blue-400">System requirements</a>.</p>
|
||||
</div>
|
||||
)}
|
||||
{(platform === "Linux" || platform === "Windows") &&
|
||||
flowIndex === 1 && (
|
||||
<div className="mt-5 flex items-center">
|
||||
<InfoCircledIcon className="size-4 mr-2" />
|
||||
<p className="text-muted-foreground">
|
||||
Confused about which build to choose?{" "}
|
||||
<Link
|
||||
href="https://github.com/zen-browser/desktop/blob/main/docs/requirements.md#supported-cpus-for-optimized-builds-windows-and-linux"
|
||||
target="_blank"
|
||||
className="text-blue-400"
|
||||
>
|
||||
System requirements
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Particles
|
||||
|
||||
@@ -3,6 +3,7 @@ import Feature, { FeatureCard } from "./feature";
|
||||
import { Button } from "./ui/button";
|
||||
import TextReveal from "./ui/text-reveal";
|
||||
import styled, { css, keyframes } from "styled-components";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -13,10 +14,21 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "./ui/table";
|
||||
import { CheckIcon, EyeIcon, EyeOffIcon, RabbitIcon, XIcon } from "lucide-react";
|
||||
import { EyeClosedIcon, LockClosedIcon, QuestionMarkIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
CheckIcon,
|
||||
EyeIcon,
|
||||
EyeOffIcon,
|
||||
RabbitIcon,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
EyeClosedIcon,
|
||||
LockClosedIcon,
|
||||
QuestionMarkIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import ShineBorder from "./ui/shine-border";
|
||||
import SparklesText from "./ui/sparkles-text";
|
||||
import Image from "next/image";
|
||||
|
||||
function Checkmark() {
|
||||
return (
|
||||
@@ -59,7 +71,13 @@ export default function Features() {
|
||||
<p className="text-muted-foreground mt-3">
|
||||
Stay organized and clutter-free by creating workspaces tailored to your browsing needs.
|
||||
</p>
|
||||
<img src="/workspaces.png" className="mt-8 w-full h-full" />
|
||||
<Image
|
||||
height={500}
|
||||
width={300}
|
||||
src="/workspaces.png"
|
||||
className="mt-8 w-full h-full"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full lg:mr-4 flex flex-col">
|
||||
@@ -68,14 +86,26 @@ export default function Features() {
|
||||
<p className="text-muted-foreground mt-3">
|
||||
Seamlessly switch between work and personal profiles for a focused browsing experience.
|
||||
</p>
|
||||
<img src="/profiles.png" className="mt-8 w-full h-full" />
|
||||
<Image
|
||||
height={500}
|
||||
width={300}
|
||||
src="/profiles.png"
|
||||
className="mt-8 w-full h-full"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div className="hover:border-blue-500 transition-all duration-100 bg-background relative mx-auto lg:mx-0 mt-8 flex-col flex lg:max-w-md justify-center rounded-xl md:border-2 p-20 md:shadow-xl">
|
||||
<h1 className="text-5xl font-bold">Side web panels</h1>
|
||||
<p className="text-muted-foreground mt-3">
|
||||
Access favorite sites and services instantly, without leaving your current page.
|
||||
</p>
|
||||
<img src="/sidebar.png" className="mt-8 w-full h-full" />
|
||||
<Image
|
||||
height={500}
|
||||
width={300}
|
||||
src="/sidebar.png"
|
||||
className="mt-8 w-full h-full"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -94,17 +124,34 @@ export default function Features() {
|
||||
How Zen compares to other browsers
|
||||
</span>
|
||||
</TableHead>
|
||||
<TableHead className="font-bold text-center min-w-[70px]">
|
||||
<img src="/favicon.ico" className="dark:bg-white rounded-md mx-auto mb-2 w-9 h-9" />
|
||||
<TableHead className="py-2 font-bold text-center">
|
||||
<Image
|
||||
height={32}
|
||||
width={32}
|
||||
src="/favicon.ico"
|
||||
className="bg-black dark:bg-white rounded-md mx-auto mb-2 w-8 h-8"
|
||||
alt="zen"
|
||||
/>
|
||||
Zen
|
||||
</TableHead>
|
||||
<TableHead className="pl-4 pr-0 font-bold text-center opacity-60 min-w-[70px]">
|
||||
<img src="/floorp.png" className="dark:bg-white rounded-md p-1 mx-auto mt-1 mb-3 w-7 h-7" />
|
||||
<TableHead className="py-2 pl-4 md:pr-0 pr-2 font-bold text-center opacity-60">
|
||||
<Image
|
||||
height={32}
|
||||
width={32}
|
||||
src="/floorp.png"
|
||||
className="bg-black dark:bg-white rounded-md p-1 mx-auto mb-2 w-7 h-7"
|
||||
alt="floorp"
|
||||
/>
|
||||
Floorp
|
||||
</TableHead>
|
||||
|
||||
<TableHead className="font-bold text-center opacity-60 min-w-[70px] ">
|
||||
<img src="/librewolf.png" className="dark:bg-white rounded-md mx-auto p-1 mt-1 mb-3 w-7 h-7" />
|
||||
<TableHead className="py-2 pl-0 font-bold text-center opacity-60">
|
||||
<Image
|
||||
height={32}
|
||||
width={32}
|
||||
src="/librewolf.png"
|
||||
className="bg-black dark:bg-white rounded-md mx-auto p-1 mb-2 w-7 h-7"
|
||||
alt="librewolf"
|
||||
/>
|
||||
LibreWolf
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
@@ -179,6 +226,7 @@ export default function Features() {
|
||||
Zen is engineered for speed, consistently outperforming competitors with every release, ensuring a faster browsing experience.
|
||||
</p>
|
||||
</div>
|
||||
<RabbitIcon className="mx-auto hidden md:block w-32 h-32" />
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row w-full mt-20 p-5 justify-between items-center">
|
||||
<div className="flex flex-col max-w-lg text-center md:text-start">
|
||||
@@ -197,6 +245,7 @@ export default function Features() {
|
||||
Zen incorporates advanced security technologies that outshine other Firefox-based browsers, keeping you safe online.
|
||||
</p>
|
||||
</div>
|
||||
<LockClosedIcon className="mx-auto hidden md:block w-32 h-32" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-20 w-full flex items-center justify-center flex-col">
|
||||
@@ -220,12 +269,9 @@ export default function Features() {
|
||||
<FeatureCard title="Vertical tabs"
|
||||
description="Maximize space and order with vertical tabs, designed for easy access." />
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => (window.location.href = "/download")}
|
||||
className="mt-8"
|
||||
>
|
||||
Download Zen Browser
|
||||
</Button>
|
||||
<Link href="/download">
|
||||
<Button className="mt-8">Download Zen Browser</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import Link from "next/link";
|
||||
import Logo from "./logo";
|
||||
import TextReveal from "./ui/text-reveal";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<div className="font-medium flex-col md:flex-row px-10 md:px-0 border-t w-full border-grey py-10 mt-10 flex justify-center align-center">
|
||||
Zen Browser © {new Date().getFullYear()} -
|
||||
Made with ❤️ by the Zen team.
|
||||
<a className="mt-5 md:mt-0 md:ml-2 font-bold" href="https://github.com/zen-browser" target="_blank">Source Code</a>
|
||||
Zen Browser © {new Date().getFullYear()} - Made with ❤️ by the Zen team.
|
||||
<Link
|
||||
href={"https://github.com/zen-browser"}
|
||||
target="_blank"
|
||||
className="mt-5 md:mt-0 md:ml-2 font-bold"
|
||||
>
|
||||
Source Code
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,86 +1,88 @@
|
||||
'use client'
|
||||
|
||||
import { ArrowRightIcon } from '@radix-ui/react-icons'
|
||||
import { useInView } from 'framer-motion'
|
||||
import { useRef } from 'react'
|
||||
import AnimatedGradientText from './ui/animated-gradient-text'
|
||||
import { Button } from './ui/button'
|
||||
import { BorderBeam } from './ui/border-beam'
|
||||
import { ny } from '@/lib/utils'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import Particles from './ui/particles'
|
||||
|
||||
"use client";
|
||||
|
||||
import { ArrowRightIcon } from "@radix-ui/react-icons";
|
||||
import { useInView } from "framer-motion";
|
||||
import { useRef } from "react";
|
||||
import AnimatedGradientText from "./ui/animated-gradient-text";
|
||||
import { Button } from "./ui/button";
|
||||
import { BorderBeam } from "./ui/border-beam";
|
||||
import { ny } from "@/lib/utils";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import Particles from "./ui/particles";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
export default function Header() {
|
||||
const ref = useRef(null)
|
||||
const inView = useInView(ref, { once: true, margin: '-100px' })
|
||||
return (
|
||||
const ref = useRef(null);
|
||||
const inView = useInView(ref, { once: true, margin: "-100px" });
|
||||
return (
|
||||
<>
|
||||
<section
|
||||
id="hero"
|
||||
className="relative mx-auto mt-40 max-w-7xl px-6 text-center md:px-8"
|
||||
id="hero"
|
||||
className="relative mx-auto mt-40 max-w-7xl px-6 text-center md:px-8"
|
||||
>
|
||||
<a href="/download">
|
||||
<AnimatedGradientText>
|
||||
🎉
|
||||
{' '}
|
||||
<hr className="mx-2 h-4 w-[1px] shrink-0 bg-gray-300" />
|
||||
{' '}
|
||||
<span
|
||||
className={ny(
|
||||
`inline animate-gradient bg-gradient-to-r from-[#ffaa40] via-[#9c40ff] to-[#ffaa40] bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent`,
|
||||
)}
|
||||
>
|
||||
Introducing Zen Alpha
|
||||
</span>
|
||||
<ChevronRight className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
||||
</AnimatedGradientText>
|
||||
</a>
|
||||
<h1 className="animate-fade-in -translate-y-4 text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text py-6 text-5xl font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-6xl md:text-7xl lg:text-8xl dark:from-white dark:to-white/40">
|
||||
Zen is the best way
|
||||
<br className="hidden md:block" />
|
||||
{' '}
|
||||
to browse the web.
|
||||
</h1>
|
||||
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms] md:text-xl">
|
||||
Beautifully designed, privacy-focused, and packed with features.
|
||||
<br className="hidden md:block" />
|
||||
{' '}
|
||||
We care about your experience, not your data.
|
||||
</p>
|
||||
<Button className="animate-fade-in -translate-y-4 gap-1 rounded-lg text-white opacity-0 ease-in-out [--animation-delay:600ms] dark:text-black" onClick={() => window.location.href = '/download'}>
|
||||
<span>Download Zen Now </span>
|
||||
<ArrowRightIcon className="ml-1 size-4 transition-transform duration-300 ease-in-out group-hover:translate-x-1" />
|
||||
</Button>
|
||||
<div
|
||||
ref={ref}
|
||||
className="animate-fade-up relative mt-32 opacity-0 [--animation-delay:400ms] [perspective:2000px] after:absolute after:inset-0 after:z-50 after:[background:linear-gradient(to_top,hsl(var(--background))_30%,transparent)]"
|
||||
>
|
||||
<div
|
||||
className={`rounded-xl border border-white/10 bg-white bg-opacity-[0.01] before:absolute before:bottom-1/2 before:left-0 before:top-0 before:size-full before:opacity-0 before:[background-image:linear-gradient(to_bottom,var(--color-one),var(--color-one),transparent_40%)] before:[filter:blur(180px)] ${
|
||||
inView ? 'before:animate-image-glow' : ''
|
||||
}`}
|
||||
<Link href="/download">
|
||||
<AnimatedGradientText>
|
||||
🎉 <hr className="mx-2 h-4 w-[1px] shrink-0 bg-gray-300" />{" "}
|
||||
<span
|
||||
className={ny(
|
||||
`inline animate-gradient bg-gradient-to-r from-[#ffaa40] via-[#9c40ff] to-[#ffaa40] bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent`
|
||||
)}
|
||||
>
|
||||
<BorderBeam
|
||||
size={200}
|
||||
duration={12}
|
||||
delay={11}
|
||||
colorFrom="var(--color-one)"
|
||||
colorTo="var(--color-two)"
|
||||
/>
|
||||
Introducing Zen Alpha
|
||||
</span>
|
||||
<ChevronRight className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
||||
</AnimatedGradientText>
|
||||
</Link>
|
||||
<h1 className="animate-fade-in -translate-y-4 text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text py-6 text-5xl font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-6xl md:text-7xl lg:text-8xl dark:from-white dark:to-white/40">
|
||||
Zen is the best way
|
||||
<br className="hidden md:block" /> to browse the web.
|
||||
</h1>
|
||||
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms] md:text-xl">
|
||||
Beautifully designed, privacy-focused, and packed with features.
|
||||
<br className="hidden md:block" /> We care about your experience, not
|
||||
your data.
|
||||
</p>
|
||||
<Link href="/download">
|
||||
<Button
|
||||
className="animate-fade-in -translate-y-4 gap-1 rounded-lg text-white opacity-0 ease-in-out [--animation-delay:600ms] dark:text-black"
|
||||
>
|
||||
<span>Download Zen Now </span>
|
||||
<ArrowRightIcon className="ml-1 size-4 transition-transform duration-300 ease-in-out group-hover:translate-x-1" />
|
||||
</Button>
|
||||
</Link>
|
||||
<div
|
||||
ref={ref}
|
||||
className="animate-fade-up relative mt-32 opacity-0 [--animation-delay:400ms] [perspective:2000px] after:absolute after:inset-0 after:z-50 after:[background:linear-gradient(to_top,hsl(var(--background))_30%,transparent)]"
|
||||
>
|
||||
<div
|
||||
className={`rounded-xl border border-white/10 bg-white bg-opacity-[0.01] before:absolute before:bottom-1/2 before:left-0 before:top-0 before:size-full before:opacity-0 before:[background-image:linear-gradient(to_bottom,var(--color-one),var(--color-one),transparent_40%)] before:[filter:blur(180px)] ${
|
||||
inView ? "before:animate-image-glow" : ""
|
||||
}`}
|
||||
>
|
||||
<BorderBeam
|
||||
size={200}
|
||||
duration={12}
|
||||
delay={11}
|
||||
colorFrom="var(--color-one)"
|
||||
colorTo="var(--color-two)"
|
||||
/>
|
||||
|
||||
<img
|
||||
src="/browser-dark.png"
|
||||
alt="browser Image"
|
||||
className="relative hidden size-full rounded-[inherit] border object-contain dark:block"
|
||||
/>
|
||||
<img
|
||||
src="/browser-light.png"
|
||||
alt="browser Image"
|
||||
className="relative block size-full rounded-[inherit] border object-contain dark:hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Image
|
||||
width={1600}
|
||||
height={800}
|
||||
src="/browser-dark.png"
|
||||
alt="browser Image"
|
||||
className="relative hidden rounded-[inherit] border object-contain dark:block"
|
||||
/>
|
||||
<Image
|
||||
width={1600}
|
||||
height={800}
|
||||
src="/browser-light.png"
|
||||
alt="browser Image"
|
||||
className="relative block rounded-[inherit] border object-contain dark:hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Particles
|
||||
className="absolute inset-0 -z-10 hidden dark:block"
|
||||
@@ -99,5 +101,5 @@ export default function Header() {
|
||||
color="#000000"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
import Image from "next/image";
|
||||
export default function Logo({ withText, ...props }: any) {
|
||||
return (
|
||||
<div className="flex items-center m-0" {...props}>
|
||||
<img src="/logo.png" alt="Zen Logo" className="w-12 h-12" />
|
||||
<Image src="/logo.png" alt="Zen Logo" width={50} height={50} />
|
||||
{withText && <span className="text-2xl font-bold ml-2">Zen</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
28
src/components/marketplace.tsx
Normal file
28
src/components/marketplace.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import ThemesSearch from "./themes-search";
|
||||
import { getAllThemes, getThemesFromSearch, ZenTheme } from "@/lib/themes";
|
||||
import ThemeCard from "./theme-card";
|
||||
|
||||
export default function MarketplacePage() {
|
||||
const [searchInput, setSearchInput] = React.useState("");
|
||||
const [themes, setThemes] = React.useState<ZenTheme[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setThemes(getAllThemes());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-1/2 items-center justify-center h-full mt-36">
|
||||
<div className="mx-auto w-full text-center">
|
||||
<h1 className="text-7xl font-bold">Themes Marketplace</h1>
|
||||
<ThemesSearch input={searchInput} setInput={setSearchInput} />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-10 w-full">
|
||||
{getThemesFromSearch(themes, searchInput).map((theme) => (
|
||||
<ThemeCard key={theme.name} theme={theme} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -42,6 +42,12 @@ export function MobileNav() {
|
||||
>
|
||||
Download
|
||||
</MobileLink>
|
||||
<MobileLink
|
||||
href="/themes"
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
Themes
|
||||
</MobileLink>
|
||||
<MobileLink
|
||||
href="/release-notes"
|
||||
onOpenChange={setOpen}
|
||||
|
||||
@@ -29,11 +29,16 @@ export const components: { title: string; href: string; description: string }[]
|
||||
href: "https://discord.gg/nnShMQzR4b",
|
||||
description: "Join our Discord server to chat with the community."
|
||||
},
|
||||
{
|
||||
title: "Source Code",
|
||||
href: "https://github.com/zen-browser",
|
||||
description: "Check out our source code on GitHub and leave a star!"
|
||||
},
|
||||
]
|
||||
|
||||
export function Navigation() {
|
||||
return (
|
||||
<div className="bg-background fixed z-10 top-0 left-0 w-full flex fixed border-b border-grey p-2 items-center justify-center">
|
||||
<div className="bg-background z-10 top-0 left-0 w-full flex fixed border-b border-grey p-2 items-center justify-center">
|
||||
<MobileNav />
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList className="w-full hidden sm:flex">
|
||||
@@ -48,7 +53,7 @@ export function Navigation() {
|
||||
<ul className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
|
||||
<li className="row-span-3">
|
||||
<NavigationMenuLink asChild>
|
||||
<a
|
||||
<Link
|
||||
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none focus:shadow-md"
|
||||
href="/"
|
||||
>
|
||||
@@ -60,14 +65,14 @@ export function Navigation() {
|
||||
Firefox based browser with a focus on privacy and
|
||||
customization.
|
||||
</p>
|
||||
</a>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<ListItem href="/download" title="Download">
|
||||
Start using Zen Browser today with just a few clicks.
|
||||
</ListItem>
|
||||
<ListItem href="https://github.com/zen-browser" title="Source Code" target="_blank">
|
||||
View the source code on GitHub and maybe leave a star!
|
||||
<ListItem href="/themes" title="Themes Marketplace">
|
||||
Customize your browser with a variety of themes!
|
||||
</ListItem>
|
||||
<ListItem href="/release-notes" title="Release Notes">
|
||||
Stay up to date with the latest changes.
|
||||
|
||||
@@ -2,20 +2,34 @@ import { ReleaseNote } from "@/lib/release-notes";
|
||||
import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
|
||||
import { CheckCheckIcon, StarIcon } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
import Link from "next/link";
|
||||
export default function ReleaseNoteElement({ data }: { data: ReleaseNote }) {
|
||||
return (
|
||||
<div className="flex flex-col mt-52 mb-24">
|
||||
<div className="mx-auto w-full px-10 md:px-0 md:w-1/2">
|
||||
<h1 className="text-4xl font-bold">Release notes for {data.version} 🎉</h1>
|
||||
<p className="text-sm mt-1 font-bold text-muted-foreground">{data.date}</p>
|
||||
<h1 className="text-4xl font-bold">
|
||||
Release notes for {data.version} 🎉
|
||||
</h1>
|
||||
<p className="text-sm mt-1 font-bold text-muted-foreground">
|
||||
{data.date}
|
||||
</p>
|
||||
<p className="text-md mt-4 text-muted-foreground">
|
||||
If you encounter any issues, please report them on <a href="https://github.com/zen-browser/desktop/issues/" className="text-underline text-blue-500">the issues page</a>. Thanks everyone for your feedback! ❤️
|
||||
If you encounter any issues, please report them on{" "}
|
||||
<Link
|
||||
href="https://github.com/zen-browser/desktop/issues/"
|
||||
className="text-underline text-blue-500"
|
||||
>
|
||||
the issues page
|
||||
</Link>
|
||||
. Thanks everyone for your feedback! ❤️
|
||||
</p>
|
||||
{data.extra && (
|
||||
<p className="text-md mt-8" dangerouslySetInnerHTML=
|
||||
{{__html: data.extra.replace(/(\n)/g, "<br />")}}>
|
||||
</p>
|
||||
<p
|
||||
className="text-md mt-8"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data.extra.replace(/(\n)/g, "<br />"),
|
||||
}}
|
||||
></p>
|
||||
)}
|
||||
{data.breakingChanges && (
|
||||
<>
|
||||
@@ -23,10 +37,14 @@ export default function ReleaseNoteElement({ data }: { data: ReleaseNote }) {
|
||||
<ExclamationTriangleIcon className="w-6 h-6 mr-4" />
|
||||
Breaking changes
|
||||
</h2>
|
||||
<p className="text-md mt-4">The following changes may break existing functionality:</p>
|
||||
<p className="text-md mt-4">
|
||||
The following changes may break existing functionality:
|
||||
</p>
|
||||
<ul className="list-disc list-inside mt-2">
|
||||
{data.breakingChanges?.map((change, index) => (
|
||||
<li key={index} className="mt-1 text-muted-foreground">{change}</li>
|
||||
<li key={index} className="mt-1 text-muted-foreground">
|
||||
{change}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
@@ -37,10 +55,14 @@ export default function ReleaseNoteElement({ data }: { data: ReleaseNote }) {
|
||||
<StarIcon className="w-6 h-6 mr-4" />
|
||||
Features
|
||||
</h2>
|
||||
<p className="text-md mt-2">The following features have been added:</p>
|
||||
<p className="text-md mt-2">
|
||||
The following features have been added:
|
||||
</p>
|
||||
<ul className="list-disc list-inside mt-4">
|
||||
{data.features?.map((feature, index) => (
|
||||
<li key={index} className="text-md mt-1 text-muted-foreground">{feature}</li>
|
||||
<li key={index} className="text-md mt-1 text-muted-foreground">
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
@@ -51,19 +73,21 @@ export default function ReleaseNoteElement({ data }: { data: ReleaseNote }) {
|
||||
<CheckCheckIcon className="w-6 h-6 mr-4" />
|
||||
Fixes
|
||||
</h2>
|
||||
<p className="text-md mt-2">The following issues have been fixed:</p>
|
||||
<p className="text-md mt-2">
|
||||
The following issues have been fixed:
|
||||
</p>
|
||||
<ul className="list-disc list-inside mt-2">
|
||||
{data.fixes?.map((fix, index) => (
|
||||
<li key={index} className="mt-1 text-muted-foreground">
|
||||
{fix.description}
|
||||
{fix.issue && (
|
||||
<a
|
||||
<Link
|
||||
href={`https://github.com/zen-browser/desktop/issues/${fix.issue}`}
|
||||
target="_blank"
|
||||
className="ml-1 text-blue-500"
|
||||
>
|
||||
issue #{fix.issue}
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
@@ -71,7 +95,11 @@ export default function ReleaseNoteElement({ data }: { data: ReleaseNote }) {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Button className="mt-12 w-fit mx-auto" onClick={() => window.location.href = '/download'}>Download zen now!</Button>
|
||||
<div className="flex flex-wrap items-center justify-center">
|
||||
<Link href="/download">
|
||||
<Button className="mt-12 w-fit mx-auto">Download zen now!</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
41
src/components/theme-card.tsx
Normal file
41
src/components/theme-card.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ZenTheme } from "@/lib/themes";
|
||||
import styled from "styled-components";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
const ThemeCardWrapepr = styled.div`
|
||||
`;
|
||||
|
||||
export default function ThemeCard({
|
||||
theme
|
||||
}: {
|
||||
theme: ZenTheme;
|
||||
}) {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<ThemeCardWrapepr className="flex flex-col justify-start p-6 rounded-lg shadow-md bg-muted/50 border border-muted w-full hover:shadow-lg transition duration-300 ease-in-out hover:bg-muted/100 hover:border-blue-500 cursor-pointer select-none">
|
||||
<img src={theme.image} alt={theme.name} className="w-full h-32 object-cover rounded-md" />
|
||||
<h2 className="text-xl font-bold mt-4 overflow-ellipsis text-start">{theme.name}</h2>
|
||||
<p className="text-md mt-2 overflow-ellipsis text-muted-foreground text-start">{theme.description}</p>
|
||||
</ThemeCardWrapepr>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<img src={theme.image} alt={theme.name} className="w-full h-32 object-cover rounded-md mb-10" />
|
||||
<DialogTitle>{theme.name}</DialogTitle>
|
||||
<DialogDescription>{theme.description}</DialogDescription>
|
||||
<hr className="!my-4" />
|
||||
<div className="w-full relative flex justify-end">
|
||||
<Button className="hidden install-theme" zen-theme-id={theme.id}>
|
||||
Install Theme
|
||||
</Button>
|
||||
<p className="text-muted-foreground text-sm install-theme-error">
|
||||
You need to have Zen Browser installed to use this theme. <a href="/download" className="text-blue-500">Download</a>
|
||||
</p>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
21
src/components/themes-search.tsx
Normal file
21
src/components/themes-search.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { SearchIcon } from "lucide-react";
|
||||
|
||||
export default function ThemesSearch({
|
||||
input, setInput
|
||||
}: {
|
||||
input: string;
|
||||
setInput: (input: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex w-full p-2 bg-muted/50 rounded-full mt-10 items-center border border-muted">
|
||||
<SearchIcon className="w-6 h-6 mx-4 text-muted" />
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Search themes"
|
||||
className="w-full bg-transparent border-none focus:outline-none focus:border-none focus:ring-0 text-white placeholder-muted"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ interface BlurFadeProps {
|
||||
delay?: number
|
||||
yOffset?: number
|
||||
inView?: boolean
|
||||
inViewMargin?: string
|
||||
inViewMargin?: any
|
||||
blur?: string
|
||||
}
|
||||
|
||||
|
||||
126
src/components/ui/dialog.tsx
Normal file
126
src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||
import { Cross2Icon } from '@radix-ui/react-icons'
|
||||
|
||||
import { ny } from '@/lib/utils'
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal
|
||||
|
||||
const DialogClose = DialogPrimitive.Close
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={ny(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={ny(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
|
||||
<Cross2Icon className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
function DialogHeader({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={ny(
|
||||
'flex flex-col space-y-1.5 text-center sm:text-left',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DialogHeader.displayName = 'DialogHeader'
|
||||
|
||||
function DialogFooter({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={ny(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DialogFooter.displayName = 'DialogFooter'
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={ny(
|
||||
'text-lg font-semibold leading-none tracking-tight',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={ny('text-muted-foreground text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
||||
Reference in New Issue
Block a user