Skip to content

Instantly share code, notes, and snippets.

@UdaraJay
Created October 15, 2023 14:43
Show Gist options
  • Save UdaraJay/81f9ef6fa9270fe658802d968095fe8e to your computer and use it in GitHub Desktop.
Save UdaraJay/81f9ef6fa9270fe658802d968095fe8e to your computer and use it in GitHub Desktop.
Animated card stack
import styles from './Apps.module.scss';
import { useEffect, useState } from 'react';
import Link from 'next/link';
const APPS = [
{
title: 'APP',
hero: 'Lorem ipsum dolor sit amet',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do.',
button: 'Button text',
plan: 'standard',
},
{
title: 'APP',
hero: 'Lorem ipsum dolor sit amet',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do.',
button: 'Button text',
plan: 'standard',
},
{
title: 'APP',
hero: 'Lorem ipsum dolor sit amet',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do.',
button: 'Button text',
plan: 'standard',
},
{
title: 'APP',
hero: 'Lorem ipsum dolor sit amet',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do.',
button: 'Button text',
plan: 'standard',
},
{
title: 'APP',
hero: 'Lorem ipsum dolor sit amet',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do.',
button: 'Button text',
plan: 'pro',
},
{
title: 'APP',
hero: 'Lorem ipsum dolor sit amet',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do.',
button: 'Button text',
plan: 'max',
},
{
title: 'APP',
hero: 'Lorem ipsum dolor sit amet',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do.',
button: 'Button text',
plan: 'max',
},
];
export default function Apps() {
const [current, setCurrent] = useState(0);
const [reverse, setReverse] = useState(false);
const [fullWidth, setFullWidth] = useState(
typeof window !== 'undefined' ? window.innerWidth : 1300
);
const fit = Math.floor(fullWidth / 500) - 1;
const updateWindowWidth = () => {
setFullWidth(window.innerWidth);
};
useEffect(() => {
if (typeof window === 'undefined') return;
window.addEventListener('resize', updateWindowWidth);
return () => {
window.removeEventListener('resize', updateWindowWidth);
};
}, []);
const forward = () => {
setReverse(false);
setCurrent((current + 1) % (APPS.length - fit));
};
const backward = () => {
if (current < 1) return;
setReverse(true);
setCurrent((current - 1) % (APPS.length - fit));
};
const renderCards = () => {
const width = 400;
return APPS.map((app, i) => {
let _i = i - current;
let transform = `scale(1)`;
let transitionDelay =
reverse || current == 0 ? 0.05 * (APPS.length - i) : 0.05 * i;
let filter = 'none';
if (i < current) {
_i = 0;
const fullSpace = 15 * current;
const scaleAdd = 0.03 * i;
const scaleStart = 1 - current * 0.03;
const brightness = 0.95 - 0.05 * (current - i);
transform = `scale(${scaleStart + scaleAdd}) translateX(-${
fullSpace - i * 15
}px)`;
filter = `brightness(${brightness})`;
}
const isMobile = fullWidth < 1250;
const margin = isMobile ? 32 : 64;
const normal = _i * (width + 25) + margin;
let left = normal;
let style = {
width: width + 'px',
left: left + 'px',
transform: transform,
filter: filter,
transitionDelay: transitionDelay + 's',
};
if (isMobile) {
style = {
...style,
width: '260px',
left: _i * (260 + 25) + margin + 'px',
};
}
return (
<div
key={`card-${i}`}
className={`${styles.pitch} ${i !== 0 && styles.base}`}
style={style}
>
<div className={styles.header}>
<div className={styles.title}>{app.title}</div>
{app.plan === 'pro' && <div className={styles.pro}>Pro</div>}
{app.plan === 'max' && <div className={styles.max}>Max</div>}
</div>
<div className={styles.bottom}>
<div className={styles.title}>{app.hero}</div>
<div className={styles.des}>{app.description}</div>
<Link href="/waitlist" className={styles.button}>
{app.button}
</Link>
</div>
</div>
);
});
};
return (
<div className={styles.container}>
<div className={styles.apps}>{renderCards()}</div>
<div className={styles.buttons}>
<button onClick={backward} disabled={current < 1}>
<div>Backward button</div>
</button>
<button onClick={forward}>
{current === APPS.length - fit - 1 ? (
<div>Refresh button</div>
) : (
<div>Forward button</div>
)}
</button>
</div>
</div>
);
}
:root {
--max-width: 1100px;
--brochure-margin: 64px;
--background: #fff;
--background-container: rgb(245, 245, 245);
--background-light: rgb(249, 249, 249);
--primary: #161616;
--secondary: #515151;
--tertiary: #7d7d7d;
}
.container {
position: relative;
display: flex;
flex-direction: column;
height: 580px;
}
.buttons {
position: absolute;
display: flex;
bottom: 0px;
right: var(--brochure-margin);
z-index: 2;
button {
background: rgba(255, 255, 255, 0.95);
height: 50px;
width: 100px;
border-radius: 90px;
border: none;
display: flex;
justify-content: center;
align-items: center;
margin-left: 10px;
fill: var(--secondary);
transition: all ease-in-out 120ms;
&:hover {
cursor: pointer;
background: rgba(255, 255, 255, 1);
fill: var(--primary);
}
&:active {
transform: scale(0.96);
}
&:first-child {
width: 50px;
}
&:disabled {
opacity: 0.7;
background: rgba(255, 255, 255, 0.9);
fill: var(--secondary);
}
.icon {
width: 18px;
height: 18px;
}
}
}
.apps {
display: flex;
max-width: 100vw;
padding-bottom: 30px;
margin: 0 auto;
width: 100vw;
max-width: 100vw;
z-index: 2;
position: relative;
height: 100%;
overflow: hidden;
}
.pitch {
position: absolute;
display: flex;
flex-direction: column;
justify-content: space-between;
color: #161616;
margin-top: 4px;
padding: 26px;
border-radius: 22px;
height: 500px;
background: var(--background-container);
transition: all cubic-bezier(0.23, 1, 0.320, 1) 500ms;
background: linear-gradient(20deg, rgba(255, 255, 255, 0.7) 1%, #efefef);
backdrop-filter: blur(5px);
&.base {
.bottom {
.title {
line-height: 1.2;
}
.des {
font-size: 1em;
}
.button {
background: #121212;
&:hover {
background: #444;
}
}
}
}
.header {
display: flex;
align-items: center;
margin-bottom: 52px;
font-weight: 500;
justify-content: space-between;
font-size: 1.1em;
font-weight: 500;
.tag {
background: rgba(0, 0, 0, 0.05);
color: #666;
border-radius: 90px;
padding: 4px 12px;
font-size: 0.85em;
}
.pro {
background: var(--pro);
color: var(--pro-text);
border-radius: 90px;
padding: 4px 12px;
font-size: 0.85em;
}
.max {
background: var(--max);
color: var(--max-text);
border-radius: 90px;
padding: 4px 12px;
font-size: 0.85em;
}
}
.bottom {
.title {
font-weight: 500;
// font-size: 48px;
font-size: 32px;
line-height: 1.2;
font-weight: bold;
margin-bottom: 12px;
font-family: var(--font-porpora);
width: 90%;
}
.des {
max-width: 360px;
font-size: 1em;
line-height: 1.5;
color: var(--secondary);
}
.button {
display: inline-flex;
background: var(--active);
color: var(--active-text);
padding: 8px 16px;
border-radius: 22px;
margin-top: 22px;
font-weight: 500;
font-size: 0.9em;
transition: all ease-in-out 120ms;
&:hover {
cursor: pointer;
filter: brightness(0.9);
}
&:active {
cursor: pointer;
filter: brightness(1.1);
transform: scale(0.98);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment