mirror of
https://github.com/zen-browser/www.git
synced 2025-07-07 17:05:32 +02:00
Refactor next.config.js and form.tsx
This commit is contained in:
parent
00c7ce4c56
commit
5ec0fcbb56
31 changed files with 388 additions and 336 deletions
|
@ -41,4 +41,4 @@ const nextConfig = (phase, { defaultConfig }) => {
|
|||
};
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
module.exports = nextConfig;
|
||||
|
|
|
@ -77,49 +77,60 @@ function formatReleaseNote(releaseNote: ReleaseNote) {
|
|||
Thanks everyone for your feedback! ❤️
|
||||
</p>`;
|
||||
|
||||
if (releaseNote.image) {
|
||||
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
|
||||
if (releaseNote.image) {
|
||||
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
|
||||
alt="Release Image for version ${releaseNote.version}"
|
||||
style="max-width: 30em; width: 100%; border-radius: 0.5rem;"
|
||||
/>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (releaseNote.extra) {
|
||||
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`;
|
||||
}
|
||||
|
||||
content += addReleaseNoteSection("⚠️ Breaking changes", releaseNote.breakingChanges);
|
||||
content += addReleaseNoteSection("✓ Fixes", releaseNote.fixes?.map(fixToReleaseNote));
|
||||
content += addReleaseNoteSection("🖌 Theme Changes", releaseNote.themeChanges)
|
||||
content += addReleaseNoteSection("⭐ Features", releaseNote.features);
|
||||
content += addReleaseNoteSection(
|
||||
"⚠️ Breaking changes",
|
||||
releaseNote.breakingChanges,
|
||||
);
|
||||
content += addReleaseNoteSection(
|
||||
"✓ Fixes",
|
||||
releaseNote.fixes?.map(fixToReleaseNote),
|
||||
);
|
||||
content += addReleaseNoteSection(
|
||||
"🖌 Theme Changes",
|
||||
releaseNote.themeChanges,
|
||||
);
|
||||
content += addReleaseNoteSection("⭐ Features", releaseNote.features);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
function addReleaseNoteSection(title: string, items?: string[]): string {
|
||||
if (!items) {
|
||||
return "";
|
||||
}
|
||||
if (!items) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let content = `<h2>${title}</h2>`;
|
||||
content += `<ul>`;
|
||||
for (const item of items) {
|
||||
if (item && item.length > 0) {
|
||||
content += `<li>${item}</li>`;
|
||||
}
|
||||
}
|
||||
content += `</ul>`;
|
||||
return content;
|
||||
let content = `<h2>${title}</h2>`;
|
||||
content += `<ul>`;
|
||||
for (const item of items) {
|
||||
if (item && item.length > 0) {
|
||||
content += `<li>${item}</li>`;
|
||||
}
|
||||
}
|
||||
content += `</ul>`;
|
||||
return content;
|
||||
}
|
||||
|
||||
function fixToReleaseNote(fix?: Exclude<ReleaseNote['fixes'], undefined>[number]) {
|
||||
if (!fix || !fix.description || fix.description.length === 0) {
|
||||
return "";
|
||||
}
|
||||
function fixToReleaseNote(
|
||||
fix?: Exclude<ReleaseNote["fixes"], undefined>[number],
|
||||
) {
|
||||
if (!fix || !fix.description || fix.description.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let note = fix.description;
|
||||
if (fix.issue) {
|
||||
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`;
|
||||
}
|
||||
return note;
|
||||
let note = fix.description;
|
||||
if (fix.issue) {
|
||||
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`;
|
||||
}
|
||||
return note;
|
||||
}
|
||||
|
|
|
@ -49,11 +49,11 @@
|
|||
--color-three: #ff2e63;
|
||||
*/
|
||||
|
||||
--color-1: 0 100% 63%;
|
||||
--color-2: 270 100% 63%;
|
||||
--color-3: 210 100% 63%;
|
||||
--color-4: 195 100% 63%;
|
||||
--color-5: 90 100% 63%;
|
||||
--color-1: 0 100% 63%;
|
||||
--color-2: 270 100% 63%;
|
||||
--color-3: 210 100% 63%;
|
||||
--color-4: 195 100% 63%;
|
||||
--color-5: 90 100% 63%;
|
||||
}
|
||||
|
||||
[data-theme="dark"],
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
}
|
||||
|
||||
code {
|
||||
font-size: .8em;
|
||||
font-size: 0.8em;
|
||||
background-color: var(--surface);
|
||||
padding: 0.2em 0.4em;
|
||||
border: 1px solid light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.2));
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function ThemesPage() {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
router.replace('/mods');
|
||||
}, [router]);
|
||||
useEffect(() => {
|
||||
router.replace("/mods");
|
||||
}, [router]);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import Image from "next/image";
|
||||
|
||||
|
||||
const inDev = process.env.NODE_ENV === "development";
|
||||
function imageLoader({ src }: { src: string }) {
|
||||
// Load locally if we are in development
|
||||
|
|
|
@ -31,7 +31,7 @@ export const AlertModal = ({
|
|||
</div>
|
||||
</AlertDialog.Title>
|
||||
<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
|
||||
version.
|
||||
</p>
|
||||
|
|
|
@ -75,7 +75,12 @@ export function BrandingAssets() {
|
|||
CC BY-SA 4.0
|
||||
</a>
|
||||
. 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)
|
||||
</a>{" "}
|
||||
for the assets.
|
||||
|
|
|
@ -6,34 +6,46 @@ import { Slider } from "./ui/slider";
|
|||
import React from "react";
|
||||
|
||||
export default function BrowserComplexityExample() {
|
||||
const [selectedImage, setSelectedImage] = React.useState([1]);
|
||||
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">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-center">How much browser do you want?</h1>
|
||||
<p className="mt-4 text-center text-md mx-auto w-2/3 text-muted-foreground">
|
||||
Zen is designed to be simple and easy to use. We believe that the best software is
|
||||
the one that you don't notice. However, we can assure you that if you want customization, we have you covered
|
||||
</p>
|
||||
<div className="w-64 mb-6 mt-12 flex gap-4">
|
||||
<span className="opacity-90">🌱</span>
|
||||
<Slider step={1} max={3} showSteps="half" value={selectedImage} onValueChange={setSelectedImage} />
|
||||
<span className="opacity-90">🌳</span>
|
||||
</div>
|
||||
<div className="mx-auto md:mb-36 flex justify-center">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<CachedImage
|
||||
width={1620}
|
||||
height={900}
|
||||
priority
|
||||
key={i}
|
||||
src={`www/public/browsers/image${i + 1}.png`}
|
||||
alt="Zen Browser"
|
||||
className={ny("rounded-md object-cover shadow object-right mx-12 w-full", selectedImage[0] === i
|
||||
? "" //"animate-fade-up duration-500 !opacity-100"
|
||||
: "hidden")}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
const [selectedImage, setSelectedImage] = React.useState([1]);
|
||||
return (
|
||||
<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-center text-4xl font-bold md:text-5xl">
|
||||
How much browser do you want?
|
||||
</h1>
|
||||
<p className="text-md mx-auto mt-4 w-2/3 text-center text-muted-foreground">
|
||||
Zen is designed to be simple and easy to use. We believe that the best
|
||||
software is the one that you don't notice. However, we can assure you
|
||||
that if you want customization, we have you covered
|
||||
</p>
|
||||
<div className="mb-6 mt-12 flex w-64 gap-4">
|
||||
<span className="opacity-90">🌱</span>
|
||||
<Slider
|
||||
step={1}
|
||||
max={3}
|
||||
showSteps="half"
|
||||
value={selectedImage}
|
||||
onValueChange={setSelectedImage}
|
||||
/>
|
||||
<span className="opacity-90">🌳</span>
|
||||
</div>
|
||||
<div className="mx-auto flex justify-center md:mb-36">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<CachedImage
|
||||
width={1620}
|
||||
height={900}
|
||||
priority
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css";
|
||||
|
||||
export default function CoolHeaderText() {
|
||||
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">
|
||||
<h1 className={s.title}>
|
||||
Stay focused, browse faster with Zen
|
||||
</h1>
|
||||
<h1 className={s.title}>Stay focused, browse faster with Zen</h1>
|
||||
</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
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
@keyframes hueShift {
|
||||
0% {
|
||||
filter: hue-rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
filter: hue-rotate(170deg);
|
||||
}
|
||||
100% {
|
||||
filter: hue-rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
background-clip: text;
|
||||
background-image: linear-gradient(90deg, #0077e7, #01d8d1);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
filter: hue-rotate(0deg);
|
||||
animation: hueShift 10s infinite linear 1s;
|
||||
padding-bottom: 8px;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
0% {
|
||||
filter: hue-rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
filter: hue-rotate(170deg);
|
||||
}
|
||||
100% {
|
||||
filter: hue-rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
background-clip: text;
|
||||
background-image: linear-gradient(90deg, #0077e7, #01d8d1);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
filter: hue-rotate(0deg);
|
||||
animation: hueShift 10s infinite linear 1s;
|
||||
padding-bottom: 8px;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Button } from "./ui/button";
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import { Architecture } from "@/components/download/types";
|
||||
import { ny } from "@/lib/utils";
|
||||
|
|
|
@ -53,4 +53,4 @@ export const FieldDescription = styled.div`
|
|||
font-size: 1rem;
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
`;
|
||||
`;
|
||||
|
|
|
@ -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-2xl font-semibold">{label}</h1>
|
||||
<p className="mx-auto text-center text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
<p className="mx-auto text-center text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import { Platforms } from "@/components/download/types";
|
||||
import { ny } from "@/lib/utils";
|
||||
|
@ -10,7 +10,7 @@ interface PlatformCardProps {
|
|||
}
|
||||
|
||||
const PLATFORMS_DATA: Record<
|
||||
Exclude<Platforms, 'Unsupported'>,
|
||||
Exclude<Platforms, "Unsupported">,
|
||||
{ label: string; icon: string; borderColor: string }
|
||||
> = {
|
||||
Windows: {
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import confetti from "canvas-confetti";
|
||||
|
||||
export const throwConfetti = () => {
|
||||
const end = Date.now() + 3 * 1000; // 3 seconds
|
||||
const colors = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"];
|
||||
const frame = () => {
|
||||
if (Date.now() > end) return;
|
||||
const end = Date.now() + 3 * 1000; // 3 seconds
|
||||
const colors = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"];
|
||||
const frame = () => {
|
||||
if (Date.now() > end) return;
|
||||
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 60,
|
||||
spread: 55,
|
||||
startVelocity: 60,
|
||||
origin: { x: 0, y: 0.5 },
|
||||
colors,
|
||||
});
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 120,
|
||||
spread: 55,
|
||||
startVelocity: 60,
|
||||
origin: { x: 1, y: 0.5 },
|
||||
colors,
|
||||
});
|
||||
requestAnimationFrame(frame);
|
||||
};
|
||||
frame();
|
||||
};
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 60,
|
||||
spread: 55,
|
||||
startVelocity: 60,
|
||||
origin: { x: 0, y: 0.5 },
|
||||
colors,
|
||||
});
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 120,
|
||||
spread: 55,
|
||||
startVelocity: 60,
|
||||
origin: { x: 1, y: 0.5 },
|
||||
colors,
|
||||
});
|
||||
requestAnimationFrame(frame);
|
||||
};
|
||||
frame();
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export type Platforms = "Windows" | "MacOS" | "Linux" | "Unsupported";
|
||||
|
||||
export type Architecture = "specific" | "generic";
|
||||
export type Architecture = "specific" | "generic";
|
||||
|
|
|
@ -10,16 +10,16 @@ interface WindowsInstallerProps {
|
|||
flowIndex: number;
|
||||
platform: Platforms | null;
|
||||
selectedArchitecture: string;
|
||||
setSelectedWindowsDownloadType: (value: string) => void;
|
||||
selectedWindowsDownloadType: string;
|
||||
setSelectedWindowsDownloadType: (value: string) => void;
|
||||
selectedWindowsDownloadType: string;
|
||||
}
|
||||
|
||||
export const WindowsInstaller = ({
|
||||
flowIndex,
|
||||
platform,
|
||||
selectedArchitecture,
|
||||
setSelectedWindowsDownloadType,
|
||||
selectedWindowsDownloadType,
|
||||
setSelectedWindowsDownloadType,
|
||||
selectedWindowsDownloadType,
|
||||
}: WindowsInstallerProps) => {
|
||||
return (
|
||||
<FormField
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
'use client'
|
||||
"use client";
|
||||
|
||||
import CachedImage from "@/components/CachedImage";
|
||||
import Logo from "@/components/logo";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import { ExternalLinkIcon, EyeClosedIcon, LockClosedIcon, QuestionMarkCircledIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
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 Link from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
export const FAQ = () => {
|
||||
const [feature, setFeature] = useState("");
|
||||
|
||||
const [feature, setFeature] = useState("");
|
||||
|
||||
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="relative flex w-full flex-col justify-center p-5 lg:w-1/2 lg:p-12">
|
||||
|
|
|
@ -48,8 +48,8 @@ export default function Features() {
|
|||
<PaintBucket className="inline h-10 w-10"></PaintBucket>
|
||||
</h3>
|
||||
<p className="mt-4 text-lg text-gray-600 dark:text-gray-300">
|
||||
With Zen Mods, you can customize your browsing experience
|
||||
to reflect your unique style and preferences. Choose from a wide
|
||||
With Zen Mods, you can customize your browsing experience to
|
||||
reflect your unique style and preferences. Choose from a wide
|
||||
array of Mods, colors, and layouts to make Zen truly your own,
|
||||
transforming your browser into a personalized digital space.
|
||||
</p>
|
||||
|
|
|
@ -12,12 +12,14 @@ export function ModeToggle() {
|
|||
setMounted(true);
|
||||
const savedTheme = localStorage.getItem("theme");
|
||||
if (savedTheme) {
|
||||
setTheme(savedTheme);
|
||||
setTheme(savedTheme);
|
||||
} else {
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
setTheme(prefersDark ? "dark" : "light");
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
).matches;
|
||||
setTheme(prefersDark ? "dark" : "light");
|
||||
}
|
||||
}, [setTheme]);
|
||||
}, [setTheme]);
|
||||
|
||||
const toggleTheme = () => {
|
||||
const newTheme = theme === "light" ? "dark" : "light";
|
||||
|
|
|
@ -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">
|
||||
<MobileNav />
|
||||
<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>
|
||||
<NavigationMenuItem className="cursor-pointer flex items-center">
|
||||
<NavigationMenuItem className="flex cursor-pointer items-center">
|
||||
<NavigationMenuLink href="/">
|
||||
<Logo withText />
|
||||
</NavigationMenuLink>
|
||||
|
@ -186,8 +186,8 @@ export function Navigation() {
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Ko-fi is a way to support us with a one-time donation and help
|
||||
us keep the project alive.
|
||||
Ko-fi is a way to support us with a one-time donation and
|
||||
help us keep the project alive.
|
||||
</ListItem2>
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
|
@ -215,7 +215,10 @@ export function Navigation() {
|
|||
<ModeToggle />
|
||||
</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}
|
||||
</NavigationMenuLink>
|
||||
</div>
|
||||
|
|
|
@ -3,8 +3,13 @@ import { getThemeAuthorLink, ZenTheme } from "@/lib/mods";
|
|||
import { TagIcon } from "lucide-react";
|
||||
|
||||
import { Badge } from "./ui/badge";
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "./ui/card";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "./ui/card";
|
||||
|
||||
export default function ThemeCard({
|
||||
theme,
|
||||
|
@ -15,10 +20,11 @@ export default function ThemeCard({
|
|||
}) {
|
||||
const maxNameLen = 50;
|
||||
const maxDescLen = 100;
|
||||
const authorLink = getThemeAuthorLink(theme);
|
||||
const authorLink = getThemeAuthorLink(theme);
|
||||
|
||||
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) => {
|
||||
// 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.
|
||||
|
@ -28,7 +34,8 @@ export default function ThemeCard({
|
|||
if (e.button !== 0 && e.button !== 1) return;
|
||||
if (e.target instanceof HTMLAnchorElement) return;
|
||||
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">
|
||||
<img
|
||||
src={theme.image}
|
||||
|
@ -64,31 +71,31 @@ export default function ThemeCard({
|
|||
</p>
|
||||
</CardContent>
|
||||
<CardFooter className="mt-2 flex">
|
||||
{theme.homepage && (
|
||||
<a
|
||||
href={theme.homepage}
|
||||
className="text-md text-blue-500"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Homepage
|
||||
</a>
|
||||
)}
|
||||
{theme.homepage && authorLink && (
|
||||
<span className="text-md mx-2 text-muted-foreground">·</span>
|
||||
)}
|
||||
{authorLink && (
|
||||
<a
|
||||
href={authorLink}
|
||||
className="text-md text-blue-500"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Author
|
||||
</a>
|
||||
)}
|
||||
{theme.homepage && (
|
||||
<a
|
||||
href={theme.homepage}
|
||||
className="text-md text-blue-500"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Homepage
|
||||
</a>
|
||||
)}
|
||||
{theme.homepage && authorLink && (
|
||||
<span className="text-md mx-2 text-muted-foreground">·</span>
|
||||
)}
|
||||
{authorLink && (
|
||||
<a
|
||||
href={authorLink}
|
||||
className="text-md text-blue-500"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Author
|
||||
</a>
|
||||
)}
|
||||
</CardFooter>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
@ -19,7 +19,7 @@ export default async function ThemePage({ themeID }: { themeID: string }) {
|
|||
return (
|
||||
<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="flex justify-between w-full items-center mb-2">
|
||||
<div className="mb-2 flex w-full items-center justify-between">
|
||||
<a
|
||||
className="flex cursor-pointer items-center opacity-70"
|
||||
href="/mods"
|
||||
|
@ -67,11 +67,9 @@ export default async function ThemePage({ themeID }: { themeID: string }) {
|
|||
</a>
|
||||
</p>
|
||||
<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>
|
||||
<span className="opacity-70">
|
||||
Theme by{" "}
|
||||
</span>
|
||||
<span className="opacity-70">Theme by </span>
|
||||
<a
|
||||
href={getThemeAuthorLink(theme)}
|
||||
className="text-md mt-4 text-blue-500"
|
||||
|
@ -81,9 +79,7 @@ export default async function ThemePage({ themeID }: { themeID: string }) {
|
|||
{theme.author}
|
||||
</a>
|
||||
</div>
|
||||
<div className="opacity-70">
|
||||
v{theme.version}
|
||||
</div>
|
||||
<div className="opacity-70">v{theme.version}</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">
|
||||
|
@ -92,7 +88,7 @@ export default async function ThemePage({ themeID }: { themeID: string }) {
|
|||
alt={theme.name}
|
||||
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 ? (
|
||||
<LoaderCircleIcon className="mx-auto h-12 w-12 animate-spin" />
|
||||
) : (
|
||||
|
|
|
@ -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<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={ny(
|
||||
"rounded-xl border bg-card text-card-foreground shadow",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
<div
|
||||
ref={ref}
|
||||
className={ny(
|
||||
"rounded-xl border bg-card text-card-foreground shadow",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Card.displayName = "Card";
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={ny("flex flex-col space-y-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
<div
|
||||
ref={ref}
|
||||
className={ny("flex flex-col space-y-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardHeader.displayName = "CardHeader";
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={ny("font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
<h3
|
||||
ref={ref}
|
||||
className={ny("font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardTitle.displayName = "CardTitle";
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={ny("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
<p
|
||||
ref={ref}
|
||||
className={ny("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardDescription.displayName = "CardDescription";
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={ny("", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
<div ref={ref} className={ny("", className)} {...props} />
|
||||
));
|
||||
CardContent.displayName = "CardContent";
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={ny("flex items-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
<div ref={ref} className={ny("flex items-center", className)} {...props} />
|
||||
));
|
||||
CardFooter.displayName = "CardFooter";
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
};
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import React from 'react'
|
||||
import React from "react";
|
||||
|
||||
import { ny } from '@/lib/utils'
|
||||
import { ny } from "@/lib/utils";
|
||||
|
||||
interface RainbowButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||
|
||||
export function RainbowButton({ children, ...props }: RainbowButtonProps) {
|
||||
return (
|
||||
<button
|
||||
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',
|
||||
return (
|
||||
<button
|
||||
className={ny(
|
||||
"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: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 styles
|
||||
"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
|
||||
'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)))]',
|
||||
// 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)))]",
|
||||
|
||||
// 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)))]',
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
// 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)))]",
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,9 +16,10 @@ export default function WelcomePage() {
|
|||
Start using it by clicking on the sidebar icon or trying out the split
|
||||
view feature!
|
||||
</p>
|
||||
<p className="text-md w-2/5 mx-auto mt-12 opacity-70">
|
||||
<InfoCircledIcon className="inline-block mr-4 size-5 text-yellow-500" />
|
||||
Zen Browser is still in development and may have bugs. This welcome page is under construction.
|
||||
<p className="text-md mx-auto mt-12 w-2/5 opacity-70">
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -38,7 +38,10 @@ function isValidDate(date: any): date is Date {
|
|||
* @param assignFutureDate - Whether to assign a future date if parsing fails.
|
||||
* @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 || "");
|
||||
if (isValidDate(date)) {
|
||||
return date;
|
||||
|
@ -83,7 +86,10 @@ export async function getAllThemes(): Promise<ZenTheme[]> {
|
|||
homepage: theme.homepage,
|
||||
readme: theme.readme,
|
||||
preferences: theme.preferences,
|
||||
isColorTheme: typeof theme.isColorTheme === 'boolean' ? theme.isColorTheme : false,
|
||||
isColorTheme:
|
||||
typeof theme.isColorTheme === "boolean"
|
||||
? theme.isColorTheme
|
||||
: false,
|
||||
author: theme.author,
|
||||
version: theme.version,
|
||||
tags: uniqueTags,
|
||||
|
@ -125,7 +131,7 @@ export function getThemesFromSearch(
|
|||
query: string,
|
||||
tags: string[],
|
||||
sortBy: string,
|
||||
createdBefore?: Date
|
||||
createdBefore?: Date,
|
||||
): ZenTheme[] {
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
|
@ -166,7 +172,9 @@ export function getThemesFromSearch(
|
|||
* @param id - The ID of the theme to retrieve.
|
||||
* @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();
|
||||
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 {
|
||||
return `https://github.com/${theme.author}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -285,8 +285,7 @@ export const releaseNotes: ReleaseNote[] = [
|
|||
issue: 76,
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Added more contrast to web context menus on light mods.",
|
||||
description: "Added more contrast to web context menus on light mods.",
|
||||
issue: 88,
|
||||
},
|
||||
{
|
||||
|
@ -745,8 +744,7 @@ export const releaseNotes: ReleaseNote[] = [
|
|||
issue: 1168,
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Theme Store settings page doesn't display installed mods",
|
||||
description: "Theme Store settings page doesn't display installed mods",
|
||||
issue: 1125,
|
||||
},
|
||||
{
|
||||
|
@ -1104,51 +1102,54 @@ export const releaseNotes: ReleaseNote[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
{
|
||||
version: "1.0.1-a.8",
|
||||
date: "10/10/2024",
|
||||
image: true,
|
||||
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!",
|
||||
features: [
|
||||
"Updated to the latest stable version of Firefox (131.0.2)",
|
||||
"Added floating compact mode",
|
||||
"Allow moving split view tabs with drag and drop functionality",
|
||||
"Added option to reset pinned tabs to original state on close",
|
||||
"Added support for syncing workspaces",
|
||||
"Allow opening tabs by middle clicking the tab sidebar",
|
||||
],
|
||||
fixes: [
|
||||
{
|
||||
description: "Fixed tab sidebar flickering when on the right",
|
||||
},
|
||||
{
|
||||
description: "Fixed performance issue when scrolling",
|
||||
},
|
||||
{
|
||||
description: "Fixed buffering issues on YouTube"
|
||||
},
|
||||
{
|
||||
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 internal keyboard shortcuts for macOS",
|
||||
issue: 1629
|
||||
},
|
||||
{
|
||||
description: "Fixed display issues with certain keyboard layouts",
|
||||
issue: 1930
|
||||
},
|
||||
{
|
||||
description: "Applied patches to fix CVE-2024-9680",
|
||||
issue: 1993
|
||||
},
|
||||
]
|
||||
}
|
||||
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!",
|
||||
features: [
|
||||
"Updated to the latest stable version of Firefox (131.0.2)",
|
||||
"Added floating compact mode",
|
||||
"Allow moving split view tabs with drag and drop functionality",
|
||||
"Added option to reset pinned tabs to original state on close",
|
||||
"Added support for syncing workspaces",
|
||||
"Allow opening tabs by middle clicking the tab sidebar",
|
||||
],
|
||||
fixes: [
|
||||
{
|
||||
description: "Fixed tab sidebar flickering when on the right",
|
||||
},
|
||||
{
|
||||
description: "Fixed performance issue when scrolling",
|
||||
},
|
||||
{
|
||||
description: "Fixed buffering issues on YouTube",
|
||||
},
|
||||
{
|
||||
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 internal keyboard shortcuts for macOS",
|
||||
issue: 1629,
|
||||
},
|
||||
{
|
||||
description: "Fixed display issues with certain keyboard layouts",
|
||||
issue: 1930,
|
||||
},
|
||||
{
|
||||
description: "Applied patches to fix CVE-2024-9680",
|
||||
issue: 1993,
|
||||
},
|
||||
],
|
||||
},
|
||||
].reverse();
|
||||
|
||||
export function releaseNoteIsAlpha(note: ReleaseNote) {
|
||||
|
|
|
@ -22,9 +22,9 @@ const config = {
|
|||
fontFamily: {
|
||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
},
|
||||
screens: {
|
||||
"3xl": "2200px",
|
||||
},
|
||||
screens: {
|
||||
"3xl": "2200px",
|
||||
},
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
|
@ -32,11 +32,11 @@ const config = {
|
|||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
surface: "var(--surface)",
|
||||
"color-1": "hsl(var(--color-1))",
|
||||
"color-2": "hsl(var(--color-2))",
|
||||
"color-3": "hsl(var(--color-3))",
|
||||
"color-4": "hsl(var(--color-4))",
|
||||
"color-5": "hsl(var(--color-5))",
|
||||
"color-1": "hsl(var(--color-1))",
|
||||
"color-2": "hsl(var(--color-2))",
|
||||
"color-3": "hsl(var(--color-3))",
|
||||
"color-4": "hsl(var(--color-4))",
|
||||
"color-5": "hsl(var(--color-5))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
|
@ -72,10 +72,10 @@ const config = {
|
|||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
rainbow: {
|
||||
"0%": { "background-position": "0%" },
|
||||
"100%": { "background-position": "200%" },
|
||||
},
|
||||
rainbow: {
|
||||
"0%": { "background-position": "0%" },
|
||||
"100%": { "background-position": "200%" },
|
||||
},
|
||||
orbit: {
|
||||
"0%": {
|
||||
transform:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue