diff --git a/package-lock.json b/package-lock.json index acdd482..9a79331 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,10 @@ "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slider": "^1.2.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", + "@supabase/supabase-js": "^2.45.1", "@vercel/postgres": "^0.9.0", "canvas-confetti": "^1.9.3", "class-variance-authority": "^0.7.0", @@ -34,6 +36,7 @@ "react-hook-form": "^7.52.2", "react-markdown": "^9.0.1", "react-spring": "^9.7.4", + "react-sticky-el": "^2.1.0", "styled-components": "^6.1.12", "tailwind-merge": "^2.5.1", "tailwindcss-animate": "^1.0.7", @@ -3522,6 +3525,38 @@ } } }, + "node_modules/@radix-ui/react-slider": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.0.tgz", + "integrity": "sha512-dAHCDA4/ySXROEPaRtaMV5WHL8+JB/DbtyTbJjYkY0RXmKMO2Ln8DFZhywG5/mVQ4WqHDBc8smc14yPXPqZHYA==", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", @@ -4546,6 +4581,73 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@supabase/auth-js": { + "version": "2.64.4", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.64.4.tgz", + "integrity": "sha512-9ITagy4WP4FLl+mke1rchapOH0RQpf++DI+WSG2sO1OFOZ0rW3cwAM0nCrMOxu+Zw4vJ4zObc08uvQrXx590Tg==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.1.tgz", + "integrity": "sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.8.tgz", + "integrity": "sha512-YunjXpoQjQ0a0/7vGAvGZA2dlMABXFdVI/8TuVKtlePxyT71sl6ERl6ay1fmIeZcqxiuFQuZw/LXUuStUG9bbg==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.2.tgz", + "integrity": "sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.6.0.tgz", + "integrity": "sha512-REAxr7myf+3utMkI2oOmZ6sdplMZZ71/2NEIEMBZHL9Fkmm3/JnaOZVSRqvG4LStYj2v5WhCruCzuMn6oD/Drw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.45.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.1.tgz", + "integrity": "sha512-/PVe3lXmalazD8BGMIoI7+ttvT1mLXy13lNcoAPtjP1TDDY83g8csZbVR6l+0/RZtvJxl3LGXfTJT4bjWgC5Nw==", + "dependencies": { + "@supabase/auth-js": "2.64.4", + "@supabase/functions-js": "2.4.1", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.15.8", + "@supabase/realtime-js": "2.10.2", + "@supabase/storage-js": "2.6.0" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -4699,6 +4801,11 @@ "pg-types": "^4.0.1" } }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==" + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -4771,6 +4878,14 @@ "license": "MIT", "peer": true }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -12813,6 +12928,15 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-sticky-el": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-sticky-el/-/react-sticky-el-2.1.0.tgz", + "integrity": "sha512-oo+a2GedF4QMfCfm20e9gD+RuuQp/ngvwGMUXAXpST+h4WnmKhuv7x6MQ4X/e3AHiLYgE0zDyJo1Pzo8m51KpA==", + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -14476,8 +14600,7 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/trim-lines": { "version": "3.0.1", @@ -15047,8 +15170,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/whatwg-fetch": { "version": "3.6.20", @@ -15062,7 +15184,6 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" diff --git a/package.json b/package.json index 4da108b..921dc8b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slider": "^1.2.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", "@supabase/supabase-js": "^2.45.1", @@ -39,6 +40,7 @@ "react-hook-form": "^7.52.2", "react-markdown": "^9.0.1", "react-spring": "^9.7.4", + "react-sticky-el": "^2.1.0", "styled-components": "^6.1.12", "tailwind-merge": "^2.5.1", "tailwindcss-animate": "^1.0.7", diff --git a/public/compact-mode.png b/public/compact-mode.png index 0dc76a9..10f8da7 100644 Binary files a/public/compact-mode.png and b/public/compact-mode.png differ diff --git a/public/sidebar.png b/public/sidebar.png index e6d2158..b0e1c07 100644 Binary files a/public/sidebar.png and b/public/sidebar.png differ diff --git a/public/split-view.png b/public/split-view.png index 7602cdc..8fcccc7 100644 Binary files a/public/split-view.png and b/public/split-view.png differ diff --git a/public/workspaces.png b/public/workspaces.png index 1b77ae6..d2dab64 100644 Binary files a/public/workspaces.png and b/public/workspaces.png differ diff --git a/src/components/create-theme.tsx b/src/components/create-theme.tsx index e1f1843..663e666 100644 --- a/src/components/create-theme.tsx +++ b/src/components/create-theme.tsx @@ -8,7 +8,7 @@ import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } import { DialogFooter, DialogHeader } from "./ui/dialog"; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "./ui/sheet"; -const COLORS = [ +export const COLORS = [ "#ffaa40", "#9c40ff", "#ff40aa", diff --git a/src/components/feature.tsx b/src/components/feature.tsx deleted file mode 100644 index de4828d..0000000 --- a/src/components/feature.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { ny } from "@/lib/utils"; - -export default function Feature({ - title, - description, - children, - color, -}: { - title: string; - description: string; - children: React.ReactNode; - color: string; -}) { - return ( -
-
-

{title}

-

{description}

-
-
- {children} -
-
- ); -} - -export function FeatureCard({ - title, - description, - todo = false, -}: { - title: string; - description: string; - todo?: boolean; -}) { - return ( -
-
- {title} -
-
- {description} -
- {todo && ( -
- Coming soon -
- )} -
- ); -} - diff --git a/src/components/features.tsx b/src/components/features.tsx index 5316f2c..b5df379 100644 --- a/src/components/features.tsx +++ b/src/components/features.tsx @@ -1,36 +1,39 @@ -import { useEffect, useState } from "react"; -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, - TableCaption, - TableCell, - TableFooter, - TableHead, - TableHeader, - TableRow, -} from "./ui/table"; +"use client"; +import Sticky from 'react-sticky-el'; import { CheckIcon, + ChevronLeft, + ChevronRight, EyeIcon, EyeOffIcon, + Github, + HomeIcon, RabbitIcon, + ShieldAlertIcon, + ShieldCheck, + SidebarCloseIcon, + SidebarIcon, + SpaceIcon, + SplitSquareHorizontal, + SplitSquareVertical, XIcon, } from "lucide-react"; import { + Cross1Icon, EyeClosedIcon, + HeartFilledIcon, LockClosedIcon, QuestionMarkIcon, + ReloadIcon, + UpdateIcon, } from "@radix-ui/react-icons"; -import ShineBorder from "./ui/shine-border"; -import SparklesText from "./ui/sparkles-text"; import Image from "next/image"; -import OrbitingCircles from "./ui/orbiting-circles"; -import { ny } from "@/lib/utils"; +import { Button } from './ui/button'; +import { COLORS } from './create-theme'; +import { Slider } from './ui/slider'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table'; +import React, { useState } from 'react'; +import { ny } from '@/lib/utils'; function Checkmark() { return ( @@ -52,318 +55,345 @@ function Question() { export default function Features() { const [feature, setFeature] = useState(0); - useEffect(() => { - setInterval(() => { + React.useEffect(() => { + const interval = setInterval(() => { setFeature((feature) => (feature + 1) % 3); - }, 9000); - }, []); + }, 3000); + return () => clearInterval(interval); + }); return ( -
-
-
-

The only limit is your

- -

- Zen's theme store offers a wide range of themes to customize your browsing experience. Try them out today! -

-

- We also offer a wide range of themes and color schemes to customize your browsing experience. -

- -
-
- - Zen Logo - - - {/* Inner Circles */} - - Zen Logo - - - Zen Logo - - - {/* Outer Circles (reverse) */} - - Zen Logo - - - Zen Logo - -
-
- {/**/} -

What does Zen offer to ?

-

- Discover how Zen Browser can transform your web experience with powerful features that keep you ahead. - Here are
some of the features that Zen offers.

-
-
-
-

Split views

-

- Multitask effortlessly by splitting your browser into multiple views, so you can browse several sites at once. -

- +
+
+
+
+
-
-

Workspaces

-

- Stay organized and clutter-free by creating workspaces tailored to your browsing needs. +

+

+ Goodbye bad performance +

+

+ We are constantly tweak firefox's engine and settings to make it + faster than ever. Learn more

-
-
-
-

Profile switching

-

- Seamlessly switch between work and personal profiles for a focused browsing experience. -

- +
+
+
-
-

Side web panels

-

- Access favorite sites and services instantly, without leaving your current page. +

+

+ Privacy first +

+

+ We don't track you. We don't sell your data. We don't even know + who you are. Learn more

-
-
- -
- - - - - - How Zen compares to other browsers - - - - - Zen - - - - Floorp - - - - LibreWolf - - - - - - - Fine-grained security like sandboxing - - - - - - - - - - - - - - Optimized for peak performance - - - - - - - - - - - - - - Based on the latest Firefox - - - - - - - - - - - - - - Customizable with cutting-edge features - - - - - - - - - - - - -
+
+
+ +
+
+

+ Secure by default +

+

+ We are always using the latest security features from firefox to + keep you safe. Learn more +

+
- -
-
-
-
-

Built for - -

-

- Zen is engineered for speed, consistently outperforming competitors with every release, ensuring a faster browsing experience. -

- +
+ +

+ User experience comes first +

+

+ We are always looking for ways to make your experience better. Always looking for feedback and suggestions! +

+
+
+
+
+
+
+
+
+

+ Customization is key +

+

+ We are always looking for ways to make your experience better. With stackable themes that can be mixed and matched, you can create a browser that is truly yours. Learn more +

+
+ +
+
+ {COLORS.map((color) => ( +
+ ))} +
+
+ +
+ + + Checkout our themes store + + + +
+
+
+
+
+
+
+
+

+ Compact mode is here! +

+

+ With a new compact mode, you can save space and focus on what matters. +

+
+ Killer feature +
+
+ +
+ +
+
+
+
+
+ What makes Zen Browser different? +
+ +
+
+ + + + + + How Zen compares to other browsers + + + + + Zen + + + + Floorp + + + + LibreWolf + + + + + + + Fine-grained security like sandboxing + + + + + + + + + + + + + + Optimized for peak performance + + + + + + + + + + + + + + Based on the latest Firefox + + + + + + + + + + + + + + Customizable with cutting-edge features + + + + + + + + + + + + +
+
+
+
+
+
+ +

+ Open source +

+
+

+ Zen Browser is open source and always will be. You can check out + the source code on our Github! +

+
+
+
+ +

+ Updated +

+
+

+ We are always working on new features and improvements. You can + expect regular updates to keep your browser up to date. +

+
+
+
+ +

+ Community +

+
+

+ Zen Browser is built by a community of passionate developers and + designers. You can join us on our Discord! +

+
+
+
+
+
+ +

+ Your Data +

+
+

+ We are always looking for ways to improve your privacy. Zen Browser + comes with built-in privacy features to keep you safe. +

+
+
+
+ +

+ Improvements +

+
+

+ We are always looking for ways to make Zen Browser better. You can + expect regular updates with new features and improvements. +

+
+
+
+
+
+ + +
-
-
-

Privacy is - -

-

- Zen strikes the perfect balance between privacy and usability, allowing you to browse without compromising your data. +

+
setFeature(0)}> +

+ + Split view +

+

+ Split your browser into two windows to work more efficiently.

-
-
-
-
-

Security is - -

-

- Zen incorporates advanced security technologies that outshine other Firefox-based browsers, keeping you safe online. +

setFeature(1)}> +

+ + Workspaces +

+

+ Organize your tabs into workspaces to keep things tidy. +

+
+
setFeature(2)}> +

+ + Sidebar +

+

+ Keep your favorite websites at your fingertips with the sidebar.

-
- -
-
-
-

Introducing

- -

- Zen{'’'}s Compact Mode offers a streamlined browsing experience that maximizes your screen space, perfect for smaller screens. -

- -
- -
-
-

Want more?

-

- Zen Browser is packed with features designed to revolutionize your browsing. - Download it today and experience a new way to explore the web. -

-
- - - - - - -
- - -
); diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index ae22d38..5eb4ad8 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -17,6 +17,7 @@ import Logo from "./logo" import { ModeToggle } from "./mode-toggle" import { MobileNav } from "./mobile-nav" import { HeartIcon } from "lucide-react" +import { HeartFilledIcon } from "@radix-ui/react-icons" export const components: { title: string; href: string; description: string }[] = [ { @@ -92,7 +93,7 @@ export function Navigation() { - + Donate diff --git a/src/components/ui/slider.tsx b/src/components/ui/slider.tsx new file mode 100644 index 0000000..d3d6189 --- /dev/null +++ b/src/components/ui/slider.tsx @@ -0,0 +1,136 @@ +'use client' + +import * as React from 'react' +import * as SliderPrimitive from '@radix-ui/react-slider' + +import { ny } from '@/lib/utils' + +interface SliderProps extends React.ComponentPropsWithoutRef { + showSteps?: 'none' | 'half' | 'full' + formatLabel?: (value: number) => string + formatLabelSide?: string +} + +const Slider = React.forwardRef< + React.ElementRef, + SliderProps +>(({ className, showSteps = 'none', formatLabel, formatLabelSide = 'top', ...props }, ref) => { + const { min = 0, max = 100, step = 1, orientation = 'horizontal', value, defaultValue, onValueChange } = props + const [hoveredThumbIndex, setHoveredThumbIndex] = React.useState(false) + const numberOfSteps = Math.floor((max - min) / step) + const stepLines = Array.from({ length: numberOfSteps }, (_, index) => index * step + min) + + const initialValue = Array.isArray(value) ? value : (Array.isArray(defaultValue) ? defaultValue : [min, max]) + const [localValues, setLocalValues] = React.useState(initialValue) + + React.useEffect(() => { + if (!isEqual(value, localValues)) + setLocalValues(Array.isArray(value) ? value : (Array.isArray(defaultValue) ? defaultValue : [min, max])) + }, [min, max, value]) + + const handleValueChange = (newValues: number[]) => { + setLocalValues(newValues) + if (onValueChange) + onValueChange(newValues) + } + + function isEqual(array1: number[] | undefined, array2: number[] | undefined) { + array1 = array1 ?? [] + array2 = array2 ?? [] + + if (array1.length !== array2.length) + return false + + for (let i = 0; i < array1.length; i++) { + if (array1[i] !== array2[i]) + return false + } + + return true + } + + return ( + handleValueChange(value)} + {...props} + onFocus={() => setHoveredThumbIndex(true)} + onBlur={() => setHoveredThumbIndex(false)} + > + + + {showSteps !== undefined && showSteps !== 'none' && stepLines.map((value, index) => { + if (value === min || value === max) + return null + + const positionPercentage = ((value - min) / (max - min)) * 100 + const adjustedPosition = 50 + (positionPercentage - 50) * 0.96 + return ( +
+ ) + })} + + + {localValues.map((numberStep, index) => ( + + {hoveredThumbIndex && formatLabel && ( +
+ {formatLabel(numberStep)} +
+ )} +
+ ))} + + ) +}) + +Slider.displayName = SliderPrimitive.Root.displayName + +export { Slider }