Refactor next.config.js and form.tsx

This commit is contained in:
mr. M 2024-10-14 22:52:01 +02:00
parent 00c7ce4c56
commit 5ec0fcbb56
No known key found for this signature in database
GPG key ID: CBD57A2AEDBDA1FB
31 changed files with 388 additions and 336 deletions

View file

@ -41,4 +41,4 @@ const nextConfig = (phase, { defaultConfig }) => {
}; };
}; };
module.exports = nextConfig; module.exports = nextConfig;

View file

@ -77,49 +77,60 @@ function formatReleaseNote(releaseNote: ReleaseNote) {
Thanks everyone for your feedback! Thanks everyone for your feedback!
</p>`; </p>`;
if (releaseNote.image) { if (releaseNote.image) {
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png" content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
alt="Release Image for version ${releaseNote.version}" alt="Release Image for version ${releaseNote.version}"
style="max-width: 30em; width: 100%; border-radius: 0.5rem;" style="max-width: 30em; width: 100%; border-radius: 0.5rem;"
/>`; />`;
} }
if (releaseNote.extra) { if (releaseNote.extra) {
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`; content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`;
} }
content += addReleaseNoteSection("⚠️ Breaking changes", releaseNote.breakingChanges); content += addReleaseNoteSection(
content += addReleaseNoteSection("✓ Fixes", releaseNote.fixes?.map(fixToReleaseNote)); "⚠️ Breaking changes",
content += addReleaseNoteSection("🖌 Theme Changes", releaseNote.themeChanges) releaseNote.breakingChanges,
content += addReleaseNoteSection("⭐ Features", releaseNote.features); );
content += addReleaseNoteSection(
"✓ Fixes",
releaseNote.fixes?.map(fixToReleaseNote),
);
content += addReleaseNoteSection(
"🖌 Theme Changes",
releaseNote.themeChanges,
);
content += addReleaseNoteSection("⭐ Features", releaseNote.features);
return content; return content;
} }
function addReleaseNoteSection(title: string, items?: string[]): string { function addReleaseNoteSection(title: string, items?: string[]): string {
if (!items) { if (!items) {
return ""; return "";
} }
let content = `<h2>${title}</h2>`; let content = `<h2>${title}</h2>`;
content += `<ul>`; content += `<ul>`;
for (const item of items) { for (const item of items) {
if (item && item.length > 0) { if (item && item.length > 0) {
content += `<li>${item}</li>`; content += `<li>${item}</li>`;
} }
} }
content += `</ul>`; content += `</ul>`;
return content; return content;
} }
function fixToReleaseNote(fix?: Exclude<ReleaseNote['fixes'], undefined>[number]) { function fixToReleaseNote(
if (!fix || !fix.description || fix.description.length === 0) { fix?: Exclude<ReleaseNote["fixes"], undefined>[number],
return ""; ) {
} if (!fix || !fix.description || fix.description.length === 0) {
return "";
}
let note = fix.description; let note = fix.description;
if (fix.issue) { if (fix.issue) {
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`; note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`;
} }
return note; return note;
} }

View file

@ -49,11 +49,11 @@
--color-three: #ff2e63; --color-three: #ff2e63;
*/ */
--color-1: 0 100% 63%; --color-1: 0 100% 63%;
--color-2: 270 100% 63%; --color-2: 270 100% 63%;
--color-3: 210 100% 63%; --color-3: 210 100% 63%;
--color-4: 195 100% 63%; --color-4: 195 100% 63%;
--color-5: 90 100% 63%; --color-5: 90 100% 63%;
} }
[data-theme="dark"], [data-theme="dark"],

View file

@ -57,7 +57,7 @@
} }
code { code {
font-size: .8em; font-size: 0.8em;
background-color: var(--surface); background-color: var(--surface);
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
border: 1px solid light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.2)); border: 1px solid light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.2));

View file

@ -1,14 +1,14 @@
'use client'; "use client";
import { useEffect } from 'react'; import { useEffect } from "react";
import { useRouter } from 'next/navigation'; import { useRouter } from "next/navigation";
export default function ThemesPage() { export default function ThemesPage() {
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
router.replace('/mods'); router.replace("/mods");
}, [router]); }, [router]);
return null; return null;
} }

View file

@ -2,7 +2,6 @@
import Image from "next/image"; import Image from "next/image";
const inDev = process.env.NODE_ENV === "development"; const inDev = process.env.NODE_ENV === "development";
function imageLoader({ src }: { src: string }) { function imageLoader({ src }: { src: string }) {
// Load locally if we are in development // Load locally if we are in development

View file

@ -31,7 +31,7 @@ export const AlertModal = ({
</div> </div>
</AlertDialog.Title> </AlertDialog.Title>
<AlertDialog.Description asChild> <AlertDialog.Description asChild>
<p className="text-center text-sm text-muted-foreground mb-6"> <p className="mb-6 text-center text-sm text-muted-foreground">
Please select other formats if you want to install the Optimized Please select other formats if you want to install the Optimized
version. version.
</p> </p>

View file

@ -75,7 +75,12 @@ export function BrandingAssets() {
CC BY-SA 4.0 CC BY-SA 4.0
</a> </a>
. Thanks to{" "} . Thanks to{" "}
<a href="https://www.onnno.nl/" rel="noopener noreferrer" target="_blank" className="text-blue-500"> <a
href="https://www.onnno.nl/"
rel="noopener noreferrer"
target="_blank"
className="text-blue-500"
>
Donno (mr. Logos) Donno (mr. Logos)
</a>{" "} </a>{" "}
for the assets. for the assets.

View file

@ -6,34 +6,46 @@ import { Slider } from "./ui/slider";
import React from "react"; import React from "react";
export default function BrowserComplexityExample() { export default function BrowserComplexityExample() {
const [selectedImage, setSelectedImage] = React.useState([1]); const [selectedImage, setSelectedImage] = React.useState([1]);
return ( return (
<div className="flex h-screen items-center flex-col mx-auto mb-32 xl:mb-64 w-full md:w-5/6 lg:w-3/4"> <div className="mx-auto mb-32 flex h-screen w-full flex-col items-center md:w-5/6 lg:w-3/4 xl:mb-64">
<h1 className="text-4xl md:text-5xl font-bold text-center">How much browser do you want?</h1> <h1 className="text-center text-4xl font-bold md:text-5xl">
<p className="mt-4 text-center text-md mx-auto w-2/3 text-muted-foreground"> How much browser do you want?
Zen is designed to be simple and easy to use. We believe that the best software is </h1>
the one that you don't notice. However, we can assure you that if you want customization, we have you covered <p className="text-md mx-auto mt-4 w-2/3 text-center text-muted-foreground">
</p> Zen is designed to be simple and easy to use. We believe that the best
<div className="w-64 mb-6 mt-12 flex gap-4"> software is the one that you don't notice. However, we can assure you
<span className="opacity-90">🌱</span> that if you want customization, we have you covered
<Slider step={1} max={3} showSteps="half" value={selectedImage} onValueChange={setSelectedImage} /> </p>
<span className="opacity-90">🌳</span> <div className="mb-6 mt-12 flex w-64 gap-4">
</div> <span className="opacity-90">🌱</span>
<div className="mx-auto md:mb-36 flex justify-center"> <Slider
{[...Array(4)].map((_, i) => ( step={1}
<CachedImage max={3}
width={1620} showSteps="half"
height={900} value={selectedImage}
priority onValueChange={setSelectedImage}
key={i} />
src={`www/public/browsers/image${i + 1}.png`} <span className="opacity-90">🌳</span>
alt="Zen Browser" </div>
className={ny("rounded-md object-cover shadow object-right mx-12 w-full", selectedImage[0] === i <div className="mx-auto flex justify-center md:mb-36">
? "" //"animate-fade-up duration-500 !opacity-100" {[...Array(4)].map((_, i) => (
: "hidden")} <CachedImage
/> width={1620}
))} height={900}
</div> priority
</div> key={i}
) src={`www/public/browsers/image${i + 1}.png`}
alt="Zen Browser"
className={ny(
"mx-12 w-full rounded-md object-cover object-right shadow",
selectedImage[0] === i
? "" //"animate-fade-up duration-500 !opacity-100"
: "hidden",
)}
/>
))}
</div>
</div>
);
} }

View file

@ -1,14 +1,12 @@
import s from './styles.module.css' import s from "./styles.module.css";
export default function CoolHeaderText() { export default function CoolHeaderText() {
return ( return (
<> <>
<div className="relative mb-3 mt-5 -translate-y-4 animate-fade-in text-balance bg-gradient-to-br 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] dark:from-white dark:to-white/40 sm:text-6xl md:text-7xl lg:text-8xl"> <div className="relative mb-3 mt-5 -translate-y-4 animate-fade-in text-balance bg-gradient-to-br 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] dark:from-white dark:to-white/40 sm:text-6xl md:text-7xl lg:text-8xl">
<h1 className={s.title}> <h1 className={s.title}>Stay focused, browse faster with Zen</h1>
Stay focused, browse faster with Zen
</h1>
</div> </div>
<div className="pointer-events-none absolute right-20 top-[-5px] mt-12 hidden h-fit w-fit !rotate-[15deg] transform animate-fade-in rounded-full bg-surface border-2 px-3 py-1 opacity-0 shadow [--animation-delay:400ms] md:block"> <div className="pointer-events-none absolute right-20 top-[-5px] mt-12 hidden h-fit w-fit !rotate-[15deg] transform animate-fade-in rounded-full border-2 bg-surface px-3 py-1 opacity-0 shadow [--animation-delay:400ms] md:block">
Alpha Version Alpha Version
</div> </div>
</> </>

View file

@ -1,24 +1,23 @@
@keyframes hueShift { @keyframes hueShift {
0% { 0% {
filter: hue-rotate(0deg); filter: hue-rotate(0deg);
} }
50% { 50% {
filter: hue-rotate(170deg); filter: hue-rotate(170deg);
} }
100% { 100% {
filter: hue-rotate(0deg); filter: hue-rotate(0deg);
} }
} }
.title { .title {
background-clip: text; background-clip: text;
background-image: linear-gradient(90deg, #0077e7, #01d8d1); background-image: linear-gradient(90deg, #0077e7, #01d8d1);
-webkit-background-clip: text; -webkit-background-clip: text;
color: transparent; color: transparent;
filter: hue-rotate(0deg); filter: hue-rotate(0deg);
animation: hueShift 10s infinite linear 1s; animation: hueShift 10s infinite linear 1s;
padding-bottom: 8px; padding-bottom: 8px;
user-select: none; user-select: none;
cursor: default; cursor: default;
} }

View file

@ -5,7 +5,6 @@ import { Button } from "./ui/button";
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { import {
Sheet, Sheet,
SheetContent, SheetContent,

View file

@ -1,4 +1,4 @@
'use client'; "use client";
import { Architecture } from "@/components/download/types"; import { Architecture } from "@/components/download/types";
import { ny } from "@/lib/utils"; import { ny } from "@/lib/utils";

View file

@ -53,4 +53,4 @@ export const FieldDescription = styled.div`
font-size: 1rem; font-size: 1rem;
color: #666; color: #666;
margin-bottom: 1rem; margin-bottom: 1rem;
`; `;

View file

@ -46,9 +46,7 @@ export const MacArchitectureCard = ({
> >
<h1 className="my-2 text-5xl opacity-40 dark:opacity-20">{icon}</h1> <h1 className="my-2 text-5xl opacity-40 dark:opacity-20">{icon}</h1>
<h1 className="my-2 text-2xl font-semibold">{label}</h1> <h1 className="my-2 text-2xl font-semibold">{label}</h1>
<p className="mx-auto text-center text-muted-foreground"> <p className="mx-auto text-center text-muted-foreground">{description}</p>
{description}
</p>
</div> </div>
); );
}; };

View file

@ -1,4 +1,4 @@
'use client'; "use client";
import { Platforms } from "@/components/download/types"; import { Platforms } from "@/components/download/types";
import { ny } from "@/lib/utils"; import { ny } from "@/lib/utils";
@ -10,7 +10,7 @@ interface PlatformCardProps {
} }
const PLATFORMS_DATA: Record< const PLATFORMS_DATA: Record<
Exclude<Platforms, 'Unsupported'>, Exclude<Platforms, "Unsupported">,
{ label: string; icon: string; borderColor: string } { label: string; icon: string; borderColor: string }
> = { > = {
Windows: { Windows: {

View file

@ -1,28 +1,28 @@
import confetti from "canvas-confetti"; import confetti from "canvas-confetti";
export const throwConfetti = () => { export const throwConfetti = () => {
const end = Date.now() + 3 * 1000; // 3 seconds const end = Date.now() + 3 * 1000; // 3 seconds
const colors = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"]; const colors = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"];
const frame = () => { const frame = () => {
if (Date.now() > end) return; if (Date.now() > end) return;
confetti({ confetti({
particleCount: 2, particleCount: 2,
angle: 60, angle: 60,
spread: 55, spread: 55,
startVelocity: 60, startVelocity: 60,
origin: { x: 0, y: 0.5 }, origin: { x: 0, y: 0.5 },
colors, colors,
}); });
confetti({ confetti({
particleCount: 2, particleCount: 2,
angle: 120, angle: 120,
spread: 55, spread: 55,
startVelocity: 60, startVelocity: 60,
origin: { x: 1, y: 0.5 }, origin: { x: 1, y: 0.5 },
colors, colors,
}); });
requestAnimationFrame(frame); requestAnimationFrame(frame);
}; };
frame(); frame();
}; };

View file

@ -1,3 +1,3 @@
export type Platforms = "Windows" | "MacOS" | "Linux" | "Unsupported"; export type Platforms = "Windows" | "MacOS" | "Linux" | "Unsupported";
export type Architecture = "specific" | "generic"; export type Architecture = "specific" | "generic";

View file

@ -10,16 +10,16 @@ interface WindowsInstallerProps {
flowIndex: number; flowIndex: number;
platform: Platforms | null; platform: Platforms | null;
selectedArchitecture: string; selectedArchitecture: string;
setSelectedWindowsDownloadType: (value: string) => void; setSelectedWindowsDownloadType: (value: string) => void;
selectedWindowsDownloadType: string; selectedWindowsDownloadType: string;
} }
export const WindowsInstaller = ({ export const WindowsInstaller = ({
flowIndex, flowIndex,
platform, platform,
selectedArchitecture, selectedArchitecture,
setSelectedWindowsDownloadType, setSelectedWindowsDownloadType,
selectedWindowsDownloadType, selectedWindowsDownloadType,
}: WindowsInstallerProps) => { }: WindowsInstallerProps) => {
return ( return (
<FormField <FormField

View file

@ -1,16 +1,26 @@
'use client' "use client";
import CachedImage from "@/components/CachedImage"; import CachedImage from "@/components/CachedImage";
import Logo from "@/components/logo"; import Logo from "@/components/logo";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import {
import { ExternalLinkIcon, EyeClosedIcon, LockClosedIcon, QuestionMarkCircledIcon } from "@radix-ui/react-icons"; Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import {
ExternalLinkIcon,
EyeClosedIcon,
LockClosedIcon,
QuestionMarkCircledIcon,
} from "@radix-ui/react-icons";
import { ShieldCheck, ShieldAlertIcon } from "lucide-react"; import { ShieldCheck, ShieldAlertIcon } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react"; import { useState } from "react";
export const FAQ = () => { export const FAQ = () => {
const [feature, setFeature] = useState(""); const [feature, setFeature] = useState("");
return ( return (
<div className="mx-auto mt-36 flex w-full flex-col bg-surface shadow md:w-5/6 md:rounded-md lg:w-3/4 lg:flex-row"> <div className="mx-auto mt-36 flex w-full flex-col bg-surface shadow md:w-5/6 md:rounded-md lg:w-3/4 lg:flex-row">
<div className="relative flex w-full flex-col justify-center p-5 lg:w-1/2 lg:p-12"> <div className="relative flex w-full flex-col justify-center p-5 lg:w-1/2 lg:p-12">

View file

@ -48,8 +48,8 @@ export default function Features() {
<PaintBucket className="inline h-10 w-10"></PaintBucket> <PaintBucket className="inline h-10 w-10"></PaintBucket>
</h3> </h3>
<p className="mt-4 text-lg text-gray-600 dark:text-gray-300"> <p className="mt-4 text-lg text-gray-600 dark:text-gray-300">
With Zen Mods, you can customize your browsing experience With Zen Mods, you can customize your browsing experience to
to reflect your unique style and preferences. Choose from a wide reflect your unique style and preferences. Choose from a wide
array of Mods, colors, and layouts to make Zen truly your own, array of Mods, colors, and layouts to make Zen truly your own,
transforming your browser into a personalized digital space. transforming your browser into a personalized digital space.
</p> </p>

View file

@ -12,12 +12,14 @@ export function ModeToggle() {
setMounted(true); setMounted(true);
const savedTheme = localStorage.getItem("theme"); const savedTheme = localStorage.getItem("theme");
if (savedTheme) { if (savedTheme) {
setTheme(savedTheme); setTheme(savedTheme);
} else { } else {
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; const prefersDark = window.matchMedia(
setTheme(prefersDark ? "dark" : "light"); "(prefers-color-scheme: dark)",
).matches;
setTheme(prefersDark ? "dark" : "light");
} }
}, [setTheme]); }, [setTheme]);
const toggleTheme = () => { const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light"; const newTheme = theme === "light" ? "dark" : "light";

View file

@ -122,9 +122,9 @@ export function Navigation() {
<div className="border-grey fixed left-0 top-0 z-40 flex w-full items-center justify-center border-b bg-background p-2"> <div className="border-grey fixed left-0 top-0 z-40 flex w-full items-center justify-center border-b bg-background p-2">
<MobileNav /> <MobileNav />
<NavigationMenu> <NavigationMenu>
<NavigationMenuList className="hidden w-full py-3 sm:flex items-center justify-between gap-32"> <NavigationMenuList className="hidden w-full items-center justify-between gap-32 py-3 sm:flex">
<div> <div>
<NavigationMenuItem className="cursor-pointer flex items-center"> <NavigationMenuItem className="flex cursor-pointer items-center">
<NavigationMenuLink href="/"> <NavigationMenuLink href="/">
<Logo withText /> <Logo withText />
</NavigationMenuLink> </NavigationMenuLink>
@ -186,8 +186,8 @@ export function Navigation() {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
Ko-fi is a way to support us with a one-time donation and help Ko-fi is a way to support us with a one-time donation and
us keep the project alive. help us keep the project alive.
</ListItem2> </ListItem2>
</ul> </ul>
</NavigationMenuContent> </NavigationMenuContent>
@ -215,7 +215,10 @@ export function Navigation() {
<ModeToggle /> <ModeToggle />
</div> </div>
<div> <div>
<NavigationMenuLink href={`/release-notes/${latestRelease.version}`} className="text-[10px] bg-surface py-1 px-2 font-semibold rounded h-fit w-fit flex items-center hover:bg-accent hover:text-accent-foreground transition-colors"> <NavigationMenuLink
href={`/release-notes/${latestRelease.version}`}
className="flex h-fit w-fit items-center rounded bg-surface px-2 py-1 text-[10px] font-semibold transition-colors hover:bg-accent hover:text-accent-foreground"
>
v{latestRelease.version} v{latestRelease.version}
</NavigationMenuLink> </NavigationMenuLink>
</div> </div>

View file

@ -3,8 +3,13 @@ import { getThemeAuthorLink, ZenTheme } from "@/lib/mods";
import { TagIcon } from "lucide-react"; import { TagIcon } from "lucide-react";
import { Badge } from "./ui/badge"; import { Badge } from "./ui/badge";
import Link from "next/link"; import {
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "./ui/card"; Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "./ui/card";
export default function ThemeCard({ export default function ThemeCard({
theme, theme,
@ -15,10 +20,11 @@ export default function ThemeCard({
}) { }) {
const maxNameLen = 50; const maxNameLen = 50;
const maxDescLen = 100; const maxDescLen = 100;
const authorLink = getThemeAuthorLink(theme); const authorLink = getThemeAuthorLink(theme);
return ( return (
<Card className="select-none h-full flex-col justify-between rounded-xl border-2 border-[transparent] bg-surface transition-all duration-200 hover:border-[rgba(0,0,0,.5)] hover:shadow-lg dark:bg-[#121212] dark:hover:border-[#333]" <Card
className="h-full select-none flex-col justify-between rounded-xl border-2 border-[transparent] bg-surface transition-all duration-200 hover:border-[rgba(0,0,0,.5)] hover:shadow-lg dark:bg-[#121212] dark:hover:border-[#333]"
onMouseDown={(e) => { onMouseDown={(e) => {
// IMPORTANT NOTE: We do NOT use a Link component here because of how zen manages site injection. // IMPORTANT NOTE: We do NOT use a Link component here because of how zen manages site injection.
// please for the love of god, dont change this to a Link component. Please. // please for the love of god, dont change this to a Link component. Please.
@ -28,7 +34,8 @@ export default function ThemeCard({
if (e.button !== 0 && e.button !== 1) return; if (e.button !== 0 && e.button !== 1) return;
if (e.target instanceof HTMLAnchorElement) return; if (e.target instanceof HTMLAnchorElement) return;
window.open(`/mods/${theme.id}`, e.button === 1 ? "_blank" : "_self"); window.open(`/mods/${theme.id}`, e.button === 1 ? "_blank" : "_self");
}}> }}
>
<div className="relative m-2 mb-0 hidden aspect-[1.85/1] h-48 overflow-hidden rounded-xl border-2 border-[rgba(0,0,0,.5)] object-cover shadow dark:border-[#333] lg:block lg:h-auto"> <div className="relative m-2 mb-0 hidden aspect-[1.85/1] h-48 overflow-hidden rounded-xl border-2 border-[rgba(0,0,0,.5)] object-cover shadow dark:border-[#333] lg:block lg:h-auto">
<img <img
src={theme.image} src={theme.image}
@ -64,31 +71,31 @@ export default function ThemeCard({
</p> </p>
</CardContent> </CardContent>
<CardFooter className="mt-2 flex"> <CardFooter className="mt-2 flex">
{theme.homepage && ( {theme.homepage && (
<a <a
href={theme.homepage} href={theme.homepage}
className="text-md text-blue-500" className="text-md text-blue-500"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
Homepage Homepage
</a> </a>
)} )}
{theme.homepage && authorLink && ( {theme.homepage && authorLink && (
<span className="text-md mx-2 text-muted-foreground">·</span> <span className="text-md mx-2 text-muted-foreground">·</span>
)} )}
{authorLink && ( {authorLink && (
<a <a
href={authorLink} href={authorLink}
className="text-md text-blue-500" className="text-md text-blue-500"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
Author Author
</a> </a>
)} )}
</CardFooter> </CardFooter>
</div> </div>
</Card> </Card>

View file

@ -19,7 +19,7 @@ export default async function ThemePage({ themeID }: { themeID: string }) {
return ( return (
<div className="relative mx-auto mt-24 flex flex-col items-start lg:mt-56 lg:flex-row"> <div className="relative mx-auto mt-24 flex flex-col items-start lg:mt-56 lg:flex-row">
<div className="w-md relative mx-auto mr-5 flex h-full w-full flex-col rounded-lg border bg-surface p-5 shadow md:mx-0 md:max-w-sm lg:sticky lg:top-0"> <div className="w-md relative mx-auto mr-5 flex h-full w-full flex-col rounded-lg border bg-surface p-5 shadow md:mx-0 md:max-w-sm lg:sticky lg:top-0">
<div className="flex justify-between w-full items-center mb-2"> <div className="mb-2 flex w-full items-center justify-between">
<a <a
className="flex cursor-pointer items-center opacity-70" className="flex cursor-pointer items-center opacity-70"
href="/mods" href="/mods"
@ -67,11 +67,9 @@ export default async function ThemePage({ themeID }: { themeID: string }) {
</a> </a>
</p> </p>
<hr className="my-4" /> <hr className="my-4" />
<div className="text-sm text-muted-foreground flex justify-between"> <div className="flex justify-between text-sm text-muted-foreground">
<div> <div>
<span className="opacity-70"> <span className="opacity-70">Theme by </span>
Theme by{" "}
</span>
<a <a
href={getThemeAuthorLink(theme)} href={getThemeAuthorLink(theme)}
className="text-md mt-4 text-blue-500" className="text-md mt-4 text-blue-500"
@ -81,9 +79,7 @@ export default async function ThemePage({ themeID }: { themeID: string }) {
{theme.author} {theme.author}
</a> </a>
</div> </div>
<div className="opacity-70"> <div className="opacity-70">v{theme.version}</div>
v{theme.version}
</div>
</div> </div>
</div> </div>
<div className="flex w-full max-w-xl flex-col px-5 lg:min-h-[calc(100vh/2-2rem)] lg:min-w-96 lg:pl-10"> <div className="flex w-full max-w-xl flex-col px-5 lg:min-h-[calc(100vh/2-2rem)] lg:min-w-96 lg:pl-10">
@ -92,7 +88,7 @@ export default async function ThemePage({ themeID }: { themeID: string }) {
alt={theme.name} alt={theme.name}
className="w-full rounded-2xl border-2 object-cover shadow" className="w-full rounded-2xl border-2 object-cover shadow"
/> />
<div id="policy" className="w-full !mt-0"> <div id="policy" className="!mt-0 w-full">
{readme === null ? ( {readme === null ? (
<LoaderCircleIcon className="mx-auto h-12 w-12 animate-spin" /> <LoaderCircleIcon className="mx-auto h-12 w-12 animate-spin" />
) : ( ) : (

View file

@ -1,76 +1,79 @@
import * as React from "react" import * as React from "react";
import { ny } from "@/lib/utils" import { ny } from "@/lib/utils";
const Card = React.forwardRef< const Card = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={ny( className={ny(
"rounded-xl border bg-card text-card-foreground shadow", "rounded-xl border bg-card text-card-foreground shadow",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Card.displayName = "Card" Card.displayName = "Card";
const CardHeader = React.forwardRef< const CardHeader = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={ny("flex flex-col space-y-1.5", className)} className={ny("flex flex-col space-y-1.5", className)}
{...props} {...props}
/> />
)) ));
CardHeader.displayName = "CardHeader" CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement> React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<h3 <h3
ref={ref} ref={ref}
className={ny("font-semibold leading-none tracking-tight", className)} className={ny("font-semibold leading-none tracking-tight", className)}
{...props} {...props}
/> />
)) ));
CardTitle.displayName = "CardTitle" CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<p <p
ref={ref} ref={ref}
className={ny("text-sm text-muted-foreground", className)} className={ny("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
CardDescription.displayName = "CardDescription" CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef< const CardContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={ny("", className)} {...props} /> <div ref={ref} className={ny("", className)} {...props} />
)) ));
CardContent.displayName = "CardContent" CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef< const CardFooter = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div ref={ref} className={ny("flex items-center", className)} {...props} />
ref={ref} ));
className={ny("flex items-center", className)} CardFooter.displayName = "CardFooter";
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View file

@ -1,28 +1,28 @@
import React from 'react' import React from "react";
import { ny } from '@/lib/utils' import { ny } from "@/lib/utils";
interface RainbowButtonProps interface RainbowButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {} extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
export function RainbowButton({ children, ...props }: RainbowButtonProps) { export function RainbowButton({ children, ...props }: RainbowButtonProps) {
return ( return (
<button <button
className={ny( className={ny(
'focus-visible:ring-ring animate-rainbow text-primary-foreground group relative inline-flex h-11 cursor-pointer items-center justify-center rounded-xl border-0 bg-[length:200%] px-4 py-2 font-medium transition-colors [background-clip:padding-box,border-box,border-box] [background-origin:border-box] [border:calc(0.08*1rem)_solid_transparent] focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50', "group relative inline-flex h-11 animate-rainbow cursor-pointer items-center justify-center rounded-xl border-0 bg-[length:200%] px-4 py-2 font-medium text-primary-foreground transition-colors [background-clip:padding-box,border-box,border-box] [background-origin:border-box] [border:calc(0.08*1rem)_solid_transparent] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
// before styles // before styles
'before:animate-rainbow before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:bg-[linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))] before:bg-[length:200%] before:[filter:blur(calc(0.8*1rem))]', "before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))] before:bg-[length:200%] before:[filter:blur(calc(0.8*1rem))]",
// light mode colors // light mode colors
'bg-[linear-gradient(#121213,#121213),linear-gradient(#121213_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))]', "bg-[linear-gradient(#121213,#121213),linear-gradient(#121213_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))]",
// dark mode colors // dark mode colors
' dark:bg-[linear-gradient(#fff,#fff),linear-gradient(#fff_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))]', "dark:bg-[linear-gradient(#fff,#fff),linear-gradient(#fff_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))]",
)} )}
{...props} {...props}
> >
{children} {children}
</button> </button>
) );
} }

View file

@ -16,9 +16,10 @@ export default function WelcomePage() {
Start using it by clicking on the sidebar icon or trying out the split Start using it by clicking on the sidebar icon or trying out the split
view feature! view feature!
</p> </p>
<p className="text-md w-2/5 mx-auto mt-12 opacity-70"> <p className="text-md mx-auto mt-12 w-2/5 opacity-70">
<InfoCircledIcon className="inline-block mr-4 size-5 text-yellow-500" /> <InfoCircledIcon className="mr-4 inline-block size-5 text-yellow-500" />
Zen Browser is still in development and may have bugs. This welcome page is under construction. Zen Browser is still in development and may have bugs. This welcome page
is under construction.
</p> </p>
</div> </div>
); );

View file

@ -38,7 +38,10 @@ function isValidDate(date: any): date is Date {
* @param assignFutureDate - Whether to assign a future date if parsing fails. * @param assignFutureDate - Whether to assign a future date if parsing fails.
* @returns A valid Date object. * @returns A valid Date object.
*/ */
function parseDate(dateString: string | undefined, assignFutureDate: boolean = false): Date { function parseDate(
dateString: string | undefined,
assignFutureDate: boolean = false,
): Date {
const date = new Date(dateString || ""); const date = new Date(dateString || "");
if (isValidDate(date)) { if (isValidDate(date)) {
return date; return date;
@ -83,7 +86,10 @@ export async function getAllThemes(): Promise<ZenTheme[]> {
homepage: theme.homepage, homepage: theme.homepage,
readme: theme.readme, readme: theme.readme,
preferences: theme.preferences, preferences: theme.preferences,
isColorTheme: typeof theme.isColorTheme === 'boolean' ? theme.isColorTheme : false, isColorTheme:
typeof theme.isColorTheme === "boolean"
? theme.isColorTheme
: false,
author: theme.author, author: theme.author,
version: theme.version, version: theme.version,
tags: uniqueTags, tags: uniqueTags,
@ -125,7 +131,7 @@ export function getThemesFromSearch(
query: string, query: string,
tags: string[], tags: string[],
sortBy: string, sortBy: string,
createdBefore?: Date createdBefore?: Date,
): ZenTheme[] { ): ZenTheme[] {
const normalizedQuery = query.toLowerCase(); const normalizedQuery = query.toLowerCase();
@ -166,7 +172,9 @@ export function getThemesFromSearch(
* @param id - The ID of the theme to retrieve. * @param id - The ID of the theme to retrieve.
* @returns A promise that resolves to the ZenTheme object or undefined if not found. * @returns A promise that resolves to the ZenTheme object or undefined if not found.
*/ */
export async function getThemeFromId(id: string): Promise<ZenTheme | undefined> { export async function getThemeFromId(
id: string,
): Promise<ZenTheme | undefined> {
const allThemes = await getAllThemes(); const allThemes = await getAllThemes();
return allThemes.find((theme) => theme.id === id); return allThemes.find((theme) => theme.id === id);
} }
@ -196,4 +204,4 @@ export async function getThemeMarkdown(theme: ZenTheme): Promise<string> {
*/ */
export function getThemeAuthorLink(theme: ZenTheme): string { export function getThemeAuthorLink(theme: ZenTheme): string {
return `https://github.com/${theme.author}`; return `https://github.com/${theme.author}`;
} }

View file

@ -285,8 +285,7 @@ export const releaseNotes: ReleaseNote[] = [
issue: 76, issue: 76,
}, },
{ {
description: description: "Added more contrast to web context menus on light mods.",
"Added more contrast to web context menus on light mods.",
issue: 88, issue: 88,
}, },
{ {
@ -745,8 +744,7 @@ export const releaseNotes: ReleaseNote[] = [
issue: 1168, issue: 1168,
}, },
{ {
description: description: "Theme Store settings page doesn't display installed mods",
"Theme Store settings page doesn't display installed mods",
issue: 1125, issue: 1125,
}, },
{ {
@ -1104,51 +1102,54 @@ export const releaseNotes: ReleaseNote[] = [
}, },
], ],
}, },
{ {
version: "1.0.1-a.8", version: "1.0.1-a.8",
date: "10/10/2024", date: "10/10/2024",
image: true, image: true,
workflowId: 11279059812, workflowId: 11279059812,
extra: "This release brings Zen to Firefox v131.0.2, which patches a significant security vulnerability.\n\nThis update improves the split view and pinned tabs features.\nWe have also released Zen Twilight; automated unstable builds where you can test out the latest features!", extra:
features: [ "This release brings Zen to Firefox v131.0.2, which patches a significant security vulnerability.\n\nThis update improves the split view and pinned tabs features.\nWe have also released Zen Twilight; automated unstable builds where you can test out the latest features!",
"Updated to the latest stable version of Firefox (131.0.2)", features: [
"Added floating compact mode", "Updated to the latest stable version of Firefox (131.0.2)",
"Allow moving split view tabs with drag and drop functionality", "Added floating compact mode",
"Added option to reset pinned tabs to original state on close", "Allow moving split view tabs with drag and drop functionality",
"Added support for syncing workspaces", "Added option to reset pinned tabs to original state on close",
"Allow opening tabs by middle clicking the tab sidebar", "Added support for syncing workspaces",
], "Allow opening tabs by middle clicking the tab sidebar",
fixes: [ ],
{ fixes: [
description: "Fixed tab sidebar flickering when on the right", {
}, description: "Fixed tab sidebar flickering when on the right",
{ },
description: "Fixed performance issue when scrolling", {
}, description: "Fixed performance issue when scrolling",
{ },
description: "Fixed buffering issues on YouTube" {
}, description: "Fixed buffering issues on YouTube",
{ },
description: "Fixed Zen Mod settings page crashing when a mod ceases to exist" {
}, description:
{ "Fixed Zen Mod settings page crashing when a mod ceases to exist",
description: "Fixed extension menu breaking compact mode when held open", },
issue: 1925 {
}, description:
{ "Fixed extension menu breaking compact mode when held open",
description: "Fixed internal keyboard shortcuts for macOS", issue: 1925,
issue: 1629 },
}, {
{ description: "Fixed internal keyboard shortcuts for macOS",
description: "Fixed display issues with certain keyboard layouts", issue: 1629,
issue: 1930 },
}, {
{ description: "Fixed display issues with certain keyboard layouts",
description: "Applied patches to fix CVE-2024-9680", issue: 1930,
issue: 1993 },
}, {
] description: "Applied patches to fix CVE-2024-9680",
} issue: 1993,
},
],
},
].reverse(); ].reverse();
export function releaseNoteIsAlpha(note: ReleaseNote) { export function releaseNoteIsAlpha(note: ReleaseNote) {

View file

@ -22,9 +22,9 @@ const config = {
fontFamily: { fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans], sans: ["var(--font-sans)", ...fontFamily.sans],
}, },
screens: { screens: {
"3xl": "2200px", "3xl": "2200px",
}, },
colors: { colors: {
border: "hsl(var(--border))", border: "hsl(var(--border))",
input: "hsl(var(--input))", input: "hsl(var(--input))",
@ -32,11 +32,11 @@ const config = {
background: "hsl(var(--background))", background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))", foreground: "hsl(var(--foreground))",
surface: "var(--surface)", surface: "var(--surface)",
"color-1": "hsl(var(--color-1))", "color-1": "hsl(var(--color-1))",
"color-2": "hsl(var(--color-2))", "color-2": "hsl(var(--color-2))",
"color-3": "hsl(var(--color-3))", "color-3": "hsl(var(--color-3))",
"color-4": "hsl(var(--color-4))", "color-4": "hsl(var(--color-4))",
"color-5": "hsl(var(--color-5))", "color-5": "hsl(var(--color-5))",
primary: { primary: {
DEFAULT: "hsl(var(--primary))", DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))", foreground: "hsl(var(--primary-foreground))",
@ -72,10 +72,10 @@ const config = {
sm: "calc(var(--radius) - 4px)", sm: "calc(var(--radius) - 4px)",
}, },
keyframes: { keyframes: {
rainbow: { rainbow: {
"0%": { "background-position": "0%" }, "0%": { "background-position": "0%" },
"100%": { "background-position": "200%" }, "100%": { "background-position": "200%" },
}, },
orbit: { orbit: {
"0%": { "0%": {
transform: transform: