diff --git a/astro.config.mjs b/astro.config.mjs index 7c7ad34..cecdd04 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -2,7 +2,9 @@ import { defineConfig } from 'astro/config'; import tailwind from '@astrojs/tailwind'; +import react from '@astrojs/react'; + // https://astro.build/config export default defineConfig({ - integrations: [tailwind()], -}); + integrations: [tailwind(), react()], +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 07519de..3a1a3f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,21 +10,26 @@ "dependencies": { "@astrojs/check": "^0.9.4", "@astrojs/cloudflare": "^12.0.1", + "@astrojs/react": "^4.1.0", "@astrojs/tailwind": "^5.1.2", "@fontsource/bricolage-grotesque": "^5.1.0", "@fortawesome/fontawesome-svg-core": "^6.7.1", "@fortawesome/free-brands-svg-icons": "^6.7.1", "@fortawesome/free-solid-svg-icons": "^6.7.1", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", "astro": "^5.0.4", "astro-navbar": "^2.3.7", "autoprefixer": "10.4.14", "free-astro-components": "^1.1.1", "lucide-astro": "^0.460.0", - "motion": "^11.13.1", + "motion": "^11.13.5", "postcss": "8.4.21", "prettier": "^3.3.3", "prettier-plugin-astro": "^0.14.1", "prettier-plugin-tailwindcss": "^0.6.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", "sharp": "^0.33.5", "tailwindcss": "^3.4.15", "typescript": "^5.6.3" @@ -569,6 +574,25 @@ "node": "^18.17.1 || ^20.3.0 || >=22.0.0" } }, + "node_modules/@astrojs/react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.1.0.tgz", + "integrity": "sha512-8F0ncvcCexVeQZMwPouLSFuzCK1KXUIYQ57lW3ZG2p7B5DGAajXGanb/CGF7MMSpX8Z0t9sELQqLHOCV/+78Ig==", + "dependencies": { + "@vitejs/plugin-react": "^4.3.4", + "ultrahtml": "^1.5.3", + "vite": "^6.0.1" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", + "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", + "react": "^17.0.2 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@astrojs/tailwind": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-5.1.2.tgz", @@ -901,6 +925,34 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -2466,6 +2518,22 @@ "@types/node": "*" } }, + "node_modules/@types/react": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.1.tgz", + "integrity": "sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz", + "integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -2476,6 +2544,24 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, "node_modules/@volar/kit": { "version": "2.4.10", "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.10.tgz", @@ -3305,6 +3391,11 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "node_modules/data-uri-to-buffer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", @@ -3687,9 +3778,9 @@ } }, "node_modules/framer-motion": { - "version": "11.13.1", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.13.1.tgz", - "integrity": "sha512-F40tpGTHByhn9h3zdBQPcEro+pSLtzARcocbNqAyfBI+u9S+KZuHH/7O9+z+GEkoF3eqFxfvVw0eBDytohwqmQ==", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.13.5.tgz", + "integrity": "sha512-rArI0zPU9VkpS3Wt0J7dmRxAFUWtzPWoSofNQAP0UO276CmJ+Xlf5xN19GMw3w2QsdrS2sU+0+Q2vtuz4IEZaw==", "dependencies": { "motion-dom": "^11.13.0", "motion-utils": "^11.13.0", @@ -3697,8 +3788,8 @@ }, "peerDependencies": { "@emotion/is-prop-valid": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/is-prop-valid": { @@ -5608,11 +5699,11 @@ } }, "node_modules/motion": { - "version": "11.13.1", - "resolved": "https://registry.npmjs.org/motion/-/motion-11.13.1.tgz", - "integrity": "sha512-64+QpZQv8WJJFn+tEEzX04il9s6ReA6lhKRZaxzD6SunGqoaq5g+AFVfcKWme8N83eytUOpGp7mpfJ9cyZlhAA==", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/motion/-/motion-11.13.5.tgz", + "integrity": "sha512-zmX/dz60w1ZtQB5NP9xYkLcCKwX9gc+pnHp4/mFhD9YW8wUe2ZmT8OPOtrTtq26/huxElSDu3hB7BMTSJa5iIQ==", "dependencies": { - "framer-motion": "^11.13.1", + "framer-motion": "^11.13.5", "tslib": "^2.4.0" }, "peerDependencies": { @@ -6326,6 +6417,33 @@ } ] }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6757,6 +6875,11 @@ "suf-log": "^2.5.3" } }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", diff --git a/package.json b/package.json index 5636482..004c5aa 100644 --- a/package.json +++ b/package.json @@ -13,21 +13,26 @@ "dependencies": { "@astrojs/check": "^0.9.4", "@astrojs/cloudflare": "^12.0.1", + "@astrojs/react": "^4.1.0", "@astrojs/tailwind": "^5.1.2", "@fontsource/bricolage-grotesque": "^5.1.0", "@fortawesome/fontawesome-svg-core": "^6.7.1", "@fortawesome/free-brands-svg-icons": "^6.7.1", "@fortawesome/free-solid-svg-icons": "^6.7.1", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", "astro": "^5.0.4", "astro-navbar": "^2.3.7", "autoprefixer": "10.4.14", "free-astro-components": "^1.1.1", "lucide-astro": "^0.460.0", - "motion": "^11.13.1", + "motion": "^11.13.5", "postcss": "8.4.21", "prettier": "^3.3.3", "prettier-plugin-astro": "^0.14.1", "prettier-plugin-tailwindcss": "^0.6.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", "sharp": "^0.33.5", "tailwindcss": "^3.4.15", "typescript": "^5.6.3" diff --git a/src/components/AnimatedText.tsx b/src/components/AnimatedText.tsx new file mode 100644 index 0000000..6aaab2f --- /dev/null +++ b/src/components/AnimatedText.tsx @@ -0,0 +1,98 @@ +import { motion, useInView, useAnimation } from "motion/react"; +import { useEffect, useRef, type JSX } from "react"; + +type AnimatedTextProps = { + text: string | string[]; + el?: keyof JSX.IntrinsicElements; + className?: string; + once?: boolean; + repeatDelay?: number; + animation?: { + hidden: any; + visible: any; + }; +}; + +const defaultAnimations = { + hidden: { + opacity: 0, + y: 20, + }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.1, + }, + }, +}; + +export const AnimatedText = ({ + text, + el: Wrapper = "p", + className, + once, + repeatDelay, + animation = defaultAnimations, +}: AnimatedTextProps) => { + const controls = useAnimation(); + const textArray = Array.isArray(text) ? text : [text]; + const ref = useRef(null); + const isInView = useInView(ref, { amount: 0.5, once }); + + useEffect(() => { + let timeout: NodeJS.Timeout; + const show = () => { + controls.start("visible"); + if (repeatDelay) { + timeout = setTimeout(async () => { + await controls.start("hidden"); + controls.start("visible"); + }, repeatDelay); + } + }; + + if (isInView) { + show(); + } else { + controls.start("hidden"); + } + + return () => clearTimeout(timeout); + }, [isInView]); + + return ( + + {textArray.join(" ")} + + {textArray.map((line, lineIndex) => ( + + {line.split(" ").map((word, wordIndex) => ( + + {word.split("").map((char, charIndex) => ( + + {char} + + ))} +   + + ))} + + ))} + + + ); +}; diff --git a/src/components/Community.astro b/src/components/Community.astro index f07b822..9cfab3c 100644 --- a/src/components/Community.astro +++ b/src/components/Community.astro @@ -13,7 +13,7 @@ import {
Community Driven diff --git a/src/components/Hero.astro b/src/components/Hero.astro index a3c60d9..011a283 100644 --- a/src/components/Hero.astro +++ b/src/components/Hero.astro @@ -4,7 +4,23 @@ import Description from '../components/Description.astro' import Button from '../components/Button.astro' import { Image } from 'astro:assets' import myImage from '../assets/browser.png' -import { ArrowRight } from 'lucide-astro' +import { ArrowRight } from 'lucide-astro'; +import { motion } from 'framer-motion'; +import { AnimatedText } from './AnimatedText'; + +let titleAnimationCounter = 0; +function getNewAnimationDelay() { + titleAnimationCounter++; + return titleAnimationCounter * 0.15; +} + +function getTitleAnimation() { + return { + initial: { opacity: 0, translateY: 20, filter: 'blur(4px)' }, + animate: { opacity: 1, translateY: 0, filter: 'blur(0px)', transition: { duration: 0.3, delay: getNewAnimationDelay() } }, + }; +} + ---
- Welcome to<br class="hidden md:block" /> a <span class="italic">calmer</span> internet - Beautifully designed, privacy-focused, and packed with features.
We care about your experience, not your data.
+ + <motion.span client:load {...getTitleAnimation()}> + Welcome + </motion.span> + <motion.span client:load {...getTitleAnimation()}> + to + </motion.span> + <br class="hidden md:block" /> + <motion.span client:load {...getTitleAnimation()}> + a + </motion.span> + <motion.span client:load {...getTitleAnimation()} className='italic'> + calmer + </motion.span> + <motion.span client:load {...getTitleAnimation()}> + internet + </motion.span> + + + Beautifully designed, privacy-focused, and packed with features.
We care about your experience, not your data.
+
- - + + + + + +
-Zen browser \ No newline at end of file + + Zen browser + \ No newline at end of file diff --git a/src/components/NavBar.astro b/src/components/NavBar.astro index 777e4b3..27f8b82 100644 --- a/src/components/NavBar.astro +++ b/src/components/NavBar.astro @@ -4,16 +4,16 @@ import Title from '../components/Title.astro' import Description from '../components/Description.astro' import Button from '../components/Button.astro' import { Astronav, MenuItems, MenuIcon, Dropdown, DropdownItems, DropdownSubmenu } from "astro-navbar"; -import { ArrowRight, ChevronDown } from 'lucide-astro' +import { ArrowRight, ChevronDown, Download, DownloadCloud } from 'lucide-astro' import Logo from './Logo.astro'; ---