feat: Add styled-components dependency and implement profile image animations

This commit is contained in:
Mauro Balades
2024-07-20 14:24:11 +02:00
parent bd61604410
commit ca10039f69
8 changed files with 191 additions and 5 deletions

119
package-lock.json generated
View File

@@ -32,6 +32,7 @@
"react-dom": "^18",
"react-hook-form": "^7.52.1",
"react-spring": "^9.7.3",
"styled-components": "^6.1.12",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
@@ -2304,6 +2305,24 @@
"node": ">=6.9.0"
}
},
"node_modules/@emotion/is-prop-valid": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
"integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
"dependencies": {
"@emotion/memoize": "^0.8.1"
}
},
"node_modules/@emotion/memoize": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
},
"node_modules/@emotion/unitless": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -4467,6 +4486,11 @@
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"peer": true
},
"node_modules/@types/stylis": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
"integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="
},
"node_modules/@types/webxr": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.19.tgz",
@@ -5414,6 +5438,14 @@
"node": ">= 6"
}
},
"node_modules/camelize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001640",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz",
@@ -5872,6 +5904,24 @@
"node": ">= 8"
}
},
"node_modules/css-color-keywords": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
"engines": {
"node": ">=4"
}
},
"node_modules/css-to-react-native": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
"dependencies": {
"camelize": "^1.0.0",
"css-color-keywords": "^1.0.0",
"postcss-value-parser": "^4.0.2"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -11578,6 +11628,11 @@
"node": ">=8"
}
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -12051,6 +12106,65 @@
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
"peer": true
},
"node_modules/styled-components": {
"version": "6.1.12",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz",
"integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==",
"dependencies": {
"@emotion/is-prop-valid": "1.2.2",
"@emotion/unitless": "0.8.1",
"@types/stylis": "4.2.5",
"css-to-react-native": "3.2.0",
"csstype": "3.1.3",
"postcss": "8.4.38",
"shallowequal": "1.1.0",
"stylis": "4.3.2",
"tslib": "2.6.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/styled-components"
},
"peerDependencies": {
"react": ">= 16.8.0",
"react-dom": ">= 16.8.0"
}
},
"node_modules/styled-components/node_modules/postcss": {
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/styled-components/node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/styled-jsx": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
@@ -12073,6 +12187,11 @@
}
}
},
"node_modules/stylis": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
"integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg=="
},
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",

View File

@@ -33,6 +33,7 @@
"react-dom": "^18",
"react-hook-form": "^7.52.1",
"react-spring": "^9.7.3",
"styled-components": "^6.1.12",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"

BIN
public/profile-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
public/profile-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/profile-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -19,7 +19,7 @@ export default function Feature({
<h1 className="text-6xl font-bold">{title}</h1>
<p className="text-muted-foreground mt-3">{description}</p>
</div>
<div className={ny(`mt-10 lg:ml-64 lg:mt-0 w-96 h-96 rounded-lg relative overflow-hidden`)} style={{ backgroundColor: color }}>
<div className={ny(`mt-10 lg:ml-64 lg:mt-0 w-96 h-96 rounded-lg relative overflow-hidden transition-all duration-300`)} style={{ backgroundColor: color }}>
{children}
</div>
</div>

View File

@@ -1,8 +1,74 @@
"use client";
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";
const profileColors = [
"#F5ED97",
"#C2E3B7",
"#EEDBF9",
];
const enterAnimation = keyframes`
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.5);
left: 100%;
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
left: 50%;
}
`;
const exitAnimation = keyframes`
from {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
left: 50%;
}
to {
opacity: 0;
transform: translate(-50%, -50%) scale(0.5);
left: 0;
}
`;
const ProfileImage = styled.img<{ enter: boolean }>`
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 75%;
animation: ${({ enter }: any) => enter ? css`${enterAnimation} 0.5s` : css`${exitAnimation} 0.5s`} forwards;
`;
export default function Features() {
const [currentProfileColor, setCurrentProfileColor] = useState(profileColors[0]);
const [profile1Enter, setProfile1Enter] = useState(false);
const [profile2Enter, setProfile2Enter] = useState(false);
const [profile3Enter, setProfile3Enter] = useState(false);
useEffect(() => {
let currentProfile = 0;
setProfile1Enter(true);
setCurrentProfileColor(profileColors[currentProfile]);
const profiles = document.querySelectorAll("#profile-1, #profile-2, #profile-3");
setInterval(() => {
currentProfile = (currentProfile + 1) % profiles.length;
setProfile1Enter(currentProfile === 0);
setProfile2Enter(currentProfile === 1);
setProfile3Enter(currentProfile === 2);
setCurrentProfileColor(profileColors[currentProfile]);
}, 3500);
}, []);
return (
<div>
<TextReveal text="Zen will change the way you browse the web. 🌟" />
@@ -25,10 +91,10 @@ export default function Features() {
<Feature
title="Profiles"
description="Switch between profiles with ease. Create multiple profiles to keep your work and personal browsing separate."
color="#C2E3B7">
<img src="/profiles.png" alt="Profiles" className="absolute left-1/2 w-3/4 top-1/2" style={{
transform: "translate(-50%, -50%)"
}} />
color={currentProfileColor}>
<ProfileImage enter={profile1Enter} src="/profile-1.png" alt="Profiles" id="profile-1" className="absolute left-1/2 w-3/4 top-1/2" />
<ProfileImage enter={profile2Enter} src="/profile-2.png" alt="Profiles" id="profile-2" className="absolute left-1/2 w-3/4 top-1/2" />
<ProfileImage enter={profile3Enter} src="/profile-3.png" alt="Profiles" id="profile-3" className="absolute left-1/2 w-3/4 top-1/2" />
</Feature>
<div className="my-40 w-full flex items-center justify-center flex-col">
<h1 className="text-5xl text-center font-bold w-1/2">Want more?</h1>